eval's local arguement ignored?

Alex Martelli aleax at aleax.it
Tue Sep 30 11:57:12 EDT 2003


Jean-S?bastien Bolduc wrote:
   ...
> is this: I'm writing a class such as this:
> 
> class Foo:
>   def __init__(self, fnc, **params):
>     ...
>   def evaluate(self, val)
>     ...
> 
> That has to be instantiated as, e.g.:
> 
> x = Foo( 'lambda x : a*x', dict( a = 2. ) )

Hmmm.  'a' is NOT a local in that lambda -- it's a global.  locals
are [a] arguments, plus [b] variables that get re-bound within the
function (which, in a lambda, means only control variables used in
list comprehensions), PERIOD.  That, if you want, is the DEFINITION
of "local variable" in Python.

So, that dict had better be in the GLOBALS, or else variable 'a' will
of course not be fond.


> so that "x.evaluate( 5. )" will return, in this case, "2.*5.".
> 
> The approach you describe will certainly work, but the thing is that
> this class will have to be used by people who don't necessarily know
> Python (yet). So I would really like the lambda function's parameter
> list to always be the same, whatever you put on its RHS.
> 
> Once again, I don't understand why the "eval", as described above with
> "globals" and "locals" arguments specified, will not work. Is there a
> way to modify a function's closure?

You may play with closures, yes (not really modifying a function's
closure but rather building a new function object with a code taken
from one place and a closure from another, say), but I'm not sure it
would help you.

Consider the following...:

>>> import dis
>>> thefunc = lambda x: a * x
>>> dis.dis(thefunc)
  1           0 LOAD_GLOBAL              0 (a)
              3 LOAD_FAST                0 (x)
              6 BINARY_MULTIPLY
              7 RETURN_VALUE
>>>

Even if you're not familiar with Python's bytecode, I hope the simple
disassembly above is clear enough anyway: the value of 'a' is "loaded"
(onto the Python virtual machine's stack) as a *GLOBAL*, the value of
'x' is loaded via the "fast" (locals-only) route, then the multiply
happens (using the two top entries of the stack and pushing the result)
and the result is returned as the function's value.

Now, since you have the lambda available AS A STRING, you may consider:

>>> def ff(a=99):
...   return lambda x: x * a
...
>>> thefunc1 = ff()
>>> dis.dis(thefunc1)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_DEREF               0 (a)
              6 BINARY_MULTIPLY
              7 RETURN_VALUE
>>>

Here, the Python compiler knows that a IS a local (an argument, in
this example) in an enclosing function, therefore it loads 'a' with
LOAD_DEREF, *NOT* with LOAD_GLOBAL.  Because of this, and of this
only, thefunc1.closure does matter (it's a cell referencing that
int value, 99 in this case).  Perhaps that might help, though it
still takes QUITE a bit of work to exploit it.

However, I do not understand why you should fight "the system" (the
fact that the Python compiler KNOWS that 'a' is global in the first
and simpler use) -- why not just supply that dict you have as (some
part of) the globals...?


Alex





More information about the Python-list mailing list