Python的元类
关于什么是元类(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