元类

前言

本篇博客学习 python 中一种高级概念,元类。在《说文》中这样描述:元,始也(ps:这也太简短了)。三个字,最后一个字还是语气词。。很形象的说明了元的意思,最开始的意思,在道教中元的意思是:开始的,最初。第一。为首的。主要,根本,整体的意思。那么很显然,元类是最开始的类的意思。我们都知道在 python 中一切皆为对象,而对象都继承与 object 类,那么元类与 object 的关系是什么呢?

可以看出元类也是继承自 object 类的。

那么元类和类的关系是什么呢?

简单来讲就是:类生成对象,元类生成自己本身同时实例化其他所有的类。

元类

类作为对象

之前已经说过 python 中一切皆对象,那么类同样也是对象,而且类是元类的对象。当使用关键词class定义一个类时,在代码执行阶段就会创建一个 空的object,并使用元类的__init__方法来出初始化一个类。这个对象(类)本身可以创建对象,因为它是一个类。

# 在内存中创建一个 Foo 对象
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>

动态创建类

由于类也是对象,因此可以像任何对象一样动态创建它们。 在python中,type 除了可以查看对象的类型,还有一个很强大的功能,type 可以动态的创建类。type 可以将类的描述作为参数并返回一个类。

使用 class关键词时,python 会自动的创建此对象,就像实例化一个对象时,调用的是类中的__init__方法,创建对象时同样的调用了元类中的__init__方法:

该函数有四个参数,第一个参数为 cls表示这是一个由类调用的初始化函数,python 在检测语法的时候发现 class则会把 class 后面的类名当做参数传给 what,bases 表示继承的父类是谁,是个元祖类型(因为会有多继承),dict 是个字典类型,是类的名称空间,可以通过__dict__查看,使用 type来创建一个类:

what = 'Music'
bases = (object,)
dict = {'music_name': '南山忆'}

这和我们使用 class关键词定义一个类没有什么不同:

之前在学习类的时候,我们知道创建一个对象是调用了类中__init__方法,同理在创建类的时候是调用了元类中也就是 type 中的__init__方法,所以我们可以通过改写 type 中的元类来自定义创建类,比如加些判断或者其他的属性:

可惜想象是美好的,这招根本行不通,那么有没有别的办法呢?当然有啦,哈哈哈哈哈

在学习类的三大特性的时候,从父类继承的属性可以改写(多态的思想),那么改写不了元类是因为元类很特殊,那我继承自元类的类肯定可以改写吧。这个到后面再讲,突然发现什么是元类还没讲清楚。。。

什么是元类

我把元类称之为 类的类。元类是创建类的‘’东西‘’,我们定义类来创建对象,类也是对象,所以定义了一个元类用来创建对象。type是 Python 用来创建所有类的元类(不包括 object 类)。其实这和用 str创建字符串对象,int创建整数对象一致的。type只是创建类对象的类。

一切,all thing都是 Python 中的一个对象。这包括整数、字符串、函数和类。所有这些都是对象,所有这些都是从一个类创建的。因此,元类是创建类对象的东西。

__metaclass__属性

除了使用 type 动态创建类以外,要控制类的创建行为,可以使用 metaclass,这也是自定义元类的方法。

metaclass 的意思就是元类:当我们定义了类以后,就可以根据这个类创建出实例,所以先定义类,然后创建实例。但是如果想创建出类呢?那就必须根据 metaclass 创建出类,所以:先定义元类(不自定义时,默认用 type),然后创建类。(大部分情况使用不到 metaclass,除非想自定义元类)。

默认情况下,类是使用 type 构造的。类主体在一个新的名称空间中执行,类名在本地绑定到类型的结果(名称,基,名称空间)。

可以通过类定义行中传递元类关键字参数来定制类的创建过程,或者从包含此类参数的现有类继承。

实例化对象的完整过程

class Foo(Bar):
    pass

当解释器执行这行代码时,执行以下操作:

Foo 有__metaclass__属性吗?如果有的话,python 会通过 __metaclass__在内存中创建一个名称为 Foo 的类对象。如果 Python 没有找到__metaclass__,它会继续在 Bar 中寻找__metaclass__属性,并尝试做和前面同样的操作。如果 Python 在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。如果还找不到__metaclass__,python 就会用内置的 type 来创建这个类对象。

那么在自定义类是,可以在__metaclass__中放置什么代码呢?

可以放用来创建一个类的东西,type 或者 type的子类都可以放。

以上面的代码为例,我们实例化一个对象obj=Foo(),会先执行 Foo 类中的__new__方法,没有则使用父类的__new__方法,创建一个空对象并返回,然后执行__init__方法(自己有就用自己的,没有就用父类的,这里分两种情况,如果是创建一个类的对象,那就是使用父类的,因为自己没有;如果是创建类,自己有就是用自己的,否则就是用父类的),为创建的对象进行初始化。

obj()会执行 Foo 类的__call__方法,没有则用父类的。现在已经知道。类同样也是对象,是元类的对象,即实例化一个对象(类)时,调用其父类(元类)的__call__方法。

元类处理过程:定义一个类时,使用声明或者默认的元类对该类进行创建,对元类求 type 运算,得到父元类(该类声明元类的父元类),调用父元类的__call__方法,在父元类的__call__方法中,调用该类声明的元类的__new__来创建一个空对象(该方法需要返回一个类对象实例),然后再调用该元类的__init__方法初始化该类对象,最终返回一个类。

  1. 对象时类创建,创建对象时类的__init__方法自动执行,对象()则会执行类的__call__方法;
  2. 类是由 type 创建的,class 定义的时候 type 的__init__方法自动执行,类()则会执行type 的__call__方法(类的__new__,类的__init__方法)

元类的__new__方法和__init__影响的是创建类的对象行为(不是创建类),父元类的__call__控制对子元类的__new__,__init__的调用,就是说控制类对象的创建和初始化。父元类的__new__和__init__由更上层的元类控制,一般来说,原始 type 是最初的父元类,其__new__和__init__是最具有普遍意义的,即应该是分配内存、初始化相关信息等。元类的__call__方法影响的是创建类的实例对象的行为,所以这时候自定义__call__就可以控制创建类对象的实例对象的行为了。比如单例模式的创建。

__new__和__init__影响的是创建对象的行为,当这些函数在元类中时,影响创建的是类;同理,当这两个函数在普通类中时,影响的是创建普通的对象实例行为。

__call__影响()调用行为,__call__是在创建类的时候调用,即:定义类时就是创建类,此时会调用元类的__call__,如果元类有继承,子元类定义时执行的是父元类的__call__。如果是普通类实例化,调用的是普通类的__call__。(昨晚上卡在这里了,其实实例化一个普通的对象时,都是调用其父类的__call__方法,除了元类,普通类中不会有__call__方法。)

自定义元类

元类的主要目的是在创建类时自动更改类,比如想要将创建的所有类都变成首字母大写的:

class MymetaClass(type):
    def __call__(cls, *args, **kwargs):

        if type(args[0]) != str:
            raise TypeError('参数必须为字符串类型')
        obj = object.__new__(cls)
        obj.__init__(*args, **kwargs)
        return obj


class Foo(metaclass=MymetaClass):
    def __init__(self, name):
        self.name = name


res = Foo(123)

这就是自定义元类的好处,可以在__call__来对传入的参数进行一些判断来做一些自定义操作。

通过函数

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

使用元类的__new__方法

因为 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)

使用 super 的__new__方法

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__等等来做扭曲的东西。实际上,元类特别适用于制作黑魔法,因此也很复杂。但他们本身很简单:

  • 拦截类的创建
  • 自定义类
  • 返回修改后的类

为什么要使用元类而不是函数?

既然__metaclass__可以接收任何调用,那么为什么要使用一个类,因为类显然比函数要复杂。

有几个原因:

  • 目的很明确,你知道使用元类会发生什么;
  • 可以使用 OOP。metaclass 可以从元类继承,覆盖父方法,元类甚至可以使用元类;
  • 如果你指定了元类,但没有使用元类函数,则类的子类将其元类的实例;
  • 可以更好地构建代码;
  • 可以和__new__,__init__,__call__搭配使用,来使得创建一个类或者一个类实例变得更有创造性。

本文参考与一下两篇文章

https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python
https://www.cnblogs.com/huchong/p/8260151.html
感谢

posted @ 2018-11-29 12:25  rsuxwvilc  阅读(550)  评论(0编辑  收藏  举报