第五章:函数与类型提示
本章学习目标
- 掌握函数的定义和调用
- 理解参数传递机制(位置参数、关键字参数、默认值参数)
- 熟练使用类型提示
- 学会使用 *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
最佳实践
- 使用类型提示:提高代码可读性和可维护性
- 保持函数简洁:单一职责原则,每个函数只做一件事
- 使用文档字符串:说明函数用途、参数和返回值
- 避免修改全局变量:使用参数和返回值
- 使用不可变默认参数:使用 None
- 使用 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 匿名函数
- 装饰器的基本概念和使用
在下一章中,我们将学习列表、字典、集合等数据结构。