一、问题

1.1 由四个基础问题引发的学习

  1. 什么是函数?Python 函数参数有哪几种类型?
  2. 简述 Python 异常处理机制 try-except-else-finally
  3. 简述 for 循环与 while 循环的使用场景。
  4. 什么是迭代器?可迭代对象是什么?

这四个问题看起来分别属于函数、异常、循环、迭代器四个知识点,但它们实际上共同构成了 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))

一个好的函数应该尽量做到:

  1. 功能单一。
  2. 命名清晰。
  3. 输入明确。
  4. 输出明确。
  5. 副作用可控。

2.2 函数参数为什么有那么多种?

函数参数的本质是函数与外部交换数据的接口。不同参数形式,是为了适应不同的调用需求。

例如:

  • 参数顺序固定时,可以使用位置参数。
  • 参数较多、希望提高可读性时,可以使用关键字参数。
  • 某些参数有常用值时,可以使用默认参数。
  • 参数数量不确定时,可以使用 *args**kwargs
  • 希望限制调用方式时,可以使用仅位置参数或仅关键字参数。

参数设计得好,函数就更容易理解和使用;参数设计混乱,函数调用就会变得难以维护。

2.3 为什么异常处理不能简单地写一个大而全的 except

很多初学者会写:

try:
    result = 10 / x
except Exception:
    print("出错了")

这样虽然可以捕获异常,但问题是:

  1. 不知道具体发生了什么错误。
  2. 可能掩盖真正的 bug。
  3. 不利于定位问题。
  4. 调用者无法根据异常类型做不同处理。

更好的方式是捕获明确异常:

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。调用者不需要关心内部判断细节,只需要知道它能判断是否成年。

函数带来的好处:

  1. 复用:相同逻辑可以多次调用。
  2. 抽象:隐藏实现细节,暴露清晰意图。
  3. 拆分复杂度:把大问题拆成小问题。
  4. 便于测试:可以单独测试某个函数。
  5. 便于维护:修改逻辑时只改函数内部。

3.3 函数调用过程

调用函数时,程序会:

  1. 根据函数名找到函数对象。
  2. 创建函数调用栈帧。
  3. 将实参绑定给形参。
  4. 执行函数体。
  5. 遇到 return 返回结果。
  6. 函数执行结束后释放局部变量引用。
def greet(name):
    message = f"Hello, {name}"
    return message

text = greet("Tom")
print(text)

其中 namemessage 是函数内部的局部变量。

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" 传给 name18 传给 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

从左到右大致是:

  1. 仅位置参数
  2. 普通位置或关键字参数
  3. 默认参数
  4. 可变位置参数 *args
  5. 仅关键字参数
  6. 可变关键字参数 **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 globalnonlocal

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 异常处理的原则

  1. 只捕获自己能处理的异常。
  2. 尽量捕获具体异常,而不是笼统捕获所有异常。
  3. 不要静默吞掉异常。
  4. 异常信息要有助于定位问题。
  5. 资源释放优先考虑 with
  6. 异常用于异常流程,不应该滥用于普通条件判断。

不推荐:

try:
    do_something()
except Exception:
    pass

这种写法会隐藏问题,让程序变得难以调试。


八、循环控制:forwhile

8.1 for 循环

for 循环用于遍历可迭代对象。

for item in [1, 2, 3]:
    print(item)

常见可遍历对象:

  • list
  • tuple
  • str
  • dict
  • set
  • range
  • 文件对象
  • 生成器
  • 迭代器

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) 生成的是 04

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 breakcontinue

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/whileelse 会在循环正常结束时执行;如果循环被 break 中断,则不会执行。

for i in range(5):
    if i == 10:
        print("found")
        break
else:
    print("not found")

这个特性常用于搜索场景。

8.7 如何选择 forwhile

场景 推荐
遍历可迭代对象 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 迭代器是什么?

迭代器是可以逐个返回元素的对象。

从协议角度看,迭代器需要实现:

  1. __iter__():返回迭代器自身。
  2. __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() 通常不可以 可以
是否保存遍历状态 通常不保存 保存当前遍历状态
示例 listtuplestr 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 循环
  • 迭代协议
  • 自动资源释放

这些知识点不是孤立的,而是在实际代码中经常组合出现。


十一、常见易错点与知识总结

  1. 函数没有显式 return 时默认返回 None
  2. 默认参数只在函数定义时计算一次,不要直接使用可变对象作为默认值。
  3. *args 接收多个位置参数,结果是 tuple。
  4. **kwargs 接收多个关键字参数,结果是 dict。
  5. 函数内部赋值默认创建局部变量,修改全局变量需要 global
  6. 闭包可以保存外层函数的变量。
  7. 异常处理不要随意写 except Exception: pass
  8. finally 无论是否异常都会执行,常用于资源释放。
  9. 文件操作优先使用 with,减少手动关闭资源的错误。
  10. for 循环本质上依赖迭代协议。
  11. 可迭代对象不一定是迭代器。
  12. 迭代器会保存遍历状态,遍历过的元素不会自动重来。
  13. 生成器是迭代器,具有惰性取值特点。
  14. 循环中的 else 只有在没有被 break 中断时才会执行。
  15. while 循环要注意退出条件,避免死循环。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐