Рубрики
Без рубрики

Закрытие? Я едва знаю ее!

Почему замыкание полезны, даже если вы не замечаете, что вы их используете. Теги с Python, функциональными, закрытыми.

Крышка изображения Кредит: этот удивительный ответ стойки Отказ

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

Что замыкают

Закрытие (также называемое крышением функции . Я покажу вам кучу примеров, если это не имеет смысла. Существует ряд способов создания закрытия, но канонический – это определить и вернуть функцию изнутри другой функции. Вот что я имею в виду.

def build_zoo():
    animals = []
    def add_animal(animal):
        animals.append(animal)
        return animals
    return add_animal

zoo_a = build_zoo()
zoo_b = build_zoo()

zoo_a("zebra")
# => ["zebra"]
zoo_a("monkey")
# => ["zebra", "monkey"]
zoo_b("snek")
# => ["snek"]
zoo_a("panda")
# => ["zebra", "monkey", "panda"]

Благодаря @Doshirae. и Николас Ли. для указывания опечатки в возвращение утверждение!

build_zoo Функция является своего рода «завод», которая создает Область и определяет функцию в этом объеме. Тогда это дает функцию У этого все еще есть доступ к тому объему (и в нем переменные) тебе. После build_zoo Функция заканчивается, она сохраняет раму стека и определенные переменные (например, Животные ) Доступно для возврата Add_animal Функция, для последующей ссылки. И каждый раз, когда вы называете это build_zoo Функция, она создает совершенно новый объем, не связанный с любым из других областей. Вот почему zoo_a и zoo_b не могли повлиять друг на друга, когда их называли!

Сторона Примечание: Python и Scopes

В Python вы не можете изменить переменные за пределами вашего объема без дополнительной работы. Итак, если вы пробовали что-то вроде этого:

def build_incrementer():
    current_value = 0
    def increment():
        current_value += 1
        return current_value
    return increment

incrementer = build_incrementer()
incrementer()
# => UnboundLocalError: local variable 'current_value' referenced before assignment

Вы получаете ошибку! Это не так на многих языках. На многих языках это нормально для доступа к переменным в родительских областях. В Python вам придется сделать это:

def build_incrementer():
    current_value = 0
    def increment():
        nonlocal current_value # <==
        current_value += 1
        return current_value
    return increment

Это позволяет вам прозревать и изменять это значение. Вы также можете использовать глобальные, но мы не животные, поэтому мы не будем.

Хорошо, но так что?

«Вы можете отслеживать состояние, как миллиард разных путей! «Вы говорите преувеличающе. «Что такое особенное в закрытиях? Они кажутся излишне сложными ». И это немного правдой. Вообще, если я хотел отслеживать свое состояние с функцией, я бы сделал это одним из нескольких разных способов.

Функции генератора

def build_incrementer():
    current_value = 0
    while True:
        current_value += 1
        yield current_value

inc_a = build_incrementer()
inc_b = build_incrementer()

next(inc_a)
# => 1
next(inc_a)
# => 2
next(inc_a)
# => 3
next(inc_b)
# => 1

Этот метод очень «питон». Он не имеет внутренних функций (что вы знаете из), имеет разумно простую для различного потока, а (при условии, что вы понимаете генераторы) и получают работу.

Построить объект

class Incrementer:
    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1
        return self.value

    # Or, just so we can match the section above:
    def __next__(self):
        return self.increment()

inc_a = Incrementer()
inc_b = Incrementer()

next(inc_a)
# => 1
next(inc_a)
# => 2
next(inc_b)
# => 1

Это еще один хороший вариант, и тот, который также имеет большое значение для меня, сделав хорошее количество Ruby, а также питона.

Глобальные переменные

current_value = 0
def increment():
    global current_value
    current_value += 1
    return current_value

increment()
# => 1
increment()
# => 2
increment()
# => 3

Нет.

Но, I–

Нет.

Ждать! Просто позволь мне–

Неа. Не делай этого.

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

Почему закрытия крутые

Закрытия захватывают по трем причинам: они довольно маленькие, они довольно быстро, и они довольно доступны.

Они маленькие

Давайте посмотрим на использование грубого памяти каждого метода (кроме глобальных переменных) выше:

import sys

def build_function_incrementer():
    # ...

funky = build_function_incrementer()
def build_generator_incrementer():
    # ...
jenny = build_generator_incrementer()
class Incrementer:
    # ...
classy = Incrementer()

### Functional Closure
sys.getsizeof(build_function_incrementer) # The factory
# => 136
sys.getsizeof(funky) # The individual closure
# => 136

### Generator Function
sys.getsizeof(build_generator_incrementer) # The factory
# => 136
sys.getsizeof(jenny) # The individual generator
# => 88

### Class
sys.getsizeof(Incrementer) # The factory (class)
# => 1056
sys.getsizeof(classy) # The instance
# => 56

Удивительно, что вывод функции генератора на самом деле заканчивается самым маленьким. Но как функция генератора, и традиционное закрытие намного меньше, чем создание класса.

Они быстро

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

import timeit

### Functional Closure
timeit.timeit("""
def build_function_incrementer():
    # ...
funky = build_function_incrementer()
for _ in range(1000):
    funky()
""", number=1)
# => 0.0003780449624173343

### Generator Function
timeit.timeit("""
def build_generator_incrementer():
    # ...
jenny = build_generator_incrementer()
for _ in range(1000):
    next(jenny)
""", number=1)
# => 0.0004897500039078295

### Class
timeit.timeit("""
class Incrementer:
    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1
        return self.value

    def __next__(self):
        return self.increment()

classy = Incrementer()
for _ in range(1000):
    next(classy)
""", number=1)
# => 0.001482799998484552

Еще раз, метод класса наступает внизу, но на этот раз мы видим предельную скорость удара с функциональным закрытием. Однако имейте в виду, окончательный аргумент для закрытия является самым сильным.

Они доступны

Это тот, который взял меня, самый длинный, чтобы узнать. Не все языки так же счастливы, как Python. (Извините, пока я готовлю свой почтовый ящик для поток ненависти.) В Python нам посчастливилось иметь генераторы, а также ряд способов создавать их, такие как функции генератора. Честно говоря, если бы мне пришлось выбирать из вышеперечисленных методов, и я писал Python, я фактически рекомендовал метод функции генератора, поскольку оно проще прочитать и рассуждать.

Тем не менее, есть много языков, которые не так, как «батареи включены. ” Это на самом деле может быть полезным, если вы хотите небольшой размер приложения, или если вы ограничены как-то. В этих случаях, если ваш язык поддерживает создание функций, вы должны быть в состоянии получить все преимущества генераторов (ленивая оценка, мемузаризация, способность итерации через возможно бесконечную серию …) без каких-либо модных функций.

В JavaScript вы можете использовать Версия генераторов , но это функциональность ES6, которая не всегда была там. Насколько я могу сказать, это не встроенная функциональность, в которой можно либо (хотя некоторые исследования показывают, что это может быть более идиоматично для Использовать каналы вместо этого ). Я уверен, что есть много других языков нижнего уровня, где простое закрытие функций проще, чем пытаться написать свой собственный генератор.

Поделитесь своей мудростью!

Поскольку у меня нет большого количества опыта с низкоуровневым языками, плюсы и минусы закрытия являются новыми для меня. Если у вас есть лучшие объяснения или любые примеры, когда закрытие является идеальным инструментом для работы, пожалуйста Дайте мне знать об этом или комментировать ниже, и я сделаю все возможное, чтобы транслировать вашу мудрость.

Первоначально опубликовано Assert_not Magic?

Оригинал: “https://dev.to/rpalo/closure-i-hardly-know-her–1h40”