考虑以下用 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())
您可以通过编写
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]
。)
我现在来回答你后面的问题:
我们如何自动解压一个可迭代对象?如果它是嵌套的,我们必须取消嵌套它。
上面这行从你原来的措辞略有修改:
*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