如何理解Python中的元类metaclass

以下理解整理翻译自Stackoverflow-What are metaclasses in Python?

关于这个已经有博主翻译转载过了,深刻理解Python中的元类(metaclass)

 

1、Python中的类

在理解元类之前,你需要掌握Python中的类。Python中借用了SmallTalk语言特性对类进行实现。

在大部分的语言中,类一般是一段生成对象的代码,这在Python中也一样。

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

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

在Python中,类也是一种对象。当执行如下代码时,Python会申请一块内存保存名为ObjectCreator的对象。

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

生成的对象(类)拥有创造对象(实例)的能力,因此称为类。

作为对象,因此它能够像对象一样操作:

  • 可以给变量赋值
  • 可以复制
  • 可以增加属性
  • 可以作为函数参数传递
>>> 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>

2.动态生成类

类可以像对象一样动态生成。

首先使用class关键字对类进行定义

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

以上方式并不是那么动态,因为你需要自己定义整个类。而类本身作为一个对象是可以通过其他来生成的,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还有另一种完全不同的能力--创建类。输入类的描述参数来生成对应的类(为什么type会根据输入参数的不同而表现出两种完全不同的功能,主要是需要满足Python向后兼容)

以下为type使用方式,需要类的名称,可能继承的父类元祖,包含属性键值对的字典三个输入参数

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)
>>> class MyShinyClass(object):
...       pass
%可以换成另一种方式:
>>> 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>

在手动创建类时,使用了MyShinyClass作为类的名字,同时也用同样的名字对该类进行引用。这两个名字可以不一样,如a=type('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 is inherited from 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

这样你就可以为创建的类动态的添加你需要的属性。

3、那么什么是元类呢?

元类(Metaclass)是创建类的元素。之前已经了解了可以通过类来创建实例对象,那么元类可以用来创建类对象,是类的类。

MyClass = MetaClass()
my_object = MyClass()

结合上文通过type来创建MyClass,我们可以了解type实际上就是元类,Python中使用type作为所有类的元类。可以通过__class__属性来查看类别。在Python中一切都是对象,不管是整数、字符串、函数还是类。但追根溯源,type是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'>

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

type是內建元类(built-in),但是可以创建自己的元类。

__metaclass__属性

在Python2中,你可以在创建类时给类添加一个__metaclass__属性,Python会使用这个元类属性来创建类对象Foo

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

当执行class Foo(object)时,Python首先在__metaclass__中寻找类的定义,如果有,就用来创建类对象Foo,如果没有找到,Python会调用type方法创建类对象Foo。

当执行以下代码时,Python的编译过程如下:

class Foo(Bar):
        pass

判断Foo中是否有__metaclass__属性?如果有,分配内存并使用__metaclass__属性创建以Foo为名的类对象;如果没有找到,Python会在当前模块里寻找该属性;如果还没有找到,那就会用父类Bar的元类(可能是默认的type)来创建类对象。这里需要注意,Foo从Bar处继承,但不会继承Bar的__metaclass__属性。如果Bar的使用type()来作为__metaclass__机制创建子类的话,那么子类不会继承这种创建机制。

而在Python3.x中,元类语法与2.x中不同。

class Foo(object, metaclass=something):
    ...

__metaclass__属性换成了关键字参数,传递给类对象,但元类的其他行为大体是相同的。3.x中新增的是可以按照关键字参数将属性传递给元类,如下:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

 

4、自定义元类

元类的主要作用是,在创建类时自动改变类的一些属性。当进行类似于调用APIs操作时需要创建对应匹配的类。

举一个例子,如果你想把模块中的所有类的属性名都使用大写,那其中一个可以实现的方式就是使用元类定义,设置__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.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    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)

__metaclass__ = upper_attr # this will affect all classes in the module注意这一行代码,会影响当前模块中的所有类

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'

现在我们换成另一个方式,定义一个元类:

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

这个方式不太符合面向对象编程的准则,这里我们直接调用了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

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

这里你会发现多出一个upperattr_metaclass参数,这个是由于__new__方法总是把它定义的类作为第一个参数接受,类似于普通类定义中的self,为简洁可用cls来代替。通常可以使用较短的参数名,如下:

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来使得类的继承更加简洁。

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)

回到Python3.x中对元类的使用:

class Foo(object, metaclass=Thing, kwarg1=value1):
    ...
%可以转换成如下:
class Thing(type):
    def __new__(class, clsname, bases, dct, kwargs1=default):
        ...   

元类的介绍差不多就这些。

元类在实际中最主要的应用是生成API,典型应用是Django的ORM

翻译水平有限,如有错误,烦请指出。

 

补充:

浅谈 Python 中的 __init__ 和 __new__ 

[Python] Python 之 __new__() 方法与实例化

python3中类的重点与难点:__new__方法与__init__方法

python 中 super函数的使用

Python: super 没那么简单

 

 

 
posted @ 2019-02-13 11:55  adminyzz  阅读(192)  评论(0编辑  收藏  举报