Ответ 1
Если у вас есть итератор, вы можете просто вызвать его метод next
. Что-то вроде:
In [3]: (5*x for x in xrange(2,4)).next()
Out[3]: 10
Мне интересно, есть ли там причина, по которой нет 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, 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)
Он не оценивает предикат дважды (как вам нужно было бы, если бы был возвращен шаблон), и он не использует хаки, например, locals в понимании.
Но у него есть два генератора, в которых логика будет диктовать использовать только одну. Поэтому лучшее решение было бы неплохо.
Там есть какая-то двусмысленность в вашем вопросе. Ваше определение first и пример регулярного выражения подразумевают, что существует логический тест. Но пример denominators явно имеет предложение if; так что это просто совпадение, что каждое целое число истинно.
Похоже, что сочетание следующего и itertools.ifilter даст вам то, что вы хотите.
match = next(itertools.ifilter(None, (regex.match(big_text) for regex in regexes)))
Haskell использует то, что вы только что описали, как функция take
(или как частичная функция take 1
, технически). Python Cookbook содержит написанные генератор-обертки, которые выполняют те же функции, что и take
, takeWhile
и drop
в Haskell.
Но что касается того, почему это не встроено, ваша догадка так же хороша, как моя.
В 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
Вы можете использовать распаковку звездочек, которая поддерживается в 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 выполняет итерацию по списку и присваивает каждому элементу переменные с левой стороны. Переменные со звездой впереди, оставляют остальное как новый список.