W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
你的程序包含一個(gè)很大的類繼承體系,你希望強(qiáng)制執(zhí)行某些編程規(guī)約(或者代碼診斷)來幫助程序員保持清醒。
如果你想監(jiān)控類的定義,通常可以通過定義一個(gè)元類。一個(gè)基本元類通常是繼承自 type
并重定義它的 __new__()
方法或者是 __init__()
方法。比如:
class MyMeta(type):
def __new__(self, clsname, bases, clsdict):
# clsname is name of class being defined
# bases is tuple of base classes
# clsdict is class dictionary
return super().__new__(cls, clsname, bases, clsdict)
另一種是,定義 __init__()
方法:
class MyMeta(type):
def __init__(self, clsname, bases, clsdict):
super().__init__(clsname, bases, clsdict)
# clsname is name of class being defined
# bases is tuple of base classes
# clsdict is class dictionary
為了使用這個(gè)元類,你通常要將它放到到一個(gè)頂級(jí)父類定義中,然后其他的類繼承這個(gè)頂級(jí)父類。例如:
class Root(metaclass=MyMeta):
pass
class A(Root):
pass
class B(Root):
pass
元類的一個(gè)關(guān)鍵特點(diǎn)是它允許你在定義的時(shí)候檢查類的內(nèi)容。在重新定義 __init__()
方法中,你可以很輕松的檢查類字典、父類等等。并且,一旦某個(gè)元類被指定給了某個(gè)類,那么就會(huì)被繼承到所有子類中去。因此,一個(gè)框架的構(gòu)建者就能在大型的繼承體系中通過給一個(gè)頂級(jí)父類指定一個(gè)元類去捕獲所有下面子類的定義。
作為一個(gè)具體的應(yīng)用例子,下面定義了一個(gè)元類,它會(huì)拒絕任何有混合大小寫名字作為方法的類定義(可能是想氣死Java程序員^_^):
class NoMixedCaseMeta(type):
def __new__(cls, clsname, bases, clsdict):
for name in clsdict:
if name.lower() != name:
raise TypeError('Bad attribute name: ' + name)
return super().__new__(cls, clsname, bases, clsdict)
class Root(metaclass=NoMixedCaseMeta):
pass
class A(Root):
def foo_bar(self): # Ok
pass
class B(Root):
def fooBar(self): # TypeError
pass
作為更高級(jí)和實(shí)用的例子,下面有一個(gè)元類,它用來檢測(cè)重載方法,確保它的調(diào)用參數(shù)跟父類中原始方法有著相同的參數(shù)簽名。
from inspect import signature
import logging
class MatchSignaturesMeta(type):
def __init__(self, clsname, bases, clsdict):
super().__init__(clsname, bases, clsdict)
sup = super(self, self)
for name, value in clsdict.items():
if name.startswith('_') or not callable(value):
continue
# Get the previous definition (if any) and compare the signatures
prev_dfn = getattr(sup,name,None)
if prev_dfn:
prev_sig = signature(prev_dfn)
val_sig = signature(value)
if prev_sig != val_sig:
logging.warning('Signature mismatch in %s. %s != %s',
value.__qualname__, prev_sig, val_sig)
# Example
class Root(metaclass=MatchSignaturesMeta):
pass
class A(Root):
def foo(self, x, y):
pass
def spam(self, x, *, z):
pass
# Class with redefined methods, but slightly different signatures
class B(A):
def foo(self, a, b):
pass
def spam(self,x,z):
pass
如果你運(yùn)行這段代碼,就會(huì)得到下面這樣的輸出結(jié)果:
WARNING:root:Signature mismatch in B.spam. (self, x, *, z) != (self, x, z)
WARNING:root:Signature mismatch in B.foo. (self, x, y) != (self, a, b)
這種警告信息對(duì)于捕獲一些微妙的程序bug是很有用的。例如,如果某個(gè)代碼依賴于傳遞給方法的關(guān)鍵字參數(shù),那么當(dāng)子類改變參數(shù)名字的時(shí)候就會(huì)調(diào)用出錯(cuò)。
在大型面向?qū)ο蟮某绦蛑校ǔ㈩惖亩x放在元類中控制是很有用的。元類可以監(jiān)控類的定義,警告編程人員某些沒有注意到的可能出現(xiàn)的問題。
有人可能會(huì)說,像這樣的錯(cuò)誤可以通過程序分析工具或IDE去做會(huì)更好些。誠然,這些工具是很有用。但是,如果你在構(gòu)建一個(gè)框架或函數(shù)庫供其他人使用,那么你沒辦法去控制使用者要使用什么工具。因此,對(duì)于這種類型的程序,如果可以在元類中做檢測(cè)或許可以帶來更好的用戶體驗(yàn)。
在元類中選擇重新定義 __new__()
方法還是 __init__()
方法取決于你想怎樣使用結(jié)果類。__new__()
方法在類創(chuàng)建之前被調(diào)用,通常用于通過某種方式(比如通過改變類字典的內(nèi)容)修改類的定義。而 __init__()
方法是在類被創(chuàng)建之后被調(diào)用,當(dāng)你需要完整構(gòu)建類對(duì)象的時(shí)候會(huì)很有用。在最后一個(gè)例子中,這是必要的,因?yàn)樗褂昧?super()
函數(shù)來搜索之前的定義。它只能在類的實(shí)例被創(chuàng)建之后,并且相應(yīng)的方法解析順序也已經(jīng)被設(shè)置好了。
最后一個(gè)例子還演示了Python的函數(shù)簽名對(duì)象的使用。實(shí)際上,元類會(huì)管理中每個(gè)一個(gè)調(diào)用定義,搜索前一個(gè)定義(如果有的話),然后通過使用 inspect.signature()
來簡(jiǎn)單的比較它們的調(diào)用簽名。
最后一點(diǎn),代碼中有一行使用了 super(self, self)
并不是排版錯(cuò)誤。當(dāng)使用元類的時(shí)候,我們要時(shí)刻記住一點(diǎn)就是 self
實(shí)際上是一個(gè)類對(duì)象。因此,這條語句其實(shí)就是用來尋找位于繼承體系中構(gòu)建 self
父類的定義。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: