Python 元类
概述
Python 是内置面向对象编程(OOP)的语言。在 Python 中,everything is a object,类也不例外。当把类看作对象时,类也是可以是被动态创建的。元类(Metaclass)就可以动态创建类对象,可以称它为“类工厂”。type 是 python 内置的元类,用户也可以创建自己的元类。
本文主要翻译自 Python Metaclass by John Sturtz 。
元类是非常难掌握的 OOP 概念,它隐藏在所有 Python 代码下面,甚至在你没有意识到它的存在的地方。在大多数情况下,你都不需要或者极少需要考虑它的存在。
自定义元类的做法是有些争议的,就像 Tim Peter 建议的那样:
元类是比99%的用户本应该担心的更深的魔法。当你想弄清楚是否需要使用它们时,那你就不需要它们(那些实际用到它们的人都清楚地知道需要它们,而不需要解释原因)。
—— Tim Peters
然后,理解 Python 元类还是值得的,因为它通常可以更好地帮助理解 Python 类的内部实现。
你绝不知道:可能在某一天发现使用自定义元类就是你需要的。
新类 VS 旧类
在新类中,统一了 class 和 type的概念。如果一个 obj 时一个新类的实例(instance),那么type(obj) 和 obj.__class__是相同的。
1 | class Foo: |
Type 和 Class
在 Python3 中,所有的类都是新类,因此,互换地引用对象的类型(object’s type)和对象的类是合理的。
类也是对象,那么类也有它自己的类型(type),那么类的类型是什么呢?
1 | class Foo: |
类 Foo 的类型是 type,通常情况下,所有新类的类型都是 type,包括用户十分熟悉的内置类:
1 | for t in int, float, dict, list, tuple: |
type 的类型是它自己:
1 | type(type) |
type是元类,其他类是它的实例。
动态定义类
内置的 type() 当被传递一个参数时,它返回参数对象的类型,对于新类,它通常和对象的 __class__属性是相同的。
但也可以向 type()传递三个参数 type(<name>, <bases>, <dct>):
name代表类名,另外该名字会成为类的__name__属性。bases代表父类的元组,它会成为类的__bases__属性。dct代表类的命名空间字典,其中包含了类体,它会作为类的__dict__属性。
这样调用type()函数会返回type元类的实例,或者说它动态创建了一个新的类。
在下面的每个示例中都包含两个代码片段,上面的一个使用 type()动态创建类,下面的一个使用class语句定义类,这两个片段在功能上是等价的。
示例1
在这个例子中,type()函数的 bases 和 dct 参数都为空:
1 | Foo = type('Foo', (), {}) |
1 | class Foo: |
示例2
在这里,bases 参数是一个单成员元组,该成员 Foo 作为所创建类的父类。另外,在命名空间中设置了属性 attr并初始化 100:
1 | Bar = type('Bar', (Foo,), dict(attr=100)) |
1 | class Bar(Foo): |
示例3
1 | Foo = type( |
1 | class Foo: |
示例4
自定义元类
考虑下面的例子:
1 | class Foo: |
当解释器解析 Foo() 时,将会发生下面的动作:
Foo的父类的__call__方法会被调用。在这里Foo的父类是type,因此type的__call__方法被调用。__call__()方法依次调用下面的方法:- new()
- init()
如果Foo没有定义 __new__() 和 __init__(),其祖先的默认方法会被调用。Foo 可以通过定义这些函数来自定义自己实例化的行为。如下所示:
1 | def new(cls): |
在上面的例子中,每次实例化 Foo类时,都会默认初始化一个 attr 属性为100。(通常,这种代码都出现在 __init__方法中而不是__new__中。这个例子仅仅作为一个 demo。)
如果你想要同样自定义类(例如 Foo)的创建行为,可以自定义 Foo 的类型的 __new__方法。但直接重新对 type 的 __new__() 方法进行赋值是不允许的。
一种方案是自定义元类,该元类继承自 type:
1 | class Meta(type): |
现在 Meta 也是元类,接下来完成另外一半:定义一个新类 Foo 并指定它的元类上面自定义的 Meta,而不是标准的 type。如下所示:
1 | class Foo(metaclass=Meta): |
当然,任何采取上面同样定义的类都默认包含属性 attr。一个类函数可以作为创建对象的模板,一个元类函数则可以作为创建类的模板。元类也可以被称为 类工厂。
是否真的必要?
实际上,第4节中的实现可以采用其他方案,例如类继承和类修饰符。
总结
通常情况下,创建自定义元类都是不必要的。如果对于当前问题存在更简单的解决方式,那么很可能就应该使用这种方式。但理解元类仍然是有利于你去理解python的类机制,并可以判断什么时候元类可以作为一个真正合适的工具来使用。