刚开始学习Python的时候,在廖雪峰老师的教程中说到日常的开发中有80%的可能性不会用到元类,所以当时并没有继续往下深入,的确日常的普通开发中除了在ORM中会碰到一些__metaclass__
使用,比较少机会会接触到使用元类。所以就一直没有机会往下学习,最近在阅读项目源码的过程中,看到了元类(metaclass
)的使用。借此机会对元类进行了学习。
Python独特的类型系统
Python中所有的东西都是对象
Python开发哲学中简洁性在语言中体现得淋漓尽致。在许多教程和博客中,经常看到一句:在Python中所有东西都是对象。也就说在Python语言中,无论变量、常量、函数甚至是类都是对象。
类本身也是对象
我们学习过OOP,都知道通过类我们能够创建实例(instance
),也就是在OOP中我们通过类创建实例。既然在Python中class
是对象,那么class
也一定是某个类的实例,而创建class
的类,就称为元类(metaclass
)。我们可以通过解释器来检验这句话1
2
3
4
5class Foo(object):
pass
...
isinstance(Foo, object)
True
和静态语言不同,Python动态语言的特性给予我们非常大的自由,通过元类,我们能够在运行时动态的创建我们所需要的类,又由于类本身就是对象,我们又可以将类存入某个变量中、传入函数中或者作为函数返回值。
元类
如上文所讲,创建class
的类就成为元类。Python中的内建类型type
就是元类的真身。诶?type
不是内建函数吗?怎么又是类型了。是的,type本身是一个内建函数,允许我们查看某个实例的类型。同时,type
本身也是一个类,更是一个对象,解释器跟我们解释了这一现象。1
2
3
4
5
61) type(
<class 'int'>
>>> isinstance(type, type)
True
>>> isinstance(type, object)
True
既然type
是元类,也就是说我们所有的类都是通过type
创建的,同样的我们可以通过解释器来看1
2
3
4
5class Foo(object):
pass
type(Foo)
<class 'type'>
又由于Foo
是type的实例,所以Foo
本身也是object
1
2 isinstance(Foo, object)
True
我们可以通过一张图来了解type
, class
, object
和 instance
之间的关系
类的生成
那么Python如何生成”类实例”呢,首先我们要熟悉一下type
的签名。1
2class type(object)
class type(name, bases, dict)
我们看到,type
函数有两个函数签名,其中一个接受一个参数,另外一个接受三个参数。其中第一个函数接受一个对象,并返回该类的类型(type object), 而第二个函数则创建一个新的类。第一个参数name
为类的名称、第二个参数bases
为类父类, 第三个参数dict
为类的属性。因此我们可以通过下面两种方式来创建Foo
类, 这两个类的功能是一样的,并且有相同的类名称1
2
3
4
5
6# version 1
class Foo(object):
name = 'Foo'
# version 2
Foo = type('Foo', (object,), dict(name='Foo'))
正如我们看到的,Python可以通过调用type
元类在运行阶段创建一个新的class
。实际上Python解释器在执行我们的脚本的时候,也是通过调用type
来创建类的。如下面这个类的定义1
2
3
4
5
6
7class Bar(object):
def __init__(self):
self.name = 'Bar'
self.maker = None
def say_hello(self):
print("Hello world")
Python解释器在阅读完代码后,通过调用type
函数在内存中就生成了一个类实例, 也就是class Bar
。1
2say_hello = lambda self: print("Hello world")
Bar = type('Bar', (object,), {'name': 'Bar', 'maker': None, 'say_hello': say_hello})
所以,我们可以通过调用type
函数,在运行时动态的创建我们的类,通过dict
参数自定义行为。
在默认情况下我们编写的类都是由type负责创建的。
__metaclass__
既然type
本身既是类又是实例(创造type
也是它自己, 先有鸡还是先有蛋?),那么我们也可以创建一个type
的子类。这个子类同样也是元类。我们可以通过制定普通类的元类为我们定制的元类来定制化类的生成。这回轮到我们成为”上帝”了,我们可以随意的修改生成类的行为。这也是Python黑魔法最独特的地方。先以Python2为例(Python3的语法略有不同,下一小节会说)。
Python2中我们可以给类增加__metaclass__
来制定创建该类的元类1
2
3class Foo(object):
__metaclass__ = something...
# -- snip --
这个something
可以是任何能够创建类的东西,包括类和函数。
custom __metaclass__
如何定制化满足我们自己需求的元类的时候,有一些需要注意的小细节。观察下面的例子1
2
3
4
5
6
7
8class MetaABCClass(type):
def __new__(cls, clsname, bases, dct):
# 方式1
klass = type.__new__(cls, clsname, bases, dct)
## 方式2
#klass = type(clsname, bases, dct)
setattr(klass, 'abc', 'I have `abc` attr')
return klass
代码中有两种创建class实例的方式,在定制化远元类的时候,我们需要使用第1种方式。
而方式2是一些新手在接触元类的时候经常犯的错误(包括我)。 明明第2中方式也能够创建出新的类,问题出现在哪里呢?
问题就出现在我们创建klass
的时候仍然是调用type
函数创建的。所以生成的类的元类仍然是type
而非我们自己自定义的类。
我们仍然能够在__new__
方法中对新建的类做一些自定义的操作,但是方式2的弊端会在类的继承上出现问题。查看如下的例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from __future__ import print_function
class MetaABCClass(type):
def __new__(cls, clsname, bases, dct):
print("MetaABCClass is called with %s" % clsname)
klass = type(clsname, bases, dct)
setattr(klass, 'abc', 'I have `abc` attr')
return klass
class Bar(object):
__metaclass__ = MetaABCClass
# -- snip --
class Foo(Bar):
pass
# -- snip --
print("Bar.__class__ is:", Bar.__class__)
print("Foo.__class__ is:", Foo.__class__)
上述的代码的输出如下1
2
3MetaABCClass is called with Bar
Bar.__class__ is: <type 'type'>
Foo.__class__ is: <type 'type'>
要解释这个现象我们就需要先介绍一下元类是如何在类的继承中发挥作用的。
当解释器在看到我们定义了class Foo
类之后,Python将会检查(这里讨论的都是新式类)
Foo
类中是否有__metaclass__
属性- 如果有,那么使用
__metaclass__
属性指定的类,创建类Foo
- 如果没有,那么将使用父类的
__metaclass__
来创建类 - 如果父类没有任何元类,那么将使用
type
来创建类Foo
上文中的代码我们不是使用type.__new__()
来创建Bar类
的而是使用type()
函数创建,那么即使我们定义了__metaclass__
,Bar
类的元类还是type
,因此,”MetaABCClass is called with Bar” 只会输出一次。
- 如果有,那么使用
Python3的写法
Python3 不再使用__metaclass__
这个属性,取而代之的是下面这种写法1
2class Foo(object, metaclass=something):
...
大部分的metaclass的行为和Python2一致,少许不同可以参考PEP 3115
总结
- Python中所有都是对象,包括类也是对象
- 创建实例的叫普通类,而创建类的是元类
- type既是类又是对象,type的元类是它本身
- 我们可以通过继承
type
实现自己的元类,并在普通类中指定元类来自定义类的行为。 - 自定义的元类,一定要使用
type.__new__()
来创建类,而不是type
函数。