简体   繁体   中英

Tell compiler about a local it doesn't know about?

I have a function called let , which modifies the calling namespace to insert a new variable.

def let(**nameValuePair):
    from inspect import stack

    name, value = nameValuePair.items()[0]
    stack()[1][0].f_locals[name] = value
    return value

The idea is it allows you insert assignment statements anywhere you want, even where assignment statements aren't typically allowed in Python (albeit, it requires 5 extra characters for let() .

This works perfectly when called from the global namespace.

>>> let(outside = 'World')
>>> print(outside)
World

This fails with the error NameError: global name 'hello' is not defined :

>>> def breaker():
...     let(hello = 'World')
...     print(hello)
...
>>> breaker()

You might jump to the conclusion that the issue is with my let function. It is not - my function performs perfectly. To show you that:

>>> def fine():
...     let(another = 'World')
...     print(locals()['another'])
...
>>> fine()
World

The issue actually comes from when the Python interpreter compiles breaker . To demonstrate, I'll create another function which does the same thing as breaker , but using an ordinary assignment instead of let , and then I'll decompile both with dis .

>>> def normal():
...     hello = 'World'
...     print(hello)
...
>>> normal()
World

>>> from dis import dis
>>> print(dis(normal))
 0 LOAD_CONST               1 ('World')
 3 STORE_FAST               0 (hello)
 6 LOAD_FAST                0 (hello)
 9 PRINT_ITEM
10 PRINT_NEWLINE
11 LOAD_CONST               0 (None)
14 RETURN_VALUE

>>> print(dis(breaker))
 0 LOAD_GLOBAL              0 (let)
 3 LOAD_CONST               1 ('hello')
 6 LOAD_CONST               2 ('World')
 9 CALL_FUNCTION          256
12 POP_TOP
13 LOAD_GLOBAL              2 (hello)  # <= This is the real problem.
16 PRINT_ITEM
17 PRINT_NEWLINE
18 LOAD_CONST               0 (None)
19 RETURN_VALUE

The problem is, the compiler is looking over breaker , doesn't see that hello gets assigned anywhere in the local scope, and so assumes that it must be a global variable.

What can I do about this? When my function gets called, could I, for example, swap out the problematic line that I indicated and replace it with:

LOAD_GLOBAL 1 (locals)
CALL_FUNCTION 0
LOAD_CONST 1 ('hello')  # Or whatever the name of the variable is
BINARY_SUBSCR

I know that I could quickly fix the immediate problem by simply storing the variable in the global scope, but that could lead to lots of subtle bugs. To name a few:

  1. Global variables get written over.
  2. Objects might persist in memory for far longer than intended.
  3. Something that would be a typo otherwise, is instead valid. (admittedly this one seems quite unlikely).

... Actually, if I can change the compiled code, I could even add a STORE_GLOBAL or DELETE_GLOBAL after RETURN_VALUE , perhaps? Cache the former global variable elsewhere or something?

The documentation for locals() notes the following:

Note The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

I checked and don't see an explicit note in the inspect docs about f_locals but I don't see why it would be any different.

The short version is, you can't do that. You can't do it in 3.x, you couldn't do it in recent versions of 2.x, and I'd be surprised if you could ever reliably do it. You won't be able to do it in the future, either, because local variables are too easy a target for optimization. There's no way one compiler would go for that, much less all the python compilers.

On the other hand, maybe there's another way to do what you're trying to do. What is that?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM