使用exec和setattr编辑内部Metaclasse的__init__方法属性

问题描述 投票:0回答:1

我试图修改类的__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__,我将在其中收集函数的代码以及对于同一函数的实际argskwargsclass,并继承了class中的__bases__。-其次,我解析__init__函数的代码,将属性替换为实际函数的属性与继承的__init__中的class的属性的合并。然后我将继承的superchargeclass作为第二行添加到其中,并通过它自己的属性。-最后,我compileexec新功能的定义,然后使用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
python inheritance exec metaclass setattr
1个回答
0
投票

[我找到了解决方案,当我尝试在类中设置方法时,我需要将方法与自身绑定:而不是setattr(cls, '__init__', eval('__init__')),(如果它是在类之外完成的,则可以使用)我设置了:cls.__init__ = types.MethodType(eval('tempo'), cls)] >

运行良好。

我的解决方案是指此exec to add a function into a class

© www.soinside.com 2019 - 2024. All rights reserved.