【python零基础教程第12讲】Python 函数工具与类型注解
Python 函数工具与类型注解:从偏函数到泛型编程的进阶之路
在 Python 开发中,函数是第一等公民,而类型注解与函数工具库 functools 则是提升代码可读性、健壮性和性能的两大利器。本文将深入探讨 functools 中的 partial 偏函数与 lru_cache 缓存机制,以及 typing 模块中的基础类型注解、Union、Optional 和泛型 TypeVar,帮助你写出更优雅、更高效的 Python 代码。
一、functools.partial:冻结函数参数的优雅方式
1.1 什么是偏函数?
偏函数(Partial Function)是指通过固定原函数的部分参数,生成一个新的可调用对象。这在需要重复调用某个函数且大部分参数固定时非常有用,可以避免重复传递相同参数。
1.2 基本用法
from functools import partial
def power(base, exponent):
return base ** exponent
# 创建一个计算平方的偏函数
square = partial(power, exponent=2)
print(square(5)) # 输出 25
# 创建一个计算立方的偏函数
cube = partial(power, exponent=3)
print(cube(3)) # 输出 27
1.3 实际应用场景
场景一:回调函数参数预绑定
def log(level, message):
print(f"[{level.upper()}] {message}")
info_log = partial(log, "info")
error_log = partial(log, "error")
info_log("系统启动成功") # [INFO] 系统启动成功
error_log("连接超时") # [ERROR] 连接超时
场景二:简化多参数函数调用
def connect(host, port, timeout, retries):
# 模拟数据库连接
print(f"Connecting to {host}:{port} (timeout={timeout}, retries={retries})")
# 创建默认配置的偏函数
default_connect = partial(connect, host="localhost", port=3306, timeout=30, retries=3)
default_connect() # 使用全部默认值
default_connect(host="192.168.1.1") # 仅覆盖 host
1.4 注意事项
partial返回的是可调用对象,不是函数,但可以像函数一样使用。- 关键字参数和位置参数都可以被固定,但需注意参数顺序。
- 偏函数不会改变原函数的签名,但
inspect.signature可以正确反映新签名。
二、functools.lru_cache:函数级缓存优化
2.1 缓存机制原理
lru_cache 是 Least Recently Used(最近最少使用)缓存装饰器,它会自动缓存函数的返回值,当相同参数再次调用时直接返回缓存结果,避免重复计算。适用于纯函数(相同输入始终产生相同输出)且计算密集的场景。
2.2 基本用法
from functools import lru_cache
import time
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 第一次计算会递归执行
start = time.time()
print(fibonacci(35)) # 9227465
print(f"耗时: {time.time() - start:.4f}s")
# 第二次调用直接返回缓存结果
start = time.time()
print(fibonacci(35)) # 立即返回
print(f"耗时: {time.time() - start:.6f}s")
2.3 参数详解
| 参数 | 说明 |
|---|---|
maxsize | 缓存最大条目数,None 表示无限制(慎用,可能耗尽内存) |
typed | 若为 True,则区分不同参数类型(如 1 和 1.0 视为不同) |
2.4 高级用法:缓存统计与清理
@lru_cache(maxsize=32)
def expensive_function(x, y):
return x ** y + y ** x
# 查看缓存统计信息
print(expensive_function.cache_info())
# CacheInfo(hits=0, misses=0, maxsize=32, currsize=0)
expensive_function(2, 3)
expensive_function(2, 3) # 命中缓存
print(expensive_function.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=32, currsize=1)
# 清空缓存
expensive_function.cache_clear()
2.5 实际应用:动态规划与递归优化
@lru_cache(maxsize=None)
def climb_stairs(n):
"""爬楼梯问题:每次可以爬1或2阶,求到达n阶的方法数"""
if n <= 2:
return n
return climb_stairs(n-1) + climb_stairs(n-2)
print(climb_stairs(100)) # 573147844013817084101,瞬间计算
2.6 注意事项
- 缓存键基于参数的位置和关键字,参数必须是可哈希的(不可变类型)。
- 对于可变参数(如列表、字典),需要先转换为不可变类型(如元组)。
- 缓存会占用内存,
maxsize应根据实际场景合理设置。
三、typing 类型注解:让代码自文档化
3.1 基础类型注解
Python 3.5+ 引入 typing 模块,允许为函数参数和返回值添加类型提示,提升代码可读性并支持静态类型检查(如 mypy)。
from typing import List, Dict, Tuple, Set
def greet(name: str, age: int) -> str:
return f"Hello {name}, you are {age} years old."
def process_items(items: List[str]) -> Dict[str, int]:
"""统计列表中每个字符串的长度"""
return {item: len(item) for item in items}
def get_coordinates() -> Tuple[float, float]:
return (39.9042, 116.4074)
def unique_values(data: Set[int]) -> List[int]:
return list(data)
3.2 Union 与 Optional:处理多类型与可选值
Union 表示参数可以是多种类型之一:
from typing import Union
def parse_number(value: Union[int, float, str]) -> float:
"""将整数、浮点数或字符串转换为浮点数"""
if isinstance(value, str):
return float(value)
return float(value)
# 使用 Python 3.10+ 的简化语法(X | Y)
def parse_number_v2(value: int | float | str) -> float:
return float(value)
Optional 是 Union[X, None] 的简写,表示参数可以是类型 X 或 None:
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
"""根据ID查找用户名,不存在返回None"""
database = {1: "Alice", 2: "Bob"}
return database.get(user_id)
# 等价写法
def find_user_v2(user_id: int) -> str | None:
return database.get(user_id)
3.3 实际应用:复杂数据结构注解
from typing import Dict, List, Optional, Union
# 定义嵌套类型别名
UserData = Dict[str, Union[str, int, List[str]]]
def process_user(data: UserData) -> Optional[str]:
name = data.get("name")
if not isinstance(name, str):
return None
return name.upper()
# 更复杂的场景:JSON响应类型
Response = Dict[str, Union[str, int, List[Dict[str, str]]]]
def fetch_api(url: str) -> Response:
# 模拟API返回
return {
"status": "ok",
"code": 200,
"data": [{"id": "1", "name": "item1"}]
}
四、TypeVar 泛型基础:编写类型安全的通用函数
4.1 为什么需要泛型?
当函数需要处理多种类型,但又希望保持类型一致性时,泛型可以避免使用 Any 导致类型信息丢失。
4.2 基本用法
from typing import TypeVar, List
T = TypeVar('T') # 定义一个类型变量
def first_element(items: List[T]) -> T:
"""返回列表的第一个元素,类型与列表元素类型一致"""
return items
# 使用示例
result1 = first_element([1, 2, 3]) # 推断为 int
result2 = first_element(["a", "b"]) # 推断为 str
result3 = first_element([1.0, 2.0]) # 推断为 float
4.3 约束类型变量
可以通过 bound 参数限制类型变量的范围:
from typing import TypeVar, List
Number = TypeVar('Number', int, float) # 只能是 int 或 float
def sum_numbers(numbers: List[Number]) -> Number:
return sum(numbers)
# 合法
print(sum_numbers([1, 2, 3])) # 6
print(sum_numbers([1.5, 2.5])) # 4.0
# 类型检查会报错(如果使用mypy)
# sum_numbers(["a", "b"]) # 错误:str 不是 Number
4.4 多个类型变量与协变/逆变
from typing import TypeVar, List, Tuple
A = TypeVar('A')
B = TypeVar('B')
def pair(first: A, second: B) -> Tuple[A, B]:
return (first, second)
result = pair(1, "hello") # Tuple[int, str]
4.5 泛型类
from typing import Generic, TypeVar, List
T = TypeVar('T')
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: List[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
# 使用
int_stack = Stack[int]()
int_stack.push(1)
int_stack.push(2)
value = int_stack.pop() # 类型为 int
str_stack = Stack[str]()
str_stack.push("hello")
4.6 实际应用:类型安全的工厂函数
from typing import TypeVar, Type
T = TypeVar('T')
def create_instance(cls: Type[T], *args, **kwargs) -> T:
"""创建指定类的实例,返回类型与传入类一致"""
return cls(*args, **kwargs)
class User:
def __init__(self, name: str):
self.name = name
user = create_instance(User, "Alice") # user 类型为 User
print(user.name) # 类型检查器能正确推断
五、综合实战:构建一个带缓存的类型安全计算器
将上述知识融合,实现一个支持缓存、类型注解的数学计算工具:
from functools import lru_cache, partial
from typing import Union, Optional, TypeVar, List
Number = Union[int, float]
T = TypeVar('T')
@lru_cache(maxsize=256)
def power(base: Number, exponent: int) -> Number:
"""计算幂运算,带缓存"""
return base ** exponent
# 创建偏函数
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
def process_numbers(numbers: List[Number], operation: str = "square") -> List[Number]:
"""对列表中的每个数字应用指定操作"""
op_map = {
"square": square,
"cube": cube,
"identity": lambda x: x
}
func = op_map.get(operation)
if func is None:
raise ValueError(f"Unknown operation: {operation}")
return [func(x) for x in numbers]
# 使用示例
data = [1, 2, 3, 4, 5]
print(process_numbers(data, "square")) # [1, 4, 9, 16, 25]
print(process_numbers(data, "cube")) # [1, 8, 27, 64, 125]
# 查看缓存统计
print(power.cache_info())
# CacheInfo(hits=0, misses=10, maxsize=256, currsize=10)
六、最佳实践与注意事项
-
类型注解不是强制约束:Python 是动态语言,类型注解仅在静态检查时生效,运行时不会阻止错误类型传入。建议配合 mypy 等工具使用。
-
缓存适用场景:
lru_cache最适合纯函数(无副作用、输入决定输出),且计算成本高、重复调用频繁的场景。对于 I/O 密集型操作(如网络请求),建议使用专门的缓存库(如cachetools)。 -
偏函数 vs 默认参数:偏函数更适合在运行时动态创建,而默认参数适合在定义时固定。偏函数返回的新对象可以传递和存储。
-
泛型命名规范:通常使用大写单字母(
T、U、V)或描述性名称(ItemType、KeyType)。 -
版本兼容性:Python 3.10+ 支持
X | Y语法替代Union[X, Y],3.9+ 支持list[str]替代List[str]。根据项目 Python 版本选择合适的写法。
七、总结
functools.partial:冻结函数参数,减少重复代码,提升可读性。functools.lru_cache:自动缓存函数结果,优化递归和计算密集型任务。typing基础注解:int、str、List、Dict等让函数签名自文档化。Union与Optional:处理多类型和可选值,增强类型表达力。TypeVar泛型:编写类型安全的通用函数和类,避免Any带来的类型信息丢失。
掌握这些工具,你的 Python 代码将兼具动态语言的灵活性和静态语言的严谨性,在团队协作和大型项目中发挥巨大价值。建议在实际项目中逐步引入类型注解,并配合 mypy 进行静态检查,让代码质量更上一层楼。
更多推荐


所有评论(0)