Правильно заменить объект кода функции - PullRequest
0 голосов
/ 09 февраля 2019

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

В основном так:

new_code = change_code(original_code)
throwaway_module = ModuleType('m')
exec(new_code, throwaway_module.__dict__)
func.__code__ = getattr(throwaway_module, func.__name__).__code__

Этоотлично работает, когда new_code не содержит имени, которого не было в исходной функции.

Однако, когда new_code содержит имя переменной, которого не было в исходной func, тогдаВ последней строке я получаю следующую ошибку:

ValueError: func() requires a code object with 1 free vars, not 0

Есть идеи?

РЕДАКТИРОВАТЬ:

Кажется, я нашел, где в исходном коде CPython это исключение возникает(файл funcobject.c).Для ясности пропущено несколько строк:

static int
func_set_code(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored))
{
    Py_ssize_t nfree, nclosure;

    // ... lines omitted

    nfree = PyCode_GetNumFree((PyCodeObject *)value);
    nclosure = (op->func_closure == NULL ? 0 :
            PyTuple_GET_SIZE(op->func_closure));
    if (nclosure != nfree) {
        PyErr_Format(PyExc_ValueError,
                     "%U() requires a code object with %zd free vars,"
                     " not %zd",
                     op->func_name,
                     nclosure, nfree);
        return -1;
    }
    Py_INCREF(value);
    Py_XSETREF(op->func_code, value);
    return 0;
}

Поможет ли это мне?:)

1 Ответ

0 голосов
/ 12 февраля 2019

Это исключение связано с попыткой присвоить объект кода функции, которая закрывается по другому количеству переменных, чем функция, из которой она вышла.Если это предложение звучит как бред, то вам стоит взглянуть на этот ответ .

Самый простой способ избежать этой проблемы - просто переназначить существующее имя очевидным образом, то есть * 1005.* вместо f.__code__ = g.__code__.Делая это таким образом, объект кода всегда остается с соответствующим ему закрытием (подробнее об этом позже).В вашем случае это выглядело бы как func = getattr(throwaway_module, func.__name__).Есть ли какая-то причина, по которой вы не можете этого сделать и вместо этого копаетесь в деталях внутренней реализации?

Чтобы лучше проиллюстрировать, что здесь происходит, предположим, у нас есть несколько глупых функций.

def dog():
    return "woof"

def cat():
    return "meow"

def do_stuff(seq):
    t1 = sum(seq)
    seq2 = [e + t1 for e in seq]
    t2 = sum(seq2)
    return t1 + t2

def pair(animal):
    def ret():
        return animal() + animal()
    return ret

cats = pair(cat)

print(dog()) # woof
print(cat()) # meow
print(cats()) # meowmeow
print(do_stuff([1,2,3])) # 30

Даже если do_stuff имеет число локальных переменных, отличное от dog, мы все же можем успешно переназначить объекты кода между ними.

do_stuff.__code__ = dog.__code__
print(do_stuff()) # woof

Однако мы не можем переназначить между cats и dog потому что cats закрывает аргумент animal.

print(cats.__code__.co_freevars) # ('animal',)
dog.__code__ = cats.__code__

ValueError: dog() requires a code object with 0 free vars, not 1

Эту проблему можно избежать, просто переназначив имя нужному функциональному объекту.

dog = cats
print(dog()) # meowmeow

Фактически, если бы вы были , чтобы успешно выполнить переназначение объекта кода для функции с замыканием, скорее всего, все было бы не так, как ожидалось, если бы функция была выполнена.Это потому, что закрытые переменные сохраняются отдельно от скомпилированного кода, поэтому они не будут совпадать.

def get_sum_func(numbers):
    def ret():
        return sum(numbers)
    return ret

sum_func = get_sum_func([2,2,2]) # sum_func closes over the provided arg

# swap code objects
# quite possibly the most disturbing single line of python I've ever written
sum_func.__code__, cats.__code__ = (cats.__code__, sum_func.__code__)

print(sum_func()) # this will attempt to execute numbers() + numbers(), which will throw
print(cats()) # this will attempt to execute sum(animal), which will throw

Как оказалось, мы не можем легко заменить атрибут __closure__, потому что онтолько для чтения.Вы могли бы предположительно обойти это , если вы действительно были настроены, но это почти наверняка ужасная идея.

# swap closures
# this results in "AttributeError: readonly attribute"
sum_func.__closure__, cats.__closure__ = (cats.__closure__, sum_func.__closure__)

Для получения дополнительной информации об атрибутах объекта функции см. этот ответ и документы .

...