6

I'd like a function in my module to be able to access and change the local namespace of the script that's importing it. This would enable functions like this:

>>> import foo
>>> foo.set_a_to_three()
>>> a
3
>>>

Is this possible in Python?

11
  • 1
    It's just a dumb example. I'll be doing more complex stuff that needs to come from foo and be assigned from foo. I know how Python assignment works normally, though. Commented Jul 3, 2016 at 16:02
  • 2
    @LukeTaylor oh sorry. I was confused because you seemed to have a good grasp on python. I'm not sure then. Commented Jul 3, 2016 at 16:02
  • 1
    @LukeTaylor Accessing the stack frames is an implementation detail though and may not necessarily work in other Python implementations. Also, this still requires you to define a before you call the function, otherwise your script will not even compile because of a NameError. Commented Jul 3, 2016 at 16:16
  • 1
    @poke Thanks for the more detailed implementation. Commented Jul 3, 2016 at 16:21
  • 1
    @vaultah I think I'm trying to go the other way Commented Jul 3, 2016 at 16:25

4 Answers 4

4

An answer was generously provided by @omz in a Slack team:

import inspect
def set_a_to_three():
    f = inspect.currentframe().f_back
    f.f_globals['a'] = 3

This provides the advantage over the __main__ solution that it works in multiple levels of imports, for example if a imports b which imports my module, foo, foo can modify b's globals, not just a's (I think)

However, if I understand correctly, modifying the local namespace is more complicated. As someone else pointed out:

It breaks when you call that function from within another function. Then the next namespace up the stack is a fake dict of locals, which you cannot write to. Well, you can, but writes are ignored.

If there's a more reliable solution, that'd be greatly appreciated.

Sign up to request clarification or add additional context in comments.

Comments

3

To let the module access your script's namespace, you will have to pass the namespace to the function. For example:

>>> import foo
>>> foo.set_a_to_three(globals(), locals())
>>> a
3

The inside of the code would look like this:

def set_a_to_three(globalDict, localDict):
    localDict['a'] = 3

Sidenote: In this case, the globals dictionary is not needed but in cases where you need to modify global variable, you will need to use the globals dictionary. I have included it here for this reason to make the function extensible.

However, it would be easier to just set a = 3 as Eli Sadoff said. It isn't a good practice to pass namespaces to other programs.

7 Comments

This is a decent answer in many cases. My situation is slightly more complicated because I'm already thoroughly abusing the Python language by replacing sys.modules[__name__ with a custom class implementing __getattr__, meaning my functions aren't being called directly. Thanks a lot, this is a good option in a lot of cases, as it is for most users.
The view returned from locals() shouldn’t be modified.
Modifying locals is unsupported.
@user2357112 Hmm... I've heard that, but the code I have given still works. Why?
In the current implementation, modifying locals only works at class or module scope, but not inside a function.
|
3

Disclaimer: This solution can only modify global variables, so it's not perfect. See Is it good practice to use import __main__? for pros and cons.

In foo.py:

import __main__

def set_a_to_three():
    __main__.a = 3

__main__ is a name given to the current top-level program. For example, if you import foo from a module bar, then bar can be referenced with the name __main__. If you import foo from bar, which was initially imported from a module qux, then qux is __main__, etc.

bar -> foo
bar is __main__

qux -> bar -> foo
qux is __main__

If you actually want to modify variables in bar rather than qux, for example because qux contains unit tests, you can use something like this:

import sys
import __main__
if "bar" in sys.modules:
    __main__ = sys.modules["bar"]

Comments

2

It's easy to set in the __main__, because we know it's name.

#foo.py
import sys

def set_a_to_three():
    sys.modules['__main__'].a = 3

3 Comments

Could this be done with a setattr if I didn't know its name?
I said the module's name.
Right. My bad. Sorry ;)

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.