python元类获取__init__的默认参数,共享给class
2020-07-03 本文已影响0人
超神雷鸣
python元类获取__init__的默认参数,共享给classmethod
获取__init__的默认参数,并在classmethod方法中为没有给定的属性赋默认值,提升代码的健壮性
#!/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)
#!/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
#!/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}
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语句体中的全部类属性
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的方法,子类如果没有特殊操作,就可以直接享受到这个方法。个人该方法用于加载字典数据到某个类还是挺方便的。