Объекты или закрытие - когда использовать?

Я могу определить объект и назначить атрибуты и методы:

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)