我们不应该把函数返回的多个值拆分到三个以上的变量里。
一个三元组最多只拆成三个普通变量,或两个普通变量与一个万能变量(带星号的变量)。
当然用于接收的变量个数也可以比这更少。
假如要拆分的值确实很多,那最好还是定义一个轻便的类或 namedtuple(参见第 37 条),并让函数返回这样的实例。
基本问题
假设现在要统计一群鳄鱼的各项指标,把每条鳄鱼的体长都保存在列表里。
接着要编写函数,查出列表中最短与最长的鳄鱼。
这可以用下面这种写法实现,可以同时把这两个值都返回。
def get_stats(numbers):
minimum = min(numbers)
maximum = max(numbers)
return minimum, maximum
lengths = [63, 73, 72, 60, 67, 66, 71, 61, 72, 70]
minimum, maximum = get_stats(lengths) # Two return values
print(f'Min: {minimum}, Max: {maximum}')
带星号变量
例如,我们还需要写一个函数,用来计算每条鳄鱼的长度与这些鳄鱼的平均长度之比。
该函数会把比值放在列表里返回,但我们在接收的时候,可以只接收最长与最短的那两条鳄鱼所对应的比值,
而把中间那些鳄鱼的比值用带星号的写法总括。
def get_avg_ratio(numbers):
average = sum(numbers) / len(numbers)
scaled = [x / average for x in numbers]
scaled.sort(reverse=True)
return scaled
longest, *middle, shortest = get_avg_ratio(lengths)
print(f'Longest: {longest:>4.0%}')
print(f'Shortest: {shortest:>4.0%}')
需求改变
假设现在需求又变了,我们这次还想知道平均长度、中位长度(长度的中位数)以及样本的总数。
我们可以扩展原有的 get_stats 函数,让它把这些指标也计算出来,然后一并通过元组返回给调用方,让调用方自己去拆分。
def get_stats(numbers):
minimum = min(numbers)
maximum = max(numbers)
count = len(numbers)
average = sum(numbers) / count
sorted_numbers = sorted(numbers)
middle = count // 2
if count % 2 == 0:
lower = sorted_numbers[middle - 1]
upper = sorted_numbers[middle]
median = (lower + upper) / 2
else:
median = sorted_numbers[middle]
return minimum, maximum, average, median, count
minimum, maximum, average, median, count = get_stats(lengths)
print(f'Min: {minimum}, Max: {maximum}')
print(f'Average: {average}, Median: {median}, Count {count}')
存在问题
这样写有两个问题。
首先,函数返回的五个值都是数字,所以很容易就会搞错顺序(例如,把平均数 average 和中位数 median 弄颠倒了),这可能让程序出现难以查找的 bug。调用方同时要接收这么一大堆返回值,也特别容易出错。
第二个问题是,调用函数并拆分返回值的那行代码会写得比较长,所以按照 PEP8 风格指南,可能需要折行(参见第 2 条),这让代码看起来很别扭。
为避免这些问题,我们不应该把函数返回的多个值拆分到三个以上的变量里。一个三元组最多只拆成三个普通变量,或两个普通变量与一个万能变量(带星号的变量)。当然用于接收的变量个数也可以比这更少。假如要拆分的值确实很多,那最好还是定义一个轻便的类或 namedtuple(参见第 37 条),并让函数返回这样的实例。
总结
- 函数可以把多个值合起来通过一个元组返回给调用者,以便利用 Python 的 unpacking 机制去拆分。
- 对于函数返回的多个值,可以把普通变量没有捕获到的那些值全都捕获到一个带星号的变量里。
- 把返回的值拆分到四个或四个以上的变量是很容易出错的,所以最好不要那么写,而是应该通过小类或 namedtuple 实例完成。