简介
在这个实验中,你将学习如何在 Python 中从函数返回多个值。你还将了解可选返回值以及如何有效地处理错误。
此外,你将探索并发编程中 Futures
的概念。虽然返回一个值看似简单,但不同的编程场景会呈现出各种模式和需要考虑的因素。
This tutorial is from open-source community. Access the source code
💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版
在这个实验中,你将学习如何在 Python 中从函数返回多个值。你还将了解可选返回值以及如何有效地处理错误。
此外,你将探索并发编程中 Futures
的概念。虽然返回一个值看似简单,但不同的编程场景会呈现出各种模式和需要考虑的因素。
在 Python 中,当你需要一个函数返回多个值时,有一个便捷的解决方案:返回一个元组(tuple)。元组是 Python 中的一种数据结构,它是一个不可变序列,这意味着一旦创建了元组,就不能更改其元素。元组很有用,因为它们可以在一个地方存储不同类型的多个值。
让我们创建一个函数来解析格式为 name=value
的配置行。这个函数的目标是接收这种格式的一行,并将名称和值作为单独的项返回。
return_values.py
的文件。你可以在终端中使用以下命令来创建这个文件:touch ~/project/return_values.py
return_values.py
文件。在这个文件中,我们将编写 parse_line
函数。这个函数将一行作为输入,在第一个 '=' 符号处将其分割,并将名称和值作为元组返回。def parse_line(line):
"""
Parse a line in the format 'name=value' and return both the name and value.
Args:
line (str): Input line to parse in the format 'name=value'
Returns:
tuple: A tuple containing (name, value)
"""
parts = line.split('=', 1) ## Split at the first equals sign
if len(parts) == 2:
name = parts[0]
value = parts[1]
return (name, value) ## Return as a tuple
在这个函数中,split
方法用于在第一个 '=' 符号处将输入行分成两部分。如果该行是正确的 name=value
格式,我们将提取名称和值,并将它们作为元组返回。
parse_line
函数并打印结果。## Test the parse_line function
if __name__ == "__main__":
result = parse_line('email=guido@python.org')
print(f"Result as tuple: {result}")
## Unpacking the tuple into separate variables
name, value = parse_line('email=guido@python.org')
print(f"Unpacked name: {name}")
print(f"Unpacked value: {value}")
在测试代码中,我们首先调用 parse_line
函数,并将返回的元组存储在 result
变量中。然后我们打印这个元组。接下来,我们使用元组解包(tuple unpacking)将元组的元素直接分配给 name
和 value
变量,并分别打印它们。
return_values.py
文件。然后,打开终端并运行以下命令来执行 Python 脚本:python ~/project/return_values.py
你应该会看到类似于以下的输出:
Result as tuple: ('email', 'guido@python.org')
Unpacked name: email
Unpacked value: guido@python.org
解释:
parse_line
函数使用 split
方法在 '=' 字符处分割输入字符串。这个方法根据指定的分隔符将字符串分成多个部分。return (name, value)
语法将两个部分作为元组返回。元组是一种将多个值组合在一起的方式。result
变量所做的那样。或者你可以使用 name, value = parse_line(...)
语法将元组直接“解包”到单独的变量中。这使得处理单个值更加容易。这种将多个值作为元组返回的模式在 Python 中非常常见。它使函数更加通用,因为它们可以向调用它们的代码提供多个信息。
在编程中,有时函数可能无法生成有效的结果。例如,当一个函数要从输入中提取特定信息,但输入的格式不符合预期时。在 Python 中,处理这种情况的常见方法是返回 None
。None
是 Python 中的一个特殊值,表示没有有效的返回值。
让我们看看如何修改一个函数,以处理输入不符合预期标准的情况。我们将对 parse_line
函数进行修改,该函数旨在解析格式为 'name=value' 的行,并返回名称和值。
return_values.py
文件中的 parse_line
函数:def parse_line(line):
"""
Parse a line in the format 'name=value' and return both the name and value.
If the line is not in the correct format, return None.
Args:
line (str): Input line to parse in the format 'name=value'
Returns:
tuple or None: A tuple containing (name, value) or None if parsing failed
"""
parts = line.split('=', 1) ## Split at the first equals sign
if len(parts) == 2:
name = parts[0]
value = parts[1]
return (name, value) ## Return as a tuple
else:
return None ## Return None for invalid input
在这个更新后的 parse_line
函数中,我们首先使用 split
方法在第一个等号处分割输入行。如果得到的列表恰好有两个元素,这意味着该行的格式是正确的 'name=value'。然后我们提取名称和值,并将它们作为元组返回。如果列表没有两个元素,这意味着输入无效,我们返回 None
。
## Test the updated parse_line function
if __name__ == "__main__":
## Valid input
result1 = parse_line('email=guido@python.org')
print(f"Valid input result: {result1}")
## Invalid input
result2 = parse_line('invalid_line_without_equals_sign')
print(f"Invalid input result: {result2}")
## Checking for None before using the result
test_line = 'user_info'
result = parse_line(test_line)
if result is None:
print(f"Could not parse the line: '{test_line}'")
else:
name, value = result
print(f"Name: {name}, Value: {value}")
这段测试代码使用有效和无效的输入调用 parse_line
函数,然后打印结果。注意,在使用 parse_line
函数的结果时,我们首先检查它是否为 None
。这很重要,因为如果我们试图像处理元组一样解包 None
值,会引发错误。
python ~/project/return_values.py
运行脚本时,你应该会看到类似于以下的输出:
Valid input result: ('email', 'guido@python.org')
Invalid input result: None
Could not parse the line: 'user_info'
解释:
None
以表明解析失败。None
很重要。否则,在尝试访问 None
值的元素时可能会遇到错误。设计讨论:
处理无效输入的另一种方法是抛出异常。这种方法在某些情况下很合适:
基于异常的处理方法示例:
def parse_line_with_exception(line):
"""Parse a line and raise an exception for invalid input."""
parts = line.split('=', 1)
if len(parts) != 2:
raise ValueError(f"Invalid format: '{line}' does not contain '='")
return (parts[0], parts[1])
返回 None
和抛出异常之间的选择取决于你的应用程序需求:
None
。例如,在列表中搜索某个项,而该项可能不存在。在 Python 中,当你需要同时(即并发)运行函数时,Python 提供了像线程(threads)和进程(processes)这样有用的工具。但你会面临一个常见问题:当一个函数在不同的线程中运行时,你如何获取它返回的值呢?这就是 Future
概念变得非常重要的地方。
Future
就像是一个占位符,代表着稍后会得到的结果。它是一种表示函数在未来会产生的值的方式,即使函数还未运行完成。让我们通过一个简单的例子来更好地理解这个概念。
首先,你需要创建一个新的 Python 文件。我们将其命名为 futures_demo.py
。你可以在终端中使用以下命令来创建这个文件:
touch ~/project/futures_demo.py
现在,打开 futures_demo.py
文件并添加以下 Python 代码。这段代码定义了一个简单的函数,并展示了普通函数调用是如何工作的。
import time
import threading
from concurrent.futures import Future, ThreadPoolExecutor
def worker(x, y):
"""A function that takes time to complete"""
print('Starting work...')
time.sleep(5) ## Simulate a time-consuming task
print('Work completed')
return x + y
## Part 1: Normal function call
print("--- Part 1: Normal function call ---")
result = worker(2, 3)
print(f"Result: {result}")
在这段代码中,worker
函数接受两个数字,将它们相加,但首先它会通过暂停 5 秒来模拟一个耗时的任务。当你以普通方式调用这个函数时,程序会等待函数完成,然后获取返回值。
保存文件并在终端中使用以下命令运行它:
python ~/project/futures_demo.py
你应该会看到如下输出:
--- Part 1: Normal function call ---
Starting work...
Work completed
Result: 5
这表明普通的函数调用会等待函数完成,然后返回结果。
接下来,让我们看看当我们在单独的线程中运行 worker
函数时会发生什么。将以下代码添加到 futures_demo.py
文件中:
## Part 2: Running in a separate thread (problem: no way to get result)
print("\n--- Part 2: Running in a separate thread ---")
t = threading.Thread(target=worker, args=(2, 3))
t.start()
print("Main thread continues while worker runs...")
t.join() ## Wait for the thread to complete
print("Worker thread finished, but we don't have its return value!")
在这里,我们使用 threading.Thread
类在一个新线程中启动 worker
函数。主线程不会等待 worker
函数完成,而是继续执行。然而,当 worker
线程完成时,我们没有简单的方法来获取返回值。
再次保存文件并使用相同的命令运行它:
python ~/project/futures_demo.py
你会注意到主线程继续执行,工作线程也在运行,但我们无法访问 worker
函数的返回值。
Future
为了解决从线程中获取返回值的问题,我们可以使用 Future
对象。将以下代码添加到 futures_demo.py
文件中:
## Part 3: Using a Future to get the result
print("\n--- Part 3: Using a Future manually ---")
def do_work_with_future(x, y, future):
"""Wrapper that sets the result in the Future"""
result = worker(x, y)
future.set_result(result)
## Create a Future object
fut = Future()
## Start a thread that will set the result in the Future
t = threading.Thread(target=do_work_with_future, args=(2, 3, fut))
t.start()
print("Main thread continues...")
print("Waiting for the result...")
## Block until the result is available
result = fut.result() ## This will wait until set_result is called
print(f"Got the result: {result}")
在这段代码中,我们创建了一个 Future
对象,并将其传递给一个新函数 do_work_with_future
。这个函数调用 worker
函数,然后将结果设置到 Future
对象中。然后主线程可以使用 Future
对象的 result()
方法在结果可用时获取结果。
Future
的代码保存文件并再次运行它:
python ~/project/futures_demo.py
现在你会看到我们可以成功地从线程中运行的函数获取返回值。
ThreadPoolExecutor
Python 中的 ThreadPoolExecutor
类让处理并发任务变得更加容易。将以下代码添加到 futures_demo.py
文件中:
## Part 4: Using ThreadPoolExecutor (easier way)
print("\n--- Part 4: Using ThreadPoolExecutor ---")
with ThreadPoolExecutor() as executor:
## Submit the work to the executor
future = executor.submit(worker, 2, 3)
print("Main thread continues after submitting work...")
print("Checking if the future is done:", future.done())
## Get the result (will wait if not ready)
result = future.result()
print("Now the future is done:", future.done())
print(f"Final result: {result}")
ThreadPoolExecutor
会为你创建和管理 Future
对象。你只需要提交函数及其参数,它就会返回一个 Future
对象,你可以使用该对象来获取结果。
最后一次保存文件并运行它:
python ~/project/futures_demo.py
Future
对象并将其传递给线程,我们可以将结果设置到 Future
中,然后从主线程获取结果。Future
对象的创建和管理,使你更容易并发运行函数并获取它们的返回值。Future
对象有几个有用的方法:
result()
:这个方法用于获取函数的结果。如果结果还未准备好,它会一直等待直到结果可用。done()
:你可以使用这个方法来检查函数的计算是否完成。add_done_callback()
:这个方法允许你注册一个函数,当结果准备好时会调用该函数。这种模式在并发编程中非常重要,特别是当你需要从并行运行的函数中获取结果时。
在本次实验中,你学习了 Python 函数返回值的几个关键模式。首先,Python 函数可以通过将多个值打包成元组来返回,这样能实现清晰且易读的值返回和解包操作。其次,对于可能无法始终产生有效结果的函数,返回 None
是一种常见的表示无值的方式,同时也介绍了抛出异常作为另一种可选方法。
最后,在并发编程中,Future
作为未来结果的占位符,使你能够从在单独线程或进程中运行的函数获取返回值。理解这些模式将增强你 Python 代码的健壮性和灵活性。为了进一步练习,你可以尝试不同的错误处理策略,将 Future
与其他并发执行类型结合使用,并探索它们在使用 async
/await
的异步编程中的应用。