Is it possible to change a variable in python that is in an external but not global scope?

Given the 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 the function B() variable b is in the outer region, but not in the global region. Is it possible to change the variable b from function B() ? Of course, I can read it here and print() , but how to change it?

+89
python
Dec 09 '11 at 15:45
source share
9 answers

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

A nonlocal operator forces the listed identifiers to refer to previously associated variables in the immediate surrounding area. This is important because the default behavior for binding is to look up the local namespace. This operator allows the encapsulated code to rearrange variables outside the local area, in addition to global ones (module).

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

Example:

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

Outputs:

 [1, 1] 
+77
Dec 09 '11 at 15:50
source share
— -

You can use an empty class to store the time domain. It looks like a fickle but a bit pretty.

 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 gives the following interactive output:

 >>> outer_fn() 8 27 >>> fs = FnScope() NameError: name 'FnScope' is not defined 
+16
Feb 10 '14 at 21:40
source share

I'm a little new to Python, but I read a little about it. I believe that the best thing you get is a Java workaround, which involves moving your external variable to a list.

 def A(): b = [1] def B(): b[0] = 2 B() print(b[0]) # The output is '2' 

Change: I think this was true before Python 3. It seems nonlocal your answer.

+10
Dec 09 '11 at 15:51
source share

No, you cannot, at least not that way.

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

+4
Dec 09 '11 at 15:49
source share

For those who look at it much later on a safer, but harder workaround. Without the need to pass variables as parameters.

 def outer(): a = [1] def inner(a=a): a[0] += 1 inner() return a[0] 
+2
Sep 01 '14 at 10:23
source share

I do not think you should do this. Functions that can change things in their surrounding context are dangerous because this context can be written without knowing the function.

You can make this explicit, either by making B an open method, or C a private method in a class (perhaps the best way); or using a mutable type, such as a list, and pass it explicitly to C:

 def A(): x = [0] def B(var): var[0] = 1 B(x) print x A() 
+1
Dec 09 '11 at 15:54
source share

You can, but you have to use global status (not a very 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() 
0
Dec 09 '11 at 15:55
source share

I don't know if there is an attribute of a function that gives __dict__ outer space of a function when that outer space is not a global space == module, which takes place when a function is a nested function, in Python 3.

But in Python 2, as far as I know, there is no such attribute.

Thus, the only possibilities to do what you want:

1) using a mutable object, as others say

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

Cédric Julien's solution has the disadvantage of:

 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 

Global b after executing A() been changed and cannot be

This is the case only if there is an object with identifier b in the global namespace

0
Dec 09 '11 at 17:16
source share

A short answer that just works automatically

I created a Python library to solve this specific problem. It is released under disrespect, so use it as you wish. You can install it using pip install seapie or check the homepage 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() 

exits

 2 

arguments have the following meanings:

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



Long answer

This is harder. Seapie works by editing frames in the call stack using the CPython API. CPython is the de facto standard, so most people don't need to worry about it.

The magic words that are likely to interest you if you read this are as follows:

 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 go to the local area. local regions, however, are optimized differently than global regions, so creating new objects has some problems when you try to call them directly if they are not initialized. I will copy a few ways to get around these problems from the github page

  • Collect, import and identify your objects in advance
  • Preassign placeholders to your objects
  • Reassign the object in the main program to update the character table: x = locals () ["x"]
  • Use exec () in the main program instead of a direct call to avoid optimization. Instead of calling x do: exec ("x")

If you feel that using exec() is not what you want to use, you can emulate the behavior by updating the true local dictionary (and not the one returned by locals ()). I will copy the 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() 

Exit:

 hack! 
0
Sep 17 '19 at 19:29
source share



All Articles