2010-03-28 132 views
3

已经寻找一种方式来强制执行自定义类型的不变性,而不是自定义类型的不可改变的Python元类已经发现我用我自己的射击在一个解决方案中的一个元类的形式提出了一份满意的答卷:强制执行

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

为什么要强制执行不变性?它不是那么和谐吗? – nikow 2010-03-28 20:54:54

+2

构建在类型中的Python也是不可变的。主要是为了帮助找出与错误地修改契约不可变对象有关的可能错误,我希望为自定义类型强制执行不变性,而这些类型一直是惯例不变的。 – 2010-03-29 06:44:07

+1

我认为Python内置的类型通常是不可变的,其他原因。一个对象是不可变的合约看起来很直截了当。如果您已经需要元类来调试这样一个相对简单的方面,那么如果遇到真正的错误,您会怎么做?通过元类强制每一份合约? Python可能不适合此语言。 – nikow 2010-03-29 10:50:18

回答

4

特殊方法总是抬头上的类型,而不是实例。所以hasattr也必须适用于类型。例如:因为它可能会错误地“捕获”每个实例属性中c本身定义__eq__,这不能作为一种特殊的方法(注意,在__eq__具体情况

>>> class A(object): pass 
... 
>>> class B(A): __eq__ = lambda *_: 1 
... 
>>> class C(B): pass 
... 
>>> c = C() 
>>> hasattr(type(c), '__eq__') 
True 

检查hasattr(c, '__eq__')会误导你因为祖先类object定义了它,并且继承只能“添加”属性,所以永远不会“减去”任何;-),所以总是会看到True结果hasattr

要检查其中祖先类第一定义的属性(以及因此,当所述查找是仅在类型确切的定义将被使用):

import inspect 

def whichancestor(c, attname): 
    for ancestor in inspect.getmro(type(c)): 
    if attname in ancestor.__dict__: 
     return ancestor 
    return None 

它最好使用inspect此类任务,因为它将比直接访问属性type(c)更广泛地工作。

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级大部分或完全实现的,所以我想你的元类有没有机会去拦截他们的状态变化。