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