Python中的元类

add by zhj: 这是大stackoverflow上一位小白提出的问题,好吧,我承认我也是小白,元类这块我也是好多次想搞明白,

但终究因为太难懂而败下阵来。看了这篇文章明白了许多,再加下啄木鸟社区的 Python 类型和对象  。卧槽,

这简直就是珠联璧合,日月神剑啊,尼玛。终于理解了元类。元类就是创建类对象的类,建议用__metaclass__用于指定元类,它的好

处也就是可以要创建类之前和之后修改类的属性,创建后修改属性也可以在__init__中做,不过完全可以用__new__代替,在元类的

__new__()方法中会直接或间接调用type(classname, parentclasses , attrs),我们可以控制这三个参数,type()就是实例化type类,

即在堆上创建一个类对象,任何类对象的创建都会调用这个接口,我们可以控制让__new__()返回什么样的类对象,比如我们可以在

__new__多次调用type(),让他生成多个类对象,然后将这些类按一定的策略组合起来返回。说白了,__metaclass__是用于控制类的

生成过程。我们大多数情况下无需指定__metaclass__,这种情况下,解释器会按一定的策略找到元类。元类的参数就是我们用class关键字

定义类时的类名,类的所有父类,类中定义的{属性名:属性对象}。

翻译时部分地方有修改。

 

原文:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python?answertab=votes#tab-top

1. 类对象

2. 通过type类创建类

3. 什么是元类

4. __metaclass__属性

5. __metaclass__为什么要用类而不是函数

6. 到底为什么要使用元类

7. 最后

 

 

类是对象

      在理解元类之前,你需要先掌握Python中的类。在Python中,对类的定义比较特殊,这点借鉴了Smalltalk语言。在大多

数语言中,类只是用于描述如何创建对象的代码片,在Python中,在一定程度上也是这样:

>>> class ObjectCreator(object):

...       pass

... 



>>> my_object = ObjectCreator()

>>> print(my_object)

<__main__.ObjectCreator object at 0x8974f2c>

不过,Python中的类不止如此。类也是对象,是的,我没说错,在《Python源码剖析》中提到除了Python的内置类型外

其它对象都是在堆上创建的,而内置类型应该是在内存的全局数据区。在Python中,一切都是对象,对象对用户来说只提供

了变量,变量就是那些标识符,变量以引用的方式操作对象,变量相当于对象暴露给用户的接口。Python中的引用跟C++

的引用差不多相同,不过也有差异。

      当你使用关键字class时,Python解释器执行时,会创建一个对象。如下,Python会在堆中创建一个对象,并在符号表

中增加一个标识符ObjectCreator,指向那个对象的地址,这就是Python中所说的引用,我们一般称ObjectCreator为变量。

>>> class ObjectCreator(object):

...       pass

... 

我们称这个对象为类对象,因为该对象可以实例化,创建实例对象,因此我们称它为类。

因为它是一个对象,所以:

  • 可以将它赋给一个变量
  • 可以copy它
  • 可以给它增加属性
  • 可以作为函数参数

比如

>>> print(ObjectCreator) # 可以print it

<class '__main__.ObjectCreator'>

>>> def echo(o):

...       print(o)

... 

>>> echo(ObjectCreator) # 类对象作为函数参数

<class '__main__.ObjectCreator'>

>>> print(hasattr(ObjectCreator, 'new_attribute'))

False

>>> ObjectCreator.new_attribute = 'foo' # 增加类的属性

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


通过type类创建类

      原文中说通过type类创建对象是动态,其实我们平时使用class关键字定义类也是动态的,当Python解释器遇到class关键字的,

它就会执行,并创建类对象。其实,类对象也是实例化的结果,即类对象是由另一个类实例化得到的,我们称创建类的类为元类,

反之也成立,如果类X实例化后得到是类对象,那X就是元类。在Python中,只有type类及其子类才可以当元类。多说

一句,这里会有鸡生蛋、蛋生鸡的问题。在Python中,一切都是对象,一切对象都是类实例化的结果。对象由类实例化得到,而类也是

对象,它也是由类(元类)实例化得到,继续向上,元类也是对象,也要由另一个元类实例化得到,这样下去就没有尽头了。在Python中,

这个追溯终止在type类。元类是type类或其子类,而type类的元类就是它自己,哈哈,type类自己创建自己,牛逼吧,当然type类的

这个特性是Python设计者设计并实现的。至于Python解释器在定义类时是怎么找到该类的元类的,后面我们会讲。

      还记得type()方法吗?我们常用它看一个对象X所属的类,即创建该对象X的类,当对象X是类对象时,看到的就是元类。如下,

>>> print(type(1))

<type 'int'>

>>> print(type("1"))

<type 'str'>

>>> print(type(ObjectCreator))     #查看创建ObjectCreator类的类

<type 'type'>

>>> print(type(ObjectCreator()))

<class '__main__.ObjectCreator'>

      type类还有另外一个功能,它可以创建类,它以类的一些信息做为type()的参数,并返回一个类。我知道,type根据输入参数的不同而有不同的功能,

这种做法是愚蠢的。当type()只有一个参数时,它的功能就是返回创建该参数的类;当多于一个参数时,type()才是type类的实例化,实例化得到

一个类并返回该类。type创建类时,参数格式如下,classname是类名,字符串类型,parentclasses是类所有父类,元组类型,attrs是类的所有{属性:值},

字典类型。如果用户是用class关键字定义的类,那解释器会自动转为下面的格式执行。

type(classname, parentclasses , attrs)

比如,

>>> class MyShinyClass(object):

...       pass

当解释器执行时,会转为下面的语句,当然,你也可以直接这么写。

MyShinyClass = type('MyShinyClass', (object,), {})

下面我们来定义一个类,并在类中定义属性,如

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

我们可以用另一个类继承它,so:

>>>   class FooChild(Foo):

...         pass

would be:

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

>>> print(FooChild)

<class '__main__.FooChild'>

>>> print(FooChild.bar) # bar is inherited from Foo

True

OK,你会想给你的类增加方法。定义一个函数,并将它分配给类的属性

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

说到这里,你应该已经明白了类的创建过程。那Python创建类都是这么简单吗?都是直接用type元类实例化得到?

不是的,你可以指定元类,我们会在“__metaclass__属性”一节会讲。

 

什么是元类

元类就是创建类的类,当通过type.__new__(cls, classname, bases, attrs)创建类时,cls就是该类的元类,

它是type类或其子类。在上面,我们提到可以使用type()查看类的元类,你也可以使用__class__,他们是完全等价的。

一切类的创建最终都会调用type.__new__(cls, classname, bases, attrs),它会在堆中创建一个类对象,并返回

>>> 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__是什么呢?__class__返回的是一个类对象,再一次__class__返回创建类对象的类,

即类的元类,如下,这个例子中类的元类都是type类,我们平时用的绝大部分类的元类都是type,不过也有例外,

有些类的元类是type的子类。当然,如果你对某个类一直调用__class__,那在有限次之后,它返回的就是type类了。

>>> age.__class__.__class__

<type 'type'>

>>> name.__class__.__class__

<type 'type'>

>>> foo.__class__.__class__

<type 'type'>

>>> b.__class__.__class__

<type 'type'>

 

__metaclass__属性

     你可以在类中添加__metaclass__属性,用它指定创建该类的元类,前面我们说过,元类必须是type类或type子类。

注:其实__metaclass__只要是一个可调用对象就行,该可调用对象的参数格式为callable(classname, parentclasses , attrs),

可以看到,这跟上一节用type类创建类对象时,参数格式完全相同。Python要求该对象执行时必须要调用执行type()或type子类(),

当Python解释器执行时,会调用这个可调用对象,参数也是Python解释器添加上去的。一般的,__metaclass__用元类,在本

文中,作者分别用了函数和元类。这里还有一点,子类如果指定了__metaclass__,该__metaclass__必须与父类的元类相同或

是父类元类的子类,不然,呵呵,Python解释器会选其中一个做元类,但选哪个,貌似没有什么规律。

      如果我们创建类时,要指定__metaclass__属性,那最好创建的这个类的父类的元类是type类(有点绕),这样就不容易出错,

不然的话,请慎重使用元类。

class Foo(father_class):

  __metaclass__ = something...

  [...]

我们假定在继承关系上,子类的元类是父类的元类或父类的元类的子类。在这个正确的前提下,我们说一下Python解释器是怎么确定

一个类的元类的(只讨论新式类的创建,旧式类不讨论,对于新式类,Python解释器的第三步是直接使用type类,作者讨论了定义类时,

没有父类的情况,这种类我们不会用到,不讨论)。add by zhj:但后面我发现,下面这个规律并不成立,在"到底为什么要使用元类"

一节我猜想了更可能的规律,并初步验证过了。

1、在类中查找__metaclass__属性,如果找到就用,如果没有,进入2

2、在继承树中查找__metaclass__属性,如果还是没有,进入3

3、使用type类

举个例子吧,比如你想将一个类的属性全部转为大写(以__开头的属性除外),其实一种办法就是使用__metaclass__属性。

前面我们提到过,__metaclass__可以是任意可调用对象,只要调用super()或super子类()就可以。

OK,我们先使用一个函数做__metaclass__,在实际上我们一般不会用函数,而是用类。

# the metaclass will automatically get passed the same argument

# that you usually pass to `type`

def upper_attr(future_class_name, future_class_parents, future_class_attr):

  """

    Return a class object, with the list of its attribute turned 

    into uppercase.

  """

  print "call upper_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 # let `type` do the class creation return type(future_class_name, future_class_parents, uppercase_attr) class Foo(object): __metaclass__ = upper_attr bar = 'bip' print(hasattr(Foo, 'bar')) # Out: False print(hasattr(Foo, 'BAR')) # Out: True f = Foo() print(f.BAR) # Out: 'bip'

 

 执行输出如下:

>>>
call upper_attr
False
True
bip
>>>

然后我们将函数换成类,__new__方法是静态方法,当创建类时,Python解释器会实例化元类

UpperAttrMetaclass(classname, parentclasses , attrs),进一步它会执行

UpperAttrMetaclass.__new__(UpperAttrMetaclass, classname, parentclasses , attrs)方法。我们在下面看到,__new__

会调用type类,其实指定__metaclass__的好处也就是可以要创建类之前修改类的属性,即在调用type(classname, parentclasses , attrs)

之前修改这三个参数,前两个参数貌似没啥好修改的,主要是修改第三个参数,第三个参数是一个字典,我们可以修改键,

即修改属性名称,也可以修改键值,即属性对象,属性对象有value/property/method。

      Django的ORM就是这样干的,我们在Model中定义的字段类型是各种field类实例,但当创建该model类时,会将这些field类实例转为Python

内置的类型,如CharField()实例转为unicode或str类型,IntegerField()实例转为int型。

# remember that `type` is actually a class like `str` and `int`

# so you can inherit from it

class UpperAttrMetaclass(type): 

    # __new__ is the method called before __init__

    # it's the method that creates the object and returns it

    # while __init__ just initializes the object passed as parameter

    # you rarely use __new__, except when you want to control how the object

    # is created.

    # here the created object is the class, and we want to customize it

    # so we override __new__

    # you can do some stuff in __init__ too if you wish

    # some advanced use involves overriding __call__ as well, but we won't

    # see this

    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(),我们没有使用super().__new__,下面我们修改一下

class UpperAttrMetaclass(type): 



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



        uppercase_attrs = {}

        for name, val in attrs.items():

            if not name.startswith('__'):

                uppercase_attrs[name.upper()] = val

            else:

                uppercase_attrs[name] = val



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

元类可以做到:

  • 拦截类的生成
  • 修改类
  • 返回修改后的类

 

__metaclass__为什么要用类而不是函数

__metaclass__可以接受任意可调用对象,为什么要使用类而不是函数呢?有下面几个原因

 

  • 意图清晰。当你读到UpperAttrMetaclass(type), 你知道接下来要做什么
  • 你可以使用OOP
  • 你可以重定义 __new__, __init____call__,不过其实你完成都在 __new__方法中完成。有些人更喜欢用 __init__

 

到底为什么要使用元类

那么问题来了,用元类谁最强?哈哈,开个玩笑。为什么你要使用容易出错的元类呢?

well,通常情况下你不需要使用

元类是一个高级特性,99%的用户不需要关心。如果你还在想是否需要它,那你其实不需要(真正需要使用元类的人是非常确定自己的确需要的).

Python大师Tim Peters说过:元类主要的使用场景是创建API,一个典型的例子是Django ORM。

在Django ORM中,可以如下定义model

class Person(models.Model):

  name = models.CharField(max_length=30)

  age = models.IntegerField()

但是当你下面这样做时

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

print(guy.age)

它并不会返回一个IntegerField实例,而是返回一个int实例。之所以会这样,是因为models.Model 定义了元类

(不过它并没有直接使用__metaclass__属性,而是使用的另一种方法定义的元类),并且它使用一些魔法将你

用几条简单语句定义的Person类转为数据库的字段。Django通过暴露给用户简单的API,并通过使用元类,使复杂

的事情对用户来说变得简单。我看了一下源码,如下。

class Model(six.with_metaclass(ModelBase)):

    _deferred = False



    def __init__(self, *args, **kwargs):

        signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)



        # Set up the storage for instance state

        self._state = ModelState()

        ……

        ……
def with_metaclass(meta, *bases):

    """Create a base class with a metaclass."""

    return meta("NewBase", bases, {})

 

class ModelBase(type): """ Metaclass for all models. """ def __new__(cls, name, bases, attrs): super_new = super(ModelBase, cls).__new__ …… ……

 

six.with_metaclass(ModelBase)等价于ModelBase("NewBase", (), {}),第二个参数为空时,Python解释器会认为该类继承于object类,

所以这句话等价于ModelBase("NewBase", (object,), {}),我在测试中发现创建的NewBase类的元类是ModelBase类,但Python

并没有给NewBase创建__metaclass__字段,这让人疑惑,看来上面说的一个类创建时查找元类的规律并不成立。那Python解释器到底是怎

么确定NewBase的元类就是ModelBase呢?经我初步验证,应该是靠调用type.__new__(cls, classname, bases, attrs)方法时,第一个参数,

因为一切类对象最终都是通过这个方法创建的,在创建时,它的第一个参数就是被创建的类的元类,第一个参数一般是type类或其子类,类创建时会记

录下它的元类。你可能会问,如果一个类是由多个类对象组合而成的呢?没问题,如果最终的类的id与其中一个类相同,那说明并没有创建新的类,

类的id不变,类的元类就不变,如果它的id与每个类都不同,那就是创建了一个新的类,创建时还是会调用type.__new__方法,第一个参数就是

它的元类。

      我非常不喜欢ModelBase("NewBase", (object,), {})这种方式来定义类,因为这样定义你很难知道NewBase的元类是谁,如果ModelBase

定义了__new__方法,且在调用type.__new__()时第一个参数是ModelBase,那NewBase的元类就是ModelBase,如果ModelBase没有重定

义__new__,那对象创建时调用的type.__new__方法,第一个参数是type,即NewBase的元类就是type类。我操,好麻烦啊,我还是推荐传统

的定义类的方式,如下,这样才能一目了然的看到NewBase的元类是ModelBase,不过有一个要求,该NewBase的__metaclass__必须是NewBase

父类的元类的子类,或者相同,这样才能确保NewBase的元类是__metaclass__指定的类。

 

class NewBase(object):

    __metaclass__ = ModelBase

 

 

 

最后

看了这么多,是不是晕了,反正我有点晕,再次提醒大家,最好不要指定元类,也不要用调用type()或其子类的方式来定义类,

因为这块很容易出错。推荐大家还是有class关键字去定义类。

首先,你要知道类是能产生实例的对象。

实际上,类是元类的实例。

>>> class Foo(object): pass

>>> id(Foo)

142630324

Python中的一切皆对象,这些对象分为两种:类对象和实例对象。type类的元类就是它自己。

其次,元类是复杂的,对于类的简单的改动是不需要使用元类的。你可以通过下面两种技术来改变类:

 

这里再说两句吧,在gevent库中就使用了monkey patching,它可以将Python标准库中的IO接口

替换为自己的接口,感觉好牛逼的样子,有时间看看它是怎么实现的。

 

你可能感兴趣的:(python)