Item 19: Never Unpack More Than Three Variables When Functions Return Multiple Values

One effect of the unpacking syntax (see Item 6: “Prefer Multiple Assignment Unpacking Over Indexing”) is that it allows Python functions to seemingly return more than one value. For example, say that I’m trying to determine various statistics for a population of alligators. Given a list of lengths, I need to calculate the minimum and maximum lengths in the population. Here, I do this in a single function that appears to return two values:

解包语法的一个使用效果是(参见第6项:“Prefer Multiple Assignment Unpacking Over Indexing”),它允许Python函数返回多个值。例如,假设我试图确定短吻鳄种群的各种统计数据。给定一个长度列表,我需要计算种群中的最小和最大长度。在这里,我在一个单一函数中实现,它似乎返回了两个值:

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} ')

>>>
Min: 60, Max: 73

The way this works is that multiple values are returned together in a two- item tuple. The calling code then unpacks the returned tuple by assigning two variables. Here, I use an even simpler example to show how an unpacking statement and multiple-return function work the same way:

它的工作原理是将多个值放在一个元组中一起返回,调用代码通过给两个变量来解包这个元组。在这里,我用一个更简单的例子来展示解包语句和多重返回函数的工作方式:

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

Multiple return values can also be received by starred expressions for catch-all unpacking (see Item 13: “Prefer Catch-All Unpacking Over Slicing”). For example, say I need another function that calculates how big each alligator is relative to the population average. This function returns a list of ratios, but I can receive the longest and shortest items individually by using a starred expression for the middle portion of the list:

多个返回值也可以使用星号表达式接收进行全面解包(参见第13项:“Prefer Catch-All Unpacking Over Slicing”)。例如,我需要另一个函数来计算每条短吻鳄相对于平均数量的大小。这个函数返回一个比率列表,我可以通过在列表的中间部分使用星号表达式,来分别接收最长和最短的项:

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%

Now, imagine that the program’s requirements change, and I need to also determine the average length, median length, and total population size of the alligators. I can do this by expanding the get_stats function to also calculate these statistics and return them in the result tuple that is unpacked by the caller:

现在,假设程序的要求改变了,我还需要确定短吻鳄的平均长度、中位数长度和总种群大小。我可以通过扩展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}')

>>>
Min: 60, Max: 73
Average: 67.5, Median: 68.5, Count 10 

There are two problems with this code. First, all the return values are numeric, so it is all too easy to reorder them accidentally (e.g., swapping average and median), which can cause bugs that are hard to spot later. Using a large number of return values is extremely error prone:

这段代码有两个问题。首先,所有的返回值都是数字的,所以很容易对它们排序错误(例如,交换平均值和中值),这可能会导致后面难以发现的bug。使用大量的返回值是非常容易出错的:

# Correct:
minimum, maximum, average, median, count = get_stats (lengths)

# Oops ! Median and average swapped:
minimum, maximum, median, average, count = get_stats (lengths)

Second, the line that calls the function and unpacks the values is long, and it likely will need to be wrapped in one of a variety of ways (due to PEP8 style; see Item 2: “Follow the PEP 8 Style Guide”), which hurts readability:

其次,在一行代码中调用函数并解包返回值,会导致这一行很长,需要以多种方式进行包装(由于PEP8样式;参见条款2:“Follow the PEP 8 Style Guide”),这会影响可读性:

minimum, maximum, average, median, count = get_stats (lengths)

minimum, maximum, average, median, count = get_stats (lengths)

(minimum, maximum, average, median, count) = get_stats (lengths)

(minimum, maximum, average, median, count) = get_stats (lengths)

To avoid these problems, you should never use more than three variables when unpacking the multiple return values from a function. These could be individual values from a three-tuple, two variables and one catch-all starred expression, or anything shorter. If you need to unpack more return values than that, you’re better off defining a lightweight class or namedtuple (see Item 37: “Compose Classes Instead of Nesting Many Levels of Built-in Types”) and having your function return an instance of that instead.

为了避免这些问题,在对函数的多个返回值进行解包时,绝不应该使用三个以上的变量。这些值可以是一个三元组、两个变量和一个星号表达式,或者其它更短的值。如果你需要解包更多的返回值,你最好定义一个轻量级的类或命名元组(参见Item 37:“Compose Classes Instead of Nesting Many Levels of Built-in Types”),并让你的函数返回一个这样的实例。

Things to Remember
要记住的事

✦ You can have functions return multiple values by putting them in a tuple and having the caller take advantage of Python’s unpacking syntax.
✦ Multiple return values from a function can also be unpacked by catch-all starred expressions.
✦ Unpacking into four or more variables is error prone and should be avoided; instead, return a small class or namedtuple instance.

✦ 你可以让函数返回多个值,把它们放入一个元组,并让调用者利用Python的解包语法进行解包。
✦ 函数的多个返回值也可以通过星号表达式来全面解包。
✦ 解包成四个或更多的变量时很容易出错,应该避免这种用法,可以返回一个轻量类或者命名元组实例。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容