2012-06-18 81 views
5

this answer似乎一類元類的元類可以改變類已經被定義後,使用以下*設置類:使用裝飾

class MyMetaClass(type): 
    # Metaclass magic... 

class A(object): 
    pass 

A = MyMetaClass(A.__name__, A.__bases__, dict(A.__dict__)) 

定義功能

def metaclass_wrapper(cls): 
    return MyMetaClass(cls.__name__, cls.__bases__, dict(cls.__dict__)) 

讓我一個裝飾應用於類定義像這樣,

@metaclass_wrapper 
class B(object): 
    pass 

它看ms表明元類魔術應用於B,但B沒有__metaclass__屬性。是上述方法來元類應用類定義一個合理的方式,即使我definiting並重新definiting類,或者我會過得更好只是寫

class B(object): 
    __metaclass__ = MyMetaClass 
    pass 

我相信有這兩者之間的一些差異方法。


*注意,在鏈接的問題,MyMetaClass(A.__name__, A.__bases__, A.__dict__)原來的答案,返回TypeError

TypeError: type() argument 3 must be a dict, not dict_proxy

看來的A(類定義)的__dict__屬性有一個類型dict_proxy,而__dict__屬性的類型實例A的類型爲dict。爲什麼是這樣?這是一個Python 2.x與3.x的區別嗎?

回答

1

該課程沒有__metaclass__屬性集...因爲您從未設置它!

通常使用哪個元類來確定設置在類塊中的名稱__metaclass____metaclass__屬性不是由元類設置的。因此,如果直接調用元類而不是設置__metaclass__並讓Python找出它,則不會設置__metaclass__屬性。

事實上,正常類是元類type的所有實例,因此如果元類總是將__metaclass__屬性上它的實例,然後類將有一個__metaclass__屬性(大多設定爲type)。


我不會使用你的裝飾器方法。它掩蓋了元類涉及(以及哪一個)的事實,仍然是一行樣板,並且僅僅從這3個定義的特徵中創建一個類只是將這3個比特從所得到的類中拉出來,把班級扔掉,並從這些相同的3位創建一個新班級!

當你這樣做在Python 2.x中:

class A(object): 
    __metaclass__ = MyMeta 
    def __init__(self): 
     pass 

你會得到大致相同的結果,如果你寫的:

attrs = {} 
attrs['__metaclass__'] = MyMeta 
def __init__(self): 
    pass 
attrs['__init__'] = __init__ 
A = attrs.get('__metaclass__', type)('A', (object,), attrs) 

在現實中計算元類是多因爲實際上必須通過搜索所有基地來確定是否存在元類衝突,並且如果其中一個基地沒有type作爲其元類而attrs不包含__metaclass__那麼默認元類是祖先的元類而不是type。這是我希望你的裝飾「解決方案」與直接使用__metaclass__不同的一種情況。我不確定如果在使用__metaclass__會給你一個元類衝突錯誤的情況下使用裝飾器會發生什麼情況,但我不希望它會令人愉快。另外,如果還有其他元類參與,你的方法會導致它們首先運行(可能會修改名稱,基礎和屬性!),然後將它們從類中拉出來並使用它創建新班。這可能會與您使用__metaclass__的情況完全不同。

至於__dict__沒有給你一個真正的字典,這只是一個實現細節;我猜是出於性能原因。我懷疑是否有任何規範說__dict__(非類)實例的類型必須是類的__dict__(它也是一個實例btw;只是元類的一個實例)。 __dict__屬性的類是一個「dictproxy」,它允許您查找屬性鍵,就好像它是一個dict但仍然不是dicttype對第三個參數的類型是挑剔的;它需要一個真正的字典,而不僅僅是一個「類字典」的對象(因爲糟糕的鴨子打字而感到羞恥)。這不是一個2.x和3.x的東西; Python 3的行爲方式相同,但它給了你一個更好的字符串表示形式dictproxy。 Python 2.4(這是我現有的最老的2.x)也有dictproxy對象用於__dict__類對象。

1

我對你的問題總結:「我嘗試了一個新的棘手方式去做一件事,但它並沒有起作用,我應該用簡單的方式來代替嗎?」

是的,你應該這樣做的簡單方法。你還沒有說過你爲什麼有興趣發明一種新的方式來做到這一點。

+0

我只是想知道是否有一個裝飾器等效於在類定義中設置__metaclass__屬性。我想我的問題的另一部分是我的'裝飾'似乎*工作,但我注意到,裝飾類沒有'__metaclass__'屬性,所以這兩種方法之間顯然是有區別的;爲什麼是這樣? – Chris

3

不可否認,我晚了一點。不過,我倒是值得補充。

這是完全可行的。這就是說,還有很多其他方法可以實現相同的目標。但是,裝飾解決方案尤其允許延遲評估(obj = dec(obj)),其中使用__metaclass__的課程沒有。在典型的裝飾風格中,我的解決方案如下。

有一個棘手的事情,你可能會遇到,如果你只是構造類而不改變字典或複製它的屬性。該類以前(裝飾之前)的任何屬性都會丟失。所以,將這些複製過來然後按照我的解決方案進行調整是絕對必要的。

就我個人而言,我喜歡能夠跟蹤物體是如何包裹的。所以,我添加了__wrapped__屬性,這不是絕對必要的。它也使得它更像Python 3中的functools.wraps。但是,它可以幫助反省。此外,還添加了__metaclass__以更像正常的元類用例。

def metaclass(meta): 
    def metaclass_wrapper(cls): 
     __name = str(cls.__name__) 
     __bases = tuple(cls.__bases__) 
     __dict = dict(cls.__dict__) 

     for each_slot in __dict.get("__slots__", tuple()): 
      __dict.pop(each_slot, None) 

     __dict["__metaclass__"] = meta 

     __dict["__wrapped__"] = cls 

     return(meta(__name, __bases, __dict)) 
    return(metaclass_wrapper) 

對於一個簡單的例子,採取以下步驟。

class MetaStaticVariablePassed(type): 
    def __new__(meta, name, bases, dct): 
     dct["passed"] = True 

     return(super(MetaStaticVariablePassed, meta).__new__(meta, name, bases, dct)) 

@metaclass(MetaStaticVariablePassed) 
class Test(object): 
    pass 

這產生了很好的結果...

|1> Test.passed 
|.> True 

使用在較平常的裝飾,但相同的方式......

class Test(object): 
    pass 

Test = metaclass_wrapper(Test) 

...收益率,符合市場預期,同樣不錯的結果。

|1> Test.passed 
|.> True