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_total
variables的需求(毫无疑问,不仅仅是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()