Объекты или закрытие - когда использовать?
Я могу определить объект и назначить атрибуты и методы:
class object:
def __init__(self,a,b):
self.a = a
self.b = b
def add(self):
self.sum = self.a + self.b
def subtr(self):
self.fin = self.sum - self.b
def getpar(self):
return self.fin
obj = object(2,3)
obj.add()
obj.subtr()
obj.getpar()
или предоставить такую же функциональность, указав замыкание:
def closure(a,b):
par = {}
def add():
par.update({'sum':a+b})
def subtr():
par.update({'fin':par['sum']-b})
def getpar():
return par['fin']
return {'add':add,'subtr':subtr,'getpar':getpar}
clos = closure(2,3)
clos['add']()
clos['subtr']()
clos['getpar']()
Я думаю, что синтаксис объекта будет выглядеть более чистым для большинства зрителей, но есть ли случаи, когда использование закрытия было бы семантически предпочтительным?
Ответы
Ответ 1
В Python закрытие может быть сложнее отлаживать и использовать, чем более обычные объекты (вы должны где-то сохранить вызовы, получить к ним доступ с загнутой нотой clos['add']
и т.д.,...). Рассмотрим, например, невозможность доступа к sum
, если вы найдете что-то странное в результате... отладка такого рода может быть очень сложной; -)
Единственное реальное компенсирующее преимущество - простота, но в основном это относится только к действительно простым случаям (к тому времени, когда у вас есть три вызова, которые являются внутренними функциями, я бы сказал, что вы за борт в этом отношении).
Возможно, очень сильная защита (защита объектов "по соглашению" для объектов класса и экземпляра) или возможное более высокое знакомство с программистами Javascript может оправдать использование закрытий, а не классов и экземпляров в маргинальных случаях, но на практике я не оказались в тех случаях, когда такие гипотетические преимущества, по-видимому, действительно применялись - поэтому я использую только закрытие в действительно простых случаях; -).
Ответ 2
Вы должны использовать версию, которая наиболее четко выражает то, что вы пытаетесь достичь.
В приведенном примере я бы сказал, что объектная версия более понятна, поскольку она, похоже, моделирует объект с измененным состоянием. Если посмотреть на код, который использует это значение, версия объекта, по-видимому, выражает ясное намерение, в то время как версия закрытия, похоже, имеет операции (индексирование и "магические" строки), которые находятся рядом с точкой.
В Python я бы предпочел подход, основанный на закрытии, когда нужно то, что в основном похоже на функцию, и, возможно, нужно захватить некоторое состояние.
def tag_closure(singular, plural):
def tag_it(n):
if n == 1:
return "1 " + singular
else:
return str(n) + " " + plural
return tag_it
t_apple = tag_closure("apple", "apples")
t_cherry = tag_closure("cherry", "cherries");
print t_apple(1), "and", t_cherry(15)
Это, возможно, немного яснее следующего:
class tag_object(object):
def __init__(self, singular, plural):
self.singular = singular
self.plural = plural
def tag(self, n):
if n == 1:
return "1 " + self.singular
else:
return str(n) + " " + self.plural
t_apple = tag_object("apple", "apples")
t_cherry = tag_object("cherry", "cherries");
print t_apple.tag(1), "and", t_cherry.tag(15)
Как правило: если вещь действительно единственная функция, и она только фиксирует статическое состояние, тогда рассмотрим закрытие. Если предмет предназначен для изменяемого состояния и/или имеет более одной функции, используйте класс.
Еще один способ выразить это: если вы создаете диктовку замыканий, вы по существу дублируете ручную машину класса. Лучше оставить его для конструкции языка, предназначенной для этого.
Ответ 3
Единственная реальная причина, по которой я могу видеть закрытие, - это, если вы хотите довольно сильную гарантию, чтобы пользователь вашего объекта/закрытия не имел доступа к скрытой переменной.
Что-то вроде этого:
class Shotgun:
def __init__(self):
me = {}
me['ammo'] = 2
def shoot():
if me['ammo']:
me['ammo'] -= 1
print "BANG"
else:
print "Click ..."
self.shoot = shoot
s = Shotgun()
s.shoot()
s.shoot()
s.shoot()
Ответ 4
Вызывающий синтаксис для класса выглядит лучше, и вы можете создавать классы подкласса. Закрытие также включает повторение всех имен, которые вы хотите открыть в инструкции return
. Возможно, это нормально, хотя если вы хотите иметь действительно закрытые методы, которые более скрыты, чем обычное условное обозначение подчеркивания
Ответ 5
Я сделал комментарий о том, что с использованием атрибутов функции закрытие может использовать тот же синтаксис, что и класс, поскольку функции являются объектами в python. Все внутренние переменные также становятся доступными, как метод класса.
Мне любопытно, поддерживает ли этот подход Bad Things, о котором я не знаю.
def closure(a, b):
def add():
closure.sum = a + b
def subtr():
closure.fin = closure.sum - b
def getpar():
return closure.fin
closure.add = add;
closure.subtr = subtr
closure.getpar = getpar
return closure
clo = closure(2,3)
clo.add()
clo.subtr()
print(clo.getpar())
print(clo.sum)
print(clo.fin)