Python元类用于强制自定义类型的不变性

时间:2010-03-28 15:15:03

标签: 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, 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注释):

  1. 如何检查特定方法是否已在类型层次结构中的任何位置实现?
  2. 如何检查哪种类型是方法声明的来源(即作为已定义方法的类型的一部分)?

2 个答案:

答案 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级实现的,所以我认为你的元类没有机会拦截它们中的状态变化。