zoukankan      html  css  js  c++  java
  • 元类

    前言

    本篇博客学习 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
    感谢

  • 相关阅读:
    洛谷P4547 [THUWC2017]随机二分图
    洛谷P4590 [TJOI2018]游园会
    洛谷P4099 [HEOI2013]SAO
    #4719. 内凸包
    #1612. 天平(scales)
    #3164. 「CEOI2019」立方填词
    #4728. 问题求解
    #2754. Count(count)
    sa模板
    bzoj 2553: [BeiJing2011]禁忌
  • 原文地址:https://www.cnblogs.com/zuanzuan/p/10037556.html
Copyright © 2011-2022 走看看