Python嵌套函数变量作用域

时间:2011-03-07 11:05:45

标签: python variables scope

我几乎阅读了有关该主题的所有其他问题,但我的代码仍无效。

我想我错过了关于python变量范围的一些内容。

这是我的代码:

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()不应该有权访问父函数的变量吗?

有人可以向我解释我对python变量范围缺少什么吗?

10 个答案:

答案 0 :(得分:128)

这是一个了解大卫答案精髓的插图。

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的局部变量。打印行尝试在分配之前在该内部范围内打印b

答案 1 :(得分:99)

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

答案 2 :(得分:52)

运行代码时出现此错误:

UnboundLocalError: local variable '_total' referenced before assignment

此问题是由以下行引起的:

_total += PRICE_RANGES[key][0]

The documentation about Scopes and Namespaces说:

  

Python的一个特殊之处在于 - 如果没有global语句生效 - 对名称的赋值总是进入最内层范围。分配不复制数据 - 它们只是将名称绑定到对象。

因为这条线有效地说:

_total = _total + PRICE_RANGES[key][0]

它在_total的命名空间中创建recurse()。由于_total是新的且未分配,因此您无法在添加中使用它。

答案 3 :(得分:29)

而不是声明特殊对象或地图或数组, 一个人也可以使用一个函数属性。 这使得变量的范围非常明确。

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

  sumsquares.total = 0
  addsquare(x)
  addsquare(y)
  return sumsquares.total

当然这个属性属于函数(defintion),而不属于函数调用。 所以必须注意线程和递归。

答案 4 :(得分:16)

这是redman的解决方案的变体,但使用适当的命名空间而不是数组来封装变量:

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

答案 5 :(得分:7)

您可能已经得到了问题的答案。但我想指出一种方式,我通常使用列表来解决这个问题。例如,如果我想这样做:

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

我会这样做:

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

这样X从不是局部变量

答案 6 :(得分:3)

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

这是一个修改过的@Hans&#39;方法,除了我使用内部函数的属性,而不是外部。这应该与递归更兼容,甚至可能是多线程:

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

所以,确实,将它们作为内在功能的属性似乎更好。

此外,在可读性方面似乎也是明智的:因为变量在概念上与内部函数有关,并且这种符号提醒读者变量在内部函数和外部函数的范围之间共享。可读性略有下降,inner.attribute只能在def inner(): ...之后语法设置。

答案 7 :(得分:2)

从哲学的角度来看,更多的答案可能是“如果你遇到命名空间问题,请给它自己的命名空间!”

在它自己的类中提供它不仅允许你封装问题,而且还使测试更容易,消除那些讨厌的全局变量,并减少在各种顶级函数之间铲除变量的需要(毫无疑问,它将超过只是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,一个hack是另一个答案中列表想法的变体,但也许更清楚,

def outer():
  order = {'total': 0}

  def inner():
    order['total'] += 42

  inner()

  return order['total']

print outer()

答案 8 :(得分:0)

>>> 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位于main函数的本地范围内,但它不在recurse的本地范围内(显然),但它不在全局范围内,因为它仅在get_order_total的本地范围内定义

答案 9 :(得分:-1)

我的方式......

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