How to modify the local namespace in python
Asked Answered
S

7

7

How can I modify the local namespace of a function in python? I know that locals() returns the local namespace of the function when called inside it, but I want to do something like this (I have a reason why I want to do this where g is not accessible to f, but it's quicker to give a trivial, stupid example to illustrate the problem):

def g():
   pass

def f():
    g()

f.add_to_locals({'g':g})
Spiceberry answered 17/7, 2009 at 8:43 Comment(5)
What is your ultimate goal using this strange technique? Why you can't just define g() before f()?Eelworm
I've tried to mess with the namespace of a function at runtime previously (for testing/mocking) and failed... just FYI.Thirst
what the heck is add_to_locals ? It doesnt seem to exist for me. AttributeError: 'function' object has no attribute 'add_to_locals'Keeling
@CharlieParker it doesn't exist. That's what he's trying to implement / asking if there is a solution to do just that.Singhalese
did you manage to get this to work?Keeling
D
10

You've a couple of options. First, note that g in your example isn't actually a local to the function (ie. not assigned within it), it's a global (ie hasn't been assigned to a local variable). This means that it will be looked up in the module the function is defined in. This is fortunate, as there's no way of altering locals externally (short of patching the bytecode), as they get assigned when the function runs, not before.

One option is simply to inject your function into the function's module's namespace. This will work, but will affect every function in that module that accesses the variable, rather than just the one function.

To affect just the one function, you need to instead point that func_globals somewhere else. Unfortunately, this is a read-only property, but you can do what you want by recreating the function with the same body, but a different global namespace:

import new
f = new.function(f.func_code, {'g': my_g_function}, f.func_name, f.func_defaults, f.func_closure)

f will now be indentical, except that it will look for globals in the provided dict. Note that this rebinds the whole global namespace - if there are variables there that f does look up, make sure you provide them too. This is also fairly hacky though, and may not work on versions of python other than cpython.

Dross answered 17/7, 2009 at 17:7 Comment(2)
I don't know if this is "good" code, but it was exactly what I wanted. I am converting one codebase to another and I wanted to be able to simulate a language construct from the other language. The other language can package code and add or remove that code from the namespace. It is useful in overriding different functions. So it manipulates the namespace in a stack like fashion. This is the closest thing that I can find that would allow me to do that efficiently. ThanksMalone
what is this new library?Keeling
O
3

Since the function isn't invoked, it has no "local" stack frame, yet. The most simple solution is to use a global context:

handler = None
def f():
    handler()

def g(): pass

handler = g

Or you could set g on the function object:

f.g = g

But I'm not sure how you can get the function object from within the function itself. If it was a method, you would use self.

Obadiah answered 17/7, 2009 at 8:54 Comment(3)
If you really wanted to use the function object inside the function, you could use inspect: import inspect import pprint def fn(): frame = inspect.currentframe() print frame.f_globals[frame.f_code.co_name].g fn.g = 'hello' fn()Baroque
That's broken for class functions, nested functions/closures, and lambdas, and anything decorated--it'll return the decorator's wrapping function, not the actual function.Doghouse
what if f is a class/instance method?Keeling
V
3

Why don't you just add an argument to f() and pass a reference to g()?

def g():
    pass

def f(func):
    func()

f(g)
Vogele answered 17/7, 2009 at 9:29 Comment(0)
S
2

I think you could solve the problem tackling it from a completely different point.
Functions are object, with their dictionaries; therefore, you can add g to f, and use it:

def g():
   print "g"

def f():
    f.g()

f.g = g
Sofar answered 17/7, 2009 at 9:24 Comment(3)
This only works for global functions; there's no way to access f reliably from within f.Doghouse
Could you expand this? Python functions are objects, and there are no differences if local or global - unless you mean methods but that's a completely different story, and not what OP was asking for.Sofar
what if f is a class/instance method?Keeling
E
2

I assume you want to do this, because the function f is defined not by you, but by some other module. So you want to change how f() works. In particular, you want to change what is called when g is called.

So I'll suggest this:

import thirdpartypackage

def mynewg():
   pass

thirdpartypackage.g = mynewg

This will change the global g for the module thirdpartypackage. So when thirdpartypackage.f() now is called, it will call mynewg() instead of g().

If this doesn't solve it, maybe g() is in fact imported from withing f(), or somthing. Then the solution is this:

import thirdpartypackage

def mynewg():
   pass

deg mynewf():
   mynewg()

thirdpartypackage.f = mynewf

That is, you override f() completely with a modified version that does what you want it to.

Expel answered 17/7, 2009 at 10:3 Comment(0)
D
1

A function that's not executing doesn't have any locals; the local context is created when you run the function, and destroyed when it exits, so there's no "local namespace" to modify from outside the function.

You can do something like this, though:

def f():
    g = [1]
    def func():
        print g[0]
    return func, g

f, val = f()
f()
val[0] = 2
f()

This uses an array to simulate a reference.

Doghouse answered 17/7, 2009 at 8:56 Comment(1)
This site should really require explanations for -1 votes, so people can't anonymously vote down correct answers without a reason.Doghouse
H
0

This seems to work

def add_to_locals(l):
    l['newlocal'] = 1

add_to_locals(locals())
assert newlocal
Hortenciahortensa answered 5/1, 2012 at 11:12 Comment(1)
This does not work inside a function. It does not add the value to the namespace.Malone

© 2022 - 2024 — McMap. All rights reserved.