关于范围:Python在嵌套函数中覆盖变量

Python overwriting variables in nested functions

假设我有以下python代码:

1
2
3
4
5
6
def outer():
    string =""
    def inner():
        string ="String was changed by a nested function!"
    inner()
    return string

我希望调用outer()返回"字符串已被嵌套函数更改!"但我得到了。我的结论是,python认为行string ="string was changed by a nested function!"是对inner()局部的新变量的声明。我的问题是:如何告诉python应该使用outer()字符串?我不能使用global关键字,因为字符串不是全局的,它只存在于外部范围内。思想?


在python 3.x中,可以使用nonlocal关键字:

1
2
3
4
5
6
7
def outer():
    string =""
    def inner():
        nonlocal string
        string ="String was changed by a nested function!"
    inner()
    return string

在python 2.x中,可以将列表与单个元素一起使用,并覆盖该单个元素:

1
2
3
4
5
6
def outer():
    string = [""]
    def inner():
        string[0] ="String was changed by a nested function!"
    inner()
    return string[0]


您还可以通过使用函数属性来绕过这个问题:

1
2
3
4
5
6
def outer():
    def inner():
        inner.string ="String was changed by a nested function!"
    inner.string =""
    inner()
    return inner.string

说明:这在python 2.x和3.x中都有效。


这种情况经常发生在我身上,当我编写一个函数时,我突然意识到有一个较小的助手函数可能是一个好主意,但在其他任何地方都没有真正的用处。这很自然地让我想在内部将它定义为一个嵌套函数。

但我有Java匿名对象的经验(即:定义一个Runnabl),并且规则是匿名对象对其外部环境进行硬拷贝,在这种情况下,外部范围的变量。因此,如果外部变量是不可变的(intchar,它们不能被匿名对象修改,因为它们是按值复制的;而如果它是可变的(collectionobjects,它们可以被更改……因为它们是由"指针"(它们在内存中的地址)复制的。

如果您了解编程,请将其视为传递值和传递引用。

在Python中,它是非常相同的。x=123是一个赋值,它们赋予变量x一个新的含义(不修改旧的x),list[i]/dict[key]是对象访问操作,它们真的修改了东西。

总而言之,您需要一个可变对象……以便进行修改(即使您可以使用[]访问一个元组,但您不能在此处使用它,因为它不可变)


要添加到Sven的答案中:

在python 2.x中,只能从内部范围读取外部范围变量。赋值只会创建一个隐藏外部作用域1的新本地(即内部作用域)变量。

如果要读取和修改,可以使用dict将变量保存在外部作用域中,然后通过内部作用域中的dict访问它们,同时在多个外部作用域变量存在的情况下保持代码相当干净和可读:

1
2
3
4
5
6
7
8
9
10
11
def outer():
    # hold some text, plus the number of spaces in the text
    vars = {'text': 'Some text.', 'num_spaces': 1}
    def inner():
        # add some more text
        more_text = ' Then some more text.'
        vars['text'] += more_text
        # keep track of the number of spaces
        vars['num_spaces'] += more_text.count(' ')
    inner()
    return vars['text'], vars['num_spaces']

输出:

1
2
>>> outer()
('Some text. Then some more text.', 5)