是否可以在外部范围而不是全局范围内的python中修改变量?


107

给定以下代码:

def A() :
    b = 1

    def B() :
        # I can access 'b' from here.
        print( b )
        # But can i modify 'b' here? 'global' and assignment will not work.

    B()
A()

因为B()函数变量中的代码在b外部范围内,但不在全局范围内。是否可以bB()函数内修改变量?我当然可以从和阅读print(),但是如何修改?


抱歉,当然是2.7 :)。对于python 3,作用域规则已更改。
grigoryvp 2011年

只要b可变就可以。的分配b将掩盖外部范围。
JimB 2011年

4
这是nonlocal尚未被移植到2.x 的Python的尴尬之一。这是关闭支持的内在部分。
格伦·梅纳德

Answers:


96

Python 3.x具有nonlocal关键字。我认为这可以满足您的要求,但是我不确定您是在运行python 2还是3。

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

对于python 2,我通常只使用可变对象(如列表或dict),然后更改值而不是重新分配。

例:

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

foo()

输出:

[1, 1]

16
一个很好的方法是class nonlocal: pass在外部范围内。然后nonlocal.x可以分配给内部作用域。
kindall 2014年

1
到目前为止,我已经有两个简单但非常有用的python技巧:您的第二个技巧:)谢谢@kindall!
swdev 2014年

@kindall是一个很棒的技巧-与Python 3语法几乎没有什么不同,并且比传递可变对象更具可读性。
dimo414 2015年

2
@kindall非常整洁的感谢堆:)尽管可能会破坏向前的兼容性,但可能需要使用其他名称。在python 3中,这是关键字冲突,将导致SyntaxError。也许NonLocal吧?
亚当·特雷

或者,因为它在技术上的一类,Nonlocal?:-)
kindall

19

您可以使用空类来保存临时范围。就像是易变但更漂亮。

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

奇怪的是,带有其字段的类在内部函数中是“可见的”,但变量不是,除非您使用“ nonlocal”关键字定义外部变量。
Celdor '18

12

我对Python有点陌生,但是我已经读了一些。我相信您将获得的最佳效果类似于Java解决方法,即将您的外部变量包装在列表中。

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

# The output is '2'

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


4

不,至少不能以这种方式。

因为“设置操作”将在当前范围内创建一个新名称,该名称将覆盖外部名称。


“哪个覆盖外面的”是什么意思?在嵌套函数中定义名称为b的对象对该函数的外部空间中具有相同名称的对象没有影响
eyquem 2011年

1
@eyquem,也就是说,无论赋值语句在哪里,它都会在整个当前作用域中引入名称。例如问题的示例代码,如果它是:def C():print(b)b = 2“ b = 2”将在整个C函数范围内引入名称b,因此当print(b)时,它将尝试使b在本地C函数范围内,但不在外部C范围内,因为本地b尚未初始化,因此会出现错误。
zchenah 2011年

1

对于以后在更安全但更重的解决方法上进行研究的人来说。无需将变量作为参数传递。

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

1

简短的答案将自动起作用

我创建了一个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!

0

我认为您不应该这样做。可以在其封闭的上下文中更改事物的功能非常危险,因为在不了解该功能的情况下可能会编写该上下文。

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

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

A()

2
如何在不知道函数内部嵌套函数的情况下编写函数?嵌套函数和闭包是他们封闭在功能的固有部分。
格伦·梅纳德·

您需要了解包含在其中的函数的接口,但不必了解它们内部发生了什么。另外,您不能期望知道他们调用的函数中发生了什么!如果函数修改了非全局或非类成员,则通常应通过其接口将该函数显式表示,即将其作为参数。
杂耍节目Bob

当然,Python并不会强迫您做到那么好,因此使用nonlocal关键字-但是,使用它取决于您如何谨慎。
杂耍节目Bob

5
@Bob:我从来没有发现使用这样的闭包完全是危险的,除了由于语言的怪癖之外。可以将locals看作是一个临时类,而将local函数看作是该类上的方法,这没有比这更复杂的了。YMMV,我猜是。
格伦·梅纳德

0

可以,但是您必须使用全局语句(使用全局变量时,并不是一如既往的很好的解决方案,但是它可以工作):

def A():
    global b
    b = 1

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

    B()
A()

请参阅我的答案,以解释此解决方案的潜在缺点
eyquem 2011年

4
使用全局变量是完全不同的。
格伦·梅纳德

0

我不知道是否有一个功能的属性 __dict__当该外部空间不是全局空间==模块时,的外部空间,当函数是嵌套函数时就是这种情况,在Python 3中

但是据我所知,在Python 2中没有这样的属性。

因此,做您想做的事的唯一可能性是:

1)使用别人所说的可变对象

2)

def A() :
    b = 1
    print 'b before B() ==', b

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

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

A()

结果

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

诺塔

CédricJulien的解决方案有一个缺点:

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的对象时,情况才如此

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.