关于python metaclass 也就是元类的一点体会。
网络上说python metaclass的文章很多,感觉这一篇看了以后还是能比较清楚对元类有一个理解。下面谈谈自己的一些体会。
何为元类,其实也没那么神秘。python中所有的东西都是对象,方法是对象,变量是对象,类也是对象。一般我们在创建一个类的时候,会在代码里这么写
1
2
3
4
|
class ClassA(object):
pass
>>> ClassA
<class '__main__.ClassA'>
|
这就创建了一个类。由于类是一个对象,那么我们可以在代码运行时动态地创建一个类,不需要预先写任何代码。
1
2
3
|
ClassA = type('ClassA', (), {})
>>> ClassA
<class '__main__.ClassA'>
|
这里我们用到了type,这样创建的一个类与之前我们在代码里写的时候创建的类一模一样。这就是类的动态创建。type是一个很特殊的方法。可以判断一个对象是属于什么类型,也可以用来动态创建类。那么既然类也是一个对象,那么这个对象是什么类型呢?
1
2
3
4
|
class ClassA(object):
pass
>>> type(ClassA)
<type 'type'>
|
可以验证,不管是通过代码里写的一个类还是用type创建的一个类,它的类型都是type!type创建类时需要三个参数,类名,继承列表,属性。于是我们甚至在不用写代码的情况下创建一个继承自ClassA并且自带一些属性的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class ClassA(object):
pass
def printa(self):
print self.a
ClassB = type('ClassB', (ClassA,), {'a': 1})
classb = ClassB()
>>> type(ClassB)
<type 'type'>
>>> ClassB.a
1
>>> classb.printa()
1
|
其实当我们写代码来声明一个类,python解释器在执行到这个类的相关代码时会收集到这个类的名字,继承列表和属性,然后用type来创建它。
type就是元类。元类就是可以创建类的类。python里type是所有类的元类。我们可以用类的class属性看到一个对象是哪个类的对象。
1
2
3
4
|
>>> classb.__class__
<class '__main__.ClassB'>
>>> classb.__class__.__class__
<type 'type'>
|
那么除了type这个元类是不是还有别的元类呢,可以的。我们可以自己创建一个类作为另一个类的元类。这里列举一段odoo的代码来看看。在odoo代码openerp/cli包的init文件中先从command.py中引入了Command类,然后在server.py,shell.py中各自继承Command创建了子类。而command.py中的Command类定义了一个元类CommandType。
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
33
34
35
36
37
|
commands = {}
class CommandType(type):
def __init__(cls, name, bases, attrs):
super(CommandType, cls).__init__(name, bases, attrs)
name = getattr(cls, name, cls.__name__.lower())
cls.name = name
if name != 'command':
commands[name] = cls
class Command(object):
"""Subclass this class to define new openerp subcommands """
__metaclass__ = CommandType
def run(self, args):
pass
class Help(Command):
"""Display the list of available commands"""
def run(self, args):
print "Available commands:\n"
names = commands.keys()
padding = max([len(k) for k in names]) + 2
for k in sorted(names):
name = k.ljust(padding, ' ')
doc = (commands[k].__doc__ or '').strip()
print " %s%s" % (name, doc)
print "\nUse '%s <command> --help' for individual command help." % sys.argv[0].split(os.path.sep)[-1]
def main():
# Default legacy command
command = "server"
if command in commands:
o = commands[command]()
o.run(args)
|
这里摘录了command.py中的部分代码,用来展示这里是如何使用元类的。可以看到Command类里定义了一个空run方法就没了。但由于它的元类是CommandType(注意所有自定义的元类都要继承自type),在解释器执行到Command类时,先收集代码中定义的Command类的属性,继承列表等,然后由于这里定义了元类,就不是直接调用type来创建一个了类了。而是会调用元类的new方法,来创建一个类,然后调用元类的init方法。
这里由于CommandType类没有定义new方法,会直接调用其父类也就是type的new方法创建一个类,然后传给init方法执行初始化。在初始化的时候传入了Command类的属性,装进了commands这个字典里。
稍后到了Help类,由于其继承自Command,也继承了它的元类属性,所以也会执行刚刚Command类创建时的一些操作,从而也将Help类的一些属性装进了commands里,server.py, shell.py中的继承自Command的类也是如此。
然后后面main方法在遍历commands字典,根据参数取出相应的类来执行其方法。这就实现了一种模式,任何想要拓展commands的行为,只需要继承Command类,然后定义run方法就行了。感觉很不错的思路。