Improve Your Python - Metaclasses and Dyanamic Classes With Types

11 February 2014

原文链接

metaclassestype 关键字都很少被使用到(因此并没有很好地被大多数Python构造理解). 在这篇文章中, 我们将会探索不同类型的type()以及怎么使用鲜为人知的与metaclasses相关的type的使用.

你是我的菜(type)吗?

type()的第一种用法就是最广为人知的: 决定一个对象的类型. 这里, Python初学者通常会打断并且说: “但是我以为Python没有类型呢!” 相反, Python里的 任何东西 都是有类型的(即使是type!) 因为 任何东西 都是一个对象. 让我们看几个例子:

1
2
3
4
5
6
7
8
>>> type(1)
<class 'int'>
>>> type('foo')
<class 'str'>
>>> type(3.0)
<class 'float'>
>>> type(float)
<class 'type'>

type的类型

一切都和预期的一样, 直到我们检查了float的类型. <class 'type'>? 这是什么? 好吧, 很奇怪, 但是让我们继续:

1
2
3
4
5
>>> class Foo(object):
...     pass
...
>>> type(Foo)
<class 'type'>

啊! 又是<class 'type'>. 显然所有类自己的类型都是type(不管它们是内置的还是用户定义的类). 那type自己的类型是什么呢?

1
2
>>> type(type)
<class 'type'>

好吧, 它不得不在某个地方结束. type是所有类型的类型, 包括它自己. 实际上, type是一个metaclass(元类), 或者说是”一个构造类的东西”. 类, 就像list()使用my_list = list()这样, 构造那个类的实例. 同样的, metaclasses构造类型, 就像Foo这样:

1
2
class Foo(object):
    pass

构造你自己的元类

和正常的类一样, metaclasses可以是用户自己定义的. 为了使用这样的元类, 你可以把一个类的__metaclass__属性设置为你自己构造的metaclass. 一个metaclass可以是任何调用, 只要它返回一个类型. 通常, 你会指定一个类的__metaclass__为一个函数, 这样在某些时候就可以使用我们还没有讨论到的type的一个变体: 使用三个参数来创建类.

type的阴暗面

如前所述, 当使用三个参数调用的时候type有一个完全独立的使用. type(name, bases, dict)以编程的方式创建了一个 新的 类型.如果我有如下的代码:

1
2
class Foo(object):
    pass

那么我们可以用如下的方式实现同样的效果:

1
Foo = type('Foo', (), {})

Foo现在就是一个叫做”Foo”的类了, 它的基类是object(使用type创建的类, 如果没有指定基类, 就自动创建为新式类).

这样很好, 但是如果我们想给Foo添加成员函数该怎么办呢? 这可以很容易地通过设置Foo的属性来实现, 就像这样:

1
2
3
4
def always_false(self):
    return False

Foo.always_false = always_false

我们可以使用如下的方法一气呵成:

1
Foo = type('Foo', (), {'always_false': always_false})

当然, bases参数是Foo的一系列基类. 我们已经让它为空了, 但是创建一个源自Foo的新类也是完全有效的, 再一次使用type来创建它:

1
FooBar = type('FooBar', (Foo), {})

这种方式什么时候有用呢?

一旦跟某人解释了某些话题, 下一个问题就很可能是”好, 那么我什么时候使用它?”, typemetaclass就是这些话题之一. 答案是, 一点都不常用. 但是, 确实 存在 有时候动态地使用type来创建类是最合适的方法. 让我看一个例子.

sandman是我写的一个库, 它可以为已经存在的数据库自动生成一个REST API和基于web的管理界面(不需要任何样板代码). 大多数繁重的工作都是由SQLAlchemy完成, SQLAlchemy是一个ORM框架.

使用SQLAlchemy只有一种方法来注册一个数据库的表: 创建一个描述了这张表的Model类(和Django的models没有什么不同). 为了让SQLAlechemy认识这个表, 必须以某种方式创建这个表的类. 既然sandman没有任何关于数据库结构的高级知识, 它不能依赖预先创建的模型类来注册表. 而是, 它需要内省这个数据库然后在内省过程中创建这些类. 听起来很熟悉? 任何你需要动态地创建新类的时候, type就是正确的/唯一的选择.

这是来自sandman的相关代码:

1
2
3
4
5
6
if not current_app.endpoint_classes:
    db.metadata.reflect(bind=db.engine)
    for name in db.metadata.tables():
        cls = type(str(name), (sandman_model, db.Model),
                {'__tablename__': name})
        register(cls)

就像你看到的, 如果用户没有手动为一张表创建一个模型类, 它会自动被创建, 并且__tablename__属性会被设置成这张表的表名(被SQLAlchemy用来匹配表和类).

总结

在这篇文章中, 我们讨论了typemetaclasses的用法, 以及什么时候需要使用type的特殊用法. 尽管metaclasses是有些模糊不清的概念, 希望你现在为以后的学习打下了良好的基础.

标签:
  • Python
comments powered by Disqus