关于什么是元类(metaclass)、怎么用元类,我查了很多资料。中文的英文的都有,但是似乎都是讲理论,没有实际的给出一个整体的代码。
理论
几乎所有教程里都会谈到这一点:
类也是一个对象,元类就是创建这个对象的类。
参考metaclass的官方文档
By default, classes are constructed using
type()
. The class body is executed in a new namespace and the class name is bound locally to the result oftype(name, bases, namespace)
.
所有类的默认元类就是type
,并且它接受三个参数name
、bases
、namespace
。
name
是一个str
,它是类的名字,一般来说,在元类里不需要动它。bases
是一个tuple
,它包含类的所有父类,如果没有父类,那么当然为空。namespace
是一个dict
,它包含这个类定义里的所有内容——类里面的方法、属性,以及类所属的模块名,类的__qualname__
(这里我不知道怎么翻译,一般来说这个值就是类名,用于类内的方法的__qualname__
生成)。
实践
所以我们来实践一下,作为一个元类,它是如何作为的。以下是一个仅仅打印执行过程而不做其他操作的元类。
class MetaClass(type):
"""
https://docs.python.org/3/reference/datamodel.html#metaclasses
"""
def __new__(cls, name, bases, namespace):
print("MetaClass.__new__ has be called")
print(" -", name, bases, namespace)
return super().__new__(cls, name, bases, namespace)
def __init__(self, name, bases, namespace):
print("MetaClass.__init__ has be called")
print(" -", name, bases, namespace)
def __call__(self, *args, **kwargs):
print("MetaClass.__call__ has be called")
print(" -", args, kwargs)
return super().__call__(*args, **kwargs)
那么接下来使用这个元类试试。
class TestClass(metaclass=MetaClass):
a = 123
def __new__(cls, *args, **kwargs):
print("TestClass.__new__ has be called")
print(" -", args, kwargs)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
print("TestClass.__init__ has be called")
print(" -", args, kwargs)
def test(self):
print("TestClass.test has be called")
执行结果如下:
MetaClass.__new__ has be called
- TestClass () {'__module__': '__main__', '__qualname__': 'TestClass', 'a': 123, '__new__': <function TestClass.__new__ at 0x000002719E926BF8>, '__init__': <function TestClass.__init__ at 0x000002719E926C80>, 'test': <function TestClass.test at 0x000002719E926D08>, '__classcell__': <cell at 0x000002719E8C98E8: empty>}
MetaClass.__init__ has be called
- TestClass () {'__module__': '__main__', '__qualname__': 'TestClass', 'a': 123, '__new__': <function TestClass.__new__ at 0x000002719E926BF8>, '__init__': <function TestClass.__init__ at 0x000002719E926C80>, 'test': <function TestClass.test at 0x000002719E926D08>, '__classcell__': <cell at 0x000002719E8C98E8: MetaClass object at 0x000002719E7CA468>}
可以看到,类在被加载的时候,就已经调用元类进行构造了。并且__new__
和__init__
的区别在于,在__new__
被调用的返回值将作为类的实际定义,__init__
没有返回值。
这就导致——如果你想更改类的定义,只能在__new__
里做;如果想查看类的最终定义,那就在__init__
里做。
可以尝试把MetaClass
当中的__new__
的返回值给删掉,它将返回一个默认的None
。当你调用类的时候,你就会发现,类已经变成了None
——无论它原本的定义是什么。
在这里我强调这一点只是想说明metaclass.__new__
的权限很大,使用的时候一定要小心使用。当你不确定是否一定需要用它的时候,那就不要用它。
接下来执行下面的代码:
t = TestClass(123, 12, 1)
t.test()
执行结果如下:
MetaClass.__new__ has be called
- TestClass () {'__module__': '__main__', '__qualname__': 'TestClass', 'a': 123, '__new__': <function TestClass.__new__ at 0x0000019C78F16BF8>, '__init__': <function TestClass.__init__ at 0x0000019C78F16C80>, 'test': <function TestClass.test at 0x0000019C78F16D08>, '__classcell__': <cell at 0x0000019C78EB98E8: empty>}
MetaClass.__init__ has be called
- TestClass () {'__module__': '__main__', '__qualname__': 'TestClass', 'a': 123, '__new__': <function TestClass.__new__ at 0x0000019C78F16BF8>, '__init__': <function TestClass.__init__ at 0x0000019C78F16C80>, 'test': <function TestClass.test at 0x0000019C78F16D08>, '__classcell__': <cell at 0x0000019C78EB98E8: MetaClass object at 0x0000019C76ECAE48>}
MetaClass.__call__ has be called
- (123, 12, 1) {}
TestClass.__new__ has be called
- (123, 12, 1) {}
TestClass.__init__ has be called
- (123, 12, 1) {}
TestClass.test has be called
你会发现元类的__call__
方法先于类的初始化方法被调用。
试试把__call__
的返回给删掉,会有如下的执行结果
MetaClass.__new__ has be called
- TestClass () {'__module__': '__main__', '__qualname__': 'TestClass', 'a': 123, '__new__': <function TestClass.__new__ at 0x0000026D8FF16BF8>, '__init__':
<function TestClass.__init__ at 0x0000026D8FF16C80>, 'test': <function TestClass.test at 0x0000026D8FF16D08>, '__classcell__': <cell at 0x0000026D8FEB98E8: empty>}
MetaClass.__init__ has be called
- TestClass () {'__module__': '__main__', '__qualname__': 'TestClass', 'a': 123, '__new__': <function TestClass.__new__ at 0x0000026D8FF16BF8>, '__init__':
<function TestClass.__init__ at 0x0000026D8FF16C80>, 'test': <function TestClass.test at 0x0000026D8FF16D08>, '__classcell__': <cell at 0x0000026D8FEB98E8: MetaClass object at 0x0000026D8DF6B188>}
MetaClass.__call__ has be called
- (123, 12, 1) {}
Traceback (most recent call last):
File "c:/Users/AberS/Desktop/metaclass.py", line 39, in <module>
t.test()
AttributeError: 'NoneType' object has no attribute 'test'
此时类的__new__
与__init__
都没有被执行,而__new__
不被执行的后果就是:t = None
。
总结
Python中类的元类能够控制类的定义、以及类创建对象的过程。
-
定义类的代码执行时:
首先调用元类的构造方法构造类。在
__new__
可读写类的定义;在__init__
中只读,但能获取类最终的定义。 -
由类创建对象时:
首先调用元类的
__call__
方法。由__call__
调用类的构造方法创建对象。
实际运用
单例类
元类的__init__
方法与__call__
方法中的第一个参数理所当然是当前元类的实例化后产生的对象,也就是被控制的类。藉由此,可以使用元类创建单例类
class Singleton(type):
def __init__(cls, name, bases, namespace):
cls.instance = None
def __call__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__call__(*args, **kwargs)
return cls.instance
使用函数作为元类
元类并不总是需要一个类去作为定义,很多时候可能使用函数就行——譬如你不需要控制类构造对象的过程时。
实际上元类只需要是一个可调用对象(Callable)并且返回type(name, bases, namespace)
就行。
看下面一个例子:
def MetaFunc(name, bases, namespace):
print("MetaFunc has be called")
print(" -", name, bases, namespace)
return type(name, bases, namespace)
class TestClass(metaclass=MetaFunc):
a = 123
def __new__(cls, *args, **kwargs):
print("TestClass.__new__ has be called")
print(" -", args, kwargs)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
print("TestClass.__init__ has be called")
print(" -", args, kwargs)
def test(self):
print("TestClass.test has be called")
t = TestClass(123, 12, 1)
t.test()
执行结果如下:
MetaFunc has be called
- TestClass () {'__module__': '__main__', '__qualname__': 'TestClass', 'a': 123, '__new__': <function TestClass.__new__ at 0x000001B0650F6C80>, '__init__': <function TestClass.__init__ at 0x000001B0650F6D08>, 'test': <function TestClass.test at 0x000001B0650F6D90>, '__classcell__': <cell at 0x000001B0650998E8: empty>}
TestClass.__new__ has be called
- (123, 12, 1) {}
TestClass.__init__ has be called
- (123, 12, 1) {}
TestClass.test has be called
传参给元类
在 ORM 之类的场景里,可能会需要传递参数给元类。
class MetaClass(type):
"""
https://docs.python.org/3/reference/datamodel.html#metaclasses
"""
def __new__(cls, name, bases, namespace, *, model):
print("MetaClass.__new__ has be called")
print(" -", name, bases, namespace, model)
return super().__new__(cls, name, bases, namespace)
def __init__(self, name, bases, namespace, *, model):
print("MetaClass.__init__ has be called")
print(" -", name, bases, namespace, model)
class TestClass(metaclass=MetaClass, model="T"):
pass