【问题标题】:Python nested functions variable scoping [duplicate]Python嵌套函数变量范围[重复]
【发布时间】:2011-07-10 06:52:52
【问题描述】:

我已经阅读了有关该主题的几乎所有其他问题,但我的代码仍然无法正常工作。

我想我遗漏了一些关于 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 变量作用域的遗漏吗?

【问题讨论】:

  • 这不是对您的实际问题的回答,只是说明您的整个函数可以写成return sum(lower for (key, (lower, upper)) in PRICE_RANGES.iteritems() if quantity % key != quantity)
  • 其实仔细一看,你返回的是(key, quantity % key),所以我贴的部分只是计算_total的方法。您的函数似乎总是返回最后访问的键 - 您可能认为字典项的顺序将被保留,但事实并非如此,因此您的函数的最终返回值有些随机。

标签: python variables scope


【解决方案1】:

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

nonlocal 语句使变量定义绑定到最近范围内先前创建的变量。下面举几个例子来说明:

def sum_list_items(_list):
    total = 0

    def do_the_sum(_list):
        for i in _list:
            total += i

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

上面的例子会失败,错误为:UnboundLocalError: local variable 'total' referenced before assignment

使用nonlocal我们可以让代码工作:

def sum_list_items(_list):
    total = 0

    def do_the_sum(_list):

        # Define the total variable as non-local, causing it to bind
        # to the nearest non-global variable also called total.
        nonlocal total

        for i in _list:
            total += i

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

但是“最近”是什么意思?这是另一个例子:

def sum_list_items(_list):

    total = 0

    def do_the_sum(_list):

        # The nonlocal total binds to this variable.
        total = 0

        def do_core_computations(_list):

            # Define the total variable as non-local, causing it to bind
            # to the nearest non-global variable also called total.
            nonlocal total

            for i in _list:
                total += i

        do_core_computations(_list)

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

在上面的例子中,total 将绑定到 do_the_sum 函数内部定义的变量,而不是 sum_list_items 函数中定义的外部变量,因此代码将返回 0

def sum_list_items(_list):

    # The nonlocal total binds to this variable.
    total = 0

    def do_the_sum(_list):

        def do_core_computations(_list):

            # Define the total variable as non-local, causing it to bind
            # to the nearest non-global variable also called total.
            nonlocal total

            for i in _list:
                total += i

        do_core_computations(_list)

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

在上面的示例中,非本地赋值在找到 sum_list_items 本地的 total 变量之前遍历了两个级别。

【讨论】:

  • 这是一个非常重要的贡献!我不知道这个说法。感谢您指出。也许您可以扩展您的答案以包含一个示例。
  • 如果您愿意,欢迎您编辑。
  • 这真的很有帮助。非本地为我工作
  • 这确实是现在的正确答案。 Python 2 在 2020 年初就已经死了,所以 Python3 特定的答案现在几乎适用于所有人。
  • 这应该是标记的答案
【解决方案2】:

我的路……

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

【讨论】:

    【解决方案3】:

    这里有一个说明大卫回答的精髓。

    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 我很困惑,但现在我明白会发生什么了。我是一名 c# 程序员,每次我开始喜欢 Python 时,都会出现这样的事情,并毁掉它。
    • Python 如此工作是有充分理由的。
    • @sudo 你能详细说明那条评论吗?
    • @radpotato 哎呀,我想我偶然评论了错误的答案。我不记得我打算把评论放在哪里。无论如何,我不知道这是多么引人注目,但问题是 b 否则会在函数内将范围从“全局”更改为“本地”。 Python 不仅在低级别上似乎不支持这一点,而且会令人困惑和不必要。
    【解决方案4】:

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

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

    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(): ... 之后。

    【讨论】:

      【解决方案5】:

      与其声明一个特殊的对象或映射或数组, 也可以使用函数属性。 这使得变量的范围非常清晰。

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

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

      【讨论】:

      • 对于python 2,只要注意上面的免责声明,这应该是公认的答案。我发现它甚至允许你嵌套一个可以操纵外部函数状态的信号处理程序——不过我不建议这样做——这只是重构一些到处都有全局变量的可怕遗留代码的第一步。现在再次重构并以正确的方式进行......
      • 虽然这对于重构可怕的全球负载代码可能非常有用,但如果您正在编写自己的代码并考虑到这一点,您可能应该将其设为类。但是,这肯定是对随机全局变量的改进。并且对于一些静态值应该是首选。
      【解决方案6】:

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

      在自己的类中提供它不仅可以让您封装问题,还可以简化测试,消除那些讨厌的全局变量,并减少在各种顶级函数之间铲除变量的需要(毫无疑问会有更多只是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()
      

      【讨论】:

        【解决方案7】:

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

        def foo():
            class local:
                counter = 0
            def bar():
                print(local.counter)
                local.counter += 1
            bar()
            bar()
            bar()
        
        foo()
        foo()
        

        我不确定以这种方式使用类对象在 python 社区中是否被认为是一种丑陋的 hack 或正确的编码技术,但它在 python 2.x 和 3.x 中运行良好(用 2.7.3 和3.2.3)。我也不确定这个解决方案的运行时效率。

        【讨论】:

        • 当本地方法有很多局部变量要修改但我也想知道“在 python 社区中以这种方式使用类对象是否被认为是丑陋的黑客或正确的编码技术时”跨度>
        【解决方案8】:

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

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

        我会这样做:

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

        这样 X 永远不是局部变量

        【讨论】:

        • 不要,请不要。这是一个可怕且低效的解决方案。
        • 有多可怕?什么是 bettwr 解决方案?
        • 我不太喜欢它,但您是唯一提供解决方法的人。在某种程度上,X 变成了一个中间命名空间...
        • 请注意while 共享其容器的命名空间。因此,它不适合您的示例。
        【解决方案9】:

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

        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]
        

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

        【讨论】:

          【解决方案10】:
          >>> 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 在主函数的本地范围内,但它不在递归的本地范围内(显然),但也不在全局范围内,因为它仅在 get_order_total 的本地范围内定义

          【讨论】:

            猜你喜欢
            • 2016-09-25
            • 1970-01-01
            • 2019-08-21
            • 1970-01-01
            • 1970-01-01
            • 2013-05-03
            • 1970-01-01
            • 1970-01-01
            • 2015-07-02
            相关资源
            最近更新 更多