tags:

views:

97

answers:

4

I'd like to define a helper function that has the ability to modify a module-level variable (with known name) from surrounding context without explicitly passing it, e.g.

# mod1.py
mod_var = 1
modify_var()
# mod_var modified
print mod_var

The problem is - I can't reference variable by mod1.mod_var, because I want to use helper function across many modules (helper itself will be defined in other module); it should dynamically 'pick' mod_var from surrounding calling context/scope.

Is this possible? How to obtain this?

My use case is to enhance defining URL -> view mapping in Django. Those definitions are spread across many sub-modules that define urlpatterns module-level variable. Helper function should pick this variable from the module that calls it and modify it. Avoiding explicitly passing it as argument would be great.

Edit: For additional solution - check this answer.


Edit2:

Wrong solution below! (left for references in comments)

Recently I've found another solution (the least magical in my opinion ;)) modify_var() function could be implemented like this:

def modify_var():
    calling_module = __import__("__main__")
    calling_module.mod_var = 42

Still, potential profits are arguable.

unittest module uses this technique in its main method.

A: 

If you really want to do that then you'll need to import mod1 in either the other module or directly in the function, and then modify it off that import. But don't do that; seasoned programmers will point and laugh.

Ignacio Vazquez-Abrams
I can't do this, because I don't know in which module my function will be called (several modules with `urlpatterns` variable).
gorsky
+4  A: 

What you want to do sounds like too much magic. Pass in urlpatterns and be done with it. Explicit is better than implicit.

Ned Batchelder
You're right I'm afraid, but I'll wait for some magicians and see their tricks;)
gorsky
+1 for The Zen.
J.J.
+2  A: 

OK, here's the magic, but again, I recommend not using it:

import sys

def modify_var():
    """Mysteriously change `mod_var` in the caller's context."""
    f = sys._getframe(1)
    f.f_locals['mod_var'] += " (modified)"

mod_var = "Hello"
modify_var()
print mod_var

prints:

Hello (modified)

As a further warning against this technique: _getframe is one of those functions that other implementations of Python don't provide, and the docs include this sentence: "This function should be used for internal and specialized purposes only."

Ned Batchelder
It works, but it's too magical indeed. Thanks though, I'll pass it explicitly. I'm enlightened by the Zen of Python.
gorsky
+2  A: 

It's a truly bad, horrible, and awful idea, which will lead to future maintenance nightmares. However, Python does offer "enough rope to shoot yourself in the foot", if you truly insist: introspection and metaprogramming tools which are mostly intended for debugging purposes, but can be abused to perform the ill-conceived task you so desperately crave.

For example, in evil.py:

import inspect

def modify_var():
  callersframe = inspect.stack()[1][0]
  callersglobals = callersframe.f_globals
  if 'mod_var' not in callersglobals:
    raise ValueError, 'calling module has no "mod_var"!'
  callersglobals['mod_var'] += 1

now say you have two modules, a.py:

import evil

mod_var = 23
evil.modify_var()
print 'a mod_var now:', mod_var

and b.py:

import evil

mod_var = 100
evil.modify_var()
print 'b mod_var now:', mod_var

you could do:

$ python -c'import a; import b'
a mod_var now: 24
b mod_var now: 101

However, maintaining this kind of black-magic tricks in the future is going to be a headache, so I'd strongly recommend not doing things this way.

Alex Martelli
Do my solution from 2nd edit (in question) have enough low magic-ness level? ;) There's still problem with lack of explicitness though.
gorsky
@gorsky, your second solution (which is reasonably explicit and non-magical) doesn't do anything even **close** to what you say in your Q that you want to do: it always modifies the var in the main module, **not** in the calling module! Just try it with the `a` and `b` modules I show in my answer, if that's not entirely obvious to you.
Alex Martelli
You're right - I've tested it wrong: calling it like `python a.py` makes `a.py` the main module, thus working as I've wanted. Wrong assumption, thanks for claryfing it to me.
gorsky