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?

Luke Taylor
  • 8,631
  • 8
  • 54
  • 92
  • What about just `a = 3`? – Eli Sadoff Jul 03 '16 at 16:01
  • 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. – Luke Taylor Jul 03 '16 at 16:02
  • Not possible, no. You could make a function change the globals, but it can only access the globals of the module it is defined in, not the one its running from. – poke Jul 03 '16 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. – Eli Sadoff Jul 03 '16 at 16:02
  • @poke What about walking up the stack with `inspect`? I think it's possible there, I'm just not sure how. – Luke Taylor Jul 03 '16 at 16:03
  • 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. – poke Jul 03 '16 at 16:16
  • 1
    @poke Thanks for the more detailed implementation. – Luke Taylor Jul 03 '16 at 16:21
  • I think `sys.modules[script_name].a = 3` should work? Am I misunderstanding something? – vaultah Jul 03 '16 at 16:24
  • 1
    @vaultah I think I'm trying to go the other way – Luke Taylor Jul 03 '16 at 16:25
  • What exactly do you mean by "local"? `locals() is globals()` is True on module level. Or do you want to be able to run `set_a_to_three` in functions/other namespaces? – vaultah Jul 03 '16 at 16:33
  • 1
    @vaultah I'd like it to work on `locals()` ideally, just as the normal behavior of `a = 3` would. – Luke Taylor Jul 03 '16 at 16:34

4 Answers4

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.

Community
  • 1
  • 1
Luke Taylor
  • 8,631
  • 8
  • 54
  • 92
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.

u8y7541
  • 548
  • 2
  • 8
  • 13
  • 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. – Luke Taylor Jul 03 '16 at 16:12
  • 2
    The view returned from [`locals()`](https://docs.python.org/3/library/functions.html#locals) shouldn’t be modified. – poke Jul 03 '16 at 16:14
  • Modifying locals is unsupported. – user2357112 Jul 03 '16 at 16:55
  • @user2357112 Hmm... I've heard that, but the code I have given still works. Why? – u8y7541 Jul 03 '16 at 16:56
  • In the current implementation, modifying locals only works at class or module scope, but not inside a function. – user2357112 Jul 03 '16 at 18:08
  • @user2357112 This code works in 3.5, and that is the most current. Are you referring to 3.5.2? – u8y7541 Jul 03 '16 at 22:55
  • Try calling `set_a_to_three` from within a function. – user2357112 Jul 04 '16 at 00:21
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"]
Community
  • 1
  • 1
fenceop
  • 1,439
  • 3
  • 18
  • 29
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
Nizam Mohamed
  • 8,751
  • 24
  • 32