Skip to content

Python 缓存与 CLI 工具入门(一):函数装饰器非常重要

· 15 min

在正式展开教程之前,我们先来回答两个基础但关键的问题:

缓存(Cache)与命令行工具(CLI)#

Python 里,缓存(Cache)是什么?#

缓存是一种性能优化机制,它通过存储计算结果来避免重复执行耗时的操作。

一想到缓存,可能很多人想到的是网络开发里面的 HTTP 缓存,但是缓存其实还有很多的用途。比如,在 Python 中,尤其是当你编写递归函数、慢速计算或网络请求等重复调用的函数时,缓存能让你的程序快得惊人。常用的工具就是 functools.lru_cache 装饰器,它能自动保存你之前计算过的结果。

为什么缓存这么重要?

  • 减少函数重复计算,提升速度
  • 节省资源(CPU、内存等)
  • 这是开发高性能应用程序的关键技巧

在 Python 中,缓存通常是通过装饰器来实现的。最常用的就是 functools.lru_cache,它可以自动缓存函数调用的结果。之后的小结里,我们就会详细介绍这个装饰器。

CLI 工具(命令行工具)是什么?#

CLI 是 Command Line Interface 的缩写,中文是“命令行界面”。CLI 工具指的是可以在终端(命令行)中运行的程序,用户通过输入指令与程序交互。

你写的 Python 脚本,本质上就是一个 CLI 工具。当你运行:

Terminal window
python your_script.py

你就是在用命令行运行程序。

为什么重要?

  • CLI 工具可以批量处理任务,无需图形界面
  • 更方便部署、调试与自动化
  • 是数据工程、系统管理、脚本开发等工作的基础技能

掌握缓存 + CLI 工具,你就拥有了加速程序、提升效率的一把“组合钥匙”。

什么是装饰器?#

装饰器(Decorator)是 Python 中的一个语法糖,允许你在函数或类定义时进行额外的功能“包装”。

最简单的形式就是用 @装饰器名 放在函数定义的上一行。例如:

def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before call")
result = func(*args, **kwargs)
print("After call")
return result
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()

输出:

Before call
Hello!
After call

这段代码做了什么?首先:

def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before call")
result = func(*args, **kwargs)
print("After call")
return result
return wrapper

这是一个装饰器函数,它接受一个函数 func 作为参数,并返回一个新的函数 wrapper

装饰器的作用:

接下来这段代码:

@my_decorator
def say_hello():
print("Hello!")

这就是使用装饰器的方式。

等价于:

def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)

最终效果:

当你执行 say_hello(),实际上运行的是 wrapper() 这个函数,所以打印的顺序是:

Before call
Hello!
After call

也就是说,这个例子展示了装饰器的本质:用一个函数(wrapper)包装另一个函数(say_hello)来扩展它的功能,而无需修改原函数的代码。


但是,为什么 my_decorator 里面还要定义一个 wrapper 函数?不能直接写一个装饰器函数吗?#

简短回答是:

可以直接写 wrapper 函数,但如果你想写通用的装饰器(能装饰任意函数、带参数、复用),那就必须用一个外层函数包住一个内层的 wrapper

让我们通过几个例子说明这个“结构”的必要性。

我们能不能只写一个 wrapper 装饰函数?#

可以,但它只能装饰一个特定的函数

比如这样:

def wrapper():
print("Before")
print("Hello!")
print("After")
wrapper()

这个 wrapper() 就是个普通函数,根本没“装饰”谁。

你也可以“硬包装”一个固定函数:

def wrapper():
print("Before")
say_hello()
print("After")

这种写法只适用于 say_hello(),不具备通用性,也不能用 @装饰器名 的形式来装饰其他函数。

真正通用的装饰器需要两层函数结构#

我们再来看一遍标准装饰器写法:

def my_decorator(func): # <--- 外层,接受要装饰的函数
def wrapper(*args, **kwargs): # <--- 内层,包装逻辑
print("Before")
result = func(*args, **kwargs)
print("After")
return result
return wrapper # 返回包装后的函数

这两层结构的作用:

层级作用
my_decorator接收原始函数作为参数
wrapper定义新的行为(前后加东西),并调用原函数
return wrapper返回这个“增强版函数”给 Python 使用(替换原函数)

这样你才能写出:

@my_decorator
def say_hello():
print("Hello!")

Python 在解释 @my_decorator 的时候,实际上做了:

say_hello = my_decorator(say_hello)

类比一下,更容易理解,你可以把这个结构想象成做饭时用保鲜膜包住便当

如果你真的只想装饰一个固定函数,确实可以不嵌套#

比如你不需要装饰通用函数,只想给 say_hello() 加点东西,那可以这样:

def say_hello():
print("Hello!")
def say_hello_with_log():
print("Before")
say_hello()
print("After")
say_hello_with_log()

这没错,但不能用 @语法糖,也不能通用。

内层 wrapper() 是真正加逻辑的地方,外层函数 my_decorator() 是为了让这个装饰器通用、动态、可复用。两层结构是“装饰器”的基本语法模式。

装饰器是 Python 中一种非常强大的语法机制,常用于:


那为什么要用装饰器来“包装”函数?#

1. 不修改原函数代码,就能扩展功能#

假设你有一个已经写好的函数(比如别人写的库函数、或者你在多个地方用过的函数),你现在想:

如果你直接在原函数里加逻辑,会:

使用装饰器,你就可以做到不动老代码,功能照样加上去。

2. 装饰器可以复用,多个函数一起增强#

比如你写了一个日志装饰器:

def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"[LOG] Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper

你可以同时加在很多函数上:

@log_decorator
def login():
...
@log_decorator
def logout():
...

统一加功能,非常方便。

3. 符合“开放封闭原则”(OCP)#

这是软件设计中的一个经典原则:“对扩展开放,对修改封闭。”

装饰器就完美地做到了:你可以扩展函数行为,但不修改它的源代码。

那么,什么是“开放封闭原则”(Open-Closed Principle, OCP)?

这是面向对象设计(OOP)中的五大原则(SOLID)之一:

“对扩展开放”: 你可以添加新功能或逻辑

“对修改封闭”:但你不应该动原来的代码

为什么要这样设计?因为:

所以我们希望: 我能增加功能,但又不碰老代码。而这,正是装饰器的价值所在。

举个例子:没用装饰器时

def send_email():
print("Sending email...")

现在你要加一个功能:记录日志

一种方法是直接改原函数:

def send_email():
print("[LOG] Calling send_email")
print("Sending email...")

这就违反了 OCP,因为你修改了函数内部。

用装饰器来扩展功能(符合 OCP)

def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"[LOG] Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_decorator
def send_email():
print("Sending email...")

这里你就:

这就是对“扩展开放、对修改封闭”的经典体现。

再举一个更复杂的例子

比如你有一个函数是机器学习模型预测接口:

def predict(data):
return model.predict(data)

现在你想加:

不用装饰器时,你可能要这样改:

def predict(data):
start = time.time()
print("Predicting...")
result = model.predict(data)
print("Time taken:", time.time() - start)
return result

很快就一团糟,而且改了原函数。

用装饰器就可以优雅拆分:

def timing(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"[Time] {func.__name__} took {time.time() - start:.4f}s")
return result
return wrapper
@timing
def predict(data):
return model.predict(data)

这就扩展了功能(加时间统计),又封闭了原函数定义,干净漂亮。

总结这个小点:

装饰器让你在不动原函数的情况下,灵活加功能 。这就是“开放封闭原则”的体现。


4. Python 本身大量使用装饰器机制#

如果你用过:

这些全都是装饰器。掌握它,等于掌握了 Python 中很多高级用法。

打个比喻:

装饰器就像给电器插上一个“智能插座”:

如果你理解了装饰器,接下来看 @lru_cache 就会感觉轻松多了,因为它的本质就是一个预定义好的装饰器。


课后作业练习(先自己做,再对后面的答案)#

请完成以下练习题,检查你是否理解了装饰器的结构与作用。

选择题#

  1. 下列关于装饰器的说法中,错误的是? A. 装饰器可以扩展函数功能但不修改原函数 B. 装饰器语法是 Python 的语法糖 C. 装饰器无法作用于带参数的函数 D. 装饰器可以复用到多个函数上

  2. @functools.lru_cache() 是什么作用? A. 检查函数参数类型 B. 缓存函数返回值,避免重复计算 C. 限制函数调用次数 D. 实现递归算法

判断题#

  1. 装饰器只能用于函数,不能用于类。(True / False)
  2. 使用装饰器的最大好处是代码运行更快。(True / False)
  3. 使用 @装饰器名 等价于:函数名 = 装饰器名(函数名)。(True / False)

编程题#

6. 编写一个装饰器 log_args,能在函数执行前,打印该函数的所有参数值。#

要求输出格式为:

[ARGS] x=1, y=2

并使用它装饰一个函数 add(x, y),返回两数之和。

7. 请你写一个装饰器 timer,用于计算函数运行时间。#

要求:


标准答案#

选择题答案#

  1. C(装饰器是可以用于带参数函数的,只需要使用 *args, **kwargs
  2. B

判断题答案#

  1. False(装饰器也可以用于类)
  2. False(装饰器的最大优势是 功能增强而不修改原代码,不是加速本身)
  3. True

编程题答案#

第6题:#

def log_args(func):
def wrapper(*args, **kwargs):
args_repr = ", ".join(f"{arg}" for arg in args)
kwargs_repr = ", ".join(f"{k}={v}" for k, v in kwargs.items())
all_args = ", ".join(filter(None, [args_repr, kwargs_repr]))
print(f"[ARGS] {all_args}")
return func(*args, **kwargs)
return wrapper
@log_args
def add(x, y):
return x + y
print(add(1, 2))

输出示例:

[ARGS] 1, 2
3

第7题:#

import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
duration = time.time() - start
print(f"[TIMER] {func.__name__} took {duration:.4f}s")
return result
return wrapper
@timer
def slow_add(x, y):
time.sleep(1) # 模拟耗时操作
return x + y
print(slow_add(3, 4))

输出示例:

[TIMER] slow_add took 1.0001s
7

如果你准备好了,那就让我们进入到下一篇教程。