python嵌套函数变量范围

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"

我知道问题出在_total作业上,但我不明白为什么。recurse()不应该访问父函数的变量吗?

有人能给我解释一下关于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()

当语句b = 4被注释掉后,这个代码输出0 1,正如您所期望的那样。

但是如果您取消对该行的注释,在print b行上,您会得到错误

1
UnboundLocalError: local variable 'b' referenced before assignment

似乎很神秘的是,b = 4的存在可能会以某种方式使b消失在它前面的线条上。但是大卫引用的文本解释了为什么:在静态分析中,解释器确定b在inner中被赋值,因此它是inner的局部变量。打印行试图在分配之前在该内部作用域中打印b


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


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

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]

它在recurse()的名称空间中创建_total。由于_total是新的,并且没有被分配,所以不能再使用它。


而不是声明一个特殊的对象、映射或数组,还可以使用函数属性。这使得变量的作用域非常清楚。

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

如果我是s/inner.attribute/outer.attribute/g,我们得到:

1
2
3
4
outer.attribute = 3
outer.attribute = 4
outer.attribute = 3
outer.attribute = 4

因此,实际上,最好将它们作为内部函数的属性。

此外,在可读性方面似乎是合理的:因为变量在概念上与内部函数相关,而这个符号提醒读者,变量在内部函数和外部函数的范围之间是共享的。可读性的一个轻微的缺点是,inner.attribute只能在def inner(): ...之后按语法设置。


从哲学的角度来看,一个答案可能是"如果你有名称空间问题,给它一个自己的名称空间!"

在自己的类中提供它不仅允许您封装问题,而且使测试更容易,消除了那些烦人的全局,减少了在各种顶级函数之间挖掘变量的需要(毫无疑问,这不仅仅是get_order_total)。

保留操作代码以关注基本更改,

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