虽然我从来都不需要这样做,但我突然意识到用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
但是你可以通过self[0]和self[1]访问a和b变量,这很烦人。
这在Pure Python中可行吗?如果不是,我该如何用C扩展来做呢?
(只能在python3中工作的答案是可以接受的)。
更新:
从Python 3.7开始,要使用的方法是使用@dataclass装饰器,参见最新接受的答案。
这种方式不停止对象。__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__)。
这里没有包括的是完全不可变性……不仅仅是父对象,还有所有的子对象。例如,元组/frozensets可能是不可变的,但它所属的对象可能不是。下面是一个小的(不完整的)版本,它在执行不变性方面做得很好:
# Initialize lists
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
l = [a,b]
# We can reassign in a list
l[0] = c
# But not a tuple
t = (a,b)
#t[0] = c -> Throws exception
# But elements can be modified
t[0][1] = 4
t
([1, 4, 3], [4, 5, 6])
# Fix it back
t[0][1] = 2
li = ImmutableObject(l)
li
[[1, 2, 3], [4, 5, 6]]
# Can't assign
#li[0] = c will fail
# Can reference
li[0]
[1, 2, 3]
# But immutability conferred on returned object too
#li[0][1] = 4 will throw an exception
# Full solution should wrap all the comparison e.g. decorators.
# Also, you'd usually want to add a hash function, i didn't put
# an interface for that.
class ImmutableObject(object):
def __init__(self, inobj):
self._inited = False
self._inobj = inobj
self._inited = True
def __repr__(self):
return self._inobj.__repr__()
def __str__(self):
return self._inobj.__str__()
def __getitem__(self, key):
return ImmutableObject(self._inobj.__getitem__(key))
def __iter__(self):
return self._inobj.__iter__()
def __setitem__(self, key, value):
raise AttributeError, 'Object is read-only'
def __getattr__(self, key):
x = getattr(self._inobj, key)
if callable(x):
return x
else:
return ImmutableObject(x)
def __hash__(self):
return self._inobj.__hash__()
def __eq__(self, second):
return self._inobj.__eq__(second)
def __setattr__(self, attr, value):
if attr not in ['_inobj', '_inited'] and self._inited == True:
raise AttributeError, 'Object is read-only'
object.__setattr__(self, attr, value)
我找到了一种方法,不用子类化tuple, namedtuple等。你所需要做的就是在初始化后禁用setattr和delattr(如果你想让一个集合成为不可变的,也要禁用setitem和delitem):
def __init__(self, *args, **kwargs):
# something here
self.lock()
其中lock可以是这样的:
@classmethod
def lock(cls):
def raiser(*a):
raise TypeError('this instance is immutable')
cls.__setattr__ = raiser
cls.__delattr__ = raiser
if hasattr(cls, '__setitem__'):
cls.__setitem__ = raiser
cls.__delitem__ = raiser
你可以用这个方法创建类Immutable,并像我展示的那样使用它。
如果你不想在每个init中都写self.lock(),你可以用元类自动实现:
class ImmutableType(type):
@classmethod
def change_init(mcs, original_init_method):
def __new_init__(self, *args, **kwargs):
if callable(original_init_method):
original_init_method(self, *args, **kwargs)
cls = self.__class__
def raiser(*a):
raise TypeError('this instance is immutable')
cls.__setattr__ = raiser
cls.__delattr__ = raiser
if hasattr(cls, '__setitem__'):
cls.__setitem__ = raiser
cls.__delitem__ = raiser
return __new_init__
def __new__(mcs, name, parents, kwargs):
kwargs['__init__'] = mcs.change_init(kwargs.get('__init__'))
return type.__new__(mcs, name, parents, kwargs)
class Immutable(metaclass=ImmutableType):
pass
Test
class SomeImmutableClass(Immutable):
def __init__(self, some_value: int):
self.important_attr = some_value
def some_method(self):
return 2 * self.important_attr
ins = SomeImmutableClass(3)
print(ins.some_method()) # 6
ins.important_attr += 1 # TypeError
ins.another_attr = 2 # TypeError
你可以在init的最后一条语句中重写setAttr。那么你可以构建,但不能改变。显然,你仍然可以用usint对象重写。但在实践中,大多数语言都有某种形式的反射,因此不可变始终是一个有漏洞的抽象。不可变性更多的是防止客户端意外地违反对象的契约。我使用:
=============================
最初提供的解决方案是不正确的,这是基于使用这里的解决方案的评论而更新的
原来的解决方案是错误的,这是一种有趣的方式,所以它被包括在底部。
===============================
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")
另一种方法是创建一个使实例不可变的包装器。
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))
也保护对象。__setattr__,但由于Python的动态特性,可能会被其他技巧所绊倒。
所以,我在写python 3的相关内容:
I)借助数据类装饰器并设置frozen=True。
我们可以在python中创建不可变对象。
为此需要从data classes lib导入data class,并需要设置frozen=True
ex.
从数据类导入数据类
@dataclass(frozen=True)
class Location:
name: str
longitude: float = 0.0
latitude: float = 0.0
o/p:
>>> l = Location("Delhi", 112.345, 234.788)
>>> l.name
'Delhi'
>>> l.longitude
112.345
>>> l.latitude
234.788
>>> l.name = "Kolkata"
dataclasses.FrozenInstanceError: cannot assign to field 'name'
>>>
来源:https://realpython.com/python-data-classes/