模块导入和Python中的__init__.py

Aen*_*aon 30 python import python-2.7

我试图了解Python(v2.7)导入机制的最佳实践.我有一个项目已经开始增长一点,让我说我的代码组织如下:

foo/
    __init__.py
    Foo.py
    module1.py
    module2.py
    module3.py
Run Code Online (Sandbox Code Playgroud)

软件包名称foo位于其下面,我有Foo.py一个包含该类代码的模块Foo.因此,我使用相同的名称作为包,模块和类,这可能不是很聪明的开始.

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

# foo/Foo.py

import module1
import module2
import module3

class Foo(object):
    def __init__(self):
....
....
if __name__ == '__main__':
    foo_obj = Foo()
Run Code Online (Sandbox Code Playgroud)

但是后来我重新考虑了这一点,我认为在__init__.py文件中包含所有导入会更好.因此,我__init__.py现在看起来像:

# foo/__init__.py

import Foo
import module1
import module2
import module3
....
....
Run Code Online (Sandbox Code Playgroud)

Foo.py唯一需要导入foo:

# foo/Foo.py

import foo
Run Code Online (Sandbox Code Playgroud)

虽然这看起来很方便,因为它是一个班轮,我有点担心它可能会创建循环导入.我的意思是,当脚本Foo.py运行时,它将导入它可以的所有内容,然后__init__.py将被调用,它将Foo.py再次导入(这是正确的吗?).另外,对于包,模块和类使用相同的名称会使事情更加混乱.

这样做是否有意义?还是我在找麻烦?

Bre*_*bel 30

如果只是为了遵守一些流行的python约定和标准,你可以采取一些措施来改善你的组织.

如果您搜索此主题,您将不可避免地遇到推荐PEP8指南的人.这些是组织python代码的事实规范标准.

模块应具有简短的全小写名称.如果提高可读性,则可以在模块名称中使用下划线.Python包也应该有简短的全小写名称,但不鼓励使用下划线.

根据这些指南,您的项目模块应如下命名:

foo/
    __init__.py
    foo.py
    module1.py
    module2.py
    module3.py
Run Code Online (Sandbox Code Playgroud)

我发现通常最好避免不必要地导入模块,__init__.py除非你出于命名空间的原因这样做.例如,如果您希望包的命名空间如下所示

from foo import Foo
Run Code Online (Sandbox Code Playgroud)

代替

from foo.foo import Foo
Run Code Online (Sandbox Code Playgroud)

然后它是有道理的

from .foo import Foo
Run Code Online (Sandbox Code Playgroud)

在你的__init__.py.随着您的包越来越大,一些用户可能不想使用所有子包和模块,因此强制用户等待所有这些模块加载是没有意义的,通过隐式导入它们__init__.py.此外,你要考虑你是否甚至想module1,module2module3为您的外部API的一部分.它们仅供Foo最终用户使用吗?如果它们仅在内部使用,则不要将它们包含在内部__init__.py

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

绝对

from foo import module1
from foo import module2
from foo import module3
Run Code Online (Sandbox Code Playgroud)

显性相对

from . import module1
from . import module2
from . import module3
Run Code Online (Sandbox Code Playgroud)

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

此外,包中的文件通常不应包含

if __name__ == '__main__'
Run Code Online (Sandbox Code Playgroud)

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

向用户提供可执行脚本的最佳方法是使用scripts或的console_scripts功能setuptools.您组织脚本的方式可能会有所不同,具体取决于您使用的方法,但我通常会按照以下方式组织我的:

foo/
    __init__.py
    foo.py
    ...
scripts/
     foo_script.py
setup.py
Run Code Online (Sandbox Code Playgroud)


use*_*786 7

根据PEP 0008,“公共和内部接口”

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

因此,这表明如果用于公开子__init__模块中的函数,则__init__可以将导入放入模块中。是我发现的一篇简短的博客文章,其中包含几个 Pythonic 使用的示例__init__,使用导入使子包在包级别可用。

您将 import 语句移至__init__以便仅在 中导入一次的示例Foo似乎遵循此规则。我的解释是,您的导入__init__应该用于外部接口,否则,只需将导入语句放在需要它们的文件中即可。当子模块名称更改时,这可以为您省去麻烦,并且当您添加使用不同子模块子集的更多文件时,可以避免不必要或难以找到的导入。

就循环引用而言,这在Python中绝对是可能的(例如)。在我实际尝试你的玩具示例之前,我写过这一点,但为了使该示例正常工作,我必须提升Foo.py一个级别,如下所示:

Foo.py
foo/
    __init__.py
    module1.py
    module2.py
    module3.py
Run Code Online (Sandbox Code Playgroud)

通过该设置和一些打印语句,运行python Foo.py会给出输出:

module 1
module 2
module 3
hello Foo constructor
Run Code Online (Sandbox Code Playgroud)

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