正确替换函数的代码obj

2024-05-20 07:16:50 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图获取一个函数的源代码,向其添加代码,然后将其放回原始函数中。在

基本上是这样的:

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中没有,那么在最后一行我得到了以下错误:

^{pr2}$

有什么想法吗?在

编辑:

我似乎找到了在CPython源代码中引发这个异常的地方(文件funcobject.c)。为了清楚起见,省略了一些行:

^{3}$

这对你有帮助吗?:)


Tags: 函数代码namenew源代码codechangedict
1条回答
网友
1楼 · 发布于 2024-05-20 07:16:50

此异常是由于试图将代码对象分配给一个函数,该函数的关闭变量数与它来自的函数不同。如果那句话听起来像胡言乱语,那么你应该看看this answer。在

避免此问题的最简单方法是简单地以明显的方式重新分配现有名称,即f = g而不是{}。通过这样做,代码对象总是与其匹配的闭包保持一致(稍后将详细介绍)。在您的例子中,这看起来像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_stuffdog具有不同数量的局部变量,我们仍然可以成功地在它们之间重新分配代码对象。在

^{pr2}$

但是,我们不能在catsdog之间重新赋值,因为cats关闭了参数{}。在

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

事实上,如果成功地为带有闭包的函数重新分配了代码对象,那么如果函数被执行,事情很可能不会如预期的那样进行。这是因为closed over变量与编译的代码分开保存,因此它们不匹配。在

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 answerthe docs。在

相关问题 更多 >