Python 嵌套函数变量作用域[重复]

2025-03-11 08:51:00
admin
原创
35
摘要:问题描述:我已经阅读了有关该主题的几乎所有其他问题,但我的代码仍然不起作用。我想我遗漏了一些有关 python 变量范围的知识。这是我的代码:PRICE_RANGES = { 64:(25, 0.35), 32:(13, 0.40), ...

问题描述:

我已经阅读了有关该主题的几乎所有其他问题,但我的代码仍然不起作用。

我想我遗漏了一些有关 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 变量范围的知识吗?


解决方案 1:

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

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。请注意,仍然可以进行双重嵌套,例如:如果在上面的例子中total声明了,则会按预期工作。nonlocal`do_the_sum`

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

在上面的例子中,非局部赋值向上遍历两层才找到total局部变量sum_list_items

解决方案 2:

下面这个例子可以体现大卫回答的本质。

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,因此它是 的局部变量。 print 行尝试在分配之前在该内部作用域中inner打印。b

解决方案 3:

当我运行你的代码时出现此错误:

UnboundLocalError: local variable '_total' referenced before assignment

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

_total += PRICE_RANGES[key][0]

关于范围和命名空间的文档 说明了这一点:

Python 的一个特殊之处是,如果没有global语句生效,对名称的赋值总是进入最内层作用域。赋值不会复制数据,它们只是将名称绑定到对象。

因此,由于该行实际上是在说:

_total = _total + PRICE_RANGES[key][0]

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

解决方案 4:

除了声明特殊对象、映射或数组外,还可以使用函数属性。这使得变量的作用域非常清晰。

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

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

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

解决方案 5:

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

解决方案 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 的基于列表的方法,但就可读性而言它并不是最佳的。

这是经过修改的 @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(): ...

解决方案 8:

我的解决方法是...

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

解决方案 9:

>>> 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 的本地范围内定义

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1887  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1425  
  在制造业数字化转型的进程中,PLM(产品生命周期管理)系统、ERP(企业资源计划)系统、MES(制造执行系统)以及 CAD(计算机辅助设计)软件都扮演着至关重要的角色。然而,这些系统和软件各自独立运行时,往往难以发挥出最大的协同效应。实现 PLM 系统与 ERP、MES、CAD 的有效集成,成为提升企业整体竞争力、优化...
plm系统的主要功能模块   3  
  产品生命周期管理(PLM)作为一种先进的管理理念和技术,在电子与半导体行业正发挥着日益重要的作用。随着电子与半导体行业的快速发展,产品更新换代速度加快,市场竞争愈发激烈,企业面临着诸多挑战,如缩短产品上市时间、提高产品质量、降低成本等。而PLM的应用为企业应对这些挑战提供了有效的解决方案,展现出巨大的应用价值。提升产品...
plm项目   4  
  PLM(产品生命周期管理)项目管理软件在现代企业的产品研发、生产与运营中扮演着至关重要的角色。它整合了从产品概念设计到退役的全流程数据与流程,助力企业提升效率、降低成本并增强创新能力。随着科技的飞速发展以及企业需求的不断演变,未来十年 PLM 项目管理软件的发展充满了无限可能,值得深入探讨与预测。智能化与自动化趋势智能...
plm产品全生命周期管理   6  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用