可迭代对象解包的默认值

8

我经常对Python的可迭代解包缺乏灵活性感到沮丧。

看下面的例子:

a, b = range(2)

运行正常。a包含0b包含1,与预期相符。现在让我们尝试这个:

a, b = range(1)

现在,我们遇到了一个 ValueError 错误:

ValueError: not enough values to unpack (expected 2, got 1)

当期望在a中得到0,在b中得到None时,这并不理想。

有许多hack方法可以解决此问题。最优雅的方法是:

a, *b = function_with_variable_number_of_return_values()
b = b[0] if b else None

这种方式不太美观,可能会让Python新手感到困惑。

那么最符合Python风格的方式是什么呢?将返回值存储在一个变量中并使用if块吗?使用*varname技巧吗?还是其他方法?


10
最具 Python 风格的方式不是让函数返回可变数量的内容,而是要么返回一个容器,要么返回一致数量的内容,要么在某些情况下(无法满足其接口的要求)从函数或解包时引发错误。 - jonrsharpe
1
那确实是一个容器,从语义上讲,在列表中具有特定数量要求是不寻常的。 - jonrsharpe
5
有什么真实世界的例子是你需要这个的?它会影响正确的解决方案。 - Ry-
1
在这种情况下,我建议使用argparse,它还会为您生成有用的帮助信息,或者类似于click的东西。 - jonrsharpe
2
像@jonrsharpe所说,argparse更适合解析参数,否则这真的取决于情况。您可以使用带有默认值的iter()next(),或者在其周围包装器,或协程,或填充默认值的函数,或... - Ry-
显示剩余7条评论
3个回答

2
如评论中所提到的,最好的方法是让您的函数返回一个常数数量的值,并且如果您的用例实际上更复杂(例如参数解析),请使用库进行处理。
然而,您的问题明确要求一种处理返回可变数量参数的 Pythonic 方法,我认为可以通过 装饰器 来干净地完成。它们并不是非常常见,大多数人倾向于更多地使用它们而不是创建它们,因此这里有一个 关于创建装饰器的平易近人的教程,以了解更多信息。
下面是一个装饰函数,它可以完成您要查找的功能。该函数返回一个带有可变数量参数的迭代器,并且它会填充到一定长度以更好地适应迭代器拆包。
def variable_return(max_values, default=None):
    # This decorator is somewhat more complicated because the decorator
    # itself needs to take arguments.
    def decorator(f):
        def wrapper(*args, **kwargs):
            actual_values = f(*args, **kwargs)
            try:
                # This will fail if `actual_values` is a single value.
                # Such as a single integer or just `None`.
                actual_values = list(actual_values)
            except:
                actual_values = [actual_values]
            extra = [default] * (max_values - len(actual_values))
            actual_values.extend(extra)
            return actual_values
        return wrapper
    return decorator

@variable_return(max_values=3)
# This would be a function that actually does something.
# It should not return more values than `max_values`.
def ret_n(n):
    return list(range(n))

a, b, c = ret_n(1)
print(a, b, c)
a, b, c = ret_n(2)
print(a, b, c)
a, b, c = ret_n(3)
print(a, b, c)

这将输出您要查找的内容:
0 None None
0 1 None
0 1 2

装饰器基本上获取被装饰的函数并返回它的输出结果,再附加足够的额外值来填充"max_values"。调用者可以假设该函数始终返回确切地"max_values"数量的参数,并像平常一样使用高级解包。

这是一个不错的想法。我没有考虑过使用装饰器。 - squirl
wrapper的主体可以被替换为return islice(chain(f(*args, **kwargs), repeat(default)), max_values)(需要from itertools import islice, chain, repeat)。 - Seb

1

这是@supersam654提出的装饰器解决方案的另一种替代版本,使用迭代器而不是列表以提高效率:

def variable_return(max_values, default=None):
    def decorator(f):
        def wrapper(*args, **kwargs):
            actual_values = f(*args, **kwargs)
            try:
                for count, value in enumerate(actual_values, 1):
                    yield value
            except TypeError:
                count = 1
                yield actual_values
            yield from [default] * (max_values - count)
        return wrapper
    return decorator

它的使用方式相同:
@variable_return(3)
def ret_n(n):
    return tuple(range(n))

a, b, c = ret_n(2)

这也可以用于非用户定义的函数,如下所示:
a, b, c = variable_return(3)(range)(2)

wrapper的主体可以被替换为return islice(chain(f(*args, **kwargs), repeat(default)), max_values)(需要from itertools import islice, chain, repeat)。 - Seb

0

我所知道的最短版本(感谢下面评论中的@KellyBundy):

a, b, c, d, e, *_ = *my_list_or_iterable, *[None]*5

显然,如果需要,可以使用除None之外的其他默认值。

此外,在Python 3.10中有一个很好的功能,当我们预先知道参数的可能数量时,它会很方便 - 就像解包sys.argv时一样。

以前的方法:

import sys.argv

_, x, y, z, *_ = *sys.argv, *[None]*3

新方法:

import sys


match sys.argv[1:]: #slice needed to drop first value of sys.argv
    case [x]:
        print(f'x={x}')
    case [x,y]:
        print(f'x={x}, y={y}')
    case [x,y,z]:
        print(f'x={x}, y={y}, z={z}')
    case _:
        print('No arguments')

@KellyBundy - 公平通知。修正了示例。 - yatsek
顺便说一下,我的建议是 稍微短一点 - Kelly Bundy
不错!我忘记了这个符号。 - yatsek
你现在所谓的“之前的方法”已经不起作用了,因为你删除了切片。 - Kelly Bundy
(摊手)- 已修复并测试 - yatsek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接