简体   繁体   English

可迭代解包的默认值

[英]Default values for iterable unpacking

I've often been frustrated by the lack of flexibility in Python's iterable unpacking.我经常对 Python 的可迭代解包缺乏灵活性感到沮丧。

Take the following example:举个例子:

a, b = range(2)

Works fine.工作正常。 a contains 0 and b contains 1 , just as expected.正如预期的那样, a包含0并且b包含1 Now let's try this:现在让我们试试这个:

a, b = range(1)

Now, we get a ValueError :现在,我们得到一个ValueError

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

Not ideal, when the desired result was 0 in a , and None in b .不理想,当所需的结果是a中的0b中的None时。

There are a number of hacks to get around this.有很多技巧可以解决这个问题。 The most elegant I've seen is this:我见过的最优雅的是:

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

Not pretty, and could be confusing to Python newcomers.不漂亮,可能会让 Python 新手感到困惑。

So what's the most Pythonic way to do this?那么最Pythonic的方法是什么? Store the return value in a variable and use an if block?将返回值存储在变量中并使用 if 块? The *varname hack? *varname破解? Something else?还有什么?

As mentioned in the comments, the best way to do this is to simply have your function return a constant number of values and if your use case is actually more complicated (like argument parsing), use a library for it.正如评论中提到的,最好的方法是让你的函数返回一个恒定数量的值,如果你的用例实际上更复杂(如参数解析),请使用它的库。

However , your question explicitly asked for a Pythonic way of handling functions that return a variable number of arguments and I believe it can be cleanly accomplished with decorators .但是,您的问题明确要求以 Python 方式处理返回可变数量参数的函数,我相信它可以用装饰器干净地完成。 They're not super common and most people tend to use them more than create them so here's a down-to-earth tutorial on creating decorators to learn more about them.它们不是很常见,大多数人倾向于使用它们而不是创建它们,所以这里有一个关于创建装饰器的实用教程,以了解更多关于它们的信息。

Below is a decorated function that does what you're looking for.下面是一个装饰功能,可以满足您的需求。 The function returns an iterator with a variable number of arguments and it is padded up to a certain length to better accommodate iterator unpacking.该函数返回一个具有可变数量参数的迭代器,并且它被填充到一定长度以更好地适应迭代器解包。

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)

Which outputs what you're looking for:哪个输出您要查找的内容:

0 None None
0 1 None
0 1 2

The decorator basically takes the decorated function and returns its output along with enough extra values to fill in max_values .装饰器基本上采用装饰函数并返回其输出以及足够的额外值来填充max_values The caller can then assume that the function always returns exactly max_values number of arguments and can use fancy unpacking like normal.然后,调用者可以假设该函数总是准确地返回max_values个参数,并且可以像平常一样使用花哨的解包。

Here's an alternative version of the decorator solution by @supersam654, using iterators rather than lists for efficiency:这是@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

It's used in the same way:它的使用方式相同:

@variable_return(3)
def ret_n(n):
    return tuple(range(n))

a, b, c = ret_n(2)

This could also be used with non-user-defined functions like so:这也可以与非用户定义的函数一起使用,如下所示:

a, b, c = variable_return(3)(range)(2)

Shortest known to me version (thanks to @KellyBundy in comments below):我知道的最短版本(感谢下面评论中的@KellyBundy):

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

Obviously it's possible to use other default value than None if necessary.显然,如果需要,可以使用None以外的其他默认值。

Also there is one nice feature in Python 3.10 which comes handy here when we know upfront possible numbers of arguments - like when unpacking sys.argv Python 3.10 中还有一个不错的功能,当我们预先知道可能的参数数量时,它在这里会派上用场——比如在解包sys.argv

Previous method:以前的方法:

import sys.argv

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

New method:新方法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM