用于强制自定义类型的不变性的Python元类
在寻找了一种强制实现自定义类型不变性的方法,但没有找到令人满意的答案后,我想出了一个元类形式的解决方案:用于强制自定义类型的不变性的Python元类,python,metaclass,immutability,Python,Metaclass,Immutability,在寻找了一种强制实现自定义类型不变性的方法,但没有找到令人满意的答案后,我想出了一个元类形式的解决方案: class ImmutableTypeException( Exception ): pass class Immutable( type ): ''' Enforce some aspects of the immutability contract for new-style classes: - attributes must not be created, mo
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注释):
特殊方法总是在类型上查找,而不是在实例上查找。因此,
hasattr
也必须应用于类型。例如:
>>> class A(object): pass
...
>>> class B(A): __eq__ = lambda *_: 1
...
>>> class C(B): pass
...
>>> c = C()
>>> hasattr(type(c), '__eq__')
True
检查hasattr(c,'.\uu eq'.
可能会产生误导,因为它可能会错误地“捕获”在c
本身中定义的每个实例属性.\uu eq'.
,而该属性不会作为特殊方法(请注意,在\uuuu eq\uuuu
的特定情况下,您将始终看到hasattr
的结果,因为祖先类对象定义了它,继承只能“添加”属性,而不能“减去”任何属性;-)
要检查哪个祖先类首先定义了属性(因此,当查找仅针对该类型时,将使用哪个确切定义),请执行以下操作:
最好对此类任务使用inspect
,因为它比直接访问type(c)
上的\uu mro\uu
属性的范围更广。这个元类强制执行“浅”不变性。例如,它不阻止
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]
我猜list的方法大部分或完全是在C级别实现的,所以我想您的元类没有机会截获其中的状态更改。为什么要强制执行不可变性?这不是很不符合Python的吗?Python内置类型也是不可变的。我主要想强制执行自定义类型的不可变性到目前为止,它是按约定不可变的,以帮助查找与错误修改约定不可变对象相关的可能错误。我认为Python内置类型通常是不可变的,因为其他原因。我认为对象不可变的约定似乎非常直截了当。如果您已经需要元类来删除ug是一个相对简单的方面,如果你遇到真正的错误,你会怎么做?通过元类强制执行每个契约?Python可能不是适合这个方面的语言。
>>> 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]