Идиомы в 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