Замикання
Час життя об'єктів
Для знищення об'єктів Python використовує алгоритм підрахунку посилань. Об'єкти видаляються як тільки на них більше немає посилань.
В Python, змінні не зберігають значення, а виступають в ролі посилань на об'єкти. Тобто коли ви присвоюєте значення новій змінній, то спочатку створюється об'єкт з цим значенням, а вже потім змінна починає посилатись на нього. На один об'єкт може посилатись багато змінних.
Кожен об'єкт в Python містить додаткове поле (лічильник посилань), в якому зберігається кількість посилань на нього. Як тільки хтось посилається на об'єкт, це поле збільшується на 1. Якщо з будь-якої причини посилання пропадає, то це поле зменшується на 1.
Приклади, коли кількість посилань збільшується:
- оператор присвоєння
- передача аргументів
- вставка нового об'єкта в
list
(збільшується кількість посиланль для об'єкта) - присвоєння вида
foo = bar
(foo
починає посилатись на той самий обєкт, що іbar
)
Як тільки лічильник посилань для певного об'єкта досягає нуля інтерпретатор запускає процес знищення об'єкта. Якщо видалений об'єкт містив посилання на інші об'єкти, то ці посилання також видаляються. Таким чином, видалення одного об'єкта може спричинити видалення інших.
Наприклад, якщо видаляється список, то лічильник посилань у всіх його элементах зміншується на 1. Якщо усі об'єкти всередині списка більше ніде не використовуються, то їх також будеь видалено.
Подовжуємо життя локальним змінним
Кожного разу, коли ми викликаємо функцію, у неї створюються локальні змінні (якщо, звичайно, вони у неї є), а після завершення — знищуються, при черговому виклику ця процедура повторюється. А чи можна зробити так, щоб після завершення роботи функції, частина локальних змінних не знищувалась, а зберігала б свої значення до наступного запуска? Відповідь — так, можна.
Локальна змінна не буде знищена, якщо на неї десь залишиться “живе” посилання після завершення роботи функції. Це посилання може зберігати вкладена функція. Функції побудовані по такому принципу можуть використовуватись для побудови спеціалізованих функцій, тобто є як би фабриками функцій. Далі розглянемо створення і використання так званих "замикань", які якраз і використовують цю ідею.
Замикання (closure) — функція, в тілі якої є посилання на змінні, які було оголошено поза тіла цієї функції в оточуючому коді і які не є її параметрами.
Розглянемо приклад:
>>> def outer():
... message = 'Hi there!'
... def inner():
... print(message)
... return inner
...
>>> f = outer()
>>> f()
Hi there!
>>>
Що тут відбувається?
- Функція
inner
"бачить" зміннуmessage
функціїouter
, вона знайшла її в області видимостіenclosing
. - Функція
outer
повертає об'єкт функціїinner
, результат ми присвоїли зміннійf
. - Змінна
f
вказує на об'єкт функціїinner
, яка, у свою чергу, "пам'ятає" зміннуmessage
. Об'єкт функціїinner
не знищено, отже і не знищено усі його об'єкти, зокрема зміннуmessage
.
Фабрика функцій
Зробимо трохи цікавіше — зовнішній функції будемо передавати параметри:
>>> def outer(message):
... def inner():
... print(message)
... return inner
...
>>> hello_func = outer('Hello')
>>> bye_func = outer('Bye')
>>>
>>> hello_func()
Hello
>>> bye_func()
Bye
>>>
У цьому прикладі функція inner
"побачила" змінну message
в облаісті видимості enclosing
функції outer
яка, у свою чергу, є параметром останньої.
Функція outer
є як би "фабрикою" інших функцій, при цьому функції вона може "виробляти" з різними властивостями.
Функції "на замовлення"
Внутрішня функція також може приймати якісь значення. Розглянемо наступну задачу.
Є квадратична функція:
f(x)=ax2+bx+c
Тут a
, b
та c
— це якісь константи, а x
є параметром функції.
Давайте напишемо "фабрику" квадратичних функцій. "Фабриці" ми будемо передавати константи a
, b
, 'c', а "вироблятиме" вона квадратичні функції з цими заданими параметрами:
>>> def make_quadratic(a, b, c):
... def quadratic(x):
... return a*x*x + b*x + c
... return quadratic
...
>>> f1 = make_quadratic(1, 0, 0) # f(x) = x*x
>>> f1(5)
25
>>> f1(7)
49
>>>
>>> f2 = make_quadratic(2, 2, 2) # f(x) = 2*x*x + 2*x + 2
>>> f2(1)
6
>>> f2(2)
14
>>>
Зауважимо, що в Python є інші інструменти щоб реалізувати описані вище речі. Їх використання є трохи зручнішим і зрозумілішим ніж замикання. Ми їх розглянемо пізніше, але про замикання варто знати і пам'ятати.
Резюме
- Одним з механізмів визначення часу життя об'єктів є підрахунок посилань на них.
- Доки об'єкт не знищено, не знищується й усі об'єкти пов'язані з ним тим чи іншим способом.
- За допомогою замикань можна робити своєрідні "фабрики" функцій.