python中的一切都是對象,類自己也是對象,元類則是建立類對象的類python
加載模塊時,
python解釋器
自動建立模塊中的類對象,全局函數對象 新建實例時,用戶
主動調用類對象來建立實例 元類經常使用方法:__prepare__
,__new__
,__init__
,__call__
shell
"""
元類經常使用方法,以及類對象建立和實例對象建立的時間點
"""
class MyMeta(type):
def __new__(mcs, name, bases, cls_dict):
""" 元類建立類對象方法
:param name: 類對象的類名
:param bases: 類對象的父類
:param cls_dict: 類對象的命名空間
:return:
"""
print('2. call MyMeta method: new', mcs)
return super().__new__(mcs, name, bases, cls_dict)
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
""" 類建立前的準備工做,返回一個字典,爲空的命名空間對象
:param name:
:param bases:
:param kwargs:
:return:
"""
print('1. call MyMeta method: prepare', mcs)
return super().__prepare__(name, bases, **kwargs)
def __init__(cls, name, bases, namespace, **kwargs):
""" 類對象建立完成後的一些初始化工做
:param name:
:param bases:
:param namespace:
:param kwargs:
"""
print('3. call MyMeta method: init', cls)
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
""" 使用類對象建立實例時調用
:param args:
:param kwargs:
:return:
"""
print('1> call MyMeta method: call', cls)
return super().__call__(*args, **kwargs)
class MyClass(metaclass=MyMeta):
def __new__(cls, *args, **kwargs):
print('2> call MyClass method: new', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('3> call MyClass method: init', self)
super().__init__()
def __str__(self):
return 'hello world'
def instance_get():
print('call instance get method:')
instance = MyClass()
print(instance)
if __name__ == '__main__':
print('load model completed!\n---------------------------------------------------------\n')
instance_get()
複製代碼
上述代碼的輸出:數據庫
1. call MyMeta method: prepare <class '__main__.MyMeta'>
2. call MyMeta method: new <class '__main__.MyMeta'>
3. call MyMeta method: init <class '__main__.MyClass'>
load model completed!
---------------------------------------------------------
call instance get method:
1> call MyMeta method: call <class '__main__.MyClass'>
2> call MyClass method: new <class '__main__.MyClass'>
3> call MyClass method: init hello world
hello world
複製代碼
咱們能夠經過繼承元類 type
,對其中的一些方法作處理,編寫咱們須要的定製化元類。元類在構建框架的時候用的較多,像django就使用了元類來構建orm框架(經過元類來感知字段的定義順序,從而順序的將字段映射到數據庫中。) 日常的開發中,可使用元類來建立單例、作對象緩存等,其它方式確定也能夠作這些工做,不過元類提供的方案比較便捷還比較優雅。django
經過上面的程序,咱們知道建立實例對象的入口方法是元類中的__call__
方法,咱們能夠經過覆寫這個方法來實現單例模式編程
""" 使用元類實現單例模式 注意: 這種寫法非線程安全! """
class Singleton(type):
def __init__(cls, *args, **kwargs):
cls._instance = None # 將實例定義爲本身的一個屬性
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
print('call Singleton call method')
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class SingletonCls(metaclass=Singleton):
def __init__(self):
print('call SingletonCls init method')
if __name__ == '__main__':
s1 = SingletonCls()
s2 = SingletonCls()
print(s1 is s2)
複製代碼
如下爲該腳本的輸出:緩存
call Singleton call method
call SingletonCls init method
call Singleton call method
True
複製代碼
對象建立代價比較打的時候,作對象緩存是個不錯的選擇,不少緩存是用專門的緩存池來完成,這裏也能夠直接用元類來作緩存。安全
import weakref
class CacheMeta(type):
""" 使用元類作對象緩存處理 """
def __init__(cls, name, bases, namespace, **kwargs):
super().__init__(name, bases, namespace)
cls._cache = weakref.WeakValueDictionary()
def __call__(cls, *args):
# 傳入的參數相同的,就給同一個對象,若是對應參數的對象還沒建立就先建立對象
if args in cls._cache:
return cls._cache[args]
obj = super().__call__(*args)
cls._cache[args] = obj
return obj
class CacheClass(metaclass=CacheMeta):
def __init__(self, name):
print('Creating instance({!r})'.format(name))
self.name = name
if __name__ == '__main__':
o1 = CacheClass('hello')
o2 = CacheClass('world')
o3 = CacheClass('hello')
print(o1 is o2)
print(o1 is o3)
複製代碼
如下爲該腳本輸出bash
Creating instance('hello')
Creating instance('world')
False
True
複製代碼
使用元類能夠檢查子類的方法前面是否和父類的保持一直,若是要強迫繼承時簽名不能修改,這個是一個操做方式。平時使用IDE在進行編碼的時候,若是參數和父類不一致(參數名不一致不會提示),是有提示的,因此這點不用太過在乎。框架
from inspect import signature
import logging
class MatchSignaturesMeta(type):
def __init__(cls, classname, bases, clsdict):
super().__init__(classname, bases, clsdict)
sup = super(cls, cls)
for name, value in clsdict.items():
if name.startswith('_') or not callable(value):
continue
sup_func = getattr(sup, name, None)
if sup_func:
sup_sig = signature(sup_func)
val_sig = signature(value)
if sup_sig != val_sig:
logging.warning(
'Signature mismatch in %s. %s != %s',
value.__qualname__,
sup_sig,
val_sig
)
class Root(metaclass=MatchSignaturesMeta):
pass
class A(Root):
def foo(self, x, y):
pass
def spam(self, x, *, z):
pass
class B(A):
def foo(self, a, b):
pass
def spam(self, x, z):
pass
b = B()
複製代碼
在初始化的時候,日誌裏面會打印以下內容:函數
WARNING:root:Signature mismatch in B.foo. (self, x, y) != (self, a, b)
WARNING:root:Signature mismatch in B.spam. (self, x, *, z) != (self, x, z)
複製代碼
建立類的時候,是將寫在類下面的屬性、方法和類對象(經過描述器協議)綁定起來,python提供了手動實現這個過程的方式,這種操做有點相似於反射。
import abc
import types
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
cls_dict = {
'__init__': __init__,
'cost': cost,
}
Stock = types.new_class(
'Stock', # 類名
(), # 父類
{'metaclass': abc.ABCMeta}, # 元類及類定義參數
lambda ns: ns.update(cls_dict) # ns 爲 prepare 方法(見上)返回的字典對象
)
Stock.__module__ = __name__
s = Stock('abc', 200, 35)
print(s)
複製代碼
打印出來的結果代表,s就是類Stock的一個對象,這個和直接將Stock定義爲class是同樣的效果。
<__main__.Stock object at 0x7f153c19bb00>
複製代碼
參考:
python cookbook 第九章; python高級編程 第三章