使方法在包中的更高级别可用
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
)是可能的,但是有没有办法让方法在树的更高层可用?
我不想想诉诸的事情:
- 很多
from ... import ...
- 将我的
eggs.py
代码放入 __init__.py
而不是
- 已加星标的进口商品
- 像
spam.exceptions.BarException
这样的长名称(可能更长)
一个例子是 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,你可能在滥用这个成语(或者至少你应该停下来说服自己你不是)。
考虑以下包结构:
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
)是可能的,但是有没有办法让方法在树的更高层可用?
我不想想诉诸的事情:
- 很多
from ... import ...
- 将我的
eggs.py
代码放入__init__.py
而不是 - 已加星标的进口商品
- 像
spam.exceptions.BarException
这样的长名称(可能更长)
一个例子是 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,你可能在滥用这个成语(或者至少你应该停下来说服自己你不是)。