关于python:使用自由变量警告每个(嵌套)函数(递归)

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

我想做以下工作:

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

为什么?主要是作为针对后期约束关闭的保险,如别处所述。即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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
>>>

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

1
...         def inner(i=i):

然后,行为变得更像Java中的嵌套类(在内部类中使用的任何变量都必须是EDCOX1(0))。

(据我所知,除了解决后期绑定问题之外,这还将促进更好地使用内存,因为如果函数"关闭"了外部作用域中的某些变量,那么只要函数存在,外部作用域就不能被垃圾收集。对吗?)

我找不到任何方法来获取嵌套在其他函数中的函数。目前,我能想到的最好的方法是插入一个解析器,这看起来是一项非常多的工作。


考虑以下功能:

1
2
3
4
5
6
7
8
9
def outer_func():
    outer_var = 1

    def inner_func():
        inner_var = outer_var
        return inner_var

    outer_var += 1
    return inner_func

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

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

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

1
inner_code.co_freevars # ('outer_var',)

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

1
hasattr(inner_code, 'co_freevars') # True

从文件中获取所有函数后,可能会出现以下情况:

1
2
3
4
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中有这样的代码:

1
2
3
def outer():
    def inner():
        print"Inner"

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

1
code = mod.outer.__code__

在Jython中,这是一个PyTableCode的实例,通过读取源代码,我们发现实际函数是在由给定的脚本编写的Java类中实现的,这是由代码对象的EDCOX1字段7引用字段引用的。(所有这些由脚本组成的类都是PyFunctionTable的子类,因此这是声明的funcs的类型。)从Jython内部看不到这一点,因为魔幻机器是一个设计师的说法,即您要自担风险访问这些东西。

所以我们需要潜入Java。像这样的课程可以做到:

1
2
3
4
5
6
7
8
9
10
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:

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

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

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

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

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

要获取感兴趣的代码对象:

1
2
3
4
5
>>> 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,(Jython有,不像co_consts)。

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


要"保留"嵌套函数(即使您正在重写它们),您必须使用eval在每个声明上创建变量定义名称。

1
2
3
4
5
6
7
8
9
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
1
2
3


你需要使用import copyrr.append(copy.copy(inner))

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