Python 函数

函数是组织代码的基本单位——把一段逻辑封装起来、起个名字、需要时调用,避免重复,提升可读性。Python 的函数非常灵活:支持默认参数、可变参数、关键字参数、多返回值、闭包、装饰器。这一章把这些全讲清楚。

1. 定义与调用

# 定义函数:def + 函数名 + (参数列表) + 冒号
def greet(name):
    """向某人打招呼(这是文档字符串 docstring)"""
    return f"Hello, {name}!"

# 调用函数
message = greet("小明")
print(message)        # Hello, 小明!

# 没有 return 的函数,默认返回 None
def say_hi(name):
    print(f"Hi, {name}")

result = say_hi("小红")   # 输出: Hi, 小红
print(result)             # None

# 用 help() 查看 docstring
help(greet)
# 输出:
# Help on function greet:
# greet(name)
#     向某人打招呼(这是文档字符串 docstring)

docstring(文档字符串)是 Python 函数的"自带说明书",用三引号紧贴 def 后面。养成写 docstring 的习惯,代码立刻提升一个档次——用 help(函数名) 就能看到。

2. 参数:位置、关键字、默认值

# 1. 位置参数:按顺序传
def add(a, b):
    return a + b
print(add(2, 3))           # 5

# 2. 关键字参数:按名字传,顺序随意
def greet(name, greeting):
    return f"{greeting}, {name}!"
print(greet(greeting="早上好", name="小明"))    # 早上好,小明!

# 3. 默认参数:有默认值,调用时可省略
def greet(name, greeting="你好"):
    return f"{greeting}, {name}!"
print(greet("小明"))           # 你好,小明!
print(greet("小红", "嗨"))     # 嗨,小红!

# ⚠ 默认参数陷阱:不要用可变对象(列表/字典)做默认值!
def buggy(item, lst=[]):      # ❌ 危险!所有调用共享同一个列表
    lst.append(item)
    return lst
print(buggy(1))    # [1]
print(buggy(2))    # [1, 2]   <- 不是 [2]!lst 被复用了

# ✅ 正确写法:用 None 做占位
def safe(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst
print(safe(1))     # [1]
print(safe(2))     # [2]   每次都是新列表

默认参数陷阱是 Python 经典面试题:可变对象(列表/字典)做默认值会在所有调用间共享。记住解法——用 None 占位,函数内创建

3. 可变参数 *args / **kwargs

# 可变参数:*args 收集多余位置参数成元组
def sum_all(*args):
    print(type(args))    # <class 'tuple'>
    return sum(args)

print(sum_all(1, 2, 3, 4, 5))   # 15

# **kwargs 收集多余关键字参数成字典
def show_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

show_info(name="小明", age=20, city="北京")
# 输出:
# name: 小明
# age: 20
# city: 北京

# 混合使用:位置参数 + *args + **kwargs
def make_profile(name, *hobbies, **details):
    print(f"姓名:{name}")
    print(f"爱好:{hobbies}")
    print(f"其他:{details}")

make_profile("小明", "读书", "编程", age=20, city="北京")
# 姓名:小明
# 爱好:('读书', '编程')
# 其他:{'age': 20, 'city': '北京'}

*args 收集多余的位置参数成元组,**kwargs 收集多余的关键字参数成字典。这种"万能参数"在写通用工具、装饰器、框架时极其常用。

4. 返回多个值

# Python 函数可以"返回多个值"(实际是返回元组)
def min_max_avg(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

low, high, avg = min_max_avg([3, 1, 4, 1, 5, 9, 2, 6])
print(f"最小:{low},最大:{high},平均:{avg:.2f}")
# 输出: 最小:1,最大:9,平均:3.88

本质是返回元组,调用方再解包。这是 Python 比 Java/C 优雅的地方(它们得定义结构体或对象)。

5. Lambda 匿名函数

# Lambda:匿名函数,一行写完的小函数
# 语法:lambda 参数: 表达式

# 等价于 def add(a, b): return a + b
add = lambda a, b: a + b
print(add(2, 3))         # 5

# Lambda 最常作为参数传给高阶函数
nums = [1, 2, 3, 4, 5]

# 配合 map:对每个元素做运算
squares = list(map(lambda x: x * x, nums))
print(squares)           # [1, 4, 9, 16, 25]

# 配合 filter:过滤元素
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)             # [2, 4]

# 配合 sort 的 key
students = [("小明", 85), ("小红", 92), ("小刚", 78)]
students.sort(key=lambda s: s[1])    # 按分数排序
print(students)
# [('小刚', 78), ('小明', 85), ('小红', 92)]

# 复杂逻辑还是用 def 更清晰
# Lambda 限制:只能写一个表达式,不能有语句(if/for/赋值)

Lambda 适合简短一次性的函数,常配合 map / filter / sort(key=) 使用。复杂逻辑还是用 def 更清晰——可读性更重要。

6. 作用域 LEGB

Python 查找变量名按 LEGB 顺序:Local(局部)→ Enclosing(嵌套)→ Global(全局)→ Built-in(内置)。

# 作用域 LEGB:Local → Enclosing → Global → Built-in
x = "全局变量"        # Global

def outer():
    y = "外层函数变量"  # Enclosing(闭包场景)

    def inner():
        z = "内层变量"  # Local
        print(z)         # 找 Local
        print(y)         # Local 没有,找 Enclosing
        print(x)         # 都没有,找 Global
        print(len)       # 都没有,找 Built-in(内置函数)

    inner()

outer()

# global 关键字:在函数内修改全局变量
counter = 0
def increment():
    global counter
    counter += 1

increment()
print(counter)          # 1

# nonlocal:修改外层(非全局)变量,用于闭包
def make_counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc

7. 闭包

闭包 = 内层函数 + 它引用的外层变量。它的核心价值是"记住"创建时的环境,常用于工厂函数、装饰器、回调。

# 闭包:函数 + 它引用的外层变量
# 经典应用:工厂函数、装饰器

def make_multiplier(factor):
    """返回一个把输入乘以 factor 的函数"""
    def multiply(n):
        return n * factor
    return multiply

# 创建不同的"乘法器"
double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))      # 10
print(triple(5))      # 15

# 每个闭包都"记住"了自己创建时的 factor
print(double(20))     # 40
print(triple(20))     # 60

8. 装饰器(Python 高级特性)

装饰器是"接收函数、返回新函数"的高阶函数,用 @装饰器名 语法糖应用。能在不动原函数代码的前提下,给它加功能(计时、日志、权限检查、缓存)。

# 装饰器:在不动原函数代码的前提下,给它加功能
# 语法:@decorator_name

import time

def timing(func):
    """测量函数执行时间的装饰器"""
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)        # 调用原函数
        elapsed = time.time() - start
        print(f"{func.__name__} 耗时 {elapsed:.4f} 秒")
        return result
    return wrapper

@timing
def slow_sum(n):
    return sum(range(n))

print(slow_sum(1000000))
# 输出:
# slow_sum 耗时 0.05xx 秒
# 499999500000

# @timing 等价于 slow_sum = timing(slow_sum)
# 装饰器本质就是"接收函数,返回新函数"的高阶函数

9. 类型注解(Python 3.5+)

Python 是动态类型,但类型注解可以让你写出"看起来像静态类型"的代码,IDE 能据此做智能补全和类型检查。大项目强烈推荐用。

# 类型注解(Python 3.5+):给参数和返回值标类型
# 注意:Python 不会强制检查,只是"提示"给 IDE 和工具用

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! ") * times

# 复杂类型用 typing 模块
from typing import List, Dict, Optional, Tuple

def process(items: List[int]) -> Dict[str, int]:
    return {"sum": sum(items), "count": len(items)}

def find_user(uid: int) -> Optional[Dict]:
    """返回字典或 None"""
    if uid == 1:
        return {"name": "小明"}
    return None

# 虽然不强制,但大项目强烈推荐写类型注解
# IDE(VS Code/PyCharm)能智能补全、提前发现类型错误

小结

函数是组织代码的核心。重点掌握:def + docstring位置/关键字/默认参数(避开可变默认值陷阱)、*args/**kwargs多返回值LambdaLEGB 作用域闭包装饰器类型注解。下一篇看模块与包——如何组织多个文件的项目。

← 上一篇 Python 条件与循环

下一篇 Python 模块与包

✈️💬