模块导入和__init__.py

80

我试图理解Python(v2.7)导入机制的最佳实践是什么。我有一个项目已经开始有点增长,假设我的代码组织如下:

foo/
    __init__.py
    Foo.py
    module1.py
    module2.py
    module3.py

包名称为foo,其中有模块Foo.py,其中包含类Foo的代码。因此,我使用相同的名称用于包、模块和类,这可能不是很明智。

__init__.py为空,类Foo需要导入module1、module2和module3,因此我的Foo.py文件的一部分看起来像:

# foo/Foo.py

import module1
import module2
import module3

class Foo(object):
    def __init__(self):
....
....
if __name__ == '__main__':
    foo_obj = Foo()

然而,后来我重新审视了这一点,并认为将所有导入放在__init__.py文件中会更好。因此,我的__init__.py现在看起来如下所示:

# foo/__init__.py

import Foo
import module1
import module2
import module3
....
....
    

我的Foo.py只需要导入foo

# foo/Foo.py

import foo

虽然这看起来很方便,因为只有一行代码,但我有点担心它可能会创建循环引用。我的意思是,当运行脚本Foo.py时,它会导入所有它可以找到的东西,然后调用__init__.py,它将再次导入Foo.py(是否正确?)。此外,将包、模块和类使用相同名称会使事情更加混乱。

这么做有意义吗?或者我在自找麻烦吗?

5个回答

88

为了符合一些流行的Python规范和标准,您可以采取一些措施来改善您的组织结构。

如果您搜索这个话题,您不可避免地会遇到人们推荐 PEP8 准则。这是组织Python代码的实际标准。

模块名称应该是短小的全小写名称。如果使用下划线可以提高可读性,可以在模块名称中使用下划线。Python包的名称也应该是短小的全小写名称,尽管不建议使用下划线。

基于这些准则,您的项目模块应该命名如下:

foo/
    __init__.py
    foo.py
    module1.py
    module2.py
    module3.py

我发现通常最好避免在__init__.py中不必要地导入模块,除非你出于命名空间的原因这么做。例如,如果你想让你的包的命名空间看起来像这样:

from foo import Foo

替代

from foo.foo import Foo

那么把它放在这里就有意义了。

from .foo import Foo

在你的__init__.py文件中。当你的包变得越来越大时,一些用户可能不想使用所有的子包和模块,因此强制用户等待所有这些模块隐式导入不是明智的选择。此外,您需要考虑是否将module1module2module3作为外部API的一部分。它们只被Foo使用,不是供最终用户使用的吗?如果它们只用于内部,则不要将它们包含在__init__.py中。

我还建议使用绝对或显式相对导入来导入子模块。例如,在foo.py中:

绝对导入

from foo import module1
from foo import module2
from foo import module3

显式相对路径

from . import module1
from . import module2
from . import module3

这将防止与其他包和模块发生可能的命名问题。如果您决定支持Python3,则使用您当前正在使用的隐式相对导入语法会更容易,因为Python3不支持它。

此外,您的包内部的文件通常不应包含

if __name__ == '__main__'

这是因为将文件作为脚本运行意味着它不会被视为所属包的一部分,因此它将无法进行相对导入。

向用户提供可执行脚本的最佳方式是使用 setuptoolsscriptsconsole_scripts 功能。您组织脚本的方式可能因使用的方法而异,但我通常将它们组织如下:

foo/
    __init__.py
    foo.py
    ...
scripts/
     foo_script.py
setup.py

建议使用绝对导入。我认为作者的例子不太可能是“复杂结构”,因此可以使用相对导入。 - The Godfather
@TheGodfather 是的,两种方式都可以接受。有趣的是,在简单项目中,我倾向于使用相对导入,因为我认为这更容易阅读。但在复杂项目中,存在多个子包和跨层次结构的导入时,我认为绝对导入更清晰。两者都比隐式相对导入好(即 import module1)。 - Brendan Abel
非常感谢,回复非常详细!对我的理解有很大帮助。 - Aenaon
你能详细解释一下“这是因为将文件作为脚本运行意味着它不会被视为所属包的一部分,因此它将无法进行相对导入。”吗?那么,如果我应该删除模块内所有跟随if __name__ == '__main__':的脚本,以防止相对导入出现问题? - VimNing

6
根据PEP 0008,“公共和内部接口”的规定:

导入的名称应始终被视为实现细节。其他模块不得依赖于对此类导入名称的间接访问,除非它们是包含模块 API 的明确记录部分,例如 os.path 或公开子模块功能的包的 __init__ 模块。

因此,这表明如果__init__用于从子模块中公开函数,则将导入放在__init__模块中是可以的。这里是我找到的一篇简短的博客文章,其中列举了几个使用导入使子包在包级别上可用的 Pythonic 示例。
你将导入语句移至__init__以便在Foo中只有一个导入的例子,似乎不符合这个规则。我的理解是,在__init__中的导入应该用于外部接口,否则,只需将导入语句放在需要它们的文件中。当子模块名称更改时,这可以节省麻烦,并且在添加使用不同子模块子集的更多文件时,可以避免不必要或难以找到的导入。
至于循环引用,在Python中肯定是可能的(例如)。我之前写过关于这个问题的文章,但为了使示例工作,我不得不将Foo.py上移一级,如下所示:
Foo.py
foo/
    __init__.py
    module1.py
    module2.py
    module3.py

有了这个设置和一些打印语句,运行python Foo.py会得到以下输出:

module 1
module 2
module 3
hello Foo constructor

并且正常退出。请注意,这是由于添加了if __name__ == "__main__" - 如果在其外部添加一个打印语句,则可以看到Python仍在两次加载模块。更好的解决方案是从您的__init__.py中删除导入。正如我之前所说,这可能有或没有意义,具体取决于这些子模块是什么。


1
只有一个评论 - 在博客文章中,您提到推荐使用“from mypackage import *”这样的结构。但是根据PEP8 https://www.python.org/dev/peps/pep-0008/#imports,不建议使用通配符导入,应该避免使用。 - The Godfather
我并没有将博客文章解释为支持那种构造方式,只是用它来展示__all__的工作原理(这本身就有一套风格问题..但那是另一个话题)。无论如何,你肯定是对的,通配符导入不是好的编码风格! - user812786

1
尝试这个:

package1

package1.py

__init__.py

包2

test.py

package1.py:-

class abc:
    a = 'hello'
    def print_a(self):
    print(a)

__init__.py:-

from .package1 import abc

package2.py:-

From package1.package1 import abc

我使用这些 __init__.py 文件从一个包中导入。

1

0

我不能确定这是否是正确的方法,但我一直都是用前者的方式。也就是说,我一直保持__init__.py为空,在Foo.py中根据需要导入东西。

从你的描述中,似乎后者形式存在一些循环逻辑问题。


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