元類 在 Python中,實例對象是由類生成的,而類本身也是可以被傳遞和自省的對象。那麼類對象是用什麼創建和生成的呢?答案是元類,元類就是一種知道如何創建和管理類的對象。 讓我們回顧一個內置函數type(),type不僅可以返回對象的類型,而且可以使用類名稱、基類元組、類主體定義的字典作為參數來創建 ...
元類
在 Python中,實例對象是由類生成的,而類本身也是可以被傳遞和自省的對象。那麼類對象是用什麼創建和生成的呢?答案是元類,元類就是一種知道如何創建和管理類的對象。
讓我們回顧一個內置函數type(),type不僅可以返回對象的類型,而且可以使用類名稱、基類元組、類主體定義的字典作為參數來創建一個新類對象:
>>> Foo = type('Foo',(object,),{'foo':lambda self:'foo'}) >>> Foo <class '__main__.Foo'> >>> type(Foo) <type 'type'>
實際上,新型類的預設元類就是type,類可以用__metaclass__類變數顯示的指定元類,上述代碼功能與下述相同:
class Foo(): __metaclass__ = type def foo(self): return 'foo'
如果沒有顯式的指定元類,class語句會檢查基類元組中的第一個基類的元類,比如新型類都是繼承object類的,所以新型類與object類的元類相同,為type,繼承object而不顯式的指定元類:
class Foo(object): def foo(self): return 'foo'
如果沒有指定基類,class語句會檢查全局變數__metaclass__,如果沒有找到__metaclass__值,Python會使用預設的元類。
在python 2中,預設的元類是types.ClassType,就是所謂的舊樣式類。python2.2以後已不提倡使用,比如不指定元類並且不繼承object基類:
class Foo(): def foo(self): return 'foo'
>>> import types >>> isinstance(Foo, types.ClassType) True
python 3以後,預設的元類皆為type了,顯式定義元類的時候需要在基類元組中提供metaclass關鍵字,class Foo(metaclass=type)如此定義。
使用元類的時候,一般會自定義一個繼承自type的子類,並重新實現__init__()與__new__()方法:
class ExampleType(type): def __new__(cls, name, bases, dct): print 'create class %s'%name return type.__new__(cls, name, bases, dct) def __init__(cls, name, bases, dct): print 'Init class %s'%name type.__init__(cls, name, bases, dct) class Foo(object): __metaclass__ = ExampleType >>> create class Foo Init class Foo >>> Foo <class '__main__.Foo'>
可見,使用class語句定義類後,元類就使用傳遞給元類的類名稱、基類元組和類方法字典創建類。
因為元類創建的實例是類對象,所以__init__方法的第一個參數按慣例寫為cls,其實與self功能相同。
面向切麵編程
在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程稱為面向切麵的編程(AOP)。
簡單地說,如果不同的類要實現相同的功能,可以將其中相同的代碼提取到一個切片中,等到需要時再切入到對象中去。這些相同的代碼片段稱為切麵,而切入到哪些類、哪些方法則叫切入點。
比如,要為每個類方法記錄日誌,在python中一個可行的方法是使用裝飾器:
def trace(func): def callfunc(self, *args, **kwargs): debug_log = open('debug_log.txt', 'a') debug_log.write('Calling %s: %s ,%s\n'%(func.__name__, args, kwargs)) result = func(self, *args, **kwargs) debug_log.write('%s returned %s\n'%(func.__name__, result)) debug_log.close() return result return callfunc def logcls(cls): for k, v in cls.__dict__.items(): if k.startswith('__'): continue if not callable(v): continue setattr(cls, k, trace(v)) return cls @logcls class Foo(object): num = 0 def spam(self): Foo.num += 1 return Foo.num
另外一個可行的方法就是使用元類了:
def trace(func): def callfunc(self, *args, **kwargs): debug_log = open('debug_log.txt', 'a') debug_log.write('Calling %s: %s ,%s\n'%(func.__name__, args, kwargs)) result = func(self, *args, **kwargs) debug_log.write('%s returned %s\n'%(func.__name__, result)) debug_log.close() return result return callfunc class LogMeta(type): def __new__(cls, name, bases, dct): for k, v in dct.items(): if k.startswith('__'): continue if not callable(v): continue dct[k] = trace(v) return type.__new__(cls, name, bases, dct) class Foo(object): __metaclass__ = LogMeta num = 0 def spam(self): Foo.num += 1 return Foo.num
元類的一個主要用途就是檢查收集或者更改類定義的內容,包括類屬性、類方法、描述符等等。
元類與基類
元類中除了可以定義__init__和__new__方法外,還可以定義其它的屬性和方法:
class ExaMeta(type): name = 'ExaMeta' def get_cls_name(cls): print cls.__name__ class Foo(object): __metaclass__ = ExaMeta
那麼,類可不可以訪問元類定義的方法和屬性呢?
>>> Foo.get_cls_name() Foo >>> Foo.name 'ExaMeta'
這很好理解,類Foo是元類的一個實例,在實例的__dict__中查找不到要查詢的屬性時,就會到實例所屬的類字典中去查找,而元類正是定義類Foo的類。
可以再嘗試下使用類Foo的實例去訪問元類的屬性或者方法:
>>> Foo().get_cls_name() AttributeError: 'Foo' object has no attribute 'get_cls_name' >>> Foo().name AttributeError: 'Foo' object has no attribute 'name'
顯然不能訪問。
查找一個不與實例關聯的屬性時,即先在實例的類中查找,然後再在從所有的基類中查找,查找的順序可以用__mro__屬性查看:
>>> Foo.__mro__ (<class '__main__.Foo'>, <type 'object'>)
元類並不在其中,畢竟,類與元類不是繼承關係,而是實例與類的創造關係。
元類屬性的可用性是不會傳遞的,也就是說,元類的屬性是對它的類實例是可用的,但是對它的類實例的實例是不可用的,這正是元類與基類的主要不同。
有時候,一個類會同時有元類和基類:
class M(type): name = 'M' class B(object): name = 'B' class A(B): __metaclass__ = M
屬性訪問是這樣的:
>>> A.name 'B' >>> A().name 'B'
可見類會先到繼承的基類中去查找屬性。
元類衝突
假如有兩個不同元類的類,要生成一個繼承這兩個類的子類,會產生什麼情況呢?
class MA(type): pass class A(object): __metaclass__ = MA class MB(type): pass class B(object): __metaclass__ = MB class C(A, B): pass
結果會報錯,提示元類衝突:
TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
我們需要手動構造新子類的元類,讓新子類的元類繼承自A和B的元類:
class MC(MA, MB): pass class C(A, B): __metaclass__ = MC