用exec和setattr编辑__init__方法的属性到Metaclasse。

问题描述 投票: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__,在这里我收集了函数的代码,对于同一个函数,我的名字是 argskwargs实际的 class 和遗传的 class__bases__ . - 第二,我解析了 __init__ 函数的属性,我用实际函数的属性和函数中的属性合并后的属性代替。__init__在固有的 class然后我在第二行添加 supercharge继承的 class在那里,我通过自己的财产。 - 最后,我 compileexec新函数的定义,然后我用 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
python inheritance exec metaclass setattr
1个回答
0
投票

我找到了解决方法,当我试图在类内部设置方法时,我需要将方法与自身绑定:而不是将其与类的属性绑定。setattr(cls, '__init__', eval('__init__')) (如果是在类外完成的,那是可以的)我设置了 。cls.__init__ = types.MethodType(eval('tempo'), cls)

它工作得很完美。

我的解决方案是指这个。exec将一个函数添加到一个类中

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