W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
你的程序包含一個很大的類繼承體系,你希望強制執(zhí)行某些編程規(guī)約(或者代碼診斷)來幫助程序員保持清醒。
如果你想監(jiān)控類的定義,通??梢酝ㄟ^定義一個元類。一個基本元類通常是繼承自 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
為了使用這個元類,你通常要將它放到到一個頂級父類定義中,然后其他的類繼承這個頂級父類。例如:
class Root(metaclass=MyMeta):
pass
class A(Root):
pass
class B(Root):
pass
元類的一個關(guān)鍵特點是它允許你在定義的時候檢查類的內(nèi)容。在重新定義 __init__()
方法中,你可以很輕松的檢查類字典、父類等等。并且,一旦某個元類被指定給了某個類,那么就會被繼承到所有子類中去。因此,一個框架的構(gòu)建者就能在大型的繼承體系中通過給一個頂級父類指定一個元類去捕獲所有下面子類的定義。
作為一個具體的應(yīng)用例子,下面定義了一個元類,它會拒絕任何有混合大小寫名字作為方法的類定義(可能是想氣死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
作為更高級和實用的例子,下面有一個元類,它用來檢測重載方法,確保它的調(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
如果你運行這段代碼,就會得到下面這樣的輸出結(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)
這種警告信息對于捕獲一些微妙的程序bug是很有用的。例如,如果某個代碼依賴于傳遞給方法的關(guān)鍵字參數(shù),那么當子類改變參數(shù)名字的時候就會調(diào)用出錯。
在大型面向?qū)ο蟮某绦蛑?,通常將類的定義放在元類中控制是很有用的。元類可以監(jiān)控類的定義,警告編程人員某些沒有注意到的可能出現(xiàn)的問題。
有人可能會說,像這樣的錯誤可以通過程序分析工具或IDE去做會更好些。誠然,這些工具是很有用。但是,如果你在構(gòu)建一個框架或函數(shù)庫供其他人使用,那么你沒辦法去控制使用者要使用什么工具。因此,對于這種類型的程序,如果可以在元類中做檢測或許可以帶來更好的用戶體驗。
在元類中選擇重新定義 __new__()
方法還是 __init__()
方法取決于你想怎樣使用結(jié)果類。__new__()
方法在類創(chuàng)建之前被調(diào)用,通常用于通過某種方式(比如通過改變類字典的內(nèi)容)修改類的定義。而 __init__()
方法是在類被創(chuàng)建之后被調(diào)用,當你需要完整構(gòu)建類對象的時候會很有用。在最后一個例子中,這是必要的,因為它使用了 super()
函數(shù)來搜索之前的定義。它只能在類的實例被創(chuàng)建之后,并且相應(yīng)的方法解析順序也已經(jīng)被設(shè)置好了。
最后一個例子還演示了Python的函數(shù)簽名對象的使用。實際上,元類會管理中每個一個調(diào)用定義,搜索前一個定義(如果有的話),然后通過使用 inspect.signature()
來簡單的比較它們的調(diào)用簽名。
最后一點,代碼中有一行使用了 super(self, self)
并不是排版錯誤。當使用元類的時候,我們要時刻記住一點就是 self
實際上是一個類對象。因此,這條語句其實就是用來尋找位于繼承體系中構(gòu)建 self
父類的定義。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: