考虑以下三个常规包的层次结构及其内容:
1 2 3 4 5 6 7 8
| quick
├── brown
│ ├── fox.py
│ └── __init__.py
├── lazy
│ ├── dog.py
│ └── __init__.py
└── __init__.py |
现在假设在模块dog中有一个函数jump,在模块fox中需要它。我该怎么办?
最近看了雷蒙德·赫廷格在皮孔的演讲2015我想要直接从包lazy的根目录导入的函数,这样地:
而且,在我看来,写相关的导入内容更简洁和使包内连接容易可见。因此,我会写这进入了lazy/__init__.py:
这就进入了fox.py:
但我想知道,这条路对吗?
首先,在lazy/__init__.py中输入名称jump对防止直接从dog进口。如果一个函数可能从许多地方导入,它会导致问题吗?例如,在单元测试中,我们是否可以从错误的位置对名称进行monkey修补?
此外,具有自动导入例程的IDE似乎更喜欢从定义函数的模块中导入。我可以把字符_放在所有模块名前面来覆盖它,但这似乎有点不切实际。
将所有需要的名称带到到__init__.py的包裹?可能这至少增加了循环进口的可能性。但我想如果一个圆遇到导入时,存在根本错误无论如何,包结构。
相对进口呢?PEP 8说建议绝对进口:当它说绝对进口意味着什么?进口比相对进口表现更好?你能给我一个例子?
- 你确定jump属于dog.py而不是更高的层次结构吗?
- 是的,我认为lazy是一个连贯的组件集合,其中一些组件在quick的其他地方也需要。
- 我建议你直截了当,尽量保持简单。随着时间的推移,包内进口可能会变得有点疯狂。所以忘记所有的江户奇幻。如果您真的想这样做,您可以只在顶层的__init__.py中为外部用户可见的名称这样做。
显式接口声明:如果您希望公开属于lazy包的jump函数,那么按照您的建议,将它包括在lazy.__init__中是有意义的。这样,您就清楚地表明它是lazy的"公共接口"的一部分。您还建议其他模块不是公共接口的一部分。
关于防止人/工具直接从dog导入:在python中,隐私权取决于用户的同意,您不能强制隐藏任何内容,但有一些约定。
使用下划线和定义dog._jump()明确表示dog不想公开_jump。我们可以假设任何IDE工具都应该遵守这种类型的约定。无论如何,如果dog定义了_jump,而lazy公开了jump,那么您就不会有不知道导入哪个的问题,因为名称不同,所以这是明确的,这在python中被认为是好的。
这里有一个关于这个主题的很好的指针:在Python中定义私有模块函数
关于相对进口:PEP8不鼓励这些进口,但它们是出于某种原因实施的,它们取代了隐含的相对进口。PEP8中的原因:特别是在处理复杂的包布局时,使用绝对导入将是不必要的冗长。
最后一点:简而言之,如果您认为lazy包是一个库,并且不想公开内部模块,那么我认为公开lazy.__init__中的对象是有意义的。如果相反,你希望人们知道有一个dog模块,那么无论如何,让其他模块做:
from lazy.dog import jump
或
from ..lazy import jump
如果brown和brown.fox总是被打包并与lazy紧密集成,那么我看不出绝对模块和相对模块之间的区别,但我更倾向于相对模块,明确地指出您指的是内部模块。
但如果你认为它们将来可能会被分割,那么相对进口就没有意义了,你宁愿这样做,这取决于以上几点:
from lazy.dog import jump
或:
from lazy import jump
- 这是一个组织良好的答案。它很好地澄清了我的一些顾虑,但我真的看不到一个明显的方法来做到这一点(正如Python的禅宗所建议的)。
- 另外,我认为将像jump这样的函数定义为像_jump那样的内部函数有点笨拙,因为那时我将不得不将几乎所有的东西都定义为内部函数。但正如你所说,这是有道理的,因为这样名字就不一样了。
- 我想没有"明显"的方式来理解Python的禅,因为Python支持这两种方式。在这里,他们有点混淆了这一原则。我建议您查看几个标准包以了解最佳实践。例如,XML包利用相对导入(仅限于一个点,如from .xmlreader import InputSource)并在包的__init__.py文件中包含一些类,并用_定义变量和类,将它们标记为私有。大多数其他的标准包没有这样做,并且有一个空的__init__.py。