关于闭包:在Python 2中,如何在父作用域中写入变量?

In Python 2, how do I write to variable in the parent scope?

我在函数中有以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
stored_blocks = {}
def replace_blocks(m):
    block = m.group(0)
    block_hash = sha1(block)
    stored_blocks[block_hash] = block
    return '{{{%s}}}' % block_hash

num_converted = 0
def convert_variables(m):
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

fixed = MATCH_DECLARE_NEW.sub('', template)
fixed = MATCH_PYTHON_BLOCK.sub(replace_blocks, fixed)
fixed = MATCH_FORMAT.sub(convert_variables, fixed)

stored_blocks中添加元素效果很好,但在第二个子函数中不能增加num_converted

UnboundLocalError: local variable 'num_converted' referenced before assignment

我可以使用global,但是全局变量很难看,我真的不需要那个变量是全局的。

所以我很好奇,我怎么能写一个父函数范围内的变量。nonlocal num_converted可能会完成这项工作,但我需要一个与Python2.x协同工作的解决方案。


问题:这是因为python的作用域规则是疯狂的。+=赋值操作符的存在将目标num_converted标记为封闭函数作用域的局部,而在python 2.x中没有健全的方法只访问其中一个作用域级别。只有global关键字可以将变量引用从当前范围中提取出来,并且可以直接带到顶部。

修正:把num_converted变成一个单元素数组。

1
2
3
4
5
num_converted = [0]
def convert_variables(m):
    name = m.group(1)
    num_converted[0] += 1
    return '<%%= %s %%>' % name


(编辑后的答案见下文)

您可以使用如下内容:

1
2
3
4
5
6
def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted += 1
    return '<%%= %s %%>' % name

convert_variables.num_converted = 0

这样,num_converted作为convert变量方法的C类"静态"变量工作。

(编辑)

1
2
3
4
def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted = convert_variables.__dict__.get("num_converted", 0) + 1
    return '<%%= %s %%>' % name

这样,您就不需要在主过程中初始化计数器。


使用global关键字很好。如果你写:

1
2
3
4
5
6
num_converted = 0
def convert_variables(m):
    global num_converted
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

num_converted不是一个"全局变量"(也就是说,它在任何其他意想不到的地方都不可见),它只是意味着它可以在convert_variables内部进行修改。这似乎正是你想要的。

换句话说,num_converted已经是一个全局变量。所有global num_converted语法都是告诉python"在这个函数中,不要创建本地num_converted变量,而是使用现有的全局变量。


使用类实例保存状态怎么样?您实例化一个类并将实例方法传递给subs,这些函数将具有对self的引用…


我有几句话。

首先,在处理原始回调时,会出现一个此类嵌套函数的应用程序,就像在xml.parsers.expat等库中使用的那样。(图书馆作者选择这种方法可能会令人反感,但……尽管如此,还是有理由使用它。)

第二:在一个类中,有许多更好的数组替代品(num_converted[0])。我想这就是塞巴斯坦所说的。

1
2
3
4
5
6
7
class MainClass:
    _num_converted = 0
    def outer_method( self ):
        def convert_variables(m):
            name = m.group(1)
            self._num_converted += 1
            return '<%%= %s %%>' % name

在代码中值得一个注释还是很奇怪的…但是变量至少是类的局部变量。


修改自:https://stackoverflow.com/a/40690954/819544

您可以利用inspect模块访问调用作用域的globals dict并写入其中。这意味着甚至可以利用这个技巧从导入的子模块中定义的嵌套函数访问调用范围。

1
2
3
4
5
6
7
8
9
10
11
12
import inspect

def get_globals(scope_level=0):
    return dict(inspect.getmembers(inspect.stack()[scope_level][0]))["f_globals"]

num_converted = 0
def foobar():
    get_globals(0)['num_converted'] += 1

foobar()
print(num_converted)
# 1

根据需要使用scope_level论证。设置scope_level=1用于子模块中定义的函数,scope_level=2用于子模块中修饰器中定义的内部函数等。

NB:仅仅因为你能做到这一点,并不意味着你应该做到。