A metaclass is a class whose instances are classes. Like an "ordinary" class defines the behavior of instancs of the class,
a metaclass defines the behavior of classes and their instances.
metaclass的實(shí)例是class, 就像普通的class定義其instance一樣.
There are numberours use cases for metaclasses. Just to name a few:
* logging and profiling 日志和解析
* interface checking 接口檢查
* registering classes at creation time
* automatically adding new methods 自動(dòng)添加方法
* automatically propery creation 自動(dòng)創(chuàng)建屬性
* proxies 代理
* automatic resource locking/synchronization 自動(dòng)資源鎖和同步
metaclass內(nèi)置魔力方法研究(magic method)
Principially, metaclasses are defined like any other Python class, but they are classes that inherit from type.
原則上,metaclass定義想普通類一樣, 但他們繼承自type.
美化輸出函數(shù),主要用于打印函數(shù)內(nèi)部參數(shù)和內(nèi)部變量.以下演示都將用到此函數(shù)
from tabulate import tabulate #自動(dòng)輸出markdown格式表格
import pandas as pd
def pprint(name, **kwargs):
print('****')
print('*', '```' + name + '```', 'is called\n')
df = pd.DataFrame(kwargs.items(), columns=['param', 'value'])
print(tabulate(df, tablefmt='pipe', headers='keys', showindex=False))
print('****')
__init__ 和__new__
class LittleMeta(type):
def __init__(cls, clsname, superclasses, attributedict):
"""定義一個(gè)由此元類創(chuàng)建的class時(shí)觸發(fā)此__init__,
相當(dāng)于給class增加類屬性(class的attribute),就像
class A:
age = 18
"""
cls.age = 18
pprint('__init__', **locals())
def __new__(cls, clsname, superclasses, attributedict):
attributedict['name'] = clsname + '$tom'
pprint('__new__', **locals())
return type.__new__(cls, clsname, superclasses, attributedict)
# 定義一個(gè)繼承自str類, 并由LittleMeta創(chuàng)建的類
# 使用LittleMeta創(chuàng)建類的兩種方法
#方法1
class A(str, metaclass=LittleMeta):
pass
# 方法2
B = LittleMeta('B', (str,), {})
- 定義A類時(shí)
__new__is called
| param | value |
|---|---|
| cls | <class 'main.LittleMeta'> |
| clsname | A |
| superclasses | (<class 'str'>,) |
| attributedict | {'module': 'main', 'qualname': 'A', 'name': 'A$tom'} |
首先LittleMeta的
__new__被調(diào)用, 第一個(gè)參數(shù)是LittleMeta自己,這里的attributedict是A.__dict__,attributedict['name'] = clsname + '$tom'給類A增加
了一個(gè)name屬性.
- 定義class A時(shí)元類的
__init__被調(diào)用
| param | value |
|---|---|
| cls | <class 'main.A'> |
| clsname | A |
| superclasses | (<class 'str'>,) |
| attributedict | {'module': 'main', 'qualname': 'A', 'name': 'A$tom'} |
這里發(fā)現(xiàn)attributedict里面沒有age, 是因?yàn)橹挥性惖?code>__new__才能直接通過設(shè)置A的attributedict的方式給A增加屬性,在元類的
__init__通過cls.attributename = value的方式增加類屬性, 也就是說在類A定義之后增加到A.__dict__
可以通過print(A.__dict__) 觀察
- 函數(shù)式調(diào)用LittleMeta創(chuàng)建B時(shí)
__new__is called
| param | value |
|---|---|
| cls | <class 'main.LittleMeta'> |
| clsname | B |
| superclasses | (<class 'str'>,) |
| attributedict | {'name': 'B$tom'} |
- 函數(shù)式調(diào)用LittleMeta創(chuàng)建B時(shí)
__init__is called
| param | value |
|---|---|
| cls | <class 'main.B'> |
| clsname | B |
| superclasses | (<class 'str'>,) |
| attributedict | {'name': 'B$tom'} |
__call__
class LittleMeta(type):
def __init__(cls, clsname, superclasses, attributedict):
pprint('__init__', **locals())
def __new__(cls, clsname, superclasses, attributedict):
attributedict['name'] = clsname + '$tom'
pprint('__new__', **locals())
return type.__new__(cls, clsname, superclasses, attributedict)
def __call__(cls, *args, **kwargs):
"""when the instance of LittleMeta is called, in other words,
class is instantiate, e.g. a = A(),
this magic method __call__ is called, bind `run` method to cls:A
當(dāng)元類的實(shí)例被調(diào)用, 也就是classA被實(shí)例化的時(shí)候觸發(fā).
這里當(dāng)class A被實(shí)例化的時(shí)候增加一個(gè)run方法.聯(lián)想class的```__call__```使得其
實(shí)例可以被調(diào)用.
"""
cls.run = lambda self, x: print(f'{self} run {x} meters')
pprint('__call__', **locals())
return super().__call__(*args, **kwargs)
class S:
pass
class A(S, metaclass=LittleMeta):
pass
print(A.__dict__)
a = A() # trigger LittleMeta's __call__, 此時(shí)才給A綁定一個(gè)run方法
a.run(88)
print(A.__dict__)
-
__new__is called
| param | value |
|---|---|
| cls | <class 'main.LittleMeta'> |
| clsname | A |
| superclasses | (<class 'main.S'>,) |
| attributedict | {'module': 'main', 'qualname': 'A', 'name': 'A$tom'} |
-
__init__is called
| param | value |
|---|---|
| cls | <class 'main.A'> |
| clsname | A |
| superclasses | (<class 'main.S'>,) |
| attributedict | {'module': 'main', 'qualname': 'A', 'name': 'A$tom'} |
{'module': 'main', 'name': 'A$tom', 'doc': None}
-
__call__is called
| param | value |
|---|---|
| cls | <class 'main.A'> |
| args | () |
| kwargs | {} |
| class | <class 'main.LittleMeta'> |
<main.A object at 0x7f41e4e694e0> run 88 meters
{'module': 'main', 'name': 'A$tom', 'doc': None, 'run': <function LittleMeta.call.<locals>.<lambda> at 0x7f41d4ef2ae8>}
此時(shí)新增了一個(gè)run方法
Creating instance cache using metaclass(使用元類實(shí)現(xiàn)實(shí)例緩存)
class InstCache(type):
_instances = {} #元類屬性
def __call__(cls, *args, **kwargs):
_kw = dict(sorted(kwargs.items()))
_key = (*args, *_kw.values())
if _key not in cls._instances:
cls._instances[_key] = super().__call__(*args, **kwargs)
# cls._instances[_key] = type.__call__(cls, *args, **kwargs)
pprint('__call__', **locals())
return cls._instances[_key]
class S(metaclass=InstCache):
def __init__(self, name, age=18):
self.name = name
self.age = age
x = S('tom', 19)
y = S('tom', age=19)
print(x, y, x is y)
-
__call__is called
| param | value |
|---|---|
| cls | <class 'main.S'> |
| args | ('tom', 19) |
| kwargs | {} |
| _kw | {} |
| _key | ('tom', 19) |
| class | <class 'main.InstCache'> |
-
__call__is called
| param | value |
|---|---|
| cls | <class 'main.S'> |
| args | ('tom',) |
| kwargs | {'age': 19} |
| _kw | {'age': 19} |
| _key | ('tom', 19) |
| class | <class 'main.InstCache'> |
<main.S object at 0x7fc356f557b8> <main.S object at 0x7fc356f557b8> True
Creating instance cache using inhrit(繼承實(shí)現(xiàn)實(shí)例緩存)
class InstCache(object):
_instances = {}
def __new__(cls, *args, **kwargs):
""""實(shí)例創(chuàng)建時(shí)觸發(fā)
"""
_kw = dict(sorted(kwargs.items()))
_key = (*args, *_kw.values())
if _key not in cls._instances:
cls._instances[_key] = super().__new__(cls)
# cls._instances[_key] = object.__new__(cls)
return cls._instances[_key]
class S(InstCache):
def __init__(self, name, age=18):
self.name = name
self.age = age
x = S('tom', 19)
y = S('tom', age=19)
print(x is y)
output
True
只要實(shí)例參數(shù)的值一致
The "count calls" metaclass 類方法調(diào)用次數(shù)的元類
from functools import wraps
class FuncCalls(type):
@staticmethod
def call_counter(func):
@wraps(func)
def wrapper(*args, **kwargs):
wrapper.calls += 1
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
def __new__(cls, clsname, superclasses, attributedict):
for attr, value in attributedict.items():
if callable(value) and not attr.startswith('__'):
value = cls.call_counter(value) #相當(dāng)于裝飾器
attributedict[attr] = value
return super().__new__(cls, clsname, superclasses, attributedict)
class A(metaclass=FuncCalls):
def foo(self):
pass
a = A()
a.foo()
print(a.foo.calls)
a.foo()
print(a.foo.calls)
# output
1
2