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

Posted

技术标签:

【中文标题】如何为基本包设置配置 __main__.py、__init__.py 和 __setup__.py?【英文标题】:How to configure __main__.py, __init__.py, and __setup__.py for a basic package setup? 【发布时间】:2017-12-12 03:11:06 【问题描述】:

背景:

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

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

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

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

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

    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'

注意事项:

我读过

https://chriswarrick.com/blog/2014/09/15/python-apps-the-right-way-entry_points-and-scripts/ http://setuptools.readthedocs.io/en/latest/setuptools.html 这里的许多问题看起来像这个,但没有回答我上面的问题。

【问题讨论】:

from code import 是不推荐的隐式相对导入(并且在 Python 3 中不起作用)。使用绝对导入或显式相对导入。 @phd 你能详细说明一下它们是什么吗? 绝对导入意味着使用完整路径:from Package.somemodule.submodule import 即使在 Package 或 somemodule 中。相对导入是从邻居导入而不使用完整路径。 from code import 是隐式 rel。导入,因为它看起来像绝对但实际上是相对的。相对导入是 Python 2 中的默认模式,但它的隐式模式现在在 Python 3 中被禁止(默认现在是绝对导入)。显式相对导入是 from .code import — 见 .code 中的点?意思是“从当前模块导入”。 知道了,所以现在我们应该一直这样做from Package.somemodule.submodule import ? 这是 PEP8 推荐的(虽然官方 PEP8 仅适用于 stdlib)。 【参考方案1】:

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

code.py

foo = 1

__init__.py:

from .code import foo

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

__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(我有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

这也可以让您省略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”。


编辑

在没有 pip install 的情况下运行 __main__.py

如果您不想通过 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中的路径

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

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 main.py,而不是运行python src/__main__.py

【讨论】:

我接受这个答案,因为它是最完整和最彻底的。感谢您的回答 a_guest!我只是感到沮丧,没有简单的方法可以克隆 repo(而不是 pip 安装)并直接运行 main.py ——我的一位同事希望以这种方式使用代码......我可能会放一个 try-except 块在用于导入的文件顶部,但感觉很恶心...... @AlexLenail 现在我更好地理解了您需要第三个用例的用途。我对我的答案进行了编辑,其中包含一些关于如何在没有事先完成 pip install 的情况下运行 __main__.py 的建议。它们基于使用源文件夹所在的目录扩展导入路径(它们需要将src 目录重命名为包的名称)。但是,不需要更改实际代码。另一方面,我希望 repo 包含setup.py 文件,您可以从该文件中pip install。所以真的没那么难。 感谢 a_guest!感谢您抽出宝贵时间提供帮助! 不错的答案。请注意,您不需要也不应该修改 Python 路径以从文件系统运行 Package/__main__.py 文件。只需键入python Package/ 而不是python Package/__main__.py 即可(python 命令接受目录)。 还有关于Python路径操作,注意你也可以在sys.prefixsys.exec_prefixsys.prefix/lib/site-packages、sys.exec_prefix这四个之一中创建一个path configuration file /lib/site-packages Python 环境的目录。它是一个带有.pth 扩展名的文件,其内容是附加到sys.path 的附加路径(每行一个),由site 模块在python 启动时自动导入,除非使用-S 选项。 【参考方案2】:

我经常使用这种设置,因为它更适合 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",]),
...)

【讨论】:

package_dir = '': 'src' 是做什么的? @ikamen 我想我在this doc page找到了这个语法【参考方案3】:

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 文件留空通常是个好主意。

【讨论】:

嗨@MatTheWhale!感谢您在这里回答。不幸的是,我不确定你是否回答了我关于__main__.py 的问题,特别是如何支持第三个用例,这可能需要替换from . import ....。此外,当其他人安装该软件包时,将此软件包的 __init__.py 留空也不起作用。 @MatTheWhale 我无法理解你的论点; code 位于 目录 src 但包的名称是 Package,所以我看不出 from src.code import ... 有什么帮助。同样,__init__.py 的“目的”在 Python 2 中与在 Python 3 中大不相同,我不同意“将__init__.py 文件留空通常是个好主意”——在很多情况下@ 987654347@被积极使用;想象一下在包级别提供内容。

以上是关于如何为基本包设置配置 __main__.py、__init__.py 和 __setup__.py?的主要内容,如果未能解决你的问题,请参考以下文章

Python 包:相对导入

import包和模块_2

如何为 python 文件设置自动标头

如何为所有函数和类包含cpp文件但忽略main函数?

包模块函数的关系结构

如何为所有列设置 AND 条件 - php