ModuleNotFoundError setup.py 使用已编译的 pyc 模块

ModuleNotFoundError with setup.py using a compiled pyc module

我通常可以导入已编译的 .pyc 模块以及 .py,但是当尝试使用 setup.py 打包一个简单的项目时,我得到 ModuleNotFoundError 已编译 .pyc 模块的异常。因为只有在使用 setup.py 时才会发生这种情况,否则工作正常,我不知道是否需要 setup.py 才能使这项工作正常进行。

项目结构目前是这样的:

proj
├── FAILING.pyc
├── __init__.py
├── aux/
│   ├── __init__.py
│   └── aux.c
└── main.py

setup.py:

from setuptools import setup, Extension, find_packages

DISTNAME = 'proj'

INSTALL_REQUIRES = [
        'cython>=0.29.13',
        'numpy>=1.16.4'
]
PYTHON_REQUIRES = '>=3.6'

ENTRY_POINTS = {
    'console_scripts': ['proj = proj.main:main']
}

def setup_extensions(metadata):
    ext_modules = [Extension('proj.aux.aux', sources=['proj/aux/aux.c'])]
    metadata['ext_modules'] = ext_modules

def setup_package():
    metadata = dict(
        name=DISTNAME,
        version='0.1',
        package_dir={'': '.'},
        packages=find_packages(),
        entry_points=ENTRY_POINTS,
        python_requires=PYTHON_REQUIRES,
        install_requires=INSTALL_REQUIRES,
        zip_safe=False,
    )
    setup_extensions(metadata)
    setup(**metadata)


if __name__ == '__main__':
    setup_package()

main.py:

#!/usr/bin/env python3

import proj.aux.aux as aux
import proj.FAILING

def main():
    print('Hello World')

如果我只是尝试在 repl 上导入 FAILING.pyc,一切都会按预期进行:

>>> import FAILING
>>>

但是如果我先 运行 python3 setup.py intall 然后调用 proj 我会收到以下错误:

$ proj
Traceback (most recent call last):
  File "/path/to/bin/proj", line 11, in <module>
    load_entry_point('proj==0.1', 'console_scripts', 'proj')()
  File "/path/to/lib/python3.8/site-packages/pkg_resources/__init__.py", line 489, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/path/to/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2852, in load_entry_point
    return ep.load()
  File "/path/to/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2443, in load
    return self.resolve()
  File "/path/to/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2449, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "/path/to/lib/python3.8/site-packages/proj-0.1-py3.8-macosx-10.14-x86_64.egg/proj/main.py", line 4, in <module>
    import proj.FAILING
ModuleNotFoundError: No module named 'proj.FAILING'

我也在 virtualenv 环境中 运行 宁此,虽然我猜这与错误无关。

我哪里做错了,或者我需要更改什么才能使这项工作正常进行?

这是一个小演示,它构建了一个包含 .pyc 文件的源代码分发和 wheel

请注意,我已经从您的示例中删除了大部分内容,因为 cython 内容与您的问题无关

set -euxo pipefail

rm -rf dist testpkg setup.py

cat > setup.py <<EOF
from setuptools import setup

setup(
    name='foo',
    version='1',
    packages=['testpkg'],
    package_data={'testpkg': ['*.pyc']},
)
EOF

mkdir testpkg
touch testpkg/__init__.py
echo 'print("hello hello world")' > testpkg/mod.py

python3 -m compileall -b testpkg/mod.py
rm testpkg/mod.py

python3 setup.py sdist bdist_wheel
tar --list -f dist/*.tar.gz
unzip -l dist/*.whl

关于 setup.py 有几点需要注意:

  • 我在 packages 中包含了包含 .pyc 文件的包 -- 我本可以使用 setuptools.find_packages 代替,但这更简单
  • .pyc 文件包含为 package_data -- 默认情况下 pyc 文件不打包,因为它们通常是剩余的构建工件
  • 我需要使用 python3 -m compileall
  • -b 标志将 pyc 编译到遗留位置
  • 即使是“已编译”的 pyc 文件也不会混淆实际代码,例如可以使用 dis 恢复它——当你在这里谈论“已编译”时,它只是意味着它已经被转换成(还是比较高级的)python字节码

您可以从源分发版或 wheel 安装包。

例如运行脚本:

$ bash t.sh
+ rm -rf dist testpkg setup.py
+ cat
+ mkdir testpkg
+ touch testpkg/__init__.py
+ echo 'print("hello hello world")'
+ python3 -m compileall -b testpkg/mod.py
Compiling 'testpkg/mod.py'...
+ rm testpkg/mod.py
+ python3 setup.py sdist bdist_wheel
running sdist
running egg_info
writing foo.egg-info/PKG-INFO
writing dependency_links to foo.egg-info/dependency_links.txt
writing top-level names to foo.egg-info/top_level.txt
reading manifest file 'foo.egg-info/SOURCES.txt'
writing manifest file 'foo.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt, README.md

running check
warning: check: missing required meta-data: url

warning: check: missing meta-data: either (author and author_email) or (maintainer and maintainer_email) must be supplied

creating foo-1
creating foo-1/foo.egg-info
creating foo-1/testpkg
copying files to foo-1...
copying setup.py -> foo-1
copying foo.egg-info/PKG-INFO -> foo-1/foo.egg-info
copying foo.egg-info/SOURCES.txt -> foo-1/foo.egg-info
copying foo.egg-info/dependency_links.txt -> foo-1/foo.egg-info
copying foo.egg-info/top_level.txt -> foo-1/foo.egg-info
copying testpkg/__init__.py -> foo-1/testpkg
copying testpkg/mod.pyc -> foo-1/testpkg
Writing foo-1/setup.cfg
creating dist
Creating tar archive
removing 'foo-1' (and everything under it)
running bdist_wheel
running build
running build_py
copying testpkg/__init__.py -> build/lib/testpkg
copying testpkg/mod.pyc -> build/lib/testpkg
installing to build/bdist.linux-x86_64/wheel
running install
running install_lib
creating build/bdist.linux-x86_64/wheel
creating build/bdist.linux-x86_64/wheel/testpkg
copying build/lib/testpkg/__init__.py -> build/bdist.linux-x86_64/wheel/testpkg
copying build/lib/testpkg/mod.pyc -> build/bdist.linux-x86_64/wheel/testpkg
running install_egg_info
Copying foo.egg-info to build/bdist.linux-x86_64/wheel/foo-1-py3.8.egg-info
running install_scripts
creating build/bdist.linux-x86_64/wheel/foo-1.dist-info/WHEEL
creating 'dist/foo-1-py3-none-any.whl' and adding 'build/bdist.linux-x86_64/wheel' to it
adding 'testpkg/__init__.py'
adding 'testpkg/mod.pyc'
adding 'foo-1.dist-info/METADATA'
adding 'foo-1.dist-info/WHEEL'
adding 'foo-1.dist-info/top_level.txt'
adding 'foo-1.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel
+ tar --list -f dist/foo-1.tar.gz
foo-1/
foo-1/PKG-INFO
foo-1/foo.egg-info/
foo-1/foo.egg-info/PKG-INFO
foo-1/foo.egg-info/SOURCES.txt
foo-1/foo.egg-info/dependency_links.txt
foo-1/foo.egg-info/top_level.txt
foo-1/setup.cfg
foo-1/setup.py
foo-1/testpkg/
foo-1/testpkg/__init__.py
foo-1/testpkg/mod.pyc
+ unzip -l dist/foo-1-py3-none-any.whl
Archive:  dist/foo-1-py3-none-any.whl
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2021-02-17 22:27   testpkg/__init__.py
      136  2021-02-17 22:27   testpkg/mod.pyc
      163  2021-02-17 22:28   foo-1.dist-info/METADATA
       92  2021-02-17 22:28   foo-1.dist-info/WHEEL
        8  2021-02-17 22:27   foo-1.dist-info/top_level.txt
      408  2021-02-17 22:28   foo-1.dist-info/RECORD
---------                     -------
      807                     6 files

之后,我可以安装这个包并使用它:

$ mkdir t
$ cd t
$ virtualenv venv
...
$ . venv/bin/activate
$ pip install ../dist/foo-1-py3-none-any.whl
...
$ python3 -c 'import testpkg.mod'
hello hello world