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

图片发自简书App

获取__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的方法,子类如果没有特殊操作,就可以直接享受到这个方法。个人该方法用于加载字典数据到某个类还是挺方便的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352