Введение
В этом разделе представлена идея использования функций для создания других функций.
Введение
Рассмотрим следующую функцию.
def add(x, y):
def do_add():
print('Adding', x, y)
return x + y
return do_add
Это функция, которая возвращает другую функцию.
>>> a = add(3,4)
>>> a
<function add.<locals>.do_add at 0x7f27d8a38790>
>>> a()
Adding 3 4
7
Локальные переменные
Обратите внимание, как внутреняя функция ссылается на переменные, определенные внешней функцией.
def add(x, y):
def do_add():
## `x` и `y` определены выше `add(x, y)`
print('Adding', x, y)
return x + y
return do_add
Далее обратите внимание, что эти переменные каким-то образом остаются живыми после завершения add().
>>> a = add(3,4)
>>> a
<function do_add at 0x6a670>
>>> a()
Adding 3 4 ## Откуда берутся эти значения?
7
Заключения (Closures)
Когда внутреняя функция возвращается в качестве результата, эта внутреняя функция называется заключением (closure).
def add(x, y):
## `do_add` - это замыкание
def do_add():
print('Adding', x, y)
return x + y
return do_add
Основная особенность: Заключение сохраняет значения всех переменных, необходимых для правильного выполнения функции позднее. Представьте замыкание как функцию плюс дополнительную среду, которая хранит значения переменных, на которые оно зависит.
Использование замыканий (Closures)
Замыкания являются важной особенностью Python. Однако их использование часто остается незаметным. Общие приложения:
- Использование в обратных вызовах (callback функциях).
- Отложенная оценка (delayed evaluation).
- Функции-декораторы (далее).
Отложенная оценка (Delayed Evaluation)
Рассмотрим функцию такого вида:
def after(seconds, func):
import time
time.sleep(seconds)
func()
Пример использования:
def greeting():
print('Hello Guido')
after(30, greeting)
after выполняет переданную функцию... позже.
Замыкания несут дополнительную информацию.
def add(x, y):
def do_add():
print(f'Adding {x} + {y} -> {x+y}')
return do_add
def after(seconds, func):
import time
time.sleep(seconds)
func()
after(30, add(2, 3))
## `do_add` имеет ссылки x -> 2 и y -> 3
Повторение кода
Замыкания также могут быть использованы в качестве техники для избежания избыточного повторения кода. Вы можете писать функции, которые генерируют код.
Упражнение 7.7: Использование замыканий для избежания повторений
Одной из более мощных функций замыканий является их использование для генерации повторяющегося кода. Если вы вспомните упражнение 5.7, вспомните код для определения свойства с проверкой типа.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
...
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value
...
Вместо того, чтобы многократно повторять этот код, вы можете автоматически создавать его с использованием замыкания.
Создайте файл typedproperty.py и поместите в него следующий код:
## typedproperty.py
def typedproperty(name, expected_type):
private_name = '_' + name
@property
def prop(self):
return getattr(self, private_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError(f'Expected {expected_type}')
setattr(self, private_name, value)
return prop
Теперь, попробуйте его, определив класс следующим образом:
from typedproperty import typedproperty
class Stock:
name = typedproperty('name', str)
shares = typedproperty('shares', int)
price = typedproperty('price', float)
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Попробуйте создать экземпляр и проверить, работает ли проверка типа.
>>> s = Stock('IBM', 50, 91.1)
>>> s.name
'IBM'
>>> s.shares = '100'
... должен получиться TypeError...
>>>
Упражнение 7.8: упрощение вызовов функций
В вышеприведенном примере пользователи могут найти вызовы, такие как typedproperty('shares', int), несколько громоздкими для ввода - особенно если они повторяются часто. Добавьте следующие определения в файл typedproperty.py:
String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)
Теперь перепишите класс Stock для использования этих функций вместо этого:
class Stock:
name = String('name')
shares = Integer('shares')
price = Float('price')
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Ах, это немного лучше. Главное, что можно извлечь здесь, - это то, что замыкания и lambda часто могут быть использованы для упрощения кода и устранения раздражающего повторения. Это часто хорошо.
Резюме
Поздравляем! Вы завершили лабораторную работу по возврату функций. Вы можете практиковаться в более лабораторных работах в LabEx, чтобы улучшить свои навыки.