Как я могу заставить генераторы / итераторы оценивать как False при исчерпании? - PullRequest
16 голосов
/ 02 ноября 2011

Другие пустые объекты в Python оцениваются как False - как я могу заставить итераторы / генераторы делать это так же?

Ответы [ 3 ]

8 голосов
/ 02 ноября 2011

Гвидо не хочет, чтобы генераторы и итераторы вели себя так.

Объекты по умолчанию верны.Они могут быть ложными, только если они определяют __len__, который возвращает ноль, или __nonzero__, который возвращает False (последний называется __bool__ в Py3.x).

Вы можете добавить один из этих методов кпользовательский итератор, но он не соответствует намерениям Гвидо.Он отклонил добавление __len__ к итераторам, где известна предстоящая длина.Таким образом, вместо этого мы получили __length_hint__.

Таким образом, единственный способ узнать, пуст ли итератор, - это вызвать на нем next () и посмотреть, вызовет ли он StopIteration .

В ASPN, я полагаю, есть некоторые рецепты, использующие эту технику для обертки.Если значение выбрано, оно сохраняется для предстоящего вызова next ().

6 голосов
/ 02 ноября 2011

По умолчанию все объекты в Python оцениваются как True.Чтобы поддерживать оценки False, класс объекта должен иметь либо метод __len__ (0 -> False), либо метод __nonzero__ (False -> False).Примечание: __nonzero__ ==> __bool__ в Python 3.x.

Поскольку протокол итератора намеренно прост, и существует много типов итераторов / генераторов, которые не могут знать,Есть еще значения, которые нужно сгенерировать, прежде чем пытаться их сгенерировать, True / False оценка не является частью протокола итератора.

Если вы действительно хотите такое поведение, вы должны предоставить его самостоятельно.Один из способов заключается в том, чтобы обернуть генератор / итератор в класс, который обеспечивает отсутствующую функциональность.

Обратите внимание, что этот код оценивается только в False после повышения StopIteration.

В качестве бонуса этот код работает для питонов 2.4 +

try:
    next
except NameError:       # doesn't show up until python 2.6
    def next(iter):
        return iter.next()

Empty = object()

class Boolean_Iterator(object):
    """Adds the abilities
    True/False tests:  True means there /may/ be items still remaining to be used
    """
    def __init__(self, iterator):
        self._iter = iter(iterator)
        self._alive = True
    def __iter__(self):
        return self
    def __next__(self):
        try:
            result = next(self._iter)
        except StopIteration:
            self._alive = False
            raise
        return result
    next = __next__                     # python 2.x
    def __bool__(self):
        return self._alive
    __nonzero__ = __bool__              # python 2.x

Если вы также хотите использовать прогнозирующее (или заглядывающее) поведение, этот код выполнит свою задачу (он оценивается как False до повышения StopIteration):

try:
    next
except NameError:       # doesn't show up until python 2.6
    def next(iter):
        return iter.next()

Empty = object()

class Iterator(object):
    """Adds the abilities
    True/False tests:  True means there are items still remaining to be used
    peek(): get the next item without removing it from the sequence
    """
    def __init__(self, iterator):
        self._iter = iter(iterator)
        self._peek = Empty
        self.peek()
    def __next__(self):
        peek, self._peek = self._peek, Empty
        self.peek()
        if peek is not Empty:
            return peek
        raise StopIteration
    next = __next__                     # python 2.x
    def __bool__(self):
        return self._peek is not Empty
    __nonzero__ = __bool__              # python 2.x
    def peek(self):
        if self._peek is not Empty:
            return self._peek
        self._peek = next(self._iter, Empty)
        return self._peek

Имейте в виду, что поведение при просмотре не подходит, когда время основного итератора / генератора соответствует его произведенным значениям.

Также имейте в виду, что сторонний код и, возможно, stdlib, может полагаться на итераторы / генераторы, всегда вычисляющие True.Если вы хотите посмотреть без bool, удалите методы __nonzero__ и __bool__.

4 голосов
/ 02 ноября 2011

'пустая вещь' автоматически не является итератором.контейнеры могут быть пустыми или нет, и вы можете получить итераторы над контейнерами, но эти итераторы не являются ложными, когда исчерпаны.

Хороший пример того, почему итераторы не становятся ложными, это sys.stdin.Проблема с созданием sys.stdin falsey, когда он достигает конца ввода, состоит в том, что невозможно на самом деле узнать, достиг ли вы конца такого потока, не пытаясь использовать входные данные из него.Основная причина, по которой итератор должен быть ложным, состоит в том, чтобы «посмотреть», чтобы убедиться, что получение следующего элемента будет правильным;но для sys.stdin это, очевидно, не практично.

вот еще один пример

(x for x in xrange(1000) if random.randrange(0, 2))

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

Решение состоит в том, чтобы просто получить следующее значение от итератора.Если он пуст, ваш цикл завершится, или вы получите исключение StopIteration, если вы не в цикле.

...