Python nested functions variable scoping
我已经阅读了关于这个主题的几乎所有其他问题,但是我的代码仍然不起作用。
我想我遗漏了一些关于python变量范围的内容。
这是我的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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) |
我得到
"global name '_total' is not defined"
我知道问题出在
有人能给我解释一下关于python变量范围我遗漏了什么吗?
下面是一个说明大卫答案的本质的例子。
1 2 3 4 5 6 7 8 9 10 11 12 | def outer(): a = 0 b = 1 def inner(): print a print b #b = 4 inner() outer() |
当语句
但是如果您取消对该行的注释,在
1 | UnboundLocalError: local variable 'b' referenced before assignment |
似乎很神秘的是,
在python 3中,可以使用
当我运行你的代码时,我会得到这个错误:
1 | UnboundLocalError: local variable '_total' referenced before assignment |
此问题由以下行引起:
1 | _total += PRICE_RANGES[key][0] |
有关范围和名称空间的文档说明了这一点:
A special quirk of Python is that – if no
global statement is in effect – assignments to names always go into the innermost scope. Assignments do not copy data — they just bind names to objects.
因此,由于该行实际上是在说:
1 | _total = _total + PRICE_RANGES[key][0] |
它在
而不是声明一个特殊的对象、映射或数组,还可以使用函数属性。这使得变量的作用域非常清楚。
1 2 3 4 5 6 7 8 | def sumsquares(x,y): def addsquare(n): sumsquares.total += n*n sumsquares.total = 0 addsquare(x) addsquare(y) return sumsquares.total |
当然,这个属性属于函数(定义),而不是函数调用。所以必须注意线程和递归。
这是Redman解决方案的一个变体,但使用适当的命名空间而不是数组来封装变量:
1 2 3 4 5 6 7 8 9 10 11 12 | def foo(): class local: counter = 0 def bar(): print(local.counter) local.counter += 1 bar() bar() bar() foo() foo() |
我不确定用这种方式使用类对象在Python社区中是否被认为是一种丑陋的黑客攻击或适当的编码技术,但是它在Python2.x和3.x中运行良好(用2.7.3和3.2.3测试)。我也不确定这个解决方案的运行时效率。
你可能已经得到了问题的答案。但我想指出一种我通常能够解决这个问题的方法,那就是使用列表。例如,如果我想这样做:
1 2 3 4 | X=0 While X<20: Do something. .. X+=1 |
我宁愿这样做:
1 2 3 4 | X=[0] While X<20: Do something.... X[0]+=1 |
这样x就永远不是局部变量
虽然我曾经使用过@redman的基于列表的方法,但它在可读性方面并不理想。
这里有一个修改过的@hans'方法,只是我使用了内部函数的属性,而不是外部函数的属性。这应该更兼容递归,甚至多线程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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() |
印刷品:
1 2 3 4 | inner.attribute = 3 inner.attribute = 3 inner.attribute = 3 inner.attribute = 3 |
如果我是
1 2 3 4 | outer.attribute = 3 outer.attribute = 4 outer.attribute = 3 outer.attribute = 4 |
因此,实际上,最好将它们作为内部函数的属性。
此外,在可读性方面似乎是合理的:因为变量在概念上与内部函数相关,而这个符号提醒读者,变量在内部函数和外部函数的范围之间是共享的。可读性的一个轻微的缺点是,
从哲学的角度来看,一个答案可能是"如果你有名称空间问题,给它一个自己的名称空间!"
在自己的类中提供它不仅允许您封装问题,而且使测试更容易,消除了那些烦人的全局,减少了在各种顶级函数之间挖掘变量的需要(毫无疑问,这不仅仅是
保留操作代码以关注基本更改,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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在另一个答案中是列表思想的变体,但可能更清楚,
1 2 3 4 5 6 7 8 9 10 11 | def outer(): order = {'total': 0} def inner(): order['total'] += 42 inner() return order['total'] print outer() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | >>> 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的本地范围内定义。
我的方法…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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() |