Python -- 元类metaclass详解

学习契机

项目中使用Elasticsearch(ES)存储海量业务数据,基于ES向外提供的API进一层封装,按需处理原始数据提供更精确、更多样化的结果。在研究这一层的代码时接触到@six.add_metaclass(abc.ABCMeta),故而学习一下Python的元类。不过,虽然@six.add_metaclass(abc.ABCMeta)实现上与元类有关,但实际应用只须要调用其接口,并不须要接触后幕后的元类操做。
翻译这篇答案是为了方便本身记忆理解,其实原文中一些地方我本身不是很明白,因此这个翻译会根据本身理解的程度持续更新。python

原连接

stackoverflow-What are metaclasses in Python?数据库

Python中的元类是什么

类也是对象

在理解元类以前,须要掌握Python中类概念。Python的类概念稍有奇特,其借鉴于Smalltalk。
在大部分语言中,类用于描述如何生成一个对象,在Python中也是如此:函数

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

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

可是,在Python中类的意义更多,类同时也是对象。当你使用关键字class时,Python解释器执行代码时会生成一个对象。如下代码会在内存中建立一个名为“ObjectCreator”的对象:学习

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

这个对象(类)自身能够建立对象(实例),这是为何它是类的缘由。
不过它仍然是一个对象,你能够:this

  • 能够将它赋值给变量
  • 能够复制
  • 能够添加属性 TODO 添加属性只是对象的特性?
  • 能够将其看成函数参数传递

举例:翻译

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<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 # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态建立类

既然类也是对象,那就可像其余对象那样,动态建立类。
首先,可使用关键字class,在函数中建立类:code

>>> 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>

可是这还不够动态,由于仍是须要本身编写整个类的代码。因此,想想,这些类既然是对象,就必然是某种东西生成的。当使用class关键字时,Python自动建立了类这个对象,可是像Python中大部分事情同样,Python中也能够手动建立类。
还记type方法吗?一个可让你知道一个对象是什么类型的方法:对象

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

除此以外,type还有一个彻底不一样的功能:动态建立类。将类的描述做为参数传递给type,会返回一个类。(我知道,同一个函数根据传参的不一样而展现出两种彻底不一样的功能很不合理,不过这是为了Python的向后兼容。)
type如何建立类:继承

type(类名,
     父类元祖 (可为空),
     包含键值对属性的字典)

举例:接口

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

上面这个MyShinyClass类能够用如下方法手动建立:

>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一个类
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 建立一个类对象
<__main__.MyShinyClass object at 0x8997cec>

应该注意到了,使用"MyShinyClass"做为类名,也将其做为一个变量名,赋值为类的引用。类名和变量名是能够不一样的,可是不必把事情搞复杂。
type能够接受一个定义类属性的字典做为参数:

>>> class Foo(object):
...       bar = True

以上定义等同于:

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

使用起来跟一个普通类同样:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)  # 利用实例打印类属性
True

固然,也能够做为基类,给其余类继承:

>>>   class FooChild(Foo):
...         pass

以上代码等同于:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar属性继承自类Foo
True

你确定还想为类添加方法。只须要定义一个名称合理的函数,并将这个函数名做为属性传递给type就行:

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

在动态建立一个类以后,为这个类添加更多的方法,就像为一个正常建立的类添加方法同样:

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

如今已经看到:在Python中,类也是对象,能够动态地建立类。当使用关键字class时,Python使用元类像这样建立类的。

什么是元类(终于讲到了)

元类就是建立类的“东西”。咱们定义类是为了建立对象,是吧?可是咱们认识到在Python中类也是对象,而元类就是建立类这种对象(类)的,它们是类的类,你能够这样理解:

MyClass = MetaClass()
MyObject = MyClass()

你已经看到type可让你作以下操做:

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

这是由于type方法其实是一个元类。事实上,type是Python中用于建立全部类的元类。不过如今你必定很奇怪为何这个类名首字母是小写,而不是Type?我猜这是同str保持一致,str是用来建立string对象的类,int是用来建立integer对象的类,type则是建立类对象的类。
在Python中,一切,对就是一切,都是对象。包括整数、字符串、函数和类。全部东西都是对象,它们都是建立自某个类。
经过__class__属性能够验证这一点:

>>> 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'>

因而可知:元类确实就是建立类对象的东西。若是你以为合适你就能够称之为“类工厂”。type是Python使用的内置元类,固然,你也能够本身建立元类。

__metaclass__属性

编写一个类时添加上__metaclass__属性:

class Foo(object):
    __metaclass__ = something...
    [...]

若是你像上面这样作,Python就会使用元类建立一个Foo类。
要小心了,这里有些小圈套。你先写下了“class Foo(object)”,但此时内存中尚未建立Foo类对象。Python会在类的声明中寻找属性__metaclass_,若是找到了就会使用其建立Foo类;若是没有,会使用type建立这个类。下面这段文字要多读几遍。
当你编写如下代码时:

class Foo(Bar):
    pass

Python作了这些事情:

在类Foo中有定义__metaclass__属性吗?
若是有,则继续;
若是没有,Python会在模块层寻找__metaclass__属性(这只针对没有继承任何其余类的状况);
若是模块层也没有,则会在Bar(第一个父类)中寻找(这就有多是内置的type)。
这样找到__metaclass__后,使用它在内存中建立名称为Foo的类对象(这边跟上,一个类对象)

须要注意的是,__metaclass__属性不会被继承,可是父类的元类(Bar.__class__)能够被继承:若是Bar的__metaclass__属性定义了使用type()(不是type.__new())建立Bar类,其子类不会继承这个行为。(Be careful here that the metaclass attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a metaclass attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior.)TODO 这边不太理解
如今有个新问题,你能够赋什么值给__metaclass__?
答案是:能够建立一个类的东西。
那什么能够建立类?type、子类化type或者使用type的东西。

自定义元类

元类的主要目的,是在建立类的时候动态地改变类。一般你会想建立符合当前上下文的类供API使用。举一个简单的例子,当你但愿一个模块中全部的类属性都是小写时,有几种方法能够实现,其中有一种方法就是在模块层设置__metaclass__。使用这种方法,这个模块中全部的类都将使用此元类建立,咱们只须要使元类将全部类属性置为小写。
幸运的是,__metaclass__能够被任意调用,不必定非要是一个正式的类(我知道,名称包含“class”不必定非要是一个类,搞清楚了...这颇有用)。
如今先用一个函数举一个简单的例子:

# 元类会自动获取一般传给`type`的参数
def upper_attr(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

    # 利用'type'建立类
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr  # 这会影响此模块中全部的类

class Foo():  # global __metaclass__ won't work with "object" though  == 没看懂
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

如今,完成一样的功能,可是为元类定义一个真实的类:

# 记住`type`其实是一个像`str`和`int`的类,能够用于被继承
class UpperAttrMetaclass(type):
    # __new__放在__init__以前调用,此方法建立对象并反回
    # 而__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,没有重写或者调用父类的__new__。如今像这样处理:

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

        # 复用type.__new__方法
        # 这是基本的OOP,没什么深奥的
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

你大概发现了传给type的额外的参数upperattr_metaclass。这没什么奇怪的:__new__的第一个参数老是其定义的类。就像类方法中第一个参数老是self。固然,为了清晰期间,这里我起的名字比较长,可是像self这样的参数一般有一个传统的名字。因此真正的产品代码中,元类是像这样的:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

使用super能够更清晰,which will ease inheritance (because yes, you can have metaclasses, inheriting from metaclasses, inheriting from type)TODO:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

以上,关于元类也没有更多了。使用元类的代码比较复杂的缘由不在于元类,而在于你一般会依靠自省、操纵继承、__dict__变量等,使用元类作一些晦涩的事情。(it's because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.)TODO
元类来用于黑魔法时的确特别有用,由于也会将事情搞得很复杂。但就其自己而言,是简单的:

  • 拦截一个类的建立
  • 修改类
  • 返回修改的类

为何会用元类代替函数?

既然__metaclass__能够被任意调用,为何要使用明显更复杂的类呢?有这样一些理由:

  • 意图明显。当你看到UpperAttrMetaclass(type),你知道接下来会发生什么。
  • 可使用OOP。元类能够继承自元类,重写父类的方法,元类甚至可使用元类。
  • 若是为一个类指定的元类是类而不是方法,这个类的子类将是元类的一个实例。Children of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.TODO
  • 你能够将代码组织得更好。使用元类时确定不会仅想像上面举的例子那样简单,一般是用于比较复杂的场景。将多个方法组织在一个类中有益于使代码更容易阅读。
  • 你可使用__new__,__init__ 和 __call__,这些方法能够处理不用的事情。即便不少时候你能够在__new__中完成全部工做,固然,一些人会更习惯用__init__。
  • 这些东西叫 “metaclass”,当心了!这必定很难搞!

为何使用元类

好了,如今的问题是:为何要是用这样晦涩且容易出错的特性?其实,一般你不会用:

元类是99%的用户根本没必要操心的深度魔法。若是你在考虑是否须要使用,那就不要用(真正须要的用户很清楚他们的需求,根本不须要解释为何要使用元类)
Python领袖 Tim Peters

使用元类的一个主要场景是建立API。Django ORM是一个典型的例子,它容许你像下面这样定义:

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

若是你这样作:

guy = Person(name='bob', age='35')
print(guy.age)

它不会返回一个IntegerField的对象,而是返回一个整数,甚至能够从数据库中直接获取数据。TODO
这是由于,在models.Model中定义了__metaclass__,使用了一些魔法将你定义的简单的Person类转换为一个复杂的数据库挂钩。(turn the Person you just defined with simple statements into a complex hook to a database field.)TODO

Django使用元类对外提供简单的API,简化了一些复杂的东西,API中重建的代码会去完成幕后真正的工做。

结语

首先,类是能够建立实例的对象。类自己是元类的对象:

>>> class Foo(object): pass
>>> id(Foo)
142630324

在Python中,除了type,一切皆对象,一切都是类或者元类的对象。事实上type是本身的元类,这在纯Python中这是没法实现的,这里在实现层上作了一些手段。
其次,元类是复杂的。你可能不但愿对很是简单的类使用元类,那么还有其余两种手段用来改变类:

  • monkey patching
  • 类装饰器

若是你须要改变类,99%的状况下使用这两种方法。但其实98%的状况你根本不须要改变类。

相关文章
相关标签/搜索