Почему в Python нет первой (итерируемой) встроенной функции?

Мне интересно, есть ли там причина, по которой нет 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] и короткого замыкания в положительном совпадении.

Ответ 1

Если у вас есть итератор, вы можете просто вызвать его метод next. Что-то вроде:

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

Ответ 2

Там пакет Pypi называется "первым" , который делает это:

>>> 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

Ответ 3

Недавно я задал аналогичный вопрос (теперь он был отмечен как дубликат этого вопроса). Моя забота также заключалась в том, что я хотел использовать встроенные модули только для решения проблемы поиска первого истинного значения генератора. Тогда мое собственное решение было следующим:

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)

Он не оценивает предикат дважды (как вам нужно было бы, если бы был возвращен шаблон), и он не использует хаки, например, locals в понимании.

Но у него есть два генератора, в которых логика будет диктовать использовать только одну. Поэтому лучшее решение было бы неплохо.

Ответ 4

Там есть какая-то двусмысленность в вашем вопросе. Ваше определение first и пример регулярного выражения подразумевают, что существует логический тест. Но пример denominators явно имеет предложение if; так что это просто совпадение, что каждое целое число истинно.

Похоже, что сочетание следующего и itertools.ifilter даст вам то, что вы хотите.

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

Ответ 5

Haskell использует то, что вы только что описали, как функция take (или как частичная функция take 1, технически). Python Cookbook содержит написанные генератор-обертки, которые выполняют те же функции, что и take, takeWhile и drop в Haskell.

Но что касается того, почему это не встроено, ваша догадка так же хороша, как моя.

Ответ 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 

Ответ 7

Вы можете использовать распаковку звездочек, которая поддерживается в Python 3.x. Вы должны прочитать этот PEP: https://www.python.org/dev/peps/pep-3132/

x = [0, 1, 2, 3]
first, *rest = x
print(first)
print(rest)

Python выполняет итерацию по списку и присваивает каждому элементу переменные с левой стороны. Переменные со звездой впереди, оставляют остальное как новый список.