虽然我从来没有需要这个,但让我感到震惊的是在Python中创建一个不可变对象可能会有些棘手。您不能只覆盖__setattr__
,因为您甚至无法在__init__
中设置属性。对元组进行子类化是一个有效的技巧:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
但是,您可以通过a
和b
访问self[0]
和self[1]
变量,这很烦人。
纯Python中有可能吗?如果没有,我将如何使用C扩展名?
(仅适用于Python 3的答案可以接受)。
更新:
所以子类化元组是在纯Python中实现它的方法,除了通过[0]
,[1]
等访问数据的额外可能性之外,它运行良好。所以,要完成这个问题所有这些缺少的是如何在C中“正确”地做到这一点,我怀疑这很简单,只是没有实现任何geititem
或setattribute
等等。但我不是自己做,而是提供赏金为此,因为我很懒。 :)
答案 0 :(得分:100)
我刚想到的另一个解决方案:获得与原始代码相同的行为的最简单方法是
Immutable = collections.namedtuple("Immutable", ["a", "b"])
它没有解决可以通过[0]
等访问属性的问题,但至少它要短得多,并且提供了与pickle
和copy
兼容的额外优势。
namedtuple
创建的类型类似于我在this answer中描述的类型,即从tuple
派生并使用__slots__
。它在Python 2.6或更高版本中可用。
答案 1 :(得分:71)
最简单的方法是使用__slots__
:
class A(object):
__slots__ = []
A
的实例现在是不可变的,因为您无法在它们上设置任何属性。
如果您希望类实例包含数据,您可以将其与从tuple
派生:
from operator import itemgetter
class Point(tuple):
__slots__ = []
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
x = property(itemgetter(0))
y = property(itemgetter(1))
p = Point(2, 3)
p.x
# 2
p.y
# 3
修改:如果您想摆脱索引,可以覆盖__getitem__()
:
class Point(tuple):
__slots__ = []
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
@property
def x(self):
return tuple.__getitem__(self, 0)
@property
def y(self):
return tuple.__getitem__(self, 1)
def __getitem__(self, item):
raise TypeError
请注意,在这种情况下,您无法使用operator.itemgetter
作为属性,因为这将依赖于Point.__getitem__()
而不是tuple.__getitem__()
。此外,这不会阻止使用tuple.__getitem__(p, 0)
,但我很难想象这应该如何构成一个问题。
我不认为创建不可变对象的“正确”方法是编写C扩展。 Python通常依赖于库实现者和库用户consenting adults,而不是真正强制执行接口,应该在文档中明确说明接口。这就是为什么我不考虑通过调用__setattr__()
问题绕过被覆盖的object.__setattr__()
的可能性。如果有人这样做,那将是她自己的风险。
答案 2 :(得分:49)
..如何在C中“正确”地做到这一点。
您可以使用Cython为Python创建扩展类型:
cdef class Immutable:
cdef readonly object a, b
cdef object __weakref__ # enable weak referencing support
def __init__(self, a, b):
self.a, self.b = a, b
它适用于Python 2.x和3.
# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable
o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2
try: o.a = 3
except AttributeError:
pass
else:
assert 0, 'attribute must be readonly'
try: o[1]
except TypeError:
pass
else:
assert 0, 'indexing must not be supported'
try: o.c = 1
except AttributeError:
pass
else:
assert 0, 'no new attributes are allowed'
o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []
o.b.append(3) # attribute may contain mutable object
assert o.b == [3]
try: o.c
except AttributeError:
pass
else:
assert 0, 'no c attribute'
o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3
try: del o.b
except AttributeError:
pass
else:
assert 0, "can't delete attribute"
d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']
o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3
try: object.__setattr__(o, 'a', 1)
except AttributeError:
pass
else:
assert 0, 'attributes are readonly'
try: object.__setattr__(o, 'c', 1)
except AttributeError:
pass
else:
assert 0, 'no new attributes'
try: Immutable(1,c=3)
except TypeError:
pass
else:
assert 0, 'accept only a,b keywords'
for kwd in [dict(a=1), dict(b=2)]:
try: Immutable(**kwd)
except TypeError:
pass
else:
assert 0, 'Immutable requires exactly 2 arguments'
如果您不介意索引支持,那么collections.namedtuple
建议的@Sven Marnach是可取的:
Immutable = collections.namedtuple("Immutable", "a b")
答案 3 :(得分:38)
另一个想法是完全禁止__setattr__
并在构造函数中使用object.__setattr__
:
class Point(object):
def __init__(self, x, y):
object.__setattr__(self, "x", x)
object.__setattr__(self, "y", y)
def __setattr__(self, *args):
raise TypeError
def __delattr__(self, *args):
raise TypeError
当然,您可以使用object.__setattr__(p, "x", 3)
修改Point
实例p
,但您的原始实施会遇到同样的问题(在{{1}上尝试tuple.__setattr__(i, "x", 42)
}实例)。
您可以在原始实现中应用相同的技巧:摆脱Immutable
,并在属性函数中使用__getitem__()
。
答案 4 :(得分:18)
您可以创建@immutable
装饰器,覆盖__setattr__
并将__slots__
更改为空列表,然后装饰__init__
用它的方法。
编辑:正如OP指出的那样,更改__slots__
属性只会阻止创建新属性,而不是修改。
Edit2:这是一个实现:
Edit3:使用__slots__
打破此代码,因为如果停止创建对象的__dict__
。我正在寻找替代方案。
class immutable(object):
def __init__(self, immutable_params):
self.immutable_params = immutable_params
def __call__(self, new):
params = self.immutable_params
def __set_if_unset__(self, name, value):
if name in self.__dict__:
raise Exception("Attribute %s has already been set" % name)
if not name in params:
raise Exception("Cannot create atribute %s" % name)
self.__dict__[name] = value;
def __new__(cls, *args, **kws):
cls.__setattr__ = __set_if_unset__
return super(cls.__class__, cls).__new__(cls, *args, **kws)
return __new__
class Point(object):
@immutable(['x', 'y'])
def __new__(): pass
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.x = 3 # Exception: Attribute x has already been set
p.z = 4 # Exception: Cannot create atribute z
答案 5 :(得分:10)
除非使用元组或命名元组,否则我认为完全不可能。无论如何,如果您覆盖__setattr__()
,用户可以通过直接调用object.__setattr__()
来绕过它。任何依赖于__setattr__
的解决方案都不会起作用。
以下是关于不使用某种元组的最接近的内容:
class Immutable:
__slots__ = ['a', 'b']
def __init__(self, a, b):
object.__setattr__(self, 'a', a)
object.__setattr__(self, 'b', b)
def __setattr__(self, *ignored):
raise NotImplementedError
__delattr__ = __setattr__
但如果你足够努力就会中断:
>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2
但是Sven对namedtuple
的使用确实是不可改变的。
<强>更新强>
由于问题已经更新以询问如何在C中正确地执行此操作,以下是我在Cython中如何正确执行此操作的答案:
首先immutable.pyx
:
cdef class Immutable:
cdef object _a, _b
def __init__(self, a, b):
self._a = a
self._b = b
property a:
def __get__(self):
return self._a
property b:
def __get__(self):
return self._b
def __repr__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
和setup.py
来编译它(使用命令setup.py build_ext --inplace
:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("immutable", ["immutable.pyx"])]
setup(
name = 'Immutable object',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)
然后尝试一下:
>>> from immutable import Immutable
>>> p = Immutable(2, 3)
>>> p
<Immutable 2, 3>
>>> p.a = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> object.__setattr__(p, 'a', 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> p.a, p.b
(2, 3)
>>>
答案 6 :(得分:4)
除了优秀的其他答案,我还想为python 3.4(或者3.3)添加一个方法。这个答案建立在这个问题的几个前面的答案之上。
在python 3.4中,您可以使用属性 without setters 来创建无法修改的类成员。 (在早期版本中,可以分配给没有setter的属性。)
class A:
__slots__=['_A__a']
def __init__(self, aValue):
self.__a=aValue
@property
def a(self):
return self.__a
你可以像这样使用它:
instance=A("constant")
print (instance.a)
将打印"constant"
但是调用instance.a=10
会导致:
AttributeError: can't set attribute
解释:没有setter的属性是python 3.4的最新特性(我认为3.3)。如果您尝试分配给此类属性,则会引发错误。
使用广告位我将membervariables限制为__A_a
(__a
)。
问题:仍然可以分配到_A__a
(instance._A__a=2
)。但是如果你分配给一个私有变量,这是你自己的错......
This answer不鼓励使用__slots__
。使用其他方法来防止属性创建可能是首选。
答案 7 :(得分:4)
这是一个优雅的解决方案:
class Immutable(object):
def __setattr__(self, key, value):
if not hasattr(self, key):
super().__setattr__(key, value)
else:
raise RuntimeError("Can't modify immutable object's attribute: {}".format(key))
从此类继承,在构造函数中初始化字段,一切就绪。
答案 8 :(得分:3)
我通过覆盖__setattr__
创建了不可变类,如果调用者是__init__
则允许该集:
import inspect
class Immutable(object):
def __setattr__(self, name, value):
if inspect.stack()[2][3] != "__init__":
raise Exception("Can't mutate an Immutable: self.%s = %r" % (name, value))
object.__setattr__(self, name, value)
这还不够,因为它允许任何人的___init__
更改对象,但你明白了。
答案 9 :(得分:3)
如果您对具有行为的对象感兴趣,那么namedtuple 几乎您的解决方案。
如namedtuple documentation的底部所述,您可以从namedtuple派生自己的类;然后,您可以添加所需的行为。
例如(代码直接取自documentation):
class Point(namedtuple('Point', 'x y')):
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
for p in Point(3, 4), Point(14, 5/7):
print(p)
这将导致:
Point: x= 3.000 y= 4.000 hypot= 5.000
Point: x=14.000 y= 0.714 hypot=14.018
这种方法适用于Python 3和Python 2.7(也在IronPython上测试过) 唯一的缺点是继承树有点奇怪;但这不是你经常玩的东西。
答案 10 :(得分:2)
这种方式不会阻止object.__setattr__
工作,但我仍然觉得它很有用:
class A(object):
def __new__(cls, children, *args, **kwargs):
self = super(A, cls).__new__(cls)
self._frozen = False # allow mutation from here to end of __init__
# other stuff you need to do in __new__ goes here
return self
def __init__(self, *args, **kwargs):
super(A, self).__init__()
self._frozen = True # prevent future mutation
def __setattr__(self, name, value):
# need to special case setting _frozen.
if name != '_frozen' and self._frozen:
raise TypeError('Instances are immutable.')
else:
super(A, self).__setattr__(name, value)
def __delattr__(self, name):
if self._frozen:
raise TypeError('Instances are immutable.')
else:
super(A, self).__delattr__(name)
根据用例,您可能需要覆盖更多内容(例如__setitem__
)。
答案 11 :(得分:2)
我不久前需要这个,并决定为它制作一个Python包。最初的版本现在在PyPI上:
$ pip install immutable
使用:
>>> from immutable import ImmutableFactory
>>> MyImmutable = ImmitableFactory.create(prop1=1, prop2=2, prop3=3)
>>> MyImmutable.prop1
1
此处的完整文档:https://github.com/theengineear/immutable
希望它有所帮助,它包含了一个已经讨论过的命名元组,但使实例化变得更加简单。
答案 12 :(得分:2)
对于Python 3.7+,您可以将 Data Class 与frozen=True
option一起使用,这是一种非常Python化且可维护的方式来完成您想要的事情。
它看起来像这样:
from dataclasses import dataclass
@dataclass(frozen=True)
class Immutable:
a: Any
b: Any
由于数据类的字段需要类型提示,因此我使用了Any from the typing
module。
在Python 3.7之前,经常看到namedtuple被用作不可变对象。在许多方面可能很棘手,其中之一是命名元组之间的__eq__
方法不考虑对象的类。例如:
from collections import namedtuple
ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])
obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)
obj1 == obj2 # will be True
如您所见,即使obj1
和obj2
的类型不同,即使它们的字段名称不同,obj1 == obj2
仍给出True
。这是因为使用的__eq__
方法是元组的方法,该方法仅比较给定位置的字段的值。这可能是一个巨大的错误源,特别是如果您要对这些类进行子类化。
答案 13 :(得分:2)
从Python 3.7开始,您可以在类中使用@dataclass
decorator,它将像结构一样是不可变的!但是,它可能会也可能不会向您的类添加__hash__()
方法。引用:
哈希()由内置的hash()使用,并且在将对象添加到诸如字典和集合之类的哈希集合中时使用。带有 hash ()表示该类的实例是不可变的。可变性是一个复杂的属性,取决于程序员的意图, eq ()的存在和行为以及dataclass()装饰器中的eq和冻结标志的值。
默认情况下,除非安全,否则dataclass()不会隐式添加 hash ()方法。它既不会添加也不会更改现有的显式定义的 hash ()方法。如 hash ()文档中所述,设置类属性 hash = None对Python具有特定的含义。
如果未明确定义 hash (),或者将其设置为None,则dataclass()可能会添加隐式的 hash ()方法。尽管不建议这样做,但是您可以强制dataclass()使用unsafe_hash = True创建 hash ()方法。如果您的类在逻辑上是不可变的,但仍然可以进行突变,则可能是这种情况。这是一个特殊的用例,应仔细考虑。
这是上面链接的文档中的示例:
type buscase
baseMVA::Float64
bus::Array{Float64,13}
gen::Array{Float64,21}
branch::Array{Float64,13}
end
mpc=buscase(100.00,
[1 2 0 0 0 0 1 1 0 230 1 1.1 0.9;
2 1 300 98.61 0 0 1 1 0 230 1 1.1 0.9;
3 2 300 98.61 0 0 1 1 0 230 1 1.1 0.9;
4 3 400 131.47 0 0 1 1 0 230 1 1.1 0.9;
5 2 0 0 0 0 1 1 0 230 1 1.1 0.9;],
[1 40 0 30 -30 1 100 1 40 0 0 0 0 0 0 0 0 0 0 0 0;
1 170 0 127.5 -127.5 1 100 1 170 0 0 0 0 0 0 0 0 0 0 0 0;
3 323.49 0 390 -390 1 100 1 520 0 0 0 0 0 0 0 0 0 0 0 0;
4 0 0 150 -150 1 100 1 200 0 0 0 0 0 0 0 0 0 0 0 0;
5 466.51 0 450 -450 1 100 1 600 0 0 0 0 0 0 0 0 0 0 0 0;],
[1 2 0.00281 0.0281 0.00712 400 400 400 0 0 1 -360 360;
1 4 0.00304 0.0304 0.00658 0 0 0 0 0 1 -360 360;
1 5 0.00064 0.0064 0.03126 0 0 0 0 0 1 -360 360;
2 3 0.00108 0.0108 0.01852 0 0 0 0 0 1 -360 360;
3 4 0.00297 0.0297 0.00674 0 0 0 0 0 1 -360 360;
4 5 0.00297 0.0297 0.00674 240 240 240 0 0 1 -360 360;]
)
答案 14 :(得分:1)
所以,我正在编写python 3:
I)在数据类修饰器的帮助下并设置Frozen = True。 我们可以在python中创建不可变的对象。
为此,需要从数据类lib中导入数据类,并需要设置Frozen = True
例如。
从数据类导入数据类
@dataclass(frozen=True)
class Location:
name: str
longitude: float = 0.0
latitude: float = 0.0
o / p:
l = Location(“ Delhi”,112.345,234.788) 名称 德里 经度 112.345 纬度 234.788 l.name =“ Kolkata”数据类。FrozenInstanceError:无法分配给字段“名称”
答案 15 :(得分:1)
您可以覆盖 setattr ,并仍然使用 init 设置变量。您将使用超类 setattr 。这是代码。
class Immutable: __slots__ = ('a','b') def __init__(self, a , b): super().__setattr__('a',a) super().__setattr__('b',b) def __str__(self): return "".format(self.a, self.b) def __setattr__(self, *ignored): raise NotImplementedError def __delattr__(self, *ignored): raise NotImplementedError
答案 16 :(得分:1)
第三方attr
模块提供this functionality。
编辑:python 3.7已将此想法用于@dataclass
的stdlib。
$ pip install attrs
$ python
>>> @attr.s(frozen=True)
... class C(object):
... x = attr.ib()
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
...
attr.exceptions.FrozenInstanceError: can't set attribute
根据文档, attr
通过覆盖__setattr__
来实现冻结类,并且在每个实例化时间都会产生轻微的性能影响。
如果您习惯使用类作为数据类型,attr
可能特别有用,因为它会为您处理样板(但不会产生任何魔法)。特别是,它会为您编写九种dunder(__X__)方法(除非您关闭其中任何一种),包括repr,init,hash和所有比较函数。
attr
还提供helper for __slots__
。
答案 17 :(得分:1)
在Immutable
方法完成执行后,继承自以下__init__
类的类与其实例一样是不可变的。正如其他人所指出的那样,因为它是纯粹的python,所以没有什么可以阻止某人使用基类object
和type
中的变异特殊方法,但这足以阻止任何人改变类/实例事故。
它通过使用元类劫持类创建过程来工作。
"""Subclasses of class Immutable are immutable after their __init__ has run, in
the sense that all special methods with mutation semantics (in-place operators,
setattr, etc.) are forbidden.
"""
# Enumerate the mutating special methods
mutation_methods = set()
# Arithmetic methods with in-place operations
iarithmetic = '''add sub mul div mod divmod pow neg pos abs bool invert lshift
rshift and xor or floordiv truediv matmul'''.split()
for op in iarithmetic:
mutation_methods.add('__i%s__' % op)
# Operations on instance components (attributes, items, slices)
for verb in ['set', 'del']:
for component in '''attr item slice'''.split():
mutation_methods.add('__%s%s__' % (verb, component))
# Operations on properties
mutation_methods.update(['__set__', '__delete__'])
def checked_call(_self, name, method, *args, **kwargs):
"""Calls special method method(*args, **kw) on self if mutable."""
self = args[0] if isinstance(_self, object) else _self
if not getattr(self, '__mutable__', True):
# self told us it's immutable, so raise an error
cname= (self if isinstance(self, type) else self.__class__).__name__
raise TypeError('%s is immutable, %s disallowed' % (cname, name))
return method(*args, **kwargs)
def method_wrapper(_self, name):
"Wrap a special method to check for mutability."
method = getattr(_self, name)
def wrapper(*args, **kwargs):
return checked_call(_self, name, method, *args, **kwargs)
wrapper.__name__ = name
wrapper.__doc__ = method.__doc__
return wrapper
def wrap_mutating_methods(_self):
"Place the wrapper methods on mutative special methods of _self"
for name in mutation_methods:
if hasattr(_self, name):
method = method_wrapper(_self, name)
type.__setattr__(_self, name, method)
def set_mutability(self, ismutable):
"Set __mutable__ by using the unprotected __setattr__"
b = _MetaImmutable if isinstance(self, type) else Immutable
super(b, self).__setattr__('__mutable__', ismutable)
class _MetaImmutable(type):
'''The metaclass of Immutable. Wraps __init__ methods via __call__.'''
def __init__(cls, *args, **kwargs):
# Make class mutable for wrapping special methods
set_mutability(cls, True)
wrap_mutating_methods(cls)
# Disable mutability
set_mutability(cls, False)
def __call__(cls, *args, **kwargs):
'''Make an immutable instance of cls'''
self = cls.__new__(cls)
# Make the instance mutable for initialization
set_mutability(self, True)
# Execute cls's custom initialization on this instance
self.__init__(*args, **kwargs)
# Disable mutability
set_mutability(self, False)
return self
# Given a class T(metaclass=_MetaImmutable), mutative special methods which
# already exist on _MetaImmutable (a basic type) cannot be over-ridden
# programmatically during _MetaImmutable's instantiation of T, because the
# first place python looks for a method on an object is on the object's
# __class__, and T.__class__ is _MetaImmutable. The two extant special
# methods on a basic type are __setattr__ and __delattr__, so those have to
# be explicitly overridden here.
def __setattr__(cls, name, value):
checked_call(cls, '__setattr__', type.__setattr__, cls, name, value)
def __delattr__(cls, name, value):
checked_call(cls, '__delattr__', type.__delattr__, cls, name, value)
class Immutable(object):
"""Inherit from this class to make an immutable object.
__init__ methods of subclasses are executed by _MetaImmutable.__call__,
which enables mutability for the duration.
"""
__metaclass__ = _MetaImmutable
class T(int, Immutable): # Checks it works with multiple inheritance, too.
"Class for testing immutability semantics"
def __init__(self, b):
self.b = b
@classmethod
def class_mutation(cls):
cls.a = 5
def instance_mutation(self):
self.c = 1
def __iadd__(self, o):
pass
def not_so_special_mutation(self):
self +=1
def immutabilityTest(f, name):
"Call f, which should try to mutate class T or T instance."
try:
f()
except TypeError, e:
assert 'T is immutable, %s disallowed' % name in e.args
else:
raise RuntimeError('Immutability failed!')
immutabilityTest(T.class_mutation, '__setattr__')
immutabilityTest(T(6).instance_mutation, '__setattr__')
immutabilityTest(T(6).not_so_special_mutation, '__iadd__')
答案 18 :(得分:0)
您可以在init的最终语句中覆盖setAttr。你可以建造但不能改变。显然你仍然可以通过usint对象覆盖。 setAttr 但实际上大多数语言都有某种形式的反射,所以不可变性总是一个漏洞的抽象。不变性更多的是防止客户意外违反对象的合同。我用:
=============================
提供的原始解决方案不正确,使用here
中的解决方案根据评论进行了更新原始解决方案以有趣的方式出错,因此它包含在底部。
===============================
class ImmutablePair(object):
__initialised = False # a class level variable that should always stay false.
def __init__(self, a, b):
try :
self.a = a
self.b = b
finally:
self.__initialised = True #an instance level variable
def __setattr__(self, key, value):
if self.__initialised:
self._raise_error()
else :
super(ImmutablePair, self).__setattr__(key, value)
def _raise_error(self, *args, **kw):
raise NotImplementedError("Attempted To Modify Immutable Object")
if __name__ == "__main__":
immutable_object = ImmutablePair(1,2)
print immutable_object.a
print immutable_object.b
try :
immutable_object.a = 3
except Exception as e:
print e
print immutable_object.a
print immutable_object.b
输出:
1
2
Attempted To Modify Immutable Object
1
2
======================================
原始实施:
在评论中正确地指出,这实际上并不起作用,因为它会阻止创建多个对象,因为你重写了类setattr方法,这意味着第二个不能创建为self。 a =将在第二次初始化时失败。
class ImmutablePair(object):
def __init__(self, a, b):
self.a = a
self.b = b
ImmutablePair.__setattr__ = self._raise_error
def _raise_error(self, *args, **kw):
raise NotImplementedError("Attempted To Modify Immutable Object")
答案 19 :(得分:0)
这里没有真正包含的一件事是完全不变性......不仅仅是父对象,还有所有孩子。例如,元组/ frozensets可能是不可变的,但它所属的对象可能不是。这是一个很小的(不完整的)版本,可以很好地执行不变性:
li a {
text-decoration:none;
}
li a:hover {
text-decoration: underline;
}
答案 20 :(得分:0)
以下基本解决方案可解决以下情况:
__init__()
来访问属性。 想法是重写__setattr__
方法并在每次对象冻结状态更改时替换其实现。
因此,我们需要一种方法(_freeze
),该方法存储这两种实现并在需要时在它们之间切换。
此机制可以在用户类内部实现,也可以从特殊的Freezer
类继承,如下所示:
class Freezer:
def _freeze(self, do_freeze=True):
def raise_sa(*args):
raise AttributeError("Attributes are frozen and can not be changed!")
super().__setattr__('_active_setattr', (super().__setattr__, raise_sa)[do_freeze])
def __setattr__(self, key, value):
return self._active_setattr(key, value)
class A(Freezer):
def __init__(self):
self._freeze(False)
self.x = 10
self._freeze()
答案 21 :(得分:0)
我使用与Alex相同的想法:一个元类和一个&#34; init标记&#34;,但结合覆盖__setattr __:
>>> from abc import ABCMeta
>>> _INIT_MARKER = '_@_in_init_@_'
>>> class _ImmutableMeta(ABCMeta):
...
... """Meta class to construct Immutable."""
...
... def __call__(cls, *args, **kwds):
... obj = cls.__new__(cls, *args, **kwds)
... object.__setattr__(obj, _INIT_MARKER, True)
... cls.__init__(obj, *args, **kwds)
... object.__delattr__(obj, _INIT_MARKER)
... return obj
...
>>> def _setattr(self, name, value):
... if hasattr(self, _INIT_MARKER):
... object.__setattr__(self, name, value)
... else:
... raise AttributeError("Instance of '%s' is immutable."
... % self.__class__.__name__)
...
>>> def _delattr(self, name):
... raise AttributeError("Instance of '%s' is immutable."
... % self.__class__.__name__)
...
>>> _im_dict = {
... '__doc__': "Mix-in class for immutable objects.",
... '__copy__': lambda self: self, # self is immutable, so just return it
... '__setattr__': _setattr,
... '__delattr__': _delattr}
...
>>> Immutable = _ImmutableMeta('Immutable', (), _im_dict)
注意:我直接调用元类,使其适用于Python 2.x和3.x.
>>> class T1(Immutable):
...
... def __init__(self, x=1, y=2):
... self.x = x
... self.y = y
...
>>> t1 = T1(y=8)
>>> t1.x, t1.y
(1, 8)
>>> t1.x = 7
AttributeError: Instance of 'T1' is immutable.
它也适用于插槽......:
>>> class T2(Immutable):
...
... __slots__ = 's1', 's2'
...
... def __init__(self, s1, s2):
... self.s1 = s1
... self.s2 = s2
...
>>> t2 = T2('abc', 'xyz')
>>> t2.s1, t2.s2
('abc', 'xyz')
>>> t2.s1 += 'd'
AttributeError: Instance of 'T2' is immutable.
...和多重继承:
>>> class T3(T1, T2):
...
... def __init__(self, x, y, s1, s2):
... T1.__init__(self, x, y)
... T2.__init__(self, s1, s2)
...
>>> t3 = T3(12, 4, 'a', 'b')
>>> t3.x, t3.y, t3.s1, t3.s2
(12, 4, 'a', 'b')
>>> t3.y -= 3
AttributeError: Instance of 'T3' is immutable.
但请注意,可变属性保持可变:
>>> t3 = T3(12, [4, 7], 'a', 'b')
>>> t3.y.append(5)
>>> t3.y
[4, 7, 5]
答案 22 :(得分:0)
dict
我有一个开放源代码库,在其中我以功能的方式工作,因此在不可变对象中移动数据很有帮助。但是,我不需要为客户端与客户端交互而转换我的数据对象。因此,我想出了这一点-它为您提供了一个不变的对象之类的字典 +一些辅助方法。
在其Sven Marnach中向answer致谢,以获取限制属性更新和删除的基本实现。
import json
# ^^ optional - If you don't care if it prints like a dict
# then rip this and __str__ and __repr__ out
class Immutable(object):
def __init__(self, **kwargs):
"""Sets all values once given
whatever is passed in kwargs
"""
for k,v in kwargs.items():
object.__setattr__(self, k, v)
def __setattr__(self, *args):
"""Disables setting attributes via
item.prop = val or item['prop'] = val
"""
raise TypeError('Immutable objects cannot have properties set after init')
def __delattr__(self, *args):
"""Disables deleting properties"""
raise TypeError('Immutable objects cannot have properties deleted')
def __getitem__(self, item):
"""Allows for dict like access of properties
val = item['prop']
"""
return self.__dict__[item]
def __repr__(self):
"""Print to repl in a dict like fashion"""
return self.pprint()
def __str__(self):
"""Convert to a str in a dict like fashion"""
return self.pprint()
def __eq__(self, other):
"""Supports equality operator
immutable({'a': 2}) == immutable({'a': 2})"""
if other is None:
return False
return self.dict() == other.dict()
def keys(self):
"""Paired with __getitem__ supports **unpacking
new = { **item, **other }
"""
return self.__dict__.keys()
def get(self, *args, **kwargs):
"""Allows for dict like property access
item.get('prop')
"""
return self.__dict__.get(*args, **kwargs)
def pprint(self):
"""Helper method used for printing that
formats in a dict like way
"""
return json.dumps(self,
default=lambda o: o.__dict__,
sort_keys=True,
indent=4)
def dict(self):
"""Helper method for getting the raw dict value
of the immutable object"""
return self.__dict__
def update(obj, **kwargs):
"""Returns a new instance of the given object with
all key/val in kwargs set on it
"""
return immutable({
**obj,
**kwargs
})
def immutable(obj):
return Immutable(**obj)
obj = immutable({
'alpha': 1,
'beta': 2,
'dalet': 4
})
obj.alpha # 1
obj['alpha'] # 1
obj.get('beta') # 2
del obj['alpha'] # TypeError
obj.alpha = 2 # TypeError
new_obj = update(obj, alpha=10)
new_obj is not obj # True
new_obj.get('alpha') == 10 # True
答案 23 :(得分:0)
另一种方法是创建一个使实例不可变的包装器。
class Immutable(object):
def __init__(self, wrapped):
super(Immutable, self).__init__()
object.__setattr__(self, '_wrapped', wrapped)
def __getattribute__(self, item):
return object.__getattribute__(self, '_wrapped').__getattribute__(item)
def __setattr__(self, key, value):
raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))
__delattr__ = __setattr__
def __iter__(self):
return object.__getattribute__(self, '_wrapped').__iter__()
def next(self):
return object.__getattribute__(self, '_wrapped').next()
def __getitem__(self, item):
return object.__getattribute__(self, '_wrapped').__getitem__(item)
immutable_instance = Immutable(my_instance)
这在只有一些实例必须是不可变的情况下很有用(比如函数调用的默认参数)。
也可用于不可变的工厂,如:
@classmethod
def immutable_factory(cls, *args, **kwargs):
return Immutable(cls.__init__(*args, **kwargs))
同样可以防止object.__setattr__
,但由于Python的动态特性而导致其他技巧失效。