如何为基本包设置配置 __main__.py、__init__.py 和 setup.py?

新手上路,请多包涵

背景:

我有一个像这样的目录结构:

 Package/
    setup.py
    src/
        __init__.py
        __main__.py
        code.py

我希望能够以多种不同的方式运行代码。

  1. pip install Package 然后 python 然后 from Package import *

  2. python -m Package 应该做的事情 __main__.py

  3. python __main__.py 这也应该在 __main__.py 但是这次,我们假设你下载的是源代码而不是 pip installing

现在我已经开始使用前两个了,但是设置很乱:

安装程序.py:

 setup(
    name='Package',
    packages=['Package'],
    package_dir={'Package': 'src'},
    ...
    entry_points={ 'console_scripts': ['Package = src.__main__:main' ] }

init.py:

 from Package.code import .......

main.py:

 from . import .......

在这两种情况下,对我来说更有意义的是写

from code import ........

但这给了我导入错误。

问题:

我拥有它的方式真的是唯一的方式吗?

最重要的是,我如何支持第三个用例?现在, python __main__.py 抛出

File "__main__.py", line 10, in <module>
    from . import code
ImportError: cannot import name 'class defined in code.py'

笔记:

我读过了

原文由 Alex Lenail 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 901
2 个回答

您几乎拥有所需的一切(甚至更多)!我会使用以下设置:

代码.py

 foo = 1

init.py:

 from .code import foo

在这里进行相对导入,因为 __init__.py 将在导入整个包时使用。请注意,我们通过使用 . 语法将导入显式标记为相对导入,因为这是 Python 3 所必需的(在 Python 2 中,如果您这样做了 from __future__ import absolute_import )。

main.py:

 from Package import foo

print('foo = ', foo)

这是包的主要脚本,因此我们使用绝对 import 语句。通过这样做,我们假设包已经安装(或者至少已经放在路径上);这就是处理包裹的方式!您可能认为这与您的第三个用例冲突,但实际上在处理包时没有理由 pip install 。这真的没什么大不了的(尤其是在使用 virtualenv 时)!

如果您关心的是修改源文件并通过运行 __main__.py 文件轻松观察更改,那么您只需使用 -e (“可编辑”)开关安装包: pip install -e . (假设你在目录中 Package )。但是,对于您当前的目录结构,这将不起作用,因为 -e 开关会将 egg-link 放置到包含 setup.py 文件的目录;该目录不包含名为 Package 的包,而是 src 包(我对此有 疑问)。

相反,如果您按照约定在包本身之后命名包源的根目录(即 Package 例如),那么安装 -e 不是问题:Python 确实在相应目录下找到 了需要的包 Package

 $ tree Package/
Package/
├── setup.py
└── Package   <-- Renamed "src" to "Package" because that's the package's name.
    ├── code.py
    ├── __init__.py
    └── __main__.py

这也让您可以省略 package_dir={'Package': 'src'}setup.py 的额外定义。

关于 setup.py 的注释:对于您指定的三个用例,无需定义入口点。也就是说,您可以跳过该行 entry_points={ 'console_scripts': ['Package = src.__main__:main' ] } 。通过运送 __main__.py 模块 python -m Package 将轻松执行此模块中的代码。您还可以添加一个额外的 if 子句:

 def main():
    print('foo = ', foo)

if __name__ == '__main__':
    main()

另一方面,入口点让您可以直接从 CLI 执行 __main__.main 中的代码;正在运行的 $ Package 将执行相应的代码。

回顾

最重要的是,在处理包时,我总是使用 pip install 。为什么不呢,特别是如果您已经创建了一个 setup.py 文件?如果要“实时”应用对包的更改,则可以使用 -e 开关安装(这可能需要重命名 src 文件夹,见上文)。因此,您的第三个用例将读作“下载源代码和 pip install (-e) Package (在 virtualenv 中);然后您可以运行 python __main__.py ”。


编辑

运行 __main__.py 没有 pip install

如果你不想通过 pip 安装包但仍然能够运行 __main__.py 脚本,我仍然会使用上面的设置。然后我们需要确保 from Package import ... 语句仍然成功,这可以通过扩展导入路径来实现(请注意,这需要重命名 src 目录到包裹的名字!)。

修改 PYTHONPATH

对于 Linux bash,您可以按如下方式设置 Pythonpath:

 export PYTHONPATH=$PYTHONPATH:/path/to/Package

或者,如果您与 __main__.py 在同一目录中:

 export PYTHONPATH=$PYTHONPATH:`cd ..; pwd`

当然不同的操作系统有不同的方法。

扩展路径 __main__.py

您(或者您的同事)可以将以下行添加到脚本的顶部(在 from Package import ... 语句之前):

 import sys
sys.path.append('/path/to/Package')

扩展路径 sitecustomize.py

您可以将名为 sitecustomize.py 的模块放置在 Python 安装的 lib/python3.5/site-packages/ 目录中,其中包含以下行:

 import sys
sys.path.append('/path/to/Package')

创建一个单独的顶级 main.py 脚本

所以你会有以下布局:

 $ tree Package/
Package/
├── main.py   <-- Add this file.
├── setup.py
└── src
    ├── code.py
    ├── __init__.py
    └── __main__.py

其中 main.py 包含

import src.__main__

现在 __main__.py 被视为 src 包的一部分,相关导入将起作用。而不是运行 python src/__main__.py 你现在运行 python main.py

原文由 a_guest 发布,翻译遵循 CC BY-SA 3.0 许可协议

from code import ......... 失败,因为系统上没有安装名为 code 的 Python 包。您的系统上有一个名为 code 的 Python _模块_,但在您的导入语句中您没有指定您的 code 模块所在的包。

您在 src/ 中拥有的 __init__.py 文件的目的告诉 Python src/ 目录应该被视为一个 Python 包,其内容作为 Python 包包裹。 Since code.py is located in src/ along with your __init__.py file, your code module is located in your src 包裹。

现在您知道可以在哪个包中找到您的 code 模块,您可以从中导入内容:

 from src.code import .........

另外,作为旁注: __init__.py 仅通过出现在您的 src/ 目录中来完成它的工作,因此它甚至不需要包含任何代码。因此,将 __init__.py 文件留空通常是个好主意。

原文由 MatTheWhale 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题