Administrator
发布于 2026-03-19 / 3 阅读
0
0

第五章:函数与类型提示

第五章:函数与类型提示

本章学习目标

  • 掌握函数的定义和调用
  • 理解参数传递机制(位置参数、关键字参数、默认值参数)
  • 熟练使用类型提示
  • 学会使用 *args 和 **kwargs
  • 理解闭包和装饰器的基本概念

5.1 函数基础

5.1.1 什么是函数

函数是组织代码的基本单元,可以接受输入(参数)、执行特定操作、返回输出(返回值)。使用函数可以:

  • 代码复用:避免重复代码
  • 模块化:将复杂问题分解为小问题
  • 提高可读性:使代码结构更清晰
  • 便于测试:独立测试每个功能

5.1.2 定义函数

# 无返回值函数
def greet(name: str) -> None:
    """问候函数"""
    print(f"Hello, {name}!")

# 有返回值函数
def add(a: int, b: int) -> int:
    """返回两个数的和"""
    return a + b

# 调用函数
greet("Alice")
result: int = add(3, 5)

5.1.3 函数命名规范

  • 使用 snake_case 命名
  • 名称要有意义,反映函数功能
  • 可以使用动词或动词短语
# ✓ 好的函数名
def calculate_sum(numbers: list[int]) -> int:
    pass

def find_max(items: list[int]) -> int | None:
    pass

def is_prime(n: int) -> bool:
    pass

# ✗ 不好的函数名
def calc(n):           # 缩写
def do_stuff():        # 过于笼统
def process():         # 不明确

5.2 文档字符串

5.2.1 文档字符串的作用

文档字符串(Docstring)用于说明函数的功能、参数和返回值。

def calculate_area(width: float, height: float) -> float:
    """
    计算矩形面积

    Args:
        width: 矩形的宽度
        height: 矩形的高度

    Returns:
        矩形的面积

    Raises:
        ValueError: 当宽度或高度为负数时
    """
    if width < 0 or height < 0:
        raise ValueError("宽度和高度不能为负数")
    return width * height

# 查看文档
help(calculate_area)
print(calculate_area.__doc__)

5.2.2 文档字符串风格

Google 风格:

def func(arg1: str, arg2: int) -> bool:
    """简短描述

    详细描述...

    Args:
        arg1: 参数1的描述
        arg2: 参数2的描述

    Returns:
        返回值的描述

    Raises:
        ValueError: 异常描述
    """
    pass

NumPy/SciPy 风格:

def func(arg1, arg2):
    """
    简短描述

    详细描述...

    Parameters
    ----------
    arg1 : type
        参数描述
    arg2 : type
        参数描述

    Returns
    -------
    type
        返回值描述
    """
    pass

5.3 参数类型

5.3.1 位置参数与关键字参数

def create_user(name: str, age: int, city: str = "Beijing") -> dict[str, str | int]:
    """创建用户信息"""
    return {"name": name, "age": age, "city": city}

# 位置参数:按顺序传递
user1: dict = create_user("Alice", 25)

# 关键字参数:指定参数名
user2: dict = create_user(name="Bob", age=30, city="Shanghai")

# 混合使用
user3: dict = create_user("Charlie", city="Guangzhou", age=28)

# 错误用法
# user4 = create_user(name="David", 35)  # SyntaxError
# user5 = create_user(age=25, "Eve")     # SyntaxError

5.3.2 默认参数

默认参数必须在位置参数之后。

# 正确
def greet(name: str, greeting: str = "Hello") -> None:
    print(f"{greeting}, {name}!")

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

# 错误
# def greet(greeting="Hello", name: str):  # SyntaxError

5.3.3 默认参数注意事项

默认参数必须是不可变对象

# ✗ 错误示例:默认参数是可变对象
def add_item(item: str, items: list[str] = []) -> list[str]:
    items.append(item)  # 警告:所有调用共享同一个列表
    return items

# 调用两次
result1 = add_item("apple")
result2 = add_item("banana")
print(result1)  # ['apple', 'banana']  ← 不是预期的 ['apple']
print(result2)  # ['apple', 'banana']

# ✓ 正确示例:使用 None
def add_item_correct(item: str, items: list[str] | None = None) -> list[str]:
    if items is None:
        items = []
    items.append(item)
    return items

5.3.4 *args(可变位置参数)

def sum_all(*args: int) -> int:
    """求所有参数的和"""
    total: int = 0
    for num in args:
        total += num
    return total

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

# *args 的类型是 tuple
def print_args(*args: str) -> None:
    for i, arg in enumerate(args):
        print(f"{i}: {arg}")

print_args("a", "b", "c")
# 0: a
# 1: b
# 2: c

5.3.5 **kwargs(可变关键字参数)

def print_info(**kwargs: str | int) -> None:
    """打印所有关键字参数"""
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="Beijing")
# name: Alice
# age: 25
# city: Beijing

# **kwargs 的类型是 dict
def process_data(required: str, **kwargs: int) -> None:
    print(f"必需: {required}")
    print(f"额外参数: {kwargs}")

process_data("data", x=1, y=2, z=3)
# 必需: data
# 额外参数: {'x': 1, 'y': 2, 'z': 3}

5.3.6 组合使用

def flexible_func(
    required: str,           # 位置参数
    *args: int,              # 可变位置参数
    keyword: str = "default", # 关键字参数
    **kwargs: int            # 可变关键字参数
) -> None:
    print(f"必需: {required}")
    print(f"位置参数: {args}")
    print(f"关键字: {keyword}")
    print(f"额外关键字: {kwargs}")

# 调用
flexible_func("hello", 1, 2, 3, keyword="value", x=10, y=20)
# 必需: hello
# 位置参数: (1, 2, 3)
# 关键字: value
# 额外关键字: {'x': 10, 'y': 20}

5.4 类型提示详解

5.4.1 基本类型提示

# 变量类型提示
name: str = "Alice"
age: int = 25
height: float = 1.65
is_active: bool = True

# 函数参数和返回值类型提示
def process(name: str, age: int, score: float) -> bool:
    """处理用户信息"""
    return score >= 60

5.4.2 容器类型提示

# 列表
def get_first(items: list[int]) -> int | None:
    return items[0] if items else None

# 字典
def get_value(data: dict[str, int], key: str) -> int | None:
    return data.get(key)

# 集合
def unique_items(items: list[str]) -> set[str]:
    return set(items)

# 元组(固定长度)
def coordinates(point: tuple[float, float]) -> float:
    x, y = point
    return (x ** 2 + y ** 2) ** 0.5

# 元组(可变长度)
def avg(*scores: float) -> float:
    return sum(scores) / len(scores) if scores else 0

5.4.3 联合类型 (Python 3.10+)

# 使用 | 运算符(Python 3.10+ 推荐)
user_id: int | str = 12345
user_id = "abc123"

# Optional 是 int | None 的简写
name: str | None = None

# 多个类型
value: int | float | str = 42

# 在函数参数中使用
def process(value: int | str | float) -> str:
    return str(value)

5.4.4 类型别名

# 给复杂类型起别名
Point = tuple[float, float]
Vector = list[float]
Matrix = list[list[float]]

def distance(p1: Point, p2: Point) -> float:
    return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5

# 使用
p1: Point = (0.0, 0.0)
p2: Point = (3.0, 4.0)
result: float = distance(p1, p2)  # 5.0

5.4.5 Callable 类型

from collections.abc import Callable

# 函数作为参数
def apply(func: Callable[[int], int], value: int) -> int:
    return func(value)

def double(x: int) -> int:
    return x * 2

result: int = apply(double, 5)  # 10

# 可调用对象作为参数
def execute(callback: Callable[[], None]) -> None:
    callback()

def say_hello() -> None:
    print("Hello!")

execute(say_hello)  # Hello!

# 返回函数
def create_multiplier(factor: int) -> Callable[[int], int]:
    def multiplier(x: int) -> int:
        return x * factor
    return multiplier

double_func: Callable[[int], int] = create_multiplier(2)
triple_func: Callable[[int], int] = create_multiplier(3)
print(double_func(5))  # 10
print(triple_func(5))  # 15

5.4.6 Literal 类型

from typing import Literal

def move(direction: Literal["up", "down", "left", "right"]) -> None:
    match direction:
        case "up":
            print("向上移动")
        case "down":
            print("向下移动")
        case "left":
            print("向左移动")
        case "right":
            print("向右移动")

move("up")    # 向上移动
# move("forward")  # 类型错误

5.4.7 TypedDict(字典类型提示)

from typing import TypedDict

class UserDict(TypedDict):
    name: str
    age: int

def create_user(data: UserDict) -> str:
    return f"{data['name']}, {data['age']}岁"

user: UserDict = {"name": "Alice", "age": 25}
print(create_user(user))  # Alice, 25岁

5.5 函数返回值

5.5.1 返回单个值

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

result: int = add(3, 5)  # 8

5.5.2 返回多个值(使用元组)

def divide(a: float, b: float) -> tuple[float, float]:
    """返回商和余数"""
    if b == 0:
        raise ValueError("除数不能为零")
    quotient: float = a // b
    remainder: float = a % b
    return quotient, remainder

# 解包返回值
quotient, remainder = divide(10, 3)
print(f"商: {quotient}, 余数: {remainder}")  # 商: 3.0, 余数: 1.0

5.5.3 返回字典

def stats(numbers: list[int]) -> dict[str, float]:
    """返回统计信息"""
    if not numbers:
        return {"sum": 0, "avg": 0, "min": 0, "max": 0}

    return {
        "sum": sum(numbers),
        "avg": sum(numbers) / len(numbers),
        "min": min(numbers),
        "max": max(numbers)
    }

result: dict[str, float] = stats([1, 2, 3, 4, 5])
print(result)  # {'sum': 15, 'avg': 3.0, 'min': 1, 'max': 5}

5.5.4 返回 None

# 明确返回 None
def log_message(message: str) -> None:
    print(message)
    # 隐式返回 None

# print() 函数的返回值为 None
result: None = print("hello")  # hello
print(result)  # None

5.6 变量作用域

5.6.1 LEGB 规则

Python 中变量查找顺序:

  • Local:局部作用域
  • Enclosing:闭包作用域
  • Global:全局作用域
  • Built-in:内置作用域
# 全局变量
global_var: int = 10

def outer() -> int:
    # 闭包变量
    enclosing_var: int = 20

    def inner() -> int:
        # 局部变量
        local_var: int = 30
        # 可以访问所有变量
        return global_var + enclosing_var + local_var

    return inner()

result: int = outer()  # 60

5.6.2 global 和 nonlocal

# 使用 global 修改全局变量
counter: int = 0

def increment() -> None:
    global counter
    counter += 1

increment()
increment()
print(counter)  # 2

# 使用 nonlocal 修改闭包变量
def outer() -> int:
    count: int = 0

    def inner() -> None:
        nonlocal count
        count += 1

    inner()
    inner()
    return count

result: int = outer()
print(result)  # 2

5.7 匿名函数 (lambda)

5.7.1 lambda 基础

lambda 用于创建简单的匿名函数。

# 基本语法:lambda 参数: 表达式

# 简单 lambda
square: Callable[[int], int] = lambda x: x ** 2
print(square(5))  # 25

# 多参数 lambda
add: Callable[[int, int], int] = lambda x, y: x + y
print(add(3, 5))  # 8

5.7.2 lambda 与内置函数

numbers: list[int] = [3, 1, 4, 1, 5, 9, 2, 6]

# sorted 使用 lambda
sorted_nums: list[int] = sorted(numbers)  # 默认升序
sorted_nums_desc: list[int] = sorted(numbers, reverse=True)

# 按字典键排序
users: list[dict[str, str | int]] = [
    {"name": "Bob", "age": 30},
    {"name": "Alice", "age": 25}
]
sorted_users: list[dict] = sorted(users, key=lambda u: u["age"])
# [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}]

# map 使用 lambda
squared: list[int] = list(map(lambda x: x ** 2, numbers))

# filter 使用 lambda
evens: list[int] = list(filter(lambda x: x % 2 == 0, numbers))

# reduce 使用 lambda
from functools import reduce
total: int = reduce(lambda x, y: x + y, numbers)

5.7.3 lambda 的局限

lambda 只能包含一个表达式,不能包含语句。

# ✓ 正确
f = lambda x: x ** 2

# ✗ 错误
# f = lambda x: if x > 0: return x else: return 0

# 复杂逻辑使用普通函数
def process(x: int) -> int:
    if x > 0:
        return x
    else:
        return 0

5.8 装饰器

5.8.1 什么是装饰器

装饰器是一个接受函数作为参数并返回新函数的函数,用于在不修改原函数的情况下增强其功能。

# 装饰器基本结构
def my_decorator(func: Callable[..., T]) -> Callable[..., T]:
    def wrapper(*args: Any, **kwargs: Any) -> T:
        # 在调用函数前执行
        print("调用前")
        # 调用原函数
        result = func(*args, **kwargs)
        # 在调用函数后执行
        print("调用后")
        return result
    return wrapper

5.8.2 基本装饰器

from functools import wraps

def timer(func: Callable[..., int]) -> Callable[..., int]:
    """计时装饰器"""
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> int:
        import time
        start: float = time.time()
        result: int = func(*args, **kwargs)
        end: float = time.time()
        print(f"{func.__name__} 执行时间: {end - start:.4f}秒")
        return result
    return wrapper

@timer
def slow_function() -> int:
    import time
    time.sleep(1)
    return 42

result: int = slow_function()
print(result)

5.8.3 带参数的装饰器

def repeat(times: int) -> Callable[[Callable[..., T]], Callable[..., T]]:
    """重复执行函数的装饰器"""
    def decorator(func: Callable[..., T]) -> Callable[..., T]:
        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> T:
            result: T | None = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet() -> str:
    return "Hello!"

print(greet())  # "Hello!" (执行3次)

5.8.4 装饰器应用场景

日志记录:

def log(func: Callable[..., T]) -> Callable[..., T]:
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> T:
        print(f"调用 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} 返回 {result}")
        return result
    return wrapper

权限检查:

def require_admin(func: Callable[..., T]) -> Callable[..., T]:
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> T:
        user = kwargs.get("user")
        if not user or not user.get("is_admin"):
            raise PermissionError("需要管理员权限")
        return func(*args, **kwargs)
    return wrapper

缓存:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

5.8.5 类装饰器

def singleton(cls: type[T]) -> type[T]:
    """单例模式装饰器"""
    instances: dict[type[T], T] = {}

    @wraps(cls)
    def get_instance(*args: Any, **kwargs: Any) -> T:
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

@singleton
class Database:
    def __init__(self) -> None:
        print("初始化数据库")

# 测试
db1 = Database()  # 初始化数据库
db2 = Database()  # 不再初始化
print(db1 is db2)  # True

5.9 综合示例

示例 1:验证函数参数

from functools import wraps

def validate_positive(func: Callable[..., float]) -> Callable[..., float]:
    """验证参数为正数"""
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> float:
        # 验证位置参数
        for i, arg in enumerate(args):
            if isinstance(arg, (int, float)) and arg <= 0:
                raise ValueError(f"参数 {i} 必须为正数")

        # 验证关键字参数
        for key, value in kwargs.items():
            if isinstance(value, (int, float)) and value <= 0:
                raise ValueError(f"参数 {key} 必须为正数")

        return func(*args, **kwargs)
    return wrapper

@validate_positive
def divide(a: float, b: float) -> float:
    return a / b

print(divide(10, 2))  # 5.0
# print(divide(10, 0))  # ValueError

示例 2:链式调用

class StringBuilder:
    def __init__(self) -> None:
        self._parts: list[str] = []

    def append(self, text: str) -> "StringBuilder":
        self._parts.append(text)
        return self

    def append_line(self, text: str = "") -> "StringBuilder":
        self._parts.append(text + "\n")
        return self

    def to_string(self) -> str:
        return "".join(self._parts)

# 使用链式调用
result: str = (StringBuilder()
    .append("Hello")
    .append(" ")
    .append("World")
    .append_line()
    .append("This is a new line")
    .to_string())

print(result)
# Hello World
# This is a new line

示例 3:偏函数

from functools import partial
from collections.abc import Callable

# 创建偏函数
def power(base: float, exponent: float) -> float:
    return base ** exponent

square: Callable[[float], float] = partial(power, exponent=2)
cube: Callable[[float], float] = partial(power, exponent=3)

print(square(5))  # 25.0
print(cube(5))    # 125.0

# 与内置函数结合
int_binary: Callable[[str], int] = partial(int, base=2)
print(int_binary("1010"))  # 10

最佳实践

  1. 使用类型提示:提高代码可读性和可维护性
  2. 保持函数简洁:单一职责原则,每个函数只做一件事
  3. 使用文档字符串:说明函数用途、参数和返回值
  4. 避免修改全局变量:使用参数和返回值
  5. 使用不可变默认参数:使用 None
  6. 使用 functools.wraps:保持装饰器元数据

课后练习

练习 5.1:基本函数

实现一个函数,计算两个数的最大公约数(GCD)。

练习 5.2:装饰器

实现一个装饰器,统计函数被调用的次数。

练习 5.3:可变参数

使用 *args 实现一个可变参数求和函数。

练习 5.4:柯里化

实现一个函数,接受一个函数作为参数,返回该函数的柯里化版本。

练习 5.5:类型提示

使用类型提示实现一个通用的列表过滤函数。

练习 5.6:文档字符串

为练习 5.1 的函数添加详细的文档字符串。

练习 5.7:lambda

使用 lambda 和内置函数实现列表排序。

练习 5.8:闭包

使用闭包实现一个计数器工厂函数。

本章小结

本章我们学习了:

  • 函数的定义和调用
  • 位置参数、关键字参数、默认值参数
  • *args 和 **kwargs 的使用
  • 类型提示的详细用法
  • 变量作用域和 global/nonlocal
  • lambda 匿名函数
  • 装饰器的基本概念和使用

在下一章中,我们将学习列表、字典、集合等数据结构。

相关资源


评论