Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: How to allow ‘inner function’ to change nonlocal variables in multiple ‘outer functions’ [duplicate]

Suppose I have a function, which has a large section of code repeated in various places within the function, I can do the following:

def foo():
    def bar():
        # do some stuff

    bar()
    # do some other stuff
    bar()

I can ‘read’ the variables that are in the scope of foo whilst inside bar, furthermore if I need to edit them, I can do this:

def foo():
    # stuff involving var1 and var2
    def bar():
        nonlocal var1, var2
        # do some stuff

    bar()
    # do some other stuff
    bar()

The Problem

Now suppose I have several functions, foo_1, foo_2, foo_3… etc, all of which have the same lines of code from bar repeated in them. It would be monotonous (not to mention a nightmare every time I wanted to change bar) to define bar inside each foo_i, however doing the following does not work, since it appears nonlocal works on the scope in which a function is defined, not in which it is called:

def bar():
    nonlocal var1, var2  # SyntaxError: no binding for nonlocal 'var1' found
    # do some stuff

def foo_1():
    # stuff involving var1 and var2
    bar()
    # do some other stuff
    bar()

A potential solution

One way round this problem it to pass in all of the variables that you need to change, and then return them afterwards. Something like this:

def bar(var1, var2):
    # do some stuff
    return var1, var2

def foo_1():
    # stuff involving var1 and var2
    var1, var2 = bar(var1, var2)
    # do some other stuff
    var1, var2 = bar(var1, var2)

My Question

The solution above has a few problems:

  • It is considerably more verbose than simply bar() (especially when there are more variables)
  • It isn't actually that much of an improvement on defining bar inside every foo_i since suppose a variable I previously just accessed within bar I now decide to edit. Not only do I need to change the function, but I need to change everywhere it is called (since now I must return an extra variable).

Is there a better way of achieving the above?

(This feels like the sort of problem that aught to have an answer, so I apologise if it’s a repeated question. I haven’t been able to find anything as yet though.)

like image 234
tim-mccurrach Avatar asked Feb 01 '26 23:02

tim-mccurrach


1 Answers

[...] it appears nonlocal works on the scope in which a function is defined, not in which it is called [...].

You are correct. nonlocal only applies to the namespace in which said function is defined. From the documentation for nonlocal:

The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals.

(emphasis mine)

As @Aran mentioned, a potential solution to avoid unnecessary verbosity is to wrap the variables you want to pass into certain functions in a class. namedtuple would be a attractive choice since you don't need a full-fledge class, but as has been stated, it is immutable. You can use a types.SimpleNamespace object instead, as they are also lightweight:

from types import SimpleNamespace


def bar(n):
    n.a = 3, n.b = 4


def foo():
    n = SimpleNamespace(a=1, b=2)
    bar(n)
    print(n.a, n.b) # 3 4
like image 193
Christian Dean Avatar answered Feb 03 '26 14:02

Christian Dean