How to set custom locals for function call?

Andras Tantos python-list at andras.tantosonline.com
Tue Sep 1 19:54:07 EDT 2020


Snipping old parts...

On 9/1/20 4:01 PM, Chris Angelico wrote:
> On Wed, Sep 2, 2020 at 8:44 AM Andras Tantos
> <python-list at andras.tantosonline.com> wrote:
>> While I'm sure you're right, it certainly is well hidden:
>>
>> ...
>>
>> In other words, I can indeed change the value of a local variable
>> through the locals() dict.
> Not quite. What you're seeing there is that there aren't any locals,
> and you're getting the globals:
>
>>>> locals() is globals()
> True
>
> In this situation, locals() DOES return a real dictionary, and it can
> be manipulated as one. But only because it's the same as globals(),
> and you can do all that with globals.
That could very well be the case.
>
>> I've modified my code to see if I can at
>> least introduce locals through the value passed in for exec, but even
>> that seems to fail:
>>
>> ...
> Your whitespace is getting messed up again, although not as badly now.
> Try posting in plain text, not "rich text" or HTML or formatted text
> or anything.

OK, and sorry about that. May third time a charm?

class MySymbolTable(dict):
     def set_type(self, t):
         self.type = t
     def __getitem__(self, key):
         print(f"Requesting key {key} from {self.type} table")
         return super().__getitem__(key)
     def __setitem__(self, key, value):
         print(f"Setting key {key} from {self.type} table to value {value}")
         return super().__setitem__(key, value)

def mylocals(func):
     def wrapper(*args, **kwargs):
         loc = MySymbolTable()
         glob = MySymbolTable(globals())
         loc.set_type("local")
         loc["xxx"] = 123
         glob.set_type("global")
         glob["yyy"] = 42
         exec(func.__code__, glob, loc)
     return wrapper

@mylocals
def fun1():
     print("locals at the beginning:")
     print(", ".join(f"{name}: {value}" for name, value in 
locals().items()))
     print("globals at the beginning:")
     print(", ".join(f"{name}: {value}" for name, value in 
globals().items()))
     print(f"fun1 with locals: {type(locals())} and globals: 
{type(globals())}")
     a = 1
     b = 2
     c = 3
     a = b
     c = 5
     print("locals at the end:")
     print(", ".join(f"{name}: {value}" for name, value in 
locals().items()))
     print("globals at the end:")
     print(", ".join(f"{name}: {value}" for name, value in 
globals().items()))

fun1()

>> It still appears to me that 'exec()' simply ignores the dict passed in
>> as the local parameter: it doesn't even seem to initialize locals() from it.
> I'm honestly not sure what's going on, because it's hard to follow
> your code. But my suspicion is that when you exec a code object
> (rather than compiling and executing a string), it's using cells/slots
> rather than actually using your locals dict.
>
> ChrisA

Thanks for all the help!

Indeed, I'm executing a code object and not a string. Is there any way 
to get to the string version of the function and force a re-compile of 
it during an exec? Would it help?

I guess if I was doing that, I would need to execute the 'body' of the 
function within the exec and by passing in a local parameter I can 
pretend it has its own call-stack entry. But is that truly equivalent to 
calling the original function?

It seems to me that even it were all possible, the solution would be 
very brittle, for example, additional decorators would mess things up. 
Though that's probably the case independent of the 
code-object/source-code distinction.

Andras




More information about the Python-list mailing list