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

How to configure __main__.py, __init__.py, and setup.py for a basic package setup?

背景:

我的目录结构如下:

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. 中的事情
  4. python __main__.py 也应该做 __main__.py 中的事情,但这次,我们假设您下载的是源代码而不是 pip installing

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

setup.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'

备注:

我已阅读

from code import ......... 失败,因为您的系统上没有安装名为 code 的 Python 软件包。在您的系统上有一个名为 code 的 Python 模块 ,但是在您的导入语句中您没有指定您的 code 模块可以使用的包在

您在 src/ 中的 __init__.py 文件的目的告诉 Python src/ 目录应该被视为一个 Python 包,其中其内容作为包内的模块。由于 code.py 与您的 __init__.py 文件一起位于 src/ 中,因此您的 code 模块位于您的 src 包中。

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

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

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

我经常使用此设置,因为它与 python setup.py develop

配合使用效果更好
Package_root/
    setup.py
    src/
        Package/
            __init__.py
            __main__.py 
            code.py

这可能不是(还)是您期望的详细答案,但我认为这三个用例值得一试。

setup( ...
    package_dir = {'': 'src'},
    entry_points = {'console_scripts': ['Package = Package.__main__:main'],},
    packages = find_packages(exclude=["Package.egg_info",]),
...)

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

code.py:

foo = 1

__init__.py:

from .code import foo

这里做相对导入,因为导入整个包时会用到__init__.py。请注意,我们使用 . 语法将导入明确标记为相对导入,因为这是 Python 3 所必需的(如果您使用了 from __future__ import absolute_import,则在 Python 2 中是必需的)。 =74=]

__main__.py:

from Package import foo

print('foo = ', foo)

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

如果您关心的是修改源文件并通过 运行 宁 __main__.py 文件轻松观察更改,那么您可以简单地使用 -e ("editable") 切换:pip install -e .(假设您在目录 Package 中)。但是,对于您当前的目录结构,这将不起作用,因为 -e 开关会将 egg-link 放置到包含 setup.py 文件的目录;此目录不包含名为 Package 的包,而是包含 src(我有 a question about that)。

相反,如果您按照惯例在包本身之后命名包源的根目录(对于您的示例是 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

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

关于 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 中的代码;即运行ning $ Package会执行相应的代码

回顾

底线是我在处理包时总是使用 pip install。为什么不呢,特别是如果您已经创建了 setup.py 文件?如果要应用对包的更改 "in real-time",那么您可以使用 -e 开关进行安装(这可能需要重命名 src 文件夹,见上文)。因此,您的第三个用例将读作 "Download the source and pip install (-e) Package (within a virtualenv); then you can run python __main__.py".


编辑

运行 __main__.pypip install

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

修改PYTHONPATH

对于Linux bash你可以设置Python路径如下:

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 包的一部分,相关导入将起作用。 而不是 运行ning python src/__main__.py 你现在 运行 python main.py