3

在寻找一种方法来强制自定义类型的不变性并且没有找到令人满意的答案后,我想出了一个元类形式的解决方案:

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. 如何检查哪种类型是方法声明的来源(即作为已定义方法的类型的一部分)?
4

2 回答 2

5

特殊方法总是在类型而不是实例上查找。所以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__')会产生误导,因为它可能会错误地“捕获”自身__eq__定义的每个实例属性c,这不会充当特殊方法(请注意,在特定情况下,__eq__您将始终看到True来自 的结果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__type(c)

于 2010-03-28T15:22:04.243 回答
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 级别实现,所以我想你的元类没有机会拦截它们的状态变化。

于 2013-06-15T02:44:07.173 回答