Отредактируйте свойства метода __init__ в Metaclasse с помощью exe c и setattr - PullRequest
0 голосов
/ 28 мая 2020

Я пытаюсь изменить __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 я создаю два dict __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

1 Ответ

0 голосов
/ 29 мая 2020

Я нашел решение, поскольку я пытаюсь установить метод внутри класса, мне нужно связать метод с самим собой: вместо setattr(cls, '__init__', eval('__init__')) (который работает, если он выполняется вне класса), я установил: cls.__init__ = types.MethodType(eval('tempo'), cls)

Он работает отлично.

Мое решение относится к этому: exe c для добавления функции в класс

...