我试图修改类的__init__
函数,以将其设置为继承类的属性,并能够从子类初始化继承的类。它应该像这样工作:
class B():
def __init__(self, b:str):
self.b = b
class Foo(B,metaclass=Meta):
def __init__(self,yolo, name ='empty', surname = None):
self.name = name
self.surname= surname
x = Foo('yolo',100)
print(x.b)
>>>100
如何进行?:-首先,在Metaclass
中创建两个字典__CODE__
,__ARGS__
,我将在其中收集函数的代码以及对于同一函数的实际args
的kwargs
和class
,并继承了class
中的__bases__
。-其次,我解析__init__
函数的代码,将属性替换为实际函数的属性与继承的__init__
中的class
的属性的合并。然后我将继承的supercharge
的class
作为第二行添加到其中,并通过它自己的属性。-最后,我compile
和exec
新功能的定义,然后使用setattr
覆盖实际的__init__
功能这是我的元类实现:
import re
from inspect import Parameter
#get all property of function f and return it as list and dict
def get_args(f):
args = list()
kwargs = dict()
for param in inspect.signature(f).parameters.values():
if (param.kind == param.POSITIONAL_OR_KEYWORD):
if param.default ==Parameter.empty:
args.append(param.name)
else:
kwargs[param.name]= param.default
return args, kwargs
# take a dict of kwargs and return kwargs format to pass in function as string
def compileKwargs(dct):
string =""
poke = False
for k, o in dct.items():
if type(o) == str:
string+= k+"='"+o+"', "
else:
string+= k+"="+str(o)+", "
return string
def stringArgs(liste):
return " ".join([e+"," for e in liste])
#merge args of the function 1 and the function 2
def compileArgs(liste1,liste2):
liste1.extend([e for e in liste2 if e not in liste1])
return liste1
#take a function code as string, and change its name between 'def ' and '('
def editFuncName(actual: str, replace:str):
print('EDITFUNCNAME')
print(actual)
string = re.sub('(?<=def ).*?(?=\()',replace, actual)
print('string', string)
return string
import inspect
from textwrap import dedent, indent
#re indent the code as it's class function it's once to often indented
def processCode(code : list):
string=""
#print('processcode')
for i,e in enumerate(code):
#print('row', e)
#print('dedent', e)
if i != 0:
string+=indent(dedent(e),'\t')
else :
string+=dedent(e)
return string
class Meta(type):
def __init__(cls, name, bases, dct):
#x = super().__new__(cls, name, bases, dct)
import inspect
import re
#print('name : {} \nbases : {} \ndct : {}'.format(name, bases, dct))
setattr(cls,'_CODE_', dict())
func = cls.__init__
cls._CODE_[func.__name__]= inspect.getsourcelines(func)
args2 =get_args(cls.__bases__[0].__init__)
setattr(cls,'_ARGS_', dict())
cls._ARGS_[func.__name__]=[get_args(func), args2]
#print("ARGS", cls._ARGS_)
#print('+++++++',cls._NAMI)
lines = cls._CODE_['__init__']
#print(lines)
string= lines[0][0]
arg, kwarg = cls._ARGS_['__init__'][0]
arg2, kwarg2 = cls._ARGS_['__init__'][1]
#print(kwarg, kwarg2)
#print(arg, arg2)
#print(compileArgs(arg, arg2))
comparg = stringArgs(compileArgs(arg, arg2))
dct = {**kwarg ,**kwarg2}
#print(dct)
newargs = comparg + compileKwargs(dct)
string = re.sub('(?<=\().*?(?=\))',newargs, string)
superarg =stringArgs(arg2) + compileKwargs(kwarg2)
#print(superarg)
superx = "super({},self).{}({})\n".format(cls.__name__, func.__name__, superarg)
code = lines[0]
#print('LINE DEF', code[0])
code[0]= editFuncName(string, '__init__')
code.insert(1, superx)
print('code:',code)
codestr = processCode(code)
print('précompile', codestr)
comp = compile(codestr, '<string>','exec')
print(comp)
exec(comp)
#exec(codestr)
setattr(cls, '__init__', eval('__init__'))
进行测试时,即使一切似乎都设置正确,我仍然遇到一些错误
第一次测试:
x = Foo('yolo',100)
>>>
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in
----> 1 x = Foo('e',b = 1)
in __init__(self, yolo, b, name, surname)
TypeError: __init__() takes 2 positional arguments but 3 were given
第二次测试:
x = Foo('yolo')
>>>
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in
----> 1 x = Foo('yolo')
TypeError: __init__() missing 1 required positional argument: 'b'
我也注意到我现在无法通过已覆盖的getsource
功能进行__init__
代码
print(inspect.getsource(B.__init__))
print(inspect.getsource(Foo.__init__))
>>>def __init__(self, b:str):
... self.b = b
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
in
1 print(inspect.getsource(B.__init__))
----> 2 print(inspect.getsource(Foo.__init__))
/anaconda3/envs/Demiurge/lib/python3.7/inspect.py in getsource(object)
971 or code object. The source code is returned as a single string. An
972 OSError is raised if the source code cannot be retrieved."""
--> 973 lines, lnum = getsourcelines(object)
974 return ''.join(lines)
975
/anaconda3/envs/Demiurge/lib/python3.7/inspect.py in getsourcelines(object)
953 raised if the source code cannot be retrieved."""
954 object = unwrap(object)
--> 955 lines, lnum = findsource(object)
956
957 if istraceback(object):
/anaconda3/envs/Demiurge/lib/python3.7/inspect.py in findsource(object)
784 lines = linecache.getlines(file)
785 if not lines:
--> 786 raise OSError('could not get source code')
787
788 if ismodule(object):
OSError: could not get source code
[我找到了解决方案,当我尝试在类中设置方法时,我需要将方法与自身绑定:而不是setattr(cls, '__init__', eval('__init__'))
,(如果它是在类之外完成的,则可以使用)我设置了:cls.__init__ = types.MethodType(eval('tempo'), cls)
] >
运行良好。
我的解决方案是指此exec to add a function into a class