Python Generator - Mutate последний результат? - PullRequest
1 голос
/ 16 ноября 2011

Я пытаюсь сделать выбор между следующими двумя определениями моего генератора. Что лучше? Что является "более питоническим"? И есть ли способ смягчить недостатки каждого из них?

def myGenerator1(howMany):
    result = [0,0,0]
    yield result
    for i in range(howMany)
        modifyListInPlace(result)
        yield result

for val in myGenerator1(1000):
    useValThenForgetIt(val)

def myGenerator2(howMany):
    result = (0,0,0)
    yield result
    for i in range(howMany)
        result = createNewUpdatedTuple(result)
        yield result

for val in myGenerator2(1000):
    useValThenForgetIt(val)

Первый изменяет значение, которое было возвращено генератором, возможно, путаясь с вызывающим кодом, который я еще не предвидел. Второй в этом случае создает мусор на 1000 кортежей, или больше, если я увеличу «howMany» (что я мог бы).

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

Ответы [ 2 ]

3 голосов
/ 16 ноября 2011

Глядя на стандартную библиотеку в качестве руководства, комбинаторные функции в модуле itertools все возвращают кортежи, даже если базовый алгоритм является алгоритмом мутирования на месте.Например, посмотрите на код для itertools.permutations .

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

Еще одна мысль.Я бы не стал слишком беспокоиться о «создании мусора на тысячи кортежей» для неиспользованных результатов.Реализация кортежей в Python очень хороша в повторном использовании ранее размещенных кортежей (используя массив свободных списков, он может создать новый кортеж из ранее использованного, не обращаясь к распределителю памяти).Таким образом, кортежная версия может быть просто производительной версией списка или даже чуть лучше.

1 голос
/ 16 ноября 2011

Тот факт, что первый может вернуть объект, а затем неявно изменить его после того, как он был возвращен, является ОГРОМНЫМ запахом кода для меня, независимо от того, какой язык вы используете (т.е. это не проблема того, чтобы быть "пифоническим"). «). Кроме того, зачем вам нужна функция, которая выдает итератор для одного и того же значения снова и снова, изменяя между выходами? Мне кажется, очень не интуитивно.

Если вы используете значения, тогда кортежи, созданные myGenerator2, не являются мусором. Если вы используете их по одному, все они никогда не будут существовать в одно и то же время, и ваша программа почти наверняка будет выполнять много других операций выделения / освобождения памяти. В отличие от списка, возвращаемого range(howMany), который создаст 1000 целых чисел, которые вы фактически никогда не используете (если только вы не на Python3, в этом случае range возвращает генератор, а не список).

Если есть какая-либо вероятность того, что вызывающая сторона может захотеть удержаться за ссылкой на что-то, возвращаемое вашим генератором (и программисты Python обычно ожидают, что, когда дан генератор, он сможет пойти items = list(generator) если им нужно использовать их более одного раза), то второе намного лучше.

...