函数是组织代码的基本单元。会写 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 是单表达式匿名函数,常配合 sortedmapfilter 使用:

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 的日志函数。理解传参机制,是看懂一切框架源码的前提。