Python 嵌套函数变量作用域[重复]
- 2025-03-11 08:51:00
- admin 原创
- 35
问题描述:
我已经阅读了有关该主题的几乎所有其他问题,但我的代码仍然不起作用。
我想我遗漏了一些有关 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 变量范围的知识吗?
解决方案 1:
在 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
。请注意,仍然可以进行双重嵌套,例如:如果在上面的例子中total
声明了,则会按预期工作。nonlocal
`do_the_sum`
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])
在上面的例子中,非局部赋值向上遍历两层才找到total
局部变量sum_list_items
。
解决方案 2:
下面这个例子可以体现大卫回答的本质。
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
,因此它是 的局部变量。 print 行尝试在分配之前在该内部作用域中inner
打印。b
解决方案 3:
当我运行你的代码时出现此错误:
UnboundLocalError: local variable '_total' referenced before assignment
该问题是由以下行引起的:
_total += PRICE_RANGES[key][0]
关于范围和命名空间的文档 说明了这一点:
Python 的一个特殊之处是,如果没有
global
语句生效,对名称的赋值总是进入最内层作用域。赋值不会复制数据,它们只是将名称绑定到对象。
因此,由于该行实际上是在说:
_total = _total + PRICE_RANGES[key][0]
_total
它在 的命名空间中创建recurse()
。由于_total
是新的且未分配,因此您无法在附加中使用它。
解决方案 4:
除了声明特殊对象、映射或数组外,还可以使用函数属性。这使得变量的作用域非常清晰。
def sumsquares(x,y):
def addsquare(n):
sumsquares.total += n*n
sumsquares.total = 0
addsquare(x)
addsquare(y)
return sumsquares.total
当然,这个属性属于函数(定义),而不是函数调用。因此必须注意线程和递归。
解决方案 5:
这是 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 进行测试)。我也不确定此解决方案的运行时效率。
解决方案 6:
从哲学角度来看,一个答案可能是“如果您遇到了命名空间问题,那就给它一个自己的命名空间!”
在自己的类中提供它不仅允许您封装问题,而且还使测试更容易,消除那些令人讨厌的全局变量,并且减少在各个顶级函数之间转移变量的需要(毫无疑问不仅仅如此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)
作为 PS,一个 hack 是另一个答案中列表想法的变体,但可能更清楚,
def outer():
order = {'total': 0}
def inner():
order['total'] += 42
inner()
return order['total']
print outer()
解决方案 7:
虽然我曾经使用过@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(): ...
。
解决方案 8:
我的解决方法是...
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()
解决方案 9:
>>> 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 在主函数的本地范围内,但它不在 recurse 的本地范围内(显然),也不在全局范围内,因为它仅在 get_order_total 的本地范围内定义