14 KiB
Python 元类
术语元编程指的是程序了解或操纵自身的潜力。Python 支持一种称为元类的类元编程形式。
元类是一个深奥的 OOP 概念,隐藏在几乎所有 Python 代码的背后。不管你是否意识到,你都在使用它们。在大多数情况下,你不需要意识到这一点。大多数 Python 程序员很少考虑元类。
然而,当需要时,Python 提供了一种并非所有面向对象语言都支持的功能:您可以在幕后定义自定义元类。自定义元类的使用有些争议,正如 Tim Peters(Python 大师,撰写了 Python 的 Zen)所说:
“元类比 99%的用户应该担心的还要神奇。如果你想知道你是否需要他们,你不需要(实际需要他们的人肯定知道他们需要他们,并且不需要关于为什么的解释)。”
— 蒂姆·彼得斯
有些 Python 爱好者(众所周知的 Python 爱好者)认为不应该使用自定义元类。这可能有点过了,但是自定义元类在大多数情况下是不必要的,这可能是真的。如果一个问题不太明显需要它们,那么如果用一种更简单的方式来解决,它可能会更干净,更易读。
尽管如此,理解 Python 元类是值得的,因为它通常会导致对 Python 类内部的更好理解。您永远不知道:有一天,您可能会发现自己处于这样一种情况,您只知道自定义元类是您想要的。
**获得通知:**不要错过本教程的后续— 点击这里加入真正的 Python 时事通讯你会知道下一期什么时候出来。
旧式与新式课堂
在 Python 领域,类可以是两种类型中的一种。官方术语尚未确定,因此它们被非正式地称为旧式和新式类。
旧式班级
对于旧式的类,类和类型不是一回事。旧式类的实例总是由一个名为instance的内置类型实现。如果obj是一个旧式类的实例,obj.__class__指定该类,但是type(obj)总是instance。以下示例摘自 Python 2.7:
>>> class Foo:
... pass
...
>>> x = Foo()
>>> x.__class__
<class __main__.Foo at 0x000000000535CC48>
>>> type(x)
<type 'instance'>
新型班级
新型类统一了类和类型的概念。如果obj是一个新型类的实例,type(obj)与obj.__class__相同:
>>> class Foo:
... pass
>>> obj = Foo()
>>> obj.__class__
<class '__main__.Foo'>
>>> type(obj)
<class '__main__.Foo'>
>>> obj.__class__ is type(obj)
True
>>> n = 5
>>> d = { 'x' : 1, 'y' : 2 }
>>> class Foo:
... pass
...
>>> x = Foo()
>>> for obj in (n, d, x):
... print(type(obj) is obj.__class__)
...
True
True
True
类型和等级
在 Python 3 中,所有的类都是新型类。因此,在 Python 3 中,互换引用对象的类型和类是合理的。
**注意:**在 Python 2 中,类默认是旧式的。在 Python 2.2 之前,根本不支持新型类。从 Python 2.2 开始,可以创建它们,但必须显式声明为 new-style。
记住,在 Python 中,一切都是对象。类也是对象。因此,一个类必须有一个类型。一个类的类型是什么?
请考虑以下情况:
>>> class Foo:
... pass
...
>>> x = Foo()
>>> type(x)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'>
如你所料,x的类型是类Foo。但是Foo的类型,职业本身,是type。一般来说,任何新型类的类型都是type。
你熟悉的内置类的类型也是type:
>>> for t in int, float, dict, list, tuple:
... print(type(t))
...
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
就此而言,type的类型也是type(是的,真的):
>>> type(type)
<class 'type'>
是一个元类,其中的类是实例。正如普通对象是一个类的实例一样,Python 中的任何新型类,以及 Python 3 中的任何类,都是type元类的实例。
在上述情况下:
x是类Foo的一个实例。Foo是type元类的一个实例。type也是type元类的一个实例,所以它是自身的一个实例。
动态定义一个类
当传递一个参数时,内置的type()函数返回一个对象的类型。对于新型类,这通常与对象的__class__属性相同:
>>> type(3)
<class 'int'>
>>> type(['foo', 'bar', 'baz'])
<class 'list'>
>>> t = (1, 2, 3, 4, 5)
>>> type(t)
<class 'tuple'>
>>> class Foo:
... pass
...
>>> type(Foo())
<class '__main__.Foo'>
也可以用三个参数调用type()—type(<name>, <bases>, <dct>):
<name>指定类名。这成为了类的__name__属性。- 指定该类继承的基类的元组。这成为了类的
__bases__属性。 <dct>指定一个包含类体定义的名称空间字典。这成为了类的__dict__属性。
以这种方式调用type()会创建一个type元类的新实例。换句话说,它动态地创建了一个新类。
在下面的每个例子中,上面的代码片段用type()动态定义了一个类,而下面的代码片段用class语句以通常的方式定义了这个类。在每种情况下,这两个片段在功能上是等效的。
示例 1
在第一个例子中,传递给type()的<bases>和<dct>参数都是空的。没有指定来自任何父类的继承,并且最初在名称空间字典中没有放置任何东西。这是最简单的类定义:
>>> Foo = type('Foo', (), {})
>>> x = Foo()
>>> x
<__main__.Foo object at 0x04CFAD50>
>>> class Foo:
... pass
...
>>> x = Foo()
>>> x
<__main__.Foo object at 0x0370AD50>
示例 2
这里,<bases>是一个只有一个元素Foo的元组,指定了Bar继承的父类。属性attr最初放在名称空间字典中:
>>> Bar = type('Bar', (Foo,), dict(attr=100))
>>> x = Bar()
>>> x.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)
>>> class Bar(Foo):
... attr = 100
...
>>> x = Bar()
>>> x.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)
示例 3
这一次,<bases>又空了。两个对象通过<dct>参数放入名称空间字典。第一个是名为attr的属性,第二个是名为attr_val的函数,它成为定义的类的方法:
>>> Foo = type(
... 'Foo',
... (),
... {
... 'attr': 100,
... 'attr_val': lambda x : x.attr
... }
... )
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100
>>> class Foo:
... attr = 100
... def attr_val(self):
... return self.attr
...
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100
示例 4
Python 中的 lambda只能定义非常简单的函数。在下面的例子中,一个稍微复杂一点的函数在外部定义,然后在名称空间字典中通过名字f赋给attr_val:
>>> def f(obj):
... print('attr =', obj.attr)
...
>>> Foo = type(
... 'Foo',
... (),
... {
... 'attr': 100,
... 'attr_val': f
... }
... )
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
attr = 100
>>> def f(obj):
... print('attr =', obj.attr)
...
>>> class Foo:
... attr = 100
... attr_val = f
...
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
attr = 100
自定义元类
再次考虑这个老生常谈的例子:
>>> class Foo:
... pass
...
>>> f = Foo()
表达式Foo()创建了类Foo的一个新实例。当解释器遇到Foo()时,会发生以下情况:
-
调用
Foo的父类的__call__()方法。因为Foo是一个标准的新型类,它的父类是type元类,所以type的__call__()方法被调用。 -
该
__call__()方法依次调用以下内容:__new__()__init__()
如果Foo没有定义__new__()和__init__(),则默认方法从Foo的祖先继承。但是如果Foo确实定义了这些方法,它们会覆盖那些来自祖先的方法,这允许在实例化Foo时定制行为。
在下文中,定义了一个名为new()的自定义方法,并将其指定为Foo的__new__()方法:
>>> def new(cls):
... x = object.__new__(cls)
... x.attr = 100
... return x
...
>>> Foo.__new__ = new
>>> f = Foo()
>>> f.attr
100
>>> g = Foo()
>>> g.attr
100
这修改了类Foo的实例化行为:每次创建Foo的实例时,默认情况下,它用一个名为attr的属性初始化,该属性的值为100。(像这样的代码更经常出现在__init__()方法中,而不是典型的__new__()。这个例子是为了演示的目的而设计的。)
现在,正如已经重申的,类也是对象。假设您想在创建类似于Foo的类时类似地定制实例化行为。如果您遵循上面的模式,您将再次定义一个自定义方法,并将其指定为类的__new__()方法,而Foo是该类的一个实例。Foo是type元类的一个实例,所以代码看起来像这样:
# Spoiler alert: This doesn't work!
>>> def new(cls):
... x = type.__new__(cls)
... x.attr = 100
... return x
...
>>> type.__new__ = new
Traceback (most recent call last):
File "<pyshell#77>", line 1, in <module>
type.__new__ = new
TypeError: can't set attributes of built-in/extension type 'type'
正如您所看到的,您不能重新分配type元类的__new__()方法。Python 不允许。
这可能是无妨的。是元类,所有新样式的类都是从它派生出来的。不管怎样,你真的不应该再瞎折腾了。但是如果你想定制一个类的实例化,有什么办法呢?
一个可能的解决方案是自定义元类。本质上,你可以定义自己的元类,它从type派生而来,然后你可以用它来代替type元类。
第一步是定义一个从type派生的元类,如下所示:
>>> class Meta(type):
... def __new__(cls, name, bases, dct):
... x = super().__new__(cls, name, bases, dct)
... x.attr = 100
... return x
...
定义头class Meta(type):指定Meta来源于type。因为type是一个元类,所以Meta也是一个元类。
注意,已经为Meta定义了一个自定义的__new__()方法。直接对type元类这样做是不可能的。__new__()方法执行以下操作:
- 通过
super()委托给父元类(type)的__new__()方法来实际创建一个新类 - 将自定义属性
attr赋值给类,值为100 - 返回新创建的类
现在巫术的另一半:定义一个新类Foo,并指定它的元类是自定义元类Meta,而不是标准元类type。这是使用类定义中的metaclass关键字完成的,如下所示:
>>> class Foo(metaclass=Meta):
... pass
...
>>> Foo.attr
100
瞧! Foo已经从Meta元类中自动选取了attr属性。当然,您以类似方式定义的任何其他类也会这样做:
>>> class Bar(metaclass=Meta):
... pass
...
>>> class Qux(metaclass=Meta):
... pass
...
>>> Bar.attr, Qux.attr
(100, 100)
与类作为创建对象的模板一样,元类也作为创建类的模板。元类有时被称为类工厂。
比较以下两个例子:
对象工厂:
>>> class Foo:
... def __init__(self):
... self.attr = 100
...
>>> x = Foo()
>>> x.attr
100
>>> y = Foo()
>>> y.attr
100
>>> z = Foo()
>>> z.attr
100
类工厂:
>>> class Meta(type):
... def __init__(
... cls, name, bases, dct
... ):
... cls.attr = 100
...
>>> class X(metaclass=Meta):
... pass
...
>>> X.attr
100
>>> class Y(metaclass=Meta):
... pass
...
>>> Y.attr
100
>>> class Z(metaclass=Meta):
... pass
...
>>> Z.attr
100
这真的有必要吗?
尽管上面的类工厂例子很简单,但它是元类工作的本质。它们允许定制类实例化。
尽管如此,仅仅是给每个新创建的类赋予自定义属性attr就太麻烦了。为此,您真的需要一个元类吗?
在 Python 中,至少有几种其他方法可以有效地完成同样的事情:
简单继承:
>>> class Base:
... attr = 100
...
>>> class X(Base):
... pass
...
>>> class Y(Base):
... pass
...
>>> class Z(Base):
... pass
...
>>> X.attr
100
>>> Y.attr
100
>>> Z.attr
100
类装饰器:
>>> def decorator(cls):
... class NewClass(cls):
... attr = 100
... return NewClass
...
>>> @decorator
... class X:
... pass
...
>>> @decorator
... class Y:
... pass
...
>>> @decorator
... class Z:
... pass
...
>>> X.attr
100
>>> Y.attr
100
>>> Z.attr
100
结论
正如 Tim Peters 所建议的,元类很容易成为“寻找问题的解决方案”通常没有必要创建自定义元类。如果手头的问题可以用一种更简单的方式来解决,它可能应该是。尽管如此,理解元类还是有好处的,这样你就能大体理解 Python 类,并能认识到什么时候元类才是真正适合使用的工具。****
