2011-08-06 24 views
21

當討論元類,the docs狀態:使用元類的__call__方法而不是__new__?

你當然也可以覆蓋其他類中的方法(或增加新 方法);例如在 元類中定義一個自定義的__call__()方法允許調用該類時的自定義行爲,例如,不是 總是創建一個新的實例。

我的問題是:假設我想在調用類時有自定義行爲,例如緩存而不是創建新對象。我可以通過重寫該類的__new__方法來做到這一點。我什麼時候想用__call__來定義元類呢?這種方法給了什麼是不可能的__new__

回答

13

直接回答你的問題是:當你想要做不僅僅是定製實例創建,或當你想分開一下類確實從它是如何創建的。

請參閱我的回答Creating a singleton in Python及相關討論。

有幾個優點。

  1. 它可以讓你分開什麼類確實從它是如何創建的細節。元類和類分別負責一件事情。

  2. 您可以在元類中編寫一次代碼,並使用它來定製幾個類的調用行爲,而不用擔心多重繼承。

  3. 子類可以覆蓋它們的__new__方法中的行爲,但元類上的__call__根本不需要甚至調用__new__

  4. 如果有設置工作,可以在元類的__new__方法中完成,它只發生一次,而不是每次調用該類。

如果您不擔心單一職責原則,定製__new__的工作方式當然也很多。

但還有其他用例必須在創建類時更早發生,而不是在創建實例時發生。當這些元素出現時,元類是必需的。請參閱What are your (concrete) use-cases for metaclasses in Python?瞭解很多很棒的例子。

0

這是一個生命週期階段和您有權訪問的問題。 __call____new__之後被稱爲,並且在之前通過了初始化參數,因此它們被傳遞到__init__,因此您可以操縱它們。試試這個代碼,並研究其輸出:

class Meta(type): 
    def __new__(cls, name, bases, newattrs): 
     print "new: %r %r %r %r" % (cls, name, bases, newattrs,) 
     return super(Meta, cls).__new__(cls, name, bases, newattrs) 

    def __call__(self, *args, **kw): 
     print "call: %r %r %r" % (self, args, kw) 
     return super(Meta, self).__call__(*args, **kw) 

class Foo: 
    __metaclass__ = Meta 

    def __init__(self, *args, **kw): 
     print "init: %r %r %r" % (self, args, kw) 

f = Foo('bar') 
print "main: %r" % f 
+1

不!元類上的'__new__'發生在創建_class_時,而不是_instance_。'__new__'發生在沒有元類的情況下'__new__'發生。 – agf

+1

我在哪裏說'__new__'與實例創建有關? – pyroscope

+4

我實際上在詢問類的'__new__',而不是元類的'__new__'。 –

12

一個區別是,通過定義元類__call__方法,你都要求它被之前的任何類的或子類的__new__方法稱爲到達被稱爲機會。

class MetaFoo(type): 
    def __call__(cls,*args,**kwargs): 
     print('MetaFoo: {c},{a},{k}'.format(c=cls,a=args,k=kwargs)) 

class Foo(object): 
    __metaclass__=MetaFoo 

class SubFoo(Foo): 
    def __new__(self,*args,**kwargs): 
     # This never gets called 
     print('Foo.__new__: {a},{k}'.format(a=args,k=kwargs)) 

sub=SubFoo() 
foo=Foo() 

# MetaFoo: <class '__main__.SubFoo'>,(),{} 
# MetaFoo: <class '__main__.Foo'>,(),{} 

請注意,SubFoo.__new__永遠不會被調用。相反,如果您定義了不帶元類的Foo.__new__,則允許子類覆蓋Foo.__new__

當然,您可以定義MetaFoo.__call__以致電cls.__new__,但這取決於您。通過拒絕這樣做,可以防止子類調用其方法。

我在這裏看不到使用元類的強大優勢。由於「簡單勝於複雜」,我推薦使用__new__

+0

還要注意,如果'MetaFoo .__ call __()'方法調用'super(MetaFoo,cls).__ call __(* args,** kwargs)','cls .__ new __()'將被間接調用。 – martineau

5

當您仔細觀察這些方法的執行順序時,細微的差異會變得更加明顯。

class Meta_1(type): 
    def __call__(cls, *a, **kw): 
     print "entering Meta_1.__call__()" 
     rv = super(Meta_1, cls).__call__(*a, **kw) 
     print "exiting Meta_1.__call__()" 
     return rv 

class Class_1(object): 
    __metaclass__ = Meta_1 
    def __new__(cls, *a, **kw): 
     print "entering Class_1.__new__()" 
     rv = super(Class_1, cls).__new__(cls, *a, **kw) 
     print "exiting Class_1.__new__()" 
     return rv 

    def __init__(self, *a, **kw): 
     print "executing Class_1.__init__()" 
     super(Class_1,self).__init__(*a, **kw) 

注意,上面的代碼實際上並不做的比登錄我們正在做什麼其他的事情。每種方法都遵循其父實現,即其默認設置。所以記錄在它旁邊是有效的,如果你只是簡單地聲明的東西如下:

class Meta_1(type): pass 
class Class_1(object): 
    __metaclass__ = Meta_1 

現在就讓如果typeMeta_1我們能想象一個僞父創建的Class_1

c = Class_1() 
# entering Meta_1.__call__() 
# entering Class_1.__new__() 
# exiting Class_1.__new__() 
# executing Class_1.__init__() 
# exiting Meta_1.__call__() 

因此一個實例實施type.__call__()本身:

class type: 
    def __call__(cls, *args, **kwarg): 

     # ... a few things could possibly be done to cls here... maybe... or maybe not... 

     # then we call cls.__new__() to get a new object 
     obj = cls.__new__(cls, *args, **kwargs) 

     # ... a few things done to obj here... maybe... or not... 

     # then we call obj.__init__() 
     obj.__init__(*args, **kwargs) 

     # ... maybe a few more things done to obj here 

     # then we return obj 
     return obj 

來自call的通知orde r以上(Meta_1.__call__())(或在此情況下爲type.__call__())有機會影響是否最終撥打Class_1.__new__()Class_1.__init__()。在執行過程中Meta_1.__call__()可能會返回一個甚至沒有被任何一個觸摸過的對象。舉個例子這種做法對單件模式:

class Meta_2(type): 
    __Class_2_singleton__ = None 
    def __call__(cls, *a, **kw): 
     # if the singleton isn't present, create and register it 
     if not Meta_2.__Class_2_singleton__: 
      print "entering Meta_2.__call__()" 
      Meta_2.__Class_2_singleton__ = super(Meta_2, cls).__call__(*a, **kw) 
      print "exiting Meta_2.__call__()" 
     else: 
      print ("Class_2 singleton returning from Meta_2.__call__(), " 
        "super(Meta_2, cls).__call__() skipped") 
     # return singleton instance 
     return Meta_2.__Class_2_singleton__ 

class Class_2(object): 
    __metaclass__ = Meta_2 
    def __new__(cls, *a, **kw): 
     print "entering Class_2.__new__()" 
     rv = super(Class_2, cls).__new__(cls, *a, **kw) 
     print "exiting Class_2.__new__()" 
     return rv 

    def __init__(self, *a, **kw): 
     print "executing Class_2.__init__()" 
     super(Class_2, self).__init__(*a, **kw) 

讓我們看到發生了什麼時,反覆嘗試創建類型Class_2

a = Class_2() 
# entering Meta_2.__call__() 
# entering Class_2.__new__() 
# exiting Class_2.__new__() 
# executing Class_2.__init__() 
# exiting Meta_2.__call__() 

b = Class_2() 
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped 

c = Class_2() 
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped 

print a is b is c 
True 

現在的對象使用一個類__new__()方法,試圖觀察此實施完成同樣的事情。

import random 
class Class_3(object): 

    __Class_3_singleton__ = None 

    def __new__(cls, *a, **kw): 
     # if singleton not present create and save it 
     if not Class_3.__Class_3_singleton__: 
      print "entering Class_3.__new__()" 
      Class_3.__Class_3_singleton__ = rv = super(Class_3, cls).__new__(cls, *a, **kw) 
      rv.random1 = random.random() 
      rv.random2 = random.random() 
      print "exiting Class_3.__new__()" 
     else: 
      print ("Class_3 singleton returning from Class_3.__new__(), " 
        "super(Class_3, cls).__new__() skipped") 

     return Class_3.__Class_3_singleton__ 

    def __init__(self, *a, **kw): 
     print "executing Class_3.__init__()" 
     print "random1 is still {random1}".format(random1=self.random1) 
     # unfortunately if self.__init__() has some property altering actions 
     # they will affect our singleton each time we try to create an instance 
     self.random2 = random.random() 
     print "random2 is now {random2}".format(random2=self.random2) 
     super(Class_3, self).__init__(*a, **kw) 

注意,上面的實現,即使在類成功註冊一個單身,並不妨礙__init__()被調用,這種情況發生在隱含type.__call__()type是默認元類,如果沒有指定)。這可能會導致一些不良影響:

a = Class_3() 
# entering Class_3.__new__() 
# exiting Class_3.__new__() 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.739298365475 

b = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.247361634396 

c = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.436144427555 

d = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.167298405242 

print a is b is c is d 
# True 
相關問題