简介:本文系统梳理 Python 中 for 循环与自定义函数两大核心机制:从迭代协议、循环控制技巧,到函数定义、参数设计、嵌套结构与变量作用域(global/nonlocal),配合典型代码示例与常见陷阱剖析,帮助读者建立清晰的心智模型,写出更健壮、更易维护的 Python 代码。
对初学者来说,for 循环和自定义函数往往是两个独立的知识点,但在真实工程中,它们是同一件事的两面:for 循环是对“重复流程”的抽象,函数是对“可复用逻辑”的抽象。当一段循环逻辑需要被多处使用时,把它封装成函数;当一个函数需要逐条处理数据时,内部往往依靠循环实现。理解二者的协同方式,是写出结构清晰代码的前提。
许多从 C/Java 转过来的开发者会下意识地把 for 当成“计数循环”,但 Python 的 for 是迭代循环:它的执行依赖迭代器协议。表达式 for x in obj 的底层过程大致等价于:
it = iter(obj) # 获取迭代器while True:try:x = next(it) # 逐个取元素# 循环体except StopIteration:break # 取完自动结束
这意味着:任何实现了迭代协议的对象——列表、字符串、字典、集合、文件对象、生成器——都可以直接放入 for 循环,而不需要手动维护下标。
# 同时拿到下标与元素for i, ch in enumerate('abc'):print(i, ch)# 并行遍历多个序列for name, score in zip(['amy', 'bob'], [90, 85]):print(name, score)# for-else:循环正常结束(未被 break 打断)时执行 elsefor n in [2, 4, 6]:if n % 2 == 1:breakelse:print('全部为偶数')
nums = [1, 2, 3, 4]for n in nums:if n % 2 == 0:nums.remove(n) # 跳元素,结果不可预期
正确做法是遍历副本或用推导式重建列表:
nums = [n for n in nums if n % 2 != 0]
def 函数名(参数列表):函数体return 返回值 # return 可省略,省略时返回 None
Python 的函数参数无需声明类型,同一个函数可以自然地处理不同数据类型,只要这些类型支持函数体内的操作:
def my_sum(a, b):return a + bprint(my_sum(3, 5)) # 数值相加 → 8print(my_sum([1, 2], [3])) # 列表拼接 → [1, 2, 3]
这背后是“鸭子类型”思想:函数关心的是对象“能做什么”(支持 + 运算),而不是它“是什么类型”。灵活的同时也带来风险——传入不支持该操作的类型会在运行时报错,因此对输入有严格要求时,应在函数内显式校验。
def func(param=0):print(param)func() # 使用默认值 → 0func(5) # 传参覆盖 → 5
需要警惕的是可变对象作默认值的陷阱:默认值在函数定义时只求值一次,会被多次调用共享。
def append_to(item, lst=[]): # 危险写法lst.append(item)return lstappend_to(1) # [1]append_to(2) # [1, 2],而非 [2]
规范写法是默认值用 None,函数体内再创建新对象:
def append_to(item, lst=None):if lst is None:lst = []lst.append(item)return lst
在函数内部再定义函数,形成嵌套结构,主要有两类典型用途。
子函数只在父函数内部可见,外部无法直接调用,适合封装敏感或易变的细节,例如数据库连接配置:
def connect_db():def get_db_config():# 外部无法直接调用,配置细节被隐藏return {'host': 'localhost', 'port': 3306}config = get_db_config()# 模拟连接数据库...return 'Connected'
这样即使配置读取逻辑将来变化,只要 connect_db 的对外行为不变,调用方代码无需改动。
把输入校验放在外层,把递归等核心计算放在内层,避免每次递归都重复执行校验,减少无效开销:
def factorial(n):def inner_factorial(x):if x == 1:return 1return x * inner_factorial(x - 1)# 校验只执行一次if not isinstance(n, int) or n < 1:raise ValueError('Invalid input')return inner_factorial(n)
此外,嵌套函数还是理解闭包与装饰器的基础:内层函数可以“记住”外层函数的变量,这正是后续进阶特性的基石。
Python 查找变量名时按 L → E → G → B 的顺序进行:
局部变量在函数执行期间存在,函数返回后即销毁;全局变量在整个模块中可读,但在函数内直接赋值只会创建同名局部变量,修改全局变量必须显式声明。
MIN_VALUE = 1MAX_VALUE = 10def validation_check(value):global MIN_VALUE # 声明操作的是全局变量MIN_VALUE = 2 # 真正修改全局变量if value > MAX_VALUE:raise ValueError('Validation failed')validation_check(5)print(MIN_VALUE) # 2
工程建议:global 应尽量少用。频繁修改全局变量会让函数行为依赖外部状态,增加排查难度;更推荐通过参数传入、返回值带出的方式传递数据。
def outer():x = 1def inner():nonlocal xx = 2 # 修改的是 outer 的 x,而非新建局部变量inner()print(x) # 2outer()
若不声明 nonlocal,x = 2 会在 inner 内创建新的局部变量,外层的 x 保持不变。global 与 nonlocal 的区别可概括为:global 跨越到模块层,nonlocal 指向最近的外层函数作用域。
count = 0def increase():count = count + 1 # 报错:右侧 count 被视为未初始化的局部变量
原因正是作用域规则:函数内一旦出现赋值,该名字在整个函数内都被视为局部变量。解决方式是加 global/nonlocal 声明,或改为参数传递。
yield 让函数变成惰性迭代器,可被 for 直接消费,适合处理大数据流,避免一次性占用大量内存。for 循环的核心是迭代协议——理解“可迭代对象被逐个取值”这一模型,就能解释 enumerate、zip、for-else 等所有衍生用法;自定义函数的核心是作用域与参数机制——掌握 LEGB 查找顺序、global/nonlocal 的适用场景、动态类型与默认值的陷阱,就能避免绝大多数隐蔽 bug。二者结合,配合“封装、校验前置、单一职责”的实践原则,足以支撑起结构清晰、易于维护的 Python 程序骨架。建议读者将文中示例逐一运行、故意制造错误并观察报错信息,在实践中把这些规则内化为直觉。