Python中import模块文件与模块目录的实质性差异探讨
__init__.py的目录模块的实质性差异 当执行import modulename时,单文件modulename.py和带同名__init__.py的modulename目录,除了你提到的__file__不同、目录模块支持子模块外,还有以下关键差异:
模块属性与类型标识差异
目录形式的包模块会拥有__path__属性,用于指定包的搜索路径;而单文件模块没有这个属性。可以通过以下代码判断:import modulename print(hasattr(modulename, '__path__')) # 包模块返回True,单文件模块返回False此外,虽然两者的
__name__属性均为modulename,但__package__属性在Python 3中的行为略有区别:单文件模块作为顶层模块导入时,__package__等于__name__;而包模块的__package__始终等于包名,即使在__init__.py内部执行时也是如此。相对导入的支持差异
包模块的__init__.py文件属于包结构的一部分,可以使用相对导入语法(如from . import submodule)来导入同一包内的子模块;而单文件模块作为顶层模块,使用相对导入会直接触发ValueError: attempted relative import in non-package错误。
例如:- 若目录
m下有sub.py,m/__init__.py中可以写from . import sub,导入后可通过m.sub访问子模块; - 但单文件
m.py中写from . import sub会直接报错,因为它不属于任何包结构。
- 若目录
字节码缓存文件的位置不同
Python导入模块时生成的.pyc字节码文件,存放位置有差异:- 单文件模块的字节码会存放在与模块同目录的
__pycache__文件夹中,文件名为modulename.cpython-<版本号>.pyc; - 包模块的
__init__.py对应的字节码,会存放在包目录内部的__pycache__文件夹中,文件名为__init__.cpython-<版本号>.pyc。
- 单文件模块的字节码会存放在与模块同目录的
直接运行时的行为差异
- 直接运行单文件模块:
python modulename.py,此时模块的__name__为__main__,属于顶层模块,无法使用相对导入; - 直接运行包的
__init__.py:python modulename/__init__.py,虽然__name__也是__main__,但此时它被当作普通脚本而非包的一部分,内部的相对导入会报错; - 使用
python -m modulename运行包时,模块的__name__为__main__,但__package__会被正确设置为包名,此时__init__.py内的相对导入可以正常工作;而单文件模块用python -m modulename运行时,__package__会被设置为空字符串(Python 3),相对导入同样无法使用。
- 直接运行单文件模块:
Python 2中的额外差异
在Python 2中,目录必须包含__init__.py才能被识别为包,而Python 3.3+支持无__init__.py的命名空间包(但你问题中是带__init__.py的情况)。此外,Python 2中单文件模块的__package__属性默认为空,而包模块的__package__等于包名,这会影响一些依赖__package__的导入逻辑。
除了上述差异外,在常规的属性访问(如示例中的m.s)、from modulename import s或import modulename as alias等导入变体场景下,两者的表现基本一致,核心差异仍集中在包特有的属性、导入逻辑和运行行为上。
内容的提问来源于stack exchange,提问作者jonathanjo

