如何将嵌套迭代器/可迭代对象解包为可变参数函数的参数?

问题描述 投票:0回答:2

考虑以下用 Python 风格语法编写的代码片段:

class k:   
    def __init__(*args):
        self._args = args

    def mimethod(self, attrname:str):   
        return type(self)(getattr(arg, attrname) for arg in self._args)

注意

getattr(arg, attrname) for arg in self._args
返回一个迭代器。

我们有以下两种不良情况发生的危险:

  • 一个迭代器被分配给
    self._args[0]
  • len(args) == 1
    .

如何展平传递给可变函数的深度嵌套迭代器,使每个长度为 1 的字符串成为 args 的元素?

将迭代器传递给可变参数函数的第二个示例如下所示:

import sys
import io

def variadic_function(*args, file_stream = sys.stdout):
    print("\n".join(str(arg) for arg in args, file=sys.stdout)

strm = io.StringIO()

# names = tuple(x.strip() for x in "LUciA, SoFiA, mArIA, MARTInA, pAuLa, LuCaS, hUgO, mArTiN, dAnIeL, pAbLo".split(","))   

names = ('LUciA', 'SoFiA', 'mArIA', 'MARTInA', 'pAuLa', 'LuCaS', 'hUgO', 'mArTiN', 'dAnIeL', 'pAbLo')

variadic_function(name[0].upper()+ name[1:].lower() for name in names, file=strm)

print(strm.getvalue())
python iterable
2个回答
1
投票

您可以通过编写

iter
.
 直接解压迭代器 
f(*iter)

以进行函数调用

你提到

getattr(arg, attrname) for arg in self._args
产生一个迭代器。这在您的上下文中是正确的,但对于更一般的陈述,最好说括号内的表达式
(getattr(arg, attrname) for arg in self._args)
产生一个 generator。 (生成器是一种迭代器。)

在您的两个示例中,我们因此使用括号,让 Python 知道星号应用于什么:

return type(self)(*(getattr(arg, attrname) for arg in self._args))

variadic_function(*(name[0].upper()+ name[1:].lower() for name in names), file=strm)

您谈到了“深度嵌套”迭代器(迭代器中的迭代器)的可能性,这将需要更多的工作和递归函数。您还提到“每个长度为 1 的字符串”成为函数的参数(因此

'abc'
将变为
('a', 'b', 'c')
),但您的两个示例似乎不需要这样做。也就是说,如果您确实需要执行后者,则字符串前的星号就可以解决问题:

print(*'abc')
# output: a b c

如果您需要将迭代器解包

iter
,而不是将其输出用作函数调用的参数,您可以将它们转换为元组或列表,如下所示:

tuple(iter)
list(iter)

具体来说:

tuple(range(5))
# output: (0, 1, 2, 3, 4)
list(range(5))
# output: [0, 1, 2, 3, 4]

原则上,您可以使用

*

为函数调用解压结果元组或列表
print(*tuple(range(5)))
print(*list(range(5)))
# output (in both cases): 0 1 2 3 4

但是写

print(*range(5))
更直接。

(执行不带星号的

print(tuple(range(5)))
print(list(range(5)))
将分别打印
(0, 1, 2, 3, 4)
[0, 1, 2, 3, 4]
。)


0
投票

我现在来回答你后面的问题:

我们如何自动解压一个可迭代对象?如果它是嵌套的,我们必须取消嵌套它。

上面这行从你原来的措辞略有修改:

  • 我假设你的意思是 iterable 而不是 iterator,因为
    *expr
    作为函数参数 requires that
    expr
    be an iterable
    。这使得问题更笼统,因为所有迭代器都是可迭代的。
  • 我假设你的意思是“如果
    args
    是嵌套的”而不是“如果
    args
    是未嵌套的”。

即我对你的问题的解读如下:

如何将嵌套迭代器/可迭代对象解压为可变参数函数的参数?

My solution correctly handle deep nesting and strings of length one.

import collections.abc

def unpack_iterable(arg):
    if isinstance(arg, str):
        return arg
    lst = []
    for x in arg:
        if isinstance(x, collections.abc.Iterable):
            lst.extend(unpack_iterable(x))
        else:
            lst.append(x)
    return lst

nested_iterable = (range(3), ['abc', '456', (78, range(91, 94)), range(101, 104)])
unpack_iterable(nested_iterable)
# output:
# [0, 1, 2, 'a', 'b', 'c', '4', '5', '6', 78, 91, 92, 93, 101, 102, 103]

函数开头的检查防止无限递归,因为即使是长度为 1 的字符串也是可迭代的。

注意函数

unpack_iterable
假定它的参数是可迭代的。因此,我们可以按如下方式使用它:

my_arg = unpack_iterable(nested_iterable) if isinstance(nested_iterable, collections.abc.Iterable) else nested_iterable
print(*my_arg)
# output:
# 0 1 2 a b c 4 5 6 78 91 92 93 101 102 103

# compare:
print(*nested_iterable)
# output:
# range(0, 3) ['abc', '456', (78, range(91, 94)), range(101, 104)]

将检查移动到函数定义中会降低我们的代码速度,因为该检查将在每次递归调用后执行。但是,我们也需要在每次递归调用之前进行检查,以决定是使用

lst.extend
还是
lst.append
。人们当然可以定义一个薄包装器只处理一次此检查:

def unwrapIterable_functionCall(f, it):
    if isinstance(it, collections.abc.Iterable):
        return f(*unpack_iterable(it))
    else:
        return f(it)

我们可以这样使用它:

unwrapIterable_functionCall(print, nested_iterable)
# output:
# 0 1 2 a b c 4 5 6 78 91 92 93 101 102 103

unwrapIterable_functionCall(print, 'xyz')
# output:
# x y z

unwrapIterable_functionCall(print, 987)
# output:
# 987
© www.soinside.com 2019 - 2024. All rights reserved.