osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

exec and globals and locals ...


Eko palypse wrote:

> Am Donnerstag, 19. September 2019 18:31:43 UTC+2 schrieb Peter Otten:
>> Eko palypse wrote:
>> 
>> > No, I have to correct myself
>> > 
>> > x = 5
>> > def f1():
>> >     exec("x = x + 1; print('f1 in:', x)")
>> >     return x
>> > print('f1 out', f1())
>> > 
>> > results in the same, for me confusing, results.
>> > 
>> > f1 in: 6
>> > f1 out 5
>> 
>> Inside a function exec assignments go to a *copy* of the local namespace.
>> Also LOAD_NAME is used to look up names. Therefore you can read and then
>> shade a global name with its local namesake.
>> 
>> Inside a function the namespace is determined statically. As f1() has no
>> assignment to x (code inside exec(...) is not considered) x is looked up
>> in directly the global namespace using LOAD_GLOBAL.
>> 
>> If you want to access the local namespace used by exec() you have to
>> provide one explicitly:
>> 
>> >>> x = 5
>> >>> def f():
>> ...     ns = {}
>> ...     exec("x += 1", globals(), ns)
>> ...     return ns["x"]
>> ...
>> >>> f()
>> 6
>> >>> x
>> 5
>> 
>> By the way, in Python 2 where exec was a statement the local namespace is
>> shared:
>> 
>> >>> x = 5
>> >>> def f():
>> ...     exec "x += 1"
>> ...     return x
>> ...
>> >>> f()
>> 6
>> >>> x
>> 5
> 
> Sorry, missed that.
> Thank you, may I ask you how I could have come myself
> to that explanation? What do I have to read to get that understanding?
> Hopefully you don't say read the C code, because that is something
> I tried but failed miserably.

https://docs.python.org/3/library/functions.html#exec
https://docs.python.org/3/reference/executionmodel.html#naming-and-binding

(I had to google for the second link.)

I usually experiment with code and the disassembler. I find its output quite 
readable, 

>>> def f(): x += 1
... 
>>> import dis
>>> dis.dis(f)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD
              7 STORE_FAST               0 (x)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE
>>> dis.dis(compile("x += 1", "<nofile>", "exec"))
  1           0 LOAD_NAME                0 (x)
              3 LOAD_CONST               0 (1)
              6 INPLACE_ADD
              7 STORE_NAME               0 (x)
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE

and you can limit yourself to small snippets.