ByteCode Questions

Skip Montanaro skip at pobox.com
Tue Aug 21 12:51:56 EDT 2001


    ...
    Stefan> def Working():
    Stefan>     EvalInCaller("a = 2\n")
    Stefan>     exec("")
    Stefan>     print a

    Stefan> def NotWorking():
    Stefan>     EvalInCaller("a = 2\n")
    Stefan>     print a
    ...

    Stefan> So the question is why does Python take the LOAD_GLOBAL byte
    Stefan> code in the not working function and the LOAD_NAME bytecode in
    Stefan> the working version. How can I force the LOAD_NAME bytecode
    Stefan> dynamically or must I exchange all LOAD_GLOBAL to LOAD_NAME in
    Stefan> the codeblock used.

Python (before 2.1 came out with nested scopes, at least) searches three
namespaces for variables: the current function, the module in which the
function resides, then the module containing all builtin functions.  There
are three types of LOAD_* instructions:

    LOAD_FAST - when the compiler knows the variable reference is to a
    variable local to the current function.  Values are found in a local
    variables array.

    LOAD_GLOBAL - when the compiler knows the variable reference is not to a
    variable local to the current function.  Values are found by dictionary
    lookup, first in the module's globals, then in the builtins.

    LOAD_NAME - when the compiler can't tell.  Values are found by
    dictionary lookup first in the locals dict, then in the globals dict,
    then in the builtins.

When you execute an exec statement, it can put symbols in whatever
namespaces you feed it as the globals and locals.  (You shouldn't use parens
with exec, BTW.  It's a statement, not a function.  Also, exec's that don't
list explicit globals and locals are not a great idea.)  The compiler
short-circuits local variable lookup by indexing into an array in the
current frame object.  That array is of a fixed size (determined at
compile-time), so the compiler can only allocate an array referencing the
objects it knows are local to the current function.  There is also a local
variable dict, but in most cases (except when LOAD_NAME is needed) it's not
needed.

Your EvalInCaller function is effectively putting an exec statement into the
body of your function, but hiding it from the compiler, so the "print a"
statement generates a LOAD_GLOBAL bytecode to find "a".  (It knows "a" was
never set in the function, so it *must* be in the module globals or the
builtins as far as the compiler is concerned.)  When you add an empty exec
statement to the Working function, it signals the bytecode compiler that any
variables the function references that weren't set within the function's
body must be found the slowest way (LOAD_NAME).  Using "from mod import *"
within a function has the same cache-busting effect.  (In 2.2, you get a
SyntaxWarning error about that.  I think it will be elevated to an error in
2.3.)

All that said, I'm not sure there's a good solution for your problem.

-- 
Skip Montanaro (skip at pobox.com)
http://www.mojam.com/
http://www.musi-cal.com/




More information about the Python-list mailing list