用于强制自定义类型的不变性的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]