PyEval_GetLocals 返回全局变量?

1 投票
1 回答
759 浏览
提问于 2025-04-17 21:32

我正在尝试从一个用boost.python导出的C++类的构造函数中访问Python的局部变量,但发现PyEval_GetLocals()似乎返回的是全局变量,而不是局部变量字典。举个例子,在C++中我这样做:

class X {
   public:
      X() {
         boost::python::object locals(boost::python::borrowed(PyEval_GetLocals()));
         locals["xyz"]=42
      }
};

BOOST_PYTHON_MODULE(test) {
   class_<X>("X", init<>());
}

如果我现在在Python中这样做:

x = X()
print(xyz)

我得到的输出是'42'(这是预期的结果)。然而,使用下面的代码:

def fun():
    x = X()

print(xyz)

同样也会打印'42',尽管'fun()'创建了一个新的作用域。我本来以为在'fun()'执行完后,'xyz'这个名字应该就不再可用了,所以当我到达打印语句时,'xyz'应该是未定义的。

我哪里做错了?有没有办法在C++对象或函数中访问局部变量名?

1 个回答

1

我觉得这个测试用例可能出现了误判。你有没有可能在调用 fun() 之前忘记删除 xyz 这个变量呢?

定义一个函数会在当前的作用域内创建一个变量,这个变量指向这个函数的对象。举个例子:

def fun():
    x = X()

这段代码创建了一个 function 对象,这个对象在当前作用域内被 fun 变量引用。如果调用这个函数,默认情况下会创建一个新的局部作用域,在这个局部作用域内,从 X() 返回的对象会被 x 引用,而不是在调用者的环境中的 locals()


下面是一个基于原始代码的例子:

#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<>());
}

交互式使用,确保可见性:

>>> 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())

为了完整起见,可以构造一个 FuncionType,它使用一个 CodeType,这个 CodeTypeco_flags 没有设置 newlocals 标志,这样在调用函数时使用的框架的 locals() 返回的结果和 globals() 是一样的。下面是一个演示这个的交互式使用例子:

>>> 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

即使在禁用 newlocals 标志后,我仍然需要在 fun() 内部调用 locals(),才能看到 x 被插入到全局符号表中。

撰写回答