Я делаю декоратор, который позволяет мне запускать функции так, как будто их строки кода написаны в main. Для этого я использую globals().update(vars()) внутри функции (работает), но теперь внутри декоратора это не удается.

class Debugger:
    @staticmethod
    def in_main(func):  # a decorator
        def wrapper():  # a wrapper that remembers the function func because it was in the closure when construced.
            exec(Debugger.convert_to_global(func))  # run the function here.
            globals().update(vars())  # update the global variables with whatever was defined as a result of the above.
        return wrapper


    @staticmethod
    def convert_to_global(name):
        """Takes in a function name, reads it source code and returns a new version of it that can be run in the main.
        """
        import inspect
        import textwrap

        codelines = inspect.getsource(name)
        # remove def func_name() line from the list
        idx = codelines.find("):\n")
        header = codelines[:idx]
        codelines = codelines[idx + 3:]

        # remove any indentation (4 for funcs and 8 for classes methods, etc)
        codelines = textwrap.dedent(codelines)

        # remove return statements
        codelines = codelines.split("\n")
        codelines = [code + "\n" for code in codelines if not code.startswith("return ")]

        code_string = ''.join(codelines)  # convert list to string.

        temp = inspect.getfullargspec(name)
        arg_string = """"""
        # if isinstance(type(name), types.MethodType) else tmp.args
        if temp.defaults:  # not None
            for key, val in zip(temp.args[1:], temp.defaults):
                arg_string += f"{key} = {val}\n"
        if "*args" in header:
            arg_string += "args = (,)\n"
        if "**kwargs" in header:
            arg_string += "kwargs = {}\n"
        result = arg_string + code_string
        return result  # ready to be run with exec()

Пример воспроизводимого отказа:


@Debugger.in_main
def func():
    a = 2
    b = 22

func()

print(a)

Дает

NameError: name 'a' is not defined
1
Alex Deft 4 Дек 2020 в 08:32

1 ответ

Лучший ответ
  1. Я не уверен, что вы пытаетесь сделать. Но возня с областью видимости и инкапсуляцией переменных может привести к очень плохому, непредсказуемому и не поддающемуся исправлению поведению. Я ЭТО СТРОГО НЕ РЕКОМЕНДАЛ .

  2. Теперь к вашей проблеме:

globals() - непростой вопрос, так как нет единого словаря globals().

Вы можете легко проверить это с помощью print(id(globals()) и посмотреть.

В вашем примере работает изменение модуля __main__ __dict__ с результатами exec.

Это делает exec(the_code_you_want_to_run, sys.modules['__main__'].__dict__. globals().update(vars()) не требуется.

Вот код:

import sys
class Debugger:
    @staticmethod
    def in_main(func):  # a decorator
        def wrapper():  # a wrapper that remembers the function func because it was in the closure when construced.
            exec(Debugger.convert_to_global(func), sys.modules['__main__'].__dict__)  # run the function here on the scope of __main__ __dict__
            # globals().update(vars())  # NOT NEEDED
        return wrapper


    @staticmethod
    def convert_to_global(name):
        """Takes in a function name, reads it source code and returns a new version of it that can be run in the main.
        """
        import inspect
        import textwrap

        codelines = inspect.getsource(name)
        # remove def func_name() line from the list
        idx = codelines.find("):\n")
        header = codelines[:idx]
        codelines = codelines[idx + 3:]

        # remove any indentation (4 for funcs and 8 for classes methods, etc)
        codelines = textwrap.dedent(codelines)

        # remove return statements
        codelines = codelines.split("\n")
        codelines = [code + "\n" for code in codelines if not code.startswith("return ")]

        code_string = ''.join(codelines)  # convert list to string.

        temp = inspect.getfullargspec(name)
        arg_string = """"""
        # if isinstance(type(name), types.MethodType) else tmp.args
        if temp.defaults:  # not None
            for key, val in zip(temp.args[1:], temp.defaults):
                arg_string += f"{key} = {val}\n"
        if "*args" in header:
            arg_string += "args = (,)\n"
        if "**kwargs" in header:
            arg_string += "kwargs = {}\n"
        result = arg_string + code_string
        return result  # ready to be run with exec()
1
Lior Cohen 4 Дек 2020 в 17:20