python 元编程学习

1,023 阅读4分钟

什么是元类

定义

python中的一切都是对象,类本身也是对象,元类则是创建类对象的类

python对象的创建过程 及 元类常使用方法的简介

加载模块时,python解释器 自动创建模块中的类对象,全局函数对象 新建实例时,用户 主动调用类对象来创建实例 元类常用方法: __prepare__, __new__, __init__, __call__

"""
  元类常用方法,以及类对象创建和实例对象创建的时间点
"""


class MyMeta(type):
    def __new__(mcs, name, bases, cls_dict):
        """ 元类创建类对象方法
        :param name: 类对象的类名
        :param bases: 类对象的父类
        :param cls_dict: 类对象的命名空间
        :return:
        """
        print('2. call MyMeta method: new', mcs)
        return super().__new__(mcs, name, bases, cls_dict)

    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        """ 类创建前的准备工作,返回一个字典,为空的命名空间对象
        :param name:
        :param bases:
        :param kwargs:
        :return:
        """
        print('1. call MyMeta method: prepare', mcs)
        return super().__prepare__(name, bases, **kwargs)

    def __init__(cls, name, bases, namespace, **kwargs):
        """ 类对象创建完成后的一些初始化工作
        :param name:
        :param bases:
        :param namespace:
        :param kwargs:
        """
        print('3. call MyMeta method: init', cls)
        super().__init__(name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        """ 使用类对象创建实例时调用
        :param args:
        :param kwargs:
        :return:
        """
        print('1> call MyMeta method: call', cls)
        return super().__call__(*args, **kwargs)


class MyClass(metaclass=MyMeta):
    def __new__(cls, *args, **kwargs):
        print('2> call MyClass method: new', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('3> call MyClass method: init', self)
        super().__init__()

    def __str__(self):
        return 'hello world'


def instance_get():
    print('call instance get method:')
    instance = MyClass()
    print(instance)


if __name__ == '__main__':
    print('load model completed!\n---------------------------------------------------------\n')
    instance_get()

上述代码的输出:

1. call MyMeta method: prepare <class '__main__.MyMeta'>
2. call MyMeta method: new <class '__main__.MyMeta'>
3. call MyMeta method: init <class '__main__.MyClass'>
load model completed!
---------------------------------------------------------

call instance get method:
1> call MyMeta method: call <class '__main__.MyClass'>
2> call MyClass method: new <class '__main__.MyClass'>
3> call MyClass method: init hello world
hello world

元类的使用例子

我们可以通过继承元类 type,对其中的一些方法做处理,编写我们需要的定制化元类。元类在构建框架的时候用的较多,像django就使用了元类来构建orm框架(通过元类来感知字段的定义顺序,从而顺序的将字段映射到数据库中。) 平常的开发中,可以使用元类来创建单例、做对象缓存等,其它方式肯定也可以做这些工作,不过元类提供的方案比较便捷还比较优雅。

1. 创建单例

通过上面的程序,我们知道创建实例对象的入口方法是元类中的__call__方法,我们可以通过覆写这个方法来实现单例模式

"""
  使用元类实现单例模式
  注意: 这种写法非线程安全!
"""


class Singleton(type):
    def __init__(cls, *args, **kwargs):
        cls._instance = None  # 将实例定义为自己的一个属性
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('call Singleton call method')
        if cls._instance is None:
            cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class SingletonCls(metaclass=Singleton):
    def __init__(self):
        print('call SingletonCls init method')


if __name__ == '__main__':
    s1 = SingletonCls()
    s2 = SingletonCls()
    print(s1 is s2)

以下为该脚本的输出:

call Singleton call method
call SingletonCls init method
call Singleton call method
True

2. 做对象缓存

对象创建代价比较打的时候,做对象缓存是个不错的选择,很多缓存是用专门的缓存池来完成,这里也可以直接用元类来做缓存。

import weakref


class CacheMeta(type):
    """
    使用元类做对象缓存处理
    """

    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace)
        cls._cache = weakref.WeakValueDictionary()

    def __call__(cls, *args):
        # 传入的参数相同的,就给同一个对象,如果对应参数的对象还没创建就先创建对象
        if args in cls._cache:
            return cls._cache[args]
        obj = super().__call__(*args)
        cls._cache[args] = obj
        return obj


class CacheClass(metaclass=CacheMeta):
    def __init__(self, name):
        print('Creating instance({!r})'.format(name))
        self.name = name


if __name__ == '__main__':
    o1 = CacheClass('hello')
    o2 = CacheClass('world')
    o3 = CacheClass('hello')
    print(o1 is o2)
    print(o1 is o3)

以下为该脚本输出

Creating instance('hello')
Creating instance('world')
False
True

3. 子类覆盖方法的签名检查

使用元类可以检查子类的方法前面是否和父类的保持一直,如果要强迫继承时签名不能修改,这个是一个操作方式。平时使用IDE在进行编码的时候,如果参数和父类不一致(参数名不一致不会提示),是有提示的,所以这点不用太过在意。

from inspect import signature
import logging


class MatchSignaturesMeta(type):

    def __init__(cls, classname, bases, clsdict):
        super().__init__(classname, bases, clsdict)
        sup = super(cls, cls)

        for name, value in clsdict.items():
            if name.startswith('_') or not callable(value):
                continue
            sup_func = getattr(sup, name, None)
            if sup_func:
                sup_sig = signature(sup_func)
                val_sig = signature(value)
                if sup_sig != val_sig:
                    logging.warning(
                        'Signature mismatch in %s. %s != %s', 
                        value.__qualname__, 
                        sup_sig, 
                    val_sig
                )


class Root(metaclass=MatchSignaturesMeta):
    pass


class A(Root):
    def foo(self, x, y):
        pass

    def spam(self, x, *, z):
        pass


class B(A):
    def foo(self, a, b):
        pass

    def spam(self, x, z):
        pass


b = B()

在初始化的时候,日志里面会打印如下内容:

WARNING:root:Signature mismatch in B.foo. (self, x, y) != (self, a, b)
WARNING:root:Signature mismatch in B.spam. (self, x, *, z) != (self, x, z)

4. 动态创建新的类

创建类的时候,是将写在类下面的属性、方法和类对象(通过描述器协议)绑定起来,python提供了手动实现这个过程的方式,这种操作有点类似于反射。

import abc
import types


def __init__(self, name, shares, price):
    self.name = name
    self.shares = shares
    self.price = price


def cost(self):
    return self.shares * self.price


cls_dict = {
    '__init__': __init__,
    'cost': cost,
}

Stock = types.new_class(
    'Stock',  # 类名
    (),  # 父类
    {'metaclass': abc.ABCMeta},  # 元类及类定义参数
    lambda ns: ns.update(cls_dict)  # ns 为 prepare 方法(见上)返回的字典对象
)
Stock.__module__ = __name__

s = Stock('abc', 200, 35)
print(s)

打印出来的结果表明,s就是类Stock的一个对象,这个和直接将Stock定义为class是一样的效果。

<__main__.Stock object at 0x7f153c19bb00>

参考: python cookbook 第九章; python高级编程 第三章