理解Python元类

想要理解 Python 中的元类只要记住:一切皆对象。记住这 5 个字,理解元类就会轻松很多。

先创建一个自定义对象瞧瞧

1
2
3
4
5
6
class MyClass(object):
pass

my_obj = MyClass()

my_obj
<__main__.MyClass at 0x10907aa20>

我们,很容易就获得了一个自定义的对象。那在这期间都发生了些什么呢?

my_obj = MyClass() 这段代码运行的时候,我们都知道这是在实例化一个类来获取这个类的对象,实例化期间会去走 MyClass 类中的 __init__() 方法。

当然,我们都知道在走 __init__() 方法之前还会先去走 MyClass 类的 __new__() 方法,通过 __new__() 方法来创建类的实例。Python 最常见的单例就是通过 __new__() 方法实现的:

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass(object):
__instance = None
def __new__(cls, *args):
print("in MyClass __new__ method")
if cls.__instance is None:
print("create a new instance")
cls.__instance = super(MyClass, cls).__new__(cls, *args)
return cls.__instance

c1 = MyClass()
c2 = MyClass()
c1, c2
in MyClass __new__ method
create a new instance
in MyClass __new__ method





(<__main__.MyClass at 0x1090a1f60>, <__main__.MyClass at 0x1090a1f60>)

实际上通过这个单例模式可以了解到,其实真正创建对象的方法是 __new__(),而 __init__() 方法只是给已经创建出来的对象赋予属性等操作。

万物皆对象

在 Python 中,函数可以是个对象,类也可以是个对象。既然类是个对象,那就应该拥有对象所拥有的特性:

  • 可以传递给其他变量
  • 可以拷贝它
  • 可以动态地给它增加属性
  • 可以作为参数传递
1
2
3
mc = MyClass
mc.new_attr = 'Hello'
mc, mc.new_attr
(__main__.MyClass, 'Hello')

动态创建类

既然类也是对象,那我们就可以动态的创建一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def create_dynamic_class(name):
if name == 'Class_1':
class Class_1:
pass
return Class_1
else:
class OtherClass:
pass
return OtherClass

d1 = create_dynamic_class('Class_1')
d2 = create_dynamic_class('Class_2')

d1, d2
(__main__.create_dynamic_class.<locals>.Class_1,
 __main__.create_dynamic_class.<locals>.OtherClass)

但是上面的类创建地还不够“动态”,熟悉 Python 应该知道,这时候就要上 type 了。

使用 type 可以这样创建类:

1
2
3
TClass = type('TClass', (), {})
tc = TClass()
tc, type(tc)
(<__main__.TClass at 0x10907a470>, __main__.TClass)

type() 传递三个参数:类名、父类们、属性。

TClass = type('TClass', (), {}) 这个形式有没有觉得很熟悉,像不像通过类来创建对象这个形式 obj = SomeClass()。实际上 type 算是一个类,只不过小写的形式有点迷惑性,不过想想 strintdictlistset 不都是小写的吗。

这样一来,其实所有对象都是 type 的子类?

1
isinstance(int, type), isinstance(str, type), isinstance(dict, type), isinstance(MyClass, type)
(True, True, True, True)

果然就是这个样子!

定义类的创建过程

通过上面单例的示例代码可知,通过 __new__() 方法可以控制对象的创建过程。

而元类就是利用这一特性!控制类的创建过程。

所有的类,默认情况下都是由 type 控制创建的,我们想自定义类的创建过程当然是修改父类 type 的属性。正常情况就是继承 type 创建自已的 “type 类”然后让我们的类继承我们的 “type 类”:

1
2
3
4
5
6
class MyMetaClass(type):
def __new__(cls, name, bases, attrs):
print('got MyMetaClass __new__()')
if name == 'DeniedClass':
raise RuntimeError
return super(MyMetaClass, cls).__new__(cls, name, bases, attrs)
1
2
3
4
5
6
7
8
9
10
class OKClass(metaclass=MyMetaClass):
def __new__(cls, *args):
print('got OKClass __new__()')
return super(OKClass, cls).__new__(cls, *args)

def __init__(self):
print('got OKClass __init__()')

ok_class = OKClass()
ok_class
got MyMetaClass __new__()
got OKClass __new__()
got OKClass __init__()





<__main__.OKClass at 0x1090cc5f8>
1
2
3
4
5
6
7
8
9
10
class DeniedClass(metaclass=MyMetaClass):
def __new__(cls, *args):
print('got DeniedClass __new__()')
return super(DeniedClass, cls).__new__(cls, *args)

def __init__(self):
print('got DeniedClass __init__()')

denied_class = DeniedClass()
denied_class
got MyMetaClass __new__()



---------------------------------------------------------------------------

RuntimeError                              Traceback (most recent call last)

<ipython-input-9-913341894ab1> in <module>()
----> 1 class DeniedClass(metaclass=MyMetaClass):
      2     def __new__(cls, *args):
      3         print('got DeniedClass __new__()')
      4         return super(DeniedClass, cls).__new__(cls, *args)
      5 


<ipython-input-7-266bb1820d0c> in __new__(cls, name, bases, attrs)
      3         print('got MyMetaClass __new__()')
      4         if name == 'DeniedClass':
----> 5             raise RuntimeError
      6         return super(MyMetaClass, cls).__new__(cls, name, bases, attrs)


RuntimeError: 

从上面两个类实例的创建例子可见,实例化一个类对象的时候会先通过该类继承的元类创建出该类的类对象,然后通过该类的类对象创建出该类的对象。

这句话很绕,其实很简单,就是从上往下不断地创建对象(元类 –> 类 –> 实例对象)。

元类应用

大家最熟悉的一个元类应用就是 DjangoORM:

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

p = Person(name='bob', age=35)
print(p.age)

最终输出 p.age35 而不是 models.IntegerField(),这是因为 models.Model 实现的自定义元类对数据库做了一系列 hook,最后暴露给我们这些简单的 API。

参考文档