• 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接口

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

  • 相关阅读:
    【CH 5501】环路运输【DP】【单调队列】
    【CH 5501】环路运输【DP】【单调队列】
    【POJ 1456】Supermarket【并查集】
    【POJ 1456】Supermarket【并查集】
    【POJ 1456】Supermarket【并查集】
    【POJ 2411】Mondriaan's Dream【DP】
    数据结构实验之二叉树二:遍历二叉树
    数据结构实验之二叉树二:遍历二叉树
    36 静态数据成员与静态成员函数
    36 静态数据成员与静态成员函数
  • 原文地址:https://www.cnblogs.com/ajianbeyourself/p/4052084.html
Copyright © 2020-2023  润新知