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的实例是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 自动添加方法
* automatically propery creation 自动创建属性
* proxies 代理
* automatic resource locking/synchronization 自动资源锁和同步
metaclass内置魔力方法研究(magic method)
Principially, metaclasses are defined like any other Python class, but they are classes that inherit from type
.
原则上,metaclass定义想普通类一样, 但他们继承自type.
美化输出函数,主要用于打印函数内部参数和内部变量.以下演示都将用到此函数
from tabulate import tabulate #自动输出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):
"""定义一个由此元类创建的class时触发此__init__,
相当于给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)
# 定义一个继承自str类, 并由LittleMeta创建的类
# 使用LittleMeta创建类的两种方法
#方法1
class A(str, metaclass=LittleMeta):
pass
# 方法2
B = LittleMeta('B', (str,), {})
- 定义A类时
__new__
is called
param | value |
---|---|
cls | <class 'main.LittleMeta'> |
clsname | A |
superclasses | (<class 'str'>,) |
attributedict | {'module': 'main', 'qualname': 'A', 'name': 'A$tom'} |
首先LittleMeta的
__new__
被调用, 第一个参数是LittleMeta自己,这里的attributedict是A.__dict__
,attributedict['name'] = clsname + '$tom'
给类A增加
了一个name属性.
- 定义class A时元类的
__init__
被调用
param | value |
---|---|
cls | <class 'main.A'> |
clsname | A |
superclasses | (<class 'str'>,) |
attributedict | {'module': 'main', 'qualname': 'A', 'name': 'A$tom'} |
这里发现attributedict里面没有age, 是因为只有元类的
__new__
才能直接通过设置A的attributedict的方式给A增加属性,在元类的__init__
通过cls.attributename = value的方式增加类属性, 也就是说在类A定义之后增加到A.__dict__
可以通过print(A.__dict__
) 观察
- 函数式调用LittleMeta创建B时
__new__
is called
param | value |
---|---|
cls | <class 'main.LittleMeta'> |
clsname | B |
superclasses | (<class 'str'>,) |
attributedict | {'name': 'B$tom'} |
- 函数式调用LittleMeta创建B时
__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
当元类的实例被调用, 也就是classA被实例化的时候触发.
这里当class A被实例化的时候增加一个run方法.联想class的```__call__```使得其
实例可以被调用.
"""
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__, 此时才给A绑定一个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>}
此时新增了一个run方法
Creating instance cache using metaclass(使用元类实现实例缓存)
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(继承实现实例缓存)
class InstCache(object):
_instances = {}
def __new__(cls, *args, **kwargs):
""""实例创建时触发
"""
_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
只要实例参数的值一致
The "count calls" metaclass 类方法调用次数的元类
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) #相当于装饰器
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