Python黑魔法之metaclass詳情
關(guān)于Python 黑魔法 metaclass 的兩種極端觀點(diǎn):
- 這種特性太牛逼了,是無(wú)所不能的阿拉丁神燈,必須找機(jī)會(huì)用上才能顯示自己的
Python實(shí)力。 - 這個(gè)特性太危險(xiǎn),會(huì)蠱惑人心去濫用,一旦打開(kāi)就會(huì)釋放惡魔,讓代碼難以維護(hù)。
今天我們就來(lái)看看,metaclass 到底是阿拉丁神燈,還是潘多拉魔盒。
一、什么是 metaclass
很多書(shū)都會(huì)翻譯成 元類(lèi),僅從字面理解, meta 的確是元,本源,翻譯沒(méi)毛病。但理解時(shí),應(yīng)該把元理解為描述數(shù)據(jù)的超越數(shù)據(jù),事實(shí)上,
metaclass的 meta 起源于希臘詞匯meta,包含兩種意思:
- “
Beyond”,例如技術(shù)詞匯metadata,意思是描述數(shù)據(jù)的超越數(shù)據(jù)。- “
Change”,例如技術(shù)詞匯metamorphosis,意思是改變的形態(tài)。因此可以理解為
metaclass為描述類(lèi)的超類(lèi),同時(shí)可以改變子類(lèi)的形態(tài)。你可能會(huì)問(wèn)了,這和元數(shù)據(jù)的定義差不多么,這種特性在編程中有什么用?用處非常大。在沒(méi)有
metaclass的情況下,子類(lèi)繼承父類(lèi),父類(lèi)是無(wú)法對(duì)子類(lèi)執(zhí)行操作的,但有了metaclass,就可以對(duì)子類(lèi)進(jìn)行操作,就像裝飾器那樣可以動(dòng)態(tài)定制和修改被裝飾的類(lèi),metaclass可以動(dòng)態(tài)的定制或修改繼承它的子類(lèi)。
二、metaclass 能解決什么問(wèn)題?
你已經(jīng)知道了
metaclass可以像裝飾器那樣定制和修改繼承它的子類(lèi),這里就說(shuō)下它能解決什么實(shí)際問(wèn)題。比方說(shuō),在一個(gè)智能語(yǔ)音助手的大型項(xiàng)目中,我們有 1 萬(wàn)個(gè)語(yǔ)音對(duì)話場(chǎng)景,每一個(gè)場(chǎng)景都是不同團(tuán)隊(duì)開(kāi)發(fā)的。作為智能語(yǔ)音助手的核心團(tuán)隊(duì)成員,你不可能去了解每個(gè)子場(chǎng)景的實(shí)現(xiàn)細(xì)節(jié)。在動(dòng)態(tài)配置實(shí)驗(yàn)不同場(chǎng)景時(shí),經(jīng)常是今天要實(shí)驗(yàn)場(chǎng)景 A 和 B 的配置,明天實(shí)驗(yàn) B 和 C 的配置,光配置文件就有幾萬(wàn)行量級(jí),工作量不可謂不小。而應(yīng)用這樣的動(dòng)態(tài)配置理念,我就可以讓引擎根據(jù)我的文本配置文件,動(dòng)態(tài)加載所需要的
Python類(lèi)。如果你還不是很清楚,那么
YAML你應(yīng)該知道,它是一個(gè)家喻戶曉的 Python 工具,可以方便地序列化和反序列化數(shù)據(jù),YAMLObject可以讓它的任意子類(lèi)支持序列化和反序列化(serialization & deserialization)。序列化和反序列化:
- 序列化:當(dāng)程序運(yùn)行時(shí),所有的變量或者對(duì)象都是存儲(chǔ)到內(nèi)存中的,一旦程序調(diào)用完成,這些變量或者對(duì)象所占有的內(nèi)存都會(huì)被回收。而為了實(shí)現(xiàn)變量和對(duì)象持久化的存儲(chǔ)到磁盤(pán)中或在網(wǎng)絡(luò)上進(jìn)行傳輸,我們需要將變量或者對(duì)象轉(zhuǎn)化為二進(jìn)制流的方式。而將其轉(zhuǎn)化為二進(jìn)制流的過(guò)程就是序列化。
- 反序列化:而反序列化就是說(shuō)程序運(yùn)行的時(shí)候不能從磁盤(pán)中進(jìn)行讀取,需要將序列化的對(duì)象或者變量從磁盤(pán)中轉(zhuǎn)移到內(nèi)存中,同時(shí)也會(huì)將二進(jìn)制流轉(zhuǎn)換為原來(lái)的數(shù)據(jù)格式。我們把這一過(guò)程叫做反序列化。
現(xiàn)在你有 1 萬(wàn)個(gè)不同格式的 YAML 配置文件,本來(lái)你需要寫(xiě) 1 萬(wàn)個(gè)類(lèi)來(lái)加載這些配置文件,有了
metaclass,你只需要實(shí)現(xiàn)一個(gè)metaclass超類(lèi),然后再實(shí)現(xiàn)一個(gè)子類(lèi)繼承這個(gè)metaclass,就可以根據(jù)不同的配置文件自動(dòng)拉取不同的類(lèi),這極大地提高了效率。
三、通過(guò)一個(gè)實(shí)例來(lái)理解 metaclass
請(qǐng)手動(dòng)在 ipython 中搞代碼,看看每一步都輸出了什么,這樣可以徹底的理解類(lèi)的創(chuàng)建和實(shí)例化步驟。
In[15]: class Mymeta(type):
...: def __init__(self, name, bases, dic):
...: super().__init__(name, bases, dic)
...: print('===>Mymeta.__init__')
...: print(self.__name__)
...: print(dic)
...: print(self.yaml_tag)
...:
...: def __new__(cls, *args, **kwargs):
...: print('===>Mymeta.__new__')
...: print(cls.__name__)
...: return type.__new__(cls, *args, **kwargs)
...:
...: def __call__(cls, *args, **kwargs):
...: print('===>Mymeta.__call__')
...: obj = cls.__new__(cls)
...: cls.__init__(cls, *args, **kwargs)
...: return obj
...:
In[16]:
In[16]:
In[16]: class Foo(metaclass=Mymeta):
...: yaml_tag = '!Foo'
...:
...: def __init__(self, name):
...: print('Foo.__init__')
...: self.name = name
...:
...: def __new__(cls, *args, **kwargs):
...: print('Foo.__new__')
...: return object.__new__(cls)
...:
===>Mymeta.__new__
Mymeta
===>Mymeta.__init__
Foo
{'__module__': '__main__', '__qualname__': 'Foo', 'yaml_tag': '!Foo', '__init__': <function Foo.__init__ at 0x0000000007EF3828>, '__new__': <function Foo.__new__ at 0x0000000007EF3558>}
!Foo
In[17]: foo = Foo('foo')
===>Mymeta.__call__
Foo.__new__
Foo.__init__
In[18]:
從上面的運(yùn)行結(jié)果可以發(fā)現(xiàn)在定義 class Foo() 定義時(shí),會(huì)依次調(diào)用 MyMeta 的 __new__ 和 __init__ 方法構(gòu)建 Foo 類(lèi),然后在調(diào)用 foo = Foo() 創(chuàng)建類(lèi)的實(shí)例對(duì)象時(shí),才會(huì)調(diào)用 MyMeta 的 __call__ 方法來(lái)調(diào)用 Foo 類(lèi)的 __new__ 和 __init__ 方法。
把上面的例子運(yùn)行完之后就會(huì)明白很多了,正常情況下我們?cè)诟割?lèi)中是不能對(duì)子類(lèi)的屬性進(jìn)行操作,但是元類(lèi)可以。換種方式理解:元類(lèi)、裝飾器、類(lèi)裝飾器都可以歸為元編程。
四、Python 底層語(yǔ)言設(shè)計(jì)層面是如何實(shí)現(xiàn) metaclass 的?
要理解 metaclass 的底層原理,你需要深入理解 Python 類(lèi)型模型。下面,將分三點(diǎn)來(lái)說(shuō)明。
1、所有的 Python 的用戶定義類(lèi),都是 type 這個(gè)類(lèi)的實(shí)例。
可能會(huì)讓你驚訝,事實(shí)上,類(lèi)本身不過(guò)是一個(gè)名為 type 類(lèi)的實(shí)例。在 Python 的類(lèi)型世界里,type 這個(gè)類(lèi)就是造物的上帝。這可以在代碼中驗(yàn)證:
In [2]: # Python 3和Python 2類(lèi)似 ...: class MyClass: ...: pass ...: ...: instance = MyClass() ...: in [3]: type(instance) ...: Out[2]: __main__.MyClass In [4]: type(MyClass) ...: Out[4]: type In [5]:
你可以看到,instance 是 MyClass 的實(shí)例,而 MyClass 不過(guò)是“上帝” type 的實(shí)例。
2、用戶自定義類(lèi),只不過(guò)是 type 類(lèi)的 __call__ 運(yùn)算符重載
當(dāng)我們定義一個(gè)類(lèi)的語(yǔ)句結(jié)束時(shí),真正發(fā)生的情況,是 Python 調(diào)用 type 的 __call__ 運(yùn)算符。簡(jiǎn)單來(lái)說(shuō),當(dāng)你定義一個(gè)類(lèi)時(shí),寫(xiě)成下面這樣時(shí):
class MyClass:
data = 1
Python 真正執(zhí)行的是下面這段代碼:
class = type(classname, superclasses, attributedict)
這里等號(hào)右邊的 type(classname, superclasses, attributedict),就是 type 的 __call__ 運(yùn)算符重載,它會(huì)進(jìn)一步調(diào)用:
type.__new__(typeclass, classname, superclasses, attributedict) type.__init__(class, classname, superclasses, attributedict)
當(dāng)然,這一切都可以通過(guò)代碼驗(yàn)證,比如
In [5]: class MyClass:
...: data = 1
...:
...: instance = MyClass()
...:
In [6]: MyClass, instance
...:
Out[6]: (__main__.MyClass, <__main__.MyClass at 0x4ef5188>)
In [7]: instance.data
...:
Out[7]: 1
In [8]: MyClass = type('MyClass', (), {'data': 1})
...: instance = MyClass()
...:
In [9]: MyClass, instance
...:
Out[9]: (__main__.MyClass, <__main__.MyClass at 0x4f40748>)
In [10]: instance.data
...:
Out[10]: 1
In [11]:
由此可見(jiàn),正常的 MyClass 定義,和你手工去調(diào)用 type 運(yùn)算符的結(jié)果是完全一樣的。
3、,“超越變形”正常的類(lèi)
metaclass 是 type 的子類(lèi),通過(guò)替換 type 的 __call__ 運(yùn)算符重載機(jī)制,“超越變形”正常的類(lèi)
其實(shí),理解了以上幾點(diǎn),我們就會(huì)明白,正是 Python 的類(lèi)創(chuàng)建機(jī)制,給了 metaclass 大展身手的機(jī)會(huì)。
一旦你把一個(gè)類(lèi)型 MyClass 的 metaclass 設(shè)置成 MyMeta,MyClass 就不再由原生的 type 創(chuàng)建,而是會(huì)調(diào)用 MyMeta 的 __call__ 運(yùn)算符重載。
class = type(classname, superclasses, attributedict) # 變?yōu)榱? class = MyMeta(classname, superclasses, attributedict)
四、使用 metaclass 的風(fēng)險(xiǎn)
不過(guò),凡事有利必有弊,尤其是
metaclass這樣“逆天”的存在。正如你所看到的那樣,metaclass會(huì)"扭曲變形"正常的 Python 類(lèi)型模型。所以,如果使用不慎,對(duì)于整個(gè)代碼庫(kù)造成的風(fēng)險(xiǎn)是不可估量的。換句話說(shuō),
metaclass僅僅是給小部分Python開(kāi)發(fā)者,在開(kāi)發(fā)框架層面的Python庫(kù)時(shí)使用的。而在應(yīng)用層,metaclass往往不是很好的選擇。
總結(jié):
本文從 Python 類(lèi)創(chuàng)建的過(guò)程,幫助你理解 metaclass 的作用。
metaclass 是黑魔法,使用得當(dāng)就是天堂,反之就是地獄。
到此這篇關(guān)于Python黑魔法之metaclass詳情的文章就介紹到這了,更多相關(guān)Python黑魔法之metaclass內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解python 字符串和日期之間轉(zhuǎn)換 StringAndDate
這篇文章主要介紹了python 字符串和日期之間轉(zhuǎn)換 StringAndDate簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05
解決pyinstaller打包exe文件出現(xiàn)命令窗口一閃而過(guò)的問(wèn)題
今天小編就為大家分享一篇解決pyinstaller打包exe文件出現(xiàn)命令窗口一閃而過(guò)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-10-10
一起來(lái)學(xué)習(xí)一下python的數(shù)據(jù)類(lèi)型
這篇文章主要為大家詳細(xì)介紹了python的數(shù)據(jù)類(lèi)型,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下希望能夠給你帶來(lái)幫助2022-01-01
關(guān)于Python中幾個(gè)有趣的函數(shù)和推導(dǎo)式解析
這篇文章主要介紹了關(guān)于Python中幾個(gè)有趣的函數(shù)和推導(dǎo)式解析,推導(dǎo)式comprehensions,又稱解析式,是Python的一種獨(dú)有特性,推導(dǎo)式是可以從一個(gè)數(shù)據(jù)序列構(gòu)建另一個(gè)新的數(shù)據(jù)序列的結(jié)構(gòu)體,需要的朋友可以參考下2023-08-08
python實(shí)現(xiàn)連續(xù)變量最優(yōu)分箱詳解--CART算法
今天小編就為大家分享一篇python實(shí)現(xiàn)連續(xù)變量最優(yōu)分箱詳解--CART算法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
Python使用pytorch動(dòng)手實(shí)現(xiàn)LSTM模塊
這篇文章主要介紹了Python使用pytorch動(dòng)手實(shí)現(xiàn)LSTM模塊,LSTM是RNN中一個(gè)較為流行的網(wǎng)絡(luò)模塊。主要包括輸入,輸入門(mén),輸出門(mén),遺忘門(mén),激活函數(shù),全連接層(Cell)和輸出2022-07-07

