子目录结构破坏了 C++ 扩展构建

Subdirectory structure breaks C++ extension build

我似乎无法解决使用子目录结构时导入 C++ 扩展模块不再起作用的问题。

下面的两个案例展示了一个简单的工作案例和一个我终生无法工作的稍微改动的案例。

场景 1(有效)

项目树:

demo_cext/         # Current working directory for all of this
├── _cmodule.cc
└── setup.py

setup.py 的内容:

import setuptools

module = setuptools.Extension("_cmod", sources=["_cmodule.cc"], language="c++")

if __name__ == "__main__":
    setuptools.setup(name="cmod", ext_modules=[module])

_cmodule.cc 的内容,基本上是 C 扩展的 hello world,它创建了一个不带参数的函数 foo() 和 returns 5.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
foo(PyObject *self, PyObject *args) {
    /* noargs() */
    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }
    return PyLong_FromLong(5);
}

static PyMethodDef FooMethods[] = {
    {"foo",  foo,      METH_VARARGS,  "Do the foo"},
    {NULL,   NULL,     0,             NULL}
};

PyDoc_STRVAR(module_doc, "This is the module docstring.");
static struct PyModuleDef cmodule = {
    PyModuleDef_HEAD_INIT,
    "cmod",
    module_doc,
    -1,
    FooMethods,
    NULL,
    NULL,
    NULL,
    NULL
};

PyMODINIT_FUNC
PyInit__cmod(void) {
    PyObject* m = PyModule_Create(&cmodule);
    if (m == NULL) {
        return NULL;
    }
    return m;
}

整个事情就像一个魅力:

$ python3 -V
Python 3.7.4
$ python3 setup.py build install
>>> import _cmod
>>> _cmod.foo()
5

场景 2(损坏)

将项目稍微调整为 the Python docs 中具体介绍的布局。

$ rm -rf build/ dist/ cmod.egg-info/ && \
> mkdir cmod/ && touch cmod/__init__.py && \
> mv _cmodule.cc cmod/

给我留下:

demo_cext/         # Current working directory for all of this
├── cmod
│   ├── __init__.py
│   └── _cmodule.cc
└── setup.py

我稍微改一下setup.py

import setuptools

module = setuptools.Extension("cmod._cmod", sources=["cmod/_cmodule.cc"], language="c++")

if __name__ == "__main__":
    setuptools.setup(name="cmod", ext_modules=[module])

再接再厉运行:

$ python3 setup.py build install

尝试导入模块让我得到:

>>> from cmod import _cmod
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name '_cmod' from 'cmod' (.../demo_cext/cmod/__init__.py)
>>> import cmod._cmod
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'cmod._cmod'

我这里有什么问题吗?我敢肯定,命名约定很简单。 这似乎直接面对 how this is all laid out in the Python docs

尝试在启动前更改目录 Python(例如 cd ..)。您的回溯表明您在 demo_cext/cmod 中找到 cmod (您的源目录,而不是安装目录),但是构建的扩展不会在那里(它会在 demo_cext/build 中的某个地方build 之后,以及 install 之后的系统站点包目录)。

如果只在 Python 3 上使用,另一种解决方案是删除 cmod/__init__.py 文件;由于 PEP 420,空 __init__.py 文件不需要在 Python 3 中制作包。通过删除 __init__.py,隐式命名空间包装应该接管,它应该自动搜索 all cmod 包在 sys.path 中的子模块正在尝试导入。