今天聊模块和库的导入。这玩意儿刚开始学的时候觉得挺简单,真正用起来才发现到处都是坑------尤其是那个路径问题,简直能把人搞疯。
一、先说清楚几个概念
模块 就是一个.py文件,比如你写了个utils.py,里面放了一些常用的函数,这个文件就是一个模块。
包 其实就是个文件夹,里面放了一堆模块,还得有个__init__.py文件(就算它是空的也行)。比如你建了个tools文件夹,里面放了calculator.py和validator.py,那tools就是个包。
标准库 是Python自带的,比如os、sys这些,不用安装就能用。
第三方库 得用pip安装,比如requests、pandas这些。
二、三种导入方式,到底用哪个?
1. 最老实的写法
import math
result = math.sqrt(16)
这么写的好处是清楚------一看就知道sqrt是从math来的,不会跟别的函数搞混。缺点是写起来有点啰嗦。
2. 直接拿过来用
from math import sqrt, pi
result = sqrt(16)
这样写代码清爽多了,但有风险------万一你别的文件里也有个sqrt函数,那就冲突了。我吃过这个亏,调试了半天才发现是命名冲突。
3. 起个小名
import numpy as np
import pandas as pd
这已经是行业惯例了。尤其是做数据分析的,没人会写numpy.array(),都是np.array()。代码短了,还不会冲突。
三、自定义模块导入的坑
场景1:同一个文件夹里
我的项目/
├── main.py
└── utils.py
这种情况最简单:
# main.py里直接写
import utils
搞定。
场景2:模块在子文件夹里
我的项目/
├── main.py
└── 工具集/
├── __init__.py
└── 计算器.py
这时候你得:
from 工具集 import 计算器
# 或者
import 工具集.计算器
注意那个__init__.py文件必须有,哪怕是空的。这是Python识别"这是个包"的标志。
场景3:想从上级目录导入(这个最坑!)
假设你的目录结构是这样的:
项目根目录/
├── 配置/
│ └── config.py
└── 源代码/
└── main.py
你在main.py里想用config.py,直接导入肯定报错。得这么搞:
import sys
import os
# 先把上级目录加到Python的搜索路径里
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from 配置 import config
每次看到这种代码都觉得挺丑陋的,但没办法,Python就是这么找模块的。
四、Python到底怎么找模块?
你可以在代码里打印一下:
import sys
print(sys.path)
你会看到一个列表,Python就按这个顺序找模块:
- 先找当前脚本所在的目录
- 再找环境变量PYTHONPATH设置的路径
- 然后找Python安装目录下的标准库
- 最后找第三方库(通常都在site-packages里)
关键问题来了 :Python解释器认为的"当前目录",跟你命令行里pwd看到的可能不一样!这就是很多导入错误的根源。
比如你在/home/user/project里执行:
cd /home/user
python project/main.py
Python会认为当前目录是/home/user,不是/home/user/project。所以如果你的模块在project文件夹里,它就找不到了。
五、几个实用技巧
1. 导入顺序有讲究
我习惯这么排:
# 1. 标准库
import os
import sys
# 2. 第三方库
import requests
import pandas as pd
# 3. 自己的模块
from mytools import utils
这样代码看起来清楚,也容易维护。
2. 避免循环导入
两个文件互相导入,Python就懵了:
# a.py
import b # 导入b
# b.py
import a # 又导入a,死循环了!
解决方案要么重新设计代码结构,要么把导入语句放到函数里面去。
3. __init__.py不只是个标记
这个文件可以很有用:
# tools/__init__.py
from .calculator import add, multiply
from .validator import is_email
# 这样在外面就可以直接:
from tools import add, is_email
# 而不用:
from tools.calculator import add
from tools.validator import is_email
六、真实项目中的经验
1. 相对导入 vs 绝对导入
在包内部,可以用相对导入:
# 在tools/advanced.py里
from .calculator import add # 从同级目录导入
from ..utils import helper # 从上级目录导入
但在项目入口文件里(比如main.py),最好用绝对导入:
from tools.calculator import add
2. 动态导入
有些特别大的模块,如果一开始就导入,启动会很慢。可以等用到的时候再导:
def process_data():
# 只有调用这个函数时才会导入pandas
import pandas as pd
data = pd.read_csv('file.csv')
# ...
不过别滥用,代码会显得很乱。
七、常见错误和解决
"ModuleNotFoundError: No module named 'xxx'"
这是最常遇到的。先检查:
- 文件名字拼对了吗?(它对大小写很敏感!)
- 文件在Python能搜到的路径里吗?
- 如果是包,有
__init__.py吗?
"ImportError: cannot import name 'xxx' from 'yyy'"
可能是:
- 循环导入了
- 模块里确实没有这个函数/类
- 有语法错误导致模块加载失败
"AttributeError: module 'xxx' has no attribute 'yyy'"
通常是:
- 你写错了函数/类名
- 导入的模块不对(同名模块?)