python元类获取__init__的默认参数,共享给class

2020-07-03  本文已影响0人  超神雷鸣
python元类获取__init__的默认参数,共享给classmethod

获取__init__的默认参数,并在classmethod方法中为没有给定的属性赋默认值,提升代码的健壮性

\color{#68A6D3}{元类定义:}

#!/usr/bin/env Python
# -- coding: utf-8 --

"""
@version: v0.1
@author: narutohyc
@file: meta_interface.py
@Description: 
@time: 2020/6/15 20:29
"""
import collections
from abc import (ABC,
                 abstractmethod,
                 ABCMeta)
import inspect

class DicMetaClass(ABCMeta):
    def __new__(cls, name, bases, attrs, **kwargs):
        if name == 'DicMeta':
            return super().__new__(cls, name, bases, attrs, **kwargs)
        # 获取__init__函数的 默认值
        argspec = inspect.getfullargspec(attrs["__init__"])
        init_defaults = dict(zip(argspec.args[-len(argspec.defaults):], argspec.defaults))
        cls.__init_defaults = init_defaults
        attrs['__init_defaults__'] = init_defaults
        return super().__new__(cls, name, bases, attrs, **kwargs)

\color{#68A6D3}{抽象父类:}

#!/usr/bin/env Python
# -- coding: utf-8 --

"""
@version: v0.1
@author: narutohyc
@file: meta_interface.py
@Description: 
@time: 2020/6/15 20:29
"""

from abc import (ABC,
                 abstractmethod,
                 ABCMeta)

class DicMeta(ABC, metaclass=DicMetaClass):
    def __init__(self):
        pass

    @abstractmethod
    def to_dict(self):
        '''
        返回字典
        '''
        pass

    @classmethod
    def load_from_mapping(cls, mapping_datas):
        '''
        用字典来构建实例对象
        '''
        assert isinstance(mapping_datas, collections.abc.Mapping)
        obj = cls.__new__(cls)
        [setattr(obj, k, v) for k, v in mapping_datas.items()]
        return obj

\color{#68A6D3}{子类实现:}

#!/usr/bin/env Python
# -- coding: utf-8 --

"""
@version: v0.1
@author: narutohyc
@file: text_meta.py
@Description: 
@time: 2020/5/22 14:55
"""
from augmentation.meta_class.meta_interface import DicMeta
from utils.utils_func import gen_md5, str2bool
import re

class TaskMeta(DicMeta):
    '''
    数据包装类的bean结构
    '''
    def __init__(self, text, doc_id, sentence_id, reg_lst,
                 has_reg=True,
                 flag=None,
                 dataset='train',
                 text_source="primitive"):
        super(TaskMeta, self).__init__()
        self.text = text
        self.doc_id = doc_id
        self.sentence_id = sentence_id
        if reg_lst and isinstance(reg_lst[0], list):
            reg_lst = ['%s %s %s' % (tag, start_idx, value) for tag, start_idx, value in reg_lst]
        self.reg_lst = sorted(reg_lst, key=lambda reg: int(re.sub(' +', ' ', reg).split(" ", 2)[1])) if reg_lst else []
        self.flag = list(set(i.split(' ', 2)[0] for i in self.reg_lst)) if flag is None else flag
        self.has_reg = str2bool(has_reg)
        self.dataset = dataset
        self.text_source = text_source
        self._id = gen_md5(self.text)

    @classmethod
    def load_from_mapping(cls, mapping_datas):
        '''
        用字典来构建 TaskMeta实例
        '''
        obj = super(TaskMeta, cls).load_from_mapping(mapping_datas)
        obj._id = gen_md5(obj.text)
        [setattr(obj, k, v) for k, v in obj.__init_defaults__.items() if not hasattr(obj, k)]
        if obj.flag is None:
            obj.flag = list(set(i.split(' ', 2)[0] for i in obj.reg_lst))
        obj.has_reg = str2bool(obj.has_reg)
        return obj
    
    @property
    def to_dict(self):
        '''
        当该类没有其他多余属性时  可以直接返回self.__dict__的副本
        '''
        return {"text": self.text,
                "doc_id": self.doc_id,
                "sentence_id": self.sentence_id,
                "reg_lst": self.reg_lst,
                "flag": list(self.flag),
                "has_reg": self.has_reg,
                "dataset": self.dataset,
                "text_source": self.text_source,
                "_id": self._id}    

\color{#68A6D3}{方法测试:}

task_meta_0 = TaskMeta.load_from_mapping({'text': '斯坦福大学开发的基于条件随机场的命名实体识别系统,该系统参数是基于CoNLL、MUC-6、MUC-7和ACE命名实体语料训练出来的。', 
                                          'doc_id': 'id1', 'sentence_id': 'id1', 
                                          'reg_lst': ['学校 0 斯坦福大学', '标注 33 CoNLL', '标注 39 MUC-6', '标注 45 MUC-7', '标注 51 ACE']})
task_meta_1 = TaskMeta.load_from_mapping({'text': '斯坦福大学开发的基于条件随机场的命名实体识别系统,该系统参数是基于CoNLL、MUC-6、MUC-7和ACE命名实体语料训练出来的。', 
                                          'doc_id': 'id1', 'sentence_id': 'id1', 
                                          'reg_lst': ['学校 0 斯坦福大学', '标注 33 CoNLL', '标注 39 MUC-6', '标注 45 MUC-7', '标注 51 ACE'], 
                                          'flag': ['学校', '标注'], 'has_reg': True, 
                                          'dataset': 'train', 'text_source': 'primitive', '_id': '3b895befc659345be8686bd7de4d7693'})
task_meta_0.to_dict == task_meta_1.to_dict
Out[33]: True

可以看出,task_meta_0和task_meta_1两者的 值是完全相同的,这里就可以做到共享__init__默认参数的效果

元类的定义
Python定义元类时,需要从 type类中继承,然后重写 __new__方法,便可以实现意想不到的功能。

class Meta(type):
    def __new__(meta,name,bases,class_dict):
        #...各种逻辑实现1
        cls = type.__new__(meta,name,bases,class_dict)
        print('当前类名',name)
        print('父类',bases)
        print('全部类属性',class_dict)
        #...各种逻辑实现2
        return cls

class MyClass(object,metaclass=Meta):
    stuff = 33
    def foo(self):
        pass
当前类名 MyClass
父类 (<class 'object'>,)
全部类属性 {'__module__': '__main__', '__qualname__': 'MyClass', 'stuff': 33, 'foo': <function MyClass.foo at 0x0000019E028315E8>}

结论:元类可以获知那个类的名称、其所继承的父类,以及定义在class语句体中的全部类属性

\color{#68A6D3}{元类关键点解析}

class DicMetaClass(ABCMeta):
    def __new__(cls, name, bases, attrs, **kwargs):
        if name == 'DicMeta':
            return super().__new__(cls, name, bases, attrs, **kwargs)
        # 获取__init__函数的 默认值
        argspec = inspect.getfullargspec(attrs["__init__"])
        init_defaults = dict(zip(argspec.args[-len(argspec.defaults):], argspec.defaults))
        cls.__init_defaults = init_defaults
        attrs['__init_defaults__'] = init_defaults
        return super().__new__(cls, name, bases, attrs, **kwargs)

inspect.getfullargspec(attrs["__init__"])可以获取到当前类(MyClass)的__init__所有属性。
这时候argspec.defaults就是__init__所有关键字参数的信息了,把这个保存到attrs['__init_defaults__'] 里面的话。
MyClass就可以用xx.__init_defaults__.items()获取到了。

综上,可以用元类实现初始化关键字参数和classmethod共享,以增强classmethod的健壮性。

ps: DicMeta已经实现了load_from_mapping的方法,子类如果没有特殊操作,就可以直接享受到这个方法。个人该方法用于加载字典数据到某个类还是挺方便的。

上一篇下一篇

猜你喜欢

热点阅读