Python零基础入门:一篇带你吃透Python核心基础

本文系统梳理Python编程的核心基础知识,从环境搭建、基础语法到数据容器、函数、文件操作、异常处理,适合零基础入门学习,也可作为复习速查手册。所有知识点均配套代码示例,开箱即用。

文章目录

一、Python概述与环境搭建

1.1 Python语言简介

Python由吉多·范·罗苏姆(龟叔)发明,是一门解释型、面向对象、语法简洁的高级编程语言。它具备以下核心特点:
在这里插入图片描述

  • 语法简单,接近自然语言,学习门槛低
  • 开源免费,拥有极其丰富的第三方库
  • 跨平台可移植,支持Windows、Linux、MacOS
  • 应用场景广泛:Web开发、数据分析、人工智能、自动化运维、爬虫等
  • 官网 https://www.python.org/

💡 小知识:Python 之父是 Guido van Rossum(龟叔),吉祥物是蛇,python因此得名。

Python属于解释型语言:
在这里插入图片描述

1.2 环境搭建

学习Python首先需要安装两个核心工具:

  1. Python解释器:负责解析执行Python代码,推荐安装3.8及以上稳定版本
  2. PyCharm:Python主流集成开发工具,代码补全、调试功能强大
    Windows平台下载安装

对于绝大多数初学者,Windows 是主要的学习平台。下面我们分步详解如何在 Windows 上安装 Python 解释器。

1. 下载官方安装包

访问 Python 官方下载页:https://www.python.org/downloads/windows/

  • Stable Releases(稳定版本)列表中,选择最新的 Python 3.10.xPython 3.11.x 版本(x 代表小版本号,如 3.10.12)。建议选择 3.10 或 3.11 这类长期支持(LTS)版本,兼容性更好。
  • 根据你的系统架构下载对应的安装包:
    • 64位系统:点击 Windows installer (64-bit) 下载。
    • 32位系统:点击 Windows installer (32-bit) 下载(目前新电脑基本都是 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)中直接输入 pythonpip 命令,而无需切换到 Python 的安装目录。如果忘记勾选,后续手动配置环境变量会比较麻烦。

  • 第二步:选择安装方式

    • Install Now(立即安装):一键安装到默认路径(通常是 C:\Users\你的用户名\AppData\Local\Programs\Python\Python310)。适合大多数用户,简单快捷。
    • Customize installation(自定义安装):允许你自定义安装路径和可选组件。建议高级用户或需要将 Python 安装到特定目录(如 D:\Python310)时使用。
  • 第三步:可选功能(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 是否已正确安装并配置到环境变量。

  1. 按下键盘 Win + R 组合键,打开“运行”对话框。
  2. 输入 cmd 并回车,打开命令提示符(CMD)。
  3. 在命令行中依次输入以下两条命令,并回车执行:
    python --version
    pip --version
    
  4. 观察输出结果:
    • 如果 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创建项目步骤

  1. 打开PyCharm → New Project → 设置项目路径与Python解释器
    在这里插入图片描述
    在这里插入图片描述

  2. 右键项目 → New → Python File → 输入文件名
    在这里插入图片描述在这里插入图片描述

  3. 编写代码后右键 → 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 变量命名规范

变量名属于标识符的一种,命名分为强制规则(违反则直接语法报错)和推荐规范(行业通用惯例,提升代码可读性)。

强制规则
  1. 只能由字母、数字、下划线三类字符组成
  2. 数字不能作为开头(例如 1name 是非法命名)
  3. 严格区分大小写(Ageage 是两个完全无关的变量)
  4. 不能使用Python关键字(如 ifforclass 等)
推荐规范
  1. 见名知意:变量名应直观体现数据含义(例如用 user_age 而非无意义的 a
  2. 遵循蛇形命名法:多个单词用下划线连接(例如 user_nametotal_score
  3. 普通变量全小写,类名使用大驼峰命名法(后续面向对象章节讲解)
2.2.3 四大基础数据类型

Python内置了丰富的数据类型,入门阶段最核心的是4种基础类型,覆盖数字、文本、真假判断三大核心场景。

类型名称 类型关键字 常见取值示例 核心说明
整型 int 100-200 所有整数,Python3中int无大小限制,支持超大整数
浮点型 float 3.14-0.51.0 所有小数,采用双精度存储,存在微小精度误差
布尔型 bool TrueFalse 表示“真/假”,是int的子类:True等价于1,False等价于0
字符串 str "你好"'Python''''多行文本''' 引号包裹的文本内容,单引号、双引号、三引号均可
各类型补充细节
  1. 整型(int)
    Python3取消了long类型,所有整数统一为int类型,同时支持多种进制写法:

    num1 = 100        # 十进制
    num2 = 0b1010     # 二进制(0b开头),等价于十进制10
    num3 = 0o12       # 八进制(0o开头),等价于十进制10
    num4 = 0xA        # 十六进制(0x开头),等价于十进制10
    
  2. 浮点型(float)
    浮点数采用科学计数法存储,存在微小的精度误差,不建议直接用 == 比较两个浮点数是否相等:

    print(0.1 + 0.2)  # 输出 0.30000000000000004,而非精确的0.3
    
  3. 布尔型(bool)
    只有两个取值 TrueFalse,首字母必须大写。由于是int的子类,可直接参与数值运算:

    print(True + 1)   # 输出 2
    print(False * 10) # 输出 0
    
  4. 字符串(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 标识符

标识符是程序员在代码中自定义的所有名称,包括变量名、函数名、类名、模块名等。
所有标识符都必须遵循统一的命名规则:

  1. 由字母、数字、下划线组成,数字不能开头
  2. 严格区分大小写
  3. 不能与关键字重名

简单理解:变量命名规则就是标识符规则的具体应用,所有自定义名称都要遵守该规则。

2.3.2 关键字

关键字(也叫保留字)是Python语言内置的、具备特殊功能的单词,例如用于条件判断的 if、循环的 for、定义函数的 def 等。
关键字不能被用作标识符(不能当变量名、函数名等),否则会触发语法错误。

查看所有关键字

通过标准库 keyword 可以查看当前Python版本的全部关键字:

import keyword
print(keyword.kwlist)

Python 3.10版本共有35个关键字,常见的有:FalseTrueNoneifelseforwhiledefclassimportreturn 等。

注意:关键字大部分为小写(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。

  1. 方式一:% 占位符(传统写法)
    用特殊符号占位,末尾通过 % 传入对应变量,按顺序一一对应。

    name = "张三"
    age = 20
    print("姓名:%s,年龄:%d岁" % (name, age))
    # 输出:姓名:张三,年龄:20岁
    
  2. 方式二:str.format() 方法
    {} 占位,通过 format() 方法传入变量,支持按索引/名称匹配,可复用变量。

    print("姓名:{},年龄:{}岁".format(name, age))
    # 按索引复用变量
    print("{0}今年{1}岁,明年{1}岁就长大了".format(name, age))
    
  3. 方式三: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
重点注意事项
  1. 除法 / 的结果永远是 float 类型,哪怕两个整数刚好整除:

    print(10 / 2)  # 输出 5.0,而非整数5
    
  2. 整除 // 是向下取整,负数运算时极易踩坑:

    print(10 // 3)    # 输出 3
    print(-10 // 3)   # 输出 -4(向下取整,不是-3)
    
  3. 取余 % 结果的正负与除数一致

    print(-10 % 3)    # 输出 2(除数3是正数,结果为正)
    print(10 % -3)    # 输出 -2(除数-3是负数,结果为负)
    
  4. 幂运算 ** 支持开方操作

    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 比较运算符

用于两个值的大小、相等判断,运算结果永远是布尔值(TrueFalse)。

运算符 含义 示例 结果
> 大于 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 隐式类型转换

程序自动完成的类型转换,无需人工干预,通常发生在不同类型的数值运算中。

  • 低精度类型会自动向高精度类型转换:intfloat
  • 布尔值会自动转为整数参与运算: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:00.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通过 ifelifelse 三个关键字实现分支逻辑。

前置核心规则:缩进语法
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("不及格")
核心注意事项
  1. 分支互斥性:多个分支中最多只有一个会被执行,判断顺序严格从上到下。
  2. 条件顺序很重要:范围更精确、更小的条件要放在前面,否则会出现逻辑覆盖错误。例如如果先写 score >= 60,再写 score >= 90,那么90分也会匹配到第一个分支,出现逻辑错误。
  3. 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)  # 输出 奇数
使用建议
  1. 仅适合简单的二选一逻辑,复杂多层分支不建议使用,会大幅降低代码可读性。
  2. 三元表达式是表达式,有明确的返回值,必须接收结果或参与运算,不能单独作为语句使用。

补充:pass占位符
如果某个分支暂时不需要编写逻辑,可以用 pass 关键字占位,避免语法报错。pass不执行任何操作,仅作为语法占位符。

if age < 18:
    pass  # 后续补充未成年处理逻辑
else:
    print("成年处理")

3.2 循环结构

循环结构用于让一段代码重复执行多次,避免重复编写冗余代码。Python提供了两种循环语句:while 循环和 for 循环,二者适用场景不同。

3.2.1 while循环

while 循环通过一个条件表达式控制循环是否继续,适合循环次数不固定、依赖条件判断的场景。

语法格式
循环变量初始化
while 循环条件:
    循环体代码
    循环变量更新
执行流程
  1. 先执行循环变量的初始化(设置循环的起始值)
  2. 判断循环条件的布尔结果:
    • 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循环最常用的搭档,用于生成指定范围的整数序列。

它有三种传参方式:

  1. 一个参数 range(end):生成 0 ~ end-1 的整数,步长默认为1

    for i in range(5):
        print(i)  # 输出 0,1,2,3,4
    
  2. 两个参数 range(start, end):生成 start ~ end-1 的整数

    for i in range(1, 6):
        print(i)  # 输出 1,2,3,4,5
    
  3. 三个参数 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 循环控制语句

在循环执行过程中,我们可以通过两个关键字手动干预循环的执行流程:breakcontinue

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...elsewhile...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 切片操作

切片用于一次性取出字符串中的一段子序列,遵循「包左不包右」的规则,即包含起始索引的元素,不包含结束索引的元素。

完整语法:字符串[起始索引:结束索引:步长]

三个参数都支持省略,不同省略规则对应不同效果:

  1. 完整写法s[1:5:2],从索引1开始到索引4结束,每2个字符取1个
  2. 省略步长s[1:5],步长默认为1,连续取子串
  3. 省略起始s[:5],默认从开头(索引0)开始截取
  4. 省略结束s[2:],默认截取到字符串末尾
  5. 全省略s[:],完整复制整个字符串
  6. 负步长:步长为负数时表示从右往左截取,可实现字符串反转
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 元组支持的操作

由于不可变特性,元组没有增删改相关的方法,仅支持查询类操作:

  1. 索引取值、切片操作,规则与列表完全一致
  2. count() 统计元素出现次数
  3. index() 查找元素索引
  4. len() 获取元组长度
  5. 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 典型使用场景
  1. 字典键值反转
    将原有字典的键和值互换,前提是原字典的值为可哈希类型且不重复。
original = {"apple": 1, "banana": 2, "orange": 3}
swapped = {value: key for key, value in original.items()}
# 结果:{1: 'apple', 2: 'banana', 3: 'orange'}

⚠️ 注意:如果值不唯一,后生成的键值对会覆盖之前的内容,造成数据丢失。

  1. 双列表合并为字典
    两个列表分别存储键和值,合并为一个字典:
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))
  1. 条件过滤字典
    按条件筛选保留符合要求的键值对:
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 无序、元素自动去重 数据去重、集合数据预处理

使用原则

  1. 简单逻辑优先用推导式,代码更简洁高效;
  2. 复杂逻辑、多层嵌套优先用普通循环,保证可读性;
  3. 大数据量场景优先考虑生成器表达式,节省内存。

六、函数基础

函数是 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 缺省参数(默认参数)

定义函数时为参数指定默认值,调用时如果不传该参数,自动使用默认值。

语法规则

  1. 带默认值的参数必须放在普通位置参数之后
  2. 调用时传值则覆盖默认值,不传则使用默认值
  3. 强烈建议默认参数使用不可变类型(数字、字符串、元组)
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:接收所有多余的关键字参数,自动打包为一个字典

命名惯例:argskwargs 是行业约定俗成的名称,也可以自定义名称,但 *** 是语法强制要求。

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 形参列表: 表达式

核心特点:

  1. 函数体只能是一个表达式,表达式的结果就是返回值,无需写 return
  2. 支持 0 个或多个参数,多个参数用逗号分隔
  3. 不能包含赋值、循环、多分支等复杂语句
# 计算两数之和
add = lambda a, b: a + b
print(add(10, 20))  # 输出:30
6.4.2 典型应用场景

lambda 一般不会赋值给变量单独调用,更多是配合 sortedmapfilter 等高阶函数使用。

示例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 核心规则
  1. 默认返回 None:没有写 return 语句的函数,执行完毕后默认返回 None
  2. 立即终止函数:函数执行到 return 会立刻结束,后续代码永远不会执行
  3. 支持多返回值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

开发建议:尽量减少全局变量的使用,优先通过「参数传入 + 返回值传出」的方式在函数间传递数据,避免副作用和维护困难。


💡 本章小结

  1. 函数通过 def 关键字定义,遵循「先定义、后调用」的规则,无 return 默认返回 None
  2. 函数按参数与返回值分为四类,日常开发最常用「有参有返回值」形式
  3. 参数体系:位置参数、关键字参数、缺省参数、不定长参数(*args/**kwargs),需严格遵守定义顺序
  4. lambda 匿名函数适合简单单行逻辑,常作为高阶函数的参数
  5. 函数支持多返回值,本质是返回元组,可通过解包接收
  6. 变量作用域:局部变量仅函数内可用,修改全局变量需 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 模式选择建议
  1. 纯文本内容(.txt、.py、.json 等)使用 r/w/a,必须指定 encoding
  2. 非文本文件(图片、音频、exe、压缩包)使用 rb/wb/ab,禁止指定 encoding
  3. 不确定文件是否存在、且不想覆盖原有内容时,优先用 a 追加模式,慎用 w 模式
  4. 绝大多数场景不需要 + 混合模式,分开读写逻辑更清晰,不易出错

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")

💡 本章小结

  1. 操作流程:文件操作遵循「打开 → 读写 → 关闭」三步,所有场景优先使用 with 语句自动管理资源
  2. 模式选择:文本文件用 r/w/a 并指定 utf-8 编码;二进制文件用 rb/wb/ab
  3. 读写原则:小文件可一次性读取,大文件必须逐行或分块读取,避免内存溢出
  4. 异常处理:IO 操作不确定性高,生产环境必须捕获 FileNotFoundErrorPermissionError 等常见异常
  5. 路径管理:使用 os.path.join() 处理路径,保证跨平台兼容性
  6. 易错点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("程序正常继续运行")

执行流程

  1. func_a 触发除零异常,自身没有捕获,向上传递给调用者 func_b
  2. func_b 也没有捕获异常,继续向上传递给调用者 main
  3. main 函数中的 try-except 匹配并捕获异常,执行处理逻辑
  4. 异常处理完成,程序继续向下执行,不会崩溃
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}")

优势:可以按业务类型精准捕获和处理不同错误,代码语义更清晰,便于分层处理业务异常与系统异常。


💡 本章小结

  1. 异常本质:运行时的非正常状态,默认会终止程序;语法错误不属于异常,在运行前就会报错。
  2. 完整语法try 存放风险代码,except 捕获并处理异常,else 无异常时执行,finally 无论如何都执行,用于释放资源。
  3. 捕获原则:优先精准捕获具体异常类型,宽泛异常放最后兜底;禁止盲目捕获所有异常,避免掩盖真实问题。
  4. 异常传递:异常会逐层向上冒泡,直到被捕获;工程中通常在最外层统一捕获处理,底层只负责抛出。
  5. 主动抛出:使用 raise 主动触发异常,常用于参数校验、业务错误提示。
  6. 自定义异常:继承 Exception 实现业务异常,用于区分业务错误与系统错误,提升代码可维护性。

九、模块与包

随着项目代码量增长,将所有逻辑写在单个 .py 文件中会导致代码臃肿、难以维护。Python 通过「模块」与「包」实现代码的拆分与组织:模块是独立的 .py 文件,包是管理多个模块的文件夹,二者共同支撑了 Python 的模块化编程,实现代码复用、命名空间隔离与分层管理。


9.1 模块(Module)

9.1.1 什么是模块

一个独立的 .py 代码文件就是一个模块。模块内部可以定义函数、类、变量,也可以编写可执行代码,是 Python 代码组织的最小单元。

使用模块的核心优势:

  • 代码复用:通用功能封装为模块后,可在多个项目/文件中导入使用,避免重复开发
  • 命名隔离:不同模块可以定义同名的函数、变量,通过模块名区分,不会产生命名冲突
  • 便于维护:按功能拆分文件,代码结构清晰,方便定位问题和迭代修改

Python 中的模块分为三类:

  1. 标准库模块:Python 官方内置,开箱即用,如 timeosrandom
  2. 第三方模块:社区开发者贡献,需通过 pip 安装,如 requestspandas
  3. 自定义模块:开发者自己编写的业务 .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 模块导入的核心特性
  1. 单次加载:同一个模块在程序中无论被导入多少次,都只会在第一次导入时执行一次顶层代码,后续导入直接复用内存中已加载的模块对象,不会重复执行。
  2. 独立命名空间:每个模块拥有独立的作用域,内部变量、函数不会污染其他模块,通过模块名访问可精准区分。

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 会在包被导入时自动执行,主要有两个用途:

  1. 包初始化:编写包的全局初始化代码、预加载依赖、定义包级变量
  2. 简化导入路径:可以在 __init__.py 中提前导入核心成员,让外部调用更简洁
    例如在 my_package/__init__.py 中写入:
    from .module_a import func_a
    
    外部就可以直接通过 from my_package import func_a 调用,无需书写完整的模块路径。

9.3 自定义模块与包的注意事项

9.3.1 命名规范
  1. 模块名、包名必须符合标识符规则:由字母、数字、下划线组成,不能以数字开头
  2. 禁止与标准库重名:不要给自己的文件命名为 time.pyos.pyrandom.py 等,否则会覆盖 Python 内置标准库,导致程序报错
  3. 推荐使用全小写蛇形命名法,单词间用下划线分隔,如 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 * 时,只能拿到 addpublic_num_private_func 不会被导入。

作用:控制模块对外暴露的接口,隐藏内部实现细节,降低调用方误用内部函数的风险。

9.3.4 模块搜索路径

导入模块时,Python 会按照 sys.path 列表中的路径依次查找对应的模块文件,找不到则抛出 ModuleNotFoundError

sys.path 默认包含三类路径:

  1. 当前执行脚本所在的目录
  2. Python 标准库目录
  3. 第三方库安装目录(site-packages

初学者常见坑:导入自定义模块时报错,大概率是模块文件不在当前工作目录,也不在 sys.path 中。解决方案是按照标准项目结构组织代码,或将项目根目录加入 sys.path


💡 本章小结

  1. 模块:单个 .py 文件,是代码拆分的最小单元;支持 5 种导入方式,优先使用精准导入,避免 import *
  2. :包含 __init__.py 的文件夹,用于分层管理模块,通过 . 表示层级关系
  3. __name__:直接运行模块时值为 __main__,常用于编写模块自测代码,导入时不执行测试逻辑
  4. __all__:控制 import * 的导入范围,用于隐藏内部实现、规范对外接口
  5. 命名注意:自定义模块禁止与标准库重名,遵循蛇形命名规范

十、总结

Python 是一门优雅且强大的语言,它的设计哲学强调可读性和简洁性。以上就是Python基础阶段的核心知识点,学习编程最重要的是多动手练习。如果你已经掌握了本系列的全部内容,恭喜你——你已经具备了 Python 入门开发者的核心能力。接下来,选择一个你感兴趣的方向深入下去,把基础转化为真正的项目经验。
后续进阶可以继续学习:面向对象、高级函数、装饰器、迭代器生成器、正则表达式、数据库操作、Web开发框架等内容。
祝你在 Python 的世界里越走越远,享受编程带来的乐趣与成就感!🚀

如果本文对你有帮助,欢迎点赞、收藏、关注,后续会持续更新Python进阶教程。

更多推荐