【问题标题】:What is __init__.py for?__init__.py 是干什么用的?
【发布时间】:2021-06-08 05:41:49
【问题描述】:

Python 源目录中的__init__.py 是什么?

【问题讨论】:

  • 根据@Rob_before_edits 和this stackoverflow thread 37139786 在下面的评论,Python 3.3+ 似乎不再需要 init.py。
  • 没有__init__ 的包是命名空间包,而不是常规包这和@methane 用an example here 指出的不一样
  • @Rainning 命名空间包与常规包没有根本区别。这只是创建包的另一种方式。创建命名空间包后,它与常规包之间没有功能差异。
  • 官方文档有一系列文章解释了它的用法,并且对每个术语都做了精确的定义。
  • @Rainning 也许你没有意识到我上面添加的解释来自pep420逐字逐句

标签: python module package python-packaging


【解决方案1】:

__init__.py有两个主要原因

  1. 为方便起见:其他用户不需要知道您的函数在您的包层次结构中的确切位置 (documentation)。

    your_package/
      __init__.py
      file1.py
      file2.py
        ...
      fileN.py
    
    # in __init__.py
    from file1 import *
    from file2 import *
    ...
    from fileN import *
    
    # in file1.py
    def add():
        pass
    

    然后其他人可以通过

    调用add()
     from your_package import add
    

    不知道file1,比如

     from your_package.file1 import add
    
  2. 如果你想初始化一些东西;例如,日志记录(应该放在顶层):

     import logging.config
     logging.config.dictConfig(Your_logging_config)
    

【讨论】:

  • 哦,在阅读您的答案之前,我认为从函数的位置显式调用函数是一种好习惯。
  • @Aerin 最好不要考虑简短的陈述(或者,在这种情况下,主观结论)总是正确的。从__init__.py 导入有时可能有用,但并非总是如此。
  • init.py 中必须包含什么?
【解决方案2】:

名为__init__.py 的文件用于将磁盘上的目录标记为Python 包目录。 如果你有文件

mydir/spam/__init__.py
mydir/spam/module.py

并且mydir在你的路径上,你可以将module.py中的代码导入为

import spam.module

from spam import module

如果您删除 __init__.py 文件,Python 将不再在该目录中查找子模块,因此导入模块的尝试将失败。

__init__.py 文件通常为空,但可用于以更方便的名称导出包的选定部分、保存方便的函数等。 给定上面的例子,init模块的内容可以被访问为

import spam

基于this

【讨论】:

  • 更新:文件 __init__.py 在 Python 2.X 下是必需的,在 Python 2.7.12 下仍然需要(我测试过),但从(据称)Python 3.3 起不再需要它,并且在 Python 3.4.3 下不需要(我测试过)。有关详细信息,请参阅stackoverflow.com/questions/37139786
  • 为什么在`init.py`里面有import spam,它有什么帮助
【解决方案3】:

__init__.py 允许的一件事是将模块转换为包而不破坏 API 或创建无关的嵌套命名空间或私有模块*。这在我想扩展命名空间时很有帮助。

如果我有一个包含 util.py 的文件

def foo():
    ...

然后用户将通过

访问foo
from util import foo

如果我想为数据库交互添加实用程序函数,并且我希望它们在util 下有自己的命名空间,我需要一个新目录**,并保持 API 兼容性(以便from util import foo仍然有效),我将其称为 util/.我可以像这样将 util.py 移动到 util/ 中,

util/
  __init__.py
  util.py
  db.py

在 util/__init__.py 中做

from util import *

但这是多余的。除了 util/util.py 文件,我们只需将 util.py 的内容放在 __init__.py 中,用户现在就可以

from util import foo
from util.db import check_schema

我认为这很好地突出了 util 包的 __init__.py 与 util 模块的行为方式相似

* 这在其他答案中有所暗示,但我想在这里强调它
** 没有使用进口体操。请注意,创建与文件同名的新包将不起作用,请参阅this

【讨论】:

  • 你的意思不是from util import check_schema,因为你已经在 __init __.py from util import * 中这样做了
  • @Mark no,from util import * 将在 util/__init__.py 中,因此不会导入 db,它将导入 util/util.py 的内容。我会澄清答案
【解决方案4】:

虽然 Python 可以在没有 __init__.py 文件的情况下工作,但您仍然应该包含一个。

它指定目录应该被视为一个包,因此包含它(即使它是空的)。

还有一种情况,您可能实际使用了__init__.py 文件:

想象一下你有以下文件结构:

main_methods 
    |- methods.py

methods.py 包含以下内容:

def foo():
    return 'foo'

要使用foo(),您需要以下条件之一:

from main_methods.methods import foo # Call with foo()
from main_methods import methods # Call with methods.foo()
import main_methods.methods # Call with main_methods.methods.foo()

也许您需要(或想要)将methods.py 保留在main_methods 中(例如运行时/依赖项),但您只想导入main_methods


如果您将methods.py 的名称更改为__init__.py,那么您可以通过导入main_methods 来使用foo()

import main_methods
print(main_methods.foo()) # Prints 'foo'

这是因为 __init__.py 被视为包的一部分。


一些 Python 包实际上是这样做的。以JSON 为例,其中运行import json 实际上是从json 包(see the package file structure here)导入__init__.py

源码:Lib/json/__init__.py

【讨论】:

    【解决方案5】:

    __init__.py 文件使导入变得容易。当包中存在__init__.py 时,可以从文件b.py 中导入函数a(),如下所示:

    from b import a
    

    但是,如果没有它,您将无法直接导入。你要修改系统路径:

    import sys
    sys.path.insert(0, 'path/to/b.py')
    
    from b import a
    

    【讨论】:

      【解决方案6】:

      它曾经是包的必需部分(old, pre-3.3 "regular package",而不是 newer 3.3+ "namespace package")。

      Here's the documentation.

      Python 定义了两种类型的包,常规包和命名空间包。常规包是 Python 3.2 及更早版本中存在的传统包。常规包通常实现为包含__init__.py 文件的目录。当一个常规包被导入时,这个__init__.py文件被隐式执行,它定义的对象被绑定到包命名空间中的名字。 __init__.py 文件可以包含任何其他模块可以包含的相同 Python 代码,并且 Python 会在模块被导入时添加一些额外的属性。

      但只要点击链接,它包含一个示例,更多信息,以及命名空间包的解释,没有__init__.py的那种包。

      【讨论】:

      • 这是什么意思:“这样做是为了防止具有公共名称(例如字符串)的目录无意中隐藏模块搜索路径中稍后出现的有效模块”?
      • @CarlG Python 搜索 list of directories 以解析例如 import 语句中的名称。因为这些可以是任何目录,并且最终用户可以添加任意目录,所以开发人员必须担心目录碰巧与有效的 Python 模块共享名称,例如文档示例中的“字符串”。为了缓解这种情况,它会忽略不包含名为 _ _ init _ _.py 的文件(无空格)的目录,即使它是空白的。
      • @CarlG 试试这个。创建一个名为“datetime”的目录,并在其中创建两个空白文件,init.py 文件(带下划线)和 datetime.py。现在打开一个解释器,导入 sys,然后发出sys.path.insert(0, '/path/to/datetime'),将那个路径替换为你刚刚创建的任何目录的路径。现在试试from datetime import datetime;datetime.now()。你应该得到一个 AttributeError (因为它现在正在导入你的空白文件)。如果您在不创建空白初始化文件的情况下重复这些步骤,则不会发生这种情况。这就是它的目的。
      【解决方案7】:

      从 Python 3.3 开始,不再需要 __init__.py 将目录定义为可导入的 Python 包。

      查看PEP 420: Implicit Namespace Packages:

      对不需要__init__.py 标记文件并且可以自动跨越多个路径段的包目录的本机支持(受各种第三方命名空间包方法的启发,如PEP 420 中所述)

      这是测试:

      $ mkdir -p /tmp/test_init
      $ touch /tmp/test_init/module.py /tmp/test_init/__init__.py
      $ tree -at /tmp/test_init
      /tmp/test_init
      ├── module.py
      └── __init__.py
      $ python3
      
      >>> import sys
      >>> sys.path.insert(0, '/tmp')
      >>> from test_init import module
      >>> import test_init.module
      
      $ rm -f /tmp/test_init/__init__.py
      $ tree -at /tmp/test_init
      /tmp/test_init
      └── module.py
      $ python3
      
      >>> import sys
      >>> sys.path.insert(0, '/tmp')
      >>> from test_init import module
      >>> import test_init.module
      

      参考资料:
      https://docs.python.org/3/whatsnew/3.3.html#pep-420-implicit-namespace-packages
      https://www.python.org/dev/peps/pep-0420/
      Is __init__.py not required for packages in Python 3?

      【讨论】:

      【解决方案8】:

      __init__.py 会将其所在的目录视为可加载模块。

      对于喜欢阅读代码的人,我把Two-Bit Alchemist's评论放在这里。

      $ find /tmp/mydir/
      /tmp/mydir/
      /tmp/mydir//spam
      /tmp/mydir//spam/__init__.py
      /tmp/mydir//spam/module.py
      $ cd ~
      $ python
      >>> import sys
      >>> sys.path.insert(0, '/tmp/mydir')
      >>> from spam import module
      >>> module.myfun(3)
      9
      >>> exit()
      $ 
      $ rm /tmp/mydir/spam/__init__.py*
      $ 
      $ python
      >>> import sys
      >>> sys.path.insert(0, '/tmp/mydir')
      >>> from spam import module
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ImportError: No module named spam
      >>> 
      

      【讨论】:

        【解决方案9】:

        在 Python 中,包的定义非常简单。与 Java 一样,层次结构和目录结构是相同的。但是你必须在一个包中有__init__.py。我会用下面的例子来解释__init__.py文件:

        package_x/
        |--  __init__.py
        |--    subPackage_a/
        |------  __init__.py
        |------  module_m1.py
        |--    subPackage_b/
        |------  __init__.py
        |------  module_n1.py
        |------  module_n2.py
        |------  module_n3.py
        

        __init__.py 可以为空,只要它存在。它表示该目录应该被视为一个包。当然__init__.py也可以设置相应的内容。

        如果我们在module_n1中添加一个函数:

        def function_X():
            print "function_X in module_n1"
            return
        

        运行后:

        >>>from package_x.subPackage_b.module_n1 import function_X
        >>>function_X()
        
        function_X in module_n1 
        

        然后我们按照层次包,调用module_n1这个函数。我们可以像这样在 subPackage_b 中使用__init__.py

        __all__ = ['module_n2', 'module_n3']
        

        运行后:

        >>>from package_x.subPackage_b import * 
        >>>module_n1.function_X()
        
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        ImportError: No module named module_n1
        

        因此使用*导入,模块包以__init__.py内容为准。

        【讨论】:

        • 我的 setup.py 如何通过打包的库执行相同的导入? from package_x.subPackage_b.module_n1 import function_X
        • 所以这里的关键是“使用*导入,模块包受init.py内容”
        【解决方案10】:

        除了将目录标记为 Python 包并定义 __all____init__.py 允许您在包级别定义任何变量。如果包定义了某些内容,这样做通常很方便将以类似 API 的方式频繁导入。这种模式促进了对 Pythonic“扁平优于嵌套”理念的坚持。

        一个例子

        这是我的一个项目中的一个示例,其中我经常导入一个名为 Sessionsessionmaker 以与我的数据库进行交互。我写了一个包含几个模块的“数据库”包:

        database/
            __init__.py
            schema.py
            insertions.py
            queries.py
        

        我的__init__.py 包含以下代码:

        import os
        
        from sqlalchemy.orm import sessionmaker
        from sqlalchemy import create_engine
        
        engine = create_engine(os.environ['DATABASE_URL'])
        Session = sessionmaker(bind=engine)
        

        由于我在这里定义了Session,我可以使用以下语法开始一个新会话。这段代码在“数据库”包目录内部或外部执行的代码都是一样的。

        from database import Session
        session = Session()
        

        当然,这是一个小小的便利——替代方法是在我的数据库包中的“create_session.py”之类的新文件中定义Session,然后使用以下命令启动新会话:

        from database.create_session import Session
        session = Session()
        

        进一步阅读

        这里有一个非常有趣的 reddit 线程,涵盖了 __init__.py 的适当用法:

        http://www.reddit.com/r/Python/comments/1bbbwk/whats_your_opinion_on_what_to_include_in_init_py/

        大多数人的意见似乎是__init__.py 文件应该非常细,以避免违反“显式优于隐式”的理念。

        【讨论】:

        • enginesessionmakercreate_engineos 现在也都可以从 database 导入...看来你已经把那个命名空间弄得一团糟了。
        • @ArtOfWarfare,您可以使用__all__ = [...] 来限制使用import * 导入的内容。但除此之外,是的,你留下了一个混乱的顶级命名空间。
        • @NathanGould 你也可以使用import * 默认不导入的单前导下划线变量。例如:import os as _os 并在 __init__.py 模块中使用 _os 代替 os
        【解决方案11】:

        它有助于导入其他 python 文件。当您将此文件放在包含其他 py 文件的目录(例如东西)中时,您可以执行诸如 import stuff.other 之类的操作。

        root\
            stuff\
                 other.py
        
            morestuff\
                 another.py
        

        如果目录 stuff 中没有这个 __init__.py,您将无法导入 other.py,因为 Python 不知道 stuff 的源代码在哪里,并且无法将其识别为包。

        【讨论】:

        • 我的项目(python 3.4)中具有相同的结构,但我无法让另一个.py 看到 other.py。我应该如何进行导入?从 root.stuff 导入其他?它可以在 VSCode 调试模式下工作,但不能在命令行下工作。有什么想法吗?
        【解决方案12】:

        __init__.py 文件使 Python 将包含它的目录视为模块。

        此外,这是要在模块中加载的第一个文件,因此您可以使用它来执行每次加载模块时要运行的代码,或指定要导出的子模块。

        【讨论】:

        • 我认为 init.py 让 Python 将目录视为 packages 而不是 modules。见docs.python.org/3/tutorial/modules.html
        • “所有包都是模块,但不是所有模块都是包”——很奇怪,但确实如此。
        猜你喜欢
        • 2019-10-06
        • 2013-05-08
        • 1970-01-01
        • 2015-09-28
        • 2018-12-29
        • 2015-05-26
        • 2015-07-13
        相关资源
        最近更新 更多