Python最佳实践:顶层__init__.py导入的最佳实践

15
当一个软件包变得很大时,很难记住每个对象的位置,而且要找到我们想要的对象也很麻烦。作者似乎通过将对“最佳”对象的引用置于顶部来解决这个问题,尽管它们的代码实际上可能存在于几个软件包级别下面。
这样可以让我们这样说:
from pandas import wide_to_long

替代

from pandas.core.reshape.melt import wide_to_long

但是这种方法的具体细节及最佳实践是什么?在顶层的 __init__.py 中加载大量导入(以使它们在顶层可用)是否意味着任何一个单一对象的导入会比需要的内存多得多,因为在 __init__.py 中提到的所有内容都会自动加载?

然而,某些包确实采用了这种方法。例如,下面的顶层 numpypandas 可以导入的内容(可以在此Gist中找到运行您自己的诊断代码)。

$ python print_top_level_diagnosis.py numpy
--------- numpy ---------
599 objects can be imported from top level numpy:
  19 modules
  300 functions
  104 types

depth   count
0   162
1   406
2   2
3   29
4   1

$ python print_top_level_diagnosis.py pandas
--------- pandas ---------
115 objects can be imported from top level pandas:
  12 modules
  55 functions
  40 types

depth   count
0   12
3   37
4   65
5   1


2
我认为这取决于项目。一些简单的项目可能需要在入口级别初始化时加载资源,但其他较大的项目应该保持模块化,只在需要时导入,或者让开发人员在包内导入一个模块,比如package.module1.module2。Django非常庞大,并且使用了多个点的功能。我更喜欢这种方式,因为这意味着我知道我从哪里导入。 - bherbruck
2
在这里,内存真的不是一个问题。通常情况下,无论如何,大部分模块/包都会被加载到内存中,你不能只选择单个对象。改变的是哪些对象在哪个命名空间中可用 - juanpa.arrivillaga
2
在这个回答中,最棒的Aaron Hall讨论了一些最佳实践:有人能解释Python中的__all__吗? - codeforester
2个回答

2
这绝对是一个更加哲学性的问题,因为没有一个固定的规则集被使用,所以请带着一颗谨慎的心来看待我的观点。
我喜欢短语“API鼓励行为”。鼓励的行为部分是你想要促进的主要用例,也就是API的主要输入和输出。所以对于像pandas这样的库,DataFrame和Series自然地在根__init__.py文件中被暴露出来。对于像scipy这样的大型库,__init__.py文件在子包中使用,因为该库的用例太广泛,无法在顶层暴露所有内容。
请记住,并不是所有的东西都需要在__init__.py文件中暴露出来。将更多面向高级用户的功能放在__init__.py文件之外,既可以保护新用户免于陷入兔子洞,也可以传达出当你看到从包的深处导入时,有一些特殊的事情正在发生。
例如:
# Anyone will immediately associate this import with a standard use case
from pandas import DataFrame

# Most people, even seasoned pandas users, will take a second to question why this import exists
from pandas.core.internals.ops import operate_blockwise

最后,使用__init__.py文件有助于保持代码的组织性,并保护用户(包括内部用户!)免受重构的影响。无论DataFrame在哪里定义,都不重要,因为已经建立的用法承诺DataFrame将是顶级的pandas导入。这对于不断发展的项目非常方便,但就像任何接口一样,您需要努力遵守已建立的接口,即使只是导入的位置。
总结一下:
- 通过公开API的主要部分来鼓励行为 - 保护初学者免受更高级功能的影响 - 组织您的代码

0
所有方法都有优点和缺点,在`__init__.py`中,你基本上可以做任何你想做的事情。话虽如此,一个重要的准则是`__init__.py`不应该有任何"副作用"。这意味着它不应该做除了声明命名空间之外的任何其他事情。具有副作用可能会让你的库的用户感到困惑,甚至成为安全风险。
模块名为"mylibrary"的示例`__init__.py`:
import requests

result = requests.get("URL TO SOME VIRUS")
...

在这个例子中,运行import mylibrary实际上是下载了某些东西(或发送了信息)。
如果你让__init__.py没有任何副作用,有几个要考虑的问题。首先是加载时间。每次导入你的库时,Python都会遍历并运行所有能找到的__init__.py文件。这意味着如果你的文件包含一个慢操作,比如加载大文件或大量外部依赖项,你的库实际上可能加载得非常慢。你可以通过将昂贵的调用封装在一个函数中并公开该函数来解决这个问题,但仍然需要考虑这一点。
另一个要考虑的问题是库的版本控制。如果你将一个文件移动到不同的文件夹,你的所有用户或者你自己是否需要更新所有的导入语句?如果你看一下库,你会发现他们创建了api子模块(例如pandas.core.api),其中包含所有的公共函数/类等。所有的导入都是从api子模块进行的,这确保了在更改后,只需要更新一个位置的导入语句,一切都将正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接