用于强制自定义类型不可变性的Python类元。
我在寻找一种方法来确保自定义类型是不可变的,但没有找到满意的答案,于是我自己想出了一个解决方案,使用了元类的方式:
class ImmutableTypeException( Exception ): pass
class Immutable( type ):
'''
Enforce some aspects of the immutability contract for new-style classes:
- attributes must not be created, modified or deleted after object construction
- immutable types must implement __eq__ and __hash__
'''
def __new__( meta, classname, bases, classDict ):
instance = type.__new__( meta, classname, bases, classDict )
# Make sure __eq__ and __hash__ have been implemented by the immutable type.
# In the case of __hash__ also make sure the object default implementation has been overridden.
# TODO: the check for eq and hash functions could probably be done more directly and thus more efficiently
# (hasattr does not seem to traverse the type hierarchy)
if not '__eq__' in dir( instance ):
raise ImmutableTypeException( 'Immutable types must implement __eq__.' )
if not '__hash__' in dir( instance ):
raise ImmutableTypeException( 'Immutable types must implement __hash__.' )
if _methodFromObjectType( instance.__hash__ ):
raise ImmutableTypeException( 'Immutable types must override object.__hash__.' )
instance.__setattr__ = _setattr
instance.__delattr__ = _delattr
return instance
def __call__( self, *args, **kwargs ):
obj = type.__call__( self, *args, **kwargs )
obj.__immutable__ = True
return obj
def _setattr( self, attr, value ):
if '__immutable__' in self.__dict__ and self.__immutable__:
raise AttributeError( "'%s' must not be modified because '%s' is immutable" % ( attr, self ) )
object.__setattr__( self, attr, value )
def _delattr( self, attr ):
raise AttributeError( "'%s' must not be deleted because '%s' is immutable" % ( attr, self ) )
def _methodFromObjectType( method ):
'''
Return True if the given method has been defined by object, False otherwise.
'''
try:
# TODO: Are we exploiting an implementation detail here? Find better solution!
return isinstance( method.__objclass__, object )
except:
return False
不过,虽然这个方法总体上看起来效果不错,但在实现细节上还有一些不太确定的地方(代码中也有TODO的注释):
- 我该如何检查某个特定的方法是否在类型层次结构中的某个地方被实现过?
- 我该如何检查某个方法声明的来源类型(也就是说,这个方法是在哪个类型中定义的)?
2 个回答
这个元类实现了“浅层”的不可变性。举个例子,它并不会阻止
immutable_obj.attr.attrs_attr = new_value
immutable_obj.attr[2] = new_value
根据 attrs_attr 是否属于这个对象,这可能会被认为是违反真正的不可变性。比如,它可能导致以下情况,这在不可变类型中是不应该发生的:
>>> a = ImmutableClass(value)
>>> b = ImmutableClass(value)
>>> c = a
>>> a == b
True
>>> b == c
True
>>> a.attr.attrs_attr = new_value
>>> b == c
False
你可能可以通过重写 getattr 来解决这个问题,让它返回某种不可变的包装器,来处理它返回的任何属性。不过这可能会比较复杂。直接阻止 setattr 的调用是可以做到的,但如果属性的方法在它们的代码中设置了属性呢?我能想到一些想法,但这会变得相当复杂。
另外,我觉得这会是你类的一个聪明用法:
class Tuple(list):
__metaclass__ = Immutable
但它并没有像我希望的那样生成一个元组。
>>> t = Tuple([1,2,3])
>>> t.append(4)
>>> t
[1, 2, 3, 4]
>>> u = t
>>> t += (5,)
>>> t
[1, 2, 3, 4, 5]
>>> u
[1, 2, 3, 4, 5]
我想列表的方法大部分或完全是在 C 语言层面实现的,所以我想你的元类没有机会去拦截它们的状态变化。
特殊方法总是根据类型来查找,而不是根据实例。所以,使用hasattr
时也必须针对类型来检查。例如:
>>> class A(object): pass
...
>>> class B(A): __eq__ = lambda *_: 1
...
>>> class C(B): pass
...
>>> c = C()
>>> hasattr(type(c), '__eq__')
True
检查hasattr(c, '__eq__')
可能会让人误解,因为它可能错误地“捕捉”到在c
实例中定义的一个实例属性__eq__
,而这个属性并不会作为特殊方法来使用(需要注意的是,在__eq__
的特定情况下,你总是会从hasattr
得到True
的结果,因为它的祖先类object
定义了这个方法,而继承只会“添加”属性,而不会“删除”任何属性;-)。
如果想要检查哪个祖先类最早定义了某个属性(也就是说,当只在类型上查找时,究竟会使用哪个具体的定义):
import inspect
def whichancestor(c, attname):
for ancestor in inspect.getmro(type(c)):
if attname in ancestor.__dict__:
return ancestor
return None
最好使用inspect
模块来完成这样的任务,因为它的适用范围比直接访问type(c)
上的__mro__
属性更广泛。