写这篇文章的缘由

前几天去一家公司面试,面试的岗位是Python后台开发,面试中被问到了一道题,考虑很久想不到答案,顾回来查阅资料在此总结一下。
题目的描述是这样的:

1
2
SQLAlchemy中Model中定义类成员时,如何做到声明顺序与数据库表的列顺序一致
提示:普通类成员是存储在__dict__,而dict本身是无序的

通过阅读SQLAlchemy源码得知,其内部实现是使用元类编程来完成的,元类编程是Python的一种高级黑暗魔法,经常听其大名,并没有进入深入了解,借此机会稍微总结一下。

元编程

元编程我个人理解是:用代码生成(操纵)代码的编程手法。
Python中元编程的几种手段:

  • 预定义方法
  • 函数赋值
  • descriptor
  • eval
  • 元类

上面的几种方式相对于元类来说简单一些,这里暂时先不讲了,以后有时间再去补充

元类

类也是对象

在Python中一切皆对象,同样“类”也是对象

1
2
3
4
5
>>> class OneClass:
... pass
...
>>> OneClass
<class '__main__.OneClass'>

当你在定义一个class时,会在内存中一个对象,对象的名字就是类的名字。
这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因

动态的创建类

最简单的动态创建方式是将类的定义写在逻辑判断中,不同条件生成不同的类,但这还不够动态,你仍然需要自己去编写整个类的代码。如果想要更动态的创建方式,那我们先来了解一个Python的内建函数type

1
2
class type(object): # 当传入一个参数时,返回该对象的类型
class type(name, bases, dict) # 创建新类型,name-类名,bases-父类元组,dict-属性字典

了解到这里,我们就可以用type动态创建class了,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> A = type('A', (), {})
>>> A
<class '__main__.A'>
>>> B = type('B', (), {'b': True})
>>> B
<class '__main__.B'>
>>> C = type('C', (B,), {'c': False})
>>> C
<class '__main__.C'>
>>> c = C()
>>> c.b
True
>>> c.c
False
>>>

现在我们终于接触到元类了,上面使用的type就是元类,type就是Python在背后用来创建所有类的元类。为了证明这一点我们可以使用class来验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> class AA:
... pass
...
>>> def func():
... pass
...
>>> var = 3
>>> AA.__class__
<class 'type'>
>>> AA.__class__.__class__
<class 'type'>
>>> func.__class__
<class 'function'>
>>> func.__class__.__class__
<class 'type'>
>>> var.__class__
<class 'int'>
>>> var.__class__.__class__
<class 'type'>

自定义元类

除了使用Python内建的type元类外,我们还可以使用metaclass来控制类的创建行为,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class MyList(list, metaclass=ListMetaclass):
pass
>>> L = MyList()
>>> L.add(1)
>> L
[1]

回归开篇的那道题目,实现大致是这样的:

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
from collections import OrderedDict
class OrderedClass(type):
@classmethod
def __prepare__(mcs, name, bases):
return OrderedDict()
def __new__(cls, name, bases, classdict):
result = type.__new__(cls, name, bases, dict(classdict))
result.__fields__ = list(classdict.keys())
return result
class Column:
pass
class MyClass(metaclass=OrderedClass):
mycol2 = Column()
mycol3 = Column()
zut = Column()
cool = Column()
menfin = Column()
a = Column()
print(MyClass.__fields__)

在元类中使用prepare返回有序字典来存储类成员定义,在元类中创建类,并将有序的属性名赋值给类的fields,通过访问fields得到与定义顺序相同的属性名称。
想了解更多可以看一下PEP 3115 – Metaclasses in Python 3000
另外,关于类属性定义顺序问题,在Python3.6已经改成默认有序了,详情参考PEP 520 – Preserving Class Attribute Definition Order

究竟为什么要使用元类?

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

元类的主要用途是创建API,一个典型的应用就是数据库的ORM,通过一个class来映射成一张table,通过一个对象来映射成一行数据,将原本复杂的代码变的很简介,这些很多都是元类的功劳。