術語元編程是指程序具備瞭解或操縱自身的潛力。Python支持一種稱爲metaclasses的類的元編程形式。html
元類是一個深奧的OOP概念,幾乎隱藏在全部Python代碼以後。不管您是否知道,都在使用它們。在大多數狀況下,您無需意識到這一點。大多數Python程序員不多(即便有的話)也沒必要考慮元類。python
可是,當須要時,Python提供了並不是全部面向對象的語言都支持的功能:您能夠深刻了解並自定義元類。自定義元類的使用引發了一些爭議,正如Python 禪意做者Tim Peters所引用的那樣:程序員
元類具備比99%的用戶應該擔憂的更深的魔力。若是您想知道是否須要它們,則不須要(實際上須要它們的人確定會知道他們須要它們,而且不須要解釋緣由)。」— 蒂姆·彼得斯shell
有一些使用者(Pythonista - 衆所周知的Python愛好者)認爲永遠不要使用自定義元類。這可能有點遠,可是極可能不須要自定義元類。若是不是很明顯有問題須要解決,那麼若是以更簡單的方式解決問題,它可能會更乾淨,更易讀。編程
儘管如此,理解 Python 元類仍是值得的,由於經過元類能夠更好地理解Python 類的內部。可能有一天會遇到一種狀況:只須要一個自定義元類便可以解決問題。函數
在Python領域中,類能夠是兩個變體之一。還沒有肯定官方術語,所以將它們非正式地稱爲舊類和新類。ui
對於老式的類,類和類型不是一回事。老式類的實例始終由稱爲的單個內置類型實現instance
。若是obj
是老式類的實例,則obj.__class__
指定該類,但type(obj)
始終爲instance
。如下示例取自 Python 2.7:this
>>> class Foo: ... pass ... >>> x = Foo() >>> x.__class__ <class __main__.Foo at 0x000000000535CC48> >>> type(x) <type 'instance'>
新型類統一了類和類型的概念。若是obj
是新型類的實例,type(obj)
則與相同obj.__class__
:spa
>>> class Foo: ... pass >>> obj = Foo() >>> obj.__class__ <class '__main__.Foo'> >>> type(obj) <class '__main__.Foo'> >>> obj.__class__ is type(obj) True
>>> n = 5 >>> d = { 'x' : 1, 'y' : 2 } >>> class Foo: ... pass ... >>> x = Foo() >>> for obj in (n, d, x): ... print(type(obj) is obj.__class__) ... True True True
在Python 3中,全部類都是新型類。所以,在Python 3中,能夠互換地引用對象的類型及其類是合理的。設計
注意:在Python 2中,默認狀況下,類爲舊樣式。在Python 2.2以前,根本不支持新型類。從Python 2.2開始,能夠建立它們,但必須將其顯式聲明爲new-style。
請記住,在Python中,一切都是對象。類也是對象。結果,一個類必須具備一個類型。什麼是課程類型?
考慮如下:
>>> class Foo: ... pass ... >>> x = Foo() >>> type(x) <class '__main__.Foo'> >>> type(Foo) <class 'type'>
如您所料,type x
是class Foo
。可是Foo
,類自己的類型是type
。一般,任何新式類的類型都是type
。
您熟悉的內置類的類型也是type
:
>>> for t in int, float, dict, list, tuple: ... print(type(t)) ... <class 'type'> <class 'type'> <class 'type'> <class 'type'> <class 'type'>
就此而言,類型type
也是type
如此(是的,確實):
>>> type(type) <class 'type'>
type
是一個元類,其中的類是實例。就像普通對象是類的實例同樣,Python中的任何新式類以及Python 3中的任何類都是type
元類的實例。
在上述狀況下:
x
是class的實例Foo
。Foo
是type
元類的實例。type
也是type
元類的實例,所以它也是自身的實例。type()
當傳遞一個參數時,內置函數將返回對象的類型。對於新型類,一般與對象的__class__
屬性相同:
>>> type(3) <class 'int'> >>> type(['foo', 'bar', 'baz']) <class 'list'> >>> t = (1, 2, 3, 4, 5) >>> type(t) <class 'tuple'> >>> class Foo: ... pass ... >>> type(Foo()) <class '__main__.Foo'>
還可使用三個參數進行調用type(<name>, <bases>, <dct>)
:
<name>
指定類名稱。這成爲__name__
該類的屬性。<bases>
指定從其繼承的基類的元組。這成爲__bases__
該類的屬性。<dct>
指定一個包含類主體定義的名稱空間字典。這成爲__dict__
該類的屬性。type()
以這種方式進行調用會建立該type
元類的新實例。換句話說,它動態建立一個新類。
在如下每一個示例中,最上面的代碼段使用來動態定義一個類type()
,而下面的代碼段則使用該class
語句以一般的方式定義該類。在每種狀況下,這兩個代碼段在功能上是等效的。
在第一個示例中,傳遞給的<bases>
和<dct>
參數type()
均爲空。沒有指定任何父類的繼承,而且最初在命名空間字典中未放置任何內容。這是最簡單的類定義:
>>> Foo = type('Foo', (), {}) >>> x = Foo() >>> x <__main__.Foo object at 0x04CFAD50>
>>> class Foo: ... pass ... >>> x = Foo() >>> x <__main__.Foo object at 0x0370AD50>
這裏<bases>
是一個具備單個元素的元組Foo
,指定Bar
從其繼承的父類。屬性attr
最初放置在名稱空間字典中:
>>> Bar = type('Bar', (Foo,), dict(attr=100)) >>> x = Bar() >>> x.attr 100 >>> x.__class__ <class '__main__.Bar'> >>> x.__class__.__bases__ (<class '__main__.Foo'>,)
>>> class Bar(Foo): ... attr = 100 ... >>> x = Bar() >>> x.attr 100 >>> x.__class__ <class '__main__.Bar'> >>> x.__class__.__bases__ (<class '__main__.Foo'>,)
此次,又<bases>
是空的。經過<dct>
參數將兩個對象放入名稱空間字典中。第一個是名爲的屬性attr
,第二個是名爲的函數attr_val
,該函數成爲已定義類的方法:
>>> Foo = type( ... 'Foo', ... (), ... { ... 'attr': 100, ... 'attr_val': lambda x : x.attr ... } ... ) >>> x = Foo() >>> x.attr 100 >>> x.attr_val() 100
>>> class Foo: ... attr = 100 ... def attr_val(self): ... return self.attr ... >>> x = Foo() >>> x.attr 100 >>> x.attr_val() 100
lambda
在Python中只能定義很是簡單的函數。在下面的示例中,在外部定義了一個稍微複雜一點的函數,而後attr_val
經過名稱在名稱空間字典中將其分配給f
:
>>> def f(obj): ... print('attr =', obj.attr) ... >>> Foo = type( ... 'Foo', ... (), ... { ... 'attr': 100, ... 'attr_val': f ... } ... ) >>> x = Foo() >>> x.attr 100 >>> x.attr_val() attr = 100
>>> def f(obj): ... print('attr =', obj.attr) ... >>> class Foo: ... attr = 100 ... attr_val = f ... >>> x = Foo() >>> x.attr 100 >>> x.attr_val() attr = 100
再次考慮這個陳舊的示例:
>>> class Foo: ... pass ... >>> f = Foo()
該表達式Foo()
建立class的新實例Foo
。解釋器遇到時Foo()
,將發生如下狀況:
__call__()
方法Foo
被調用。因爲Foo
是標準的新型類,所以其父類是type
元類,所以調用type
的__call__()
方法。該__call__()
方法依次調用如下內容:
__new__()
__init__()
若是Foo
未定義__new__()
和__init__()
,則默認方法繼承自Foo
的祖先。可是,若是Foo
確實定義了這些方法,則它們會覆蓋祖先中的方法,從而在實例化時容許自定義行爲Foo
。
在下面,定義了一個自定義方法,並將new()
其指定爲__new__()
用於的方法Foo
:
>>> def new(cls): ... x = object.__new__(cls) ... x.attr = 100 ... return x ... >>> Foo.__new__ = new >>> f = Foo() >>> f.attr 100 >>> g = Foo() >>> g.attr 100
這會修改類的實例化行爲Foo
:每次Foo
建立實例時,默認狀況下都會使用名爲的屬性對其進行初始化,該屬性attr
的值爲100
。(這樣的代碼一般會出如今__init__()
方法中,而一般不會出如今方法中__new__()
,這個示例是爲演示目的而設計的)
如今,正如已經重申的,類也是對象。假設您要在建立相似的類時,能夠以相似的自定義方式完成Foo
實例化行爲。若是要遵循上述模式,須要再次定義一個自定義方法,並將其分配__new__()
爲該類Foo
是實例的方法。Foo
是type
元類的實例,所以代碼以下所示:
# Spoiler alert: This doesn't work! >>> def new(cls): ... x = type.__new__(cls) ... x.attr = 100 ... return x ... >>> type.__new__ = new Traceback (most recent call last): File "<pyshell#77>", line 1, in <module> type.__new__ = new TypeError: can't set attributes of built-in/extension type 'type'
如您所見,除了不能從新分配元類type
的__new__()
方法。Python不容許這樣作。
這可能也是同樣。type
是從其派生全部新樣式類的元類。不管如何,您真的不該該對此亂搞。可是,若是要自定義類的實例化,那又有什麼辦法?
一種可能的解決方案是自定義元類。本質上,您沒必要定義type
元類,而能夠定義本身的元類,該元類是從派生的type
,而後您就可使用元類。
第一步是定義一個從派生的元類,type
以下所示:
>>> class Meta(type): ... def __new__(cls, name, bases, dct): ... x = super().__new__(cls, name, bases, dct) ... x.attr = 100 ... return x ...
定義class Meta(type):
聲明,指定Meta
從type
派生。因爲type
是一個元類,所以也構成Meta
了一個元類。
請注意,__new__()
已爲定義了自定義方法Meta
。沒法type
直接對元類執行此操做。該__new__()
方法執行如下操做:
type
)的代理--super()
的__new__()
方法建立一個新的類attr
給類,其值爲100
如今,巫毒教的另外一半:定義一個新類Foo
,並指定其元類是自定義元類Meta
,而不是標準元類type
。使用metaclass
類定義中的關鍵字來完成此操做,以下所示:
>>> class Foo(metaclass=Meta): ... pass ... >>> Foo.attr 100
瞧! Foo
已經拿到Meta
元類的attr
自動屬性。固然,相似定義的任何其餘類也將這樣作:
>>> class Bar(metaclass=Meta): ... pass ... >>> class Qux(metaclass=Meta): ... pass ... >>> Bar.attr, Qux.attr (100, 100)
與類充當建立對象的模板的方式相同,元類充當建立類的模板。元類有時稱爲類工廠)。
比較如下兩個示例:
對象工廠:
>>> class Foo: ... def __init__(self): ... self.attr = 100 ... >>> x = Foo() >>> x.attr 100 >>> y = Foo() >>> y.attr 100 >>> z = Foo() >>> z.attr 100
類工廠:
>>> class Meta(type): ... def __init__( ... cls, name, bases, dct ... ): ... cls.attr = 100 ... >>> class X(metaclass=Meta): ... pass ... >>> X.attr 100 >>> class Y(metaclass=Meta): ... pass ... >>> Y.attr 100 >>> class Z(metaclass=Meta): ... pass ... >>> Z.attr 100
就像上面的類工廠示例同樣簡單,這是元類如何工做的本質。它們容許自定義類如何實例化。
儘管如此,attr
在每一個新建立的類上賦予自定義屬性仍然有不少麻煩。您真的須要一個元類嗎?
在 Python 中,至少有幾種其餘方法能夠有效地完成同一件事:
簡單繼承:
>>> class Base: ... attr = 100 ... >>> class X(Base): ... pass ... >>> class Y(Base): ... pass ... >>> class Z(Base): ... pass ... >>> X.attr 100 >>> Y.attr 100 >>> Z.attr 100
類裝飾器:
>>> def decorator(cls): ... class NewClass(cls): ... attr = 100 ... return NewClass ... >>> @decorator ... class X: ... pass ... >>> @decorator ... class Y: ... pass ... >>> @decorator ... class Z: ... pass ... >>> X.attr 100 >>> Y.attr 100 >>> Z.attr 100
正如蒂姆·彼得斯(Tim Peters)所建議的那樣,元類很容易進入「從問題中尋找解決方案」的境界。一般不須要建立自定義元類。若是眼前的問題能夠用更簡單的方法解決,那就應該這樣解決。儘管如此,理解元類仍是有好處的,這樣您就能夠大體理解Python類,並能夠識別什麼時候才真正適合使用元類。
🐍Python技巧💌
關於做者:
約翰·斯圖茲
奧爾登·桑托斯
丹·巴德
喬安娜·賈布隆斯基
❤️快樂Pythoning!
>>> import this