函数是组织代码的基本单元。会写 def 只是入门,真正拉开差距的是对参数机制和作用域规则的理解:默认参数、可变参数、关键字参数、闭包、global 与 nonlocal……这些细节决定了你的函数是"好用"还是"处处是坑"。
1. 参数传递的本质
Python 的参数传递是"传对象引用"。不可变对象(数字、字符串)在函数内修改不影响外部;可变对象(列表、字典)在函数内修改会穿透到外部:
def change(a, lst):
a = a + 1 # 重新绑定,外部不受影响
lst.append(99) # 原地修改,外部可见
x, my_list = 10, [1, 2]
change(x, my_list)
print(x) # 10
print(my_list) # [1, 2, 99]
2. 位置参数与关键字参数
调用函数时可以按位置传参,也可以按名字传参,两者可以混用(位置在前、关键字在后):
def greet(name, greeting):
return f"{greeting},{name}!"
print(greet("小明", "你好")) # 位置参数
print(greet(greeting="早上好", name="小红")) # 关键字参数,顺序无关
3. 默认参数与可变参数
默认参数让函数更灵活,但有一个经典陷阱:默认值只在定义时求值一次,所以不要用可变对象做默认值:
# 错误示范:默认列表会被共享
def bad(item, bag=[]):
bag.append(item)
return bag
print(bad("a")) # ['a']
print(bad("b")) # ['a', 'b'] ← 上次的结果还在!
# 正确写法:用 None 占位
def good(item, bag=None):
if bag is None:
bag = []
bag.append(item)
return bag
*args 收集任意多个位置参数为元组,**kwargs 收集任意多个关键字参数为字典:
def collect(*args, **kwargs):
print("位置参数:", args)
print("关键字参数:", kwargs)
collect(1, 2, 3, name="Python", year=1991)
# 位置参数: (1, 2, 3); 关键字参数: {'name': 'Python', 'year': 1991}
4. 返回值与多返回值
函数用 return 返回结果,可以返回多个值(实际是打包成元组)。没有 return 时返回 None:
def divide(a, b):
return a // b, a % b # 商和余数
quotient, remainder = divide(17, 5)
print(quotient, remainder) # 3 2
5. 作用域:LEGB 规则
名字查找顺序是 Local(局部)→ Enclosing(外层闭包)→ Global(全局)→ Built-in(内置)。函数内默认只能读全局变量,要修改必须声明 global:
count = 0 # 全局变量
def increment():
global count # 声明后才可以修改全局
count += 1
increment()
increment()
print(count) # 2
嵌套函数中,修改外层函数的局部变量要用 nonlocal,这也是闭包的核心机制:
def counter():
n = 0
def add():
nonlocal n
n += 1
return n
return add
c = counter()
print(c())
print(c())
6. lambda 与高阶函数
lambda 是单表达式匿名函数,常配合 sorted、map、filter 使用:
students = [("Ada", 92), ("Bob", 78), ("Cara", 85)]
# 按分数降序排序
students.sort(key=lambda s: s[1], reverse=True)
print(students) # [('Ada', 92), ('Cara', 85), ('Bob', 78)]
nums = [1, 2, 3, 4, 5]
print(list(filter(lambda x: x % 2 == 1, nums))) # [1, 3, 5]
print(list(map(lambda x: x * 10, nums))) # [10, 20, 30, 40, 50]
💡 学习建议:亲手把"默认参数陷阱"的坏例子和好例子各跑一遍,再试着写一个带 *args 和 **kwargs 的日志函数。理解传参机制,是看懂一切框架源码的前提。