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

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

给定以下代码:

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(),但是如何修改?

Given following code:

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

For the code in B() function variable b is in outer scope, but not in global scope. Is it possible to modify b variable from within B() function? Surely I can read it from here and print(), but how to modify it?


回答 0

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]

Python 3.x has the nonlocal keyword. I think this does what you want, but I’m not sure if you are running python 2 or 3.

The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope. This is important because the default behavior for binding is to search the local namespace first. The statement allows encapsulated code to rebind variables outside of the local scope besides the global (module) scope.

For python 2, I usually just use a mutable object (like a list, or dict), and mutate the value instead of reassign.

example:

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

foo()

Outputs:

[1, 1]

回答 1

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

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

You can use an empty class to hold a temporary scope. It’s like the mutable but a bit prettier.

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

This yields the following interactive output:

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

回答 2

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

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

# The output is '2'

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

I’m a little new to Python, but I’ve read a bit about this. I believe the best you’re going to get is similar to the Java work-around, which is to wrap your outer variable in a list.

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

# The output is '2'

Edit: I guess this was probably true before Python 3. Looks like nonlocal is your answer.


回答 3

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

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

No you cannot, at least in this way.

Because the “set operation” will create a new name in the current scope, which covers the outer one.


回答 4

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

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

For anyone looking at this much later on a safer but heavier workaround is. Without a need to pass variables as parameters.

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

回答 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!

The short answer that will just work automagically

I created a python library for solving this specific problem. It is released under the unlisence so use it however you wish. You can install it with pip install seapie or check out the home page here 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()

outputs

2

the arguments have following meaning:

  • The first argument is execution scope. 0 would mean local B(), 1 means parent A() and 2 would mean grandparent <module> aka global
  • The second argument is a string or code object you want to execute in the given scope
  • You can also call it without arguments for interactive shell inside your program

The long answer

This is more complicated. Seapie works by editing the frames in call stack using CPython api. CPython is the de facto standard so most people don’t have to worry about it.

The magic words you are probably most likely interesed in if you are reading this are the following:

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

The latter will force updates to pass into local scope. local scopes are however optimized differently than global scope so intoducing new objects has some problems when you try to call them directly if they are not initialized in any way. I will copy few ways to circumvent these problems from the github page

  • Assingn, import and define your objects beforehand
  • Assingn placeholder to your objects beforehand
  • Reassign object to itself in main program to update symbol table: x = locals()[“x”]
  • Use exec() in main program instead of directly calling to avoid optimization. Instead of calling x do: exec(“x”)

If you are feeling that using exec() is not something you want to go with you can emulate the behaviour by updating the the true local dictionary (not the one returned by locals()). I will copy an example from 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()

Output:

hack!

回答 6

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

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

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

A()

I don’t think you should want to do this. Functions that can alter things in their enclosing context are dangerous, as that context may be written without the knowledge of the function.

You could make it explicit, either by making B a public method and C a private method in a class (the best way probably); or by using a mutable type such as a list and passing it explicitly to C:

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

A()

回答 7

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

def A():
    global b
    b = 1

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

    B()
A()

You can, but you’ll have to use the global statment (not a really good solution as always when using global variables, but it works):

def A():
    global b
    b = 1

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

    B()
A()

回答 8

我不知道是否有一个功能的属性 __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的对象时,情况才如此

I don’t know if there is an attribute of a function that gives the __dict__ of the outer space of the function when this outer space isn’t the global space == the module, which is the case when the function is a nested function, in Python 3.

But in Python 2, as far as I know, there isn’t such an attribute.

So the only possibilities to do what you want is:

1) using a mutable object, as said by others

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

result

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

.

Nota

The solution of Cédric Julien has a drawback:

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

result

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

The global b after execution of A() has been modified and it may be not whished so

That’s the case only if there is an object with identifier b in the global namespace