Is there any way to affect locals at runtime?

2019-09-08 16:07发布

问题:

I actually want to create a new local. I know it sounds dubious, but I think I have a nice use case for this. Essentially my problem is that this code throws "NameError: global name 'eggs' is not defined" when I try to print eggs:

def f():
    import inspect
    frame_who_called = inspect.stack()[1][0]
    frame_who_called.f_locals['eggs'] = 123

def g():
    f()
    print(eggs)

g()

I found this old thing: http://mail.python.org/pipermail/python-dev/2005-January/051018.html

Which would mean I might be able to do it using ctypes and calling some secret function, though they only talked about updating a value. But maybe there's an easier way?

回答1:

As Greg Hewgill mentioned in a comment on the question, I answered another question about modifying locals in Python 3, and I'll give a bit of a recap here.

There is a post on the Python 3 bug list about this issue -- it's somewhat poorly documented in the Python 3 manuals. Python 3 uses an array for locals instead of a dictionary like in Python 2 -- the advantage is a faster lookup time for local variables (Lua does this too). Basically, the array is defined at "bytecode-compile-time" and cannot be modified at runtime.

See specifically the last paragraph in Georg Brandl's post on the bug list for finer details about why this cannot (and probably never will) work in Python 3.



回答2:

I am highly curious as to your use case. Why on Earth are you trying to poke a new local into the caller's frame, rather than simply doing something like this:

def f():
    return 123

def g():
    eggs = f()
    print(eggs)

After all, you can return a tuple with as many values as you like:

def f():
    return 123, 456, 789

def g():
    eggs, ham, bacon = f()
    print(eggs, ham, bacon)


回答3:

In Python 2.*, you can get such code to work by defeating the normal optimization of locals:

>>> def g():
...   exec 'pass'
...   f()
...   print(eggs)

The presence of an exec statement causes Python 2 to compile g in a totally non-optimized fashion, so locals are in a dict instead of in an array as they normally would be. (The performance hit can be considerable).

This "de-optimization" does not exist in Python 3, where exec is not a statement any more (not even a keyword, just a function) -- even putting parentheses after it doesn't help...:

>>> def x():
...   exec('a=23')
...   print(a)
... 
>>> x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in x
NameError: global name 'a' is not defined
>>> 

i.e., not even exec can now "create locals" that were not know at def-time (i.e., when the compiler did its pass to turn the function body into bytecode).

Your best bet would be to give up. Second best would be to have your f function inject new names into the caller's globals -- those are still a dict, after all.