13

Is there a way in Python to manage scope so that variables in calling functions are visible within called functions? I want something like the following

z = 1

def a():
    z = z * 2
    print z

def b():
    z = z + 1
    print z
    a()
    print z

b()

I would like to get the following output

2
4
2

The real solution to this problem is just to pass z as a variable. I don't want to do this.

I have a large and complex codebase with users of various levels of expertise. They are currently trained to pass one variable through and now I have to add another. I do not trust them to consistently pass the second variable through all function calls so I'm looking for an alternative that maintains the interface. There is a decent chance that this isn't possible.

MRocklin
  • 55,641
  • 23
  • 163
  • 235
  • Are you trying to do this without passing `z` to `a()`? Because that seems like a simple solution given that it looks like you don't want changes to `z` in `a()` to persist back to `b()` – jonhopkins Feb 04 '13 at 17:29
  • 2
    Yes, without passing z to a. This is a simplified version of a more complex problem. – MRocklin Feb 04 '13 at 17:31
  • 1
    Ah, ok. I'm favoriting this then because the answer could be really useful. – jonhopkins Feb 04 '13 at 17:32

5 Answers5

14

I think this is what you're looking for. If you control the inner functions, and the variable you're looking for is already in scope somewhere in the call chain, but you don't want your users to have to include the variable in the call itself. You clearly don't want the global keyword, because you don't want inner assignments to affect the outer scope.

Using the inspect module gets you access to the calling context. (But, be warned that it's somewhat fragile. You should probably use only for CPython unthreaded.)

import inspect

def calling_scope_variable(name):
  frame = inspect.stack()[1][0]
  while name not in frame.f_locals:
    frame = frame.f_back
    if frame is None:
      return None
  return frame.f_locals[name]

z = 1

def a():
  z = calling_scope_variable('z')
  z = z * 2
  print z

def b():
  z = calling_scope_variable('z')
  z = z + 1
  print z
  a()
  print z

b()

This does give the right answer of:

2
4
2
John Hazen
  • 1,296
  • 1
  • 8
  • 19
6

This might be the right place for a class:

class MyClass(object):
    def __init__(self, z):
        self.z = 1

    def a(self):
        self.z = self.z * 2
        print self.z

    def b():
        self.z = self.z + 1
        print self.z
        self.a()
        print self.z

You can get the same effect with globals but I'd suggest creating your own separate namespace (such as with a class) and using that for your scope.

Kirk Strauser
  • 30,189
  • 5
  • 49
  • 65
  • +1, This is a good call if there is an object to represent. As we don't know what data is being handled here, it's hard to say if it's the best solution. – Gareth Latty Feb 04 '13 at 17:41
  • Does this print 2, 4, 4 or 2. 4, 2? – MRocklin Feb 04 '13 at 17:41
  • @MRocklin since you don't want `z` to be changed after the call to `a()`, would it be better for you to perform the calculations in `a()` on a local variable? Something like `def a(self): tmp = self.z tmp = tmp * 2 print tmp` – jonhopkins Feb 04 '13 at 17:44
  • @jonhopkins in general yes, that's the way to go. Really the way to go is to pass z as an input. In this case I have 100 functions that all might call each other and it is very inconvenient to pass the extra variable. The shared name serves to coordinate which variable is passed implicitly. Everyone knows to look for `z` in the calling function's scope, make a local copy and edit there. – MRocklin Feb 04 '13 at 17:47
  • @MRocklin this problem seems more interesting than I originally thought. I have no more ideas at the moment, but I hopefully someone knows how to do exactly what you're looking for. Is passing variables really that bad for your case, though? Since you already know exactly which variable is being used anyway, that's not much of an issue, but I didn't think it had any effect on performance. – jonhopkins Feb 04 '13 at 17:53
  • @jonhopkins I've made my motivation slightly less vague in the original description. In short I'm changing the implementation of a large and complex codebase and don't want to change the interface. – MRocklin Feb 04 '13 at 18:06
  • I need to modify as `def b(self):` otherwise it tells `self` is not defined in python 3.8!! – Learner Apr 18 '23 at 19:37
3

This is a bit ugly, but it avoids using globals, and I tested it and it works. Using the code from the selected answer found here, the function getSetZ() has a "static" variable that can be used to store a value that is passed to it, and then retrieved when the function is called with None as the parameter. Certain restrictions are that it assumes None is not a possible value for z, and that you don't use threads. You just have to remember to call getSetZ() right before each call to a function that you want the calling function's z to be available in, and to get the value out of getSetZ() and put it in a local variable in that function.

def getSetZ(newZ):
    if newZ is not None:
        getSetZ.z = newZ
    else:
        return getSetZ.z

def a():
    z = getSetZ(None)
    z = z * 2
    print z

def b():
    z = getSetZ(None)
    z = z + 1
    print z
    getSetZ(z)
    a()
    print z

getSetZ.z = 0
getSetZ(1)
b()

I hope this helps.

Community
  • 1
  • 1
jonhopkins
  • 3,844
  • 3
  • 27
  • 39
2

In general, the best solution is always just to pass the values around - this will be much more predictable and natural, easier to work with and debug:

def a(z):
    z = z * 2
    print z

def b():
    global z
    z = z + 1
    print z
    a(z)
    print z

b()

You could define a() in b() and do something similar:

def b():
    global z
    z = z + 1
    print z
    def a():
        z = z * 2
        print z
    a()
    print z

However, this isn't really optimal.

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
2

I don't quite understand what you're trying to achieve, but this prints 2, 4, 2:

z = 1

def a():
    global z
    y = z * 2
    print y

def b():
    global z
    z = z + 1
    print z
    a()
    print z

b()
Ronan Lamy
  • 577
  • 3
  • 9