истощенные итераторы - что с ними делать? - PullRequest
5 голосов
/ 15 октября 2010

(в Python 3.1) (Отчасти связан с другим вопросом, который я задал , но этот вопрос касается исчерпания итераторов.)

# trying to see the ratio of the max and min element in a container c
filtered = filter(lambda x : x is not None and x != 0, c)
ratio = max(filtered) / min(filtered)

Мне потребовалось полчаса, чтобы понять, в чем проблема (итератор, возвращаемый фильтром, исчерпывается к тому времени, когда он получает второй вызов функции). Как мне переписать это наиболее питонским / каноническим способом?

Кроме того, что я могу сделать, чтобы избежать ошибок такого рода, кроме получения большего опыта? (Честно говоря, мне не нравится эта языковая функция, так как ошибки такого типа легко сделать и их трудно поймать.)

Ответы [ 4 ]

7 голосов
/ 15 октября 2010

Здесь может помочь функция itertools.tee:

import itertools

f1, f2 = itertools.tee(filtered, 2)
ratio = max(f1) / min(f2)
5 голосов
/ 15 октября 2010

На самом деле ваш код вызывает исключение, которое предотвратит эту проблему! Итак, я думаю, проблема была в том, что вы замаскировали исключение?

>>> min([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: min() arg is an empty sequence
>>> min(x for x in ())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: min() arg is an empty sequence

В любом случае, вы также можете написать новую функцию, которая будет указывать минимальное и максимальное значения одновременно:

def minmax( seq ):
    " returns the `(min, max)` of sequence `seq`"
    it = iter(seq)
    try:
        min = max = next(it)
    except StopIteration:
        raise ValueError('arg is an empty sequence')
    for item in it:
        if item < min:
            min = item
        elif item > max:
            max = item
    return min, max
5 голосов
/ 15 октября 2010

вы можете преобразовать итератор в кортеж, просто вызвав tuple (итератор)

однако я бы переписал этот фильтр для понимания списка, который бы выглядел примерно так

# original
filtered = filter(lambda x : x is not None and x != 0, c)

# list comp
filtered = [x for x in c if x is not None and x != 0]
3 голосов
/ 15 октября 2010

Сущность filtered по сути является объектом с состоянием.Конечно, сейчас очевидно, что запуск max или min на нем изменит это состояние.Чтобы перестать спотыкаться об этом, я хотел бы прояснить (на самом деле для себя), что я создаю что-то, а не просто преобразую что-то:

Добавление дополнительного шага действительно может помочь:

def filtered(container):
    return filter(lambda x : x is not None and x != 0, container)

ratio = max(filtered(c)) / min(filtered(c))

Если вы поместите filtered(...) в какую-то функцию (может быть, она не нужна для чего-то еще) или определите ее как функцию уровня модуля, то вам решать,но в этом случае я бы предположил, что если filtered (итератор) нужен только для функции, оставьте его там, пока он не понадобится где-то еще.

Другая вещь, которую вы можете сделать, - это создать list от него, который оценит итератор:

filtered_iter = filter(lambda x : x is not None and x != 0, container)
filtered = list(filtered_iter)

ratio = max(filtered) / min(filtered)

(Конечно, вы можете просто сказать filtered = list(filter(...)).)

...