194

Consider this example:

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

For the code in the B function, the variable b is in a non-global, enclosing (outer) scope. How can I modify b from within B? I get an UnboundLocalError if I try it directly, and using global does not fix the problem since b is not global.


Python implements lexical, not dynamic scope - like almost all modern languages. The techniques here will not allow access to the caller's variables - unless the caller also happens to be an enclosing function - because the caller is not in scope. For more on this problem, see How can I access variables from the caller, even if it isn't an enclosing scope (i.e., implement dynamic scoping)?.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
grigoryvp
  • 40,413
  • 64
  • 174
  • 277
  • 1
    You can as long as `b` is mutable. An assignment to `b` will mask the outer scope. – JimB Dec 09 '11 at 15:54
  • 6
    It's one of Python's embarrassments that `nonlocal` hasn't been backported to 2.x. It's an intrinsic part of closure support. – Glenn Maynard Dec 09 '11 at 18:28
  • It looks that using nonlocal or using lists, like explained above, does not work with classes well. Python wrongly assumes the variable would be in the scope class, instead internally of one of the function classes. – GarouDan Dec 09 '20 at 19:45
  • The 3.x `nonlocal` keyword is explained here: https://stackoverflow.com/questions/1261875/python-nonlocal-statement. The current question is a better canonical most of the time, since most askers will have a problem that is solved by the `nonlocal` keyword, and **not** already be aware of it. However, that question is a useful reference, e.g. for people who have encountered `nonlocal` in someone else's code. – Karl Knechtel Jul 03 '22 at 19:08

9 Answers9

210

On Python 3, use the nonlocal keyword:

The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. 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.

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

On Python 2, use a mutable object (like a list, or dict) and mutate the value instead of reassigning a variable:

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

foo()

Outputs:

[1, 1]
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Adam Wagner
  • 15,469
  • 7
  • 52
  • 66
  • 19
    A nice way to do this is `class nonlocal: pass` in the outer scope. Then `nonlocal.x` can be assigned to in the inner scope. – kindall Feb 10 '14 at 21:54
  • 2
    @kindall very neat thanks heaps :) probably need a different name though because it breaks forward compatibility. In python 3 it is a keyword conflict and will cause a `SyntaxError`. Perhaps `NonLocal` ? – Adam Terrey Aug 26 '16 at 00:36
  • 1
    or, since it's technically a class, `Nonlocal`? :-) – kindall Sep 08 '16 at 21:22
  • Example code here: https://stackoverflow.com/questions/1261875/python-nonlocal-statement – Mr-IDE Mar 06 '18 at 14:54
  • Could someone explain/link to the logic to this behavior? Being able to mutate a list from the outer scope but not being able to modify the outer scopes variables(without `nonlocal` keyword. Thanks in advance – PrimeTimeTran Dec 07 '22 at 06:41
  • 1
    @PrimeTimeTran the statement `a = 2` is ambiguous, since it's used to declare fresh variables and to modify existing variables (e.g. compare to Scheme, which has separate `let` and `set!`). Python 2 had no way to distinguish the intended meaning, so it assumed a fresh variable was wanted. Python 3 defaults to the same behaviour, but allows the other to be used via the `nonlocal` keyword. – Warbo Aug 17 '23 at 17:42
27

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
chrisk
  • 271
  • 3
  • 3
  • 1
    This is odd that class with its fields is "visible" in an inner function but variables are not, unless you define outer variable with the "nonlocal" keyword. – Celdor Feb 02 '18 at 13:01
  • Setting a key on a dictionary initialized in the outer scope also works. – Boris Verkhovskiy Aug 23 '21 at 08:43
14

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.

Monolith
  • 1,067
  • 1
  • 13
  • 29
Mike Edwards
  • 3,742
  • 17
  • 23
2

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.

zchenah
  • 2,060
  • 16
  • 30
  • _" which cover the outer one"_ What do you mean ? Defining an object with name _b_ in a nested function has no influence on an object with the same name in the outer space of this function – eyquem Dec 09 '11 at 17:21
  • 1
    @eyquem that is, wherever the assignment statement is, it will introduce the name in the entire current scope. Such as the question's sample code, if it is: def C():print( b ) b=2 the "b=2" will introduce the name b in the entire C func scope, so when print(b), it will try to get b in the local C func scope but not the outer one, the local b has not be initialized yet, so there will be an error. – zchenah Dec 10 '11 at 04:40
  • 1
    This "no you can't" answer is only true on Python 2. On 3 you can declare your variable as `nonlocal my_variable` in the inner scope before assigning to it. – Boris Verkhovskiy Aug 23 '21 at 09:09
1

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!
1

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

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
eyquem
  • 26,771
  • 7
  • 38
  • 46
0

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()
Sideshow Bob
  • 4,566
  • 5
  • 42
  • 79
  • 2
    How can you write a function without knowing about the nested functions inside it? Nested functions and closures are an intrinsic part of the function they're enclosed in. – Glenn Maynard Dec 09 '11 at 18:26
  • You need to know about the interface of the functions enclosed in yours, but you shouldn't have to know about what goes on inside them. Also, you can't be expected to know what goes on in the functions *they* call, etc! If a function modifies a non-global or non-classmember it should usually make that explicit through its interface, ie take it as a parameter. – Sideshow Bob Dec 12 '11 at 11:19
  • Python doesn't force you to be that good of course, hence the `nonlocal` keyword - but it's up to you to use it with great caution. – Sideshow Bob Dec 12 '11 at 11:20
  • 5
    @Bob: I've never found using closures like this to be hazardous at all, other than due to language quirks. Think of locals as a temporary class, and local functions as methods on the class, and it's no more complicated than that. YMMV, I guess. – Glenn Maynard Dec 12 '11 at 23:23
-1

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]
Michael Giba
  • 118
  • 4
-1

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()
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
Cédric Julien
  • 78,516
  • 15
  • 127
  • 132