Init Package Progress

(date: 2025-10-24)

怎样才能构成一个包 (原理)

在 Python 中,通常一个目录要被识别为一个包(package),需要在该目录中包含一个 __init__.py 文件。这个文件可以是空的,但它的存在是必要的,因为它告诉 Python 解释器这个目录是一个包。

不过,从 Python 3.3 开始,引入了一个新的特性,即“隐式命名空间包”(Implicit Namespace Packages)。这种类型的包不需要 __init__.py 文件,但有一些限制和要求:

  1. 目录结构:每个目录(包)必须包含一个 __init__.py 文件,或者至少包含一个 Python 文件或子目录。

  2. PEP 420:Python 的 PEP 420 定义了命名空间包的规范,要求包目录必须包含 __init__.py 文件,或者至少包含一个 Python 文件或子目录。

因此,如果你的子目录中不包含 __init__.py 文件,但包含至少一个 Python 文件或子目录,并且你使用的是 Python 3.3 或更高版本,那么这个子目录仍然可以被识别为一个包。

实验说明

这段笔记记录了一个关于Python模块导入机制的实验,主要目的是:

  1. 测试不同包结构的导入方式 - 包括常规包、子模块和命名空间包

  2. 验证导入路径和工作目录 - 通过os.getcwd()检查当前工作目录

  3. 演示命名空间包的特性 - package3是一个空目录,形成了命名空间包

文件结构

module_test/
├── package1
   ├── __init__.py
   └── module1.py
├── package2
   ├── __init__.py
   └── module2.py
├── package3  # 空目录,形成命名空间包
└── script.py

module1.py

# module_test/package1/module1.py
import os

def run():
    print("module 1 start ...")
    print(os.getcwd())
    print("module 1 end ...")

module2.py

# module_test/package2/module2.py
import os

def run():
    print("module 2 start ...")
    print(os.getcwd())
    print("module 2 end ...")

script.py

# module_test/script.py
import os

print("script start ...")
print("Current working directory:", os.getcwd())
print("script end")

# 导入测试
from package1 import module1
from package2 import module2
import package3

# 执行导入的模块功能
module1.run()
module2.run()

# 显示package3的信息
print("package3 info:", package3)
print("package3 __path__:", getattr(package3, '__path__', 'No __path__'))

def run1():
    print("run1 start ...")
    print("Current working directory:", os.getcwd())
    print("run1 end ...")

def run2():
    print("run2 start ...")
    print("Current working directory:", os.getcwd())
    print("run2 end ...")

if __name__ == '__main__':
    run1()
    run2()

预期执行结果:

script start ...
Current working directory: /home/dai/PycharmProjects/module_test
script end
module 1 start ...
/home/dai/PycharmProjects/module_test
module 1 end ...
module 2 start ...
/home/dai/PycharmProjects/module_test
module 2 end ...
package3 info: <module 'package3' (namespace)>
package3 __path__: _NamespacePath(['/home/dai/PycharmProjects/module_test/package3'])
run1 start ...
Current working directory: /home/dai/PycharmProjects/module_test
run1 end ...
run2 start ...
Current working directory: /home/dai/PycharmProjects/module_test
run2 end ...

关键现象说明:

  1. 工作目录一致性:所有模块中的os.getcwd()都显示相同的工作目录,即项目根目录

  2. 命名空间包特性

    • package3作为空目录(无__init__.py)被Python识别为命名空间包

    • 输出显示<module 'package3' (namespace)>,表明这是一个命名空间模块

    • 命名空间包可以跨多个目录分布,为灵活的包结构提供支持

  3. 导入机制

    • 常规包(package1、package2)需要__init__.py文件

    • 命名空间包(package3)不需要__init__.py文件

    • 所有导入都成功执行,没有报错

这个实验清晰地展示了Python中不同包类型的导入行为和工作目录的保持一致性。