美文网首页
python元类获取__init__的默认参数,共享给class

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

作者: 超神雷鸣 | 来源:发表于2020-07-03 23:04 被阅读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的方法,子类如果没有特殊操作,就可以直接享受到这个方法。个人该方法用于加载字典数据到某个类还是挺方便的。

    相关文章

      网友评论

          本文标题:python元类获取__init__的默认参数,共享给class

          本文链接:https://www.haomeiwen.com/subject/pxzeqktx.html