蒂莫西正在开发一个文本处理模块时,注意到了一些奇怪的事情。“玛格丽特,我有五个函数都返回字符串,并且它们都需要将结果转换为大写。我在每个函数的末尾都调用了.upper()。一定有更好的方法。”
玛格丽特查看了他的代码。“你说得对——这太重复了。当你发现自己在多个函数的输出中添加相同的转换时,这正是使用装饰器的完美场景。”
“装饰器可以转换返回值?”蒂莫西问。“我以为装饰器只是用于日志记录和计时。”
问题:重复的输出转换
玛格丽特向他展示了他正在做的事情:
def get_greeting(name):
return f"hello, {name}".upper()
def get_status():
return "系统在线".upper()
def get_error_message(code):
return f"错误代码: {code}".upper()</span
“看到了吗?”玛格丽特问道。“每个函数的末尾都有 .upper()。这就是实现细节渗透到你的业务逻辑中。”
蒂莫西点了点头。“如果我决定要标题大小写,我就得在每个函数中都进行更改。”
“没错。现在看看装饰器是如何解决这个问题的。”
def uppercase_result(func):
"""装饰器,用于将函数'的字符串结果转换为大写。"""
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if isinstance(result, str):
return result.upper()
return result
return wrapper
@uppercase_result
def get_greeting(name):
"""返回个性化的问候语。"""
return f"你好, {name}"
@uppercase_result
def get_status():
"""返回系统状态。"""
return "系统在线"
@uppercase_result
def get_error_message(code):
"""返回错误信息。"""
return f"错误代码: {code}"
# 用法
print(get_greeting("爱丽丝"))
print(get_status())
print(get_error_message(404))
理解返回值拦截
蒂莫西研究了装饰器。“所以包装器调用我的原始函数,获取结果,然后在返回之前进行转换?”
玛格丽特向他展示了结构:
树状视图:
uppercase_result(func)
"装饰器,用于将函数的字符串结果转换为大写。"
wrapper()
result = func(*args, **kwargs)
如果 isinstance(result, str)
└── 返回 result.upper()
返回 result
返回 wrapper
@uppercase_result
get_greeting(name)
'返回个性化问候。'
返回 f'hello, {name}'
@uppercase_result
get_status()
'返回系统状态。'
返回 'system online'
@uppercase_result
get_error_message(code)
'返回错误信息。'
返回 f'error code: {code}'
print(get_greeting('Alice'))
print(get_status())
print(get_error_message(404))
中文视图:
函数 uppercase_result(func):
评估 "装饰器,将函数的字符串结果转换为大写。"。
函数包装器():
设置结果为 func(*args, **kwargs)。
如果 isinstance(result, str):
返回 result.upper()。
返回结果。
返回包装器。
装饰器 @uppercase_result
函数 get_greeting(name):
评估 '返回个性化问候。'。
返回 f'你好, {name}'。
装饰器 @uppercase_result
函数 get_status():
评估 '返回系统状态。'。
返回 '系统在线'。
装饰器 @uppercase_result
函数 get_error_message(code):
评估 '返回错误信息。'。
返回 f'错误代码: {code}'。
评估 print(get_greeting('Alice'))。
评估 print(get_status())。
评估 print(get_error_message(404))。
“看看这个流程,”玛格丽特说。“包装器调用 func(*args, **kwargs),存储结果,检查它是否是字符串,然后才应用 .upper()。如果结果不是字符串,它将原封不动地传递。”蒂莫西追踪了执行过程:
HELLO, ALICE
SYSTEM ONLINE
ERROR CODE: 404
“所以装饰器拦截返回值,转换它,然后返回转换后的版本?我的函数只是返回它们的正常值,而装饰器处理大写?”
“没错。你的函数保持干净,专注于它们的逻辑。转换由装饰器一致地应用。”
类型安全的转换
蒂莫西注意到装饰器中的一些事情。“你在调用 .upper() 之前检查 isinstance(result, str)。为什么?”
“因为并不是所有函数都返回字符串,”玛格丽特说。“看看如果我们装饰一个返回数字的函数会发生什么:”
@uppercase_result
def get_error_code():
"""返回一个数字,不应受到影响。"""
return 404
print(get_error_code()) # 输出: 404
“装饰器检查类型,仅在适当时应用转换。非字符串结果保持不变。”
蒂莫西点了点头。“所以装饰器是防御性的——它转换它能转换的内容,其他的则不作处理。”
“没错。这是转换装饰器的一个好模式。不要假设你知道函数返回什么;先检查一下。”
何时使用返回值装饰器
“那么我什么时候会使用这个模式?”蒂莫西问。
玛格丽特列出了使用场景:
“当你需要时,使用返回值转换装饰器:
- 对函数输出应用一致的格式(大写、小写、标题格式)
- 一致地对数字结果进行四舍五入或格式化
- 对输出进行清理或转义以确保安全性
- 在数据格式之间转换(字典到JSON,对象到字符串)
- 添加元数据或将结果包装在标准结构中
她给他展示了另一个例子:
def round_result(decimals=2):
"""装饰器,用于将数值结果四舍五入到指定的小数位。"""
def decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if isinstance(result, (int, float)):
return round(result, decimals)
return result
return wrapper
return decorator
@round_result(decimals=2)
def calculate_average(numbers):
"""计算一组数字的平均值。"""
return sum(numbers) / len(numbers)
print(calculate_average([1.234, 2.567, 3.891])) # 输出: 2.56
“看到了吗?同样的模式 – 调用函数,检查结果类型,如果合适则应用转换。”
蒂莫西开始看到可能性了。“所以我可以有一个 @to_json 装饰器,将字典结果转换为 JSON 字符串,或者一个 @sanitize_html 装饰器,将字符串结果中的 HTML 进行转义?”
“没错。你对函数输出应用的任何一致性转换都是装饰器的候选者。”
保持函数的清晰
玛格丽特总结了这个课程。“关键的见解是关注点的分离。你的函数专注于计算或生成它们的结果。装饰器处理展示、格式化或转换。”
蒂莫西看着他清理过的函数:
@uppercase_result
def get_greeting(name):
return f"hello, {name}"
“现在干净多了。没有 .upper() 让返回语句变得杂乱。这个函数只返回它计算的结果,而装饰器处理格式化。”
“这就是返回值装饰器的力量,”玛格丽特说。“它们让你在不触碰函数核心逻辑的情况下,始终如一地应用转换。如果你改变了对转换的想法,只需更改一次装饰器,而不是每个函数。”
蒂莫西测试了更改装饰器:
def titlecase_result(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if isinstance(result, str):
return result.title()
return result
return wrapper
“现在我只需将 @uppercase_result 替换为 @titlecase_result,我的所有函数的行为就会改变。结构清晰地显示了这一点——装饰器包装了函数,拦截返回值,并在传递之前进行转换。”
自己分析 Python 结构: 下载 Python 结构查看器 – 一款免费的工具,可以以树形和简单英语视图显示代码结构。支持离线使用,无需安装。

