复杂包结构中的 Python 导入语句?

Posted

技术标签:

【中文标题】复杂包结构中的 Python 导入语句?【英文标题】:Python import statements in complex package structures? 【发布时间】:2015-07-26 04:42:10 【问题描述】:

考虑以下三个常规包的层次结构及其 内容:

quick
├── brown
│   ├── fox.py
│   └── __init__.py
├── lazy
│   ├── dog.py
│   └── __init__.py
└── __init__.py

现在假设在模块dog 中有一个函数jump,并且在模块fox 中需要它。我应该如何进行?

最近看过 Raymond Hettinger 的 talk at Pycon 2015 我会喜欢 可以从包lazy的根目录直接导入的函数, 像这样:

from lazy import jump 

另外,在我看来,写相对导入更简洁, 使封装内的连接很容易看到。因此,我会写 这变成lazy/__init__.py

from .dog import jump

这变成fox.py:

from ..lazy import jump

但我想知道,这是正确的方法吗?

首先,在 lazy/__init__.py 中导入名称 jump 不会 防止它直接从dog 导入。如果一个函数可能从许多地方导入,它会导致问题吗?例如,在单元测试中,我们可以猴子修补错误位置的名称吗?

此外,带有自动导入例程的 IDE 似乎更喜欢从定义函数的模块中导入。我也许可以通过将字符 _ 放在所有模块名称前面来覆盖它,但这似乎有点不切实际。

将所有需要的名称带到外部是否有危险? 打包到__init__.py?可能这至少增加了 循环进口的可能性。但我想如果一个圆形 遇到 import 有一些根本性的错误 无论如何都是包结构。

相对进口呢? PEP 8 说 推荐绝对进口:当它说绝对是什么意思 进口的表现比相对的好?你能给我一个 例子?

【问题讨论】:

您确定jump 属于dog.py,而不是更高层级吗? 是的,我认为lazy 是某种连贯的组件集合,其中一些在quick 的其他地方也需要。 我建议明确一点,尽可能简单。随着时间的推移,包内导入会变得有点疯狂。所以忘记所有__init__.py 魔法吧。如果你真的想要,你可以只在***__init__.py 中为外部用户可见名称执行此操作。 【参考方案1】:

显式接口声明:如果您想将 jump 函数公开为属于 lazy 包,那么按照您的建议包含它是有意义的 lazy.__init__ 。这样您就可以清楚地表明它是lazy 的“公共接口”的一部分。您还建议其他模块不是公共接口的一部分。

关于防止人/工具直接从dog导入:在Python中,隐私权由用户同意,不能强行隐藏任何东西,但有约定。

使用下划线和定义dog._jump() 可以清楚地表明dog想要公开_jump。我们可以假设任何 IDE 工具都应该遵守这种约定。无论如何,如果dog定义了_jump,而lazy暴露了jump,那么你就不会有不知道哪个是导入的问题,因为名称不同,所以这是显式的,即在 Python 中被认为很好。

这是关于这个主题的一个很好的指针:Defining private module functions in python

关于相对导入::PEP 8 不鼓励这些,但它们的实现是有原因的,它们取代了隐式相对导入。 PEP 8 中的原因:尤其是在处理复杂的包布局时,使用绝对导入会不必要地冗长

最后的想法:简而言之,如果您认为lazy 包是一个库并且不想暴露内部模块,那么我认为将对象暴露在lazy.__init__。相反,如果您想让人们知道有一个dog 模块,那么请务必让其他模块来做:

from lazy.dog import jump

from ..lazy import jump

如果brownbrown.fox 将始终被打包并与lazy 紧密集成,那么我看不出绝对和相对之间的区别,但我更喜欢相对,以明确表明您指的是到一个内部模块。

但是如果您认为它们将来可能会被拆分,那么相对导入没有意义,您宁愿这样做,具体取决于上述几点:

from lazy.dog import jump或:from lazy import jump

【讨论】:

这是一个组织良好的答案。它很好地澄清了我的一些担忧,但我真的看不出有明显的方法来做到这一点(正如The Zen of Python 所建议的那样)。 另外,我认为将jump 之类的函数定义为_jump 之类的内部函数有点笨拙,因为我必须将几乎所有内容都定义为内部函数。但正如你所说,有一点是有道理的,因为那样名字就会不同。 我猜在 Python 的禅宗意义上没有“明显”的方式,因为 Python 支持这两种方式。在这里,他们有点混淆了这个原则。我建议您查看几个标准包以了解最佳实践。例如,xml包利用了相对导入(仅限于from .xmlreader import InputSource等一个点),并且还包括一些类到包的__init__.py文件中,并用_定义变量和类来标记它们私人的。大多数其他标准包不这样做,并且有一个空的__init__.py

以上是关于复杂包结构中的 Python 导入语句?的主要内容,如果未能解决你的问题,请参考以下文章

尝试在当前结构中的非包错误中进行相对导入

python怎么导入自己写的包

Python中的“尝试相对导入超出***包”错误意味着啥?

如何将相邻包正确导入python中的当前包?

python怎么导入库包?

第七章 Python 盒子:模块包和程序