On 9/1/20 1:42 PM, Chris Angelico wrote:
On Wed, Sep 2, 2020 at 5:00 AM Andras Tantos
<python-l...@andras.tantosonline.com> 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()|
Your code's messed up in the post, so I'm guessing on how this is
actually meant to be written. Most of your whitespace has been
destroyed.

Indeed. That's annoying, lemme see if I can copy-paste something in...

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")
        glob.set_type("global")
        exec(func.__code__, glob, loc)
    return wrapper

@mylocals
def fun1():
    print(f"fun1 with locals: {type(locals())} and globals: {type(globals())}")
    a = 1
    b = 2
    c = 3
    a = b
    c = 5

fun1()

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.
Yes; and furthermore, CPython generally doesn't even use a dictionary
for a function's locals. Instead, it uses cells in a special array;
you can poke around with this by disassembling the code (see the
built-in "dis" module for details) and looking at the
LOAD_FAST/STORE_FAST opcodes.

You might be able to mess with this by using a closure, but local
variable references are generally considered to be an implementation
detail of the function, and it's not going to be easy to mess with
them from the outside reliably. What's the goal here?

ChrisA

I did see these macros in the CPython source code. What it seems to imply is that if I wanted to do what I intend, that is to hook every local variable assignment, I would have to modify the AST. That seems rather more effort than simply supplying my own locals dictionary. I will try to look myself as well, but do you have an inkling as to how complex would it be to convince CPython to optimize locals only if no custom locals is provided during exec?

To my untrained eye, it seems to be an unsafe optimization that breaks the contract of the exec API.

Andras




--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to