Python | 怎么理解元类「metaclass」

虽然「metaclass」不常用(据说 Python 中 object 里有太多没必要的杂七杂八特性),不过 ORM 这种复杂的结构还是会需要的,之前写 ORM 遇上了,不管3721,总结下。

一、什么是 「metaclass」?

面向对象编程中,实例为类的类便是 「metaclass」,或者说类是「metaclass」实例。一般类都是定义对象的行为,而「metaclass」则是定义特定类和其实例的行为。

Instance_of

在 Python 中 一切皆为对象,类也是对象,这个特性使得我们可以随时随地的创建类。

创建一个类

通常我们创建一个类的方法是:

1
2
3
4
class ClassName(object):
"""docstring for """
def __init__(self, arg):
pass

类在方法中创建:

1
2
3
4
def class_in_method(_class):
if _class:
class ClassInMethod(Object):
pass

使用class关键字时,Python 会自动给我们创建这个类的对象。那么有没有其他方法创建一个类的对象呢?
答案是用type

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> MyClass = type('ClassName', (), {})

>>> print(MyClass)
<class '__main__.ClassName'>

>>> print(MyClass())
<__main__.ClassName object at 0x000000000508BC18>

>>> type(MyClass)
<class 'type'>

>>> type(MyClass())
<class '__main__.ClassName'>

可以发现,这个跟用 class 创建出来的类是一样的。

额?是不是很奇怪?

Python 中所有标准类型的「metaclass」就是“type”对象。type本身是一个类,既是类的实例,又是其本身的实例。

1
2
3
4
5
6
7
8
9
10
11

>>> type(object)
<type 'type'>
>>> type(int)
<type 'type'>
>>> type(list)
<type 'type'>
>>> type(type)
<type 'type'>
>>> isinstance(MyClass, type)
True

type 是 Python 中默认用来创建类的「metaclass」。
当你用 class 关键字创建类时,实际过程是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyClass(object):
def __init__(self):
pass

class_name = 'MyClass'
class_parents = (object,)
class_body = """
def __init__(self, name):
pass
"""

# a new dict is used as local namespace
class_dict = {}

# the body of the class is executed using dict from above as local namespace
exec(class_body, globals(), class_dict)

# 类的主体里的方法存储在 class_dict 中
>>> class_dict
{'__init__': <function __init__ at 0x10066f8c8>, }
# 最后调用 type 创建类实例
Foo = type(class_name, class_parents, class_dict)

typeobject 关系

object 和 type的关系很像鸡和蛋的关系,先有object还是先有type没法说,obejct和type是共生的关系,必须同时出现的。
为了更好的理解 typeobject,我们来看下面这幅图:
types_map

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> type.__base__
<class 'object'>
>>> type.__class__
<class 'type'>

>>> object.__base__ # object无父类
>>> object.__class__
<class 'type'>

>>> list.__base__
<class 'object'>
>>> list.__class__
<class 'type'>

通过 __base__ 查看「父类」;__class__ 查看「类型」我们可以发现。

  1. 「type」 是所有 object 的 “type”,包括其自身;
  2. 「object」 是所有类的父类,但其自身没有父类,type的父类也是object;
  3. 所有 object 都是 「type」的实例,而type 与object互为实例。

二、自定义「metaclass」

使用函数

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
31
32
33
34
35
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""


# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)

# __metaclass__ = upper_attr # this will affect all classes in the module

class Foo(metaclass=upper_attr): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children

bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

使用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):


uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

return type(future_class_name, future_class_parents, uppercase_attr)

这还不够 OOP,我们可以直接使用type,而不重载或调用__new__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UpperAttrMetaclass(type):

def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):


uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)

注意到这个upperattr_metaclass的额外参数了没?这个其实没什么,就像是方法中的self一样,__new__ 总是接收它定义的类作为第一个参数。

上面这枚写是为了好区分,事实上惯用写法是:

1
2
3
4
5
6
7
8
9
10
11
12
class UpperAttrMetaclass(type):

def __new__(cls, clsname, bases, dct):

uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

return type.__new__(cls, clsname, bases, uppercase_attr)

最后,还可以使用 super 是其变得更为简明:

1
2
3
4
5
6
7
8
9
10
11
12
class UpperAttrMetaclass(type):

def __new__(cls, clsname, bases, dct):

uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

三、为何使用「metaclass」?

难用的「metaclass」

「metaclass」这么难用的东西,肯定有很多 黑魔法 XD、、

Metaclasses are deeper magic that 99% of users should never worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).
Python Guru Tim Peters

但是其本身又是很简单的东西:

  1. 拦截类的创建
  2. 修改类
  3. 返回修改了的类

为什么要用「metaclass」而不是「函数」?

由于metaclass可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?这里有好几个原因:

  1. 意图会更加清晰。当你读到 UpperAttrMetaclass(type) 时,你知道接下来要发生什么。

  2. 你可以使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。

  3. 你可以把代码组织的更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。

  4. 你可以使用__new__, __init__以及__call__这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__里处理掉,有些人还是觉得用__init__更舒服些。

  5. Fuck!「metaclass」!Fire in the house!!!!!!

四、「Metaclass」 示例

使用「metaclass」可以发挥无限潜力,目前开发的一些用法有:记录日志、检查接口、自动提交事务、自动创建属性、代理、框架、自动锁定资源/同步。

下面这个例子是利用 collections.OrderedDict 来记录一个类中变量的顺序的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class OrderedClass(type):

@classmethod
def __prepare__(metacls, name, bases, **kwds):
return collections.OrderedDict()

def __new__(cls, name, bases, namespace, **kwds):
result = type.__new__(cls, name, bases, dict(namespace))
result.members = tuple(namespace)
return result

class A(metaclass=OrderedClass):
def one(self): pass
def two(self): pass
def three(self): pass
def four(self): pass

>>> A.members
('__module__', 'one', 'two', 'three', 'four')

五、总结

  1. 「Metaclass」生产「class」,「class」生产「object」,「type」是 Python 中默认 「Metaclass」;
  2. type 是所有「object」的“type”,包括其自身;
  3. object 是所有类的父类,只有object 没有父类
  4. 万物皆「object」,所有「object」都是 type 实例,所有对象都是 object 的实例。

References:

  1. StackOverFlow: What is a metaclass in python
  2. Understanding python metaclass, ionel’s blog
  3. Wikipedia: Metaclass
  4. PyDoc: data
  5. Python Types and Objects, Shalabh Chaturvedi
  6. 知乎:Type 和 Object 的关系