每个对象都是由另一个特殊对象创建出来的。 这些特殊对象就是类(class), 而你通过配置类,让它们产生你想要的对象。
类本身也是对象,而对象是可以修改的。 这里的代码清单使用 display_object(), 它是本章在构建 display_object()一节中实现的 inspection helper:
# modify_class.py
from display import display_object
class Clay:
pass
display_object(Clay)
#: [Attributes]
#: None
#: [Methods]
#: None
x = Clay()
display_object(x)
#: [Attributes]
#: None
#: [Methods]
#: None
Clay.n = 42 # type: ignore
display_object(Clay)
#: [Attributes]
#: • n = 42 [CV]
#: [Methods]
#: None
Clay.m = lambda self: f"{self.n = }" # type: ignore
display_object(Clay)
#: [Attributes]
#: • n = 42 [CV]
#: [Methods]
#: • m(self)
print(x.m()) # type: ignore
#: self.n = 42
display_object(x)
#: [Attributes]
#: • n = 42 [CV]
#: [Methods]
#: • m(self)
print(vars(x))
#: {}
即使是在 x 创建之后,你对 class 所做的修改,x 仍然能看到。 instance 本身并没有发生变化。 最后一行表明,它自己的 instance dictionary 仍然是空的。 对 instance 做 attribute lookup 时,如果 instance 上找不到,就会继续到它的 class 上查找, 因此对 class 的一次修改会影响该 class 的所有对象, 包括那些早已创建出来的对象。
那么,这些“class”对象又是谁创建的? 答案是另一类特殊对象,叫作元类(metaclass)。 默认 metaclass 是 type,而绝大多数情况下,它做的事情都正合适。 你可以自定义 Python 创建 class 的方式:在构建每个 class 时运行额外代码,或者向其中注入成员。 这就是 metaclass programming。
其实你已经使用过 metaclass,只是没有亲手写过。 abc.ABCMeta 会构建 abc.ABC (重新思考对象会实际使用它), 并让一个仍有 abstract method 未实现的 class 拒绝实例化。 enum.EnumType 会构建每个 Enum subclass, 把 class body 中的每个 assignment 都转换为一个 member,并让 for c in Color 能遍历这些 member。 遍历一个 class,属于 class object 自身的行为; metaclass 可以把这种行为放到 class object 上,而普通 class 做不到。
你很少真正需要 metaclass。 它是一种非常有意思、也很容易让人产生使用冲动的工具, 但更简单的 hook 已经覆盖了过去 metaclass 才能处理的几乎所有场景:
__init_subclass__() 在 subclass 创建时运行。 它可以取代大多数“每定义一个 class 就做某件事”的 metaclass。__set_name__() 让 class attribute 在 class 创建时知道自己的名字。只有当这些办法都无法完成任务时,才使用 metaclass。 本章首先通过手工构建 class,展示一条 class 语句实际上做了什么。 接着介绍这些更简单的 hook,然后再讨论那些确实仍需要 metaclass 的场景。 最后用 inspect module 从另一个方向收尾: 读取 class 的结构,而不是修改它。
type 生成类既然 metaclass 用来创建 class,你也可以自己直接调用 metaclass。 type 接收一个参数时,会返回已有对象的类型。 type 接收三个参数时,会创建一个新的 class。 这三个参数分别是名称、base class tuple, 以及包含 field 和 method 的 namespace dictionary。 class definition 只是调用 type 的一种简写:
# class_via_type.py
class C:
pass
D = type("D", (), {}) # The same construction, by hand
print(type(C), type(D))
#: <class 'type'> <class 'type'>
# Both inherit object:
print(C.__bases__, D.__bases__)
#: (<class 'object'>,) (<class 'object'>,)
# Both make ordinary instances:
print(isinstance(C(), C), isinstance(D(), D))
#: True True
你也可以用同样的方式添加 base class、field 和 method:
# my_list.py
from display import display_object
def howdy(self, you: str) -> None:
print(f"Howdy,{you}")
MyList = type("MyList", (list,), dict(x=42, howdy=howdy))
display_object(MyList)
#: [Attributes]
#: • x = 42 [CV]
#: [Methods]
#: • append(self, object, /)
#: • clear(self, /)
#: • copy(self, /)
#: • count(self, value, /)
#: • extend(self, iterable, /)
#: • howdy(self, you: str) -> None
#: • index(self, value, start=0, stop=92233720368547758...
#: • insert(self, index, object, /)
#: • pop(self, index=-1, /)
#: • remove(self, value, /)
#: • reverse(self, /)
#: • sort(self, /, *, key=None, reverse=False)
ml = MyList()
ml.append("Camembert")
print(ml)
#: ['Camembert']
print(ml.x)
#: 42
ml.howdy("John")
#: Howdy, John
print(ml.__class__.__class__)
#: <class 'type'>
因为 MyList 继承自 list,所以它获得了 list 的所有 method。
打印 class 的 class,得到的就是 metaclass。