Python 嵌套函数变量作用域

192 投票
9 回答
159198 浏览
提问于 2025-04-16 13:09

我看过几乎所有关于这个话题的问题,但我的代码还是不行。

我觉得我对Python的变量作用域理解得不太对。

这是我的代码:

PRICE_RANGES = {
                64:(25, 0.35),
                32:(13, 0.40),
                16:(7, 0.45),
                8:(4, 0.5)
                }

def get_order_total(quantity):
    global PRICE_RANGES
    _total = 0
    _i = PRICE_RANGES.iterkeys()
    def recurse(_i):
        try:
            key = _i.next()
            if quantity % key != quantity:
                _total += PRICE_RANGES[key][0]
            return recurse(_i) 
        except StopIteration:
            return (key, quantity % key)

    res = recurse(_i)

然后我得到了

"全局名称'_total'未定义"

我知道问题出在_total的赋值上,但我不明白为什么。

难道recurse()不能访问父函数的变量吗?

有人能给我解释一下我对Python变量作用域的理解有什么问题吗?

9 个回答

198

这里有一个例子,可以帮助理解大卫的回答。

def outer():
    a = 0
    b = 1

    def inner():
        print a
        print b
        #b = 4

    inner()

outer()

当你把 b = 4 这一行注释掉时,这段代码会输出 0 1,这正是你所期待的结果。

但是如果你把那一行取消注释,在 print b 这一行,你会遇到错误

UnboundLocalError: local variable 'b' referenced before assignment

看起来很神秘的是,b = 4 的存在似乎让 b 在它之前的行消失了。但大卫引用的文本解释了原因:在静态分析过程中,解释器判断 binner 函数中被赋值,因此它被认为是 inner 的局部变量。在打印那一行时,它试图打印在这个局部范围内的 b,但此时还没有给它赋值。

393

在Python 3中,你可以使用nonlocal语句来访问那些既不是本地的也不是全局的变量。

nonlocal语句的作用是让一个变量的定义绑定到最近的一个已经创建的变量上。下面有一些例子来说明这个概念:

def sum_list_items(_list):
    total = 0

    def do_the_sum(_list):
        for i in _list:
            total += i

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

上面的例子会出错,错误信息是:UnboundLocalError: local variable 'total' referenced before assignment,意思是你在使用这个变量之前没有给它赋值。

如果使用nonlocal,代码就能正常运行了:

def sum_list_items(_list):
    total = 0

    def do_the_sum(_list):

        # Define the total variable as non-local, causing it to bind
        # to the nearest non-global variable also called total.
        nonlocal total

        for i in _list:
            total += i

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

那么“最近”到底是什么意思呢?这里有另一个例子:

def sum_list_items(_list):

    total = 0

    def do_the_sum(_list):

        # The nonlocal total binds to this variable.
        total = 0

        def do_core_computations(_list):

            # Define the total variable as non-local, causing it to bind
            # to the nearest non-global variable also called total.
            nonlocal total

            for i in _list:
                total += i

        do_core_computations(_list)

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

在上面的例子中,total会绑定到do_the_sum函数内部定义的变量,而不是sum_list_items函数外部定义的变量,所以代码会返回0。需要注意的是,如果在do_the_sum中将total声明为nonlocal,那么上面的例子就会按预期工作。

def sum_list_items(_list):

    # The nonlocal total binds to this variable.
    total = 0

    def do_the_sum(_list):

        def do_core_computations(_list):

            # Define the total variable as non-local, causing it to bind
            # to the nearest non-global variable also called total.
            nonlocal total

            for i in _list:
                total += i

        do_core_computations(_list)

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

在上面的例子中,nonlocal的赋值会向上查找两层,才找到sum_list_items函数内部的total变量。

74

当我运行你的代码时,出现了这个错误:

UnboundLocalError: local variable '_total' referenced before assignment

这个问题是由这一行引起的:

_total += PRICE_RANGES[key][0]

关于作用域和命名空间的文档中提到:

Python有一个特别的特点——如果没有global语句生效,对变量的赋值总是进入最内层的作用域。赋值并不是复制数据——它只是将变量名与对象绑定在一起。

所以这行代码实际上是在说:

_total = _total + PRICE_RANGES[key][0]

它在recurse()的命名空间中创建了_total。因为_total是新的且未被赋值,所以你不能在加法中使用它。

撰写回答