2014-09-12 68 views
0

其中定義內嵌元類的功能,我得到了一個玩具蟒蛇例如行爲不同於其定義爲一類,我試圖理解爲什麼:的Python 2.7元類從繼承

>>> class Test(object): 
...  def __metaclass__(name, bases, nmspc): 
...   cls = type(name, bases, nmspc) 
...   def __init__(self, field, *args, **kwargs): 
...    print "making " + field 
...    super(cls, self).__init__() 
...   cls.__init__ = __init__ 
...   return cls 
... 
>>> t = Test('lol') 
making lol 
>>> class Test2(Test): pass 
... 
>>> t = Test2('lol') 
making lol 

對戰:

>>> class Test(object): 
...  class __metaclass__(type): 
...    def __init__(cls, name, bases, nmspc): 
...      type.__init__(cls, name, bases, nmspc) 
...      def __init__(self, field, *args, **kwargs): 
...        print "making " + field 
...        super(cls, self).__init__() 
...      cls.__init__ = __init__ 
... 
>>> t = Test('lol') 
making lol 
>>> class Test2(Test): pass 
... 
>>> t2 = Test2('lol') 
making lol 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 7, in __init__ 
TypeError: __init__() takes at least 2 arguments (1 given) 

那麼,定義一個類與定義一個函數究竟有什麼不同?它們最終都以一種稍微不同的方式返回一個類,因爲修改後的CLS .__ init__會以一種方式繼承,而不是其他方式。

我也檢查了類似的結構,通過外部函數和類聲明,我得到了相同的行爲。

+0

你的問題似乎是*「爲什麼不同的事情有不同的表現?」* - 你能澄清你的期望嗎? – jonrsharpe 2014-09-12 22:28:27

+0

我期待\ _ \ _ metaclass \ _ \ _這兩個定義的子類獲得相同的\ _ \ _ init \ _ \ _方法。 – ThrustVectoring 2014-09-12 22:43:56

回答

1

如果你仔細閱讀documentation on metaclasses,你會注意到,當Python的檢查繼承元類,它不看在基類的__metaclass__屬性,它會尋找其__class__或類型(因爲一般來說,類的元類是它的類)。 (您可以在the build_class function中看到實現此行爲的C代碼。)

您的函數版本調用type來構造該類,然後對其進行修補。但班級的班級仍然設置爲type,因爲這是實際建造的地方。

另一方面,您的班級版本type的小班版本本身成爲創建班級的__class__

你可以看到這種差異(在這裏我命名你的函數版本TestF和你的類版本TestC,並從名爲mc模塊進口他們倆):

In [7]: TestC.__class__ 
Out[7]: mc.__metaclass__ 

In [8]: TestF.__class__ 
Out[8]: type 

In [9]: type(TestC) 
Out[9]: mc.__metaclass__ 

In [10]: type(TestF) 
Out[10]: type 

由於存在這種差異,當你從TestC繼承,__metaclass__用作新子類的元類,而當您從TestF繼承時,使用type

爲什麼這會導致觀察到的差異?因爲在TestF的情況下,由於沒有自定義元類應用於創建子類,所以子類沒有得到應用的修改的__init__方法。所以調用的__init__方法仍然爲TestF.__init__(),當它調用super(cls, self).__init__()時,它調用object.__init__(),它不帶任何參數,並且工作正常。

但在TestC的情況下,當您創建子類時,元類將再次使用。 (如果在每個版本的cls.__init__ = __init__行之前插入print語句,則可以觀察到這種差異。)因此,該子類獲取自己的自定義__init__方法。因此,在這種情況下,當它調用super(cls, self).__init__()時,它調用TestC.__init__(),它本身是由元類創建的,它採用了您未傳遞的必需參數field。因此你得到TypeError: __init__() takes at least 2 arguments (1 given)錯誤。