python 元编程学习

什么是元类

定义

python中的一切都是对象,类自己也是对象,元类则是建立类对象的类python

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

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

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


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框架(经过元类来感知字段的定义顺序,从而顺序的将字段映射到数据库中。) 日常的开发中,可使用元类来建立单例、作对象缓存等,其它方式确定也能够作这些工做,不过元类提供的方案比较便捷还比较优雅。django

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)
复制代码

如下为该脚本输出bash

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高级编程 第三章

相关文章
相关标签/搜索