子包和__init__.py

3

所以,这是关于如何在包/子包中使用__init__.py的一组问题。我已经搜索过了,但惊讶地没有找到一个像样的答案。

如果我有以下结构(这只是一个明显简化的示例):

my_package/
  __init__.py
  module1.py
  my_sub_package/
    __init__.py
    module2.py

module1.py的内容如下:

my_string = 'Hello'

module2.py的内容如下:

my_int = 42

第一个问题:如何默认导入包中的多个模块

__init__.py 文件应该包含什么内容?

我可以将它们留空,这种情况下 import my_package 实际上并没有做什么(很明显它会导入该包,但是该包实际上并不包含任何内容)。这通常是可以接受的,并且在大多数情况下应该发生。

但在这种情况下,我希望 import my_package 可以让我使用 my_package.module1.my_stringmy_package.my_sub_package.module2.my_int

我可以在 my_package/__init__.py 中添加 __all__ = [ 'module1' ],在 my_package/my_sub_package/__init__.py 中添加 __all__ = [ 'module2' ],但我理解这只对使用通配符的导入语句有效(即只有 from my_package import *from my_package.my_sub_package import *)。

要实现这个功能,我可以这样做:

import my_package.module1
import my_package.my_sub_package

在 my_package/__init__.py 文件中

import my_package.my_sub_package.module2

我应该将代码放在my_package / my_sub_package / __init__.py中,但这是个好主意吗?当我在Python解释器(3.5.5)中执行此操作时,它会创建一个(看似)无限的my_package.my_package.my_package....序列。

一个相关但高度相关的问题:使用模块使文件保持合理大小

如果我想要能够进行以下操作

import my_package
print(my_package.my_string)
print(str(my_package.my_sub_package.my_int))

例如,如果我实际上有很多模块在每个包中(这在这个简单的示例中显然不适用,但可以轻松实现),我希望使用module1和module2仅将代码分离成更小、更易读的文件。

在my_package/__init__.py中执行from my_package.module1 import *,在my_package/my_sub_package/__init__.py中执行from my_package.my_sub_package.module2 import *,这样做是否合理?我不喜欢使用通配符导入,但是看起来导入一个模块中定义的所有内容并将它们列出来会使代码冗长。

第三个问题(也与此密切相关):避免在多个位置编写包名称

是否有一种方法可以在不将包名称放入源代码中的情况下实现上述目标?我之所以这样问,是因为我想避免在多个地方更改它的名称(在这个简单的示例中很容易通过IDE或脚本完成,但知道如何避免这种情况会很好)。


3
如果您有多个问题,即使它们相关,请分别提出每个问题。 - dgw
确实有太多问题了,但另一方面它们非常相关,单独提出来问也没有意义... - bruno desthuilliers
没有时间在这里写一个适当的答案,所以只有一个非常重要的观点:在__init__.py中使用星号导入并且通过黑客方式避免“不得不将包的名称放入源代码”是长期来看非常糟糕的想法——曾经历过这种情况,这是一个纯粹的维护噩梦。明确你从哪里导入和导入什么具有巨大的可读性和可维护性优势,并且实际上使重命名/重构更容易(因为你知道所有东西来自哪里)。 - bruno desthuilliers
对于你问题的第二部分(实际上应该是“使用_包_来保持_模块_大小合理”),这是一个非常常见的模式 - 你有一个“mymodule.py”文件,它变得太大了,你把它变成了一个“mymodule”包(“mymodule”目录+ __init__.py + 子模块)并使用init.py`作为你想要从子模块中公开的门面。 - bruno desthuilliers
1个回答

9
my_package/__init__.py文件中,使用:
from . import my_sub_package

例如,查看NumPy的__init__.py,其中包含from . import random,并允许...等。
import numpy as np
np.random.random

通常在单个包内,通配符导入很常见,前提是你已经在导入的模块和子包中定义了__all__。 再举个例子,NumPy的__init__.py中有几个通配符导入。

下面是__init__.py的一部分:

from . import core
from .core import *
from . import compat
from . import lib
from .lib import *
from . import linalg
from . import fft
from . import polynomial
from . import random
from . import ctypeslib
from . import ma
from . import matrixlib as _mat
from .matrixlib import *
from .compat import long

注意这两个core导入行。导入了numpy.core和核心定义(函数、类等)。
如果有疑问如何做某事或是否符合良好实践,可以查看一些知名库或包的代码。这有助于获得一些宝贵的见解。

谢谢。我有一个问题。我看了一下numpyrandom文件夹(子包)的__init__.py,你提到的方式也在numpy文件夹(主包)的__init__.py中导入了random文件夹。那么,当我执行import numpy as np时,是否不会生成两个副本,如np.random,因为在Python3中,导入包会运行包及其所有子包的__init__.py文件? - Naveen Reddy Marthala
1
@NaveenReddyMarthala 我不确定你具体在问什么,但有两点需要注意:1/ numpy.__init__.py 并没有特别导入 random.__init__.py,而是使用了 from . import random。因此,它导入了完整的子包,并使用该子包的名称(空间)。结果,一旦你在自己的代码中执行了 import numpy,你就可以直接使用 numpy.random - 9769953
1
2/ 在import numpy之后使用import numpy.random不会增加额外开销,然后使用numpy.random.<function>也是一样的。即使你使用from numpy import random,你也会得到相同的效果,但现在你已经将子包的命名空间导入到你的代码中,所以你可以更直接地使用它。但在所有情况下,都不会创建任何副本:只有一些命名空间“指针”,为用户/编码人员方便起见。 - 9769953

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