Python函数进阶:从闭包、装饰器到函数式编程实战
1. 项目概述:深入Python函数的核心机制
“Python函数使用(四)”这个标题,乍一看像是某个系列教程的第四部分,但对于真正想深入理解Python编程的开发者来说,它指向的是一个更核心的议题:当我们已经掌握了函数定义、参数传递、返回值这些基础之后,接下来该学什么?是时候深入到函数的“内脏”,去理解那些让代码更灵活、更强大、也更优雅的高级特性和底层机制了。这不仅仅是学会几个新语法,而是关于如何像资深工程师一样思考,如何构建可维护、可扩展且高效的代码结构。
从网络热词中频繁出现的“回调函数”、“lambda”、“装饰器”等关键词就能看出,社区关注的焦点早已超越了def和return。大家在实际项目中遇到的困惑,比如如何优雅地处理事件、如何在不修改原有代码的情况下增强功能、如何写出更简洁的循环与映射代码,都指向了函数式编程思想在Python中的实践。本篇文章,我将以一个多年Python开发者的视角,带你系统性地拆解这些高级函数概念。我们不只讲“怎么用”,更要深挖“为什么这么设计”以及“在什么场景下用最合适”。无论你是正在啃“Python函数实验报告”的学生,还是被“yolov8改进损失函数”这类实际问题困扰的研究者,理解这些内容都将为你打开一扇新的大门。
2. 核心概念深化:从“工具”到“构建块”
在基础阶段,函数被看作完成特定任务的“工具”。但在中级到高级阶段,函数应该被视为一等公民和核心“构建块”。这种思维的转变,是写出Pythonic代码的关键。
2.1 函数作为一等对象:理解赋值的本质
在Python中,函数是“一等对象”。这意味着函数可以像整数、字符串、列表一样被赋值给变量、作为参数传递给其他函数、作为其他函数的返回值,甚至可以被存储在数据结构中。这是许多高级特性的基石。
def greet(name): return f"Hello, {name}!" # 1. 赋值给变量 my_function = greet print(my_function("Alice")) # 输出: Hello, Alice! # 2. 存储在列表中 function_list = [greet, str.lower, abs] result = function_list[0]("Bob") print(result) # 输出: Hello, Bob! # 3. 作为字典的值 function_dict = {'say_hello': greet, 'get_length': len} print(function_dict['say_hello']('Charlie'))为什么这很重要?这种特性使得“策略模式”等设计模式在Python中实现起来异常简洁。你可以根据运行时条件,动态地选择要执行的算法或行为。
实操心得:当你发现代码中有大量的
if-elif-else语句,每个分支都在调用不同的函数处理相似逻辑时,就应该考虑使用函数字典(Dispatch Table)来优化。这不仅能提升代码可读性,也更容易扩展。例如,一个简单的命令行工具的命令解析器,用字典映射命令字符串到处理函数,比一长串的if语句要清晰得多。
2.2 嵌套函数与闭包:数据的“私人保险箱”
你可以在一个函数内部定义另一个函数,这就是嵌套函数。当内部函数引用了外部函数的局部变量时,就形成了一个“闭包”。闭包的神奇之处在于,它“记住”了自身被定义时的环境(即外部函数的命名空间),即使外部函数已经执行完毕。
def make_multiplier(factor): """工厂函数,创建一个乘以特定因子的函数""" def multiplier(x): # 内部函数 multiplier 引用了外部变量 factor return x * factor return multiplier # 创建两个专门的乘法器 double = make_multiplier(2) triple = make_multiplier(3) print(double(5)) # 输出: 10 print(triple(5)) # 输出: 15 # 查看闭包“记住”的值 print(double.__closure__[0].cell_contents) # 输出: 2核心原理:factor是make_multiplier的局部变量。通常,函数执行完后,其局部变量就会被销毁。但由于multiplier函数内部引用了factor,Python解释器会将它“捕获”并保存在multiplier函数的__closure__属性中,使其生命周期得以延续。
应用场景:
- 数据封装与隐藏:创建具有特定配置的函数,而配置参数对外部不可见。这在创建回调函数时特别有用。
- 装饰器的基础:装饰器本质上就是返回了一个闭包的高阶函数。
- 实现有状态的函数:在不使用类的情况下,让函数拥有“记忆”。例如,实现一个简单的计数器:
def counter(start=0): count = [start] # 使用可变对象(如列表)来存储状态 def increment(): count[0] += 1 return count[0] return increment c1 = counter(10) print(c1(), c1(), c1()) # 输出: 11 12 13注意事项:闭包捕获的是变量的引用,而不是变量的值。如果捕获的变量是可变对象(如列表、字典),并且在闭包外部被修改,闭包内部看到的值也会改变。这有时会导致难以察觉的Bug。对于需要“冻结”状态的情况,可以考虑在闭包内使用默认参数或
functools.partial来绑定值。
3. 装饰器:不修改源码的“代码增强术”
装饰器是Python中最具特色也最强大的特性之一,它允许你在不修改被装饰函数源代码的情况下,为其添加额外的功能。理解了闭包,装饰器就水到渠成了。
3.1 装饰器的工作原理与手动实现
一个最简单的装饰器,就是一个接受函数作为参数,并返回一个新函数的高阶函数。
def my_decorator(func): """一个简单的装饰器,在函数调用前后打印日志""" def wrapper(): print(f"即将调用函数: {func.__name__}") result = func() # 执行原始函数 print(f"函数 {func.__name__} 调用完毕") return result return wrapper @my_decorator def say_hello(): print("Hello!") # 调用被装饰后的函数 say_hello() # 输出: # 即将调用函数: say_hello # Hello! # 函数 say_hello 调用完毕语法糖@my_decorator等价于say_hello = my_decorator(say_hello)。现在say_hello这个名字指向的不再是原来的函数,而是wrapper函数。
3.2 处理带参数的被装饰函数
上面的wrapper函数没有参数,如果say_hello需要参数怎么办?我们需要让wrapper接受任意参数,并原封不动地传给原始函数。
def log_call(func): def wrapper(*args, **kwargs): # 使用 *args 和 **kwargs 接收任意参数 print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}") result = func(*args, **kwargs) # 将参数解包后传递给原始函数 print(f"{func.__name__} returned {result}") return result return wrapper @log_call def add(a, b): return a + b print(add(3, 5)) print(add(a=10, b=20))3.3 保留原函数的元信息
使用装饰器后,函数的__name__、__doc__等元信息会丢失(变成wrapper的)。这不利于调试和文档生成。functools.wraps装饰器就是用来解决这个问题的。
from functools import wraps def smart_decorator(func): @wraps(func) # 将原函数的元信息复制到 wrapper 函数 def wrapper(*args, **kwargs): print(f"Enhanced {func.__name__}") return func(*args, **kwargs) return wrapper @smart_decorator def example(): """这是一个示例函数。""" pass print(example.__name__) # 输出: example print(example.__doc__) # 输出: 这是一个示例函数。应用场景实录:
- 日志记录:自动记录函数的输入、输出和执行时间。
- 权限校验:在Web框架中,检查用户是否有权访问某个视图函数。
- 性能监控:计算函数执行耗时。
- 数据验证与格式化:在函数执行前验证参数,执行后格式化返回值。
- 单例模式/缓存(Memoization):使用装饰器实现非常优雅。
踩坑记录:装饰器的执行顺序是从下往上(或从里到外)。例如,
@a @b @c def f(): ...等价于f = a(b(c(f)))。在组合多个装饰器时,务必清楚它们增强功能的顺序。我曾在一个项目中,因为缓存装饰器放在了权限校验装饰器里面,导致未授权用户的错误请求也被缓存了,引发了严重问题。
4. 匿名函数与高阶函数:函数式编程的利器
Python并非纯函数式语言,但它吸收了许多函数式编程的精华,lambda表达式和map、filter、reduce等高阶函数就是代表。
4.1 Lambda表达式:简洁的临时函数
lambda用于创建匿名函数,即没有名字的简单函数。其语法为:lambda arguments: expression。表达式的结果会自动返回。
# 传统函数定义 def square(x): return x ** 2 # 等价的 lambda 表达式 square_lambda = lambda x: x ** 2 print(square(5)) # 25 print(square_lambda(5)) # 25lambda的典型使用场景:
- 作为
sort/sorted的key参数:students = [('Alice', 'B', 22), ('Bob', 'A', 19), ('Charlie', 'C', 25)] # 按年龄排序 students_sorted_by_age = sorted(students, key=lambda s: s[2]) # 按姓名第二个字母排序 students_sorted_by_second_char = sorted(students, key=lambda s: s[0][1]) - 在
map、filter等函数中作为参数(见下文)。 - 在GUI编程或回调中定义简单的事件处理器。
重要建议:
lambda函数应只包含一个表达式,过于复杂的逻辑应该用def定义正式函数。滥用lambda会严重降低代码可读性。如果lambda表达式超过了屏幕宽度,或者你需要写注释来解释它,那就该换用def了。
4.2 高阶函数实战:map, filter, reduce
这些函数接受一个函数和一个可迭代对象作为参数,对可迭代对象中的元素进行某种处理。
map(function, iterable, ...):将函数应用于可迭代对象的每一个元素,返回一个map对象(可迭代)。
numbers = [1, 2, 3, 4, 5] squared = map(lambda x: x*x, numbers) print(list(squared)) # 输出: [1, 4, 9, 16, 25] # 同时处理多个可迭代对象 a = [1, 2, 3] b = [10, 20, 30] sums = map(lambda x, y: x + y, a, b) print(list(sums)) # 输出: [11, 22, 33]在现代Python中,列表推导式[x*x for x in numbers]通常比map更受推荐,因为它更清晰。但map在处理多个可迭代对象或与lambda简单结合时仍有其价值。
filter(function, iterable):用函数过滤可迭代对象,只保留使函数返回True的元素。
numbers = range(-5, 6) positives = filter(lambda x: x > 0, numbers) print(list(positives)) # 输出: [1, 2, 3, 4, 5] # 等价列表推导式: [x for x in numbers if x > 0]同样,带条件的列表推导式通常是更Pythonic的选择。
functools.reduce(function, iterable[, initializer]):用二元函数对可迭代对象中的元素进行累积计算。function接收两个参数,第一个是累积值,第二个是当前元素。
from functools import reduce numbers = [1, 2, 3, 4, 5] # 计算乘积: ((((1*2)*3)*4)*5) product = reduce(lambda acc, x: acc * x, numbers) print(product) # 输出: 120 # 计算阶乘 5! factorial_5 = reduce(lambda acc, x: acc * x, range(1, 6)) print(factorial_5) # 输出: 120 # 带初始值 sum_from_100 = reduce(lambda acc, x: acc + x, numbers, 100) print(sum_from_100) # 输出: 115 (100+1+2+3+4+5)reduce的功能非常强大,但可读性有时不如显式的for循环。Python之父Guido van Rossum甚至曾考虑将其从语言中移除。除非操作非常标准(如求和、求积),否则谨慎使用,优先考虑可读性。
5. 生成器函数与yield:惰性求值的艺术
当函数体中使用yield关键字时,这个函数就变成了一个生成器函数。调用它不会立即执行,而是返回一个生成器对象。这是一种实现惰性求值、节省内存的强大工具。
5.1 生成器的工作机制
def simple_generator(): print("开始执行") yield 1 print("继续执行") yield 2 print("即将结束") yield 3 print("结束") gen = simple_generator() # 此时函数并未执行,只是创建了生成器对象 print(gen) # 输出: <generator object simple_generator at 0x...> # 每次调用 next(),函数从上次 yield 的位置恢复执行,直到遇到下一个 yield print(next(gen)) # 输出: “开始执行” 然后 1 print(next(gen)) # 输出: “继续执行” 然后 2 print(next(gen)) # 输出: “即将结束” 然后 3 # print(next(gen)) # 如果再次调用,会抛出 StopIteration 异常核心价值:生成器一次只产生一个值,并在产生后立即“暂停”,将控制权交还给调用者。这意味着你可以在不将所有数据加载到内存的情况下,处理海量数据流。
5.2 实际应用场景解析
场景一:读取大文件
def read_large_file(file_path): with open(file_path, 'r', encoding='utf-8') as f: for line in f: # 文件对象本身也是迭代器,逐行读取 yield line.strip() # 使用 for line in read_large_file('huge_log.txt'): process(line) # 每次只在内存中保留一行数据场景二:生成无限序列
def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b fib = fibonacci() for _ in range(10): print(next(fib), end=' ') # 输出: 0 1 1 2 3 5 8 13 21 34场景三:管道式数据处理(类似Unix管道)
def read_numbers(): for i in range(10): yield i def filter_even(iterable): for num in iterable: if num % 2 == 0: yield num def square(iterable): for num in iterable: yield num ** 2 # 构建处理管道:读取 -> 过滤偶数 -> 平方 pipeline = square(filter_even(read_numbers())) print(list(pipeline)) # 输出: [0, 4, 16, 36, 64]5.3 生成器表达式
这是创建简单生成器的语法糖,类似于列表推导式,但使用圆括号。
# 列表推导式:立即求值,生成完整列表 squares_list = [x**2 for x in range(1000000)] # 占用大量内存 # 生成器表达式:惰性求值,生成生成器对象 squares_gen = (x**2 for x in range(1000000)) # 几乎不占内存 print(next(squares_gen)) # 0 print(next(squares_gen)) # 1性能与内存权衡:生成器牺牲了少量速度(因为需要频繁的上下文切换)来换取巨大的内存优势。在处理序列未知或无限的数据,以及处理远超内存容量的大数据集时,生成器是唯一可行的选择。但在需要随机访问、多次遍历数据,或数据量本身就很小的场景下,直接使用列表可能更合适。
6. 函数参数的高级玩法:*args与**kwargs的深层理解
*args和**kwargs是Python函数定义中用于处理可变数量参数的机制,它们让函数接口变得极其灵活。
6.1 *args:接收任意数量的位置参数
*args将传入的所有位置参数打包成一个元组(tuple)。
def sum_all(*args): print(f"args的类型: {type(args)}, 值: {args}") return sum(args) print(sum_all(1, 2, 3)) # 输出: args的类型: <class 'tuple'>, 值: (1, 2, 3) \n 6 print(sum_all(1, 2, 3, 4, 5)) # 输出: args的类型: <class 'tuple'>, 值: (1, 2, 3, 4, 5) \n 156.2 **kwargs:接收任意数量的关键字参数
**kwargs将传入的所有关键字参数打包成一个字典(dict)。
def print_info(**kwargs): for key, value in kwargs.items(): print(f"{key}: {value}") print_info(name='Alice', age=30, city='New York') # 输出: # name: Alice # age: 30 # city: New York6.3 混合使用与参数解包
它们可以和其他固定参数一起使用,但顺序必须固定:(固定位置参数, *args, 固定关键字参数, **kwargs)。
更强大的是在函数调用时使用*和**进行参数解包。
def func(a, b, c, d=10): print(a, b, c, d) # 假设我们有一个元组和一个字典 args_tuple = (1, 2, 3) kwargs_dict = {'d': 40} # 解包传入 func(*args_tuple, **kwargs_dict) # 等价于 func(1, 2, 3, d=40) # 输出: 1 2 3 40实战应用:
- 编写装饰器:如前所述,为了让装饰器能处理任何函数,必须在
wrapper函数中使用*args, **kwargs。 - 子类化与super()调用:在面向对象编程中,子类的
__init__方法经常使用*args, **kwargs来将所有参数原样传递给父类。 - 创建灵活的API或包装函数:当你需要编写一个函数,它只是对另一个函数进行简单包装或路由时,可变参数非常有用。
def logged_get(url, **kwargs): print(f"GET请求: {url}") # 假设 requests.get 是目标函数 # return requests.get(url, **kwargs) # 将所有关键字参数原样传递
命名规范:
args和kwargs只是约定俗成的名称,你可以用*params和**options,但使用args/kwargs能让其他开发者立刻明白你的意图。清晰永远比聪明更重要。
7. 内置高阶函数与functools模块
Python的functools模块提供了一系列用于高阶函数(操作或返回其他函数的函数)的工具,它们能极大提升代码的抽象能力和表达能力。
7.1 partial:冻结函数参数
functools.partial用于“冻结”函数的部分参数,从而创建一个新的、参数更少的函数对象。
from functools import partial def power(base, exponent): return base ** exponent # 创建一个专门计算平方的函数 square = partial(power, exponent=2) # 创建一个专门计算立方的函数 cube = partial(power, exponent=3) print(square(5)) # 25, 等价于 power(5, 2) print(cube(3)) # 27, 等价于 power(3, 3) # 这在回调函数配置中非常有用,例如GUI按钮事件 # button.clicked.connect(partial(handle_click, user_id=123))7.2 lru_cache:自动记忆化缓存
这是一个装饰器,为函数提供最近最少使用(LRU)缓存。对于计算昂贵且经常被相同参数调用的纯函数(输出仅由输入决定),它能带来巨大的性能提升。
from functools import lru_cache import time @lru_cache(maxsize=128) # maxsize 指定缓存大小,None表示无限制(不推荐) def expensive_computation(n): print(f"Computing {n}...") time.sleep(2) # 模拟耗时计算 return n * n # 第一次调用,需要计算 print(expensive_computation(10)) # 输出: Computing 10... (等待2秒) 100 # 第二次用相同参数调用,直接从缓存返回结果 print(expensive_computation(10)) # 立即输出: 100 (无等待,无打印Computing) print(expensive_computation(20)) # 输出: Computing 20... (等待2秒) 400 print(expensive_computation(10)) # 仍然立即输出: 100注意事项:
- 被装饰的函数参数必须是可哈希的(如字符串、数字、元组),因为要用作字典的键。
- 函数不应有副作用,且返回值不应随时间或外部状态改变。
- 对于递归函数(如斐波那契数列),
lru_cache能将其时间复杂度从指数级降到线性级,效果惊人。
7.3 singledispatch:单分派泛型函数
它允许你根据第一个参数的类型,来定义不同的函数实现。这是一种简洁的实现多态的方式。
from functools import singledispatch @singledispatch def pretty_print(obj): return f"Unknown object: {repr(obj)}" @pretty_print.register(str) def _(text): return f"String: '{text}'" @pretty_print.register(int) @pretty_print.register(float) def _(number): return f"Number: {number:.2f}" @pretty_print.register(list) def _(lst): items = ', '.join(pretty_print(item) for item in lst) return f"List: [{items}]" print(pretty_print("hello")) # 输出: String: 'hello' print(pretty_print(3.14159)) # 输出: Number: 3.14 print(pretty_print([1, "test"])) # 输出: List: [Number: 1.00, String: 'test'] print(pretty_print({"a": 1})) # 输出: Unknown object: {'a': 1}这在处理多种数据格式的序列化、日志记录或数据展示时非常有用。
8. 作用域与命名空间解析规则:LEGB
理解变量在何处被查找,是避免UnboundLocalError等错误的关键。Python遵循LEGB规则:
- L (Local):局部作用域。在函数内部定义。
- E (Enclosing):闭包函数的外层函数作用域。
- G (Global):全局作用域。在模块级别定义。
- B (Built-in):内建作用域。包含
len、print等Python内置函数和异常。
x = 'global' # G def outer(): x = 'enclosing' # E def inner(): x = 'local' # L print(x) # 输出: local inner() print(x) # 输出: enclosing outer() print(x) # 输出: global关键陷阱:在局部作用域中修改全局变量
count = 0 def increment(): # 这行会报错: UnboundLocalError: local variable 'count' referenced before assignment # 因为Python发现你在函数内对count进行了赋值(count += 1), # 所以它将count视为局部变量,但局部变量count在加1之前并未定义。 count += 1 def correct_increment(): global count # 使用 global 关键字声明要修改的是全局变量 count += 1 correct_increment() print(count) # 输出: 1对于闭包中的外层变量,使用nonlocal关键字。
def counter(): num = 0 def increment(): nonlocal num # 声明 num 不是局部变量,而是外层函数的变量 num += 1 return num return increment c = counter() print(c(), c(), c()) # 输出: 1 2 3调试技巧:当你遇到变量作用域相关的错误时,可以使用
globals()和locals()函数来查看当前命名空间中的变量。在IPython或Jupyter中,%whos魔法命令也非常有用。理解LEGB规则,能让你清晰地知道代码在访问哪个变量,这是写出可靠函数的基础。
9. 函数式编程工具:itertools与更多模式
itertools模块提供了一系列用于操作迭代器的函数,它们返回的都是迭代器或生成器,完美契合函数式编程的惰性求值思想。
9.1 无限迭代器
import itertools # count: 从 start 开始,以 step 为步长无限计数 counter = itertools.count(start=10, step=2) print(next(counter), next(counter), next(counter)) # 10, 12, 14 # cycle: 无限循环一个可迭代对象 cycler = itertools.cycle('ABC') print([next(cycler) for _ in range(5)]) # ['A', 'B', 'C', 'A', 'B'] # repeat: 重复一个对象,可以指定次数 repeater = itertools.repeat('spam', 3) print(list(repeater)) # ['spam', 'spam', 'spam']9.2 组合迭代器
# chain: 将多个可迭代对象连接成一个 chained = itertools.chain('ABC', 'DEF', [1, 2, 3]) print(list(chained)) # ['A', 'B', 'C', 'D', 'E', 'F', 1, 2, 3] # zip_longest: 类似于zip,但以最长的可迭代对象为准,用 fillvalue 填充缺失值 zipped = itertools.zip_longest('AB', 'xyz', fillvalue='-') print(list(zipped)) # [('A', 'x'), ('B', 'y'), ('-', 'z')] # permutations: 排列 perms = itertools.permutations('ABC', 2) # 长度为2的所有排列 print(list(perms)) # [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')] # combinations: 组合 combs = itertools.combinations('ABC', 2) # 长度为2的所有组合(无序) print(list(combs)) # [('A', 'B'), ('A', 'C'), ('B', 'C')]9.3 数据切片与分组
# islice: 对迭代器进行切片,类似于列表切片,但惰性求值 numbers = itertools.count() first_ten = itertools.islice(numbers, 10) # 取前10个 print(list(first_ten)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # takewhile / dropwhile: 根据条件取/舍元素 data = [1, 4, 6, 4, 1] print(list(itertools.takewhile(lambda x: x < 5, data))) # [1, 4] print(list(itertools.dropwhile(lambda x: x < 5, data))) # [6, 4, 1] # groupby: 将迭代器中相邻的重复元素分组(需要先排序!) data = sorted(['apple', 'banana', 'cherry', 'apricot', 'blueberry'], key=len) for key, group in itertools.groupby(data, key=len): print(f"Length {key}: {list(group)}") # 输出: # Length 5: ['apple', 'cherry'] # Length 6: ['banana', 'apricot'] # Length 9: ['blueberry']掌握itertools能让你在处理数据流时写出既高效又简洁的代码,避免不必要的中间列表创建。
10. 函数签名与注解:让接口更清晰
Python 3引入了函数注解,允许你为参数和返回值附加元数据。虽然Python解释器本身不强制使用这些注解(它们只是存储在函数的__annotations__属性中),但它们对文档、类型检查工具(如mypy)、IDE智能提示和框架(如FastAPI)非常有价值。
10.1 类型注解(主流用法)
def greet(name: str, times: int = 1) -> str: """返回重复的问候语。""" return ' '.join([f"Hello, {name}!"] * times) print(greet("World", 2)) # Hello, World! Hello, World! print(greet.__annotations__) # 输出: {'name': <class 'str'>, 'times': <class 'int'>, 'return': <class 'str'>}10.2 使用inspect模块获取签名信息
inspect.signature函数可以获取函数签名的丰富信息。
import inspect sig = inspect.signature(greet) print(sig) # (name: str, times: int = 1) -> str for param_name, param in sig.parameters.items(): print(f"{param_name}: kind={param.kind}, default={param.default}, annotation={param.annotation}") # 输出: # name: kind=POSITIONAL_OR_KEYWORD, default=<class 'inspect._empty'>, annotation=<class 'str'> # times: kind=POSITIONAL_OR_KEYWORD, default=1, annotation=<class 'int'> print(sig.return_annotation) # <class 'str'>10.3 结合装饰器进行运行时类型检查
你可以利用注解和inspect模块,自己实现一个简单的运行时类型检查装饰器。
def type_check(func): @wraps(func) def wrapper(*args, **kwargs): sig = inspect.signature(func) bound_args = sig.bind(*args, **kwargs) bound_args.apply_defaults() for name, value in bound_args.arguments.items(): if name in func.__annotations__: expected_type = func.__annotations__[name] if not isinstance(value, expected_type): raise TypeError(f"参数 '{name}' 应为 {expected_type} 类型,但传入的是 {type(value)} 类型") result = func(*args, **kwargs) if 'return' in func.__annotations__: expected_return_type = func.__annotations__['return'] if not isinstance(result, expected_return_type): raise TypeError(f"返回值应为 {expected_return_type} 类型,但返回的是 {type(result)} 类型") return result return wrapper @type_check def add(a: int, b: int) -> int: return a + b print(add(1, 2)) # 3 # print(add(1, "2")) # 会引发 TypeError个人建议:即使在小型项目中,也强烈建议使用类型注解。它几乎零成本,却能极大提升代码的可读性和可维护性。配合PyCharm、VSCode等现代IDE,可以获得精准的代码补全和错误提示。对于大型项目或团队协作,使用
mypy进行静态类型检查,能在运行前就捕获许多潜在的类型错误,是提升代码质量的利器。
