4

NB: This question assumes Python 2.7.3.

I'm looking for a sane approach to dynamically modify a function's local namespace, preferably in a way that adds the least clutter to the body function.

What I have in mind would look something like this:

import os
from namespace_updater import update_locals

def somefunc(x, y, z):
    # ...
    # ...
    # this and that
    # ...
    # ...

    if os.environ.get('FROBNICATE'):
        from frobnitz import frobnicate
        update_locals(frobnicate(locals()))

    #
    # life goes on, possibly with duly frobnicated local variables...
    # ...
    # ...
    # ...

Thanks!


PS: Below are approaches that don't work.

The most naive approach to this would be something like:

locals().update(new_locals(locals())

...but the documentation for locals() very explicitly warns against relying on such voodoo to modify local variables, so please do not submit this as an answer (unless you can make an excellent case for disregarding the documentation's warning).

Next in the naivete scale is something like

for k, v in new_locals(locals()).items():
    exec ('%s = v' % k)

AFAICT, such code cannot be "out of the way" (i.e., it has to be in the body of the function), which is not ideal. But the real deal-breaker is that the exec ('%s = v' % k) hack can lead to some bizarre bugs.

When I write "bizarre bugs" what I mean is "bugs that look bizarre to someone with as tenuous a grasp of exec ('%s = v' % k) as mine". How tenuous is my grasp of this hack? To answer this, consider the script below. It has three variants: (1) exactly as shown; (2) after deleting the leading # of line 18; (3) after deleting the first # in both lines 15 and 18 (i.e. for this variant, no code is commented out). I could have not predicted the behavior of variants (2) and (3) of this script. I could not have even predicted with more than a 50% confidence the behavior of variant (1). That's how tenuous my grasp of the exec ('%s = v' % k) hack. Unless you can confidently and correctly predict how the three variants of this script will behave (under python 2.7), it is safe to say that your grasp of the situation is about as tenuous as mine, and you probably should stay clear of exec ('%s = v' % k) too.

x = 'global x'                            # 01
y = 'global y'                            # 02
def main():                               # 03
    x = 'local x'                         # 04
    y = 'local y'                         # 05
    run(locals())                         # 06
    print 'OK'                            # 07
    return 0                              # 08
                                          # 09
def run(namespace):                       # 10
    global y                              # 11
    print locals().keys()                 # 12
    for k, v in namespace.items():        # 13
        print '%s <- %r' % (k, v)         # 14
        exec ('%s = v' % k) #in locals()  # 15
    print locals().keys()                 # 16
    x = x                                 # 17
    #z = lambda: k                        # 18
    print x                               # 19
    print y                               # 20
                                          # 21
exit(main())                              # 22
kjo
  • 33,683
  • 52
  • 148
  • 265
  • Question http://stackoverflow.com/questions/1142068 has a title similar to mine's, but I think the two questions are quite different in content; moreover, I don't find any of the answers given to that question answer mine. – kjo May 07 '12 at 20:02
  • There is some dreadful hack using ctypes which makes it possible, but I agree with Niklas: this is almost certainly not the solution to any problem. – Thomas K May 07 '12 at 20:29
  • @NiklasB.: I am sorry to be so prickly about this, but your comment is horribly condescending. I've been programming for >25 years. I know what I'm doing. – kjo May 07 '12 at 22:04
  • @kjo: Sorry, I think that came over wrong. Still, my point stands: Python doesn't have the feature you are looking for, and for good reasons. Probably you can work around it (after all, you can create any kind of code dynamically and `exec` it), but it won't be a good solution and maybe even implementation-specific. A good solution will take the problem into account, which you didn't describe. From my experience, SO doesn't work very well for these kinds of questions, so maybe this is indeed another case of the [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Niklas B. May 07 '12 at 22:14
  • 1
    For example, what keeps you from keeping your name->value association inside a dictionary or object instead of the local name scope? Are you trying to implement some kind of embedded language? – Niklas B. May 07 '12 at 22:17
  • What I don't understand is why the thread can't remain focused strictly on what I asked. If you don't like what I would like to do, just move on, or at the very list provide a link to a detailed explanation of why you think this is such a bad idea. Endlessly ragging on it won't change my mind. As I already told you, hard as it may be for you to believe, I am not an idiot. I've thought about what I'm asking it long enough, and a few brief comments won't change my mind. – kjo May 08 '12 at 16:40
  • what if we have is a class/instance method instead and still want to change its local variable namespace? – Charlie Parker Jun 22 '17 at 22:26
  • is there a version of this question that works for python 3 or higher? – Charlie Parker Oct 16 '17 at 23:14

3 Answers3

1

I'll present the only approach I can think of that is close to reasonable, and then I'll try to convince you not to use it.

def process(**kw):
  mycode = """\
print 'Value of foo is %s' % (foo,)
print 'Value of bar is %s' % (bar,)
"""
  exec mycode in kw

vars = {'foo': 2, 'bar': 3}
process(**vars)

With this approach, you have at least some protection from code-injection attacks. The dictionary containing the "local variables" of the code is specified explicitly, so you have complete control over what the variable space will be when you run the exec statement. You don't have to hack into the internals of function objects or other such.

I know that the decorator module uses exec in the implementation of @decorator to manipulate argument names in dynamically created functions, and there may be other common modules that use it. But I have been in only one situation where exec was a clear win over the alternatives in Python, and one for eval.

I do not see such a situation in your question. Unless mycode from above needs to do something really funky, like create a function with argument names given in kw, chances are you can get away with just writing the code plainly, and maybe using locals() in a pinch.

def process(**kw):
  print 'Value of foo is %s' % (kw['foo'],)
  print 'Value of bar is %s' % (kw['bar'],)

process(foo=2, bar=3)
wberry
  • 18,519
  • 8
  • 53
  • 85
  • Just a side note, you can also just do `'foo %s bar' % 'some'`, you don't need a singleton tuple there :) – Niklas B. May 07 '12 at 22:39
0

May be something like that

def foo():
    print(x)

foo.__globals__["x"] = "Hello Python"

foo()

unfortunately this does not works in body of function if varible has been defined

def foo(flag):
    x = "Hello World"
    if flag:
        foo.__globals__["x"] = "Hello Python"

    print(x)

prints Hello World in both flag is True or False

atomAltera
  • 1,702
  • 2
  • 19
  • 38
  • It doesn't work because you're modifying the global namespace, not the local one, and local variables take precedence over globals. – Thomas K May 07 '12 at 20:40
  • what if we have is a class/instance method instead and still want to change its local variable namespace? – Charlie Parker Jun 22 '17 at 22:26
0

Not sure if it is possible with an external function only. I've created a snippet:

def get_module_prefix(mod, localsDict):
    for name, value in localsDict.iteritems():
        if value == mod:
            return name
    raise Exception("Not found")

def get_new_locals(mod, localsDict):
    modulePrefix = get_module_prefix(mod, localsDict)
    stmts = []
    for name in dir(mod):
        if name.startswith('_'):
            continue
        if name not in localsDict:
            continue
        stmts.append("%s = %s.%s" % (name, modulePrefix, name))
    return "\n".join(stmts)

def func(someName):
    from some.dotted.prefix import some.dotted.name
    #here we update locals
    exec(get_new_locals(some.dotted.name, "some.dotted.name", locals()))
    print locals()
    print someName # value taken from aModule instead of parameter value


func(5)

where:

  • get_module_prefix is used to find the name under which the module is imported,
  • get_new_locals returns assignment statements which can be used to update locals,

The actual update of locals is performed in line exec(get_new_locals(some.dotted.name, locals())) where we simply execute assignment statements in which we assign values from the module to local variables.

I am not sure if it is what you actually ment.

uhz
  • 2,468
  • 1
  • 20
  • 20