在理解元類以前,您須要掌握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中已更改:
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__
。
確實,元類對於作黑魔法特別有用,所以也很複雜。但就其自己而言,它們很簡單:
既然__metaclass__
能夠接受任何可調用對象,那麼爲何要使用一個類,由於它顯然更復雜?
這樣作有幾個緣由:
UpperAttrMetaclass(type)
,您會知道接下來會發生什麼__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.Model
define __metaclass__
並使用了一些魔術,這些魔術將使Person
您使用簡單的語句定義的對象變成與數據庫字段的複雜掛鉤。
Django經過公開一個簡單的API並使用元類,從該API從新建立代碼來完成幕後的實際工做,使看起來複雜的事情變得簡單。
首先,您知道類是能夠建立實例的對象。
實際上,類自己就是實例。元類。
>>> class Foo(object): pass>>> id(Foo)142630324
一切都是Python中的對象,它們都是類的實例或元類的實例。
除了type
。
type
其實是它本身的元類。這不是您能夠在純Python中複製的東西,而是經過在實現級別上做弊來完成的。
其次,元類很複雜。您可能不但願將它們用於很是簡單的類更改。您可使用兩種不一樣的技術來更改類:
歡迎點贊,收藏,關注,三連擊,謝謝,今天文章就到此結束了