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

我想知道在 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]和正匹配上的短路,可以节省大量不必要的处理。

43492 次浏览

In Python 2, if you have an iterator, you can just call its next method. Something like:

>>> (5*x for x in xrange(2,4)).next()
10

In Python 3, you can use the next built-in with an iterator:

>>> next(5*x for x in range(2,4))
10

Haskell makes use of what you just described, as the function take (or as the partial function take 1, technically). Python Cookbook has generator-wrappers written that perform the same functionality as take, takeWhile, and drop in Haskell.

But as to why that's not a built-in, your guess is as good as mine.

There's some ambiguity in your question. Your definition of first and the regex example imply that there is a boolean test. But the denominators example explicitly has an if clause; so it's only a coincidence that each integer happens to be true.

It looks like the combination of next and itertools.ifilter will give you what you want.

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

There's a Pypi package called “first” that does this:

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

Here's how you would use to return the first odd number, for example:

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

If you just want to return the first element from the iterator regardless of whether is true or not, do this:

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

It's a very small package: it only contains this function, it has no dependencies, and it works on Python 2 and 3. It's a single file, so you don't even have to install it to use it.

In fact, here's almost the entire source code (from version 2.0.1, by Hynek Schlawack, released under the MIT licence):

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

I asked a similar question recently (it got marked as a duplicate of this question by now). My concern also was that I'd liked to use built-ins only to solve the problem of finding the first true value of a generator. My own solution then was this:

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

For the example of finding the first regexp match (not the first matching pattern!) this would look like this:

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)

It does not evaluate the predicate twice (as you would have to do if just the pattern was returned) and it does not use hacks like locals in comprehensions.

But it has two generators nested where the logic would dictate to use just one. So a better solution would be nice.

There is a "slice" iterator in itertools. It emulates the slice operations that we're familiar with in python. What you're looking for is something similar to this:

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

The equivalent using itertools for iterators:

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


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