我试图获取函数的源代码,向其中添加代码,然后将其放回原始函数中。
基本上像这样:
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;
}
这对您有帮助吗? :)
答案 0 :(得分:0)
此异常是由于尝试将代码对象分配给一个函数而导致的,该函数关闭的变量数量与其来源的函数不同。如果该句子听起来像胡言乱语,那么您应该看看this answer。
避免此问题的最简单方法是简单地以明显的方式重新分配现有名称,即用f = g
代替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
实际上,如果您被成功地为带有闭包的函数完成了代码对象的重新分配,则如果执行该函数,事情很可能不会按预期进行。这是因为close结束变量与已编译代码分开保存,因此它们不匹配。
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__
属性,因为它是只读的。如果您确实有决心,大概可以work around it,但这几乎可以肯定是一个可怕的主意。
# swap closures
# this results in "AttributeError: readonly attribute"
sum_func.__closure__, cats.__closure__ = (cats.__closure__, sum_func.__closure__)
有关功能对象属性的更多详细信息,请参见this answer和the docs。