How to set custom locals for function call?

Andras Tantos python-list at andras.tantosonline.com
Tue Sep 1 17:58:10 EDT 2020


On 9/1/2020 12:41 PM, MRAB wrote:
> On 2020-09-01 05:45, Andras Tantos wrote:
>> All,
>>
>> I'm new here, so please direct me to the right forum, if this is not the
>> one...
>>
>> What I'm trying to do is to call a function, but monitor all the local
>> variable accesses within that function. What I thought I would need to
>> do, is to |exec| the function with a custom |locals| dictionary. The
>> code below attempts to do it.
>>
>> |classMySymbolTable(dict):defset_type(self,t):self.type =t
>> def__getitem__(self,key):print(f"Requesting key {key} from {self.type}
>> table")returnsuper().__getitem__(key)def__setitem__(self,key,value):print(f"Setting 
>>
>> key {key} from {self.type} table to value
>> {value}")returnsuper().__setitem__(key,value)defmylocals(func):defwrapper(*args,**kwargs):loc 
>>
>> =MySymbolTable()glob
>> =MySymbolTable(globals())loc.set_type("local")glob.set_type("global")exec(func.__code__,glob,loc)returnwrapper 
>>
>> @mylocalsdeffun1():print(f"fun1 with locals: {type(locals())} and
>> globals: {type(globals())}")a =1b =2c =3a =b c =5fun1()|
>>
>> However, when I run it, I get the following output:
>>
>> |Requestingkey printfromglobaltable Requestingkey type fromglobaltable
>> Requestingkey locals fromglobaltable Requestingkey type fromglobaltable
>> Requestingkey globals fromglobaltable fun1
>> withlocals:<class'dict'>andglobals:<class'__main__.MySymbolTable'>|
>>
>> That is to say, global accesses are redirected to my custom dict, but
>> local assignments are not. You can even see that in the types of the two
>> objects printed in the last line.
>>
>> My hunch is that since I'm using the functions |__code__| member, I end
>> up executing pre-compiled byte-code which has already assumptions about
>> the locals dict built into it.
>>
>> If I'm right (or even if I'm not), what is the right way to achieve my
>> goal: that local assignments get redirected to the supplied dictionary?
>>
> CPython is able to identify all of the local names of a function and, 
> basically, for reasons of efficiency, it uses slots for the local 
> names instead of an actual dict. 'locals()' just returns a dict that 
> represents those local names and their current values, but modifying 
> that dict has no effect on the actual local names. In short, there 
> isn't really a local dict that you can replace.

While I'm sure you're right, it certainly is well hidden:

Python 3.8.2 | packaged by conda-forge | (default, Apr 24 2020, 
07:34:03) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
 >>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, 
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': 
None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
 >>> a =3
 >>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, 
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': 
None, '__annotations__': {}, '__builtins__': <module 'builtins' 
(built-in)>, 'a': 3}
 >>> locals()['a'] = 4
 >>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, 
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': 
None, '__annotations__': {}, '__builtins__': <module 'builtins' 
(built-in)>, 'a': 4}
 >>>

In other words, I can indeed change the value of a local variable 
through the locals() dict. 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:

classMySymbolTable(dict):
defset_type(self,t):
self.type = t
def__getitem__(self,key):
print(f"Requesting key {key} from {self.type} table")
returnsuper().__getitem__(key)
def__setitem__(self,key,value):
print(f"Setting key {key} from {self.type} table to value {value}")
returnsuper().__setitem__(key, value)
defmylocals(func):
defwrapper(*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
deffun1():
print("locals at the beginning:")
print(", ".join(f"{name}: {value}"for name, value inlocals().items()))
print("globals at the beginning:")
print(", ".join(f"{name}: {value}"for name, value inglobals().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 inlocals().items()))
print("globals at the end:")
print(", ".join(f"{name}: {value}"for name, value inglobals().items()))
fun1()

This however still fails:

Setting key xxx from local table to value 123
Setting key yyy from global table to value 42
Requesting key print from global table
locals at the beginning:
Requesting key print from global table
Requesting key locals from global table

Requesting key print from global table
globals at the beginning:
Requesting key print from global table
Requesting key globals from global table
__name__: __main__, __doc__: None, __package__: None, __loader__: 
<_frozen_importlib_external.SourceFileLoader object at 
0x000001CA2DC22640>, __spec__: None, __annotations__: {}, __builtins__: 
<module 'builtins' (built-in)>, __file__: locals_test.py, __cached__: 
None, MySymbolTable: <class '__main__.MySymbolTable'>, mylocals: 
<function mylocals at 0x000001CA2E1391F0>, fun1: <function 
mylocals.<locals>.wrapper at 0x000001CA2E1394C0>, yyy: 42
Requesting key print from global table
Requesting key type from global table
Requesting key locals from global table
Requesting key type from global table
Requesting key globals from global table
fun1 with locals: <class 'dict'> and globals: <class 
'__main__.MySymbolTable'>
Requesting key print from global table
locals at the end:
Requesting key print from global table
Requesting key locals from global table
a: 2, b: 2, c: 5
Requesting key print from global table
globals at the end:
Requesting key print from global table
Requesting key globals from global table
__name__: __main__, __doc__: None, __package__: None, __loader__: 
<_frozen_importlib_external.SourceFileLoader object at 
0x000001CA2DC22640>, __spec__: None, __annotations__: {}, __builtins__: 
<module 'builtins' (built-in)>, __file__: locals_test.py, __cached__: 
None, MySymbolTable: <class '__main__.MySymbolTable'>, mylocals: 
<function mylocals at 0x000001CA2E1391F0>, fun1: <function 
mylocals.<locals>.wrapper at 0x000001CA2E1394C0>, yyy: 42

Notice how 'globals()' has the value 'yyy', while 'locals()' doesn't 
show 'xxx'.

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.

Andras




More information about the Python-list mailing list