首页 > 解决方案 > 通过 setup.py 安装的程序中的 Python 导入

问题描述

考虑一个具有以下结构的模型项目:

.
├── mod1
│   ├── good_day.py
│   ├── __init__.py
│   └── say_hello.py
└── setup.py

say_hello.py包含一个带有方法的类:

class SayHello():

    def sayIt(self):
        print("Hello World!") 

good_day.pySayHello是一个使用类的小程序:

#!/usr/bin/python3

from say_hello import SayHello

hello = SayHello()
hello.sayIt()

print("And have a good day!")

该程序执行预期的操作:

$ ./good_day.py 
Hello World!
And have a good day!

现在想象这个程序要以一个包的形式分发,这就是setup.py进来的地方:

#!/usr/bin/python3
# coding=utf8

from setuptools import setup, find_packages

setup(
    name = "good-day",
    version = "0.1",
    packages = find_packages(),
    entry_points={
        'console_scripts': [
            'good_day=mod1.good_day',
        ],
    },
)

但是在虚拟环境中安装程序的练习以悲剧告终:

$ python3 -m venv env

$ source env/bin/activate

(env) $ python3 setup.py install
running install
running bdist_egg
running egg_info
writing good_day.egg-info/PKG-INFO
writing dependency_links to good_day.egg-info/dependency_links.txt
writing entry points to good_day.egg-info/entry_points.txt
writing top-level names to good_day.egg-info/top_level.txt
reading manifest file 'good_day.egg-info/SOURCES.txt'
writing manifest file 'good_day.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
running build_py
creating build/lib
creating build/lib/mod1
copying mod1/say_hello.py -> build/lib/mod1
copying mod1/__init__.py -> build/lib/mod1
copying mod1/good_day.py -> build/lib/mod1
creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/mod1
copying build/lib/mod1/say_hello.py -> build/bdist.linux-x86_64/egg/mod1
copying build/lib/mod1/__init__.py -> build/bdist.linux-x86_64/egg/mod1
copying build/lib/mod1/good_day.py -> build/bdist.linux-x86_64/egg/mod1
byte-compiling build/bdist.linux-x86_64/egg/mod1/say_hello.py to say_hello.cpython-38.pyc
byte-compiling build/bdist.linux-x86_64/egg/mod1/__init__.py to __init__.cpython-38.pyc
byte-compiling build/bdist.linux-x86_64/egg/mod1/good_day.py to good_day.cpython-38.pyc
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying good_day.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying good_day.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying good_day.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying good_day.egg-info/entry_points.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying good_day.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating 'dist/good_day-0.1-py3.8.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Processing good_day-0.1-py3.8.egg
Removing /home/user/src/py-mods/env/lib/python3.8/site-packages/good_day-0.1-py3.8.egg
Copying good_day-0.1-py3.8.egg to /home/user/src/py-mods/env/lib/python3.8/site-packages
good-day 0.1 is already the active version in easy-install.pth
Installing good_day script to /home/user/src/py-mods/env/bin

Installed /home/user/src/py-mods/env/lib/python3.8/site-packages/good_day-0.1-py3.8.egg
Processing dependencies for good-day==0.1
Finished processing dependencies for good-day==0.1

$ good_day
Traceback (most recent call last):
  File "/home/user/src/py-mods/env/bin/good_day", line 11, in <module>
    load_entry_point('good-day==0.1', 'console_scripts', 'good_day')()
  File "/home/user/src/py-mods/env/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 "/home/user/src/py-mods/env/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2852, in load_entry_point
    return ep.load()
  File "/home/user/src/py-mods/env/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2443, in load
    return self.resolve()
  File "/home/user/src/py-mods/env/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2449, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "<frozen zipimport>", line 259, in load_module
  File "/home/user/src/py-mods/env/lib/python3.8/site-packages/good_day-0.1-py3.8.egg/mod1/good_day.py", line 3, in <module>
ModuleNotFoundError: No module named 'say_hello'

本质上,一旦安装,程序只能在指令明确引用模块时才能找到SayHello类:import

from mod1.say_hello import SayHello

虽然此导入修复了通过setup它安装的程序,但它会破坏直接执行:

$ mod1/good_day.py 
Traceback (most recent call last):
  File "mod1/good_day.py", line 3, in <module>
    from mod1.say_hello import SayHello
ModuleNotFoundError: No module named 'mod1'

是什么赋予了?是否有任何公式可以成功导入已安装的程序及其直接执行?

更新:下面是直接使用AKX的建议运行程序的结果。首先是导入语句中的完整路径:

$ mod1/good_day.py
Traceback (most recent call last):
  File "mod1/good_day.py", line 3, in <module>
    from mod1.say_hello import SayHello
ModuleNotFoundError: No module named 'mod1'

然后使用相对导入路径

$ mod1/good_day.py
Traceback (most recent call last):
  File "mod1/good_day.py", line 3, in <module>
    from .say_hello import SayHello
ImportError: attempted relative import with no known parent package

标签: pythonpython-3.x

解决方案


您使用导入语法完全忽略了包,并使用例如python mod1/good_day.py添加mod1/sys.path首先运行事物,这就是绝对导入首先起作用的原因。

mod1/good_day.py应该是例如

from mod1.say_hello import SayHello
# or "from .say_hello import SayHello"

def main():
    hello = SayHello()
    hello.sayIt()
    print("And have a good day!")

if __name__ == "__main__":
    main()

之后你可以运行

python -m mod1.good_day

事情应该会奏效。

入口点也需要稍微调整(添加main):

        'console_scripts': [
            'good_day=mod1.good_day:main',
        ],

使用此配置,good_day在安装和使用该入口点时应该可以工作。

您可以在此处找到-m("run module") 的文档。


推荐阅读