2

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

tim-mccurrach
  • 6,395
  • 4
  • 23
  • 41

2 Answers2

1

[...] 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
Christian Dean
  • 22,138
  • 7
  • 54
  • 87
  • It would probably make sense to write a custom class for these variables. If they're always passed between functions together, that would imply that they should actually be a class. (And not a `NameSpace` thingy that's essentially just a thin wrapper that makes a dict look like a class.) – Aran-Fey Apr 12 '18 at 18:39
  • That would make sense @Aran-Fey. However, it seems to me that using a class just as a namespace is clunky. And yes, `NameSpace` is a then wrapper around a `dict`, but being able to use the `.` operator rather than the `[]` operator (IMO) makes for more readable code. – Christian Dean Apr 12 '18 at 18:43
  • 1
    Yes, that's true. However, it could make sense to implement these `foo_1`, `foo_2` etc functions as methods of that class. It's impossible to say without knowing what exactly the OP is doing though. Just throwing it out there as a suggestion. – Aran-Fey Apr 12 '18 at 18:46
  • Hmm, good point @Aran-Fey. I'll update if the OP clarifies anything. – Christian Dean Apr 12 '18 at 18:55
  • Thankyou both for your help :) I'm just trying to refactor a few (quite long) functions, to make them less repetitive and more maintainable. Accessing all of the variables of each function through a class/dict/object will make the code quite a lot more verbose (which I was trying to avoid) although it does address well the issue of maintainability every place `bar` is called. – tim-mccurrach Apr 12 '18 at 19:41
  • The various `foo_i` don't share any data, so I don't think there would be too much advantage to making them all methods of the same class. (Especially as my main aim is to simplify the existing code, and this will complicate calling each `foo_i`). – tim-mccurrach Apr 12 '18 at 19:44
0

You can declare those variables as global before modifying them inside each function, but they need to be declared before any function that uses them, also keep in mind that this will modify the variables for the scopes of ANY function in which they are declared as global:

var1 = var2 = None
def bar():
    global var1, var2
    # do some stuff

def foo_1():
    # stuff involving var1 and var2
    bar()
    # do some other stuff
    bar()
flapedriza
  • 342
  • 2
  • 12