Python 元类(MetaClass)

元类(MetaClass)就是类的类型,传说中的黑魔法。虽然大多数情况下并不需要编写及使用元类,但是理解元类的内核及工作原理,对于我们读代码及深层次理解Python都有很大的帮助。本文主要着重于Python 3中的元类。Python 2中元类在语法上有些差别,但是工作原理大致相同。

Python 万物皆对象

在理解元类之前,需要掌握Python中的类的概念。在大多数编程语言中,类是一段有特殊结构的代码,代码中定义了类的一些特性(属性、方法等),类的实例为对象。Python中也是这样,但是有Python特色的是:类自身也是对象。Python中,函数是对象,类是对象,所以称为万物皆对象。

Python解释器运行时,当解释器发现class关键字时,解释器处理class代码库生成类对象。本文中我们用类对象指类本身这个对象,用实例对象指由类构建的对象。而实例对象正是由类对象创建的。既然类对象是一个对象,就像所有对象一样,你可以对它做如下操作:

  • 将它赋值给一个变量
  • 为它增加属性
  • 可以作为参数传递给函数
>>> class A(object):
...     pass
...
>>> #这里类对象已经生成
...
>>> print(A)              #作为参数传递
<class '__main__.A'>
>>> A.attr = "abc"        #为类对象增加属性
>>> print(A.attr)
abc
>>> var = A               #将类对象赋值给变量
>>> var.attr
'abc'
>>>

动态的创建类

既然类也是对象,那么可以动态的创建类吗?当然是可以的,实际上创建一个类对象就是动态的创建了一个类。下面的代码可以根据传入的参数创建不同的类。

>>> def get_class(n:int):
...     if n % 2 == 0:
...             class Even(object):
...                     pass
...             return Even
...     else:
...             class Odd(object):
...                     pass
...             return Odd
...
>>> C = get_class(1)
>>> print(C)
<class '__main__.get_class.<locals>.Odd'>
>>> print(C())
<__main__.get_class.<locals>.Odd object at 0x10bc65978>

这个方法称不上完全的”动态“,因为还是要编写class代码块。有没有更好的办法呢?实际上Python中的type函数可以用来创建类对象。

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

共有三个参数:

  • 类的名称
  • 父类元组,可以为空
  • 属性字典

例如下面的代码返回一个类对象,类对象中定义了属性name

>>> class_a = type("A", (), {"name":"A"})
>>> print(class_a)
<class '__main__.A'>
>>> o = class_a()
>>> o.name
'A'
>>>

当然也可以动态生成A的子类,并添加方法:

>>> def show_name(self):
...     print(self.name)
...
>>> class_b = type("B",(class_a,),{"name":"B", "show_name": show_name})
>>> print(class_b)
<class '__main__.B'>
>>> b = class_b()
>>> b.show_name()
B
>>>

B继承了类A,并且定义了show_name方法。

元类

什么是元类呢?元类就是用来创建类(类对象)的东东,实际上type就是元类。元类用来创建类(类对象),而类用来创建实例对象。Python中万物皆对象,字符串、整数、函数等都是对象,都是由类(类对象)构建的,对象的属性__class__记录了创建它的类。

>>> s = "abc"
>>> s.__class__
<class 'str'>
>>> n = 100
>>> n.__class__
<class 'int'>
>>> def func():pass
...
>>> func.__class__
<class 'function'>
>>> class A(object):
...     pass
...
>>> a = A()
>>> a.__class__
<class '__main__.A'>
>>>

由于类也是对象,那么类的*__class__是什么呢?

>>> s.__class__.__class__
<class 'type'>
>>> n.__class__.__class__
<class 'type'>
>>> func.__class__.__class__
<class 'type'>
>>> a.__class__.__class__
<class 'type'>

特别的是:

>>> type.__class__
<class 'type'>
>>>

type是Python内置而且默认的元类。Python也允许自定义元类。

自定义元类

type是Python内置且默认的元类,所有自定义的元类都依赖于type,实际上所有自定义的元类都继承自type

>>> class MyMeta(type):
...     def __new__(cls, name, bases, dct):
...             return type.__new__(cls, name, bases, dct)
...
>>> class X(object, metaclass=MyMeta):
...     pass
...
>>> x = X()
>>> x.__class__
<class '__main__.X'>
>>> X.__class__
<class '__main__.MyMeta'>
>>> MyMeta.__class__
<class 'type'>
>>>
>>>

元类的用处

元类是用来创建类的,也可以用来拦截类的创建及在过程中修改类。日常大部分开发都不会用到元类。

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

目前元类最典型,流传最广的应用就是ORM(对象关系映射),不考虑外键等复杂因素,ORM可以简单理解为:

  • 数据库中每个表都是一个类
  • 每行数据是该类的一个实例

在面向对象的思维方式下,这种思路既自然又合理。估计很多的开发者都有这种想法并且动手实践过,但往往都半途放弃了,原因就是在没有强大的工具或框架的支持下,这种设计性价比太低。但是在Python元类的帮助下,代码具有了动态创建类的能力,大大提高了开发的简便性与效率。Django ORM框架便是基于此实现。