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

标签 python c++ python-c-api 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__ 到底是什么。

最佳答案

所以最初的问题(在评论中解决)是具有不同的全局变量和局部变量导致它被评估为好像它在一个类中(参见 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 C:
    import sys
    def f():
        print(sys.version)
        # but C.sys.version would work

因此您的代码不起作用。


pybind11::globals 返回字典 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__ 但一般来说,“当前执行框架”可能会因调用而异,具体取决于您跨越 C++-Python 边界的程度。例如,如果 Python 函数调用修改 globals() 的 C++ 函数,那么您修改的具体内容取决于调用者。

我的建议是创建一个新的空 dict 并将其传递给 exec。这可确保您在全新的非共享命名空间中运行。


__main__ 只是一个 special module that represents the "top level code environment" .就像任何模块都有一个 __dict__。在 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__")) 来复制内置字典并改用它。但是,我认为没有必要)

关于python - 如何使用 pybind11 从 C++ 调用 python 函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70846440/

相关文章:

python - 从 Django/Python 上传特定文件

python - 如何使用 python API 连接到 Elasticsearch 中的特定集群

Python-C api并发问题

python - 我如何从 Python C 代码断言?

c++ - 我的 C++ 编译器会优化我的代码吗?

python - PyErr_CheckSignals 没有接收到信号

python - python中的正则表达式

python - 在 Python 中使用 Requests 库发送 "User-agent"

c++ - 如何删除C++字符串中所有出现的字符

c++ - vector 下标超出范围错误,C++