我试着修改 __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__
函数在这里来实现我的Metaclasses。
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将一个函数添加到一个类中