Python学习
一、学前花絮
我们上篇文章讨论了在 Python 开发中经常用到的模块相对导入的问题,比如在一个项目有多个子目录,如何相对导入某个模块。在那篇文章中提到了__init__.py文件是区分包模块和一般目录的区别。
那么问题来了,这个init文件到底是干什么用的?当我们在pycharm中有个程序import requests的时候,我们可以用ctrl + 去点击reuests就进入了该模块(实际上是进入了reuqests包模块下面的__init__.py文件)。我们发现在应用程序中用到的request.get函数并没有在该文件中直接定义,而是from .api import delete, get,也就是说在requests包模块下面有个api.py文件定义了get函数,但是在init文件中导入。这个也太高级了吧?
由此我联想到在类的定义中,有init方法,当类实例化对象之后可以起到初始化的作用。那么这个方法与包模块的init文件有什么关系吗?
二、Python中类的init方法与包模块init文件详解
在 Python 的世界里,存在着一种有趣的“分形”结构:小到一个对象,大到一个完整的包,其构建和初始化的逻辑竟如此相似。理解这种相似性,能让我们写出更专业、更易用的代码。
2.1 核心类比:构造与初始化的镜像
在深入代码前,我们需要建立一个宏观的视角。Python 中的类 与包在逻辑上是同构的。
1.类的 __init__(self):个体的诞生
当我们实例化一个对象()时, 方法被触发。
作用: 为这个特定的“个体”分配初始状态(属性),组装它的内部组件。
结果: 外界通过它与之交互,而无需关心内部细节。
2.包的__init__.py:模块的诞生
当我们导入一个包时, 该文件被自动执行。
作用:为这个“模块”搭建运行环境,组装分散在各处的子模块和函数。
结果:外界通过它与之交互,获得一个整洁的接口。
结论: 它就是包的“构造函数”。它不负责业务逻辑,而是负责把包组装好,让使用者感觉这是一个整体,而不是一堆散落的文件。
2.2 现实案例:Requests 库的“门面”艺术
著名的Requests库是这种设计哲学的完美典范。如果你通过ctrl+点击查看其代码,你会发现它并没有写发送 HTTP 请求的具体代码,而是充满了这样的语句:
它解决了什么问题?
如果没有这种“封装”,用户必须这样写代码:
(from requests.api import get用户需要知道内部结构,体验差)。
有了__init__.py 的构造:
用户只需写:import requests (用户只关心功能,不关心实现)。
这就是封装的力量:把复杂留给自己,把简单留给用户。
2.3 进阶实战:将 My_Project 改造为专业级库
2.3.1改造示例
之前的项目文件结构:

如果我们需要再main.py中引入create_product函数:
from core.product import create_product |
改造后的结构(专业版)
我们希望用户能直接用 import core 然后 core.create_product()。
修改 core/__init__.py:

类似系统的requests模块中的init文件,我们改造自己项目中的init文件,里面的product类似requests中的api。这样应用程序就可以直接用core.create_product类似requests.get了。
2.3.2上述内容总结
针对自己项目的“专业级”改造,我们感受到了这种设计模式的魅力。
1. 改造前:脚本式思维(散装)
结构: 目录下直接放各种py文件。
使用: 很具体的导入,类似requests.api.get。而api是与应用无关的,用户只想用requests.get。
问题: 用户必须知道get是在api.py里实现的。如果以后我改名了api为webapi,所有用户的代码都得改。
2. 改造后:库式思维(封装)
我们要让使用者像用request.get一样用core.create_product。
步骤一:定义包的“门面” (core/__init__.py)
这是改造的核心。我们在这里把内部的细节“导出”到包级别。
步骤二:保持内部实现 (core/product.py)
内部逻辑保持不变,但不再关心外部怎么调用我。
步骤三:像使用 Requests 一样使用它 (main.py)
这才是我们想要的优雅。
结论:
__init__.py 是包的“门面”:它决定了别人看到你的包时,第一眼能看到什么。
requests 的做法是标准:它把最常用的函数(get, post)放在门口,把复杂的实现藏在后面。
你的项目:如果你希望它看起来像个“正经的库”,就学着 requests 的样子,在 __init__.py 里把常用的函数、类 import 进来。
这就叫“把复杂留给自己,把简单留给用户”。
2.4 进一步了解init文件的作用
我们在使用pycharm创建文件夹的时候,有个选项是创建python软件包还是创建目录?如果你选择前者,那么目录下面自动生成__init__.py文件,内容是空的。而如果你选择后者,那就是普通的文件夹,与在cmd下创建目录没有不同。
那么问题来了,在学习本文之前,我们也做了一些项目,包括包模块的使用,但对于init文件却没有修改,一直是空的。而且调试程序也都成功了。
但空的 __init__.py 就好比一个只挂了牌子但大门紧闭的公司——虽然注册成了“包”,但既没装修也没招人,确实和普通文件夹没太大区别。而 __init__.py 真正的威力,就在于把它从一个“空壳”变成一个精心布置的“门面”。
2.4.1表面上的区别:包 vs 文件夹
空的 __init__.py:
作用: 它只有一个作用,就是告诉 Python 解释器:“这是一个包,不是普通文件夹”。
效果:有了它,你才能进行 import 操作,才能使用相对导入(.)。没有它,Python 就把那个目录当成普通文件夹,不许你导入里面的文件。
比喻:它就像给一个房间挂上了一块写着“办公室”的牌子。它让这个房间具备了“办公”的资格,但房间里其实是空的,还是堆满杂物,它不管。
普通文件夹(没有 __init__.py):
作用: 纯粹的物理容器,用来存放文件。
效果: Python 无法直接从这个文件夹导入模块(除非你修改路径)。
结论: 从“能不能导入”这个角度看,空的 __init__.py 和普通文件夹是不一样的,它赋予了目录“包的身份”。但从“功能”上看,如果里面是空的,它确实没有做任何封装工作。
2.4.1逻辑上的区别:空壳 vs 门面
空的 __init__.py(空壳模式):
逻辑:“我是一个包,里面的东西都在各自的文件里,你自己去翻吧。”
使用者体验:使用者必须知道你的内部结构。比如,他必须写 from core.product import create_product。他得知道 create_product 是在 product.py 里实现的。
有代码的 __init__.py(门面模式):
逻辑:“我是一个包,我负责把里面的东西整理好,只把有用的给你。”
使用者体验: 使用者不需要知道内部结构。他只需要写 import core,然后 core.create_product()。他不需要知道 create_product 是在 product.py 还是 goods.py 里实现的。
结论: 空的 __init__.py 只是完成了“身份认证”,而有代码的 __init__.py 才完成了“接口封装”。
三、小结
所以,一个专业的 Python 包,它的__init__.py 通常都不是空的,而是充满了 from ... import ... 和变量定义,因为它要充当这个包的“门面”,控制使用者的体验。通过了解python的包模块init文件与类中init方法的对比,并改造自己的项目,让自己的代码看上去更加高级。
让我们保持学习热情,多做练习。我们下期再见!