1. 从“人狗大作战”到企业级应用:为什么函数是Python的基石
最近在社区里看到一个挺有意思的帖子,有人在问“人狗大作战python代码2023”这个梗,想找源码学习。点进去一看,发现核心其实就是用几个函数来模拟角色属性和战斗过程。这让我想起刚学Python那会儿,总觉得函数就是个“打包代码”的工具,能省点重复劳动。直到后来参与真实项目,被几千行毫无结构的脚本折磨得死去活来,才真正明白:函数远不止是“打包”,它是构建可维护、可协作、可测试代码的绝对核心,是区分脚本小子和合格工程师的第一道分水岭。
无论是你想复现一个“人狗大作战”的小游戏,还是处理yolov8的损失函数改进,亦或是用pandas做数据分析与可视化,函数都是你绕不开的武器。很多新手卡壳的地方,比如vscode配置Python环境时路径问题,或是遇到“无法将‘xxx’项识别为cmdlet、函数、脚本文件...”这类错误,其根源往往是对“函数”以及更广义的“可执行单元”在系统中的作用域理解不清。这篇文章,我就结合十多年的踩坑经验,把Python函数从里到外、从基础到高阶掰开揉碎了讲清楚。目标很明确:让你不仅会“用”函数,更能“设计”函数,写出让人(包括三个月后的你自己)看了直呼专业的代码。
2. 函数基础:超越def的声明与调用
很多人以为定义一个函数就是用def关键字,然后调用它。这没错,但只对了一半。真正理解函数,要从它在Python运行时中的“身份”开始。
2.1 函数对象:一等公民的实质
在Python中,函数是“一等对象”。这意味着函数可以像整数、字符串、列表一样,被赋值给变量、作为参数传递给另一个函数、作为另一个函数的返回值,甚至可以被存储在数据结构中。这是Python函数式编程能力的基石。
def greet(name): return f"Hello, {name}!" # 1. 赋值给变量 my_func = greet print(my_func("Alice")) # 输出: Hello, Alice! # 2. 存储在列表中 func_list = [greet, str.lower, abs] print(func_list[0]("Bob")) # 输出: Hello, Bob! # 3. 作为参数传递 def apply_func(func, value): return func(value) print(apply_func(greet, "Charlie")) # 输出: Hello, Charlie! print(apply_func(len, "Hello")) # 输出: 5为什么这个特性至关重要?它使得代码模式极其灵活。比如,map、filter、sorted等高阶函数都依赖于接收函数作为参数。当你配置一个回调函数(callback function),或者像在yolov8中自定义损失函数时,你本质上就是在利用函数作为对象进行传递。
2.2 定义与调用的完整语法与细节
定义一个函数的基本骨架如下:
def function_name(parameter1, parameter2, ...): """可选的文档字符串,用于说明函数功能。""" # 函数体 # 可以包含任意语句 return value # 可选的返回语句关键细节与常见坑点:
参数(Parameters) vs 实参(Arguments):这是两个最容易混淆的概念。
parameter是函数定义时括号里的变量,即“形参”。argument是函数调用时传入的具体值,即“实参”。在函数内部,你操作的是形参,但实际影响的是传入的实参(对于可变对象需特别注意)。函数体与作用域:函数体是一个新的、局部的作用域。在函数内部定义的变量(包括形参)都是局部变量,除非你用
global或nonlocal关键字声明。这避免了命名冲突,是模块化编程的基础。x = 10 # 全局变量 def modify(): x = 20 # 这是一个新的局部变量,不影响全局的x print(f"局部 x: {x}") modify() # 输出: 局部 x: 20 print(f"全局 x: {x}") # 输出: 全局 x: 10return语句:return用于结束函数执行并返回一个或多个值。如果没有return语句,或者return后面没有跟值,函数默认返回None。- 返回多个值:实际上是返回一个元组(tuple),利用解包可以优雅地接收。
def get_coordinates(): x = 10 y = 20 return x, y # 等价于 return (x, y) a, b = get_coordinates() # 元组解包 print(a, b) # 输出: 10 20
- 返回多个值:实际上是返回一个元组(tuple),利用解包可以优雅地接收。
2.3 从“无法识别”错误理解函数查找路径
很多新手在命令行或VSCode里会遇到类似无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称的错误。虽然这是系统命令的问题,但其原理和Python寻找函数一模一样。
当Python执行function_name()时,它按照以下顺序查找:
- 局部作用域(Local):当前函数内部。
- 闭包作用域(Enclosing):嵌套函数的外层函数。
- 全局作用域(Global):当前模块(文件)级别。
- 内置作用域(Built-in):Python内置的函数,如
len,print。
这就是LEGB规则。如果在这四个地方都找不到,就会抛出NameError。系统命令报错也是因为它在系统的PATH环境变量(相当于Python的sys.path)中找不到对应的可执行文件。
实操心得:在
VSCode中配置Python环境,核心就是确保你选择的Python解释器路径正确,并且其Scripts(Windows)或bin(Mac/Linux)目录被添加到系统PATH中。这样,该解释器下的可执行命令(如pip,pytest)才能被终端和VSCode识别。函数找不到和命令找不到,本质是同一个问题——搜索路径不对。
3. 参数传递的深水区:位置、关键字与可变参数
参数是函数与外界沟通的桥梁。Python提供了极其灵活的参数机制,但灵活也意味着容易用错。
3.1 位置参数与关键字参数
这是最基础的两种传参方式。
- 位置参数:调用时根据参数的位置顺序进行匹配。
- 关键字参数:调用时通过
参数名=值的形式指定,顺序无关。
def describe_pet(pet_name, animal_type='dog'): print(f"I have a {animal_type} named {pet_name}.") # 位置参数调用 describe_pet('Hamster', 'Harry') # I have a Harry named Hamster. (注意,顺序错了!) describe_pet('Harry', 'Hamster') # I have a Hamster named Harry. (正确) # 关键字参数调用(清晰,避免顺序错误) describe_pet(pet_name='Harry', animal_type='Hamster') describe_pet(animal_type='Hamster', pet_name='Harry') # 顺序不影响经验之谈:对于参数多于2个的函数,强烈建议使用关键字参数调用。这大大提高了代码的可读性,并避免了因参数顺序错误导致的隐蔽bug。在团队协作中,这几乎是一条不成文的规范。
3.2 默认参数:陷阱与最佳实践
默认参数让函数调用更简洁。但有一个著名的“陷阱”:
def append_to_list(value, my_list=[]): # 危险!默认参数是可变对象 my_list.append(value) return my_list print(append_to_list(1)) # 输出: [1] print(append_to_list(2)) # 输出: [1, 2] !第二次调用竟然保留了第一次的结果!原因:默认参数my_list=[]在函数定义时就被求值并创建,这个列表对象在后续所有调用中都被复用,而不是每次调用都新建一个空列表。
解决方案:使用None作为默认值,在函数体内进行判断和创建。
def append_to_list_safe(value, my_list=None): if my_list is None: my_list = [] my_list.append(value) return my_list print(append_to_list_safe(1)) # [1] print(append_to_list_safe(2)) # [2] 符合预期3.3*args与**kwargs:处理可变数量参数
这两个魔法变量是Python动态特性的体现。
*args:用于接收任意数量的位置参数,在函数内部它是一个元组。**kwargs:用于接收任意数量的关键字参数,在函数内部它是一个字典。
def make_profile(name, *skills, **personal_info): print(f"Name: {name}") print(f"Skills: {skills}") # 元组,如 ('Python', 'SQL') print(f"Personal Info: {personal_info}") # 字典,如 {'city': 'Beijing', 'age': 30} make_profile('Alice', 'Python', 'SQL', 'Docker', city='Shanghai', years_exp=5)高级用法:参数解包你还可以在调用函数时使用*和**来解包序列和字典。
def connect(host, port, username, password): print(f"Connecting to {host}:{port} as {username}") # 假设配置来自一个列表和一个字典 server_config = ['db.example.com', 5432] credential = {'username': 'admin', 'password': 'secret'} connect(*server_config, **credential) # 等价于 connect('db.example.com', 5432, username='admin', password='secret')这种技巧在调用库函数或父类方法时非常有用,尤其是当参数动态生成时。
3.4 仅限关键字参数与仅限位置参数(Python 3.8+)
为了增强API的清晰度和健壮性,Python引入了更严格的参数控制。
仅限关键字参数:在
*args或一个单独的*之后定义的参数,必须通过关键字传入。def safe_divide(numerator, denominator, *, ignore_zero=False): """ignore_zero必须用关键字指定""" if denominator == 0 and not ignore_zero: raise ValueError("Denominator cannot be zero!") return numerator / denominator # safe_divide(10, 0, True) # 错误!True会被*args捕获或导致TypeError safe_divide(10, 0, ignore_zero=True) # 正确,返回inf这强制调用者明确意图,避免了像
True这样的字面量带来的歧义。仅限位置参数:在参数列表中使用
/来指定其前的参数必须通过位置传入。def vector(x, y, /, z): """x和y必须是位置参数""" return (x**2 + y**2 + z**2) ** 0.5 vector(1, 2, 3) # 正确 # vector(x=1, y=2, z=3) # 错误!x和y不能使用关键字 vector(1, 2, z=3) # 正确,z可以用关键字这常用于函数参数名没有实际语义,或者你想在未来更改参数名而不影响现有调用代码的情况。
设计原则:在设计供他人使用的函数(如库的API)时,合理使用仅限关键字和仅限位置参数,可以极大地提升接口的清晰度和健壮性,减少误用。对于内部工具函数,则可以根据团队习惯灵活选择。
4. 作用域、闭包与装饰器:理解函数的“生存环境”
函数不是孤立的,它存在于作用域链中。理解这一点,是掌握高级用法的关键。
4.1 变量查找的LEGB规则再深入
我们之前提到了LEGB规则。一个常见的困惑是:在函数内修改全局变量。
count = 0 def increment(): count += 1 # UnboundLocalError! Python认为你在修改局部变量count,但此时它还未定义。 increment()修正:需要使用global关键字声明。
count = 0 def increment(): global count # 声明count引用的是全局变量 count += 1 increment() print(count) # 输出: 1对于嵌套函数,如果想修改外层(非全局)函数的变量,则需要使用nonlocal关键字。
def outer(): counter = 0 def inner(): nonlocal counter # 声明counter引用的是外层函数的变量 counter += 1 return counter return inner func = outer() print(func()) # 1 print(func()) # 24.2 闭包:携带状态的函数
上面outer和inner的例子,其实已经演示了一个简单的闭包。闭包是指一个函数(inner)引用了其外部作用域(outer的函数作用域)中的变量(counter),并且这个内部函数在外部函数执行完毕后仍然存活(被返回出去了)。
闭包的核心价值在于它能够“记住”它被创建时的环境。counter对于inner来说,就像一个私有的、持久化的状态。
一个更实用的例子是创建一个“函数工厂”,用于生成具有特定配置的函数:
def make_multiplier(factor): """返回一个将输入乘以factor的函数""" def multiplier(x): return x * factor return multiplier double = make_multiplier(2) triple = make_multiplier(3) print(double(5)) # 10 print(triple(5)) # 15 # double和triple各自记住了自己的factor(2和3)4.3 装饰器:闭包的终极应用
装饰器可能是Python中最优雅、最强大的语法糖之一,它本质上是一个返回函数的高阶函数,使用了闭包来“包装”原函数。
基础装饰器模板:
def my_decorator(func): def wrapper(*args, **kwargs): # 在调用原函数前做一些事 print(f"Calling {func.__name__}") result = func(*args, **kwargs) # 调用原函数 # 在调用原函数后做一些事 print(f"{func.__name__} finished") return result return wrapper @my_decorator def say_hello(name): print(f"Hello, {name}!") say_hello("World") # 输出: # Calling say_hello # Hello, World! # say_hello finished@my_decorator只是语法糖,它等价于say_hello = my_decorator(say_hello)。
装饰器的核心用途:
- 日志记录:自动记录函数的调用、参数和返回值。
- 性能测试:计时函数运行时间。
- 权限校验:在Web框架中检查用户是否登录。
- 缓存(Memoization):存储函数计算结果,避免重复计算。
- 输入验证/格式化:统一处理函数输入。
带参数的装饰器:如果需要装饰器本身也能接收参数,就需要再嵌套一层。
def repeat(num_times): """一个执行指定次数的装饰器工厂""" def decorator_repeat(func): def wrapper(*args, **kwargs): for _ in range(num_times): result = func(*args, **kwargs) return result # 通常返回最后一次的结果 return wrapper return decorator_repeat @repeat(num_times=3) def greet(name): print(f"Hello {name}") greet("Alice") # 输出: # Hello Alice # Hello Alice # Hello Alice使用functools.wraps保持元信息:直接使用装饰器会导致原函数的名称、文档字符串等元信息被包装函数wrapper覆盖。使用functools.wraps可以修复这个问题。
from functools import wraps def my_decorator(func): @wraps(func) # 将原函数的元信息复制到wrapper中 def wrapper(*args, **kwargs): # ... 包装逻辑 ... return func(*args, **kwargs) return wrapper踩坑实录:装饰器会改变函数的签名,这在依赖函数签名的场景(如某些Web框架的路由)可能会出问题。
functools.wraps是标准解决方案。另外,多层装饰器的应用顺序是“从下往上”,即最靠近函数定义的装饰器最先执行。
5. 函数式编程工具:map,filter,reduce与Lambda
Python虽然不是纯函数式语言,但提供了一些强大的函数式编程工具,能让代码更简洁、表达力更强。
5.1lambda表达式:匿名函数
lambda用于创建小巧的、匿名的函数对象。它的语法是:lambda arguments: expression。表达式的结果会自动返回。
# 等价于 def add(x, y): return x + y add = lambda x, y: x + y print(add(2, 3)) # 5 # 更常见的用法是作为参数传递给高阶函数 points = [(1, 2), (3, 1), (5, -1), (2, 4)] # 按每个元组的第二个元素排序 points_sorted = sorted(points, key=lambda point: point[1]) print(points_sorted) # [(5, -1), (3, 1), (1, 2), (2, 4)]使用建议:lambda适用于逻辑非常简单(通常一行表达式能搞定)且只在一处使用的函数。如果逻辑复杂或需要复用,请使用def定义正式函数。
5.2map,filter与列表推导式
map(function, iterable, ...):将函数应用于可迭代对象的每一个元素,返回一个map对象(可迭代)。
numbers = [1, 2, 3, 4, 5] squared = map(lambda x: x**2, numbers) print(list(squared)) # [1, 4, 9, 16, 25]filter(function, iterable):用函数过滤可迭代对象,函数返回True的元素被保留,返回一个filter对象。
numbers = [1, 2, 3, 4, 5, 6] evens = filter(lambda x: x % 2 == 0, numbers) print(list(evens)) # [2, 4, 6]现代Python更推荐使用列表推导式,它们通常更易读,性能也相当。
# 等价于上面的map squared = [x**2 for x in numbers] # 等价于上面的filter evens = [x for x in numbers if x % 2 == 0] # 还可以组合 squared_evens = [x**2 for x in numbers if x % 2 == 0]5.3functools.reduce:累积计算
reduce(function, iterable[, initializer]):用带有两个参数的函数从左到右累积地应用在可迭代对象上,将其缩减为单个值。
from functools import reduce numbers = [1, 2, 3, 4, 5] # 计算乘积:((((1*2)*3)*4)*5) product = reduce(lambda x, y: x * y, numbers) print(product) # 120 # 计算阶乘 5! factorial_5 = reduce(lambda x, y: x * y, range(1, 6)) print(factorial_5) # 120虽然reduce很强大,但很多时候用显式的for循环会更清晰。Python之父Guido van Rossum也曾表示过对reduce可读性的担忧。所以,请谨慎使用,确保它确实让代码更优雅。
6. 类型提示与文档:让函数接口清晰可维护
随着项目规模增长和团队协作,清晰的函数接口至关重要。Python 3.5+引入了类型提示(Type Hints),它不是强制性的,但能极大提升代码的可读性和可维护性,并被现代IDE(如VSCode、PyCharm)用于智能提示和静态检查。
6.1 基础类型提示
def greet(name: str) -> str: """向某人打招呼。 Args: name: 需要打招呼的人名。 Returns: 一句问候语。 """ return f"Hello, {name}!" # 更复杂的例子 from typing import List, Tuple, Optional, Dict, Union def process_data( data: List[int], config: Optional[Dict[str, Union[int, float]]] = None ) -> Tuple[bool, str]: """处理整数列表数据。 Args: data: 待处理的整数列表。 config: 可选的配置字典。 Returns: 一个元组,包含处理是否成功的布尔值和消息字符串。 """ if config is None: config = {} # ... 处理逻辑 ... return True, "Processing completed"常用类型注解:
List[int]: 元素为整数的列表。Dict[str, int]: 键为字符串,值为整数的字典。Optional[str]: 可能是str,也可能是None。等价于Union[str, None]。Union[int, float]: 可以是int或float。Callable[[int, str], bool]: 一个接收一个int和一个str参数,返回bool的可调用对象(如函数)。
6.2 使用mypy进行静态类型检查
安装了类型提示后,可以使用mypy工具在运行前检查类型错误。
pip install mypy mypy your_script.py如果greet函数被错误地传入一个整数,mypy会给出警告。这能在开发早期捕获许多潜在bug。
6.3 编写有效的文档字符串(Docstring)
好的文档字符串是代码的说明书。PEP 257定义了文档字符串的约定。常见的格式有Google风格、NumPy/SciPy风格和reStructuredText风格。
Google风格示例:
def fetch_data(url: str, timeout: int = 5) -> dict: """从指定的URL获取JSON数据。 此函数向给定的URL发起GET请求,并尝试将响应解析为JSON字典。 Args: url: 目标资源的URL地址。 timeout: 请求超时时间,单位为秒。默认为5秒。 Returns: 解析后的JSON数据字典。 Raises: requests.exceptions.Timeout: 如果请求超时。 ValueError: 如果响应内容不是有效的JSON。 Examples: >>> data = fetch_data('https://api.example.com/data') >>> print(data['key']) """ import requests response = requests.get(url, timeout=timeout) response.raise_for_status() return response.json()为什么文档字符串重要?
- 自解释性:让其他开发者(或未来的你)快速理解函数用途。
- IDE集成:在
VSCode等编辑器中,鼠标悬停时会显示文档字符串。 - 自动生成文档:Sphinx等工具可以直接从代码生成漂亮的API文档。
工程化建议:对于任何预计会被他人调用或项目周期超过一个月的函数,花几分钟写好类型提示和文档字符串。这笔时间投资在后续的调试、重构和团队协作中会带来十倍百倍的回报。使用
pydocstyle等工具可以检查文档字符串是否符合规范。
7. 高级话题:生成器、协程与异步函数
当函数与yield关键字结合,它就变成了一个生成器函数,这是Python迭代器协议的实现方式,也是理解现代异步编程的基础。
7.1 生成器函数与yield
普通函数用return返回值并结束。生成器函数用yield“产生”一个值,并暂停其状态,下次从暂停处继续。
def simple_generator(): print("Start") yield 1 print("Continue") yield 2 print("End") # 隐式 return None gen = simple_generator() print(next(gen)) # 输出: Start \n 1 print(next(gen)) # 输出: Continue \n 2 # print(next(gen)) # 抛出 StopIteration 异常生成器的核心优势:惰性求值与节省内存。想象你需要处理一个巨大的日志文件,有几十GB。一次性读入内存是不可能的。
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'): if 'ERROR' in line: process_error(line) # 处理错误行7.2 从生成器到协程:send,throw,close
生成器通过.send(value)、.throw(exception)和.close()方法,可以演变为“协程”,即在yield处不仅能产出值,还能接收外部传入的值或异常,实现双向通信。
def coroutine_example(): print("Coroutine started") try: while True: received = yield # 暂停,等待外部发送值 print(f"Received: {received}") except GeneratorExit: print("Coroutine closing") coro = coroutine_example() next(coro) # 启动协程,执行到第一个yield coro.send("Hello") # 输出: Received: Hello coro.send("World") # 输出: Received: World coro.close() # 输出: Coroutine closing这种模式是Python早期协程(asyncio库出现前)的实现基础。
7.3 异步函数async def与await
Python 3.5+引入了原生的异步编程支持。使用async def定义的函数是异步函数,它返回一个协程对象。在异步函数内部,使用await来等待另一个异步操作完成,而不会阻塞整个线程。
import asyncio async def fetch_url(url): # 模拟一个耗时的网络请求 print(f"Fetching {url}...") await asyncio.sleep(1) # 模拟I/O等待 print(f"Finished {url}") return f"Data from {url}" async def main(): # 并发执行多个异步任务 task1 = asyncio.create_task(fetch_url("https://api.1.com")) task2 = asyncio.create_task(fetch_url("https://api.2.com")) # 等待所有任务完成 results = await asyncio.gather(task1, task2) print(results) # 运行异步主函数 asyncio.run(main()) # 输出 (顺序可能不同): # Fetching https://api.1.com... # Fetching https://api.2.com... # (等待约1秒) # Finished https://api.1.com # Finished https://api.2.com # ['Data from https://api.1.com', 'Data from https://api.2.com']关键理解:
async def定义的是一个协程,调用它不会立即执行,而是返回一个协程对象。await只能在async def函数内部使用。它挂起当前协程,直到其后的awaitable对象(如另一个协程、asyncio.Task、asyncio.Future)完成。asyncio.run()是运行顶级异步入口函数的标准方式。- 异步编程的核心价值在于处理I/O密集型任务(如网络请求、文件读写、数据库查询),在等待I/O时让出控制权去执行其他任务,从而实现高并发。
实战选择:如果你的项目涉及大量高并发的网络操作(如爬虫、Web服务、微服务通信),
asyncio是现代Python的必选项。对于纯CPU密集型任务,异步并不会带来性能提升,反而可能增加复杂度,此时应选择多进程(multiprocessing)。
8. 函数设计最佳实践与性能考量
最后,我们来聊聊如何设计一个好的函数,以及一些影响性能的细微之处。
8.1 函数设计原则
- 单一职责原则(SRP):一个函数只做一件事,并且做好。如果一个函数名字里出现了“和”、“与”、“然后”等连接词,或者它的代码超过一屏,很可能它就做了太多事。应该考虑拆分。
- 函数名应清晰表达意图:函数名应该是一个动词或动词短语,明确告诉别人这个函数是干什么的。比如
calculate_average()就比process_data()好得多。 - 参数数量不宜过多:参数越多,函数越难理解和使用。如果参数超过5个,考虑是否可以将相关参数封装成一个字典、数据类(
dataclass)或命名元组(NamedTuple)。 - 避免副作用:理想情况下,函数应该像一个数学函数,给定相同的输入,总是返回相同的输出,并且不修改外部状态(如全局变量、传入的可变对象)。纯函数更容易测试、理解和调试。如果必须有副作用(如写文件、修改数据库),应在函数名或文档中明确说明。
- 优先返回新对象,而非修改输入:除非明确是“原地操作”函数(如
list.sort()),否则函数应该返回一个新的对象,而不是修改传入的参数。这避免了意外的副作用。
8.2 局部变量查找更快
在函数内部,访问局部变量比访问全局变量或内置变量要快,因为Python使用一个数组(而非字典)来存储局部变量,查找是O(1)的索引操作。
import math def slow_calc(radius): # 每次循环都要在全局作用域查找math.pi return 2 * math.pi * radius def fast_calc(radius): # 将全局变量赋值给局部变量 pi = math.pi return 2 * pi * radius在性能关键的循环中,将频繁使用的全局变量或属性赋值给局部变量,是一个简单有效的优化技巧。
8.3 理解函数调用的开销
函数调用在Python中是有开销的(创建栈帧、传递参数等)。在极端性能敏感的代码段(如最内层循环),有时可以通过“内联”代码来避免函数调用。但这绝对是最后的手段,并且会严重损害代码的可读性和可维护性。绝大多数情况下,清晰的结构比微小的性能提升更重要。如果需要性能,首先应该考虑使用更高效的算法、数据结构,或者用NumPy、Cython等工具。
8.4 使用functools.lru_cache进行记忆化
对于纯函数(输出仅由输入决定)且计算成本高昂的函数,可以使用functools.lru_cache装饰器来缓存结果,避免重复计算。
from functools import lru_cache import time @lru_cache(maxsize=128) # 缓存最近128次调用 def expensive_computation(n): print(f"Computing for {n}...") # 打印以观察缓存效果 time.sleep(1) # 模拟耗时计算 return n * n print(expensive_computation(5)) # 第一次计算,会等待1秒并打印 print(expensive_computation(5)) # 第二次,直接从缓存返回,瞬间完成,不打印 print(expensive_computation(10)) # 新的输入,重新计算 print(expensive_computation(5)) # 依然从缓存获取这在动态规划、递归函数(如斐波那契数列)或需要频繁查询外部资源(在合理缓存策略下)的场景中非常有用。
函数是Python编程的灵魂。从最简单的代码复用,到构建复杂的异步应用和元编程框架,一切都离不开对函数的深刻理解。掌握它,你就能写出更清晰、更健壮、更高效的Python代码。