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

时间:2009-07-03 00:07:07

标签: python iterator generator

我想知道是否存在Python内置函数中没有first(iterable)的原因,有点类似于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')

这样你就可以表达:

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

显然,在这种情况下你不能做list(generator)[0],因为生成器不会终止。

或者如果你有一堆正则数据要匹配(当它们都有相同的groupdict接口时很有用):

match = first(regex.match(big_text) for regex in regexes)

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

6 个答案:

答案 0 :(得分:46)

如果你有一个迭代器,你可以调用它的next方法。类似的东西:

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

答案 1 :(得分:13)

有一个Pypi package called “first”可以做到这一点:

>>> from first import first
>>> first([0, None, False, [], (), 42])
42

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

>> first([2, 14, 7, 41, 53], key=lambda x: x % 2 == 1)
7

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

>>> first([0, None, False, [], (), 42], key=lambda x: True)
0

这是一个非常小的包:它只包含这个函数,它没有依赖关系,它适用于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

答案 2 :(得分:10)

我最近问过similar question(现在它被标记为此问题的副本)。我还担心的是,我喜欢使用内置的 来解决查找生成器第一个真值的问题。我自己的解决方案就是:

x = next((v for v in (f(x) for x in a) if v), False)

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

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)

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

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

答案 3 :(得分:6)

你的问题有些含糊不清。您对第一个的定义和正则表达式示例意味着存在布尔测试。但是分母示例明确地有一个if子句;所以每个整数恰好都是真的只是巧合。

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

match = next(itertools.ifilter(None, (regex.match(big_text) for regex in regexes)))

答案 4 :(得分:5)

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

myList = [0,1,2,3,4,5]
firstValue = myList[:1]

迭代器使用itertools的等价物:

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

mygen = MyGenFunc()
firstValue = islice(mygen, 0, 1)
print firstValue 

答案 5 :(得分:4)

Haskell使用您刚才描述的内容,作为函数take(或技术上的部分函数take 1)。 Python Cookbook编写了generator-wrappers,它在Haskell中执行与taketakeWhiledrop相同的功能。

但至于为什么这不是内置的,你的猜测和我的一样好。