如何为基本包设置配置 __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.prefix
、sys.exec_prefix
、sys.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?的主要内容,如果未能解决你的问题,请参考以下文章