Генератор питона с проверкой на пустое состояние
Генераторы python - хорошая замена для списков, в большинстве случаев ожидающих, где я хочу проверить пустое состояние, которое невозможно с помощью простых генераторов. Я пытаюсь написать оболочку, которая позволит проверять пустое состояние, но все еще ленив и дает преимущество генераторов.
class mygen:
def __init__(self,iterable):
self.iterable = (x for x in iterable)
self.peeked = False
self.peek = None
def __iter__(self):
if self.peeked:
yield self.peek
self.peeked = False
for val in self.iterable:
if self.peeked:
yield self.peek
self.peeked = False
yield val
if self.peeked:
yield self.peek
self.peeked = False
def __nonzero__(self):
if self.peeked:
return True
try:
self.peek = self.iterable.next()
self.peeked = True
return True
except:
return False
- Я думаю, что он ведет себя правильно, как простой генератор. Есть ли угловой корпус
Я скучаю?
- Это не выглядит элегантно. Есть ли еще лучший питонический способ сделать то же самое?
Использование образца:
def get_odd(l):
return mygen(x for x in l if x%2)
def print_odd(odd_nums):
if odd_nums:
print "odd numbers found",list(odd_nums)
else:
print "No odd numbers found"
print_odd(get_odd([2,4,6,8]))
print_odd(get_odd([2,4,6,8,7]))
Ответы
Ответ 1
Я бы обычно не применял этот вид
генератора. Существует идиоматический способ проверки, если итератор it
исчерпан:
try:
next_item = next(it)
except StopIteration:
# exhausted, handle this case
Подставляя эту идиому EAFP в какую-то специфическую для проекта LBYL-идиому,
путают и вообще не выгодны.
Итак, вот как я мог бы реализовать это, если бы я действительно хотел:
class MyIterator(object):
def __init__(self, iterable):
self._iterable = iter(iterable)
self._exhausted = False
self._cache_next_item()
def _cache_next_item(self):
try:
self._next_item = next(self._iterable)
except StopIteration:
self._exhausted = True
def __iter__(self):
return self
def next(self):
if self._exhausted:
raise StopIteration
next_item = self._next_item
self._cache_next_item()
return next_item
def __nonzero__(self):
return not self._exhausted
Ответ 2
Используйте itertools.tee
для реализации ненулевого теста и просто кэшируйте его при создании:
from itertools import tee
class NonZeroIterable(object):
def __init__(self, iterable):
self.__iterable, test = tee(iter(iterable))
try:
test.next()
self.__nonzero = True
except StopIteration:
self.__nonzero = False
def __nonzero__(self):
return self.__nonzero
def __iter__(self):
return self.__iterable
Маленькая демонстрация:
>>> nz = NonZeroIterable('foobar')
>>> if nz: print list(nz)
...
['f', 'o', 'o', 'b', 'a', 'r']
>>> nz2 = NonZeroIterable([])
>>> if not nz2: print 'empty'
...
empty
Эта версия NonZeroIterable кэширует флаг; он, таким образом, только сообщает вам, был ли итератор не пустым в начале. Если вам нужно проверить итерацию в других точках жизненного цикла, используйте версию Sven; там флаг __nonzero__
будет сообщать вам после каждой итерации, если еще осталось еще элементов.
Боковое примечание на вашем примере
Ваш образец кода слишком прост и не является хорошим аргументом для вашего использования; вы сначала проверяете на непустоту (который потенциально выполняет итерации по списку ввода для поиска нечетного числа), но затем исчерпает весь итератор. Следующий код был бы столь же эффективным и не требовал бы, чтобы вы изобретали способы сломать идиомы python:
def print_odd(odd_nums):
odd_nums = list(odd_nums)
if odd_nums:
print "odd numbers found", odd_nums
else:
print "No odd numbers found"