写点什么

Python type 和元类 metaclass

作者:菜皮日记
  • 2023-09-09
    北京
  • 本文字数:5802 字

    阅读完需:约 19 分钟

Python 一切皆对象,包括类 class 也是对象

众所周知 Python 跟 Java 一样,一切皆对象,所以 class 类也是对象,而对象可以动态创建,所以一个 class 类也可以被动态创建出来。

通过 type() 创建类

type 的定义,type 是一个类 class,只不过可以 callable

class type(name, bases, dict, **kwds)
复制代码

type 传入一个参数时,返回的是参数的类型。

>>> type(int)<class 'type'>>>> type(list)<class 'type'>>>> type(T)<class 'type'>>>> type(C)<class 'type'>>>> type(object)<class 'type'>
复制代码

type 传入三个参数时,用来创建类:

第一个参数 name 是被创建的类的名字,str 类型

第二个参数 bases 是被创建的类的父类,tuple 类型,不传默认是 (object,)

第三个参数 dict 是被创建的类的属性和方法,dict 类型

下面两种创建类的方式,结果是一样的

class X:    a = 1
X = type('X', (), dict(a=1))
复制代码

通过 metaclass 创建类

我们知道可以用 class 类来创建 object 对象,而 class 类本身也是对象,那么 class 这个对象由谁来创建呢?答案就是 metaclass,metaclass 是 class 的 class,metaclass 创建 class,class 创建 object,metaclass→class→object:

MyClass = MetaClass()my_object = MyClass()
复制代码

a 是对象,对象的类型是 class,class 的类型是 metaclass

>>> a = 1 # 对象>>> a.__class__  # 对象的类型<class 'int'>  # class>>> type(a)<class 'int'>
>>> a.__class__.__class__ # class的类型<class 'type'> # metaclass>>> type(a.__class__)<class 'type'>
复制代码

能创建类的类,就是 metaclass 元类,上述的 type 就是一个元类。

Python2 中给一个 class 指定一个创建它的元类:

class Foo(object):    __metaclass__ = something...    [...]
复制代码

Python3 中语法有变化:

class Foo(object, metaclass=something):    ...# 或class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):    ...
复制代码

metaclass 的存在的意义是动态创建类、拦截类、修改类

就像动态创建对象一样,你想在哪创建一个对象都可以,同样的你想创建一个自定义的类,然后根据它创建实例。

假设有一个需求是将左右的类的属性,都处理成大写的,演进过程如下:

给 module 指定一个 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_attrs):    """      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_attrs = {        attr if attr.startswith("__") else attr.upper(): v        for attr, v in future_class_attrs.items()    }
    # let `type` do the class creation    return type(future_class_name, future_class_parents, uppercase_attrs)
__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'
复制代码

自定义一个元类,注意元类是用来创建类的,所以必须继承自 type

# remember that `type` is actually a class like `str` and `int`# so you can inherit from itclass 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_attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in future_class_attrs.items()        }        return type(future_class_name, future_class_parents, uppercase_attrs)
复制代码

简写一下:

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return type(clsname, bases, uppercase_attrs)
复制代码

注意到最后一行,调用的是 type,这还不算 OOP,因为并没有调用父类,所以最好改成:

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return type.__new__(cls, clsname, bases, uppercase_attrs)
复制代码

或者可以改成使用 super:

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return super(UpperAttrMetaclass, cls).__new__(            cls, clsname, bases, uppercase_attrs)
复制代码

分层思想的体现

metaclass 可以做的事:

  • intercept a class creation

  • modify the class

  • return the modified class

应用层想用类创建实例,然后使用实例。而至于类是怎么来的,应用层并不关心,创建类这一步就交给元类处理,而在元类这一层中做修改,对上层应用来说是透明的。

metaclass 实际应用场景

最多的是用在定义 API 方面,这个 API 不是狭义的应用接口,而是更广泛意义的接口、协议,类似一种转换器的概念,API 给应用提供了简单的使用接口,而把复杂的处理转换隐藏在 metaclass 内部,经过处理的结果最终输出到另一个系统中。典型的例子就是 Django 中的 ORM:

class Person(models.Model):    name = models.CharField(max_length=30)    age = models.IntegerField()
person = Person(name='bob', age='35')print(person.age)
复制代码

当打印 person.age 时,并不是返回 IntegerField 对象,而是返回一个 int 值,而且还可以使用这个对象写入到 db 中,这一些都是因为 models.Model 背后的 metaclass,其讲复杂的类型转换等操作隐藏在内部,而给业务提供了一个十分简洁的使用接口。

另外就是需要动态生成类的地方,例如写一个 CacheMeta,可以给各种未知的类加缓存,具体给哪些类加缓存,对于这个元类来说是未知的,所以需要在运行过程中动态生成,此时就可以使用元类的方式。

别忘了修改类其实还可以使用装饰器

装饰器的思路也是分层,在类使用之前,给类套一层外壳。

type 是自己的对象,也是自己的元类

Python 一切皆对象,要么是 class 的对象,要么是 metaclass 的对象,而只有 type 例外。

type 是 type 自己的对象,type 也是 type 自己的 metaclass,这没办法在纯 Python 中实现,而是在语言实现层面做的特殊处理实现的。

type.new type.init 和 type.call 关系

  • type.__new__:是在元类中,根据入参,创建出普通类,即从类语法定义生成类实体 metaclass 调用 new 创建 class,就像 class 调用 new 创建 object 一样

  • type.__init__:在 new 完成后,即根据语法定义创建出类之后调用,给该类做初始化操作不产生什么返回值 metaclass 调用 init 初始化 class,就像 class 调用 init 初始化 object 一样

  • type.__call__:根据一个实体存在的类,创建一个该类的对象在一个具体对象 object 上直接调用 对象名(),会执行 class 中定义的 __call__,同理在 class 上直接调用 class(),会执行 metaclass 中定义的 call

class CachedMate(type):    """    作用跟type一样,就是用来创建 class 的    """
    def __new__(mcs, clsname, bases, attrs):        """        type 的 __new__ 方法作用是根据 类名clsname、依赖bases、属性attrs 这些字面量来创建出一个类        就像普通 class 中定义的 __new__ 方法作用是创建一个 object        metaclass 中定义的 __new__ 方法作用是创建一个 class        返回的 new_class 就是被创建出来的类        """        print(f'CachedMate __new__ start {clsname} || {bases} || {attrs}')        new_class = super().__new__(mcs, clsname, bases, attrs)        print(f'CachedMate __new__ gen class {new_class} || {type(new_class)} || {type(type(new_class))}')        print(f'CachedMate __new__ end')        print('======')        return new_class
    def __init__(cls, clsname, bases, attrs):        """        给创建出来的 class 做一些初始化操作        就像普通 class 中定义的 __init__ 方法作用是给创建的 object 初始化        metaclass 中定义的 __init__ 方法作用是给创建的 class 初始化        """        print(f'CachedMate __init__ start {clsname} {bases} {attrs}')        obj = super().__init__(clsname, bases, attrs)        print(f'CachedMate __init__ gen obj {obj} {type(obj)} {type(type(obj))}')        print(f'CachedMate __init__ end')        print('======')
        # self.__cache = weakref.WeakValueDictionary()        cls._cache = {}
    def __call__(cls, *args, **kwargs):        """        继续类比普通class中的场景        对象object以调用方式出现时,就是在调用class中定义的 __call__,如object()        而类class以调用方式出现时,就是在调用metaclass中定义的 __call__,如class()        这里就是当Spam()时,也就是实例化Spam时就会调用
        这也就是为什么单例模式可以放在这里做的原因,目标类实例化时,必然会调用 __call__ 所以固定返回同一个实例,即实现单例        或者说,想要控制一个类的创建过程,都可以在这里坐处理        """        print(f'CachedMate __call__ start', args, kwargs)
        if args in cls._cache:            print('CachedMate __call__ cached')            return cls._cache[args]        else:            print('CachedMate __call__ before super().__call__')            obj = super().__call__(*args, **kwargs)            print('CachedMate __call__ after super().__call__', obj, type(obj))            cls._cache[args] = obj            return obj
# Exampleclass Spam(metaclass=CachedMate):    def __new__(cls, *args, **kwargs):        print('Spam __new__', args, kwargs)        return super(Spam, cls).__new__(cls)
    def __init__(self, *args, **kwargs):        print('Spam __init__', args, kwargs)        self.args = args        self.kwargs = kwargs
    def __call__(self, *args, **kwargs):        # 在Spam实例上使用 spam() 才会调用这里        print('Spam __call__', args, kwargs)
# 结果中Spam实例化参数不变,得到的就是缓存的结果,参数变了则是新接口print(11111, Spam._cache)# 11111 {} 一开始没有缓存
sp = Spam(1,2,'test1', 'test2', name='test_name')print(22222, Spam._cache)# 22222 {(1, 2, 'test1', 'test2'): <__main__.Spam object at 0x10b71b160>}# 有了一个缓存
sp2 = Spam(1,2,'test1', 'test2', name='test_name')print(33333, Spam._cache)# 33333 {(1, 2, 'test1', 'test2'): <__main__.Spam object at 0x10b71b160>}# 因为参数一样,所以读的缓存
sp3 = Spam(1,2,'test1', 'test3', name='test_name3')print(44444, Spam._cache)# 44444 {(1, 2, 'test1', 'test2'): <__main__.Spam object at 0x10b71b160>, (1, 2, 'test1', 'test3'): <__main__.Spam object at 0x10b71b250>}# 参数变了,重新生成了新的缓存,独立开来
复制代码


用户头像

菜皮日记

关注

全干程序员 2018-08-08 加入

个人站点:菜皮日记 lipijin

评论

发布
暂无评论
Python type 和元类 metaclass_Python_菜皮日记_InfoQ写作社区