第 19 条 不要把函数返回的多个数值拆分到三个以上的变量
unpacking 机制允许 Python 函数返回一个以上的值(参见 第 6 条)。假设现在要统计一群鳄鱼的各项指标,把每条鳄鱼的体型都保存在列表里面,需要查询列表中最短和最长的鳄鱼。这可以用下面这种写法实现,它可以同时把两个值返回。
def get_status(numbers):
minimum = min(numbers)
maximum = max(numbers)
return minimum, maximum
lengths = [63, 73, 72, 60, 67, 66, 71, 61, 72, 70]
minimum, maximum = get_status(lengths)
print(f'Min: {minimum}, Max: {maximum}')
>>>
Min: 60, Max: 73
函数返回的其实是个元组(tuple),同时返回的那两个值就是元组的两个元素。程序通过把函数返回的元组赋给 minumum 与 maximum 这两个变量来拆解元组,相当于用这两个变量分别接受元组的两个元素。下面举一个更简单的例子,来演示 unpacking 语句和返回多个值的函数是怎样调用的。
first, second = 1, 2
assert first == 1
assert second == 2
def my_function():
return 1, 2
first, second = my_function()
assert first == 1
assert second == 2
在返回多个值的时候,可以用带星号的表达式接受那些没有被普通变量捕获到的值(参见 第 13 条 )。例如,我们需要写一个函数,用来计算每条鳄鱼的长度与这些鳄鱼的平均长度之比。该函数会把比值放在列表中返回,但我们在接收的时候,可以只接收最长与最短的那两条鳄鱼的比值,而把中间那些鳄鱼的比值用带星号的写法总括。
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%}')
>>>
Longest: 108%
Shortest: 89%
假定现在需求变了,这次需要平均长度、中位长度(长度的中位数)以及样本的总数。我们可以扩展原油的 get_status 函数,让它把这些指标也计算出来,然后一并通过元组返回给调用方,让调用方自己去拆分。
def get_status(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]
higher = sorted_numbers[middle]
median = (lower + higher) / 2
else:
median = sorted_numbers[middle]
return minimum, maximum, average, median, count
minimum, maximum, average, median, count = get_status(lengths)
print(f'Min: {minimum}, Max: {maximum}')
print(f'Average: {average}, Medain: {median}, Count: {count}')
>>>
Min: 60, Max: 73
Average: 67.5, Medain: 68.5, Count: 10
这样写有两个问题。首先,函数返回的五个值都是数字,所以很容易就会搞错顺序,这样很容易让程序出现 bug。
第二个问题就是,调用函数并拆分返回值的那行代码会写的特别长,所以按照 PEP 8 风格指南,需要折行(参见 第 2 条),这样让代码看着别扭。
minimum, maximum, average, median, count = get_status(
lengths)
minimum, maximum, average, median, count = \
get_status(lengths)
(minimum, maximum, average,
median, count) = get_status(lengths)
(minimum, maximum, average, median, count
)= get_status(lengths)
为了避免这些问题,我们不应该把函数返回值拆分到三个值以上的变量里。一个元组最多只拆成三个普通变量,或两个变量与一个万能变量(带星号的变量)。当然用于接收的变量个数也有可能比这个更少。假如要拆分的值确实很多,那最好还是定义一个轻便的类或 nametuple (参见 第37条),并让函数返回这样的实例。