Python Metaclasses 元类
翻译自realpython-mataclass介绍
全文手打翻译,翻译不易还请点个赞
metaprogramming元编程 指的是有可能的针对程序行为(指编程语言的默认行为)本身进行编程,Python支持通过metaclasses元类进行元编程。
metaclass元类是在所有python代码运行逻辑背后的比较深奥的OOP(面向对象)概念。虽然多数情况你并没注意到,但是你无时无刻不在使用metaclass,尽管多数情况并不用在意metaclass。大多数Python编程者很少或者说根本不用考虑什么是metaclass。
当需要使用元类时候,Python提供了不是所有面向对象编程语言都有的能力:你可以自定义自己的metaclass。对于metaclass的使用是比较有争议的(译者注:资料较少且容易在继承时出错),Python大拿《Python之禅(zen of python)》的作者Tim Peters这样评论过:
“metaclass是99%的用户用不到的黑魔法,如果你正在考虑是否需要用他,那么你一定不需要(因为需要用的人有确定使用metaclass的原因,他们不用象你一样考虑是否需要使用它)(译者注:框架的编写很多需要操作类的生成行为,了解metaclass有助于了解各种框架的搭建原理)”
一些Python爱好者相信应该永远不用metaclass,这就有点过了。不过大部分时候你确实不是非要用metaclass,如果这个问题不是一看就需要用mataclass解决,那么用其他更简便的方法的可读性应该更强。
话虽如此,但是理解Python metaclass 元编程也是很值得一试的。因为理解它有助于你对Python类的内部运作有更深的理解。谁也说不准哪天你就需要自定义个metaclass了。
Old-Style vs. New-Style Classes
新旧两种类的模式
旧模式类中:
在旧模式类里,类class和类型type不是一个东西,一个旧模式类的实例总是从一个内部的类instance类型下实例化的,他的__class__
指向这个类,但是他的如果使用type(这个实例)
的话返回的总是instance
下面的例子来自于Python2.7:
>>> class Foo:
... pass
...
>>> x = Foo()
>>> x.__class__
<class __main__.Foo at 0x000000000535CC48>
>>> type(x)
<type 'instance'>
新模式类中 这两个返回的值是一样的 都是这个类的名字 平时用的都是新模式类
在新模式类中已经将class 和type类和类型都统一起来了,如果一个新模式类的实例那么他的type(实例)
和实例.__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
Type and Class 类型与类
在Python3.x版本中,所有的类都是新模式类。因此在Python内部需要让他的type和它的class的指向是相同的
在Python3中只有新模式类,在Python2.2后才支持新模式类,不过需要显示声明
注意在Python中 所有的东西都是对象,包括类本身也是对象,因此一个类必须要有他的type(类型),思考一下一个类的类型是什么?
>>> class Foo:
... pass
...
>>> x = Foo()
>>> type(x)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'>
估计跟你想的一样 x的type是Foo,但是Foo这个类的type都是type,往大了说所有新模式类的type都是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(type)
<class 'type'>
type是一个metaclass,类其实是type这个元类的实例,而普通的常见对象是类的实例。因此所有在python3里的新模式类的其实都是type元类的实例。
Defining a Class Dynamically 动态地创建一个类
给一个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()函数传递三个参数 (, , ):
- 指定生成的类的名字,就是这个类的
__name__
属性 - 指定他的父类 就是这个类的
__bases__
属性 - 指定这个命名空间下的字典,包含了类的主体部分,也就是这个类的
__dict__
属性
这样调用type()函数会生成type这的实例,换句话说就是动态生成了一个类。
在下面几个代码段里,顶部的代码段用type()函数动态定一个类,底部用平时的方式定义的类,每个例子里上下两个代码段作用相同。
例子1
在这个例子里,传递的 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 是只有一个元素的元组,指示了类Bar是由那个类继承得来的,这个类有一个类属性attr,把他放到命名空间的字典 dct 里:
>>> 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参数,第一个是一个数值,第二个是一个函数
>>> 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
只有简单的函数能用lambda传递,这里演示了如何用f传递更复杂的函数
>>> 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
Custom Metaclasses 自定义元类
脑子里回想下这个老例子:
>>> class Foo:
... pass
...
>>> f = Foo()
表达式 Foo() 创建了一个Foo类的实例,当解释器碰到Foo()语句时候,发生的事情如下:
- Foo类的父类的
__call__()
方法被调用,因为Foo是新模式类因此他是type的子类因此实际上就是调用了type.__call__()
- 这个
__call__()
方法将依次引用如下两个方法:__new__()
__init__()
如果Foo没有定义 __new__()
和 __init__()
的话,默认方法静继承自他的父类,但如果他定义了这些方法,将会覆盖他父类的方法,以此达到定制Foo实例化行为的目的 。
看下面的例子,我们自己定义了一个new方法并将他赋给了Foo的__new__()
方法 (译者注:object是一个type的实例,type继承自object 本例子看成一个就行 详细请看:object 和 type的关系)
>>> 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实例化的行为,每次他实例化的时候都会创建一个attr的属性并将其值设置为100,(通常这种事都是用’init()'来做,这里这么搞纯粹是演示目的)
再次强调下类本身也是对象,那么你想要定制这个类的生成过程,类似于控制Foo实例生成的过程一样。一路看下来你可能想到了,因为类是一个type的实例,你可能会定义一个自己的函数并把它赋到type函数的__new__()
类似于这样:
# 剧透: 玩不转!
>>> 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'
可惜你看到了 ,Python不许你更改type元类的__new__()
函数
因为type是所有新模式类的父类,你其实不应该搞他,那么问题来了,如何定制化类的创建呢?
一个可能的方式就是创建自己的metaclass,与其瞎搞type这个metaclass不如创建一个自己的、由type派生出的metaclass。
定义一个由type派生的metaclass第一步如下:
>>> class Meta(type):
... def __new__(cls, name, bases, dct):
... x = super().__new__(cls, name, bases, dct)
... x.attr = 100
... return x
在头部写class Meta(type):
表明这个类是继承自type,因为type是个metaclass因此他也是个metaclass。
注意一点,这里不能象上文一样直接给Meta类指定__new__()
的方法来做了,这个__new__
方法做了如下事情:
- 委托super()函数调用父类的type的
__new__()
方法创建一个类 - 将类赋一个属性 并把属性值设置为100
- 返回这个修改过的类
现在介绍魔法的另一半,当定义一个类的时候指定它的metaclass为我们自定义的metaclass——Meta,而不是默认的metaclass——type,在类的定义参数里指定参数metaclass (译者注:就能调用我们的metaclass来创建这个类了) 例子如下:
>>> class Bar(metaclass=Meta):
... pass
...
>>> class Qux(metaclass=Meta):
... pass
...
>>> Bar.attr, Qux.attr
(100, 100)
跟类是常见的实例的模板一样,metaclass元类就是类的模板,metaclass有时候也被称为类的工厂
请看看下面的两个例子 (译者注:这里是用定义metaclass的__init__
方法来让生成的类继承metaclass的__init__
方法达到类在创建后的初始化过程中产生一个attr属性并赋值,注意使用这个metaclass的其他类 如果复写__new__
方法时候不调用super
会跳过其自身的__init__
方法):
对象工厂
>>> 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
Is This Really Necessary? 有介个必要么?
上面的例子展示了metaclass如何工作的,以及如何定制类的生成
话虽如此,但是为了一个属性就这样大费周章的是否有介个必要?
在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
Conclusion 结论
就像Tim Peters 建议的一样,虽然元类很容易成为一个问题的可能解,但是通常来讲并不是每个事情都适用定制一个metaclass这种方法,如果一个问题有更简便的方法尽量用更简单的方法。不过理解了metaclass元类是十分有助于理解Python的类的 的行为的。而且有助于你理解哪些问题更适合用定制元类metaclass来解决的。
前后用了一整天翻译和校对本文,如果对您有帮助请务必点个赞。
基本按照原文的意思来翻译的,英文好的小伙伴推荐去看看原文