为什么PyImport_Import无法从当前目录加载模块?

10

我正在尝试运行嵌入式示例,但是除非我将其明确添加到sys.path,否则无法从当前工作目录加载模块,然后它才能正常工作:

PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append(\".\")"); 

Python难道不应该在当前目录中查找模块吗?

Edit1: 尝试使用以下方式导入模块:

Py_Initialize();
PyRun_SimpleString("import multiply"); 

但它仍然失败并出现以下错误:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named multiply

编辑2:sys.path 文档 中得知:

  

如果脚本目录不可用(例如,如果交互地调用解释器或者从标准输入读取脚本),path[0] 将是一个空字符串,这将指导Python先在当前目录中搜索模块。

不确定“不可用”是什么意思,但是如果我打印sys.path[0],它不是空的:

/usr/lib/pymodules/python2.7

Python应该,你尝试打印os.getcwd()来检查你是否在你认为的位置吗? - Matti Lyra
假设multiply是根据教程在文件中定义的函数,请尝试使用from __main__ import multiply - Matti Lyra
4个回答

19

要使相对导入起作用,您需要调用PySys_SetArgv(int argc, char **argv, int updatepath)。如果updatepath0(有关详细信息,请参见文档),则这将在sys.path中添加正在执行的脚本的路径。

以下代码应该可以解决问题:

#include <Python.h>

int
main(int argc, char *argv[])
{
  Py_SetProgramName(argv[0]);  /* optional but recommended */
  Py_Initialize();
  PySys_SetArgv(argc, argv); // must call this to get sys.argv and relative imports
  PyRun_SimpleString("import os, sys\n"
                     "print sys.argv, \"\\n\".join(sys.path)\n"
                     "print os.getcwd()\n"
                     "import thing\n" // import a relative module
                     "thing.printer()\n");
  Py_Finalize();
  return 0;
}

我认为你应该从文档中包含以下句子,并相应修改你的示例代码:“这些参数与传递给程序 main() 函数的参数类似,不同之处在于第一个条目应该是要执行的脚本文件而不是托管 Python 解释器的可执行文件。” - Sebastian Mach

0

要从当前目录加载模块,有许多选项。

  1. 您需要通过初始化结构配置Python(包括路径配置)。请参阅Python Initialization Configuration
Py_Initialize():
//...
PyConfig config;
PyConfig_InitPythonConfig(&config);
config.module_search_paths_set = 1;
PyWideStringList_Append(&config.module_search_paths, L"."); // append local dir to path
Py_InitializeFromConfig(&config);
//...
pModule = PyImport_Import(pName); // import module

一个简单的选项是在Py_Initialize()之后也运行PySys_SetArgv(0, NULL);,以将当前目录添加到Python的路径中。虽然这个选项自Python 3.11起已被弃用,但在许多情况下仍然是一个简单有效的选项。该选项利用了PySys_SetArgvEx(int argc, char **argv, int updatepath)的行为,当满足以下条件时:
  • 非零的updatepath
  • argc = 0或者argv没有指向现有文件名。

请参见docs。从Python 2.7开始有效。要添加特定目录,argv[0]应包含该目录中Python脚本的路径(例如"NULL")。PySys_SetArgv会为您正确设置updatepath值。尽管如上所述,此选项目前已被弃用


示例项目

从当前目录中加载在Python脚本中定义的模块,并使用输入参数调用该模块中的函数,所有这些都指定给C应用程序。为简单起见,检查已省略。此示例与嵌入C/C ++应用程序中的Python文档页面提供的示例密切相关。

./hello.py

def sayHello1():
  print("Hello from sayHello1!")
  return 1

def sayHello2():
  print("Hello from sayHello2!")
  return 2

./main.c

(为简单起见,已删除检查)

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int main(int argc, char *argv[])
{
  PyObject *pName = NULL;
  PyObject *pModule = NULL;
  PyObject *pFunc = NULL;
  PyObject *pValue = NULL;

  Py_Initialize();
// PySys_SetArgv(0, NULL); // Option 2 [deprecated]

// Option 1 - Python Initialization api
  PyConfig config;
  PyConfig_InitPythonConfig(&config);
  config.module_search_paths_set = 1;
  PyWideStringList_Append(&config.module_search_paths, L".");
  Py_InitializeFromConfig(&config);
// ...

  pName = PyUnicode_FromString(argv[1]);
  pModule = PyImport_Import(pName);
  Py_XDECREF(pName);

  pFunc = PyObject_GetAttrString(pModule, argv[2]);
  pValue = PyObject_CallNoArgs(pFunc); // actual call to the fcn

  Py_XDECREF(pValue);
  Py_XDECREF(pFunc);
  Py_XDECREF(pModule);
  
  Py_Finalize();
  return 0;
}

编译(cmake):

cmake_minimum_required(VERSION 3.2)
project(python_c_api)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

find_package(Python COMPONENTS Interpreter Development)
cmake_path(GET Python_EXECUTABLE PARENT_PATH Python_BIN_DIR) 

set(config_cmd_flags "--cflags")
execute_process(COMMAND ${Python_BIN_DIR}/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}-config ${config_cmd_flags} 
  OUTPUT_VARIABLE Python_FLAGS)

add_executable(python_extension main.c)
target_link_libraries(python_extension PUBLIC python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR})
target_link_directories(python_extension PUBLIC ${Python_LIBRARY_DIRS})
target_include_directories(python_extension PUBLIC ${Python_INCLUDE_DIRS})
set_target_properties(python_extension PROPERTIES 
  OUTPUT_NAME "call"
  RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}
  )
separate_arguments(Python_FLAGS_NORM UNIX_COMMAND "${Python_FLAGS}")
target_compile_options(python_extension PUBLIC ${Python_FLAGS_NORM})


cmake .
cmake --build .

执行:(为简单起见,省略了检查。按照此处所示调用应用程序)。

./call hello sayHello1
Hello from sayHello1!

./call hello sayHello2
Hello from sayHello2!

编辑:此解决方案将当前工作目录附加到搜索路径中,而不是包含嵌入式Python的二进制可执行文件所在的目录。因此,如果从不同目录调用应用程序,则会将该目录附加到搜索路径中。 将包含嵌入式Python解释器的二进制可执行文件的路径添加到搜索路径中并不容易,并且似乎不存在易于跨平台的解决方案。如果需要这样做,建议将应用程序包装在另一个应用程序或脚本中,在正确的目录中运行。


0
我需要在Python 3.5中使用PySys_SetPath来从当前工作目录(cwd)导入模块。
QString qs = QDir::currentPath();
std::wstring ws = qs.toStdWString();
PySys_SetPath(ws.data());

这里的Q是指Qt。


-1

我曾经遇到过完全相同的问题,只需添加 Py_Initialize();Py_Finalize(); 就解决了。

希望这能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接