python的元类以及应用

前言

记得在学习了一段时间python之后,开始涉及比较复杂的概念。 其中看到了元类这个东西,很抽象,看完很迷惑,于是开始一番搜索。 搜索完让我更加迷惑。
首先是元类的定义比较难以理解,其次是搜索元类的应用的时候,发现出来的资料,全都是与ORM相关的。
由此我有几个问题:
元类是什么?
元类有什么用?
ORM一定要用元类实现吗?

官网介绍元类应用

在python官方文档上介绍了元类很多的应用。但是搜索资料的时候,都是通过元类实现的ORM。

python的对象

要了解元类有什么作用,要先了解一下python的面向对象的概念。

所有都是对象

在python中,不管字符串、列表、字典都是对象,包括类本身也是对象。
使用type()函数可获得对象的类型:

print(type(1))
print(type('python'))

class A:
    pass

print(type(A))

打印结果:

<class 'int'>
<class 'str'>
<class 'type'>

题外话,如果print(type(type))呢?

由此可知,当你定义一个类的时候,类本身也是一个对象。所有通过class定义的类都是type的实例。

type的作用

type函数除了能够获得对象的类型,还能创建一个新的type实例,也就是和class的作用是一样的。

#通过class创建类
class Person:
    def __init__(self, age):
        self.age = age

# 通过type创建类
def init(self, age):
    self.age = age


Person2 = type('Person', (), {'__init__': init})

当type接收一个参数的时候就获取它的类型,当接收三个参数的时候,就返回一个type的实例,也就是一个类。
type(name, bases, dict, ***kwargs)
name就是class的名字,当我们用type()去检查Person的实例的时候,会获得class的name。

p = Person(21)
print(p)
<class '__main__.Person'>

bases是父类的集合,注意它是一个元组,可以多继承。 如果bases是空的,那么就会继承object
dict是一个字典,用来传递类的属性,上面的例子传递了一个init函数。
由此,我们可以通过type动态生成一个类,再由这个类去实例化一个对象。
类也是对象,我们不妨称为【类对象】区别普通【对象】

graph TD
type --实例化--> 类对象
类对象  --实例化--> 对象

元类

通过上述内容,我们可以得出2个重要结论:
类也是对象
类可以被动态创建
可以通过type进行创建

metaclass

除了使用type来创建类之外,python还提供了metaclass的来影响类的创建。
看一个简单的例子:

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print('运行到元类的__new__')
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        print('运行到元类的__init__')

    def __call__(self, *args, **kwargs):
        print('运行到元类的__call__')
        return self


class A(metaclass=MetaClass):
    pass


print('开始实例化A')
a = A()

输出:

运行到元类的__new__
运行到元类的__init__
开始实例化A
运行到元类的__call__

在创建类的时候,通过传入metaclass关键字参数时,python会用metaclass的值去创建类。
上文说过,python中的类也是对象。 我们结合一下普通类和对象,以及对象的实例化过程:

class A:
    def __new__(cls, *args, **kwargs):
        print('运行到普通类的__new__')
        return super().__new__(cls)

    def __init__(self):
        print('运行到普通类的__init__')


a = A()

输出:

运行到普通类的__new__
运行到普通类的__init__

对比二者的创建过程是非常相似的。 也就是普通对象创建过程是:类的new——>类的init
类对象的创建过程:元类的new——>元类的init
普通类的实例化——>对象
元类的实例化——>类对象

  • 这边元类的call有一些特殊的地方,因为普通类的call是在实例被调用的时候触发,而元类的实例就是类对象,那么在类对象进行实例化的时候,就会触发。后面有机会深入探讨元类的执行过程的时候再补充。

由此,我们可以通过metaclass对类的创建进行拦截,完成我们的需求。接下来举几个例子来说明一下体验一下元类的实际应用。

元类的应用

单例

先看一下不用元类实现的单例:

class Singleton:
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls)
        return cls._instance


a = Singleton()
b = Singleton()

print(a == b)

输出:

True

用元类实现的单例:

class MetaSingleton(type):
    def __init__(self, *args, **kwargs):
        self._instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super().__call__(*args, **kwargs)
        return self._instance


class Singleton(metaclass=MetaSingleton):
    pass


a = Singleton()
b = Singleton()

print(a == b)

输出:

True

注意看二者的差别,普通类通过new在实例化对象的时候,判断是否已经实例化过了。而元类的new实例化的是一个类对象,在创建出来的类对象进行实例化的时候才会调用元类的call,所以在call中进行判断类对象是否已经实例化了。

缓存实例

类似上面的单例,我们创建一个缓存实例的例子。
不使用元类实现:

class ObjectCache:
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_obj_cache'):
            cls._obj_cache = {}
        if args not in cls._obj_cache:
            cls._obj_cache[args] = super().__new__(cls)
        return cls._obj_cache[args]


a = ObjectCache('a')
b = ObjectCache('a')
c = ObjectCache('c')
print(a == b)
print(a == c)

输出:

True
False

使用元类实现:

class MetaObjectCache(type):
    def __init__(self, *args, **kwargs):
        self._object_cache = {}
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if args not in self._object_cache:
            self._object_cache[args] = super().__call__(*args, **kwargs)
        return self._object_cache[args]


class ObjectCache(metaclass=MetaObjectCache):
    def __init__(self, *args, **kwargs):
        pass


a = ObjectCache('a')
b = ObjectCache('a')
c = ObjectCache('c')

print(a == b)
print(a == c)

输出:

True
False

实现接口类

python虽然是鸭子类型,但是其中的abc模块也提供了抽象基类的功能。

import abc


class Base(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def test(self):
        pass


class A(Base):
    pass


a2 = A()

这段代码中,如果子类A没有实现test的方法,就会抛出错误:

TypeError: Can't instantiate abstract class A with abstract method test

可以通过元类实现类似的功能:

class Base(type):
    def __new__(cls, *args, **kwargs):
        name, base, attrs = args
        if not base:
            cls._methods = {}
            for k, v in attrs.items():
                if callable(v):
                    cls._methods[k] = v
        else:
            for k in cls._methods:
                if k not in attrs:
                    raise Exception(f'子类没有实现{k}方法')
        return super().__new__(cls, *args, **kwargs)


class A(metaclass=Base):
    def test(self):
        pass


class B(A):
    pass

输出:

Exception: 子类没有实现test方法

代码中的name, base, attrs = args就是上文提到tpye的参数,这里直接用*args传参。
在元类的new函数中,先判断类对象是否有父类,如果没有父类,就将方法储存起来。 如果有父类,就遍历_methods,判断每一个方法都存在attrs中,否则就是子类没有实现某个方法。
这里也体现了元类的一个特征,父类如果通过metaclass创建,那么子类也会受到影响。类B会首先检查此类是否有metaclass,如果没有就会去检查父类是否有metaclass.

拓展类功能

class MetaList(type):
    def __new__(cls, *args, **kwargs):
        _, _, attrs = args
        attrs['add'] = lambda self, val: self.append(val)
        return super().__new__(cls, *args, **kwargs)


class Mylist(list, metaclass=MetaList):
    pass


L = Mylist()
print(L)
L.add(3)
print(L)

输出:

[]
[3]

这样便利用了元类拓展了类的功能。

元类的实质

通过上面的分析和例子,可以得出结论,元类可以用来控制类、拦截类,包括创建,实例化。python通过元类提供了一种动态创建类的功能。
在python中确实是比较难以理解的概念,因此Tim Peters曾说:

元类就是深度的魔法,99%的⽤户应该根本不必为此操⼼。
如果你想搞清楚 究竟是否需要⽤到元类,那么你就不需要它。
那些实际⽤到元类的⼈都⾮常 清楚地知道他们需要做什么,⽽且根本不需要解释为什么要⽤元类。
—— TimPeters

特别是上面的几个例子,好像也没有非要通过元类来实现不可。并且,虽然元类能够拦截类的创建过程,但是“动态”的概念体现不够深刻。
后面的内容会解决开头的疑问:ORM和元类有什么关系?ORM一定要用元类实现吗?
通过ORM这个例子,也能更好体现,“动态”创建类。

为什么ORM要使用元类

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容