使方法在包中的更高级别可用

Make methods available at higher level in a package

考虑以下包结构:

foo/                          # package name
    spam/                     # module name
        __init__.py
        eggs.py               # contains "bar" method
        exceptions.py         # contains "BarException" class

现在为了调用bar方法,我们必须做

import spam
spam.eggs.bar()

我想输eggs

现在我知道 import ... as(和 from ... import)是可能的,但是有没有办法让方法在树的更高层可用?

不想想诉诸的事情:

一个例子是 exceptions.py 我在其中定义我的异常 类.

每当我想让用户可以使用它们时,我都不希望他们使用 spam.exceptions.BarException,而是能够使用 spam.BarException.

目标:

import spam
try:
    spam.bar()   # in this case throws BarException
except spam.BarException:
    pass

在您的 __init__.py 中,您可以从包中的其他模块导入内容。如果在 __init__.py 中您执行 from .eggs import bar,那么其他人可以执行 import spam 并访问 spam.bar。如果在 __init__.py 中你做 from .exceptions import BarException,那么有人可以做 import spam 然后做 spam.BarException.

但是,您应该小心不要做得太过火。在包和模块中使用嵌套有一个目的,即创建单独的名称空间。从子模块显式导入一些常用的东西到顶层是可以的,但是如果你开始尝试隐式地让顶层的所有东西都可用,你就会为自己设置名字冲突的道路(例如,如果一个模块定义了一个叫做Blah 后来另一个模块也这样做了,没有意识到当它们都导入到顶层时它们会发生冲突)。

“强制”用户使用 from 并不是一项繁重的要求。如果需要繁琐的导入才能使用您的库,这可能表明您的 package/module 结构过于繁琐,您应该合并一些东西而不是将它们拆分成单独的 directories/files.

顺便说一句,您在 post 中指出的文件结构存在一些问题。如您所示,顶级 foo 不是包,因为它没有 __init__.py。第二层 spam 不是模块,因为它不是文件。在您的示例中,spam 是一个包,它内部有一个名为 eggs 的模块(在文件 eggs.py 中);顶级 foo 目录在 Python 打包系统中没有状态。

请注意,与您的评论相反,顶部 foo 不是包名称,它只是一个目录(大概)在您的 sys.path 某处,而 spam 是不是模块名称而是包名称,eggs 是模块名称。所以:

foo/                          # directory package is in
    spam/                     # package name
        __init__.py
        eggs.py               # contains "bar" method
        exceptions.py         # contains "BarException" class

你想做什么的关键是:

  • spam/__init__.py 中的任何全局名称都是 spam 包的成员。它们实际上是在 __init__.py 中定义的,还是从其他地方 import 定义的,这并不重要。

所以,如果你想让 spam.eggs.bar 函数像 spam.bar 一样可用,你所要做的就是将此行添加到 spam/__init__.py:

from .eggs import bar

如果您在 spam/__init__.py 中有一个 __all__ 属性来定义 spam 的 public 属性,您需要将 bar 添加到该列表:

__all__ = ['other', 'stuff', 'directly', 'in', 'spam', 'bar']

如果您想 re-export public 来自 spam.eggs 作为 spam 的 public 一部分,你可以这样做:

from .eggs import *

__all__ = ['other', 'stuff', directly', 'in', spam'] + eggs.__all__

当然,您可以将其扩展到多个 child 模块:

from .eggs import *
from .exceptions import *

__all__ = (['other', 'stuff', directly', 'in', spam'] + 
           eggs.__all__ +
           exceptions.__all__)

这在 stdlib 和流行的 third-party 包中很常见。举一个很好的例子,请参阅 Python 3.4.

asyncio/__init__.py 的来源

然而,它只在这种 exact 情况下才真正常见:您希望您的用户能够将您的包视为一个简单的平面模块,但实际上有一些内部结构(或者因为否则实现会太复杂,或者因为偶尔用户会需要那个结构)。如果你从祖父 child、兄弟姐妹或 parent 中提取名字而不是 child,你可能在滥用这个成语(或者至少你应该停下来说服自己你不是)。