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

StackOverflow https://stackoverflow.com/questions/1077307

  •  21-08-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

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

Другие советы

Есть Пакет 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, написанной Хайнеком Шлаваком, выпущенной под лицензией 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

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

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)

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

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

В 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 

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

Похоже, что комбинация next и itertools.ifilter даст вам то, что вы хотите.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top