PyEval_GetLocals returns globals?
Question: I am trying to access the python locals from the constructor of a C++ class
exported with boost.python, but PyEval_GetLocals() seems to return the global
instead of local dict. An example: in C++ I do

    
    
    class X {
       public:
          X() {
             boost::python::object locals(boost::python::borrowed(PyEval_GetLocals()));
             locals["xyz"]=42
          }
    };
    
    BOOST_PYTHON_MODULE(test) {
       class_<X>("X", init<>());
    }
    

If I now do in Python

    
    
    x = X()
    print(xyz)
    

I get '42' as output (as expected). However, the same happens with

    
    
    def fun():
        x = X()
    
    print(xyz)
    

which also prints '42', despite the fact that 'fun()' has created a new scope.
I would have expected the 'xyz' name to have gone out of scope again after
fun() exits, and thus be left with an undefined 'xyz' by the time I reach the
print statement.

What am I doing wrong? Is there any way to get access to the local names from
within a C++ object or function?


Answer: I think the testcase may be resulting in a false positive. Is it possible that
you forgot to `del` the `xyz` variable prior to calling `fun()`?

Defining a function creates a variable local to the current scope that refers
to the function object. For example:

    
    
    def fun():
        x = X()
    

Creates a `function` object that is referenced by the `fun` variable within
the current scope. If the function is invoked, then (by default) a new local
scope is created, where in the object returned from `X()` will be referenced
by `x` within the local scope of the function and not within the caller's
frame's `locals()`.

* * *

Here is an example based on the original code:

    
    
    #include <boost/python.hpp>
    
    /// @brief Mockup types.
    struct X
    {
      X()
      {
        // Borrow a reference from the locals dictionary to create a handle.
        // If PyEval_GetLocals() returns NULL, then Boost.Python will throw.
        namespace python = boost::python;
        python::object locals(python::borrowed(PyEval_GetLocals()));
    
        // Inject a reference to the int(42) object as 'xyz' into the 
        // frame's local variables.
        locals["xyz"] = 42;
      }
    };
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
      python::class_<X>("X", python::init<>());
    }
    

Interactive usage that asserts visibility:

    
    
    >>> import example
    >>> def fun():
    ...     assert('xyz' not in locals())
    ...     x = example.X()
    ...     assert('xyz' in locals())
    ...     assert('xyz' not in globals())
    ... 
    >>> assert('xyz' not in globals())
    >>> fun()
    >>> assert('xyz' not in globals())
    >>> x = example.X()
    >>> assert('xyz' in globals())
    >>> del xyz
    >>> fun()
    >>> assert('xyz' not in globals())
    

* * *

For completeness, a
[`FuncionType`](http://docs.python.org/2/library/types.html#types.FunctionType)
can be constructed with a
[`CodeType`](http://docs.python.org/2/library/types.html#types.CodeType) whose
[`co_flags`](http://docs.python.org/2/library/inspect.html#types-and-members)
does not have the `newlocals` flag set, causing the frame used for a
function's invocation to have its `locals()` return the same as `globals()`.
Here is an interactive usage example demonstrating this:

    
    
    >>> def fun():
    ...     x = 42
    ...     print "local id in fun:", id(locals())
    ... 
    >>> import types
    >>> def no_locals(fn):
    ...     func_code = fn.func_code
    ...     return types.FunctionType(
    ...         types.CodeType(
    ...             func_code.co_argcount,
    ...             func_code.co_nlocals,
    ...             func_code.co_stacksize,
    ...             func_code.co_flags & ~2, # disable newlocals
    ...             func_code.co_code,
    ...             func_code.co_consts,
    ...             func_code.co_names,
    ...             func_code.co_varnames,
    ...             func_code.co_filename,
    ...             func_code.co_name,
    ...             func_code.co_firstlineno,
    ...             func_code.co_lnotab),
    ...         globals())
    ... 
    >>> id(globals())
    3075430164L
    >>> assert('x' not in locals())
    >>> fun()
    local id in fun: 3074819588
    >>> assert('x' not in locals())
    >>> fun = no_locals(fun) # disable newlocals flag for fun
    >>> assert('x' not in locals())
    >>> fun()
    local id in fun: 3075430164
    >>> assert('x' in locals())
    >>> x
    42
    

Even after disabling the `newlocals` flag, I had to invoke `locals()` within
`fun()` to observe `x` being inserted into the global symbol table.

