Python 嵌套函数变量范围

我已经阅读了几乎所有关于这个主题的其他问题,但是我的代码仍然不能工作。

我想我遗漏了一些关于 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 变量作用域我漏掉了什么?

132773 次浏览
>>> def get_order_total(quantity):
global PRICE_RANGES


total = 0
_i = PRICE_RANGES.iterkeys()
def recurse(_i):
print locals()
print globals()
try:
key = _i.next()
if quantity % key != quantity:
total += PRICE_RANGES[key][0]
return recurse(_i)
except StopIteration:
return (key, quantity % key)
print 'main function', locals(), globals()


res = recurse(_i)




>>> get_order_total(20)
main function {'total': 0, 'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20} {'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}


Traceback (most recent call last):
File "<pyshell#32>", line 1, in <module>
get_order_total(20)
File "<pyshell#31>", line 18, in get_order_total
res = recurse(_i)
File "<pyshell#31>", line 13, in recurse
return recurse(_i)
File "<pyshell#31>", line 13, in recurse
return recurse(_i)
File "<pyshell#31>", line 12, in recurse
total += PRICE_RANGES[key][0]
UnboundLocalError: local variable 'total' referenced before assignment
>>>

正如您所看到的,total 在 main 函数的局部作用域中,但是它不在递归的局部作用域中(很明显) ,但是它也不在全局作用域中,’因为它只在 get _ order _ total 的局部作用域中定义

当我运行你的代码时,我得到这个错误:

UnboundLocalError: local variable '_total' referenced before assignment

这个问题是由这句话引起的:

_total += PRICE_RANGES[key][0]

关于作用域和名称空间 的文档说明如下:

Python 的一个特殊之处在于——如果没有 global语句生效—— 对名称的赋值总是进入最内部的作用域。赋值不复制数据ーー它们只是将名称绑定到对象。

因此,这句话实际上是在说:

_total = _total + PRICE_RANGES[key][0]

它在 recurse()的名称空间中创建 _total。因为 _total是新的和未分配的,所以你不能在加法中使用它。

在 Python3中,可以使用 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。注意,仍然可以进行这样的双重嵌套: 如果 totaldo_the_sum中声明为 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])

在上面的示例中,非局部赋值遍历了两个级别,然后找到了 sum_list_items的局部 total变量。

这里有一个例子可以说明大卫的答案的本质。

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消失在之前的线条上,这似乎很神秘。但是 David 引用的文本解释了为什么: 在静态分析期间,解释器确定 b 在 inner中被赋值,因此它是 inner的一个局部变量。打印行试图在分配 b之前在该内部作用域中打印它。

你可能已经得到了问题的答案。但是我想指出一个方法,我通常是通过使用列表来解决这个问题的。例如,如果我想这样做:

X=0
While X<20:
Do something. ..
X+=1

相反,我会这样做:

X=[0]
While X<20:
Do something....
X[0]+=1

这样 X 永远不会是局部变量

这是 redman 解决方案的一个变体,但是使用适当的名称空间而不是数组来封装变量:

def foo():
class local:
counter = 0
def bar():
print(local.counter)
local.counter += 1
bar()
bar()
bar()


foo()
foo()

我不确定以这种方式使用类对象是否被认为是一种丑陋的黑客技术,或者在 python 社区中是一种正确的编码技术,但是它在 python 2.x 和3.x 中工作得很好(在2.7.3和3.2.3中进行了测试)。我也不确定这个解决方案的运行时效率。

从哲学的角度来看,一个答案可能是“如果您有名称空间问题,那么给它一个它自己的名称空间!”

在它自己的类中提供它不仅允许您封装问题,而且使得测试更加容易,消除了那些烦人的全局变量,并且减少了在各种顶级函数之间挖掘变量的需要(毫无疑问,不仅仅是 get_order_total)。

保留 OP 的代码,将重点放在基本的更改上,

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




def __init__(self):
self._total = None


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


res = recurse(_i)


#order = Order()
#order.get_order_total(100)

作为附言,一次黑客攻击是列表中另一个答案的变体,但也许更清楚,

def outer():
order = {'total': 0}


def inner():
order['total'] += 42


inner()


return order['total']


print outer()

与其声明一个特殊的对象、映射或数组, 也可以使用函数属性。 这使得变量的作用域非常清晰。

def sumsquares(x,y):
def addsquare(n):
sumsquares.total += n*n


sumsquares.total = 0
addsquare(x)
addsquare(y)
return sumsquares.total

当然,这个属性属于函数(定义) ,而不属于函数调用。 所以必须注意线程和递归。

虽然我过去常常使用@redman 的基于列表的方法,但就可读性而言,它并不是最佳的。

下面是修改后的@Hans 方法,只不过我使用的是内部函数的属性,而不是外部函数的属性。这应该与递归、甚至多线程更加兼容:

def outer(recurse=2):
if 0 == recurse:
return


def inner():
inner.attribute += 1


inner.attribute = 0
inner()
inner()
outer(recurse-1)
inner()
print "inner.attribute =", inner.attribute


outer()
outer()

这张照片:

inner.attribute = 3
inner.attribute = 3
inner.attribute = 3
inner.attribute = 3

如果我 s/inner.attribute/outer.attribute/g,我们得到:

outer.attribute = 3
outer.attribute = 4
outer.attribute = 3
outer.attribute = 4

因此,实际上,似乎最好将它们设置为内部函数的属性。

此外,从可读性的角度来看,这似乎是合理的: 因为这样一来,变量在概念上就与内部函数相关,而且这种表示法提醒读者,变量是在内部函数和外部函数的作用域之间共享的。可读性的一个小缺点是,inner.attribute可能只能在 def inner(): ...之后进行语法设置。

我的方式..。

def outer():


class Cont(object):
var1 = None
@classmethod
def inner(cls, arg):
cls.var1 = arg




Cont.var1 = "Before"
print Cont.var1
Cont.inner("After")
print Cont.var1


outer()