如何使用 pybind11 从 C++ 调用 python 函数?

How to call a python function from C++ with pybind11?

请考虑以下 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
}

现在可以使用了。

我不是 100% 确定我明白发生了什么,所以我希望得到解释。

(特别是对通用 globals / locals 字典的修改会影响任何其他脚本。是否有一些全局字典是 python 解释器的一部分 exec 脚本正在修改?还是 py::globals() 获取该状态的副本以便执行的脚本与其他脚本隔离?)

更新 2:

所以看起来全局变量和局部变量是同一个字典是默认状态:

$ python
>>> globals() == locals()
True
>>> from __main__ import __dict__ as x
>>> x == globals()
True
>>> x == locals()
True

...并且两者的默认值是 __main__.__dict__,不管它是什么(__main__.__dict__py::globals() 返回的字典)

我还是不清楚 __main__.__dict__ 到底是什么。

所以最初的问题(在评论中解决)是具有不同的全局变量和局部变量导致它被评估为好像它在 class 中(参见 the Python documentation for exec - PyBind11 函数的行为基本相同):

Remember that at the module level, globals and locals are the same dictionary. If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition.

函数作用域不查找在其封闭 class 中定义的变量 - 这是行不通的

class C:
    import sys
    def f():
        print(sys.version)
        # but C.sys.version would work

因此您的代码不起作用。


pybind11::globals returns 字典 that's shared in a number of places:

Return a dictionary representing the global variables in the current execution frame, or __main__.__dict__ if there is no frame (usually when the interpreter is embedded).

因此对这本词典的任何修改都将持久存在(这可能不是您想要的!)。在您的情况下,它可能是 __main__.__dict__,但通常“当前执行框架”可能会从 call-to-call 改变,具体取决于您跨越 C++-Python 边界的程度。例如,如果 Python 函数调用修改 globals() 的 C++ 函数,那么您修改的具体内容取决于调用者。

我的建议是创建一个新的空 dict 并将其传递给 exec。这确保您 运行 在一个新的 non-shared 命名空间中。


__main__ 只是一个 special module that represents the "top level code environment"。就像任何模块一样,都有一个 __dict__。当 运行ning 在 REPL 中时,它是全局范围。从 pybind11 的角度来看,它只是一个带有 dict 的模块,您可能不应该随便写入它(除非您真的决定要故意将一些东西放在那里以在全球范围内共享)。


关于 __builtins__:Python exec 函数的文档说

If the globals dictionary does not contain a value for the key __builtins__, a reference to the dictionary of the built-in module builtins is inserted under that key. That way you can control what builtins are available to the executed code by inserting your own __builtins__ dictionary into globals before passing it to exec().

并查看 Pybind11 exec 调用的 PyRun_String 的代码,同样适用于那里。

这本字典似乎足以让内置函数被正确查找。 (如果不是这种情况,那么您总是可以 pybind11::dict(pybind11::module::import("builtins").attr("__dict__")) 复制内置字典并使用它。但是,我认为没有必要)