Метод Python, который также является функцией генератора? - PullRequest
1 голос
/ 25 марта 2011

Я пытаюсь создать метод, который также действует как функция генератора, при переключении переключателя (want_gen ниже).

Что-то вроде:

def optimize(x, want_gen):
    # ... declaration and validation code
    for i in range(100):
        # estimate foo, bar, baz
        # ... some code here

        x = calculate_next_x(x, foo, bar, baz)

        if want_gen:
           yield x

    if not want_gen:
       return x

Но, конечно, это не работает - Python, по-видимому, не допускает yield и return в одном методе, даже если они не могут быть выполнены одновременно.

Код довольно сложен, и рефакторинг объявления и кода проверки не имеет большого смысла (слишком много переменных состояния - я в итоге получу трудные для имени подпрограммы-помощники из 7+ параметров, которыерешительно некрасиво).И, конечно же, я бы хотел как можно больше избегать дублирования кода.

Есть ли здесь какой-нибудь шаблон кода, который имел бы смысл для достижения желаемого поведения?


Почемумне это нужно?

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

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

Ответы [ 7 ]

3 голосов
/ 25 марта 2011

Поскольку все, что вам нужно, это какая-то обратная связь для долго выполняющейся функции, почему бы просто не передать ссылку на процедуру обратного вызова, которая будет вызываться через регулярные интервалы?

2 голосов
/ 25 марта 2011

Ну ... мы всегда можем помнить, что yield был реализован в языке как способ облегчить существование объектов-генераторов, но всегда можно реализовать их либо с нуля, либо используя все лучшее из обоих миров:

class  Optimize(object):
    def __init__(self, x):
        self.x = x
    def __iter__(self):
        x = self.x
        # ... declaration and validation code
        for i in range(100):
            # estimate foo, bar, baz
            # ... some code here

            x = calculate_next_x(x, foo, bar, baz)
            yield x
    def __call__(self):
        gen = iter(self)
        return gen.next()

def optimize(x, wantgen):
    if wantgen:
        return iter(Optimize(x))
    else:
        return Optimize(x)()

Не то чтобы вам даже не нужна оболочка функции "optimize" - я просто вставил ее туда, чтобы она стала заменой для вашего примера (сработало бы).

Способ объявления класса, вы можете сделать просто:

for y in Optimize(x):
    #code

чтобы использовать его как генератор, или:

 k = Optimize(x)()

чтобы использовать его как функцию.

2 голосов
/ 25 марта 2011

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

def optimize(x, want_gen):
    def optimize_gen(x):
        # ... declaration and validation code
        for i in range(100):
            # estimate foo, bar, baz
            # ... some code here

            x = calculate_next_x(x, foo, bar, baz)

            if want_gen:
               yield x
    if want_gen:
        return optimize_gen(x)

    for x in optimize_gen(x):
        pass
    return x

В качестве альтернативы цикл for в конце может быть записан:

    return list(optimize_gen(x))[-1]

Теперь спросите себя, действительно ли вы хотите это сделать. Почему вы иногда хотите всю последовательность, а иногда хотите только последний элемент? Пахнет немного подозрительно для меня.

2 голосов
/ 25 марта 2011

Правка моего ответа, почему бы просто не всегда уступать?Вы можете иметь функцию, которая выдает одно значение.Если вы этого не хотите, просто выберите, чтобы ваша функция возвращала сам генератор или значение:

 def stuff(x, want_gen):
     if want_gen:
         def my_gen(x):
             #code with yield
         return my_gen
     else:
         return x

Таким образом, вы всегда возвращаете значение.В Python функции являются объектами.

1 голос
/ 25 марта 2011

Не совсем ясно, что вы хотите сделать, если вы переключаетесь между режимами генератора и функции.

Но в качестве первой попытки: возможно, обернуть версию генератора в новый метод, который явно отбрасывает промежуточные шаги?

def gen():
    for i in range(100):
        yield i

def wrap():
    for x in gen():
        pass
    return x

print "wrap=", wrap()

В этой версии вы можете перейти к gen(), циклически изменяя меньшие числа диапазона, внести коррективы, а затем использовать wrap() только тогда, когда хотите закончить.

0 голосов
/ 01 декабря 2011

Как насчет этого шаблона. Сделайте 3 строки изменений, чтобы преобразовать функцию в генератор. Переименуйте его в NewFunctionName. Замените существующую функцию той, которая либо возвращает генератор, если want_gen равен True, либо исчерпывает генератор и возвращает окончательное значение.

0 голосов
/ 25 марта 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...