警告带有自由变量的每个(嵌套)函数(递归)

Warn for every (nested) function with free variables (recursively)

我想做以下事情:

for every nested function f anywhere in this_py_file:
    if has_free_variables(f):
        print warning

为什么?主要是作为 described elsewhere 的后期绑定闭包陷阱的保险。即:

>>> def outer():
...     rr = []
...     for i in range(3):
...         def inner():
...             print i
...         rr.append(inner)
...     return rr
... 
>>> for f in outer(): f()
... 
2
2
2
>>> 

每当我收到有关自由变量的警告时,我都会添加一个显式异常(在极少数情况下我会想要这种行为)或像这样修复它:

...         def inner(i=i):

然后行为变得更像 Java 中的嵌套 classes(其中要在内部 class 中使用的任何变量都必须是 final)。

(据我所知,除了解决后期绑定的问题,这也会促进更好地利用内存,因为如果一个函数"closes over"一些变量在外部范围内,那么外部范围就不能只要函数存在,就会被垃圾收集。对吧?)

我找不到任何方法来获取嵌套在其他函数中的函数。目前,我能想到的最好的方法是检测解析器,这看起来工作量很大。

对于 "get a hold" 的嵌套函数(即使您正在覆盖它们),您必须使用 eval 在每个声明中创建变量定义名称。

def outer():
     rr = []
     for i in range(3):
         eval("def inner"+str(i)+"""():
             print """+str(i))
         rr.append(eval("inner"+str(i)))
     return rr

for f in outer(): f()

打印

1
2
3

您需要 import copy 并使用 rr.append(copy.copy(inner))

https://pymotw.com/2/copy/

考虑以下函数:

def outer_func():
    outer_var = 1

    def inner_func():
        inner_var = outer_var
        return inner_var

    outer_var += 1
    return inner_func

__code__对象可用于恢复内部函数的代码对象:

outer_code = outer_func.__code__
inner_code = outer_code.co_consts[2]

从这个代码对象中,可以恢复自由变量:

inner_code.co_freevars # ('outer_var',)

您可以检查是否应检查代码对象:

hasattr(inner_code, 'co_freevars') # True

从文件中获取所有函数后,这可能类似于:

for func in function_list:
    for code in outer_func.__code__.co_consts[1:-1]:
        if hasattr(code, 'co_freevars'):
            assert len(code.co_freevars) == 0

了解更多内部工作原理的人可能会提供更好的解释或更简洁的解决方案。

我也想用 Jython 做这个。但是接受的答案中显示的方式在那里不起作用,因为 co_consts 在代码对象上不可用。 (此外,似乎没有任何其他方法可以查询代码对象以获取嵌套函数的代码对象。)

当然,代码对象就在某处,我们拥有源代码和完全访问权限,所以只需要在合理的时间内找到一种简单的方法即可。所以这是一种可行的方法。 (请坐好。)

假设我们在模块 mod:

中有这样的代码
def outer():
    def inner():
        print "Inner"

先直接获取外层函数的代码对象:

code = mod.outer.__code__

在 Jython 中,这是 PyTableCode 的实例,通过阅读源代码,我们发现实际功能是在 Java-class 中实现的您给定的脚本,由代码对象的 funcs 字段引用。 (所有这些由脚本组成的 classes 都是 PyFunctionTable 的子 classes,因此这是 funcs 的声明类型。)这在 Jython 中是不可见的,由于神奇的机器,这是设计师的说法,您访问这些东西的风险由您自己承担。

所以我们需要深入研究一下 Java。像这样的 class 就可以了:

import java.lang.reflect.Field;

public class Getter {
    public static Object getFuncs(Object o) 
    throws NoSuchFieldException, IllegalAccessException {
        Field f = o.getClass().getDeclaredField("funcs");
        f.setAccessible(true);
        return f.get(o);
    }
}

回到 Jython:

>>> import Getter
>>> funcs = Getter.getFuncs(mod.outer.__code__)
>>> funcs
mod$py@1bfa3a2

现在,这个 funcs 对象具有在 Jython 脚本中任意位置声明的所有函数(那些任意嵌套在 classes 中的函数,等等)。此外,它还有保存相应代码对象的字段。

>>> fields = funcs.class.getDeclaredFields()

在我的例子中,嵌套函数对应的代码对象恰好是最后一个:

>>> flast = fields[-1]
>>> flast
static final org.python.core.PyCode mod$py.inner

获取感兴趣的代码对象:

>>> flast.setAccessible(True)
>>> inner_code = flast.get(None)  #"None" because it's a static field.
>>> dir(inner_code)
co_argcount co_filename    co_flags co_freevars co_name co_nlocals co_varnames
co_cellvars co_firstlineno

其余与接受的答案相同,即检查 co_freevars,(与 co_consts 不同,在 Jython 中有)。

这种方法的一个好处是,您可以准确枚举在源代码文件中任何位置声明的所有代码对象:函数、方法、生成器,无论它们是否嵌套在任何事物之下或彼此之下。他们无处可藏。