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

时间:2016-05-03 02:44:58

标签: python reflection closures nested-function

我想做以下事情:

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中的嵌套类(其中任何要在内部类中使用的变量必须是final)。

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

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

4 个答案:

答案 0 :(得分:2)

考虑以下功能:

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

对内部运作有更多了解的人可能会提供更好的解释或更简洁的解决方案。

答案 1 :(得分:0)

要“抓住”嵌套函数(即使你要覆盖它们),你必须使用eval在每个声明上创建变量定义名称。

JALR

打印

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()

答案 2 :(得分:0)

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

但是,当然,代码对象存在于某个地方,我们拥有源代码和完全访问权限,所以它只是在合理的时间内找到一种简单的方法。所以这是一种有效的方法。 (等你的座位。)

假设我们在模块mod中有这样的代码:

def outer():
    def inner():
        print "Inner"

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

code = mod.outer.__code__

在Jython中,这是PyTableCode的一个实例,通过阅读源代码,我们发现实际的函数是在由给定脚本构成的Java类中实现的,该脚本由代码引用对象的funcs字段。 (所有这些由脚本构成的类都是PyFunctionTable的子类,因此它是funcs的声明类型。)由于魔法,这在Jython中是不可见的机器是一种设计师的方式,说明你自己承担这些风险。

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

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脚本中任意位置声明的所有函数(任意嵌套,类内等)。此外,它还包含保存相应代码对象的字段。

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

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

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

获取感兴趣的代码对象:

>>> 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不同)。

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

答案 3 :(得分:-1)

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

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