python中的元类编程详解

类是动态创建的

我们知道Python 里一切都是对象,那么是对象就有对应的“类(Class)”,或称“类型(type)”。 Python 中可以用 type(obj) 来得到对象的“类”。既然一切都是对象,一个“类(class)”也可以认为是一个对象,那么类的“类型(type)”是什么呢?“类(class)”的类型(type) 都是 type。那 type 的类型又是什么呢?抱歉,type 的类型还是 type,是一个递归的类型。“普通类(class)”可以用来生成实例(instance),同样的,元类 (meta-class)也可以生成实例,生成的实例就是“普通类”了。类(class)可以有多个实例(instance)。而创建实例的方法就是调用类的构造函数(constructor):

1
2
3
4
class Spam(object):
def __init__(self, name):
self.name = name
spam = Spam('name')

上例我们定义了一个类,并调用类的构造函数创建了该类的一个实例。我们知道类也可以看作类 type 的一个实例,那么如何用 type 的构造函数来动态创建一个类呢?我们先看看type的构造函数:class type(name, bases, dict)

  • name: 字符串类型,存放新类的名字
  • bases: 元组(tuple)类型,指定类的基类/父类
  • dict: 字典类型,存放该类的所有属性(attributes)和方法(method)
1
2
3
>>> class X:
a = 1
>>> X = type('X', (object,), dict(a=1))

类的创建过程

要了解元类(meta-class)的作用,我们就需要了解 Python 里类的创建过程,如下:
类class的创建过程

  1. 当 Python 见到 class 关键字时,会首先解析 class 中的内容。例如解析基类信息,最重要的是找到对应的元类信息(默认是 type)。
  2. 元类找到后,Python 需要准备 namespace (也可以认为是上节中 type 的 dict 参数)。如果元类实现了 prepare 函数,则会调用它来得到默认的 namespace 。
  3. 之后是调用 exec 来执行类的 body,包括属性和方法的定义,最后这些定义会被保存进 namespace。
  4. 上述步骤结束后,就得到了创建类需要的所有信息,这时 Python 会调用元类的构造函数来真正创建类。如果你想在类的创建过程中做一些定制(customization)的话,创建过程中任何用到了元类的地方,我们都能通过覆盖元类的默认方法来实现定制。这也是元类“无所不能”的所在,它深深地嵌入了类的创建过程。

自定义元类

元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过设定metaclass。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
'''返回一个类对象,将属性都转为大写形式'''
#选择所有不以'__'开头的属性
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
#通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr) #返回一个类

class Foo(object):
__metaclass__ = upper_attr
bar = 'bip'

使用class来创建元类:

1
2
3
4
5
class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

元类中的特殊方法

type 类,作为Python中所有类(包括type本身)的默认元类,定义了一些特殊方法,可供自定义的元类覆盖,以完成特定的行为。常见的几个特殊方法有:

  • __new__(cls,name,base,attr): 元类中, new 会在你定义类的时候执行, 只执行一次.
  • __init__(self, name, *args, **kwargs): 该元类的实例(也就是普通类)创建后被调用,用于为初始化实例的.
  • __call__(self, *args, **kwargs): 元类创建的实例(也就是普通类),构造普通类的对象时调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class NewMeta(type):
def __new__(*args, **kwargs):
print('__new__() from NewMeta',args,kwargs)
return type.__new__(*args, **kwargs)

def __init__(self,name,*args,**kwargs):
print('__init__() from NewMeta',args,kwargs)
return type.__init__(self,name,*args,**kwargs)

def __call__(self, *args, **kwargs):
print('__call__() from NewMeta',args,kwargs)
return type.__call__(self, *args, **kwargs)

class Test(object,metaclass=NewMeta):
pass

print('---------------------------------')
Test()

结果为:

1
2
3
4
__new__() from NewMeta (<class '__main__.NewMeta'>, 'Test', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'Test'}) {}
__init__() from NewMeta ((<class 'object'>,), {'__module__': '__main__', '__qualname__': 'Test'}) {}
---------------------------------
__call__() from NewMeta () {}

类在创建类的时候new只会被调用一次。而这个new就是用来创建出我们的类。因为普通类是元类创建出来的类,可以认为普通类是元类的实例对象。所以每次运行普通类的时候都会去调用元类的call。而在call中我们拿到已经创建好的实例对象。

通过元类来实现单例模式

当初我也很疑惑为什么我们是从写使用元类的init方法,而不是使用new方法来初为元类增加一个属性。其实我只是上面那一段关于元类中new方法迷惑了,它主要用于我们需要对类的结构进行改变的时候我们才要重写这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Singleton(type):
def __init__(self, *args, **kwargs):
print "__init__"
self.__instance = None
super(Singleton,self).__init__(*args, **kwargs)

def __call__(self, *args, **kwargs):
print "__call__"
if self.__instance is None:
self.__instance = super(Singleton,self).__call__(*args, **kwargs)
return self.__instance

class Foo(object):
__metaclass__ = Singleton
#在代码执行到这里的时候,元类中的__new__方法和__init__方法其实已经被执行了,而不是在Foo实例化的时候执行。且仅会执行一次。

foo1 = Foo()
foo2 = Foo()
print Foo.__dict__
# _Singleton__instance': <__main__.Foo object at 0x100c52f10>
# 存在一个私有属性来保存属性,而不会污染Foo类(其实还是会污染,只是无法直接通过__instance属性访问)

print foo1 is foo2 # True

# 输出
# __init__
# __call__
# __call__
# {'__module__': '__main__', '__metaclass__': <class '__main__.Singleton'>, '_Singleton__instance': <__main__.Foo object at 0x100c52f10>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
# True

基于这个例子:

  • 我们知道元类(Singleton)生成的实例是一个类(Foo),而这里我们仅仅需要对这个实例(Foo)增加一个属性(instance)来判断和保存生成的单例。想想也知道为一个类添加一个属性当然是在init__中实现了。
  • 关于call方法的调用,因为Foo是Singleton的一个实例。所以Foo()这样的方式就调用了Singleton的call方法。不明白就回头看看上一节中的call方法介绍。假如我们通过元类的new方法来也可以实现,但显然没有通过init来实现优雅,因为我们不会为了为实例增加一个属性而重写new方法。所以这个形式不推荐。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Singleton(type):
def __new__(cls, name,bases,attrs):
print "__new__"
attrs["_instance"] = None
return super(Singleton,cls).__new__(cls,name,bases,attrs)

def __call__(self, *args, **kwargs):
print "__call__"
if self._instance is None:
self._instance = super(Singleton,self).__call__(*args, **kwargs)
return self._instance

class Foo(object):
__metaclass__ = Singleton

foo1 = Foo()
foo2 = Foo()
print Foo.__dict__print foo1 is foo2 # True

# 输出# __new__
# __call__
# __call__
# {'__module__': '__main__', '__metaclass__': <class '__main__.Singleton'>, '_instance': <__main__.Foo object at 0x103e07ed0>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
# True

Reference

  1. 深刻理解Python中的元类(metaclass):http://blog.jobbole.com/21351/
  2. Python 元类 (MetaClass) 小教程: https://lotabout.me/2018/Understanding-Python-MetaClass/
  3. 深刻理解Python中的元类(metaclass)以及元类实现单例模式: https://www.cnblogs.com/tkqasn/p/6524879.html
  4. python3中的元类: http://blog.lujun9972.win/blog/2018/02/23/python3%E4%B8%AD%E7%9A%84%E5%85%83%E7%B1%BB/
  5. 定义可选参数的元类: https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p15_define_metaclass_that_takes_optional_arguments.html
  6. Python中的类元编程: https://segmentfault.com/a/1190000014323911
  7. Python 类与元类的深度挖掘: http://python.jobbole.com/84986/
  8. 如何从Python 3.x中的类定义传递参数到元类?