Python3元类

介绍

元类是类的类对象,换言之类是元类的实例,Python中默认的元类为type,可以通过自定义元类的方式实现对类创建的控制。

类对象的创建顺序

class Base:
    a = 1
    b = 2

    print('class defined')

    def __new__(cls, *args, **kwargs):
        print(cls.__name__, 'class instance created')
        return super().__new__(cls)

    def __init__(self):
        print(type(self).__name__, 'class instance inited')

    def hello(self):
        pass


b = Base()

""" output
class defined
Base class instance created
Base class instance inited
"""

上面代码中,我们定义了类属性a、b,和函数hello,print(‘class defined’)语句会在调用b = Base()之前打印,在使用class Base定义类后,这条语句就会触发。
当我们调用b = Base()创建实例时,Base类中的__new__函数将会触发,返回一个Base类的实例,这个操作由object对象实现,并调用__init__对该实例进行初始化。

Python3中的元类

类是type的实例

当调用print(type(b)),得到<class ‘main.Base’>,可知b是Base类的实例。Python是纯面向对象语言,因此类也是对象,当调用print(type(Base))时得到<class ‘type’>,可知类Base是type的实例,type就是Python中的原生元类,用来控制、生成类这个对象。
一般地,在定义类时,默认的此类的元类是type,因此,如果我们想控制类的创建,需要将类的元类指为我们自定义的元类,这个自定义的元类需要继承type元类。

class Meta(type):
    def __new__(meta, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, 'class created')
        return super().__new__(meta, *args)

    def __init__(cls, *args, **kwargs):
        print(cls.__name__, 'class inited')
        super().__init__(*args)


class Base(metaclass=Meta):
    a = 1
    b = 2

    print('class defined')

    def __new__(cls, *args, **kwargs):
        print(cls.__name__, 'class instance created')
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print(type(self).__name__, 'class instance inited')

    def hello(self):
        pass


b = Base()
print(type(Base))


b = Base()
print(type(Base))

""" output
class defined
Base class created
Base class inited
Base class instance created
Base class instance inited
<class '__main__.Meta'>
"""

Python3中,在定义类的参数中,用metaclass=xxx指明该类的元类,此时Base类由自定义的元类‘Meta’创建,因此当调用print(type(Base))时返回<class ‘main.Meta’>,说明Base是Meta类的实例。

元类中的__new__和__init__方法

在第一节中,我们看到了,一个对象的实例化是由它的类中定义的__new__和__init__控制。__new__用来创建并返回实例,__init__用来初始化实例。在元类中的__new__和__init__也同样扮演者相同的角色。
通过观察上例中的程序输出顺序,可以更进一步了解到实例创建的细节。

  1. 首先在类中定义的部分会先执行(print(‘class defined’))。
  2. Base的元类Meta中的__new__方法触发,生成实例类–Base。
  3. Base的元类Meta中的__init__方法触发,对上步生成的实例类–Base进行初始化。
  4. Base的__new__方法触发,实例b被创建。
  5. Base的__init__方法触发,实例b被初始化。

元类中的__new__方法

下面解释下上例中的参数含义。
meta:
和普通类中定义的__new__相似,meta参数指的是元类Meta。
args:
Python会给元类中的__new__方法传入三个参数,由*args收到传入的三个参数,在上例中我们对args进行解压操作clsname, bases, namespace = args。
clsname是个字符串,指的是被元类控制的类的名字’Base’。
bases是一个元组,包含Base类所有的继承类。
namespace则是Base类的命名空间包含其中中定义的属性,(a, b, new, init, hello…)。
kwargs将在下文中进行讲述。

元类中的__init__方法参数和__new__基本相同,除了第一个参数cls指的是__new__中返回的实例,在上例中cls指的就是类Base。

元类中的__prepare__

在定义类的时候,类中的属性和方法是由类的命名空间管理,命名空间是由元类的__prepare__方法提供的Mapping类型的对象。

class Meta(type):
    def __new__(meta, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, 'class created')
        return super().__new__(meta, *args)

    def __init__(cls, *args, **kwargs):
        print(cls.__name__, 'class inited')
        super().__init__(*args)


    @classmethod
    def __prepare__(meta, name, bases):
        print(name, "class namespace created")
        return {}

class Base(metaclass=Meta):
    a = 1
    b = 2

    # print('class defined')

    def __new__(cls, *args, **kwargs):
        print(cls.__name__, 'class instance created')
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print(type(self).__name__, 'class instance inited')

    def hello(self):
        pass

b = Base('4')
print(type(Base))
""" output
Base class namespace created
Base class created
Base class inited
Base class instance created
Base class instance inited
<class '__main__.Meta'>
"""

在类的属性和方法定义之前触发元类的__prepare__方法提供命名空间,我们可以通过改造__prepare__方法来实现提供不同类型的命名空间对象。例如:实现Python类中的方法重载。利用函数注解实现方法重载

可选参数的元类

上节中还没介绍元类中__new__和__init__方法中的可选参数**kwargs,这个参数是由定义类时传入的。

class Base(metaclass=Meta, hey='hello'):
    pass

定义可选参数hey='hello’时,元类Meta中的__prepare__, __new__, __init__方法中的**kwargs都会接受这个参数,并可以针对性处理。我们将在最后一节中介绍具体用法。

对比类装饰器和元类

通过上节的概述,我们了解到可以通过元类控制类的定义,在Python中,利用装饰器也可以对类定义进行控制。

def decorate(cls):
    cls.a += 1

    return cls


class Meta(type):

    def __new__(meta, *args, **kwargs):
        cls = super().__new__(meta, *args)

        cls.a += 1

        return cls


@decorate
class A:
    a = 0


class B(A):
    a = 0


class C(metaclass=Meta):
    a = 0


class D(C):
    a = 0


print(A.a)
print(B.a)
print(C.a)
print(D.a)

""" output
1
0
1
1
"""

通过上面例子可以看到,利用装饰器的方式在继承中,子类没有继承到装饰器的方法,而元类的方式则支持继承。

那么如果同时给类添加自定义元类和装饰器时的执行顺序是怎样的呢。

from collections import defaultdict, OrderedDict

def decorate(cls):
    print('decorate class', cls.__name__)

    return cls

class Meta(type):
    def __new__(meta, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, 'class created')
        return super().__new__(meta, *args)

    def __init__(cls, *args, **kwargs):
        print(cls.__name__, 'class inited')
        super().__init__(*args)

    @classmethod
    def __prepare__(meta, name, bases, **kwargs):
        print(name, "class namespace created")
        return {}

@decorate
class Base(metaclass=Meta, hey='hello'):
    a = 1
    b = 2

    print('class defined')

    def __new__(cls, *args, **kwargs):
        print(cls.__name__, 'class instance created')
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print(type(self).__name__, 'class instance inited')

    def hello(self):
        pass


b = Base()
print(type(Base))
""" output
Base class namespace created
class defined
Base class created
Base class inited
decorate class Base
Base class instance created
Base class instance inited
<class '__main__.Meta'>
"""

我们可以通过上述代码看到,类在实例化前先执行元类的相关函数,再执行装饰器的相关方法。

元类的运用

通过上文的描述,我们大概掌握了什么是元类,在实际中若想运用好元类,需要清晰掌握类的创建顺序。下面再对此进行较为详细的总结, 这里为类和元类都添加了__call__方法,该方法定以在某个类时(元类也是如此),当该类的实例在被用于函数的调用方式时触发__call__方法。例如实例x, 执行x(arg1, arg2)时,则相当于调用x.call(arg1, arg2)。

def decorate(cls):
    print('decorate class', cls.__name__)

    return cls


class Meta(type):
    def __new__(meta, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, 'class created')
        return super().__new__(meta, *args)

    def __init__(cls, *args, **kwargs):
        print(cls.__name__, 'class inited')
        super().__init__(*args)

    def __call__(self, *args, **kwargs):
        print(self.__name__, "class called")
        return super().__call__(*args, **kwargs)

    @classmethod
    def __prepare__(meta, name, bases, **kwargs):
        print(name, "class namespace created")
        return {}


@decorate
class Base(metaclass=Meta, hey='hello'):
    a = 1
    b = 2

    print('class defined')

    def __new__(cls, *args, **kwargs):
        print(cls.__name__, 'class instance created')
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print(type(self).__name__, 'class instance inited')

    def __call__(self, *args, **kwargs):
        return self.hello()

    def hello(self):
        print('hello')
        pass


b = Base()
b.hello()
b()
""" output
Base class namespace created
class defined
Base class created
Base class inited
decorate class Base
Base class called
Base class instance created
Base class instance inited
hello
hello
"""

下面按输出行解释每一步:

  1. 当发现Base的元类为Meta,执行元类的__prepare__方法,为Base类准备命名空间。当元类没有定义__prepare__方法时,默认地会给Base类提供OrderedDict的命名空间。
  2. Base类定义中更新__prepare__提供的命名空间的映射对象(更新类属性、函数等)。
  3. Base类的名字、父类组成的元组、上一步更新后的命名空间对象会传入元类的__new__(meta, *args, **kwargs)中的args参数中,Base类中定义的hey='hello’可选参数传入到kwargs参数中,__new__类会返回一个由super(type)类的__new__方法得到的类对象(这里就是Base类本身),此时上述的命名空间对象会被拷贝到一个有序的映射对象中,同时命名空间对象被遗弃,该对象被一个只读的proxy字典引用,最后成为Base.__dict__中的对象。在这一步后,一个完整的Baes类已经定义好,该类的属性及方法都保存在Base.__dict__中。
  4. 这一步用来初始化上步中得到的Base类,*args与**kwargs参数与__new__方法中的相同不再概述。
  5. 在Base类由元类生成好后,执行装饰器decorate,这一步可以对元类生产的类再次进行修改。
  6. 上面的几步都是在调用b = Base()前发生的事,由于Base是元类Meta的实例,当调用这条语句后,将触发Meta类中的__call__方法。该方法调用了type中的__call__方法用来生成初始化Base类的实例对象并返回。
  7. 上一步的super().__call__执行过程中,Base类中的__new__方法将被触发,用来生成一个Base类的实例对象。
  8. 接着Base中的__init__方法将被触发,用来初始化上一步中生成的Base类的实例。
  9. 此时的b为Base类的一个实例,可以通过b.hello()调用方法。
  10. 由于我们在Base类中声明了__call__方法,因此b对象将可以被用来调用,这里直接返回了self.hello(),因此这一步的结果与上一步相同。

单例的实现

当我们调用b = Base()时,会产生Base的新的实例对象,我们可以通过多种方式改造使Base()返回的永远是同一个实例对象。首先试试通过改造__new__方法的方式。

def decorate(cls):
    print('decorate class', cls.__name__)

    return cls


class Meta(type):
    def __new__(meta, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, 'class created')
        return super().__new__(meta, *args)

    def __init__(cls, *args, **kwargs):
        print(cls.__name__, 'class inited')
        super().__init__(*args)

    def __call__(self, *args, **kwargs):
        print(self.__name__, "class called")
        return super().__call__(*args, **kwargs)

    @classmethod
    def __prepare__(meta, name, bases, **kwargs):
        print(name, "class namespace created")
        return {}


@decorate
class Base(metaclass=Meta, hey='hello'):
    a = 1
    b = 2
    __instance__ = None

    print('class defined')

    def __new__(cls, *args, **kwargs):
        if cls.__instance__ is None:
            print(cls.__name__, 'class instance created')
            cls.__instance__ = super().__new__(cls)
        return cls.__instance__

    def __init__(self, *args, **kwargs):
        print(type(self).__name__, 'class instance inited')

    def __call__(self, *args, **kwargs):
        return self.hello()

    def hello(self):
        print('hello')
        pass


b = Base()
b2 = Base()
print(b == b2)
""" output
Base class namespace created
class defined
Base class created
Base class inited
decorate class Base
Base class called
Base class instance created
Base class instance inited
Base class called
Base class instance inited
True
"""

我们通过在Base中定义一个__instance__类属性,并在__new__方法中判断是否生成过Base实例返回__instance__引用的唯一实例。观察程序输出我们发现,在调用b2 = Base()时,输出了Base class called 和 Base class instance inited,说明Base()返回的是b引用的实例,因此b == b2为True,但Base中的__init__方法被调用了两次,因此,虽然b和b2引用的实例相同,但却被初始化了两次。若想让实例只被初始化一次,需要在__init__方法中也进行类似的改造。
除此之外,这种单例的实现还存在着一个问题,当某个类继承Base类时,如果不在该类中定义__instance__属性,__new__方法中的cls.instance__会找到父类(Base)中的__instance,这样会导致这个新类的对象也是Base之前实例化的对象,而不是针对该类产生的新对象。

元类方式实现单例

为了解决上述问题,可以通过改造元类中的__call__方法实现。

def decorate(cls):
    print('decorate class', cls.__name__)

    return cls


class Meta(type):
    def __new__(meta, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, 'class created')
        return super().__new__(meta, *args)

    def __init__(cls, *args, **kwargs):
        print(cls.__name__, 'class inited')
        cls.__instance__ = None
        super().__init__(*args)

    def __call__(self, *args, **kwargs):
        print(self.__name__, "class called")
        if self.__instance__ is None:
            self.__instance__ = super().__call__(*args, **kwargs)
        return self.__instance__

    @classmethod
    def __prepare__(meta, name, bases, **kwargs):
        print(name, "class namespace created")
        return {}


@decorate
class Base(metaclass=Meta, hey='hello'):
    a = 1
    b = 2

    print('class defined')

    def __new__(cls, *args, **kwargs):
        print(cls.__name__, 'class instance created')
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print(type(self).__name__, 'class instance inited')

    def __call__(self, *args, **kwargs):
        return self.hello()

    def hello(self):
        print('hello')
        pass


b = Base()
b2 = Base()
print(b == b2)
""" output
Base class namespace created
class defined
Base class created
Base class inited
decorate class Base
Base class called
Base class instance created
Base class instance inited
Base class called
True
"""

这里我们在元类中的__init__方法中定义了类的属性__instance__并在__call__方法中控制__instance__的生成,通过结果我们看到,在第二次调用Base()时元类的__call__被执行,但此时该类的__instance__属性存储了Base的实例,因此不会再执行Base中的__new__和__init__方法,同时这种方式也解决了继承的问题,因为在某个类继承Base后,该类的元类还是由Meta控制(会触发元类的__new__和__init__方法),因此元类会为每个被该元类控制的类生成一个__instance__属性。

总结

可以通过元类实现对类的控制。
可以通过定义__new__ __init__ __call__ __prepare__等方法控制类和类实例的创建。

猜你喜欢

转载自blog.csdn.net/miuric/article/details/83412202