__main__.py 和 __init__.py

本文探讨了Python包如何通过__init__.py和__main__.py文件实现统一的执行入口,并介绍了不同调用方式下Python解释器的行为差异。

转载:http://zengrong.net/post/2192.htm


本文基于 python 3.4 ,在 python 2.7 上也可以使用。


在昨天的文章 在 setuptools 中使用 dependency_links 里,我提到了发布hhlb 工具集的工作。今天又遇到了一些具体的问题。

hhlb 的文件夹结构如下:

.
├── MANIFEST.in
├── README.rst
├── hhlb
│   ├── __init__.py
│   ├── __main__.py
│   ├── admin.py
│   ├── base.py
│   ├── bin
│   ├── build.conf
│   ├── config.py
│   ├── init.py
│   ├── res.py
│   ├── templ.py
│   └── update.py
├── requirements.txt
├── setup.py
└── test
    └── testbuild.py

hhlb 是一个 python package。最初的做法,我在 hhlb 的同级目录中放了一个 build.py 文件,在这个文件中 import hhlb 这个 package ,使用其功能。

例如,下面的代码会强制初始化整个项目:

python build.py init -af

build.py 这个引导文件其实 没有存在的必要 。既然我要将 hhlb 发布成一个工具,我就应该让它能像这样被调用:

hhlb init -af
python -m hhlb init -af

第一种调用方法,就像 pip 一样。第二种方法,就像 http.server 一样。

另外,在开发过程中,还可以直接通过指定 hhlb 文件夹路径的方式来调用:

python /path/to/hhlb init -af

1. 调用顺序

为了实现上面的功能,我们先需要了解一下 python 对于 package 的调用顺序。

如果你希望 python 将一个文件夹作为 package 对待,那么这个文件夹中必须包含一个名为 __init__.py 的文件,即使它是空的。 参见: Packages

如果你需要 python 讲一个文件夹作为 package 执行,那么这个文件夹中必须包含一个名为 __main__.py 的文件,当执行 python -m hhlb 或者python hhlb 的时候,这个文件中的代码都会被执行。参见: main — Top-level script environment

让我们做个试验。

在 hhlb/__init__.py 中写入如下内容:

print('__init__')
print('__init__.__name__', __name__)
print('__init__.__package__', __package__)

在 hhlb/__main__.py 中写入如下内容:

print('__main__')
print('__main__.__name__', __name__)
print('__main__.__package__', __package__)

执行 python hhlb ,打印如下:

-> % python hhlb
__main__
__main__.__name__ __main__
__main__.__package__

这说明,将 hhlb 当作文件夹执行的时候,对于 __main__.py 来说,变量__package__ 是一个空字符串。而 __init__.py 不会被执行。

下面看看执行 python -m hhlb 的效果:

-> % python -m hhlb
__init__
__init__.__name__ hhlb
__init__.__package__ hhlb
__main__
__main__.__name__ __main__
__main__.__package__ hhlb

当作为模块执行的时候,python 会先执行 __init__.py ,然后执行__main__.py 。而且,前者和后者对于 __name__ 变量的理解是不同的。

2. 定义一个 main()

对于一个 package 来说,既然 __init__.py 必须存在,而且当作为模块执行的时候,它会先执行,我们就应该把入口函数 main() 定义在 __init__.py中。

当我们使用模块方式 -m 调用包的时候,__init__.py 定义了 main() 函数,然后在 __main__.py 中调用它,就能实现我们统一入口的目的。

那么继续试验。

将 hhlb/__init__.py 修改成这样:

print('__init__')
print('__init__.__name__', __name__)
print('__init__.__package__', __package__)

def main():
    print('__init__.main()')

在 hhlb/__main__.py 中对 main() 进行调用:

print('__main__')
print('__main__.__name__', __name__)
print('__main__.__package__', __package__)

import hhlb
hhlb.main()

注意,在 __main__.py 中,没有必要判断 __name__ 是否为 __main__ ,因为无论如何调用, __name__ 的值都一定是 __main__ 。 

Python 项目中正确设置 `__init__.py` 文件是组织模块包结构的重要步骤。尽管从 Python 3.3 开始,`__init__.py` 不再是必需的文件[^2],但为了向后兼容、更好的模块管理以及提供更清晰的包结构定义,通常仍然建议使用 `__init__.py` 文件。 ### 设置 `__init__.py` 文件的基本方法 1. **创建空的 `__init__.py` 文件** 最简单的做法是在每个包含模块的目录中创建一个空的 `__init__.py` 文件。这样做的目的是告诉 Python 解释器该目录应被视为一个包。例如,如果你的项目结构如下: ``` my_project/ ├── package1/ │ ├── __init__.py │ └── module1.py └── main.py ``` 那么你可以通过以下方式导入模块: ```python from package1 import module1 ``` 2. **在 `__init__.py` 中定义 `__all__` 变量** 如果你希望控制当用户使用 `from package import *` 时导入哪些模块,可以在 `__init__.py` 中定义 `__all__` 列表。例如: ```python # package1/__init__.py __all__ = ["module1", "module2"] ``` 这样,当你执行 `from package1 import *` 时,只会导入 `module1` `module2`。 3. **在 `__init__.py` 中导入子模块或子包** 你可以在 `__init__.py` 中预加载一些常用的模块或函数,以便用户可以直接从包级别访问它们。例如: ```python # package1/__init__.py from .module1 import some_function ``` 现在你可以直接使用: ```python from package1 import some_function ``` 4. **设置包级别的初始化代码** 如果你需要在导入包时执行某些初始化逻辑(如日志记录、配置加载等),可以在 `__init__.py` 中添加相应的代码。例如: ```python # package1/__init__.py print("Initializing package1") ``` 5. **支持命名空间包(Namespace Packages)** 在 Python 3.3+ 中,即使没有 `__init__.py` 文件,也可以创建命名空间包。这种包允许你在不同位置分布同一名称的子包,适用于大型项目或多团队协作场景。但在实际开发中,除非明确需要,否则仍推荐使用传统的 `__init__.py` 包结构。 ### 示例:完整项目中的 `__init__.py` 配置 假设你的项目结构如下: ``` my_project/ ├── src/ │ ├── __init__.py │ ├── utils/ │ │ ├── __init__.py │ │ └── helpers.py │ └── core/ │ ├── __init__.py │ └── engine.py └── main.py ``` 你可以在各层级的 `__init__.py` 中进行适当的配置。例如: - `src/utils/__init__.py`: ```python from .helpers import format_data, validate_input ``` - `src/core/__init__.py`: ```python from .engine import run_simulation ``` - `src/__init__.py`: ```python from .utils import format_data from .core import run_simulation ``` 这样,在 `main.py` 中可以方便地导入: ```python from src.utils import format_data from src.core import run_simulation ``` ### 总结 合理配置 `__init__.py` 文件可以提升项目的可维护性模块化程度。虽然现代 Python 版本不再强制要求该文件,但在实际项目中保留它有助于保持一致性兼容性,并为包提供更清晰的接口定义。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值