天天一道 python 面試題 - Python中的元類(metaclass) 詳細版本

類做爲對象

在理解元類以前,您須要掌握Python的類。Python從Smalltalk語言中借用了一個很是特殊的類概念。python

在大多數語言中,類只是描述如何產生對象的代碼段。在Python中也是如此:數據庫

>>> class ObjectCreator(object):...       pass...>>> my_object = ObjectCreator()>>> print(my_object)<__main__.ObjectCreator object at 0x8974f2c>

可是類比Python中的更多。類也是對象。ide

一旦使用關鍵字class,Python就會執行它並建立一個對象函數

>>> class ObjectCreator(object):...       pass...

在內存中建立一個名稱爲「 ObjectCreator」的對象。this

這個對象(類)自己具備建立對象(實例)的能力,這就是爲何它是一個類spa

可是,它仍然是一個對象,所以:翻譯

  • 您能夠將其分配給變量
  • 你能夠複製它
  • 您能夠爲其添加屬性
  • 您能夠將其做爲函數參數傳遞

例如:code

>>> print(ObjectCreator) # you can print a class because it's an object<class '__main__.ObjectCreator'>>>> def echo(o):...       print(o)...>>> echo(ObjectCreator) # you can pass a class as a parameter<class '__main__.ObjectCreator'>>>> print(hasattr(ObjectCreator, 'new_attribute'))False>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class>>> print(hasattr(ObjectCreator, 'new_attribute'))True>>> print(ObjectCreator.new_attribute)foo>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable>>> print(ObjectCreatorMirror.new_attribute)foo>>> print(ObjectCreatorMirror())<__main__.ObjectCreator object at 0x8997b4c>

動態建立類

因爲類是對象,所以您能夠像建立任何對象同樣即時建立它們。對象

首先,您可使用class如下方法在函數中建立一個類:blog

>>> def choose_class(name):...     if name == 'foo':...         class Foo(object):...             pass...         return Foo # return the class, not an instance...     else:...         class Bar(object):...             pass...         return Bar...>>> MyClass = choose_class('foo')>>> print(MyClass) # the function returns a class, not an instance<class '__main__.Foo'>>>> print(MyClass()) # you can create an object from this class<__main__.Foo object at 0x89c6d4c>

但這並非那麼動態,由於您仍然必須本身編寫整個類。

因爲類是對象,所以它們必須由某種東西生成。

使用class關鍵字時,Python會自動建立此對象。可是,與Python中的大多數事情同樣,它爲您提供了一種手動進行操做的方法。

還記得功能type嗎?好的舊函數可讓您知道對象的類型:

>>> print(type(1))<type 'int'>>>> print(type("1"))<type 'str'>>>> print(type(ObjectCreator))<type 'type'>>>> print(type(ObjectCreator()))<class '__main__.ObjectCreator'>

嗯,type具備徹底不一樣的功能,它也能夠動態建立類。type能夠將類的描述做爲參數,並返回一個類。

(我知道,根據傳遞給它的參數,同一個函數能夠有兩種徹底不一樣的用法是很愚蠢的。因爲Python中的向後兼容性,這是一個問題)

type 這樣工做:

type(name, bases, attrs)
  • name:班級名稱
  • bases:父類的元組(對於繼承,能夠爲空)
  • attrs:包含屬性名稱和值的字典

例如:

>>> class MyShinyClass(object):...       pass

能夠經過如下方式手動建立:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object>>> print(MyShinyClass)<class '__main__.MyShinyClass'>>>> print(MyShinyClass()) # create an instance with the class<__main__.MyShinyClass object at 0x8997cec>

您會注意到,咱們使用「 MyShinyClass」做爲類的名稱和變量來保存類引用。它們能夠不一樣,可是沒有理由使事情複雜化。

type接受字典來定義類的屬性。因此:

>>> class Foo(object):...       bar = True

能夠翻譯爲:

>>> Foo = type('Foo', (), {'bar':True})

並用做普通類:

>>> print(Foo)<class '__main__.Foo'>>>> print(Foo.bar)True>>> f = Foo()>>> print(f)<__main__.Foo object at 0x8a9b84c>>>> print(f.bar)True

固然,您能夠從中繼承,所以:

>>>   class FooChild(Foo):...         pass

將會:

>>> FooChild = type('FooChild', (Foo,), {})>>> print(FooChild)<class '__main__.FooChild'>>>> print(FooChild.bar) # bar is inherited from FooTrue

最終,您須要向類中添加方法。只需定義具備適當簽名的函數並將其分配爲屬性便可

>>> def echo_bar(self):...       print(self.bar)...>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})>>> hasattr(Foo, 'echo_bar')False>>> hasattr(FooChild, 'echo_bar')True>>> my_foo = FooChild()>>> my_foo.echo_bar()True

在動態建立類以後,您能夠添加更多方法,就像將方法添加到正常建立的類對象中同樣

>>> def echo_bar_more(self):...       print('yet another method')...>>> FooChild.echo_bar_more = echo_bar_more>>> hasattr(FooChild, 'echo_bar_more')True

您會看到咱們要去的方向:在Python中,類是對象,您能夠動態動態地建立一個類。

這就是Python在使用關鍵字class時所作的事情,而且經過使用元類來作到這一點。

什麼是元類(最終)

元類是建立類的「東西」。

您定義類是爲了建立對象,對嗎?

可是咱們瞭解到Python類是對象。

好吧,元類就是建立這些對象的緣由。它們是班級的班級,您能夠經過如下方式描繪它們:

MyClass = MetaClass()my_object = MyClass()

您已經看到,type您能夠執行如下操做:

MyClass = type('MyClass', (), {})

這是由於該函數type其實是一個元類。type是Python用於在幕後建立全部類的元類。

如今,您想知道爲何用小寫而不是小寫Type

好吧,我想這與str建立字符串對象int的類和建立整數對象的類的一致性有關。type只是建立類對象的類。

您能夠經過檢查__class__屬性來看到。

一切,個人意思是,一切都是Python中的對象。其中包括整數,字符串,函數和類。它們都是對象。全部這些都是從一個類建立的:

>>> age = 35>>> age.__class__<type 'int'>>>> name = 'bob'>>> name.__class__<type 'str'>>>> def foo(): pass>>> foo.__class__<type 'function'>>>> class Bar(object): pass>>> b = Bar()>>> b.__class__<class '__main__.Bar'>

如今,什麼是__class__任何__class__

>>> age.__class__.__class__<type 'type'>>>> name.__class__.__class__<type 'type'>>>> foo.__class__.__class__<type 'type'>>>> b.__class__.__class__<type 'type'>

所以,元類只是建立類對象的東西。

若是願意,能夠將其稱爲「班級工廠」。

type 是Python使用的內置元類,可是您固然能夠建立本身的元類。

__metaclass__屬性

在Python 2中,您能夠__metaclass__在編寫類時添加屬性(有關Python 3語法,請參見下一部分):

class Foo(object):    __metaclass__ = something...    [...]

若是這樣作,Python將使用元類建立類Foo

當心點,這很棘手。

class Foo(object)先編寫,但Foo還沒有在內存中建立類對象。

Python將__metaclass__在類定義中尋找。若是找到它,它將使用它來建立對象類Foo。若是沒有,它將 type用於建立類。

讀幾回。

當您這樣作時:

class Foo(Bar):    pass

Python執行如下操做:

中有__metaclass__屬性Foo嗎?

若是是,請在內存中建立一個類對象(我說一個類對象,在這裏呆在一塊兒),並Foo使用in中的名稱__metaclass__

若是Python找不到__metaclass__,它將__metaclass__在MODULE級別查找,並嘗試執行相同的操做(但僅適用於不繼承任何內容的類,基本上是老式的類)。

而後,若是根本找不到任何對象__metaclass__,它將使用Bar的(第一個父對象)本身的元類(多是默認值type)建立類對象。

請注意,該__metaclass__屬性將不會被繼承,而父(Bar.__class__)的元類將被繼承。若是Bar使用經過(而不是)__metaclass__建立的屬性,則子類將不會繼承該行爲。Bar`type()`type.__new__()

如今最大的問題是,您能夠輸入__metaclass__什麼?

答案是:能夠建立類的東西。

什麼能夠建立一個類?type,或任何繼承或使用它的內容。

Python 3中的元類

設置元類的語法在Python 3中已更改:

class Foo(object, metaclass=something):    ...

__metaclass__再也不使用該屬性,而在基類列表中使用關鍵字參數。

可是,元類的行爲基本保持不變。

在python 3中添加到元類的一件事是,您還能夠將屬性做爲關鍵字參數傳遞給元類,以下所示:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):    ...

閱讀如下部分,瞭解python如何處理此問題。

自定義元類

元類的主要目的是在建立類時自動更改它。

一般,您要對API進行此操做,在API中要建立與當前上下文匹配的類。

想象一個愚蠢的示例,在該示例中,您決定模塊中的全部類的屬性都應大寫。有多種方法能夠執行此操做,可是一種方法是__metaclass__在模塊級別進行設置。

這樣,將使用此元類建立該模塊的全部類,而咱們只須要告訴元類將全部屬性都轉換爲大寫便可。

幸運的是,__metaclass__實際上能夠是任何可調用的,它沒必要是正式的類(我知道,名稱中帶有「 class」的東西沒必要是類,請弄清楚……但這頗有用)。

所以,咱們將從使用函數的簡單示例開始。

# the metaclass will automatically get passed the same argument# that you usually pass to `type`def upper_attr(future_class_name, future_class_parents, future_class_attrs):    """      Return a class object, with the list of its attribute turned      into uppercase.    """    # pick up any attribute that doesn't start with '__' and uppercase it    uppercase_attrs = {        attr if attr.startswith("__") else attr.upper(): v        for attr, v in future_class_attrs.items()    }    # let `type` do the class creation    return type(future_class_name, future_class_parents, uppercase_attrs)__metaclass__ = upper_attr # this will affect all classes in the moduleclass Foo(): # global __metaclass__ won't work with "object" though    # but we can define __metaclass__ here instead to affect only this class    # and this will work with "object" children    bar = 'bip'

讓咱們檢查:

>>> hasattr(Foo, 'bar')False>>> hasattr(Foo, 'BAR')True>>> Foo.BAR'bip'

如今,讓咱們作徹底同樣的操做,可是對元類使用真實的類:

# remember that `type` is actually a class like `str` and `int`# so you can inherit from itclass UpperAttrMetaclass(type):    # __new__ is the method called before __init__    # it's the method that creates the object and returns it    # while __init__ just initializes the object passed as parameter    # you rarely use __new__, except when you want to control how the object    # is created.    # here the created object is the class, and we want to customize it    # so we override __new__    # you can do some stuff in __init__ too if you wish    # some advanced use involves overriding __call__ as well, but we won't    # see this    def __new__(upperattr_metaclass, future_class_name,                future_class_parents, future_class_attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in future_class_attrs.items()        }        return type(future_class_name, future_class_parents, uppercase_attrs)

讓咱們重寫上面的內容,可是如今有了更短,更實際的變量名,咱們知道它們的含義了:

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return type(clsname, bases, uppercase_attrs)

您可能已經注意到了額外的爭論cls。它沒有什麼特別的:__new__始終將其定義的類做爲第一個參數。就像您有self將實例做爲第一個參數接收的普通方法同樣,仍是爲類方法定義了類。

但這不是適當的OOP。咱們正在type直接致電,而不是覆蓋或致電父母的__new__。讓咱們改成:

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return type.__new__(cls, clsname, bases, uppercase_attrs)

經過使用super,咱們可使其更加整潔,這將簡化繼承(由於是的,您能夠具備元類,從元類繼承,從類型繼承):

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return super(UpperAttrMetaclass, cls).__new__(            cls, clsname, bases, uppercase_attrs)

在python 3中,若是您使用關鍵字參數進行此調用,例如:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):    ...

它將在元類中轉換爲使用它:

class MyMetaclass(type):    def __new__(cls, clsname, bases, dct, kwargs1=default):        ...

而已。實際上,關於元類的更多信息。

使用元類編寫代碼的複雜性背後的緣由不是由於元類,而是由於您一般使用元類依靠自省,操縱繼承和諸如var之類的變量來作扭曲的事情__dict__

確實,元類對於作黑魔法特別有用,所以也很複雜。但就其自己而言,它們很簡單:

  • 攔截class建立
  • 修改class
  • 返回修改後的類

爲何要使用元類類而不是函數?

既然__metaclass__能夠接受任何可調用對象,那麼爲何要使用一個類,由於它顯然更復雜?

這樣作有幾個緣由:

  • 意圖很明確。閱讀時UpperAttrMetaclass(type),您會知道接下來會發生什麼
  • 您可使用OOP。元類能夠繼承元類,重寫父方法。元類甚至可使用元類。
  • 若是您指定了元類類,但沒有元類函數,則該類的子類將是其元類的實例。
  • 您能夠更好地構建代碼。絕對不要像上面的示例那樣將元類用於瑣碎的事情。一般用於複雜的事情。可以製做幾種方法並將它們分組在一個類中的能力對於使代碼更易於閱讀很是有用。
  • 您能夠勾上__new____init____call__。這將容許您作不一樣的事情。即便一般您能夠所有__new__使用它,有些人也更習慣使用__init__
  • 這些被稱爲元類,該死!它必定意味着什麼!

爲何要使用元類?

如今是個大問題。爲何要使用一些晦澀的易錯功能?

好吧,一般您不會:

元類是更深層的魔術,99%的用戶永遠沒必要擔憂。若是您想知道是否須要它們,則不須要(實際上須要它們的人確定會知道他們須要它們,而且不須要解釋緣由)。

Python大師Tim Peters

元類的主要用例是建立API。一個典型的例子是Django ORM。它容許您定義以下內容:

class Person(models.Model):    name = models.CharField(max_length=30)    age = models.IntegerField()

可是,若是您這樣作:

person = Person(name='bob', age='35')print(person.age)

它不會返回IntegerField對象。它將返回int,甚至能夠直接從數據庫中獲取它。

這是可能的,由於models.Modeldefine __metaclass__並使用了一些魔術,這些魔術將使Person您使用簡單的語句定義的對象變成與數據庫字段的複雜掛鉤。

Django經過公開一個簡單的API並使用元類,從該API從新建立代碼來完成幕後的實際工做,使看起來複雜的事情變得簡單。

最後一個字

首先,您知道類是能夠建立實例的對象。

實際上,類自己就是實例。元類。

>>> class Foo(object): pass>>> id(Foo)142630324

一切都是Python中的對象,它們都是類的實例或元類的實例。

除了type

type其實是它本身的元類。這不是您能夠在純Python中複製的東西,而是經過在實現級別上做弊來完成的。

其次,元類很複雜。您可能不但願將它們用於很是簡單的類更改。您可使用兩種不一樣的技術來更改類:

歡迎點贊,收藏,關注,三連擊,謝謝,今天文章就到此結束了

相關文章
相關標籤/搜索