请考虑以下C++ pybind11程序:
#include <pybind11/embed.h>
namespace py = pybind11;
int main() {
py::scoped_interpreter guard{};
py::dict locals;
py::exec(R"(
import sys
def f():
print(sys.version)
)", py::globals(), locals);
locals["f"](); // <-- ERROR
}
py::exec
调用和封闭的import sys
调用都成功,但调用locals["f"]()
引发异常:
NameError: name 'sys' is not defined
在函数的第一行f
上。
预期的行为是程序打印python系统版本。
有什么想法吗?
更新:
我按照@DavidW的建议修改了程序:
#include <pybind11/embed.h>
namespace py = pybind11;
int main() {
py::scoped_interpreter guard{};
py::dict globals = py::globals();
py::exec(R"(
import sys
def f():
print(sys.version)
)", globals, globals);
globals["f"](); // <-- WORKS NOW
}
现在起作用了。
我不能百分之百确定我明白发生了什么,所以我希望得到一个解释。
(特别是,对通用globals
/ locals
字典的修改是否会影响任何其他脚本。是否有一些全局字典是exec
脚本正在修改的python解释器的一部分?或者py::globals()
是否获取该状态的副本,以便将已执行的脚本与其他脚本隔离?)
更新2
因此,全局和局部变量相同的字典是默认状态:
$ python
>>> globals() == locals()
True
>>> from __main__ import __dict__ as x
>>> x == globals()
True
>>> x == locals()
True
...and表示两者的默认值是__main__.__dict__
,不管是什么(__main__.__dict__
是py::globals()
返回的字典)。
我仍然不清楚__main__.__dict__
到底是什么。
发布于 2022-01-26 20:05:56
因此,最初的问题(在注释中得到了解决)是,具有不同的全局值和局部变量使得计算结果就像在类中一样(参见exec
-- PyBind11函数的行为基本相同):
请记住,在模块级别上,全局词和局部变量是相同的字典。如果exec以全局和局部变量的形式获得两个单独的对象,则代码将被执行,就像它嵌入到类定义中一样。
函数作用域不查找其封闭类中定义的变量--这是行不通的。
class C:
import sys
def f():
print(sys.version)
# but C.sys.version would work
因此,您的代码无法工作。
pybind11::globals
返回一个字典在很多地方都有
返回表示当前执行框架中的全局变量的字典,如果没有框架(通常是在嵌入解释器时),则返回
__main__.__dict__
。
因此,对本词典的任何修改都将是持久的,并且保持不变(这可能不是您想要的!)在您的例子中,它可能是__main__.__dict__
,但通常“当前执行框架”可能会根据跨越C++-Python边界的程度而改变。例如,如果一个Python函数调用一个修改globals()
的globals()
函数,那么您所修改的内容完全取决于调用者。
我的建议是创建一个新的空dict
,并将其传递给exec
。这确保您在一个新的、非共享的命名空间中运行。
__main__
只是一个表示“顶级代码环境”的特殊模块。就像任何模块都有一个__dict__
。在REPL中运行时,它是全局范围。从pybind11
的角度来看,它只是一个带有dict的模块,您可能不应该随意地将它写入其中(除非您真的决定有意地将一些东西放在那里,以便在全球共享它)。
关于__builtins__
:Python exec
函数的文档如下
如果全局字典不包含键
__builtins__
的值,则在该键下插入对内置模块内置字典的引用。这样,您就可以通过将自己的__builtins__
字典插入全局,然后将其传递给exec()
,来控制执行的代码可以使用哪些内置程序。
看看PyRun_String
的代码,Pybind11 exec
调用的代码,同样适用于这里。
这本词典似乎足以正确查找内建函数。(如果不是这样的话,那么您可以始终使用pybind11::dict(pybind11::module::import("builtins").attr("__dict__"))
来复制内置数据集并使用它。但是,我不认为这是必要的
https://stackoverflow.com/questions/70846440
复制相似问题