Идиомы в python: закрытие vs functor vs object

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

1) Закрытие:

def outer():
    previous_state = None
    def inner(current_state) :
        nonlocal previous_state
        #do something
        previous_state=current_state
        return something

Итак, если вы исходите из фона javascript, это, несомненно, будет для вас естественным. В python это тоже довольно естественно, вплоть до того, что вам нужно получить доступ к охватывающей области, когда вы в конце концов сделаете что-то вроде inner.__code__.co_freevars, которое даст вам имена ваших охватывающих переменных как кортеж и найдет индекс тот, который вы хотите, а затем перейдите к inner.__closure__[index].cell_contents, чтобы получить его значение. Не совсем элегантный, но я полагаю, что точка часто скрывает область видимости, поэтому имеет смысл, что ее трудно достичь. С другой стороны, также кажется немного странным, что python делает закрывающую функцию частной, когда она почти полностью покончила с частной переменной по сравнению с языками ООП.

2) Функтор

def outer():
    def inner(current_state):
        #do something
        inner.previous_state=current_state
        return something
    ret = inner
    ret.previous_state=None
    return ret

Это "открывает закрытие", поскольку теперь охватывающее состояние полностью отображается как атрибут функции. Это работает, потому что функции на самом деле просто маскируют объекты. Я склоняюсь к этому как к самому пифоническому. Его ясная, краткая и читаемая.

3) Объекты Это, вероятно, наиболее знакомый программистам ООП

class Calculator(Object) :
    def __init__(self):
        self.previous_state=None

    def do_something(self, current_state) :
        #do_something
        self.previous_state = current_state
        return something

Самая большая проблема заключается в том, что вы, как правило, получаете множество определений классов. Это хорошо на полностью OOP-языке, таком как Java, где у вас есть интерфейсы и т.д., Чтобы управлять этим, но на утином языке кажется немного странным, чтобы иметь много простых классов, чтобы нести вокруг функции, которая нуждается в немного состоянии.

4) globals - я не буду демонстрировать это, поскольку я специально хочу избежать загрязнения глобального пространства имен

5) Декораторы - это немного криволинейный шар, но вы можете использовать декораторы для хранения информации о частичном состоянии.

@outer
def inner(previous_state, current_state):
    #do something
    return something

def outer(inner) :
    def wrapper(current_state) :
        result =  inner(wrapper.previous_state, current_state)
        wrapper.previous_state = current_state
        return result
    ret = wrapper
    ret.previous_state=None
    return result

Этот тип синтаксиса менее всего мне знаком, но если я теперь позвоню

func = inner

Я действительно получаю

func = outer(inner)

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

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

Ответ 1

Таким образом, правильным ответом на этот вопрос является вызываемый объект, который существенно заменяет идиому закрытия в python.

поэтому отмените вариант 3 выше:

class Calculator(Object) :
    def __init__(self):
        self.previous_state=None

    def do_something(self, current_state) :
        #do_something
        self.previous_state = current_state
        return something

to

class Calculator(Object) :
    def __init__(self):
        self.previous_state=None

    def __call__(self, current_state) :
        #do_something
        self.previous_state = current_state
        return something

и теперь вы можете называть его как функцию. Итак,

func = Calculator():
for x in list:
    func(x)

Ответ 2

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

def make_gen():
    previous_state = None
    for row in rows:
        # do something
        previous_state = current_state
        yield something

thing = make_gen()
for item in thing:
    # Each iteration, item is a different value
    # "returned" by the yield statement in the generator

Вместо того, чтобы повторно называть thing (который заменяет вашу внутреннюю функцию), вы перебираете его (что в основном повторяет вызов next(thing)).

Состояние полностью содержится внутри тела генератора.

Если вы не хотите на самом деле перебирать его, вы можете выборочно "повторно ввести" сопроцесс, явно вызывая next.

thing = make_gen()
first_item = next(thing)
# do some stuff
second_item = next(thing)
# do more stuff
third_item = next(thing)
fourth_item = next(thing)
# etc