Python 中的元类
《流畅的 Python》一书中类元编程章的开头第一段话就提到:
(元类)是很深奥的知识,99%的用户都无需关注。如果你想知道是否需要使用元类,我告诉你,不需要(真正需要使用元类的人确信他们需要,无需解释原因)。 ——Tim Peters
事实上确实如此,在日常的编码过程中,几乎没有遇到过不得不使用元类的情况,或许只有编写框架的开发者们会用到元类。
说到元类,就不得不提到特殊方法__new__
。或许你已经知道,特殊方法__init__
并不是类的构造方法,而是类的初始化方法,真正的构造方法是__new__
,书中的第19章作者编写FrozenJSON
类的那一节已经详细地解释了这一点。
元类与装饰器有点异曲同工。比如@foo
这个语句中,函数foo
就是装饰器函数,但如果是@foo(arg)
的话,你应该能认识到装饰器函数并不是foo
,而是foo(arg)
,foo(arg)
运行之后返回了一个装饰器函数。我们可以把foo
函数理解为一个装饰器工厂,把传给它的参数arg
想象成原材料,生产出的产品foo(arg)
就是装饰器,这么一想就能理解了,而且也就理解为什么有的装饰器函数中函数被嵌套得一层又一层了。而元类就是负责生产类的类工厂函数。
初次接触__new__
时很容易一脸懵逼:这是什么?这做了什么?这返回了什么?为什么要这样做?
我简单地做了一下总结,大致上可以这么理解:
__init__
:类的初始化方法
__new__
:类的构造方法
__init__
方法不允许返回任何值
__new__
方法必须返回一个实例
若__new__
方法返回的是类本身的实例,则返回的实例将作为第一个参数(即self)传递给__init__
方法并进行调用
若__new__
方法返回的是其他类的实例,则不会调用__init__方法
配合书中最终版的FrozenJSON
代码来看就更容易理解了:
from collections import abc
class FrozenJSON:
def __new__(cls, arg):
# 如果arg是字典 返回一个FrozenJSON实例
# 调用基类的__new__方法 代入的唯一的参数是类本身
# 这样就可以返回一个正常的FrozenJSON实例了 之后就会调用__init__方法
# 参数arg也会顺便传给__init__方法
if isinstance(arg, abc.Mapping):
return super().__new__(cls)
# 如果arg是列表
# 注: 当__new__返回的是其他类的实例时 不会调用__init__方法
elif isinstance(arg, abc.MutableSequence):
return [cls(item) for item in arg]
# 如果arg既不是字典也不是列表 则直接返回
else:
return arg
def __init__(self, mapping):
""" 下略 """
可惜的是FrozenJSON
类没有突出类的工厂函数的概念,如果要理解类工厂函数的用法,可以参考廖老师的Python教程中使用元类一节。