2010-03-28 129 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級大部分或完全實現的,所以我想你的元類有沒有機會去攔截他們的狀態變化。