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

7 65

7 ответов:

Если у вас есть итератор, вы можете просто назвать его 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, 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

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

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)

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

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

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

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

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

в itertools есть итератор "slice". Он эмулирует операции среза, с которыми мы знакомы в 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 

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

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

вы можете использовать звездную распаковку, которая поддерживается в 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 перебирает список и присваивает каждый элемент переменным с левой стороны. Переменные со звездой впереди, принимает остальные как новый список.