# Python 函数、异常、循环与迭代:从基础语法到执行模型的系统理解
一、问题
1.1 由四个基础问题引发的学习
- 什么是函数?Python 函数参数有哪几种类型?
- 简述 Python 异常处理机制
try-except-else-finally。 - 简述
for循环与while循环的使用场景。 - 什么是迭代器?可迭代对象是什么?
这四个问题看起来分别属于函数、异常、循环、迭代器四个知识点,但它们实际上共同构成了 Python 核心基础中非常重要的一条主线:
如何把代码封装成可复用逻辑,如何控制程序执行流程,如何处理异常情况,如何用统一的迭代协议遍历数据。
因此,本文不只是为了回答这四个问题,而是以它们为入口,系统梳理 Python 中函数、异常、循环和迭代机制背后的核心概念。
1.2 四个问题的简要回答
问题 1:什么是函数?Python 函数参数有哪几种类型?
函数是封装一段独立功能的代码块,用来提高代码复用性、可读性和可维护性。
Python 常见参数类型包括:
| 参数类型 | 示例 | 说明 |
|---|---|---|
| 位置参数 | func(a, b) |
按传入位置匹配参数 |
| 关键字参数 | func(name="Tom") |
按参数名传值 |
| 默认参数 | def func(a=1) |
未传值时使用默认值 |
| 可变位置参数 | *args |
接收多个位置参数,形成 tuple |
| 可变关键字参数 | **kwargs |
接收多个关键字参数,形成 dict |
| 仅位置参数 | / |
只能按位置传入 |
| 仅关键字参数 | * 后的参数 |
只能按关键字传入 |
问题 2:简述 Python 异常处理机制 try-except-else-finally
| 结构 | 作用 |
|---|---|
try |
包裹可能发生异常的代码 |
except |
捕获并处理指定异常 |
else |
当 try 中没有异常时执行 |
finally |
无论是否发生异常都会执行,常用于资源释放 |
问题 3:简述 for 循环与 while 循环的使用场景
for循环:适合遍历可迭代对象,或者循环次数明确的场景。while循环:适合循环次数不确定,需要根据条件持续执行的场景。
问题 4:什么是迭代器?可迭代对象是什么?
- 可迭代对象:实现了
__iter__()方法,可以被for循环遍历的对象。 - 迭代器:同时实现了
__iter__()和__next__()方法,可以逐个返回数据,直到抛出StopIteration。
简单理解:
可迭代对象是“可以被遍历的对象”,迭代器是“真正负责逐个取值的对象”。
二、思考:围绕这四个问题还能追问什么?
2.1 为什么需要函数?
如果没有函数,代码很容易变成从上到下一大段重复逻辑:
price1 = 100
count1 = 2
total1 = price1 * count1
price2 = 80
count2 = 3
total2 = price2 * count2
当逻辑越来越复杂时,重复代码会带来很多问题:
- 修改困难:同样逻辑需要改很多处。
- 可读性差:代码意图不清晰。
- 复用性差:相同功能无法方便复用。
- 测试困难:无法单独验证某块逻辑。
函数的核心意义就是把一段逻辑抽象成一个有名字的功能单元。
def calculate_total(price, count):
return price * count
print(calculate_total(100, 2))
print(calculate_total(80, 3))
一个好的函数应该尽量做到:
- 功能单一。
- 命名清晰。
- 输入明确。
- 输出明确。
- 副作用可控。
2.2 函数参数为什么有那么多种?
函数参数的本质是函数与外部交换数据的接口。不同参数形式,是为了适应不同的调用需求。
例如:
- 参数顺序固定时,可以使用位置参数。
- 参数较多、希望提高可读性时,可以使用关键字参数。
- 某些参数有常用值时,可以使用默认参数。
- 参数数量不确定时,可以使用
*args或**kwargs。 - 希望限制调用方式时,可以使用仅位置参数或仅关键字参数。
参数设计得好,函数就更容易理解和使用;参数设计混乱,函数调用就会变得难以维护。
2.3 为什么异常处理不能简单地写一个大而全的 except?
很多初学者会写:
try:
result = 10 / x
except Exception:
print("出错了")
这样虽然可以捕获异常,但问题是:
- 不知道具体发生了什么错误。
- 可能掩盖真正的 bug。
- 不利于定位问题。
- 调用者无法根据异常类型做不同处理。
更好的方式是捕获明确异常:
try:
result = 10 / x
except ZeroDivisionError:
print("除数不能为 0")
except TypeError:
print("参数类型不正确")
异常处理不是为了“让程序不报错”,而是为了让程序在异常场景下仍然有清晰、可控的行为。
2.4 for 循环为什么可以遍历很多不同对象?
for 不只可以遍历列表,还可以遍历字符串、元组、字典、集合、文件对象、生成器等。
for char in "Python":
print(char)
for key in {"name": "Tom", "age": 18}:
print(key)
这是因为这些对象都遵守了 Python 的迭代协议。
也就是说,for 循环的背后并不是只认识 list,而是认识“可迭代对象”。
2.5 迭代器为什么强调“惰性取值”?
迭代器不会一次性把所有数据都放到内存中,而是需要一个取一个。
这对处理大数据、文件流、网络流非常重要。
with open("data.txt", "r", encoding="utf-8") as f:
for line in f:
print(line)
文件可能很大,但 for line in f 不需要一次性读取整个文件,而是逐行读取。这就是迭代器惰性取值的价值。
三、函数基础:从代码封装到抽象能力
3.1 函数是什么?
函数是一段具有独立功能的代码块。
基本语法:
def 函数名(参数列表):
函数体
return 返回值
示例:
def add(a, b):
return a + b
result = add(1, 2)
print(result) # 3
函数通常包含三部分:
| 部分 | 说明 |
|---|---|
| 函数名 | 表达函数功能 |
| 参数 | 外部传入函数的数据 |
| 返回值 | 函数执行后的结果 |
3.2 函数的意义
函数的核心价值不只是“少写重复代码”,更重要的是建立抽象。
例如:
def is_adult(age):
return age >= 18
这个函数把 age >= 18 这个判断封装成了 is_adult。调用者不需要关心内部判断细节,只需要知道它能判断是否成年。
函数带来的好处:
- 复用:相同逻辑可以多次调用。
- 抽象:隐藏实现细节,暴露清晰意图。
- 拆分复杂度:把大问题拆成小问题。
- 便于测试:可以单独测试某个函数。
- 便于维护:修改逻辑时只改函数内部。
3.3 函数调用过程
调用函数时,程序会:
- 根据函数名找到函数对象。
- 创建函数调用栈帧。
- 将实参绑定给形参。
- 执行函数体。
- 遇到
return返回结果。 - 函数执行结束后释放局部变量引用。
def greet(name):
message = f"Hello, {name}"
return message
text = greet("Tom")
print(text)
其中 name 和 message 是函数内部的局部变量。
3.4 return 的作用
return 用来结束函数并返回结果。
def square(x):
return x * x
print(square(3)) # 9
如果函数没有显式写 return,默认返回 None。
def say_hello():
print("hello")
result = say_hello()
print(result) # None
函数可以返回多个值:
def get_point():
return 10, 20
x, y = get_point()
print(x, y)
严格来说,Python 函数返回多个值时,本质上是返回了一个元组。
def get_point():
return 10, 20
result = get_point()
print(result) # (10, 20)
print(type(result)) # <class 'tuple'>
四、Python 函数参数系统
4.1 形参与实参
- 形参:函数定义时写在括号里的参数。
- 实参:函数调用时实际传入的值。
def add(a, b): # a、b 是形参
return a + b
add(1, 2) # 1、2 是实参
4.2 位置参数
位置参数按照传入顺序与形参匹配。
def introduce(name, age):
print(f"name={name}, age={age}")
introduce("Tom", 18)
这里 "Tom" 传给 name,18 传给 age。
位置参数的优点是简洁,缺点是参数较多时可读性下降。
4.3 关键字参数
关键字参数通过参数名指定传值。
def introduce(name, age):
print(f"name={name}, age={age}")
introduce(age=18, name="Tom")
关键字参数的优点是可读性更强,也不依赖参数顺序。
4.4 默认参数
默认参数是在函数定义时为参数指定默认值。
def connect(host, port=3306):
print(f"connect to {host}:{port}")
connect("localhost")
connect("localhost", 3307)
默认参数适合表达“常用值”。
需要注意:默认参数只在函数定义时计算一次。
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b']
这是因为 items=[] 这个列表在函数定义时就创建好了,之后多次调用会复用同一个列表。
推荐写法:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
4.5 可变位置参数:*args
*args 用来接收不定数量的位置参数,函数内部得到的是一个元组。
def total(*args):
print(args)
return sum(args)
print(total(1, 2, 3))
输出:
(1, 2, 3)
6
args 只是习惯命名,本质上关键是前面的 *。
def total(*numbers):
return sum(numbers)
4.6 可变关键字参数:**kwargs
**kwargs 用来接收不定数量的关键字参数,函数内部得到的是一个字典。
def show_info(**kwargs):
print(kwargs)
show_info(name="Tom", age=18)
输出:
{'name': 'Tom', 'age': 18}
kwargs 也是习惯命名,本质上关键是前面的 **。
4.7 参数解包
调用函数时,也可以使用 * 和 ** 进行参数解包。
def add(a, b, c):
return a + b + c
nums = [1, 2, 3]
print(add(*nums))
字典解包:
def introduce(name, age):
print(f"name={name}, age={age}")
info = {"name": "Tom", "age": 18}
introduce(**info)
4.8 仅位置参数:/
Python 3 支持使用 / 表示它左边的参数只能按位置传入。
def func(a, b, /, c):
print(a, b, c)
func(1, 2, c=3)
下面这种调用会报错:
# TypeError
# func(a=1, b=2, c=3)
仅位置参数常见于一些内置函数或希望隐藏参数名的 API 设计中。
4.9 仅关键字参数:*
* 后面的参数必须通过关键字传入。
def create_user(name, *, age, city):
print(name, age, city)
create_user("Tom", age=18, city="Beijing")
下面这种调用会报错:
# TypeError
# create_user("Tom", 18, "Beijing")
仅关键字参数适合参数较多、希望调用更清晰的场景。
4.10 参数顺序规则
函数定义中常见参数顺序如下:
def func(pos1, pos2, /, normal, default=0, *args, keyword_only, **kwargs):
pass
从左到右大致是:
- 仅位置参数
- 普通位置或关键字参数
- 默认参数
- 可变位置参数
*args - 仅关键字参数
- 可变关键字参数
**kwargs
常见普通写法:
def func(a, b=1, *args, c, **kwargs):
pass
五、作用域、局部变量与闭包
5.1 局部变量与全局变量
函数内部定义的变量是局部变量,只在函数内部可见。
def func():
x = 10
print(x)
func()
# print(x) # NameError
函数外部定义的变量是全局变量。
x = 10
def func():
print(x)
func()
5.2 LEGB 规则
Python 查找变量时遵循 LEGB 规则:
| 层级 | 含义 |
|---|---|
| L | Local,局部作用域 |
| E | Enclosing,外层嵌套函数作用域 |
| G | Global,全局作用域 |
| B | Built-in,内置作用域 |
示例:
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x)
inner()
outer() # local
5.3 global 与 nonlocal
global 用于在函数内部声明使用全局变量。
count = 0
def increase():
global count
count += 1
increase()
print(count) # 1
nonlocal 用于在嵌套函数中修改外层函数的变量。
def outer():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
counter = outer()
print(counter()) # 1
print(counter()) # 2
5.4 闭包
闭包是指内部函数引用了外部函数的变量,并且外部函数执行结束后,这些变量仍然被内部函数保存。
def make_multiplier(n):
def multiplier(x):
return x * n
return multiplier
double = make_multiplier(2)
print(double(10)) # 20
这里 multiplier 记住了外部变量 n,这就是闭包。
闭包常用于:
- 保存状态
- 构造函数工厂
- 装饰器
- 延迟执行逻辑
六、函数是一等对象
6.1 函数可以赋值给变量
在 Python 中,函数也是对象。
def say_hello():
print("hello")
func = say_hello
func()
6.2 函数可以作为参数传递
def apply(func, value):
return func(value)
def square(x):
return x * x
print(apply(square, 3)) # 9
这也是很多高阶函数的基础。
6.3 函数可以作为返回值
def get_operation(op):
if op == "add":
def add(a, b):
return a + b
return add
operation = get_operation("add")
print(operation(1, 2))
函数能够作为值传递,是 Python 支持函数式编程风格的基础。
6.4 匿名函数 lambda
lambda 用于创建简单匿名函数。
square = lambda x: x * x
print(square(3)) # 9
常见于需要传入简单函数的场景:
students = [
{"name": "Tom", "score": 90},
{"name": "Jerry", "score": 85},
]
students.sort(key=lambda item: item["score"])
print(students)
lambda 适合简单表达式,不适合写复杂逻辑。复杂逻辑应该使用普通函数。
七、异常处理机制
7.1 什么是异常?
异常是程序运行过程中出现的错误或非正常情况。
例如:
# ZeroDivisionError
# print(10 / 0)
# ValueError
# int("abc")
# KeyError
# {"name": "Tom"}["age"]
如果异常没有被处理,程序会终止并输出错误信息。
7.2 try-except 基本结构
try:
num = int(input("请输入数字:"))
print(10 / num)
except ValueError:
print("输入的不是合法数字")
except ZeroDivisionError:
print("除数不能为 0")
try 中放可能出错的代码,except 中放异常处理逻辑。
7.3 获取异常对象
try:
int("abc")
except ValueError as e:
print(f"发生异常:{e}")
异常对象中通常包含错误信息。
7.4 多个异常处理
可以分别处理不同异常:
try:
value = int("abc")
result = 10 / value
except ValueError:
print("转换失败")
except ZeroDivisionError:
print("除数不能为 0")
也可以把多个异常放在一个元组中:
try:
result = 10 / int("abc")
except (ValueError, ZeroDivisionError) as e:
print(f"处理异常:{e}")
7.5 else:没有异常时执行
else 会在 try 代码块没有发生异常时执行。
try:
num = int("10")
except ValueError:
print("转换失败")
else:
print("转换成功", num)
else 的好处是让“正常流程”与“异常处理”分离得更清晰。
7.6 finally:无论如何都会执行
finally 无论是否发生异常都会执行,常用于资源释放。
try:
f = open("data.txt", "r", encoding="utf-8")
content = f.read()
except FileNotFoundError:
print("文件不存在")
finally:
try:
f.close()
except NameError:
pass
不过实际开发中,文件操作更推荐使用 with 语句。
try:
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
print("文件不存在")
with 会自动管理资源释放,比手动 finally 更简洁安全。
7.7 主动抛出异常:raise
可以使用 raise 主动抛出异常。
def divide(a, b):
if b == 0:
raise ValueError("除数不能为 0")
return a / b
主动抛出异常适合在参数不合法、状态不满足要求时明确告知调用者。
7.8 自定义异常
可以通过继承 Exception 定义自己的异常类型。
class InvalidAgeError(Exception):
pass
def set_age(age):
if age < 0:
raise InvalidAgeError("年龄不能为负数")
return age
自定义异常适合表达业务语义,例如订单状态错误、权限不足、余额不足等。
7.9 异常处理的原则
- 只捕获自己能处理的异常。
- 尽量捕获具体异常,而不是笼统捕获所有异常。
- 不要静默吞掉异常。
- 异常信息要有助于定位问题。
- 资源释放优先考虑
with。 - 异常用于异常流程,不应该滥用于普通条件判断。
不推荐:
try:
do_something()
except Exception:
pass
这种写法会隐藏问题,让程序变得难以调试。
八、循环控制:for 与 while
8.1 for 循环
for 循环用于遍历可迭代对象。
for item in [1, 2, 3]:
print(item)
常见可遍历对象:
listtuplestrdictsetrange- 文件对象
- 生成器
- 迭代器
8.2 遍历字典
遍历字典时,默认遍历 key。
student = {"name": "Tom", "age": 18}
for key in student:
print(key)
如果要同时获取 key 和 value,可以使用 items()。
for key, value in student.items():
print(key, value)
8.3 range() 与循环次数
当循环次数明确时,常使用 range()。
for i in range(5):
print(i)
range(5) 生成的是 0 到 4。
print(list(range(1, 10, 2))) # [1, 3, 5, 7, 9]
8.4 while 循环
while 循环在条件为真时持续执行。
count = 0
while count < 3:
print(count)
count += 1
while 更适合循环次数不确定的场景,例如:
- 等待某个条件满足
- 用户持续输入
- 任务重试
- 事件循环
示例:
password = ""
while password != "123456":
password = input("请输入密码:")
print("验证通过")
8.5 break 与 continue
break 用于立即结束整个循环。
for i in range(10):
if i == 3:
break
print(i)
continue 用于跳过本次循环,进入下一次循环。
for i in range(5):
if i == 2:
continue
print(i)
8.6 循环中的 else
Python 的循环也可以搭配 else。
for/while 的 else 会在循环正常结束时执行;如果循环被 break 中断,则不会执行。
for i in range(5):
if i == 10:
print("found")
break
else:
print("not found")
这个特性常用于搜索场景。
8.7 如何选择 for 和 while
| 场景 | 推荐 |
|---|---|
| 遍历可迭代对象 | for |
| 循环次数明确 | for + range() |
| 循环次数不确定 | while |
| 根据条件持续执行 | while |
| 读取文件每一行 | for |
| 需要手动控制退出条件 | while |
如果能自然表达为“遍历某个对象”,优先考虑 for;如果只能表达为“只要条件成立就继续”,使用 while。
九、可迭代对象与迭代器
9.1 可迭代对象是什么?
可迭代对象是可以被 for 遍历的对象。
从协议角度看,一个对象只要实现了 __iter__() 方法,就可以被称为可迭代对象。
nums = [1, 2, 3]
print(iter(nums))
常见可迭代对象包括:
- 列表
- 元组
- 字符串
- 字典
- 集合
range- 文件对象
- 生成器
可以使用 collections.abc.Iterable 判断对象是否可迭代。
from collections.abc import Iterable
print(isinstance([1, 2, 3], Iterable)) # True
print(isinstance(123, Iterable)) # False
9.2 迭代器是什么?
迭代器是可以逐个返回元素的对象。
从协议角度看,迭代器需要实现:
__iter__():返回迭代器自身。__next__():返回下一个元素;没有元素时抛出StopIteration。
nums = [1, 2, 3]
it = iter(nums)
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# print(next(it)) # StopIteration
可以使用 collections.abc.Iterator 判断对象是否是迭代器。
from collections.abc import Iterable, Iterator
nums = [1, 2, 3]
it = iter(nums)
print(isinstance(nums, Iterable)) # True
print(isinstance(nums, Iterator)) # False
print(isinstance(it, Iterator)) # True
9.3 可迭代对象与迭代器的区别
| 对比项 | 可迭代对象 | 迭代器 |
|---|---|---|
| 核心方法 | __iter__() |
__iter__() 和 __next__() |
能否用 for 遍历 |
可以 | 可以 |
能否直接 next() |
通常不可以 | 可以 |
| 是否保存遍历状态 | 通常不保存 | 保存当前遍历状态 |
| 示例 | list、tuple、str |
iter(list)、生成器、文件对象 |
列表是可迭代对象,但不是迭代器:
nums = [1, 2, 3]
# TypeError
# next(nums)
it = iter(nums)
print(next(it))
9.4 for 循环的本质
下面的 for 循环:
for item in [1, 2, 3]:
print(item)
本质上类似于:
it = iter([1, 2, 3])
while True:
try:
item = next(it)
except StopIteration:
break
print(item)
这说明:
for循环背后依赖的是迭代协议。
9.5 自定义迭代器
可以自己实现一个迭代器。
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
for num in CountDown(3):
print(num)
输出:
3
2
1
9.6 生成器:更简单的迭代器
使用 yield 的函数叫生成器函数,调用后返回生成器对象。
def count_down(start):
while start > 0:
yield start
start -= 1
for num in count_down(3):
print(num)
生成器也是迭代器,具有惰性取值的特点。
g = count_down(3)
print(next(g)) # 3
print(next(g)) # 2
print(next(g)) # 1
9.7 生成器表达式
生成器表达式类似列表推导式,但不会一次性生成完整列表。
squares = (x * x for x in range(5))
print(next(squares)) # 0
print(next(squares)) # 1
对比列表推导式:
list_squares = [x * x for x in range(5)]
gen_squares = (x * x for x in range(5))
- 列表推导式:立即生成完整列表,占用内存更多。
- 生成器表达式:按需生成元素,占用内存更少。
十、函数、循环、异常、迭代之间的联系
10.1 for 循环与迭代器
for 循环依赖可迭代对象和迭代器协议。
for line in open("data.txt", encoding="utf-8"):
print(line)
这里文件对象可以被 for 遍历,是因为它支持迭代协议。
10.2 迭代器与异常
迭代器在没有更多元素时,会抛出 StopIteration。
it = iter([1])
print(next(it))
# print(next(it)) # StopIteration
for 循环内部会自动捕获 StopIteration,并结束循环。
所以异常不只用于错误处理,也参与了 Python 某些语言机制的实现。
10.3 函数与生成器
普通函数使用 return 返回结果,生成器函数使用 yield 逐个产出结果。
普通函数:
def get_nums():
return [1, 2, 3]
生成器函数:
def gen_nums():
yield 1
yield 2
yield 3
生成器适合处理大量数据或流式数据。
10.4 异常与资源管理
异常处理中的 finally 常用于释放资源,而 with 语句是更常用的资源管理方式。
with open("data.txt", "r", encoding="utf-8") as f:
for line in f:
print(line)
这段代码同时涉及:
- 文件对象
- 上下文管理器
for循环- 迭代协议
- 自动资源释放
这些知识点不是孤立的,而是在实际代码中经常组合出现。
十一、常见易错点与知识总结
- 函数没有显式
return时默认返回None。 - 默认参数只在函数定义时计算一次,不要直接使用可变对象作为默认值。
*args接收多个位置参数,结果是 tuple。**kwargs接收多个关键字参数,结果是 dict。- 函数内部赋值默认创建局部变量,修改全局变量需要
global。 - 闭包可以保存外层函数的变量。
- 异常处理不要随意写
except Exception: pass。 finally无论是否异常都会执行,常用于资源释放。- 文件操作优先使用
with,减少手动关闭资源的错误。 for循环本质上依赖迭代协议。- 可迭代对象不一定是迭代器。
- 迭代器会保存遍历状态,遍历过的元素不会自动重来。
- 生成器是迭代器,具有惰性取值特点。
- 循环中的
else只有在没有被break中断时才会执行。 while循环要注意退出条件,避免死循环。
更多推荐

所有评论(0)