元编程(meta programming)是一项很神奇的能力,可以通过代码在运行时动态生成代码。
元类(meta classes)是 Python 提供的一种元编程的能力。在 Python 中,类也是一种对象,那么类这种对象就是元类的实例,所以我们可以在运行时通过实例化元类动态生成类。
使用 type “函数”
首先我们来了解一下 type,type 可以作为函数使用,用来获得对象的类型:
>>> class Foo: ... pass >>> obj = Foo() >>> obj.__class__ <class '__main__.Foo'> >>> type(obj) <class '__main__.Foo'> >>> obj.__class__ is type(obj) True
实际上 type 并不是一个函数,而是一个类,我们可以使用 type(type) 来确定一下:
>>> type(type) <class 'type'>
type 实际上不只是类,而是一个“元类”。我们接下来要可以看到,所有的元类都需要继承自 type。type 是所以类的元类,所以在上面的例子中 x 是 Foo 的实例,Foo 是 type 的实例,type 又是他自己的实例。
用 type 动态创建类
如果传递给 type 的参数是三个的时候,type 的语义就不再是返回给定参数的类,而是实例化生成一个新的类。
type(name: str, bases: tuple, namespace: dict)
第一个参数是新生成的类的名字;第二个参数是新生成的类的基类列表;第三个参数是要个这个类绑定的属性的列表,比如说这个类的一些方法。实际上 class Foo 这种语法只是使用 type 生成类的语法糖而已。
最简单的一个例子,比如我们要创建 Foo[0..9] 这些类,可以这样做:
classes = [] for i in range(10): cls = type("Foo%s" % i, tuple(), {}) classes.append(cls) # 就像使用普通类一样初始化 Foo0 foo0 = clssses[0]()
如果要实现类的方法,一定要记得同样是要使用 self 变量的。在 Python 中 self 只是一个约定俗成的变量,而不是关键字。
def __init__(self, name): self.name = name def print_name(self): print(self.name) Duck = type("Duck", tuple(), {"__init__": __init__, "print_name": print_name}) duck = Duck("Donald") duck.print_name() # Donald
创建自己的元类
首先我们来回顾一下 Python 中类的初始化过程:
foo = Foo()
当这条语句运行的时候,Python 会依次调用 Foo 的 __new__
和 __init__
方法。其中 __new__
方法在 __init__
之前调用,并返回已经创建好的新对象,而 __init__
函数是没有返回结果的。一般情况下,我们都会覆盖 __init__
方法来对新创建的对象做一些初始化操作。
现在回归到元类上,进入烧脑部分。前面我们说过元类的实例化就是类,所以大致相当于:
Foo = MetaFoo(name, bases, attrs) # MetaFoo 默认情况下是 type foo = Foo()
默认情况下,所有类的元类是 type,也就是在这个类是通过 type 来创建的,这和前面说的通过 type 来动态创建类也是一致的。
那么怎样定义一个 MetaFoo 呢?只需要继承自 type 就行了。因为元类的实例化就是类的创建过程,所以在元类中,我们可以修改 __new__
来在 __init__
之前对新创建的类做一些操作。
>>> class MetaFoo(type): ... def __new__(cls, name, bases, namespace): ... x = super().__new__(cls, name, bases, namespace) # super实际上就是 type ... x.bar = 100# 为这个类增加一个属性 ... return x ... >>> Foo = MetaFoo("Foo", tuple(), {}) # MetaFoo 在这里就相当于 type 了,可以动态创建类 >>> Foo.bar 100 >>> foo = Foo() >>> foo.bar 100
在这里我们创建了 MetaFoo 这个元类,他会给新创建的类增加一个叫做 bar 的属性。
在实际的代码中,我们一般还是不会直接动态生成类的,还是调用 class Foo
语法来生成类比较常见一点,这时候可以指定 metaclass 参数就好了。可以通过 Foo(metaclass=MetaFoo) 这种方式来指定元类。
class Foo(metaclass=MetaFoo): pass
这种定义和上面的元类用法效果完全是一致的。