首页 / 知识库 / 0基础入门-阅读资料 / 0基础-python入门到精通

第 6 章 函数

6.1 函数定义与调用

函数是组织代码的基本单元,用 def 关键字定义:

def greet(name):
    """向指定的人打招呼"""
    return f"Hello, {name}!"

result = greet("Alice")
print(result)  # Hello, Alice!

函数也是对象,可以赋值给变量、作为参数传递:

say_hello = greet       # 函数赋值给变量
print(say_hello("Bob")) # Hello, Bob!
print(type(greet))      # <class 'function'>

pass 语句

定义空函数时用 pass 占位:

def not_implemented_yet():
    pass

# 或使用 ... (Ellipsis)
def also_not_implemented():
    ...

6.2 参数类型全解析

6.2.1 位置参数与关键字参数

def power(base, exponent):
    return base ** exponent

# 位置参数 — 按顺序传递
power(2, 10)           # 1024

# 关键字参数 — 按名字传递,顺序无所谓
power(exponent=10, base=2)  # 1024

# 混合使用 — 位置参数必须在前
power(2, exponent=10)  # 1024
# power(base=2, 10)    # SyntaxError!

6.2.2 默认参数

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

greet("Alice")            # "Hello, Alice!"
greet("Alice", "Hi")      # "Hi, Alice!"

可变默认参数陷阱

# 错误示范!
def append_to(item, target=[]):
    target.append(item)
    return target

print(append_to(1))  # [1]
print(append_to(2))  # [1, 2] ← 不是 [2]!默认列表在所有调用间共享

# 正确做法:用 None 作为哨兵值
def append_to(item, target=None):
    if target is None:
        target = []
    target.append(item)
    return target

print(append_to(1))  # [1]
print(append_to(2))  # [2] ✓

原因:默认参数在函数定义时求值一次,而非每次调用时。

6.2.3 可变参数 *args**kwargs

# *args — 接收任意数量的位置参数,打包为元组
def total(*numbers):
    return sum(numbers)

total(1, 2, 3)      # 6
total(1, 2, 3, 4, 5) # 15

# **kwargs — 接收任意数量的关键字参数,打包为字典
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="Beijing")

# 组合使用
def func(a, b, *args, **kwargs):
    print(f"a={a}, b={b}")
    print(f"args={args}")
    print(f"kwargs={kwargs}")

func(1, 2, 3, 4, x=5, y=6)
# a=1, b=2
# args=(3, 4)
# kwargs={'x': 5, 'y': 6}

解包传参

def add(a, b, c):
    return a + b + c

args = [1, 2, 3]
add(*args)              # 6 — 列表解包

kwargs = {"a": 1, "b": 2, "c": 3}
add(**kwargs)            # 6 — 字典解包

# 同时解包
add(*[1, 2], **{"c": 3}) # 6

6.2.4 仅限位置参数 / 与仅限关键字参数 *

Python 3.8+ 引入 /,用于强制某些参数只能按位置传递:

#                仅限位置    普通    仅限关键字
def func(pos_only, /, normal, *, kw_only):
    print(pos_only, normal, kw_only)

func(1, 2, kw_only=3)        # ✓
func(1, normal=2, kw_only=3) # ✓
# func(pos_only=1, 2, kw_only=3)  # TypeError! pos_only 不能按名字传
# func(1, 2, 3)                    # TypeError! kw_only 必须按名字传

为什么需要仅限位置参数?

# 参数名不重要时,避免调用者依赖参数名
def calculate(x, y, /, *, method="add"):
    if method == "add":
        return x + y
    return x * y

# x、y 只是占位名,将来可以改名而不影响调用者

完整的参数顺序

def func(pos_only, /, normal, *args, kw_only, **kwargs):
    pass
# 顺序:仅限位置 → / → 普通 → *args → 仅限关键字 → **kwargs

6.3 返回值与多值返回

# 没有 return 语句(或 return 后无表达式),返回 None
def do_nothing():
    pass

result = do_nothing()
print(result)  # None

# 多值返回(实际上是返回元组)
def min_max(data):
    return min(data), max(data)

result = min_max([3, 1, 4, 1, 5])
print(result)        # (1, 5) — 元组
print(type(result))  # <class 'tuple'>

lo, hi = min_max([3, 1, 4, 1, 5])  # 解包

6.4 作用域与 LEGB 规则

Python 按 LEGB 顺序查找变量:

  • L — Local:函数内部
  • E — Enclosing:外层函数(闭包)
  • G — Global:模块级别
  • B — Built-in:内置名称
x = "global"

def outer():
    x = "enclosing"
    
    def inner():
        x = "local"
        print(x)   # local — L
    
    inner()
    print(x)       # enclosing — E

outer()
print(x)           # global — G

global 和 nonlocal

count = 0

def increment():
    global count   # 声明使用全局变量
    count += 1

increment()
print(count)  # 1

def outer():
    x = 10
    
    def inner():
        nonlocal x  # 声明使用外层函数的变量
        x += 1
    
    inner()
    print(x)  # 11

outer()

注意:尽量避免使用 global,它会让代码难以维护和测试。

6.5 闭包(Closure)

当内层函数引用了外层函数的变量,并且外层函数返回了内层函数,就形成了闭包:

def make_multiplier(factor):
    def multiply(x):
        return x * factor    # factor 是自由变量,被闭包"捕获"
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)

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

# 查看闭包捕获的变量
print(double.__closure__[0].cell_contents)  # 2

闭包的经典陷阱

# 循环中创建闭包
functions = []
for i in range(5):
    functions.append(lambda: i)

# 你以为输出 0, 1, 2, 3, 4?
for f in functions:
    print(f(), end=" ")  # 4 4 4 4 4 — 全是 4!

# 原因:lambda 捕获的是变量 i 的引用,不是值
# 循环结束后 i = 4

# 解决方案 1:默认参数(在定义时绑定值)
functions = []
for i in range(5):
    functions.append(lambda x=i: x)

# 解决方案 2:额外的函数层
def make_func(i):
    return lambda: i

functions = [make_func(i) for i in range(5)]

6.6 lambda 匿名函数

lambda 创建简单的一行函数:

# lambda 参数: 表达式
square = lambda x: x ** 2
add = lambda a, b: a + b

print(square(5))   # 25
print(add(3, 4))   # 7

主要用途:作为简短的回调函数

# 排序
students = [("Alice", 90), ("Bob", 75), ("Charlie", 88)]
students.sort(key=lambda s: s[1], reverse=True)

# 过滤
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))

# 映射
squares = list(map(lambda x: x**2, numbers))

限制:lambda 只能包含一个表达式,不能包含语句(if/for/while/赋值等)。复杂逻辑应使用 def

6.7 高阶函数:map()、filter()、reduce()

高阶函数是接收函数作为参数,或返回函数的函数。

map() — 对每个元素应用函数

numbers = [1, 2, 3, 4, 5]

# map 返回迭代器
squares = list(map(lambda x: x**2, numbers))  # [1, 4, 9, 16, 25]

# 多参数 map
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b))  # [11, 22, 33]

# 常用:类型转换
str_nums = ["1", "2", "3"]
int_nums = list(map(int, str_nums))  # [1, 2, 3]

# 推导式替代(通常更推荐)
squares = [x**2 for x in numbers]

filter() — 过滤元素

numbers = range(1, 21)

# 过滤出偶数
evens = list(filter(lambda x: x % 2 == 0, numbers))
# [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

# None 作为函数 — 过滤掉 falsy 值
data = [0, 1, "", "hello", None, True, False, [], [1]]
truthy = list(filter(None, data))
# [1, 'hello', True, [1]]

# 推导式替代
evens = [x for x in numbers if x % 2 == 0]

reduce() — 累积计算

from functools import reduce

numbers = [1, 2, 3, 4, 5]

# 累积求和
total = reduce(lambda acc, x: acc + x, numbers)  # 15
# 过程: ((((1+2)+3)+4)+5)

# 带初始值
total = reduce(lambda acc, x: acc + x, numbers, 100)  # 115

# 累积求积
product = reduce(lambda acc, x: acc * x, numbers)  # 120

# 实用场景:展平嵌套列表
nested = [[1, 2], [3, 4], [5, 6]]
flat = reduce(lambda acc, x: acc + x, nested)  # [1, 2, 3, 4, 5, 6]

什么时候用推导式 vs map/filter?

推荐推导式——更 Pythonic、更直观。map/filter 在函数已存在时更简洁:

# 函数已存在时,map 更简洁
names = ["  alice  ", " BOB ", "  Charlie  "]
cleaned = list(map(str.strip, names))   # 比推导式更直接
cleaned = [name.strip() for name in names]  # 也可以

6.8 递归与尾递归优化

# 阶乘
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print(factorial(5))  # 120

# 斐波那契(朴素递归,指数时间复杂度)
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

# 加缓存 — 记忆化
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(100))  # 354224848179261915075 — 瞬间完成

Python 的递归限制

import sys
print(sys.getrecursionlimit())  # 1000(默认)

# 可以修改,但不推荐设太大
sys.setrecursionlimit(5000)

Python 不支持尾递归优化(这是设计决策,Guido 认为保留调用栈信息比优化更重要)。深度递归应改为迭代:

# 迭代版阶乘
def factorial_iter(n):
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

# 迭代版斐波那契
def fib_iter(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a

6.9 函数注解与类型提示(Type Hints)

类型提示让代码更清晰、更易维护,IDE 也能提供更好的补全和检查:

def greet(name: str) -> str:
    return f"Hello, {name}!"

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

# 类型提示不会强制执行——Python 仍然是动态类型
add("hello", " world")  # 运行时不会报错

常用类型注解

from typing import Optional, Union

# 基本类型
def process(name: str, age: int, score: float, active: bool) -> None:
    pass

# Optional — 可以是 None
def find_user(user_id: int) -> Optional[str]:
    # 等价于 str | None(Python 3.10+)
    return None

# Union — 多种类型
def parse(value: Union[str, int]) -> str:
    # 等价于 str | int(Python 3.10+)
    return str(value)

# 容器类型(Python 3.9+ 可直接用内置类型)
def process_items(items: list[str], lookup: dict[str, int]) -> set[int]:
    return {lookup[item] for item in items}

# 默认参数 + 类型提示
def connect(host: str, port: int = 8080) -> bool:
    return True

类型提示的真正价值在于配合 mypy 或 pyright 等静态检查工具使用(详见第 18 章)。


本章小结:函数是 Python 编程的基石。理解各种参数类型(特别是 *args**kwargs/*)、闭包的变量捕获机制、以及可变默认参数陷阱,是写出健壮 Python 代码的关键。类型提示虽非强制,但现代 Python 项目中已成为标配。