[译]理解python的metaclass

前言

这篇博客是我在stackoverflow上看了一个提问回复后写的,例子基本用的都是e-satis本人的例子,语言组织也基本按照翻译来。python

但我并非一个翻译者,并不会严格遵照每行每句的翻译;
有时候我会将表述换个顺序,省略一些我认为可有可无的话,以便读者更好理解。web

因此,若是你不喜欢个人语言表述,或者想要看英文原文,能够去查看原回复数据库

类也是对象

在理解metaclass以前,咱们先要掌握python中的类class是什么。
python中类的概念,是借鉴自smalltalk语言。
在大部分语言中,类指的是"描述如何产生一个对象(object)"的一段代码,这对于python也是如此。编程

>>> class ObjectCreator(object):
...     pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

可是,在python中,类远不止如此,类同时也是对象。
当你遇到关键词class的时候,python就会自动执行产生一个对象。下面的代码段中:框架

>>> class ObjectCreator(object):
...     pass
...

python在内存中产生了一个名叫作"ObjectCreator"的对象。这个对象(类)自身拥有产生对象(实例instance)的能力。 这就是为何称呼这东西(后面遇到容易混淆的地方,咱们称之为:类对象)也是类的缘由。同时,它也是一个对象,所以你能够对它作以下操做:函数

  • 赋值给变量
  • 复制它
  • 为它增长属性(attribute)
  • 做为参数传值给函数

举例:工具

>>> print(ObjectCreator) # 你能够打印一个类,由于它同时也是对象
<class '__main__.ObjectCreator'>

>>> def echo(o):
...     print(o)
...
>>> echo(ObjectCreator) # 做为参数传值给函数
<class '__main__.ObjectCreator'>

>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo

>>> ObjectCreatorMirror = ObjectCreator # 将类赋值给变量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态建立类

既然类也是对象,那么咱们就能够在运行的时候建立它,跟建立对象同样天然。学习

首先,咱们使用class关键字定义一个产生类的函数:this

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

这很容易理解吧。可是,这并不那么动态啊。咱们仍是须要本身来写这个类的代码。spa

既然类也是对象,那就应该有用来产生它的东西。这东西就是type

先来讲说你所认识的type。这个古老而好用的函数,可让咱们知道一个对象的类型是什么。

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

实际上,type还有一个彻底不一样的功能,它能够在运行时产生类。type能够传入一些参数,而后返回一个类。(好吧,必须认可,根据不一样的传入参数,一个相同的函数type竟然会有两个彻底不一样的做用,这很愚蠢。不过python这样作是为了保持向后兼容性。)

下面举例type建立类的用法。首先,对于类通常是这么定义的:

>>> class MyShinyClass(object):
...     pass

在下面,MyShinyClass也能够这样子被建立出来,而且跟上面的建立方法有同样的表现:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

type建立类须要传入三个参数,分别为:

  • 类的名字
  • 一组"类的父类"的元组(tuple) (这个会实现继承,也能够为空)
  • 字典 (类的属性名与值,key-value的形式,不传至关于为空,如通常写法中的pass).

下面来点复杂的,来更好的理解type传入的三个参数:

class Foo(object):
    bar = True

    def echo_bar(self):
        print(self.bar)

等价于:

def echo_bar(self):
    print(self.bar)

Foo = type('Foo', (), {'bar':True, 'echo_bar': echo_bar})

想要看点有继承关系的类的实现,来:

class FooChild(Foo):
    pass

等价于:

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

回顾一下咱们学到哪了: 在python中,类就是对象,而且你能够在运行的时候动态建立类.

那到底什么是metaclass(元类)

metaclass 就是建立类的那家伙。(事实上,type就是一个metaclass)

咱们知道,咱们定义了class就是为了可以建立object的,没错吧?

咱们也学习了,python中类也是对象。

那么,metaclass就是用来创造“类对象”的类.它是“类对象”的“类”。

能够这样子来理解:

图片

MyClass = MetaClass()
    MyObject = MyClass()

也能够用咱们上面学到的type来表示:

MyClass = type('MyClass', (), {})

说白了,函数type就是一个特殊的metaclass.
python在背后使用type创造了全部的类。type是全部类的metaclass.

咱们可使用__class__属性来验证这个说法.

在python中,一切皆为对象:整数、字符串、函数、类.全部这些对象,都是经过类来创造的.

>>> age = 35
    >>> age.__class__
    <type 'int'>

    >>> name = 'bob'
    >>> name.__class__
    <type 'str'>

    >>> def foo(): pass
    >>> foo.__class__
    <type 'function'>

    >>> class Bar(object): pass
    >>> b = Bar()
    >>> b.__class__
    <class '__main__.Bar'>

那么,__class____class__又是什么呢?

>>> age.__class__.__class__
    <type 'type'>
    >>> name.__class__.__class__
    <type 'type'>
    >>> foo.__class__.__class__
    <type 'type'>
    >>> b.__class__.__class__
    <type 'type'>

metaclass就是创造类对象的工具.若是你喜欢,你也能够称之为"类的工厂".

type是python內置的metaclass。不过,你也能够编写本身的metaclass.

__metaclass__ 属性

咱们能够在一个类中加入 __metaclass__ 属性.

class Foo(object):
        __metaclass__ = something...
        ......  # 省略

当你这么作了,python就会使用metaclass来创造类:Foo。

注意啦,这里有些技巧的。

当你写下class Foo(object)的时候,类对象Foo尚未在内存中生成。

python会在类定义中寻找__metaclass__ 。若是找到了,python就会使用这个__metaclass__ 来创造类对象: Foo。若是没找到,python就使用type来创造Foo。

请把下面的几段话重复几遍:

当你写以下代码的时候:

class Foo(Bar):
        pass

python作了如下事情:

Foo中有__metaclass__这个属性吗?
若是有,python会在内存中经过__metaclass__建立一个名字为Foo的类对象。
若是python没有在Foo中找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__,并尝试作和前面一样的操做。
若是python由下往上遍历父类也都没有找不到__metaclass__,它就会在模块(module)中去寻找__metaclass__,并尝试作一样的操做。
若是仍是没有找不到__metaclass__, python才会用内置的type(这也是一个metaclass)来建立这个类对象。

如今问题来了,咱们要怎么用代码来实现__metaclass__呢? 写一些能够用来产生类(class)的东西就行。

那什么能够产生类?无疑就是type,或者type的任何子类,或者任何使用到type的东西都行.

自定义metaclass

使用metaclass的主要目的,是为了可以在建立类的时候,自动地修改类。

一个很傻的需求,咱们决定要将该模块中的全部类的属性,改成大写。

有几种方法能够作到,这里使用__metaclass__来实现.

在模块的层次定义metaclass,模块中的全部类都会使用它来创造类。咱们只须要告诉metaclass,将全部的属性转化为大写。

# type也是一个类,咱们能够继承它.
    class UpperAttrMetaclass(type):
        # __new__ 是在__init__以前被调用的特殊方法
        # __new__是用来建立对象并返回这个对象
        # 而__init__只是将传入的参数初始化给对象
        # 实际中,你不多会用到__new__,除非你但愿可以控制对象的建立
        # 在这里,类是咱们要建立的对象,咱们但愿可以自定义它,因此咱们改写了__new__
        # 若是你但愿的话,你也能够在__init__中作些事情
        # 还有一些高级的用法会涉及到改写__call__,但这里咱们就先不这样.

        def __new__(upperattr_metaclass, future_class_name,
                    future_class_parents, future_class_attr):

            uppercase_attr = {}
            for name, val in future_class_attr.items():
                if not name.startswith('__'):
                    uppercase_attr[name.upper()] = val
                else:
                    uppercase_attr[name] = val
            return type(future_class_name, future_class_parents, uppercase_attr)

这里的方式其实不是OOP(面向对象编程).由于咱们直接调用了type,而不是改写父类的__type__方法.

因此咱们也能够这样子处理:

class UpperAttrMetaclass(type):

        def __new__(upperattr_metaclass, future_class_name,
                    future_class_parents, future_class_attr):

            uppercase_attr = {}
            for name, val in future_class_attr.items():
                if not name.startswith('__'):
                    uppercase_attr[name.upper()] = val
                else:
                    uppercase_attr[name] = val
            return type.__new__(upperattr_metaclass, future_class_name,
                                future_class_parents, uppercase_attr)

这样子看,咱们只是复用了 type.__new__方法,这就是咱们熟悉的基本的OOP编程,没什么魔法可言.

你可能注意到,__new__方法相比于

type(future_class_name, future_class_parents, future_class_attr)

多了一个参数: upperattr_metaclass, 请别在乎,这没什么特别的: __new__老是将"它要定义的类"做为第一个参数。

这就比如是 self 在类的通常方法(method)中同样,也是被做为第一个参数传入。

固然啦,这里的名字的确是我起的太长了。就像self同样,全部的参数都有它们传统的名称。
所以,在实际的代码中,一个metaclass应该是写成下面样子的:

(咱们同时使用常见的super来让代码更清晰)

class UpperAttrMetaclass(type):

        def __new__(cls, clsname, bases, attrs):
            uppercase_attr = {}
            for name, val in attrs.items():
                if not name.startswith('__'):
                    uppercase_attr[name.upper()] = val
                else:
                    uppercase_attr[name] = val
            return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, attrs)

使用了 metaclass 的代码是比较复杂,但咱们使用它的缘由并非为了复杂, 而是由于咱们一般会使用 metaclass 去作一些晦涩的事情,好比, 依赖于自省,控制继承等等。

确实,用 metaclass 来搞些“黑魔法”是特别有用的,于是会复杂化代码。

但就metaclass自己而言,它们实际上是很简单的:中断类的默认建立、修改类、最后返回修改后的类.

到底为何要使用metaclass

如今咱们面临一个问题: 为何要使用metaclass? 它容易出错且晦涩难懂.

好吧,通常来讲,咱们根本就用不上它, 99%的用户应该根本没必要为此操心。

实际用到metaclass的人,很清楚他们到底须要作什么,根本不用解释为何要用.

metaclass 的一个主要用途就是构建API。Django(一个python写的web框架)的ORM 就是一个例子。

用Django先定义了如下Model:

class Person(models.Model):
        name = models.CharField(max_length=30)
        age = models.IntegerField()

而后执行下面代码:

guy = Person.objects.get(name='bob')
    print guy.age  # result is 35

这里打印的输出并非IntegerField,而是一个intint是从数据库中获取的.

这是由于 models.Model 使用 __metaclass__来实现了复杂的数据库查询。但对于你看来,这就是简单的API而已,不用关心背后的复杂工做。

结语

复习一下,咱们知道了,类是可以创造对象实例的对象,同时也是metaclass的对象实例(由于metaclass创造了它们).

在python中,一切皆为对象。它们要么是类的实例,要么是metaclass的实例, 除了type。

type是它自身的metaclass。至因而怎么实现的,总之纯python语言是不可能实现的,这须要在实现层面上耍一些小手段才能作到的。

metaclass用起来比较复杂, 若是须要对很是简单的类进行修改, 你可能不会使用它。有如下两个技术能够供你选择:

相关文章
相关标签/搜索