Python嵌套函数variables范围

我已经阅读了几乎所有关于该主题的其他问题,但是我的代码仍然无法正常工作。

我想我错过了一些关于pythonvariables作用域的东西。

这是我的代码:

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()有权访问父函数的variables?

有人可以向我解释我错过了有关pythonvariables作用域?

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

 UnboundLocalError: local variable '_total' referenced before assignment 

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

 _total += PRICE_RANGES[key][0] 

有关范围和命名空间的文档说:

Python的一个特别的问题是,如果没有global语句生效, 对名称的分配总是进入最内层的范围 。 分配不会复制数据 – 它们只是将名称绑定到对象。

所以,因为这条线有效地说:

 _total = _total + PRICE_RANGES[key][0] 

它会在recurse()的命名空间中创build_total 。 由于_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的局部variables。 打印行尝试在分配之前在内部范围中打印b

在Python 3中,可以使用nonlocal语句来访问非本地非全局范围。

也可以使用函数属性,而不是声明特殊的对象或映射或数组。 这使variables的范围真的很清楚。

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

当然,这个属性属于函数(defintion),而不是函数调用。 所以一定要注意线程和recursion。

这是redman解决scheme的变体,但是使用适当的名称空间而不是数组来封装variables:

 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 )。 我也不确定这个解决scheme的运行时效率。

你可能已经得到了你的问题的答案。 但是我想表明一种方法,我无意中解决了这个问题,那就是使用列表。 例如,如果我想这样做:

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

我会做这个:

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

这样X从来就不是局部variables

 >>> 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 >>> 

如你所见,总是在主函数的本地范围内,但它不在局部范围内(明显),但它也不在全局范围内,因为它只在get_order_total

更多从哲学的angular度来看,一个答案可能是“如果你有命名空间问题,给它一个它自己的命名空间!”

在它自己的类中提供它不仅可以让你封装问题,而且使testing变得更容易,消除了那些烦人的全局variables,并且减less了在各种顶层函数之间get_order_totalvariables的需求(毫无疑问,不仅仅是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,在另一个答案中是一个在列表中的变种,但也许更清晰,

 def outer(): order = {'total': 0} def inner(): order['total'] += 42 inner() return order['total'] print outer() 

虽然我曾经使用@ redman的基于列表的方法,但在可读性方面并不理想。

这是一个修改的@Hans的方法,除了我使用内部函数的属性,而不是外部。 这应该更加兼容recursion,甚至可能是multithreading:

 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 

所以,把它们作为内在function的属性似乎更好。

而且,从可读性的angular度来看似乎是明智的:因为这个variables在概念上与内部函数有关,这个符号提醒读者,variables是在内部函数和外部函数的范围之间共享的。 可读性的一个小缺点是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()