14

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.

3
  • 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() Commented Feb 4, 2013 at 17:29
  • 2
    Yes, without passing z to a. This is a simplified version of a more complex problem. Commented Feb 4, 2013 at 17:31
  • 1
    Ah, ok. I'm favoriting this then because the answer could be really useful. Commented Feb 4, 2013 at 17:32

5 Answers 5

15

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
Sign up to request clarification or add additional context in comments.

3 Comments

Very nice! I didn't know this module existed.
Indeed. I also didn't know that module existed. +1 for the answer and for qualifying that the solution was fragile.
I like that we can do this in python but I argue this is not a very stable API and dependant on the structure of the inspect.stack method return value which could possibly change over minor versions
7

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.

7 Comments

+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.
Does this print 2, 4, 4 or 2. 4, 2?
@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 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 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.
|
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.

Comments

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

Comments

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.

3 Comments

Would this example print 2, 4, 2? or 2. 4. 4?
Oh, I didn't realize that's what you wanted. In that case, it's even more trivial.
@RonanLamy Ugh, this is true, I hate globals. Added the global statement.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.