【译】Python中的metaclass

译者前言

刚开始学习Python,一直不理解Python中的memetaclass到底是用来做什么的,直到我在stackoverflow上看到这个回答。感觉第二个回答比较好,闲暇时间将它翻译出来作为备用吧。

类对象

在理解metaclass之前,你需要掌握Python中的类。Python中的类借鉴了Smalltalk语言,并且关于类拥有一些奇特的想法。

在大多数语言中,类仅仅是一小段用来描述如何产生对象的代码,这一点在Python中也成立。

1
2
3
4
5
6
7
>>> class ObjectCreator(object):
... pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是,Python中的类不仅仅如此。Python中的类也是对象。

一旦你使用关键字class,Python立即执行并创建一个对象,就像这样在内存中创建一个名字为ObjectCreator的对象

1
2
3
>>> class ObjectCreator(object):
... pass
...

这个对象(类)能创建对象(实例),这就是为什么被称作类。

译者注:这里的类也是对象,而生成的实例也是对象,或者说称之为实例更好些,英文原文如下:

This object (the class) is itself capable of creating objects (the instances), and this is why it’s a class.

它是一个对象,因此:

  • 你可以将它赋值给一个变量
  • 你可以拷贝它
  • 你可以给它添加属性
  • 你可以个哦他传递函数参数

栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> print(ObjectCreator) # 你可以打印一个类,因为它是一个对象
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # 你可以把类当做一个参数传递
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # 你可以给类添加属性
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # 你可把给类赋值给一个变量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态创建类

由于类是一个对象,你可以在运行时动态创建,就像其他对象那样。首先,你可以使用class在函数中创建一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # 返回值是一个类,不是一个实例
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # 函数返回一个类,不是一个实例
<class '__main__.Foo'>
>>> print(MyClass()) # 你可以根据从这个类中创建一个实例
<__main__.Foo object at 0x89c6d4c>

但是这样并不是那么动态,因为你仍然必须写整个类。既然类是对象,那么肯定有东西能生成它。

当你使用class关键字,Python会自动的创建对象。但是像Python中大多数的东西一样,它可以通过手动创建。

还记得函数type吗?这个古老又非常好用的函数可以让你知道某个对象的类型。

1
2
3
4
5
6
7
8
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

type还拥有不同的能力,他也可以在运行时创建一个类。type可以接收一个类描述作为参数,并且返回一个类。(我知道,根据你传入参数的不同,同一个函数拥有两种不同的作用是一件很傻的事情。在Python中这个为了能够向后兼容)

type的用法是这样的:

1
2
3
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)

栗子:

1
2
>>> class MyShinyClass(object):
... pass

也可以通过这种方法手动创建:

1
2
3
4
5
>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一个类对象
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 创建这个类的实例
<__main__.MyShinyClass object at 0x8997cec>

你会注意到我们使用”MyShinyClass”作为类名,并且作为变量作为这个类的引用。类和变量是不同的,在这里没有必要将它们复杂化。

type接受一个字典来定义类的属性。所以:

1
2
>>> class Foo(object):
... bar = True

也可以写成:

1
>>> Foo = type('Foo', (), {'bar':True})

可以像正常类一样来使用:

1
2
3
4
5
6
7
8
9
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

当然,你也可以从它继承,因此:

1
2
>>>   class FooChild(Foo):
... pass

或者这样:
译者注:这里要注意type函数的第二个参数要使用单个元素的元组写法。

1
2
3
4
5
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # barFoo继承
True

最终你想要给你的类添加方法,你只要有定义一个函数,并将其赋值给字典中的某个属性即可。

1
2
3
4
5
6
7
8
9
10
11
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

在动态创建类之后,你可以添加更多的方法,就像向常规的类对象添加方法一样。

1
2
3
4
5
6
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

你可以看到,在Python里,类就是对象,你可以在运行时动态的创建类。这就是当你使用关键字class的时候,Python在幕后做的事情,他是通过元类来实现的。

什么是元类 (终于)

元类就是创建类的东西,你定义类是为了创建对象,对吧?同样我们也知道Python中的类就是对象。那么,元类创建类,它们是类的类,你可以将它们当成这样:

1
2
MyClass = MetaClass()
MyObject = MyClass()

你已经看到type让你这样做:

1
MyClass = type('MyClass', (), {})

这是因为函数type是一个元类,它是Python中所有类的元类。现在你可能会疑惑为什么它是小写,而不是Type

我想它可能是为了与str保持一致,str是创建字符串对象,int是创建整数对象,type就是创建类对象。你可以检查__class__属性。

在Python中一切都是对象,包括整数、字符串、函数、类。它们都是对象,并且所有的这些都可以从类中创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

那么,__class____class__属性是什么?

1
2
3
4
5
6
7
8
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

所以说,元类就是创建类对象的东西,你可以叫它“类工厂”。type是Python中的内建元类,当然,你也可以创建自己的元类。

metaclass 属性

当你写一个类的时候,可以添加__metaclass__属性:

1
2
3
class Foo(object):
__metaclass__ = something...
[...]

如果你这样做了,Python将会使用元类来创建类Foo。当心,这里有一些技巧。当你写下class Foo(object),但是类Foo现在还没有在内存当中创建。Python将会在类中查找__metaclass__定义,如果找到了,将会创建类对象Foo,如果没有找到,它将会使用type来创建类。请反复的读这句话。当你写下以下时:

1
2
class Foo(Bar):
pass

Python会按照以下运行:

Foo中有__metaclass__属性吗?如果有,Python通过使用__metaclass__在内存中创建名字为Foo的类对象(我说的是类对象,请跟上我)。如果Python中找不到__metaclass__,它会在模块层级上找__metaclass__,并尝试同样的操作(但只适用于不继承任何东西的类,基本上是旧式的类)。接着,如果Python中没有找到__metaclass__,它会使用Bar(父类)的元类(这里或许默认是type)来穿件类对象。

这里要小心,__metaclass__属性并不是从父类继承而来的,父类中的元类(Bar.__class__)是的。如果Bar使用type()(并不是type.__new__())来创建Bar__metaclass__属性,子类将不会继承这种行为。

现在有一个很大的问题,__metaclass__中可以存放什么?答案是:可以创建类的东西。那么什么可以创建类呢?type,或者是任何使用到type或者子类化type的东西都可以。

自定义元类

元类的主要目的是当创建类的时候能自动的改变类。通常你会为API做这样的事情,你希望可以创建符合当前上下文的类。

假想已个很傻的例子,当你决定模块中所有的类的属性都应该是大写的形式。有好几种办法可以做到,其中一种是设置模块级别的__metaclass__。幸运的是,__metaclass__能够自动被调用,他并不需要是一个正式的类(我知道某些被称为’class’的东西并不需要是一个class,可以尝试画图理解下,很有帮助)。所以,我们以一个简单的例子作为开始:

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
# 元类将自动通过传给 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.
"""

# 选出属性不已 ‘__’ 开都的属性,并把它们变成大写
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

# 用 type 创建类
return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # 将会影响模块中的所有类

class Foo(): # 全局 __metaclass__ 并不会作用于对象
# 我们也可以定义 __metaclass__ 只作用于这个类和它的子类
bar = 'bip'

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

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

现在让我们使用真正的class来作为元类在做一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ‘type’ 实际上是一个类,就像 'str', 'int'
# 所以,你也可以直接从它继承
class UpperAttrMetaclass(type):
# __new__ 是在 __init__ 之前被调用的特殊方法
# __new__ 是用来创建对象,并返回它的方法
# 而 __init__ 只是用来将传入的参数初始化给对象
# 你很少使用到 __new__ ,除非你想控制对象的创建
# 这里创建对象是类,我们想自定义它,所以我们这里重写 __new__
# 你可以在 __init__ 中做你想要做的事情
# 一些高级的用法会涉及到重写 __call__ ,但是我们不用
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

# 重用 type.__new__ 方法
# 这是基本的面向对象,并没有什么魔法
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)

你或许已经注意到额外的参数upperattr_metaclass,这并没有什么特别的:__new__总是接收它定义的类作为第一个参数。就像你使用self作为一个普通方法或者类的类方法的第一个参数那样。

当然,为了更加清晰,名字我定义的很长。但是就像self那样,所有的参数都有传统的名称,因此在真正的产品中元类应该是这样:

1
2
3
4
5
6
7
8
9
10
11
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来是它更加清晰,这回有利于继承(是的,你可以拥有元类,从元类继承,从type继承):

1
2
3
4
5
6
7
8
9
10
11
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)

就是这样,关于元类真的没什么可以说的了。

使用元类的代码比较复杂,其背后原因并不是元类本身。而是你通常使用元类做一些晦涩难懂的事情,依赖自省、控制继承、__dict__等等。

的确,元类在使用黑魔法的时候非常有用,因此会弄出复杂的东西来,但就元类本身,它们是非常简单的:

  • 拦截类的创建
  • 修改类
  • 返回修改的类

为什么使用元类来代替函数?

因为 __metaclass__可以接收任何调用对象,为什么使用类,因为它看起来比较复杂?这里有好几个原因:

  • 意图更加清晰,当你读到UpperAttrMetaclass(type),你会知道接下来做什么。
  • 你可以使用面向对象(OOP),元类可以冲元类中继承二改,重写父类的方法,元类甚至可以使用元类。
  • 你可以使你的代码更加有结构化。当你使用元类的时候像上面的例子那样简单。通常非常的复杂。有能力将多个方法归到一个类当中,对你的代码可读性非常有帮助。
  • 你可以使用 __new____init____call__方法,这些都允许你做不同的事情。就算你讲所有的东西都放在__new__当中处理,游戏而还是觉得使用__init__更加舒服。
  • 这个东西被称作元类,一定要小心使用。

为什么使用元类?

现在有一个大问题,为什么要使用一些容易出错的功能?好吧,通常你用不到它:

“元类就是深入的魔法,99%的用户应该不要担心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。(那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。)” —— Python界的领袖 Tim Peters

元类最主要的用途是创建API。一个典型的例子是 Django ORM。它允许你这样定义:

1
2
3
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()

但是,如果你这样定义:

1
2
guy = Person(name='bob', age='35')
print(guy.age)

它不会返回IntegerField对象,它会返回一个int,甚至可以从数据库中取出来。这是可能的,因为models.Model中定义了一个__metaclass__,并使用了一些魔法能够将你定义的Person类转变成对数据库的复杂的hook。Django将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。

结语

首先,你知道类是一个对象并且能够穿件实例。事实上,类本身也是元类的实例。

1
2
3
>>> class Foo(object): pass
>>> id(Foo)
142630324

Python中一切都是对象,要么是类的实例,要么是元类的实例。

除了typetype实际上是它自己的元类。在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。

其次,元类是复杂的。你或许不想通过使用它来对简单的类做修改,你可以改变类使用下面的两种技巧:

99%的时间你需要改变类,你最好使用这些,但是99%的时间你根本不需要修改类。:D


参考

stackoverflow: What is a metaclass in Python