是否可以修改 Python 中位于外部(封闭)但不在全局范围内的变量?

2024-11-28 08:37:00
admin
原创
163
摘要:问题描述:考虑这个例子:def A(): b = 1 def B(): # I can access 'b' from here. print(b) # But can i modify 'b' here? B() A() 对于函数中的代...

问题描述:

考虑这个例子:

def A():
    b = 1
    def B():
        # I can access 'b' from here.
        print(b)
        # But can i modify 'b' here?
    B()
A()

对于函数中的代码B,变量b处于非全局的封闭(外部)作用域中。我如何b从内部进行修改?如果我直接尝试,B我会得到一个,并且使用不能解决问题,因为不是全局的。UnboundLocalError`global`b


Python 实现的是词法作用域,而不是动态作用域——就像几乎所有现代语言一样。这里的技术不允许访问调用者的变量——除非调用者恰好也是一个封闭函数——因为调用者不在作用域内。有​​关此问题的更多信息,请参阅如何从调用者访问变量,即使它不是封闭作用域(即实现动态作用域)?。


解决方案 1:

Python 3上,使用nonlocal关键字:

nonlocal语句使列出的标识符引用最近封闭范围内先前绑定的变量(不包括全局变量)。这很重要,因为绑定的默认行为是先搜索本地命名空间。该语句允许封装的代码重新绑定除全局(模块)范围之外的本地范围之外的变量。

def foo():
    a = 1
    def bar():
        nonlocal a
        a = 2
    bar()
    print(a)  # Output: 2

在Python 2上,使用可变对象(如列表或字典)并改变其值,而不是重新分配变量:

def foo():
    a = []
    def bar():
        a.append(1)
    bar()
    bar()
    print a

foo()

输出:

[1, 1]

解决方案 2:

您可以使用空类来保存临时范围。它类似于可变的,但更漂亮一些。

def outer_fn():
   class FnScope:
     b = 5
     c = 6
   def inner_fn():
      FnScope.b += 1
      FnScope.c += FnScope.b
   inner_fn()
   inner_fn()
   inner_fn()

这将产生以下交互式输出:

>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined

解决方案 3:

我对 Python 还不熟悉,但我读过一些相关内容。我相信你能得到的最好的方法类似于 Java 解决方法,即将外部变量包装在列表中。

def A():
   b = [1]
   def B():
      b[0] = 2
   B()
   print(b[0])

# The output is '2'

编辑:我猜这在 Python 3 之前可能是正确的。看起来nonlocal这就是你的答案。

解决方案 4:

不,你不能,至少从这个角度来说不行。

因为“set操作”会在当前作用域内创建一个新的名称,覆盖外面的名称。

解决方案 5:

简短的答案会自动起作用

我创建了一个 Python 库来解决这个特定问题。它是在未授权的情况下发布的,因此您可以随意使用它。您可以使用它进行安装pip install seapie,也可以在此处查看主页https://github.com/hirsimaki-markus/SEAPIE

user@pc:home$ pip install seapie

from seapie import Seapie as seapie
def A():
    b = 1

    def B():
        seapie(1, "b=2")
        print(b)

    B()
A()

输出

2

这些参数的含义如下:

  • 第一个参数是执行范围。0 表示本地B(),1 表示父级A(),2 表示祖级<module>,即全局

  • 第二个参数是要在给定范围内执行的字符串或代码对象

  • 您也可以在程序内部调用它而无需参数来进行交互式 shell


详细答案

这比较复杂。Seapie 通过使用 CPython api 编辑调用堆栈中的框架来工作。CPython 是事实上的标准,因此大多数人不必担心它。

如果你正在阅读这篇文章,你最感兴趣的可能是以下这些神奇的词语:

frame = sys._getframe(1)          # 1 stands for previous frame
parent_locals = frame.f_locals    # true dictionary of parent locals
parent_globals = frame.f_globals  # true dictionary of parent globals

exec(codeblock, parent_globals, parent_locals)

ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
# the magic value 1 stands for ability to introduce new variables. 0 for update-only

后者将强制更新传递到本地作用域。但是,本地作用域的优化方式与全局作用域不同,因此,如果尝试直接调用新对象(如果它们未以任何方式初始化),则引入新对象会出现一些问题。我将从 github 页面复制一些规避这些问题的方法

  • 预先分配、导入和定义您的对象

  • 预先为您的对象分配占位符

  • 在主程序中将对象重新分配给其自身以更新符号表:x = locals()["x"]

  • 在主程序中使用 exec() 而不是直接调用以避免优化。不要调用 x,而是执行:exec("x")

如果您觉得使用exec()不是您想要的,您可以通过更新真正本地字典(而不是 locals() 返回的字典)来模拟该行为。我将从https://faster-cpython.readthedocs.io/mutable.html复制一个示例

import sys
import ctypes

def hack():
    # Get the frame object of the caller
    frame = sys._getframe(1)
    frame.f_locals['x'] = "hack!"
    # Force an update of locals array from locals dict
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
                                          ctypes.c_int(0))

def func():
    x = 1
    hack()
    print(x)

func()

输出:

hack!

解决方案 6:

Python 3

我不知道__dict__在 Python 3 中,当函数的外部空间不是全局空间 == 模块时(当函数是嵌套函数时就是这种情况),函数是否有一个属性可以给出函数的外部空间。

Python 2

但据我所知,在 Python 2 中没有这样的属性。因此,在 Python 2 中执行所需操作的唯一可能性是:

  1. 使用可变对象,正如其他人所说

  2. def A() : b = 1 print 'b 在 B() == 之前', b

  def B() :
      b = 10
      print 'b ==', b
      return b

  b = B()
  print 'b after B() ==', b

一个()

结果

b before B() == 1
b == 10
b after B() == 10

笔记:

Cédric Julien 的解决方法(“使变量全局化”)有一个缺点:

def A() :
    global b # N1
    b = 1
    print '   b in function B before executing C() :', b

    def B() :
        global b # N2
        print '     b in function B before assigning b = 2 :', b
        b = 2
        print '     b in function B after  assigning b = 2 :', b

    B()
    print '   b in function A , after execution of B()', b

b = 450
print 'global b , before execution of A() :', b
A()
print 'global b , after execution of A() :', b

结果

global b , before execution of A() : 450
   b in function B before executing B() : 1
     b in function B before assigning b = 2 : 1
     b in function B after  assigning b = 2 : 2
   b in function A , after execution of B() 2
global b , after execution of A() : 2

执行后的全局bA()已被修改,可能不希望如此

仅当全局命名空间中存在标识符为b 的对象时,情况才会如此

解决方案 7:

我认为你不应该这么做。可以改变其封闭上下文中事物的函数是危险的,因为该上下文可能是在函数不知情的情况下编写的。

您可以使其明确,方法是将 B 设为公共方法,将 C 设为类中的私有方法(这可能是最好的方法);或者通过使用可变类型(例如列表)并将其明确传递给 C:

def A():
    x = [0]
    def B(var): 
        var[0] = 1
    B(x)
    print x

A()

解决方案 8:

对于任何稍后再考虑这个问题的人来说,一个更安全但更繁重的解决方法是。无需将变量作为参数传递。

def outer():
    a = [1]
    def inner(a=a):
        a[0] += 1
    inner()
    return a[0]

解决方案 9:

你可以,但是你必须使用全局语句(当使用全局变量时,这不是一个真正好的解决方案,但它有效):

def A():
    global b
    b = 1

    def B():
      global b
      print( b )
      b = 2

    B()
A()
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用