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


75

我想知道是否有某种原因,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')

这样,您可以表达以下内容:

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]正向匹配并使其短路,可以节省许多不必要的处理。


请注意:我意识到谓词kwarg对于生成器功能是多余的。我只是想彻底定义“第一”的真正含义。
cdleary

Answers:


50

如果有迭代器,则可以调用其next方法。就像是:

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

14
上面的方法在Python 3中不起作用,请使用next(x)ifx是迭代器,还是next(iter(d))ifd是可迭代的
Taha Jahangir

2
适用于Python 2.6及更高版本中所有可迭代对象(例如列表和生成器)的通用解决方案是next(iter(xs))。在Python 2.5中可以做到iter(xs).next()
sah

9
我不明白这个答案。问题中显示的“第一个”跳过了序列的初始元素,即“虚假”(由bool(predicate(item))定义)。我认为这就是重点。'next()'不这样做。我很迷惑。
乔纳森·哈特利

2
@JonathanHartley:问题是,有next()和建立一个过滤序列的通用方法(例如,使用itertools.ifilter()或者(… for … in … if condition),将它们组合起来是不是足够的努力来证明有其他的内置工具,需要注意的是OP的正则表达式的例子是公正的。next(regex for regex in regexes if regex.match(big_text))
liori

4
OP的示例返回的内容不同于next(regex for regex in regexes if 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)))与第一个相比,它似乎过于复杂。
2013年

14

有一个叫做“ first”Pypi包可以做到这一点:

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

例如,这是返回第一个奇数的方法:

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

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

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

这是一个非常小的软件包:它仅包含此函数,没有依赖关系,并且可在Python 2和3上运行。它是一个文件,因此您甚至不必安装它即可使用。

实际上,这几乎是全部源代码(来自MIT许可下的Hynek Schlawack的2.0.1版本):

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

6
真好 但是,由您自己实现将需要三行代码。这几乎无法证明安装完整软件包的开销(引入了所有可移植性问题等)。问题仍然存在:为什么这不是Python内置函数的一部分?还是使用内置的Python结构最清晰,最Python化的方法来解决这个问题?
阿尔夫,

2
@Alfe:使用软件包是干净且Pythonic的。至于为什么它不是内置的,那不是Stack Overflow的问题,因为它不是任何非核心提交者都无法回答的。
Flimm

2
好的,然后让我这样说:您将如何first从模块实现功能first?当我问为什么这不是内置函数时,我这样做是因为我怀疑有Python方式可以使用列表推导等更通用的功能来表达它,从而使其多余以致于无法使用。
阿尔夫,

2
@Alfie:足够公平。其他答案试图做到这一点,但是如您所见,结果并不是那么漂亮,而且我敢肯定,有些可以轻松安装模块的人会发现first有用。我包括了该函数的源代码,以引起兴趣。
Flimm 2013年

“但是,单独实现大约需要三行代码”->好吧,我至少看到了十个有关此功能的答案,而且似乎所有这些都至少涉及到带有循环或函数的几行具有收益率,或者来自python新手的任何看似神奇的pythonic技巧。在ruby中,执行mylist.first或myiterator.first即可。操作简单,errorproof和最可读
亚历˚F

11

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

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)

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

但是它有两个嵌套的生成器,逻辑将只使用其中一个。因此,更好的解决方案将是不错的选择。


6

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 

6

您的问题有些含糊。您对first和regex示例的定义意味着存在布尔测试。但是分母示例显式具有if子句;因此,每个整数碰巧都是真是一个巧合。

看起来next和itertools.ifilter的组合将为您提供所需的内容。

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

是的,如果答案为零,那么我们会有问题。这next(iterator)就是我所缺少的答案。
09年

4

Haskell将您刚刚描述的内容作为函数take(或从take 1技术上说作为部分函数)使用。 蟒食谱具有书面发电机-包装,作为执行相同的功能taketakeWhiledrop在Haskell。

但是关于为什么它不是内置的,您的猜测和我的一样好。


3
这样的函数(在类型和速度方面都是“内置”!)在标准库的itertools模块中-就像(例如)正则表达式在re模块中,math函数在math模块中一样。确定在主命名空间中最好的呈现方式– Perl具有RE作为内置函数,Fortran具有SIN和COS&c,Haskell保留了诸如take之类的名称。Python倾向于在标准库模块中具有所有这些名称组。
亚历克斯·马丁里

2
Haskell等同于firsthead吗?“ take 1”返回一个列表,而不是一个元素。
tokland
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.