在搜索了一种强制自定义类型的不变性并且没有找到满意答案的方法后,我想出了一个以元类形式提供的解决方案:
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注释):
答案 0 :(得分:4)
总是在类型上查找特殊方法,而不是实例。因此,hasattr
也必须应用于类型。 E.g:
>>> class A(object): pass
...
>>> class B(A): __eq__ = lambda *_: 1
...
>>> class C(B): pass
...
>>> c = C()
>>> hasattr(type(c), '__eq__')
True
检查hasattr(c, '__eq__')
会产生误导,因为它可能会错误地“捕获”__eq__
本身定义的每个实例属性c
,这不会作为特殊方法(请注意, __eq__
的特定情况,您始终会看到来自True
的{{1}}结果,因为祖先类hasattr
定义了它,而继承只能“添加”属性,永远不会“减去”任何; - )。
检查哪个祖先类首先定义了一个属性(因此当查找仅在类型上时将使用哪个确切的定义):
object
最好使用import inspect
def whichancestor(c, attname):
for ancestor in inspect.getmro(type(c)):
if attname in ancestor.__dict__:
return ancestor
return None
执行此类任务,因为它比inspect
上__mro__
属性的直接访问权限更广泛。
答案 1 :(得分:0)
这个元类强制执行“浅薄”的不变性。例如,它不会阻止
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级实现的,所以我认为你的元类没有机会拦截它们中的状态变化。