Проблема при изменении функции, которая закрывает какую-то переменную.Старое закрытие не подходит.Пустое закрытие сбивает интерпретатор - PullRequest
0 голосов
/ 20 сентября 2018

Я хочу динамически добавить аргументы в функцию run_func, которая вызывает другую функцию.

Если я не копирую объект __closure__ из run_func в modified_func, Python падает, как толькоЯ пытаюсь вызвать modified_func.

. Если я пытаюсь скопировать старый объект замыкания в измененную функцию, он не подходит: "#ValueError: run_func требует замыкания длины 0, а не 1"

Как правильно выполнить модификацию, чтобы измененная функция не вылетала?

import types

def make_facade(func, param_names):
    def run_func():
        return func()

    code = run_func.__code__
    modified_code = types.CodeType(
        len(param_names), #code.co_argcount
        code.co_kwonlyargcount,
        len(param_names), #code.co_nlocals
        code.co_stacksize,
        code.co_flags,
        code.co_code,
        code.co_consts,
        code.co_names,
        tuple(param_names), #code.co_varnames
        code.co_filename,
        code.co_name,
        code.co_firstlineno,
        code.co_lnotab
    )

    #modified_func = types.FunctionType(modified_code, run_func.__globals__, closure=run_func.__closure__) #ValueError: run_func requires closure of length 0, not 1
    modified_func = types.FunctionType(modified_code, run_func.__globals__)

    return modified_func

def ffff(dict):
    print(dict)

ffff_facade = make_facade(ffff, ['arg1', 'arg2'])

help(ffff_facade)    #Help on function run_func in module __main__:    run_func(arg1, arg2)

ffff_facade(1, 2) #Crash

1 Ответ

0 голосов
/ 20 сентября 2018

Самый простой способ добавить параметры в функцию - это заменить ее другой функцией, которая принимает *args и / или **kwargs.Затем вы также можете присвоить фальшивый Signature атрибуту новой функции __signature__, чтобы получить хороший вывод из функции help.


Код:

import functools
import inspect


def add_parameters_to_signature(signature, param_names):
    params = list(signature.parameters.values())

    for param_name in param_names:
        param = inspect.Parameter(param_name, inspect.Parameter.POSITIONAL_OR_KEYWORD)
        params.append(param)

    return signature.replace(parameters=params)

def add_parameters(func, param_names):
    signature = inspect.signature(func)
    signature = add_parameters_to_signature(signature, param_names)

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        bound_args = signature.bind(*args, **kwargs)

        # now we can do something with the arguments we received, for example
        # pass them to the wrapped function:
        print('Received positional arguments:', bound_args.args)
        print('Received keyword arguments:', bound_args.kwargs)
        return func(bound_args.arguments['foo'])

    # assign the new signature to the function's __signature__ attribute so that
    # functions like `help` and `inspect.signature` can see it
    wrapper.__signature__ = signature

    return wrapper


def print_foo(foo):
    print('Received foo:', foo)

facade = add_parameters(print_foo, ['arg1', 'arg2'])

facade('foo', 1, 2)
# Output:
# Received positional arguments: ('foo', 1, 2)
# Received keyword arguments: {}
# Received foo: foo

Справочник по функциям и классам:

  • inspect.signature используется для получения подписи завернутой функции
  • inspect.Parameter создаются для изменения полученной подписи
  • Signature.bind используется для сопоставления полученных *args и **kwargs с параметрами функции
...