📖 知识点简介
当你写了几百行代码后会发现,全塞在一个文件里就是灾难。Python 用 模块(Module) 和 包(Package) 这两大武器帮你组织代码。学完今天的内容,你会彻底搞懂 import 的底层逻辑、__init__.py 的意义、以及如何避免循环导入这种经典噩梦。
1️⃣ 模块:就是一个 .py 文件
模块 = 一个 .py 文件。你用 import 引入它,就能用它里面的变量、函数和类。
示例
# my_utils.py
def greet(name):
return f"Hello, {name}!"
PI = 3.14159
class Calculator:
@staticmethod
def add(a, b):
return a + b
# main.py — 导入并使用
import my_utils
print(my_utils.greet("Alice")) # Hello, Alice!
print(my_utils.PI) # 3.14159
calc = my_utils.Calculator()
print(calc.add(1, 2)) # 3
导入风格对比
| 方式 | 写法 | 使用 | 优缺点 |
|---|---|---|---|
import module | import my_utils | my_utils.greet() | 命名清晰,避免冲突 |
from module import name | from my_utils import greet | greet() | 简洁,但可能污染命名空间 |
from module import * | from my_utils import * | 全部暴露 | 🚫 不推荐!你不知道导入了什么 |
import module as alias | import my_utils as mu | mu.greet() | 适合长名字模块 |
🚨 避坑:__name__ 与模块入口
每个 Python 模块都有 __name__ 属性:
- 被直接运行时 →
__name__ = "__main__" - 被导入时 →
__name__ = 模块名
# calc.py
def add(a, b): return a + b
# 只有直接运行时才执行这行
if __name__ == "__main__":
print(add(3, 5)) # 测试代码放这里
为什么重要? 如果不加这个判断,别人 import 你的模块时,测试代码会跟着跑一遍!
2️⃣ 包:就是一个带 __init__.py 的文件夹
包(Package)本质是一个包含 __init__.py 文件的目录。__init__.py 可以是空文件,它的存在告诉 Python:「这个目录是一个包」。
目录结构示例
project/
├── main.py
└── mypackage/
├── __init__.py
├── math_ops.py
└── string_ops.py
# mypackage/__init__.py
print("包已加载!")
# mypackage/math_ops.py
def add(a, b): return a + b
def multiply(a, b): return a * b
# mypackage/string_ops.py
def upper(text): return text.upper()
# main.py 中导入
import mypackage.math_ops
print(mypackage.math_ops.add(3, 4)) # 7
__init__.py 的妙用
不只是标记包的存在,还能做很多事:
# mypackage/__init__.py
# 1. 简化导入路径
from mypackage.math_ops import add, multiply
from mypackage.string_ops import upper
# 2. 定义 __all__ 控制 from package import *
__all__ = ["add", "multiply", "upper"]
# 3. 包级别初始化:读取配置、建立数据库连接
这样用户在 main.py 里就可以直接写:
import mypackage
mypackage.add(3, 4) # 不用写 mypackage.math_ops.add
3️⃣ Python 的 import 机制(必知底层原理)
当你写 import foo 时,Python 按顺序搜索这些位置:
- 内置模块(sys.modules)— 已加载的模块缓存
- 当前脚本所在目录(第一个找到的)
- PYTHONPATH 环境变量指定的路径
- 标准库路径(Python 安装目录下的 lib)
- site-packages(第三方包安装位置)
import sys
# 查看模块搜索路径
for path in sys.path:
print(path)
模块只加载一次!
import mymodule # 首次:执行并缓存
import mymodule # 第二次:直接从缓存读取,不会再次执行!
Python 用 sys.modules 字典缓存所有已加载的模块:
import sys
print(sys.modules.keys()) # 看看有哪些模块
4️⃣ 绝对导入 vs 相对导入
绝对导入(推荐)
从项目根目录或 sys.path 已知位置开始引用:
# 任何时候都推荐这样写
from package.subpackage import module
from package import module
相对导入(🚨 有坑)
# 在一个包内部使用
from . import sibling_module # 当前包的兄弟模块
from .. import parent_package # 父包
from ..sibling import some_function # 兄弟包里的内容
关键规则:
| 导入方式 | 唯一 | 可使用场景 |
|---|---|---|
| 绝对导入 | ✅ | 任何文件 |
| 相对导入 | ✅ | 仅在包的内部文件(用 . 开头) |
| 直接运行的文件 | ❌ | 不能用相对导入!会报错 ImportError |
🚨 经典坑:相对导入 + 直接运行
# mypackage/foo.py
from . import bar # 写得很开心
# 当你 python mypackage/foo.py 直接运行 → ❌ 报错!
# 正确使用:在包外部通过 import 引用它
5️⃣ 实战:打造可维护的包结构
project/
├── main.py
├── config.py
├── requirements.txt
├── README.md
└── myapp/
├── __init__.py # 应用入口
├── models/
│ ├── __init__.py
│ ├── user.py
│ └── order.py
├── services/
│ ├── __init__.py
│ ├── auth.py
│ └── payment.py
└── utils/
├── __init__.py
├── logger.py
└── validators.py
# myapp/__init__.py
from myapp.services.auth import login
from myapp.services.payment import process_payment
__version__ = "1.0.0"
# myapp/models/user.py
class User:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
# myapp/services/auth.py
from myapp.models.user import User # 绝对导入
def login(email: str, password: str) -> User | None:
"""用户登录逻辑"""
...
# main.py — 使用简洁接口
from myapp import login, __version__
print(f"myapp v{__version__}")
user = login("alice@example.com", "123456")
⚡ 避坑 & 要点总结
| # | 要点 | 说明 |
|---|---|---|
| 1️⃣ | __init__.py 不是必须的?(Python 3.3+) | 有它可以做初始化,没有也可做命名空间包 |
| 2️⃣ | 相对导入用 dot | . = 当前包,.. = 父包,... = 爷爷包 |
| 3️⃣ | 循环导入是设计问题 | 把公共依赖提取到第三个模块,不要用"延迟导入"糊弄 |
| 4️⃣ | __all__ 控制通配导入 | 在 __init__.py 定义,from pkg import * 时生效 |
| 5️⃣ | 模块缓存用 sys.modules | importlib.reload(module) 可强制重载 |
| 6️⃣ | 脚本入口加 __name__ 检查 | 这是 Python 社区共识,没有例外 |
# 调试小技巧:看模块从哪里来的
import requests
print(requests.__file__)
# /Users/you/.venv/lib/python3.12/site-packages/requests/__init__.py