为什么Python中没有第一个(可迭代的)内置函数?

cdl*_*ary 66 python iterator generator

我不知道是否有一个原因,有没有first(iterable)在Python内置的功能,有点类似于any(iterable)all(iterable)(可能一STDLIB模块中夹着地方,但我没有看到它itertools).first将执行短路发生器评估,以便可以避免不必要的(和可能无限数量的)操作; 即

def identity(item):
    return item

def first(iterable, predicate=identity):
    for item in iterable:
        if predicate(item):
            return item
    raise ValueError('No satisfactory value found')
Run Code Online (Sandbox Code Playgroud)

这样你可以表达如下内容:

denominators = (2, 3, 4, 5)
lcd = first(i for i in itertools.count(1)
    if all(i % denominators == 0 for denominator in denominators))
Run Code Online (Sandbox Code Playgroud)

很明显,list(generator)[0]在这种情况下你不能这样做,因为发生器不会终止.

或者,如果你有一堆正则表达式匹配(当它们都具有相同的groupdict接口时很有用):

match = first(regex.match(big_text) for regex in regexes)
Run Code Online (Sandbox Code Playgroud)

通过避免list(generator)[0]和短路匹配来节省大量不必要的处理.

lio*_*ori 47

如果你有一个迭代器,你可以调用它的next方法.就像是:

In [3]: (5*x for x in xrange(2,4)).next()
Out[3]: 10
Run Code Online (Sandbox Code Playgroud)

  • 上面的方法在Python 3中不起作用,使用`next(x)`如果`x`是迭代器,或者`next(iter(d))`如果`d`是可迭代的 (10认同)
  • 噢,当然!在Py3k中,内置函数是`next(iterator)`. (9认同)
  • 我不明白这个答案.问题中显示的'first'跳过序列的初始元素'falsy'(由bool(谓词(item))定义).我认为那就是重点.'next()'不会这样做.我很迷惑. (8认同)
  • OP的示例返回的内容与`next(regex for regex,regex,regex,如果regex.match(big_text))`; 它返回regex.match(big_text)的结果.没有first(),如何做到这一点?`next(regex.match(big_text)for regex in regexes if regex.match(big_text))`是多余的.`next(ifilter(imap(lambda x:x.match(big_text),regexes)))`看起来过于复杂,与第一次相比. (4认同)
  • Python 2.6 及更高版本中所有可迭代对象(例如列表和生成器)的通用解决方案是`next(iter(xs))`。在 Python 2.5 中,您可以执行 `iter(xs).next()`。 (2认同)
  • @JonathanHartley:重点是拥有next()和一个通用的方法来构建一个过滤的序列(例如使用`itertools.ifilter()`或`(... for ... in ... if condition)`,结合它们是不够的努力证明有另一个内置工具.请注意,OP的正则表达式只是`下一步(regex,正则表达式,正则表达式,如果regex.match(big_text))`. (2认同)
  • @JonathanHartley 和 @pjz 是对的。所以我必须投反对票(并责骂所有支持者没有考虑到这一点;-))。请参阅我的答案以获取更多详细信息。 (2认同)
  • 请注意,这不是幂等的。(这意味着“next()”只会在第一次调用时返回第一个元素)。 (2认同)

Fli*_*imm 13

有一个名为"first"Pypi包实现了这个目的:

>>> from first import first
>>> first([0, None, False, [], (), 42])
42
Run Code Online (Sandbox Code Playgroud)

以下是用于返回第一个奇数的方法,例如:

>> first([2, 14, 7, 41, 53], key=lambda x: x % 2 == 1)
7
Run Code Online (Sandbox Code Playgroud)

如果您只想从迭代器返回第一个元素而不管它是否为真,请执行以下操作:

>>> first([0, None, False, [], (), 42], key=lambda x: True)
0
Run Code Online (Sandbox Code Playgroud)

它是一个非常小的包:它只包含这个函数,它没有依赖关系,它适用于Python 2和3.它是一个文件,所以你甚至不必安装它来使用它.

事实上,这里几乎是整个源代码(来自版本2.0.1,由Hynek Schlawack,在MIT许可下发布):

def first(iterable, default=None, key=None):
    if key is None:
        for el in iterable:
            if el:
                return el
    else:
        for el in iterable:
            if key(el):
                return el
    return default
Run Code Online (Sandbox Code Playgroud)

  • 好的。但是自己实现它大约需要三行代码。这很难证明安装完整包的开销是合理的(引入所有可移植性问题等)。问题仍然存在:为什么这部分不是 Python 的内置函数?或者使用内置的 Python 结构来拼写这一点的最简洁、最 Pythonic 的方法是什么? (8认同)
  • @Alfe:使用包是干净的和Pythonic.至于为什么它不是内置的,这不是Stack Overflow的问题,因为任何不是核心提交者的人都不可能回答它. (2认同)
  • 好吧,那么让我这样说吧:你如何从模块`first`实现`first`功能?当我问为什么这不是内置的时候,我这样做是因为我怀疑有一种Pythonic的方法可以使用列表推导等更常见的功能来表达这一点,这使得它足够冗余以便将其排除在外. (2认同)
  • @Alfie:够公平的.其他答案试图这样做,但正如你所看到的,结果并不那么漂亮,我相信一些能够轻松安装模块的人会发现`first`很有用.我已经包含了感兴趣的函数的源代码. (2认同)

Alf*_*lfe 10

我最近问了一个类似的问题(现在它被标记为这个问题的副本).我担心的也是,我会喜欢使用内置插件解决发现发电机的第一个真正的价值的问题.我自己的解决方案是这样的:

x = next((v for v in (f(x) for x in a) if v), False)
Run Code Online (Sandbox Code Playgroud)

对于查找第一个正则表达式匹配(不是第一个匹配模式!)的示例,这将如下所示:

patterns = [ r'\d+', r'\s+', r'\w+', r'.*' ]
text = 'abc'
firstMatch = next(
  (match for match in
    (re.match(pattern, text) for pattern in patterns)
   if match),
  False)
Run Code Online (Sandbox Code Playgroud)

它不会对谓词进行两次评估(如果只返回模式就必须这样做)并且它不会在理解中使用像本地人这样的黑客.

但它有两个嵌套的生成器,逻辑只能指定使用一个.所以更好的解决方案会很好.


A. *_*ady 6

你的问题有些含糊不清.你对first和regex例子的定义意味着有一个布尔测试.但是分母示例明确地有一个if子句; 所以每个整数恰好都是真的只是巧合.

看起来next和itertools.ifilter的组合会给你你想要的东西.

match = next(itertools.ifilter(None, (regex.match(big_text) for regex in regexes)))
Run Code Online (Sandbox Code Playgroud)


Zor*_*vic 6

itertools中有一个"切片"迭代器.它模拟了我们在python中熟悉的切片操作.您正在寻找的是类似于此的东西:

myList = [0,1,2,3,4,5]
firstValue = myList[:1]
Run Code Online (Sandbox Code Playgroud)

迭代器使用itertools的等价物:

from itertools import islice
def MyGenFunc():
    for i in range(5):
        yield i

mygen = MyGenFunc()
firstValue = islice(mygen, 0, 1)
print firstValue 
Run Code Online (Sandbox Code Playgroud)