Python零基础入门:一篇带你吃透Python核心基础
Python零基础入门:一篇带你吃透Python核心基础
本文系统梳理Python编程的核心基础知识,从环境搭建、基础语法到数据容器、函数、文件操作、异常处理,适合零基础入门学习,也可作为复习速查手册。所有知识点均配套代码示例,开箱即用。
文章目录
- Python零基础入门:一篇带你吃透Python核心基础
-
- 一、Python概述与环境搭建
- 二、基础语法入门
- 三、流程控制语句
- 四、数据容器详解
- 五、推导式(Comprehension)
- 六、函数基础
- 七、文件操作
- 八、异常处理
- 九、模块与包
- 十、总结
一、Python概述与环境搭建
1.1 Python语言简介
Python由吉多·范·罗苏姆(龟叔)发明,是一门解释型、面向对象、语法简洁的高级编程语言。它具备以下核心特点:
- 语法简单,接近自然语言,学习门槛低
- 开源免费,拥有极其丰富的第三方库
- 跨平台可移植,支持Windows、Linux、MacOS
- 应用场景广泛:Web开发、数据分析、人工智能、自动化运维、爬虫等
- 官网 https://www.python.org/
💡 小知识:Python 之父是 Guido van Rossum(龟叔),吉祥物是蛇,python因此得名。
Python属于解释型语言:
1.2 环境搭建
学习Python首先需要安装两个核心工具:
- Python解释器:负责解析执行Python代码,推荐安装3.8及以上稳定版本
- PyCharm:Python主流集成开发工具,代码补全、调试功能强大
Windows平台下载安装
对于绝大多数初学者,Windows 是主要的学习平台。下面我们分步详解如何在 Windows 上安装 Python 解释器。
1. 下载官方安装包
访问 Python 官方下载页:https://www.python.org/downloads/windows/
- 在 Stable Releases(稳定版本)列表中,选择最新的
Python 3.10.x或Python 3.11.x版本(x 代表小版本号,如 3.10.12)。建议选择 3.10 或 3.11 这类长期支持(LTS)版本,兼容性更好。 - 根据你的系统架构下载对应的安装包:
- 64位系统:点击
Windows installer (64-bit)下载。 - 32位系统:点击
Windows installer (32-bit)下载(目前新电脑基本都是 64 位,如果不确定,可以在“此电脑”右键“属性”中查看系统类型)。
- 64位系统:点击
💡 版本选择建议:对于零基础学习,不要下载 Python 2.x,它与 Python 3 语法不兼容。也不建议下载最新的预览版(如 3.12.0b1),可能存在未知问题。选择标注为 “Stable” 的版本即可。
2. 执行安装程序
下载完成后,双击运行 .exe 安装包,进入安装向导。这里有几个关键选项需要特别注意:
-
第一步:勾选 “Add Python.exe to PATH”
这是最重要的一步!务必在安装界面最底部找到并勾选 “Add Python.exe to PATH”。勾选后,安装程序会自动将 Python 和 pip 的可执行文件路径添加到系统的环境变量中。这样,以后你就可以在任意目录下的命令行(CMD 或 PowerShell)中直接输入python或pip命令,而无需切换到 Python 的安装目录。如果忘记勾选,后续手动配置环境变量会比较麻烦。 -
第二步:选择安装方式
- Install Now(立即安装):一键安装到默认路径(通常是
C:\Users\你的用户名\AppData\Local\Programs\Python\Python310)。适合大多数用户,简单快捷。 - Customize installation(自定义安装):允许你自定义安装路径和可选组件。建议高级用户或需要将 Python 安装到特定目录(如
D:\Python310)时使用。
- Install Now(立即安装):一键安装到默认路径(通常是
-
第三步:可选功能(Optional Features)
如果选择了“自定义安装”,会进入此页面。建议保持默认勾选:- pip:Python 的包管理工具,用于安装第三方库(如 requests, pandas),必须勾选。
- tcl/tk and IDLE:IDLE 是 Python 自带的简易集成开发环境,适合初学者做简单练习。
- Python test suite:Python 测试套件,一般用户可不勾选。
- py launcher:用于在命令行中启动不同版本的 Python,建议勾选。
点击 Next 继续。
-
第四步:高级选项(Advanced Options)
- Install for all users:为所有用户安装。如果你的电脑有多个用户账户且都需要使用 Python,可以勾选。
- Associate files with Python:将
.py文件关联到 Python,双击.py文件时用 Python 运行。 - Create shortcuts for installed applications:创建开始菜单快捷方式。
- Add Python to environment variables:如果第一步已勾选,这里会默认勾选并变灰。
- Precompile standard library:预编译标准库,可以加快 Python 启动速度。
- Download debugging symbols / Download debug binaries:下载调试符号和二进制文件,一般用户不需要。
- 自定义安装路径:在底部可以修改 Python 的安装目录,例如
C:\Python310。路径中不要包含中文或空格,以免某些库出现兼容性问题。
设置完成后,点击 Install 开始安装。
-
第五步:等待安装完成
安装过程会有进度条,通常需要 1-3 分钟。安装完成后,会出现 “Setup was successful” 的提示窗口,点击 Close 关闭即可。
3. 验证安装成功
安装完成后,我们需要验证 Python 和 pip 是否已正确安装并配置到环境变量。
- 按下键盘 Win + R 组合键,打开“运行”对话框。
- 输入
cmd并回车,打开命令提示符(CMD)。 - 在命令行中依次输入以下两条命令,并回车执行:
python --version pip --version - 观察输出结果:
- 如果
python --version输出了类似Python 3.10.12的版本号。 - 如果
pip --version输出了 pip 的版本号及路径信息。 - 并且两条命令都没有报错(如“不是内部或外部命令”),则说明 Python 环境已成功搭建!
- 如果
⚠️ 常见问题:
- 如果提示“python 不是内部或外部命令”,说明环境变量未正确配置。可以尝试重新运行安装程序,确保第一步勾选了 “Add Python.exe to PATH”,或者手动将 Python 安装目录(如
C:\Python310)和其下的Scripts目录(如C:\Python310\Scripts)添加到系统的 PATH 环境变量中。- 如果输入
python没反应但输入py可以,这是因为 Windows 上同时安装了多个 Python 版本,py启动器会帮你选择。对于学习来说,使用py命令也是可以的。。

安装完成后,我们可以编写第一个Python程序:
# 第一个Python程序
print("Hello World")
PyCharm创建项目步骤:
-
打开PyCharm → New Project → 设置项目路径与Python解释器


-
右键项目 → New → Python File → 输入文件名


-
编写代码后右键 → Run 即可运行


能跑出来,环境就 OK 了 ✅,很简单吧
二、基础语法入门
2.1 注释
注释是对代码的解释说明,不会被程序执行,用于提升代码可读性。
| 注释类型 | 语法 | 适用场景 |
|---|---|---|
| 单行注释 | # 注释内容 |
单行代码说明 |
| 多行注释 | """注释内容""" 或 '''注释内容''' |
大段功能说明、文档字符串 |
# 这是单行注释
print("Hello Python")
"""
这是多行注释
可以写很多行内容
一般用于函数、类的功能说明
"""

2.2 变量与数据类型
变量与数据类型是Python编程的基础单元:变量负责承载数据,数据类型决定了数据的存储形式与可执行的操作。作为典型的动态类型语言,Python无需提前声明变量类型,赋值即可完成变量定义。
2.2.1 变量的定义与本质
变量可以理解为内存中存储数据的“标签”:我们给数据起一个名字,后续通过这个名字就能读取或修改对应的数据。与静态语言不同,Python变量本身没有固定类型,同一个变量可以先后赋值不同类型的数据。
基础语法
Python中通过 = 进行变量赋值,格式为 变量名 = 变量值,赋值即完成变量定义。
# 定义单个变量
name = "张三" # str
age = 20 # int
height = 1.75 # float
is_student = True # bool
扩展赋值写法
Python支持更灵活的赋值方式,可大幅简化代码书写:
# 链式赋值:多个变量指向同一个值
a = b = c = 100
# 多变量同时赋值:按位置一一对应赋值
name, age, gender = "李四", 22, "男"
# 变量交换:无需中间变量,Python独有特性
x, y = 10, 20
x, y = y, x
print(x, y) # 输出 20 10
注意:变量必须先赋值再使用,直接访问未定义的变量会抛出
NameError报错。
2.2.2 变量命名规范
变量名属于标识符的一种,命名分为强制规则(违反则直接语法报错)和推荐规范(行业通用惯例,提升代码可读性)。
强制规则
- 只能由字母、数字、下划线三类字符组成
- 数字不能作为开头(例如
1name是非法命名) - 严格区分大小写(
Age和age是两个完全无关的变量) - 不能使用Python关键字(如
if、for、class等)
推荐规范
- 见名知意:变量名应直观体现数据含义(例如用
user_age而非无意义的a) - 遵循蛇形命名法:多个单词用下划线连接(例如
user_name、total_score) - 普通变量全小写,类名使用大驼峰命名法(后续面向对象章节讲解)
2.2.3 四大基础数据类型
Python内置了丰富的数据类型,入门阶段最核心的是4种基础类型,覆盖数字、文本、真假判断三大核心场景。
| 类型名称 | 类型关键字 | 常见取值示例 | 核心说明 |
|---|---|---|---|
| 整型 | int |
100、-20、0 |
所有整数,Python3中int无大小限制,支持超大整数 |
| 浮点型 | float |
3.14、-0.5、1.0 |
所有小数,采用双精度存储,存在微小精度误差 |
| 布尔型 | bool |
True、False |
表示“真/假”,是int的子类:True等价于1,False等价于0 |
| 字符串 | str |
"你好"、'Python'、'''多行文本''' |
引号包裹的文本内容,单引号、双引号、三引号均可 |
各类型补充细节
-
整型(int)
Python3取消了long类型,所有整数统一为int类型,同时支持多种进制写法:num1 = 100 # 十进制 num2 = 0b1010 # 二进制(0b开头),等价于十进制10 num3 = 0o12 # 八进制(0o开头),等价于十进制10 num4 = 0xA # 十六进制(0x开头),等价于十进制10 -
浮点型(float)
浮点数采用科学计数法存储,存在微小的精度误差,不建议直接用==比较两个浮点数是否相等:print(0.1 + 0.2) # 输出 0.30000000000000004,而非精确的0.3 -
布尔型(bool)
只有两个取值True和False,首字母必须大写。由于是int的子类,可直接参与数值运算:print(True + 1) # 输出 2 print(False * 10) # 输出 0 -
字符串(str)
- 单引号和双引号效果完全一致,可互相嵌套避免转义
- 三引号支持多行文本,常用于多行注释和长字符串定义
s1 = '单引号字符串' s2 = "双引号字符串,内部可以直接用'单引号'" s3 = '''三引号 支持多行 文本内容'''
查看数据类型
使用内置函数 type() 可以查看变量或值对应的数据类型:
age = 20
print(type(age)) # <class 'int'>
print(type("hello")) # <class 'str'>
print(type(True)) # <class 'bool'>
2.3 标识符与关键字
Python中所有自定义的名称(变量名、函数名、类名等)都属于标识符,而关键字是Python官方预留的、具备特殊含义的单词,二者共同构成了Python的命名规则体系。
2.3.1 标识符
标识符是程序员在代码中自定义的所有名称,包括变量名、函数名、类名、模块名等。
所有标识符都必须遵循统一的命名规则:
- 由字母、数字、下划线组成,数字不能开头
- 严格区分大小写
- 不能与关键字重名
简单理解:变量命名规则就是标识符规则的具体应用,所有自定义名称都要遵守该规则。
2.3.2 关键字
关键字(也叫保留字)是Python语言内置的、具备特殊功能的单词,例如用于条件判断的 if、循环的 for、定义函数的 def 等。
关键字不能被用作标识符(不能当变量名、函数名等),否则会触发语法错误。
查看所有关键字
通过标准库 keyword 可以查看当前Python版本的全部关键字:
import keyword
print(keyword.kwlist)
Python 3.10版本共有35个关键字,常见的有:False、True、None、if、else、for、while、def、class、import、return 等。
注意:关键字大部分为小写(
True/False/None除外),无需刻意背诵,后续语法学习中会逐一接触并掌握。
2.4 输入与输出
输入(Input)与输出(Output)是程序与用户交互的基础:print() 负责向控制台输出内容,input() 负责接收用户的键盘输入。
2.4.1 输出函数:print()
print() 是Python中最常用的输出函数,支持单内容、多内容输出,以及格式化输出等多种用法。
基础用法
# 输出单个内容
print("Hello Python")
# 同时输出多个内容,默认用空格分隔
name = "张三"
age = 20
print(name, age) # 输出 张三 20
核心参数
print() 有两个常用参数,可自定义输出格式:
sep:多个内容之间的分隔符,默认为空格end:输出结束后的追加字符,默认为换行符\n
# 自定义分隔符
print(2024, 6, 1, sep="-") # 输出 2024-6-1
# 取消默认换行
print("hello", end="")
print("world") # 输出 helloworld(两行print连在一起)
三种格式化输出方式
当需要把变量嵌入到字符串中输出时,有三种主流实现方式,日常开发优先推荐使用 f-string。
-
方式一:% 占位符(传统写法)
用特殊符号占位,末尾通过%传入对应变量,按顺序一一对应。name = "张三" age = 20 print("姓名:%s,年龄:%d岁" % (name, age)) # 输出:姓名:张三,年龄:20岁 -
方式二:str.format() 方法
用{}占位,通过format()方法传入变量,支持按索引/名称匹配,可复用变量。print("姓名:{},年龄:{}岁".format(name, age)) # 按索引复用变量 print("{0}今年{1}岁,明年{1}岁就长大了".format(name, age)) -
方式三:f-string(Python3.6+ 推荐)
在字符串前加f前缀,直接在{}内写变量名或表达式,语法最简洁直观,可读性最强。print(f"姓名:{name},年龄:{age}岁") # {}内支持直接写表达式 print(f"明年年龄:{age + 1}岁")

常用格式化符号
占位符方式中,不同符号对应不同数据类型,还可控制输出宽度与小数精度:
| 符号 | 对应类型 | 说明 |
|---|---|---|
%s |
字符串 | 通用占位符,可匹配所有类型 |
%d |
整数 | 十进制整数,%5d 表示占5个字符宽度,右对齐 |
%f |
浮点数 | %.2f 表示保留2位小数 |
pi = 3.1415926
print("圆周率保留2位小数:%.2f" % pi) # 输出 3.14
print("整数占5位:%5d" % 12) # 输出 " 12"(前面补3个空格)
2.4.2 输入函数:input()
input() 用于暂停程序,等待用户从键盘输入内容,用户按下回车后程序继续执行。
基础语法
# 括号内可写提示文字,输入的返回值用变量接收
password = input("请输入您的密码:")
print(f"您输入的密码是:{password}")
核心特性:返回值恒为字符串
无论用户输入什么内容,input() 的返回值永远是字符串类型(str)。
如果需要获取数字并进行运算,必须手动做类型转换,否则会触发类型错误。
# ❌ 错误写法:字符串不能直接和数字相加
age = input("请输入年龄:")
print(age + 1) # 报错:TypeError: can only concatenate str (not "int") to str
# ✅ 正确写法:先转为整数类型
age = int(input("请输入年龄:"))
print(f"明年你就{age + 1}岁了")
常见坑:如果用户输入的不是纯数字,
int()转换会抛出ValueError报错,该问题可通过后续的异常处理机制解决。
2.5 运算符
运算符是用于执行程序运算的符号,Python提供了丰富的运算符体系,入门阶段重点掌握算术、赋值、比较、逻辑四大类。
2.5.1 算术运算符
用于数值类的数学运算,是最基础的运算符类型。
| 运算符 | 功能描述 | 示例 | 运算结果 |
|---|---|---|---|
+ |
加法 | 10 + 3 |
13 |
- |
减法 | 10 - 3 |
7 |
* |
乘法 | 10 * 3 |
30 |
/ |
除法 | 10 / 3 |
3.3333333333333335 |
// |
整除(向下取整) | 10 // 3 |
3 |
% |
取余(取模) | 10 % 3 |
1 |
** |
幂运算 | 10 ** 3 |
1000 |
重点注意事项
-
除法
/的结果永远是 float 类型,哪怕两个整数刚好整除:print(10 / 2) # 输出 5.0,而非整数5 -
整除
//是向下取整,负数运算时极易踩坑:print(10 // 3) # 输出 3 print(-10 // 3) # 输出 -4(向下取整,不是-3) -
取余
%结果的正负与除数一致:print(-10 % 3) # 输出 2(除数3是正数,结果为正) print(10 % -3) # 输出 -2(除数-3是负数,结果为负) -
幂运算
**支持开方操作:print(16 ** 0.5) # 输出 4.0,等价于开平方
2.5.2 赋值运算符
用于给变量赋值,分为基础赋值和复合赋值两类。
基础赋值
# 单个变量赋值
a = 10
复合赋值运算符
将算术运算与赋值合并,简化代码书写,运算优先级与赋值一致:
a = 10
a += 5 # 等价于 a = a + 5,执行后a=15
a -= 3 # 等价于 a = a - 3,执行后a=12
a *= 2 # 等价于 a = a * 2,执行后a=24
a /= 4 # 等价于 a = a / 4,执行后a=6.0
a //= 2 # 等价于 a = a // 2
a %= 3 # 等价于 a = a % 3
a **= 2 # 等价于 a = a ** 2
注意:复合运算符中间不能有空格,例如
+ =是错误写法。
2.5.3 比较运算符
用于两个值的大小、相等判断,运算结果永远是布尔值(True 或 False)。
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
> |
大于 | 10 > 3 |
True |
>= |
大于等于 | 10 >= 10 |
True |
< |
小于 | 10 < 3 |
False |
<= |
小于等于 | 3 <= 3 |
True |
== |
等于(判断值是否相等) | 5 == 5 |
True |
!= |
不等于 | 5 != 3 |
True |
Python独有特性:链式比较
Python支持连续的比较表达式,代码更简洁,等价于逻辑与的关系:
age = 20
print(18 < age < 30) # 等价于 18 < age and age < 30,输出 True
易错点:
==是判断相等,=是赋值,二者功能完全不同,条件判断时切勿写反。
2.5.4 逻辑运算符
用于多个布尔值之间的逻辑运算,共有 and(与)、or(或)、not(非)三个。
| 运算符 | 运算规则 | 示例 | 结果 |
|---|---|---|---|
and |
两边都为True,结果才为True;有一个为False则为False | True and False |
False |
or |
两边有一个为True,结果就为True;全为False才为False | True or False |
True |
not |
取反,True变False,False变True | not True |
False |
短路求值特性
逻辑运算符具备短路特性:当左边的表达式已经能确定最终结果时,右边的代码不会执行。
and:左边为False时,直接返回False,右边不执行or:左边为True时,直接返回True,右边不执行
# and 短路示例:左边0等价于False,右边不执行
a = 0
b = 10
result = a and (b := 20) # 海象运算符赋值,Python3.8+支持
print(b) # 输出 10,右边代码未执行
# or 短路示例:左边10等价于True,右边不执行
result = 10 or (b := 30)
print(b) # 输出 10,右边代码未执行
优先级
逻辑运算符的优先级:not > and > or,实际开发中建议通过括号明确运算顺序,提升代码可读性。
2.6 数据类型转换
不同数据类型支持的操作不同,实际开发中经常需要在类型之间进行转换。Python中的类型转换分为隐式转换和显式转换两类。
2.6.1 隐式类型转换
程序自动完成的类型转换,无需人工干预,通常发生在不同类型的数值运算中。
- 低精度类型会自动向高精度类型转换:
int→float - 布尔值会自动转为整数参与运算:
True→1,False→0
# int + float,自动转为float
num = 10 + 3.14
print(type(num)) # <class 'float'>
# bool + int,自动转为int
result = True + 5
print(result) # 输出 6
2.6.2 显式类型转换
通过内置函数手动强制转换类型,也叫强制类型转换,是开发中最常用的方式。
| 转换函数 | 功能描述 |
|---|---|
int(x) |
将x转换为整数类型 |
float(x) |
将x转换为浮点数类型 |
str(x) |
将x转换为字符串类型 |
bool(x) |
将x转换为布尔值类型 |
1. int() 转整数
支持将浮点数、数字格式的字符串转为整数:
- 浮点数转整数:直接舍弃小数部分,不是四舍五入
- 字符串转整数:内容必须是纯整数格式,否则报错
print(int(3.99)) # 输出 3(直接舍弃小数)
print(int("123")) # 输出 123
print(int("-456")) # 输出 -456
# print(int("3.14")) # ❌ 报错,含小数的字符串不能直接转int
2. float() 转浮点数
支持将整数、数字格式的字符串转为浮点数:
print(float(10)) # 输出 10.0
print(float("3.14")) # 输出 3.14
print(float("100")) # 输出 100.0
3. str() 转字符串
万能转换,可将任意类型转为字符串格式,常用于字符串拼接场景:
print(str(100)) # 输出 "100"
print(str(3.14)) # 输出 "3.14"
print(str(True)) # 输出 "True"
4. bool() 转布尔值
布尔转换遵循“空/零为假,非空/非零为真”的规则,以下值转换后为 False:
- 数值0:
0、0.0 - 空值:
None - 空序列:
""(空字符串)、[](空列表)、()(空元组)、{}(空字典) - 其余所有值转换后均为
True
print(bool(0)) # False
print(bool("")) # False
print(bool(None)) # False
print(bool(100)) # True
print(bool("hello")) # True
print(bool(" ")) # True(空格也是有效字符,不是空字符串)
三、流程控制语句
程序默认按照从上到下的顺序逐行执行,这种执行方式称为顺序结构。但仅靠顺序结构无法实现复杂的业务逻辑,因此Python提供了流程控制语句,用于改变代码的执行路径,主要分为分支结构和循环结构两类,它们是所有复杂程序的基础构建单元。
3.1 分支结构:if语句
分支结构的核心是“条件判断”:根据表达式的真假结果,选择执行不同的代码分支,从而实现逻辑选择。Python通过 if、elif、else 三个关键字实现分支逻辑。
前置核心规则:缩进语法
Python没有使用其他语言常见的大括号{}来划分代码块,而是通过代码缩进来区分代码的层级关系。同一代码块的语句必须保持相同的缩进量,行业通用标准为4个空格。
缩进是Python语法的一部分,缩进错误会直接导致程序运行报错,这也是初学者最容易踩的坑。
3.1.1 单分支结构(if)
单分支是最简单的分支结构:只有一个判断条件,条件成立则执行对应代码块,条件不成立则直接跳过该段代码。
语法格式
if 条件表达式:
条件成立时执行的代码块
执行逻辑
程序执行到 if 时,先判断条件表达式的布尔结果:
- 结果为
True:执行缩进内的代码块,执行完成后继续执行后续代码 - 结果为
False:跳过缩进内的所有代码,直接执行后续代码
代码示例
age = 18
# 判断是否成年
if age >= 18:
print("已成年,可以独立访问")
print("欢迎进入系统")
print("程序结束") # 无论条件是否成立,这行都会执行
注意:条件表达式支持隐式布尔转换,数值0、空字符串、空列表、None等会被判定为False,其余值均视为True。
3.1.2 双分支结构(if…else)
双分支对应“二选一”的场景:条件成立执行if代码块,条件不成立则执行else代码块,两个分支必然有且只有一个会执行。
语法格式
if 条件表达式:
条件成立时执行的代码块
else:
条件不成立时执行的代码块
代码示例
age = 16
if age >= 18:
print("已成年")
else:
print("未成年,需监护人陪同")
注意:
else不能单独使用,必须和if配对出现;一个if最多对应一个else。
3.1.3 多分支结构(if…elif…else)
当存在多个条件、需要进行多级判断时,使用多分支结构。程序会从上到下依次判断每个条件,第一个满足条件的分支会被执行,执行后直接结束整个if结构,后续分支不再判断。
语法格式
if 条件表达式1:
代码块1
elif 条件表达式2:
代码块2
elif 条件表达式3:
代码块3
...
else:
所有条件都不满足时执行的代码块
代码示例:成绩等级判定
score = 85
if score >= 90:
print("优秀")
elif score >= 80:
print("良好")
elif score >= 60:
print("及格")
else:
print("不及格")
核心注意事项
- 分支互斥性:多个分支中最多只有一个会被执行,判断顺序严格从上到下。
- 条件顺序很重要:范围更精确、更小的条件要放在前面,否则会出现逻辑覆盖错误。例如如果先写
score >= 60,再写score >= 90,那么90分也会匹配到第一个分支,出现逻辑错误。 elif可以有任意多个,else最多一个且必须放在所有分支的最后,也可以根据场景省略else。
Python独有特性:支持链式比较写法,让条件表达式更简洁自然。
# 等价于 score >= 0 and score <= 100 if 0 <= score <= 100: print("分数合法")
3.1.4 三元表达式
三元表达式是双分支 if...else 的简化写法,专门用于简单的二选一赋值场景,可以用一行代码完成变量赋值,代码更精简。
语法格式
变量 = 条件成立时的值 if 条件表达式 else 条件不成立时的值
代码示例
a, b = 10, 20
# 求两个数的最大值
max_num = a if a > b else b
print(max_num) # 输出 20
# 判断数字奇偶
num = 7
result = "奇数" if num % 2 != 0 else "偶数"
print(result) # 输出 奇数
使用建议
- 仅适合简单的二选一逻辑,复杂多层分支不建议使用,会大幅降低代码可读性。
- 三元表达式是表达式,有明确的返回值,必须接收结果或参与运算,不能单独作为语句使用。
补充:pass占位符
如果某个分支暂时不需要编写逻辑,可以用pass关键字占位,避免语法报错。pass不执行任何操作,仅作为语法占位符。if age < 18: pass # 后续补充未成年处理逻辑 else: print("成年处理")
3.2 循环结构
循环结构用于让一段代码重复执行多次,避免重复编写冗余代码。Python提供了两种循环语句:while 循环和 for 循环,二者适用场景不同。
3.2.1 while循环
while 循环通过一个条件表达式控制循环是否继续,适合循环次数不固定、依赖条件判断的场景。
语法格式
循环变量初始化
while 循环条件:
循环体代码
循环变量更新
执行流程
- 先执行循环变量的初始化(设置循环的起始值)
- 判断循环条件的布尔结果:
- 为
True:执行循环体代码,执行完后更新循环变量,再次回到条件判断 - 为
False:结束循环,执行后续代码
- 为
代码示例:打印1~5的数字
i = 1 # 1. 初始化循环变量
while i <= 5: # 2. 循环条件判断
print(i) # 3. 循环体
i += 1 # 4. 更新循环变量(计数器+1)
死循环与应用
如果循环条件永远为True,循环会无限执行,称为死循环。死循环并非都是错误,在持续运行的服务程序、用户交互场景中非常常用,通常配合 break 语句在合适时机退出。
# 用户密码验证,输对为止
while True:
pwd = input("请输入密码:")
if pwd == "123456":
print("密码正确,登录成功")
break # 终止循环
print("密码错误,请重试")
易错提醒:如果忘记更新循环变量,会导致条件永远成立,形成意外死循环,这是初学者编写while循环的最常见错误。
3.2.2 for循环
for 循环主要用于遍历可迭代对象(字符串、列表、元组、range序列等),依次取出其中的每个元素进行处理,适合循环次数固定、已知遍历目标的场景。
语法格式
for 临时变量 in 可迭代对象:
循环体代码
执行流程
依次取出可迭代对象中的每一个元素,赋值给临时变量,每取一个元素就执行一次循环体,直到所有元素遍历完成,循环自动结束。
基础示例:遍历字符串
# 逐个打印字符串中的每个字符
for char in "Python":
print(char)
range() 函数详解
range() 是Python内置的整数序列生成函数,是for循环最常用的搭档,用于生成指定范围的整数序列。
它有三种传参方式:
-
一个参数
range(end):生成0 ~ end-1的整数,步长默认为1for i in range(5): print(i) # 输出 0,1,2,3,4 -
两个参数
range(start, end):生成start ~ end-1的整数for i in range(1, 6): print(i) # 输出 1,2,3,4,5 -
三个参数
range(start, end, step):按指定步长生成整数序列- 步长为正数:从左到右递增生成
- 步长为负数:从右到左递减生成(倒序)
# 打印1-10的偶数 for i in range(2, 11, 2): print(i) # 输出 2,4,6,8,10 # 倒序打印10-1 for i in range(10, 0, -1): print(i) # 输出 10,9,8,...,1
核心特性:range生成的序列遵循**包左不包右(左闭右开)**规则,结束值永远不会被包含在序列中。
3.2.3 循环控制语句
在循环执行过程中,我们可以通过两个关键字手动干预循环的执行流程:break 和 continue。
1. break:终止整个循环
break 的作用是立即终止当前所在的整个循环,循环中剩余的代码和后续所有迭代都不再执行,直接跳出循环体。
# 遍历字符串,遇到字符 'l' 就停止循环
for s in "hello":
if s == "l":
break
print(s)
# 输出结果:h e
2. continue:跳过本次循环
continue 的作用是结束当前这一轮循环,跳过本次循环中continue之后的所有代码,直接进入下一轮循环的条件判断。
# 打印1-5,跳过数字3
for i in range(1, 6):
if i == 3:
continue
print(i)
# 输出结果:1 2 4 5
二者核心区别
break:彻底终止循环,循环完全结束continue:仅跳过当前一轮,循环还会继续执行后续迭代- 共同特性:都只作用于当前层级的循环,在循环嵌套中不会直接终止外层循环
3.2.4 循环嵌套
在一个循环的循环体内部,再编写另一个循环,称为循环嵌套。外层循环执行1次,内层循环会完整执行一整轮。最常见的是for循环嵌套for循环,常用于处理二维数据、行列排版等场景。
执行逻辑
- 外层循环:控制“行”,决定一共执行多少轮
- 内层循环:控制“列”,决定每一行内执行多少次
经典案例:打印九九乘法表
# 外层循环:控制行数,一共9行,i 代表当前行数
for i in range(1, 10):
# 内层循环:控制每行的式子数量,第i行有i个式子
for j in range(1, i + 1):
# 格式化输出式子,用制表符分隔,不换行
print(f"{j} * {i} = {i*j}", end="\t")
# 每一行结束后换行
print()
运行效果:
1 * 1 = 1
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
...
注意:循环嵌套中,内层的break/continue只能控制内层循环,无法直接终止外层循环。如果需要跳出多层循环,可借助标记变量、异常捕获等方式实现。
拓展:循环的else子句(Python特性)
Python支持 for...else 和 while...else 语法,这是很多编程语言没有的特性:
- 如果循环正常执行结束(没有被break强制中断),则执行else代码块
- 如果循环被
break提前终止,则else代码块不会执行
这个语法非常适合“遍历查找元素,没找到时给出提示”的场景。
# 在1-5中查找数字7
target = 7
for i in range(1, 6):
if i == target:
print("找到了目标数字")
break
else:
print("遍历结束,未找到目标数字")
# 输出:遍历结束,未找到目标数字
四、数据容器详解
在实际开发中,我们经常需要批量存储和处理一组数据,单靠基础变量无法高效完成这类需求。Python内置了5种核心数据容器,它们各自具备不同的特性和适用场景,是Python数据处理的核心基础。
按存储结构可分为三类:
- 序列型容器:字符串(str)、列表(list)、元组(tuple),元素有序排列,支持通过索引访问
- 映射型容器:字典(dict),以键值对形式存储,通过键快速查找对应的值
- 集合型容器:集合(set),元素无序且唯一,用于去重和集合运算
按可变性可分为两类:
- 可变类型:列表、字典、集合,定义后可随时增删改元素
- 不可变类型:字符串、元组,定义后无法修改内部元素,所有操作都会生成新对象
4.1 字符串(str)
字符串是由零个或多个字符组成的有序字符序列,属于不可变类型——所有对字符串的修改操作,都不会改变原字符串,而是生成一个全新的字符串。字符串是Python中最常用的文本处理容器。
4.1.1 索引取值
字符串是有序序列,每个字符都有对应的位置编号,称为索引(下标)。Python支持两种索引方式:
- 正向索引:从左往右,从0开始依次递增,第一个字符索引为0
- 反向索引:从右往左,从-1开始依次递减,最后一个字符索引为-1
语法:字符串[索引]
s = "abcdefg"
# 正向索引
print(s[0]) # 'a' 第1个字符
print(s[2]) # 'c' 第3个字符
# 反向索引
print(s[-1]) # 'g' 最后1个字符
print(s[-3]) # 'e' 倒数第3个字符
易错提醒:索引不能超出字符串的范围,否则会抛出
IndexError报错。例如长度为7的字符串,最大正向索引是6,最小反向索引是-7。
4.1.2 切片操作
切片用于一次性取出字符串中的一段子序列,遵循「包左不包右」的规则,即包含起始索引的元素,不包含结束索引的元素。
完整语法:字符串[起始索引:结束索引:步长]
三个参数都支持省略,不同省略规则对应不同效果:
- 完整写法:
s[1:5:2],从索引1开始到索引4结束,每2个字符取1个 - 省略步长:
s[1:5],步长默认为1,连续取子串 - 省略起始:
s[:5],默认从开头(索引0)开始截取 - 省略结束:
s[2:],默认截取到字符串末尾 - 全省略:
s[:],完整复制整个字符串 - 负步长:步长为负数时表示从右往左截取,可实现字符串反转
s = "abcdefg"
# 基础切片:取索引1到3的字符(包含1,不包含4)
print(s[1:4]) # 'bcd'
# 省略起始:从头截取到索引2
print(s[:3]) # 'abc'
# 省略结束:从索引2截取到末尾
print(s[2:]) # 'cdefg'
# 带步长:每隔1个字符取1个
print(s[::2]) # 'aceg'
# 负步长反转字符串
print(s[::-1]) # 'gfedcba'
注意:切片操作索引越界不会报错,会自动截取到边界,这是和索引取值的重要区别。
4.1.3 常用内置方法
由于字符串是不可变类型,所有方法都会返回新字符串,原字符串不会被修改。以下是开发中最高频的方法:
| 方法 | 功能说明 | 示例 |
|---|---|---|
find(sub) |
查找子串首次出现的起始索引,找不到返回-1 | "hello".find("l") → 2 |
replace(old, new) |
将所有旧子串替换为新子串 | "hello".replace("l","x") → "hexxo" |
split(sep) |
按指定分隔符切割字符串,返回列表 | "a,b,c".split(",") → ["a","b","c"] |
strip() |
去除字符串首尾的空白字符(空格、换行、制表符) | " hi ".strip() → "hi" |
upper() / lower() |
全部转为大写/小写 | "Hello".upper() → "HELLO" |
startswith(prefix) |
判断是否以指定字符串开头,返回布尔值 | "test.py".startswith("test") → True |
endswith(suffix) |
判断是否以指定字符串结尾,返回布尔值 | "test.py".endswith(".py") → True |
count(sub) |
统计子串出现的次数 | "hello".count("l") → 2 |
此外,使用Python内置函数 len(字符串) 可以获取字符串的总长度:
s = "Python"
print(len(s)) # 6
4.2 列表(list)
列表是Python中最灵活、最常用的有序容器,属于可变类型——可以随时对内部元素进行增、删、改操作。列表支持存放任意类型的数据,元素可重复,是日常开发中使用频率最高的数据容器。
4.2.1 列表的定义
列表使用方括号 [] 包裹,元素之间用逗号分隔。
# 定义空列表
list1 = []
# 定义带初始元素的列表,支持混合类型
list2 = [10, 3.14, "hello", True, [1,2,3]]
列表同样支持索引和切片操作,语法规则与字符串完全一致,区别在于列表的切片结果仍是列表。
nums = [10, 20, 30, 40, 50]
print(nums[2]) # 30
print(nums[1:4]) # [20, 30, 40]
4.2.2 列表的增删改查
1. 新增元素
| 方法 | 功能说明 | 示例 |
|---|---|---|
append(元素) |
在列表末尾追加单个元素 | nums.append(60) → 末尾新增60 |
insert(索引, 元素) |
在指定索引位置插入元素,原位置及后续元素后移 | nums.insert(1, 15) → 索引1处插入15 |
extend(可迭代对象) |
批量追加,将另一个容器的元素逐个添加到列表末尾 | nums.extend([70,80]) → 末尾新增70、80两个元素 |
易混区分:
append是追加一整个对象,extend是拆分后逐个追加。例如nums.append([70,80])会把整个列表当作一个元素添加,最终列表长度只+1。
2. 删除元素
| 方式 | 功能说明 | 示例 |
|---|---|---|
del 列表[索引] |
根据索引删除指定位置的元素 | del nums[2] → 删除索引2的元素 |
remove(元素值) |
根据值删除第一个匹配的元素,元素不存在会报错 | nums.remove(30) → 删除值为30的元素 |
pop(索引) |
删除并返回指定索引的元素,默认删除最后一个元素 | nums.pop() → 删除并返回最后一个元素 |
clear() |
清空列表所有元素,变成空列表 | nums.clear() → 列表变为 [] |
3. 修改元素
通过索引直接赋值即可修改对应位置的元素:
nums = [10, 20, 30]
nums[0] = 100
print(nums) # [100, 20, 30]
4. 查询元素
| 方法/函数 | 功能说明 |
|---|---|
列表.index(元素) |
查找元素首次出现的索引,元素不存在会报错 |
列表.count(元素) |
统计指定元素在列表中出现的次数 |
len(列表) |
获取列表的元素总个数 |
nums = [10, 20, 30, 20]
print(nums.index(20)) # 1
print(nums.count(20)) # 2
print(len(nums)) # 4
4.2.3 列表的遍历
遍历是列表最常用的操作,即依次取出列表中的每个元素进行处理。
方式一:直接遍历元素(最常用)
fruits = ["苹果", "香蕉", "橙子"]
for fruit in fruits:
print(fruit)
方式二:带索引遍历(enumerate)
如果同时需要索引和元素值,使用 enumerate() 函数更简洁:
for index, fruit in enumerate(fruits):
print(f"第{index}个元素:{fruit}")
4.3 元组(tuple)
元组的结构与列表非常相似,都是有序序列,核心区别在于:元组是不可变类型,一旦定义完成,就无法对内部元素进行增、删、改操作。因此元组适合存储不希望被修改的数据,安全性更高。
4.3.1 元组的定义
元组使用圆括号 () 包裹,元素之间用逗号分隔。
# 空元组
t1 = ()
# 多个元素的元组
t2 = (10, 20, 30, "hello")
# 单个元素的元组:必须在元素后加逗号,否则括号会被当作运算符
t3 = (10,) # 正确,类型为tuple
t4 = (10) # 错误,类型为int
高频易错点:单个元素的元组必须加逗号。这是Python的语法规则,没有逗号的括号会被识别为普通的优先级括号,而非元组。
4.3.2 元组支持的操作
由于不可变特性,元组没有增删改相关的方法,仅支持查询类操作:
- 索引取值、切片操作,规则与列表完全一致
count()统计元素出现次数index()查找元素索引len()获取元组长度- for循环遍历
t = (10, 20, 30, 20)
print(t[1]) # 20
print(t[1:3]) # (20, 30)
print(t.count(20)) # 2
补充说明:元组的“不可变”指的是元素的引用不可变。如果元组中嵌套了可变类型(如列表),嵌套列表的内部元素是可以修改的,只是元组本身存储的引用地址不变。
4.4 字典(dict)
字典是典型的键值对(key-value)映射型容器,核心优势是查找速度极快,通过键可以直接定位到对应的值,无需像列表一样逐个遍历。字典属于可变类型,Python 3.7及以上版本中字典默认保持插入顺序。
4.4.1 字典的定义
字典使用大括号 {} 包裹,每个元素由 键: 值 组成,键值对之间用逗号分隔。
- 键(key):必须是不可变类型(字符串、数字、元组),且在字典中唯一,重复定义会覆盖原有值
- 值(value):可以是任意类型,可重复
# 空字典
dict1 = {}
# 标准字典定义
student = {
"name": "张三",
"age": 20,
"gender": "男",
"scores": [90, 85, 95]
}
4.4.2 字典的增删改查
1. 新增与修改
字典的增、改使用同一种语法:通过键进行赋值。
- 键不存在:新增一个键值对
- 键已存在:覆盖修改原有值
student = {"name": "张三", "age": 20}
# 修改:键已存在
student["age"] = 21
# 新增:键不存在
student["tel"] = "13800138000"
批量更新可使用 update() 方法,一次性合并另一个字典的所有键值对:
student.update({"address": "北京", "age": 22})
2. 查询元素
有两种常用的取值方式,核心区别在于键不存在时的表现:
字典[key]:键不存在会直接抛出KeyError报错字典.get(key):键不存在返回None,也可自定义默认值,更安全
print(student["name"]) # 张三
print(student.get("age")) # 20
print(student.get("email")) # None(键不存在,返回None)
print(student.get("email", "无")) # 自定义默认值,返回"无"
3. 删除元素
| 方式 | 功能说明 |
|---|---|
del 字典[key] |
删除指定键对应的键值对,键不存在报错 |
字典.pop(key) |
删除并返回指定键对应的值,键不存在报错 |
字典.clear() |
清空字典所有键值对 |
del student["gender"]
age = student.pop("age")
4.4.3 字典的遍历
字典有三种常用的遍历方式,分别对应遍历键、遍历值、遍历键值对。
1. 遍历所有键
使用 keys() 方法获取所有键的视图:
for key in student.keys():
print(key)
2. 遍历所有值
使用 values() 方法获取所有值的视图:
for value in student.values():
print(value)
3. 遍历键值对(最常用)
使用 items() 方法获取所有键值对,配合解包语法同时拿到键和值:
for key, value in student.items():
print(f"{key}: {value}")
4.5 集合(set)
集合是一个无序、元素唯一的容器,核心特性是自动去重。集合属于可变类型,底层基于哈希表实现,查找速度极快,主要用于数据去重和集合数学运算。
4.5.1 集合的定义
集合使用大括号 {} 包裹,元素之间用逗号分隔。
# 定义集合,自动去重
s1 = {10, 20, 10, 30, 20}
print(s1) # {10, 20, 30} 重复元素自动保留一个
易错提醒:空集合不能用
{}定义,因为{}默认是空字典。定义空集合必须使用set():empty_set = set() # 正确的空集合 empty_dict = {} # 这是空字典
集合最常用的场景就是列表快速去重:
list1 = [1, 2, 2, 3, 3, 3]
# 先转集合去重,再转回列表
list1 = list(set(list1))
print(list1) # [1, 2, 3]
注意:转集合去重后,原列表的顺序会丢失,因为集合是无序的。
4.5.2 集合的常用操作
1. 增删元素
| 方法 | 功能说明 |
|---|---|
add(元素) |
添加单个元素 |
update(可迭代对象) |
批量添加多个元素 |
remove(元素) |
删除指定元素,元素不存在会报错 |
discard(元素) |
删除指定元素,元素不存在也不会报错 |
clear() |
清空集合 |
s = {10, 20, 30}
s.add(40)
s.remove(20)
2. 集合数学运算
集合支持数学中的交集、并集、差集、对称差集运算,这是集合的核心功能之一。
| 运算符 | 运算 | 说明 |
|---|---|---|
& |
交集 | 两个集合都有的元素 |
| ` | ` | 并集 |
- |
差集 | 属于第一个集合,但不属于第二个集合的元素 |
^ |
对称差集 | 两个集合中各自独有的元素(去掉公共部分) |
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
print(set1 & set2) # {3, 4} 交集
print(set1 | set2) # {1,2,3,4,5,6} 并集
print(set1 - set2) # {1, 2} 差集
print(set1 ^ set2) # {1,2,5,6} 对称差集
五大数据容器特性对比
为了便于记忆和选型,这里将五种容器的核心特性整理为对比表格:
| 容器类型 | 关键字 | 有序性 | 可变性 | 元素唯一性 | 核心适用场景 |
|---|---|---|---|---|---|
| 字符串 | str |
有序 | 不可变 | 元素可重复 | 文本处理、字符序列操作 |
| 列表 | list |
有序 | 可变 | 元素可重复 | 通用数据存储、顺序遍历、动态增删 |
| 元组 | tuple |
有序 | 不可变 | 元素可重复 | 存储不可修改的数据、函数多返回值、字典的键 |
| 字典 | dict |
3.7+插入有序 | 可变 | 键唯一,值可重复 | 键值对映射、快速查找、结构化数据存储 |
| 集合 | set |
无序 | 可变 | 元素唯一 | 数据去重、集合数学运算、成员关系判断 |
需要我补充列表推导式、字典推导式这类进阶语法,或者对应的课后练习题吗?
五、推导式(Comprehension)
推导式(也叫解析式)是 Python 极具代表性的语法特性,能够以极简的一行代码,从可迭代对象中快速生成列表、字典、集合等容器。相较于传统 for 循环搭配追加操作的写法,推导式语法更紧凑、可读性更强,且底层经过C级优化,执行效率显著高于普通Python循环。
Python 共支持三种原生推导式:列表推导式、字典推导式、集合推导式。
注意:Python 不存在「元组推导式」,使用圆括号的同类语法是生成器表达式,二者本质与适用场景完全不同,文末会详细说明。
5.1 列表推导式(List Comprehension)
列表推导式是日常开发中使用频率最高的推导式,用于快速生成新的列表,返回结果为 list 类型。
5.1.1 基础语法
最简形式由「表达式 + 循环」构成,语法格式:
[表达式 for 变量 in 可迭代对象]
执行逻辑:遍历可迭代对象的每一个元素,将元素赋值给临时变量,经表达式计算后,把结果依次存入新列表。
示例对比:生成 0~9 的整数列表
- 传统 for 循环写法
list1 = [] for i in range(10): list1.append(i) - 列表推导式写法
list1 = [i for i in range(10)] # 结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
表达式支持对元素做任意运算处理:
# 生成 1~5 每个数字的平方
square_list = [i ** 2 for i in range(1, 6)]
# 结果:[1, 4, 9, 16, 25]
5.1.2 带条件筛选(if 后置)
在循环后追加 if 条件,只有满足条件的元素才会被处理并加入新列表,相当于循环内部的条件判断。
语法格式:
[表达式 for 变量 in 可迭代对象 if 条件表达式]
示例1:生成 0~9 中的所有偶数
even_list = [i for i in range(10) if i % 2 == 0]
# 结果:[0, 2, 4, 6, 8]
示例2:筛选并处理字符串列表
筛选长度大于3的单词,统一转为大写:
words = ["python", "java", "go", "c++", "javascript"]
result = [word.upper() for word in words if len(word) > 3]
# 结果:['PYTHON', 'JAVA', 'JAVASCRIPT']
5.1.3 带三元表达式(if-else 前置)
如果需要对每个元素都做「二选一」的转换,而非筛选,需要将 if-else 三元表达式放在 for 前面。
核心区别:后置 if 是筛选元素,不满足的直接丢弃;前置 if-else 是转换元素,每个元素都会保留,只是值不同。
示例:偶数保留原值,奇数变为0
list4 = [x if x % 2 == 0 else 0 for x in range(1, 11)]
# 结果:[0, 2, 0, 4, 0, 6, 0, 8, 0, 10]
5.1.4 多层循环嵌套
列表推导式支持多层 for 循环,对应代码中的嵌套结构,外层循环写在前,内层循环写在后。
语法格式:
[表达式 for 外层变量 in 外层序列 for 内层变量 in 内层序列]
示例1:生成两组数的全排列元组
pairs = [(i, j) for i in range(1, 3) for j in range(3)]
# 结果:[(1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
等价于传统嵌套循环:
pairs = []
for i in range(1, 3):
for j in range(3):
pairs.append((i, j))
示例2:带条件嵌套 + 二维列表扁平化
# 筛选 i 不等于 j 的数字组合
pairs2 = [(i, j) for i in range(1, 4) for j in range(1, 4) if i != j]
# 将二维嵌套列表展开为一维列表
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in matrix for num in row]
# 结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
使用建议:列表推导式适合简单逻辑,嵌套建议不超过2层。如果逻辑复杂、条件过多,建议改用普通循环并补充注释,避免可读性下降。
5.1.5 性能与适用场景
列表推导式底层由C语言实现,相较于Python层级的for循环+append,执行速度通常快2~3倍,在数据量较大时优势更明显。
适用场景:数据转换、元素筛选、简单嵌套生成列表。
5.2 字典推导式(Dict Comprehension)
字典推导式用于快速生成字典,语法框架与列表推导式一致,区别在于使用大括号包裹,且元素为 键表达式: 值表达式 格式,返回 dict 类型。
5.2.1 基础语法
{键表达式: 值表达式 for 变量 in 可迭代对象 if 条件}
示例:生成数字-平方映射字典
square_dict = {i: i ** 2 for i in range(1, 6)}
# 结果:{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
5.2.2 典型使用场景
- 字典键值反转
将原有字典的键和值互换,前提是原字典的值为可哈希类型且不重复。
original = {"apple": 1, "banana": 2, "orange": 3}
swapped = {value: key for key, value in original.items()}
# 结果:{1: 'apple', 2: 'banana', 3: 'orange'}
⚠️ 注意:如果值不唯一,后生成的键值对会覆盖之前的内容,造成数据丢失。
- 双列表合并为字典
两个列表分别存储键和值,合并为一个字典:
keys = ["name", "age", "city"]
values = ["张三", 20, "北京"]
# 索引遍历方式
student = {keys[i]: values[i] for i in range(len(keys))}
# 更推荐 zip 配对写法,更简洁高效
student = {k: v for k, v in zip(keys, values)}
# 最简写法:直接 dict(zip(keys, values))
- 条件过滤字典
按条件筛选保留符合要求的键值对:
scores = {"张三": 90, "李四": 75, "王五": 88, "赵六": 60}
good_scores = {k: v for k, v in scores.items() if v >= 80}
# 结果:{'张三': 90, '王五': 88}
5.3 集合推导式(Set Comprehension)
集合推导式同样使用大括号包裹,元素为单一表达式(无冒号),返回 set 类型,天然具备无序、元素自动去重的特性。
基础语法
{表达式 for 变量 in 可迭代对象 if 条件}
示例1:元素求平方并自动去重
num_list = [1, 1, 2, 2, 3, 3]
square_set = {i ** 2 for i in num_list}
# 结果:{1, 4, 9}
示例2:提取字符串中的唯一字符
text = "abracadabra"
unique_chars = {char for char in text}
# 结果:{'a', 'b', 'r', 'c', 'd'} 顺序不固定
集合推导式同样支持条件筛选、多层循环,用法与列表推导式完全一致,核心区别在于返回结果自动去重、无序。
5.4 拓展:生成器表达式(易混淆点)
很多初学者会误以为圆括号写法是「元组推导式」,实际上 Python 并不存在元组推导式,圆括号对应的是生成器表达式(Generator Expression)。
gen = (i ** 2 for i in range(5))
print(type(gen)) # <class 'generator'>
核心特性
生成器不会一次性生成所有元素存入内存,而是在迭代时逐个计算生成元素,属于「惰性计算」,内存占用极低,非常适合处理大数据量、超大规模序列的场景。
如果需要得到元组,可通过 tuple() 转换:
t = tuple(i for i in range(10))
5.5 总结对比
三类推导式核心区别
| 推导式类型 | 语法符号 | 返回类型 | 核心特性 | 典型适用场景 |
|---|---|---|---|---|
| 列表推导式 | [] |
list |
有序、元素可重复 | 数据转换、元素筛选、生成列表 |
| 字典推导式 | {key:value} |
dict |
键唯一、键值映射 | 字典生成、键值反转、字典过滤 |
| 集合推导式 | {} |
set |
无序、元素自动去重 | 数据去重、集合数据预处理 |
使用原则
- 简单逻辑优先用推导式,代码更简洁高效;
- 复杂逻辑、多层嵌套优先用普通循环,保证可读性;
- 大数据量场景优先考虑生成器表达式,节省内存。
六、函数基础
函数是 Python 实现代码复用、模块化编程的核心机制。它将一段具备独立功能的逻辑封装为一个可通过名称调用的代码块,实现「一次定义、多次调用」,能够显著减少代码冗余、提升可读性与可维护性。
本章将从定义调用、参数体系、返回值规则、变量作用域四个维度,系统讲解 Python 函数的基础用法、最佳实践与常见陷阱。
6.1 函数的定义与调用
6.1.1 基本语法
Python 使用 def 关键字定义函数,完整结构如下:
def 函数名(参数列表):
"""文档字符串(docstring):描述函数功能、参数与返回值"""
函数体代码
return 返回值
各组成部分说明:
def:定义函数的固定关键字,不可省略- 函数名:遵循标识符命名规则,推荐蛇形命名法(如
get_user_info),做到见名知意 - 参数列表:函数的输入入口,可以是 0 个或多个,多个参数用逗号分隔;也叫「形参」
- 文档字符串:可选但强烈推荐,用于说明函数用途,可通过
help(函数名)查看 - 函数体:实现具体功能的代码块,必须保持缩进
return:可选,用于将结果返回给调用方;函数执行到return会立即终止
6.1.2 完整示例
def get_sum(a, b):
"""计算两个整数的和并返回结果
:param a: 第一个参与运算的整数
:param b: 第二个参与运算的整数
:return: 两个整数相加的结果
"""
return a + b
# 调用函数:传入实参,用变量接收返回值
result = get_sum(10, 20)
print(result) # 输出:30
# 查看函数文档
help(get_sum)
注意:函数必须先定义、后调用,顺序颠倒会触发
NameError报错。
6.2 函数的四种常见形式
根据「是否有参数」和「是否有返回值」,函数可分为四类,分别适配不同业务场景:
| 函数类型 | 有无参数 | 有无返回值 | 典型适用场景 |
|---|---|---|---|
| 无参无返回值 | ❌ | ❌ | 打印固定菜单、输出分隔线、纯执行类操作 |
| 有参无返回值 | ✅ | ❌ | 数据打印、信息展示、处理后直接输出无需复用 |
| 无参有返回值 | ❌ | ✅ | 获取系统配置、读取状态、生成随机数 |
| 有参有返回值 | ✅ | ✅ | 最常用,数值计算、数据处理、逻辑判断等绝大多数场景 |
代码示例
# 1. 无参无返回值
def print_separator():
print("-" * 30)
# 2. 有参无返回值
def greet(name):
print(f"你好,{name}!")
# 3. 无参有返回值
def get_pi():
return 3.1415926
# 4. 有参有返回值
def add(a, b):
return a + b
提示:即使不写
return,函数执行完毕后也会隐式返回None。可以显式写return None,但非必要不推荐。
6.3 函数参数详解
参数是函数与外部数据交互的入口,Python 提供了灵活多样的参数机制,覆盖不同传参需求。
6.3.1 位置参数与关键字参数
这是最基础的两种传参方式:
- 位置参数:调用时按照形参定义的顺序依次传递实参,个数必须与形参完全一致。
- 关键字参数:调用时通过
形参名=实参值的形式传递,不受定义顺序限制,精准匹配。
def user_info(name, age):
print(f"{name}:{age}岁")
# 位置参数:严格按顺序匹配
user_info("张三", 20) # 输出:张三:20岁
# 关键字参数:顺序可任意
user_info(age=21, name="李四") # 输出:李四:21岁
# 混合使用:位置参数必须写在关键字参数之前
user_info("王五", age=22) # 正确
# user_info(name="赵六", 23) # 语法错误!位置参数不能放在关键字参数之后
6.3.2 缺省参数(默认参数)
定义函数时为参数指定默认值,调用时如果不传该参数,自动使用默认值。
语法规则:
- 带默认值的参数必须放在普通位置参数之后
- 调用时传值则覆盖默认值,不传则使用默认值
- 强烈建议默认参数使用不可变类型(数字、字符串、元组)
def user_info(name, age, address="北京"):
print(f"{name},{age}岁,住在{address}")
user_info("张三", 20) # 使用默认值:北京
user_info("李四", 21, "上海") # 位置传参覆盖默认值
user_info("王五", 22, address="广州") # 关键字传参覆盖默认值
⚠️ 经典陷阱:可变默认参数
如果默认参数是列表、字典等可变对象,多次调用会共享同一个对象,产生累积副作用。
# 错误写法
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] # 并非预期的 [2]
原因:默认参数在函数定义时就会被创建,每次不传参都会复用同一个对象。
解决方案:默认值设为 None,在函数内部初始化可变对象。
# 正确写法
def add_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
6.3.3 不定长参数(可变参数)
当无法提前确定参数个数时,使用不定长参数自动接收所有多余参数,分为两类:
*args:接收所有多余的位置参数,自动打包为一个元组**kwargs:接收所有多余的关键字参数,自动打包为一个字典
命名惯例:
args和kwargs是行业约定俗成的名称,也可以自定义名称,但*和**是语法强制要求。
def func(*args, **kwargs):
print("位置参数打包:", args)
print("关键字参数打包:", kwargs)
func(10, 20, name="张三", age=20)
# 输出:
# 位置参数打包: (10, 20)
# 关键字参数打包: {'name': '张三', 'age': 20}
典型应用场景:装饰器、通用日志函数、接口封装等需要兼容任意参数的场景。
6.3.4 参数定义的正确顺序
当多种参数同时存在时,必须遵循固定的先后顺序,否则会触发语法错误:
**位置参数(必选) → *args(可变位置) → 缺省参数(默认) → kwargs(可变关键字)
def demo(a, b, *args, c=10, d=20, **kwargs):
print(f"必选参数:a={a}, b={b}")
print(f"可变位置参数:{args}")
print(f"默认参数:c={c}, d={d}")
print(f"可变关键字参数:{kwargs}")
demo(1, 2, 3, 4, c=30, name="张三", age=20)
6.3.5 序列拆包传参
调用函数时,可以通过 * 拆列表/元组、** 拆字典,将容器内的元素批量作为参数传入。
def add(a, b):
return a + b
nums = [10, 20]
print(add(*nums)) # 等价于 add(10, 20),输出:30
def user_info(name, age):
print(name, age)
user = {"name": "张三", "age": 20}
user_info(**user) # 等价于 user_info(name="张三", age=20)
6.4 匿名函数(lambda)
匿名函数是使用 lambda 关键字定义的简易函数,适合处理简单的、一次性的单行逻辑,常作为参数传递给高阶函数。
6.4.1 基本语法
lambda 形参列表: 表达式
核心特点:
- 函数体只能是一个表达式,表达式的结果就是返回值,无需写
return - 支持 0 个或多个参数,多个参数用逗号分隔
- 不能包含赋值、循环、多分支等复杂语句
# 计算两数之和
add = lambda a, b: a + b
print(add(10, 20)) # 输出:30
6.4.2 典型应用场景
lambda 一般不会赋值给变量单独调用,更多是配合 sorted、map、filter 等高阶函数使用。
示例1:按指定规则排序
students = [("张三", 20), ("李四", 18), ("王五", 22)]
# 按元组的第二个元素(年龄)升序排序
students.sort(key=lambda s: s[1])
print(students) # [('李四', 18), ('张三', 20), ('王五', 22)]
示例2:批量转换列表元素
nums = [1, 2, 3, 4, 5]
# 对每个元素求平方
squared = list(map(lambda x: x**2, nums))
print(squared) # [1, 4, 9, 16, 25]
6.4.3 lambda 与普通函数对比
| 对比项 | lambda 匿名函数 | def 普通函数 |
|---|---|---|
| 语法 | 单行表达式 | 多行代码块 |
| 返回值 | 自动返回表达式结果 | 需显式写 return |
| 文档字符串 | 不支持 | 支持 |
| 可读性 | 简单逻辑清晰,复杂逻辑可读性差 | 结构化强,易读易维护 |
| 适用场景 | 回调函数、简单数据变换、一次性逻辑 | 复杂业务逻辑、需要复用的功能 |
6.5 函数的返回值
6.5.1 核心规则
- 默认返回 None:没有写
return语句的函数,执行完毕后默认返回None - 立即终止函数:函数执行到
return会立刻结束,后续代码永远不会执行 - 支持多返回值:
return后可以写多个值,底层会自动封装为元组返回
# 多返回值示例
def calc(a, b):
sum_val = a + b
diff = a - b
return sum_val, diff # 等价于 return (sum_val, diff)
# 整体接收为元组
result = calc(10, 3)
print(result) # (13, 7)
# 解包分别接收
s, d = calc(10, 3)
print(s, d) # 13 7
6.6 变量的作用域
作用域指的是变量能够生效的代码范围,Python 中主要分为局部作用域和全局作用域。
6.6.1 局部变量与全局变量
- 局部变量:在函数内部定义的变量,仅在函数内部可用,函数执行结束后自动销毁
- 全局变量:在函数外部定义的变量,整个 py 文件内都可读取
x = 10 # 全局变量
def test():
y = 20 # 局部变量
print(x) # 函数内部可以读取全局变量
print(y)
test()
# print(y) # 报错!外部无法访问局部变量
6.6.2 global 关键字
函数内部默认只能读取全局变量,如果要修改全局变量,必须用 global 声明,否则会被当作新建的局部变量。
x = 10
def modify():
global x # 声明 x 是全局变量
x = 20 # 修改全局变量
modify()
print(x) # 输出:20
开发建议:尽量减少全局变量的使用,优先通过「参数传入 + 返回值传出」的方式在函数间传递数据,避免副作用和维护困难。
💡 本章小结
- 函数通过
def关键字定义,遵循「先定义、后调用」的规则,无return默认返回None - 函数按参数与返回值分为四类,日常开发最常用「有参有返回值」形式
- 参数体系:位置参数、关键字参数、缺省参数、不定长参数(
*args/**kwargs),需严格遵守定义顺序 lambda匿名函数适合简单单行逻辑,常作为高阶函数的参数- 函数支持多返回值,本质是返回元组,可通过解包接收
- 变量作用域:局部变量仅函数内可用,修改全局变量需
global声明
后续文章将深入讲解函数进阶特性:递归、闭包、装饰器与生成器,进一步提升代码的优雅度与复用能力。
七、文件操作
文件操作是 Python 实现数据持久化的核心手段。程序运行时的数据都驻留在内存中,进程退出后数据随之消失;通过文件读写,可以将数据永久保存到磁盘,实现配置读取、日志写入、资源文件处理等各类需求,是后端开发、自动化脚本、数据处理场景的必备技能。
7.1 基本操作流程
Python 操作本地文件遵循标准的「三步曲」:打开文件 → 执行读写 → 关闭文件。
7.1.1 核心函数 open()
通过内置函数 open() 打开文件,函数返回一个文件对象,所有读写操作都基于该对象完成。
基础语法:
open(file, mode='r', encoding=None)
核心参数说明:
file:文件路径,支持相对路径(相对于当前工作目录)和绝对路径mode:文件打开模式,默认值为r(只读)encoding:指定文件编码。处理中文文本时必须显式设置为utf-8,否则会因系统默认编码不同出现乱码;二进制模式下不可指定该参数
7.1.2 基础三步式示例
# 1. 打开文件:只读模式 + UTF-8 编码
f = open("test.txt", "r", encoding="utf-8")
# 2. 执行读取操作
content = f.read()
print(content)
# 3. 关闭文件,释放系统资源
f.close()
7.1.3 为什么必须关闭文件?
文件对象会占用操作系统的文件句柄资源,同时写入的数据会先暂存在内存缓冲区,不会立刻写入磁盘。
- 不主动关闭可能造成资源泄漏,超出系统文件句柄上限后无法再打开新文件
- 程序异常退出时,缓冲区数据可能未刷入磁盘,导致数据丢失
- Python 的垃圾回收机制无法保证及时、可靠地释放文件资源
7.2 文件打开模式详解
mode 参数决定了文件的操作权限、初始指针位置以及文件不存在时的处理逻辑,分为文本模式、二进制模式两大类,可通过追加符号扩展功能。
7.2.1 模式对比总表
| 模式 | 操作权限 | 文件不存在 | 文件已存在 | 指针初始位置 | 适用场景 |
|---|---|---|---|---|---|
r |
只读 | 抛出 FileNotFoundError |
正常读取,不修改 | 文件开头 | 读取已有文本文件 |
w |
只写 | 自动创建新文件 | 清空原有内容,完全覆盖 | 文件开头 | 新建/覆盖写入文件 |
a |
只写(追加) | 自动创建新文件 | 保留原内容,在末尾追加 | 文件末尾 | 日志追加、内容补充 |
r+ |
读写 | 抛出 FileNotFoundError |
可读可写,写入从指针位置覆盖 | 文件开头 | 同时需要读写已有文件 |
w+ |
读写 | 自动创建新文件 | 先清空原内容,再读写 | 文件开头 | 新建文件并读写 |
a+ |
读写 | 自动创建新文件 | 可读,写入只能追加到末尾 | 文件末尾 | 追加写入同时支持读取 |
rb/wb/ab |
二进制读写 | 对应文本模式规则 | 对应文本模式规则 | 对应文本模式规则 | 图片、视频、压缩包等非文本文件 |
7.2.2 模式选择建议
- 纯文本内容(.txt、.py、.json 等)使用
r/w/a,必须指定encoding - 非文本文件(图片、音频、exe、压缩包)使用
rb/wb/ab,禁止指定encoding - 不确定文件是否存在、且不想覆盖原有内容时,优先用
a追加模式,慎用w模式 - 绝大多数场景不需要
+混合模式,分开读写逻辑更清晰,不易出错
7.3 读写操作详解
7.3.1 读取文件的四种方式
根据文件大小和业务需求,可选择不同的读取方法。
1. read() 一次性读取
- 不传参数:读取整个文件,返回完整字符串
- 传 size 参数:读取指定数量的字符(文本模式)或字节(二进制模式)
- 特点:使用最简单,但大文件会一次性加载到内存,容易造成内存溢出
with open("test.txt", "r", encoding="utf-8") as f:
content = f.read()
2. for 循环逐行迭代(推荐大文件使用)
文件对象本身是可迭代对象,直接用 for 循环遍历即可逐行读取。
- 特点:内存占用极低,始终只加载当前行数据,是处理 GB 级大文件的标准方案
with open("test.txt", "r", encoding="utf-8") as f:
for line in f:
# strip() 去除首尾空白和换行符
print(line.strip())
3. readline() 读取单行
每次调用读取一行内容,读到文件末尾时返回空字符串,适合按行控制读取进度。
with open("test.txt", "r", encoding="utf-8") as f:
line = f.readline()
while line:
print(line.strip())
line = f.readline()
4. readlines() 批量读取所有行
一次性读取全部内容,按行拆分后返回列表,每个元素对应一行内容(包含换行符)。
- 特点:返回列表便于后续处理,但同样不适合超大文件
with open("test.txt", "r", encoding="utf-8") as f:
lines = f.readlines()
💡 性能建议:百 MB 以上的大文件,禁止使用 read() 和 readlines(),优先选择逐行迭代或分块读取的方式。
7.3.2 写入文件的两种方式
1. write() 写入单段内容
将字符串写入文件,返回写入的字符数。
⚠️ 注意:write() 不会自动添加换行符,需要手动拼接 \n 实现换行。
# 覆盖写入
with open("output.txt", "w", encoding="utf-8") as f:
f.write("第一行内容\n")
f.write("第二行内容\n")
# 追加写入
with open("output.txt", "a", encoding="utf-8") as f:
f.write("追加的新内容\n")
2. writelines() 批量写入多行
接收一个字符串列表,将所有元素依次写入文件。
⚠️ 注意:同样不会自动添加换行符,列表中的每个元素需要自行包含 \n。
lines = ["Python\n", "Java\n", "Go\n"]
with open("output.txt", "w", encoding="utf-8") as f:
f.writelines(lines)
7.4 with 上下文管理器(官方推荐)
手动调用 close() 存在致命缺陷:如果读写过程中代码抛出异常,close() 不会执行,最终导致文件资源泄漏。
Python 提供的 with 上下文管理器可以自动管理文件生命周期:无论代码正常执行还是发生异常,退出代码块时都会自动关闭文件、刷新缓冲区,是 Python 文件操作的标准最佳实践。
7.4.1 基础用法
with open("test.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
# 离开 with 代码块后,文件自动关闭,无需手动调用 close()
对比传统的 try-finally 写法,with 语法更简洁、安全性更高:
# 传统写法,繁琐且容易遗漏
f = open("test.txt", "r", encoding="utf-8")
try:
content = f.read()
finally:
f.close()
7.4.2 同时打开多个文件
with 支持同时管理多个文件对象,非常适合文件拷贝、对比等场景。
示例:文本文件拷贝
with open("source.txt", "r", encoding="utf-8") as src, \
open("dest.txt", "w", encoding="utf-8") as dst:
for line in src:
dst.write(line)
7.5 文件指针与定位
文件内部维护一个「指针」标记当前读写位置,所有读写操作都会从指针位置向后执行,操作完成后指针自动后移。
常用方法
tell():返回当前指针的位置(单位:字节)seek(offset, whence):手动移动指针位置offset:偏移量,正数向后、负数向前whence:参考基准,可选值:0:以文件开头为基准(默认值,文本模式仅支持该模式)1:以当前位置为基准2:以文件末尾为基准
with open("test.txt", "r", encoding="utf-8") as f:
print(f.tell()) # 初始位置为 0
f.read(5)
print(f.tell()) # 读取 5 个字符后指针移动
f.seek(0) # 指针回到文件开头
⚠️ 注意:文本模式下 seek() 仅支持从文件开头(whence=0)偏移,不支持从当前位置或末尾偏移;二进制模式无此限制,可自由移动指针。
7.6 文件操作的异常处理
文件操作属于 IO 操作,受外部环境影响大,极易出现异常(文件不存在、权限不足、路径非法等)。生产环境中必须配合异常处理,提升程序健壮性。
常见异常类型:
FileNotFoundError:文件不存在,或路径错误PermissionError:没有读写权限IsADirectoryError:传入了目录路径而非文件路径UnicodeDecodeError:编码不匹配,读取时出现乱码错误
完整异常处理示例:
try:
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
print("错误:文件不存在,请检查文件路径是否正确")
except PermissionError:
print("错误:没有文件的读取权限")
except UnicodeDecodeError:
print("错误:文件编码不匹配,请检查 encoding 参数")
except Exception as e:
print(f"文件读取失败,未知错误:{e}")
7.7 os 模块:文件与目录管理
Python 内置的 os 模块提供了操作系统级别的文件、目录管理能力,覆盖创建、删除、重命名、路径判断等常用操作。使用前需先导入:
import os
7.7.1 目录操作
| 方法 | 功能说明 |
|---|---|
os.getcwd() |
获取当前工作目录的绝对路径 |
os.mkdir(dir_name) |
创建单层空目录,目录已存在则报错 |
os.makedirs("a/b/c") |
递归创建多级嵌套目录 |
os.rmdir(dir_name) |
删除空目录,非空目录会报错 |
os.removedirs("a/b/c") |
递归删除多层空目录 |
os.listdir(path) |
列出指定目录下的所有文件和文件夹名称,返回列表 |
os.chdir(path) |
切换当前工作目录到指定路径 |
7.7.2 文件操作
| 方法 | 功能说明 |
|---|---|
os.remove(file_name) |
删除指定文件,文件不存在则报错 |
os.rename(old_name, new_name) |
重命名文件或文件夹 |
7.7.3 os.path 路径处理
os.path 是 os 模块的子模块,专门用于路径处理,天然跨平台兼容,避免手动拼接路径带来的斜杠不兼容问题。
| 方法 | 功能说明 |
|---|---|
os.path.exists(path) |
判断文件/目录是否存在,返回布尔值 |
os.path.isfile(path) |
判断目标是否为文件 |
os.path.isdir(path) |
判断目标是否为目录 |
os.path.join(path1, path2, ...) |
智能拼接路径,自动适配当前系统的分隔符 |
os.path.getsize(path) |
获取文件大小,单位为字节 |
os.path.abspath(path) |
返回文件的绝对路径 |
💡 最佳实践:永远使用 os.path.join() 拼接路径,不要手动写 / 或 \,保证代码在 Windows、Linux、Mac 上都能正常运行。
综合示例:判断文件存在再读取
file_path = os.path.join("config", "settings.txt")
if os.path.exists(file_path) and os.path.isfile(file_path):
with open(file_path, "r", encoding="utf-8") as f:
print(f.read())
else:
print("配置文件不存在")
7.8 综合实战案例:通用文件备份工具
实现一个通用的文件备份函数,支持任意类型文件,采用二进制分块读取,兼顾兼容性与内存效率。
import os
def backup_file(filename):
"""
备份指定文件,生成 文件名[备份].后缀 格式的备份文件
:param filename: 待备份的文件路径
"""
# 校验文件是否存在
if not os.path.exists(filename) or not os.path.isfile(filename):
print(f"错误:文件 {filename} 不存在")
return
# 分离文件名与后缀,生成备份文件名
dot_index = filename.rfind(".")
if dot_index > 0:
base_name = filename[:dot_index]
ext = filename[dot_index:]
backup_name = f"{base_name}[备份]{ext}"
else:
backup_name = f"{filename}[备份]"
# 二进制分块拷贝,支持任意文件类型,8KB 缓冲区平衡性能与内存
with open(filename, "rb") as src, open(backup_name, "wb") as dst:
while True:
chunk = src.read(8192) # 每次读取 8KB
if not chunk:
break
dst.write(chunk)
print(f"备份成功,已生成:{backup_name}")
# 使用示例
backup_file("important.docx")
💡 本章小结
- 操作流程:文件操作遵循「打开 → 读写 → 关闭」三步,所有场景优先使用 with 语句自动管理资源
- 模式选择:文本文件用
r/w/a并指定utf-8编码;二进制文件用rb/wb/ab - 读写原则:小文件可一次性读取,大文件必须逐行或分块读取,避免内存溢出
- 异常处理:IO 操作不确定性高,生产环境必须捕获
FileNotFoundError、PermissionError等常见异常 - 路径管理:使用
os.path.join()处理路径,保证跨平台兼容性 - 易错点:
write()/writelines()不会自动加换行;w模式会清空原文件;文本模式seek只能从开头偏移
下一章我们将深入学习 异常处理与程序调试,掌握错误捕获、堆栈分析与调试技巧,让程序具备更强的容错能力。
八、异常处理
程序运行过程中,总会遇到各类意料之外的错误:文件不存在、用户输入非法、除数为零、网络连接中断……如果不做任何处理,这些错误会直接导致程序崩溃终止。异常处理是Python用于捕获、处理运行时错误的语法机制,能够让程序在出错后优雅降级、继续运行,是提升程序健壮性与容错能力的核心手段。
8.1 什么是异常
8.1.1 异常与语法错误的区别
很多初学者会混淆“代码写错”和“运行出错”,二者本质完全不同:
- 语法错误(SyntaxError):代码不符合Python语法规则,程序在解释阶段就会报错,根本无法运行。例如缩进错误、括号不配对、关键字拼写错误。
- 运行时异常:语法完全合法,但程序运行过程中因外部条件、数据不合法等原因触发的错误。例如打开不存在的文件、除以零、访问未定义的变量,这类运行时错误就是本章要处理的「异常」。
异常发生时,程序默认会终止执行,并在控制台打印完整的错误堆栈,包含异常类型、错误描述、出错的代码位置,便于定位问题。
8.1.2 常见内置异常
Python内置了数十种异常类型,覆盖绝大多数运行时错误场景,开发中最常用的有以下几类:
| 异常类型 | 触发场景 |
|---|---|
ZeroDivisionError |
除法运算中除数为 0 |
NameError |
使用了未定义的变量/函数名 |
TypeError |
数据类型不匹配,例如字符串与数字直接相加 |
ValueError |
数据类型正确但值非法,例如 int("abc") |
IndexError |
索引超出序列范围,例如长度为3的列表访问索引5 |
KeyError |
字典中不存在对应的键 |
FileNotFoundError |
尝试打开的文件/目录不存在 |
PermissionError |
没有文件读写、目录操作的权限 |
AttributeError |
对象不存在对应的属性或方法 |
Exception |
所有常规运行时异常的父类,可匹配绝大多数业务异常 |
注意:
BaseException是所有异常的最顶层父类,除了常规异常外,还包含KeyboardInterrupt(Ctrl+C中断)、SystemExit(程序退出)等系统级异常。日常开发禁止直接捕获BaseException,避免拦截系统退出信号,导致程序无法正常终止。
8.2 异常捕获语法
Python 通过 try-except 语句实现异常捕获,从基础单分支到完整结构,支持多种灵活写法。
8.2.1 基础捕获:try-except
最核心的异常捕获结构:try 块中放置可能出错的代码,except 块中编写出错后的处理逻辑。
语法格式
try:
可能抛出异常的代码
except 异常类型:
异常发生后的处理代码
执行逻辑:
- try内代码无异常:跳过except块,程序继续向下执行
- try内代码触发匹配的异常:立即跳转到对应except块执行,执行完后程序继续运行,不会崩溃
示例:捕获除零错误
num1, num2 = 10, 0
try:
result = num1 / num2
print(f"计算结果:{result}")
except ZeroDivisionError:
print("错误:除数不能为0")
print("程序继续执行")
8.2.2 多分支捕获:精准处理不同异常
当try块中可能触发多种不同异常时,可以编写多个except分支,分别处理不同类型的错误,实现精细化错误处理。
匹配规则:从上到下依次匹配异常类型,第一个匹配成功的分支会被执行,后续分支不再判断。
try:
num = int(input("请输入一个数字:"))
result = 10 / num
print(f"计算结果:{result}")
except ValueError:
print("输入错误:请输入合法的整数")
except ZeroDivisionError:
print("计算错误:不能输入0作为除数")
except Exception as e:
print(f"未知错误:{e}")
最佳实践:精准的具体异常写在前面,宽泛的父类异常写在最后,用于兜底未知错误。
8.2.3 获取异常详情:as 变量名
在except后追加 as 变量名,可以接收异常实例对象,获取具体的错误描述信息,便于日志记录和问题排查。
try:
f = open("不存在的文件.txt", "r")
except FileNotFoundError as e:
print(f"文件打开失败,错误详情:{e}")
8.2.4 else 分支:无异常时执行
else 是可选分支,只有当try块中没有发生任何异常时,才会执行else内的代码。适合将“操作成功后才执行”的逻辑与核心操作做分离,让代码结构更清晰。
try:
f = open("test.txt", "r", encoding="utf-8")
content = f.read()
except FileNotFoundError as e:
print(f"读取失败:{e}")
else:
print("文件读取成功,内容如下:")
print(content)
f.close()
8.2.5 finally 分支:无论如何都执行
finally 同样是可选分支,无论try块是否发生异常,甚至except/else中出现了新异常,finally里的代码一定会执行。
它的核心作用是资源释放:比如关闭文件、关闭数据库连接、释放网络连接等,保证资源一定会被回收,不会因为异常造成资源泄漏。
f = None
try:
f = open("test.txt", "r", encoding="utf-8")
content = f.read()
print(content)
except Exception as e:
print(f"读取出错:{e}")
finally:
# 无论是否出错,都尝试关闭文件
if f:
f.close()
print("文件资源已释放")
补充:我们之前学习的
with上下文管理器,本质就是封装了try-finally的资源自动释放逻辑,比手动编写finally更简洁可靠。
⚠️ 开发禁忌:不要盲目捕获所有异常
很多初学者图省事会直接写空except或捕获Exception,这会掩盖代码中的真实Bug,导致问题难以排查。
正确原则:优先精准捕获已知的异常类型,用Exception兜底未知异常,同时必须打印/记录错误详情。
8.3 异常的传递性
8.3.1 异常冒泡机制
Python的异常具备传递性(冒泡特性):
如果一个函数内部发生了异常,且该函数没有捕获处理,异常就会自动向上一层传递给调用它的函数,逐层向上冒泡;如果传到程序最外层依然没有被捕获,程序才会终止并报错。
代码示例
def func_a():
print("执行func_a")
1 / 0 # 触发异常,函数内部没有捕获
def func_b():
print("执行func_b")
func_a() # 调用func_a,异常传递到这里
def main():
try:
func_b()
except ZeroDivisionError as e:
print(f"在主函数捕获到异常:{e}")
main()
print("程序正常继续运行")
执行流程:
func_a触发除零异常,自身没有捕获,向上传递给调用者func_bfunc_b也没有捕获异常,继续向上传递给调用者mainmain函数中的try-except匹配并捕获异常,执行处理逻辑- 异常处理完成,程序继续向下执行,不会崩溃
8.3.2 工程化应用
基于异常的传递性,实际项目中普遍采用「底层抛出、上层统一处理」的设计模式:
- 底层功能函数只负责实现业务逻辑,不做异常捕获,出现问题直接抛出
- 在程序主入口、核心调度层统一捕获异常、记录日志、做降级处理
- 既保证错误不会导致整体程序崩溃,又避免每层函数都编写重复的异常处理代码
8.4 主动抛出异常:raise
并非只有系统会触发异常,开发中我们也可以根据业务规则主动抛出异常,比如参数校验不通过、业务状态不符合要求时,用异常告知调用方操作失败。Python 通过 raise 关键字主动抛出异常。
8.4.1 基础用法
语法:raise 异常类型(错误描述信息)
示例:参数校验
def register(username, password):
if len(password) < 6:
# 主动抛出值错误异常
raise ValueError("密码长度不能少于6位")
print(f"用户 {username} 注册成功")
try:
register("张三", "123")
except ValueError as e:
print(f"注册失败:{e}")
8.4.2 捕获后重新抛出
有时候我们需要先记录异常日志,再把异常原样交给上层处理,这时可以在except块中直接写 raise,重新抛出当前捕获的异常。
def data_process():
try:
1 / 0
except ZeroDivisionError as e:
# 先记录错误日志
print(f"[记录日志] 计算异常:{e}")
# 重新抛出异常,交给上层做业务降级
raise
try:
data_process()
except ZeroDivisionError:
print("上层捕获异常,执行降级逻辑")
8.5 自定义异常
Python内置的异常仅能覆盖通用的系统错误,而实际业务中会存在大量业务层面的错误(如余额不足、用户不存在、库存不足),这类错误无法用内置异常精准区分,这时就可以通过自定义异常类来实现业务级别的错误分类。
自定义异常非常简单,只需要继承 Exception 类即可:
# 业务异常基类
class BusinessError(Exception):
"""所有业务异常的父类"""
pass
# 具体业务异常
class BalanceNotEnoughError(BusinessError):
"""账户余额不足异常"""
pass
class UserNotExistError(BusinessError):
"""用户不存在异常"""
pass
使用示例
def pay(money, balance):
if balance < money:
raise BalanceNotEnoughError("账户余额不足,支付失败")
balance -= money
print(f"支付成功,剩余余额:{balance}")
try:
pay(100, 50)
except BalanceNotEnoughError as e:
print(e)
except BusinessError as e:
print(f"业务处理失败:{e}")
except Exception as e:
print(f"系统异常:{e}")
优势:可以按业务类型精准捕获和处理不同错误,代码语义更清晰,便于分层处理业务异常与系统异常。
💡 本章小结
- 异常本质:运行时的非正常状态,默认会终止程序;语法错误不属于异常,在运行前就会报错。
- 完整语法:
try存放风险代码,except捕获并处理异常,else无异常时执行,finally无论如何都执行,用于释放资源。 - 捕获原则:优先精准捕获具体异常类型,宽泛异常放最后兜底;禁止盲目捕获所有异常,避免掩盖真实问题。
- 异常传递:异常会逐层向上冒泡,直到被捕获;工程中通常在最外层统一捕获处理,底层只负责抛出。
- 主动抛出:使用
raise主动触发异常,常用于参数校验、业务错误提示。 - 自定义异常:继承
Exception实现业务异常,用于区分业务错误与系统错误,提升代码可维护性。
九、模块与包
随着项目代码量增长,将所有逻辑写在单个 .py 文件中会导致代码臃肿、难以维护。Python 通过「模块」与「包」实现代码的拆分与组织:模块是独立的 .py 文件,包是管理多个模块的文件夹,二者共同支撑了 Python 的模块化编程,实现代码复用、命名空间隔离与分层管理。
9.1 模块(Module)
9.1.1 什么是模块
一个独立的 .py 代码文件就是一个模块。模块内部可以定义函数、类、变量,也可以编写可执行代码,是 Python 代码组织的最小单元。
使用模块的核心优势:
- 代码复用:通用功能封装为模块后,可在多个项目/文件中导入使用,避免重复开发
- 命名隔离:不同模块可以定义同名的函数、变量,通过模块名区分,不会产生命名冲突
- 便于维护:按功能拆分文件,代码结构清晰,方便定位问题和迭代修改
Python 中的模块分为三类:
- 标准库模块:Python 官方内置,开箱即用,如
time、os、random - 第三方模块:社区开发者贡献,需通过
pip安装,如requests、pandas - 自定义模块:开发者自己编写的业务
.py文件
9.1.2 模块的五种导入方式
Python 提供了多种导入语法,适配不同场景,各有优劣。
1. 导入整个模块
语法:import 模块名
导入后通过 模块名.成员 的方式调用模块内的函数、类和变量。
import time
# 调用模块内的 sleep 函数
time.sleep(1)
- 优点:命名空间独立,不会与当前文件的变量冲突;可读性强,可清晰看出成员来源
- 缺点:调用时需完整书写模块名,长模块名稍显繁琐
2. 导入模块并起别名
语法:import 模块名 as 别名
当模块名较长,或存在命名冲突时,可为模块设置简短别名。
import time as t
t.sleep(1)
- 适用场景:简化长模块名、区分重名模块
3. 导入模块内指定成员
语法:from 模块名 import 成员名
仅导入需要的函数、类或变量,调用时可直接使用,无需加模块前缀。
from time import sleep
sleep(1)
- 优点:调用简洁,无需重复书写模块名
- 缺点:成员直接进入当前命名空间,若当前文件有同名变量,会发生覆盖冲突
4. 导入指定成员并起别名
语法:from 模块名 import 成员名 as 别名
既简化调用,又能解决成员名冲突问题。
from time import sleep as sl
sl(1)
5. 导入模块内所有成员
语法:from 模块名 import *
将模块中所有非下划线开头的成员全部导入当前命名空间。
from time import *
sleep(1)
⚠️ 强烈不推荐日常使用:会污染当前命名空间,无法追溯变量/函数的来源,极易引发命名冲突,也不利于代码调试。仅在快速脚本、交互式环境中可临时使用。
最佳实践:优先选择
import 模块或from 模块 import 指定成员,精准导入、可读性优先,尽量避免import *。
9.1.3 模块导入的核心特性
- 单次加载:同一个模块在程序中无论被导入多少次,都只会在第一次导入时执行一次顶层代码,后续导入直接复用内存中已加载的模块对象,不会重复执行。
- 独立命名空间:每个模块拥有独立的作用域,内部变量、函数不会污染其他模块,通过模块名访问可精准区分。
9.2 包(Package)
9.2.1 什么是包
当模块数量越来越多,仅靠零散文件无法有效管理时,就需要用包来分层组织模块。简单来说,包就是一个包含 __init__.py 文件的文件夹,可以存放多个相关模块,也可以嵌套子包。
__init__.py是包的标识文件,Python 会将包含该文件的文件夹识别为正规包__init__.py可以是空文件,也可以编写包的初始化代码、定义对外暴露的接口- 核心作用:按功能分类管理模块、解决同名模块冲突、构建清晰的项目层级
补充:Python 3.3 之后引入了「命名空间包」,即使没有
__init__.py也能导入文件夹中的模块。但正规项目中仍推荐保留__init__.py,兼容性更好,也便于统一管理对外接口。
9.2.2 包的导入方式
包的导入语法与模块类似,通过 . 表示层级关系。
假设项目结构如下:
my_package/ # 根包
__init__.py
module_a.py # 功能模块A
module_b.py # 功能模块B
常见的三种导入写法:
# 方式1:完整路径导入
import my_package.module_a
my_package.module_a.func_a()
# 方式2:导入包内的模块
from my_package import module_a
module_a.func_a()
# 方式3:直接导入模块内的函数/类
from my_package.module_a import func_a
func_a()
9.2.3 __init__.py 的作用
__init__.py 会在包被导入时自动执行,主要有两个用途:
- 包初始化:编写包的全局初始化代码、预加载依赖、定义包级变量
- 简化导入路径:可以在
__init__.py中提前导入核心成员,让外部调用更简洁
例如在my_package/__init__.py中写入:
外部就可以直接通过from .module_a import func_afrom my_package import func_a调用,无需书写完整的模块路径。
9.3 自定义模块与包的注意事项
9.3.1 命名规范
- 模块名、包名必须符合标识符规则:由字母、数字、下划线组成,不能以数字开头
- 禁止与标准库重名:不要给自己的文件命名为
time.py、os.py、random.py等,否则会覆盖 Python 内置标准库,导致程序报错 - 推荐使用全小写蛇形命名法,单词间用下划线分隔,如
user_utils.py
9.3.2 __name__ 变量与测试代码
每个模块都有一个内置变量 __name__,它的值取决于模块的运行方式:
- 当模块被直接运行时(直接执行该
.py文件),__name__的值为字符串'__main__' - 当模块被导入时,
__name__的值为模块本身的文件名
基于这个特性,我们可以把模块的测试代码写在 if __name__ == '__main__': 代码块中:
- 直接运行模块文件时,测试代码会执行,方便本地调试功能
- 模块被其他文件导入时,测试代码不会执行,不会污染调用方逻辑
示例:calc.py
def add(a, b):
return a + b
def sub(a, b):
return a - b
# 测试代码:仅当前文件直接运行时执行
if __name__ == '__main__':
print("模块自测:")
print(add(1, 2))
print(sub(10, 3))
9.3.3 __all__ 变量控制导入范围
__all__ 是一个字符串列表,定义在模块或包的 __init__.py 中,专门用于控制 from xxx import * 时的导入范围。
- 未定义
__all__:import *会导入所有非下划线开头的成员 - 定义
__all__:import *只会导入列表中指定的成员
示例:utils.py
__all__ = ["add", "public_num"] # 仅对外暴露这两个成员
public_num = 100
def add(a, b):
return a + b
# 内部工具函数,不希望对外暴露
def _private_func():
pass
当其他文件执行 from utils import * 时,只能拿到 add 和 public_num,_private_func 不会被导入。
作用:控制模块对外暴露的接口,隐藏内部实现细节,降低调用方误用内部函数的风险。
9.3.4 模块搜索路径
导入模块时,Python 会按照 sys.path 列表中的路径依次查找对应的模块文件,找不到则抛出 ModuleNotFoundError。
sys.path 默认包含三类路径:
- 当前执行脚本所在的目录
- Python 标准库目录
- 第三方库安装目录(
site-packages)
初学者常见坑:导入自定义模块时报错,大概率是模块文件不在当前工作目录,也不在
sys.path中。解决方案是按照标准项目结构组织代码,或将项目根目录加入sys.path。
💡 本章小结
- 模块:单个
.py文件,是代码拆分的最小单元;支持 5 种导入方式,优先使用精准导入,避免import * - 包:包含
__init__.py的文件夹,用于分层管理模块,通过.表示层级关系 __name__:直接运行模块时值为__main__,常用于编写模块自测代码,导入时不执行测试逻辑__all__:控制import *的导入范围,用于隐藏内部实现、规范对外接口- 命名注意:自定义模块禁止与标准库重名,遵循蛇形命名规范
十、总结
Python 是一门优雅且强大的语言,它的设计哲学强调可读性和简洁性。以上就是Python基础阶段的核心知识点,学习编程最重要的是多动手练习。如果你已经掌握了本系列的全部内容,恭喜你——你已经具备了 Python 入门开发者的核心能力。接下来,选择一个你感兴趣的方向深入下去,把基础转化为真正的项目经验。
后续进阶可以继续学习:面向对象、高级函数、装饰器、迭代器生成器、正则表达式、数据库操作、Web开发框架等内容。
祝你在 Python 的世界里越走越远,享受编程带来的乐趣与成就感!🚀
如果本文对你有帮助,欢迎点赞、收藏、关注,后续会持续更新Python进阶教程。
更多推荐
所有评论(0)