Share This
Связаться со мной
Крути в низ
Categories
//Функции высшего порядка в Python: замыкания и декораторы

Функции высшего порядка в Python: замыкания и декораторы

Разберем важные концепции, связанные с функциями высшего порядка, напишем собственные версии map(), reduce() и filter(), потренируемся в создании декораторов и решим 10 практических заданий.

funkcii vysshego porjadka v python zamykanija i dekoratory 1f86719 - Функции высшего порядка в Python: замыкания и декораторы

← Часть 13 Рекурсивные функции

Функции высшего порядка

В программировании (и в математике) функциями высшего порядка называются функции, которые выполняют одно (или оба) из этих действий:

  1. Принимают одну (и более) функций в качестве аргументов.
  2. Возвращают функцию в качестве результата.

Все остальные функции считаются функциями первого порядка. Вот простейший пример обработки нескольких функций первого порядка multiply(), power(), add(), subtract() функцией высшего порядка higher_order():

         def higher_order(function):  # функция высшего порядка     return function(15)  def multiply(x): # функция первого порядка     return x * x  def power(x): # функция первого порядка     return x ** x  def add(x): # функция первого порядка     return x + x  def subtract(x): # функция первого порядка     return x - (x * x)  print(higher_order(multiply)) print(higher_order(power)) print(higher_order(add)) print(higher_order(subtract))      

Вывод:

         225 437893890380859375 30 -210      

Декораторы в Python

Синтаксис Python позволяет использовать декораторы для получения результата «прохождения» функции первого порядка через функцию высшего порядка. Декоратор – это функция высшего порядка, которая принимает функцию первого порядка и добавляет в результат что-нибудь от себя, не вмешиваясь в логику полученной функции:

         def print_result(f):     def result(x):         r = f(x)         print(f'Результат вычисления: {r}')         return r     return result  @print_result def triple(x):     return x * 3  @print_result def divide(x):     return x / 5  triple(5) divide(5)      

Вывод:

         Результат вычисления: 15 Результат вычисления: 1.0      

Более того, Python позволяет писать функции, которые создают декораторы:

         def print_with(message):     def result(f):         def add_message(x):             r = f(x)             print(f'{message} {r}')             return r         return add_message     return result  @print_with('Функция вернула результат:') def power(x):     return x ** x  power(int(input()))      

Вывод для n = 5:

         Функция вернула результат: 3125     

Поскольку функции в Python являются объектами (класса function), при желании их можно добавлять в словари или списки:

         def print_with(message):     def result(f):         def add_message(x):             r = f(x)             print(f'{message} {r}')             return r         return add_message     return result  functions = []  def function(f):     functions.append(f)  @function @print_with('Функция вернула результат:') def add(x):     return x + x  print(functions[0](6))      

Вывод:

         Функция вернула результат: 12 12      

Порядок перечисления декораторов имеет значение – в приведенном выше примере в стек попала функция add(), уже измененная функцией высшего порядка print_with(). При изменении порядка декораторов результат будет просто 12.

Декораторы очень часто используются при разработке приложений в Python фреймворках – они позволяют программисту использовать мощную функциональность фреймворка, не задумываясь о том, что именно происходит «под капотом». В приведенном ниже примере декоратор app.route обеспечивает маршрутизацию сайта на основе фреймворка Flask:

         from flask import Flask    app = Flask(__name__)   @app.route('/') def index():     return 'Главная страница сайта'  @app.route('/hello') def hello():     return 'Привет, добро пожаловать на сайт!'  if __name__ == '__main__':     app.run(host='127.0.0.1', port=8000, debug=True)     

При запуске этого примера по адресу http://localhost:8000/ будет выведена надпись «Главная страница сайта», а по адресу http://localhost:8000/hello – «Привет, добро пожаловать на сайт!»

А в этом примере из функции представления фреймворка Django декоратор @login_required проверяет, вошел ли посетитель на сайт, и в зависимости от результата проверки либо показывает ему страницу, заполненную информацией, созданной этим конкретным пользователем, либо перенаправляет на страницу входа:

         @login_required(login_url='/login') def home(request):     all_tasks = request.user.tasks.all()     return render(request, 'index.html', {'tasks': all_tasks })      

Как работают встроенные функции высшего порядка

В предыдущих главах мы уже неоднократно использовали три самые популярные встроенные функции высшего порядка – map(), reduce() и filter() для обработки наборов данных. В этом примере встроенная функция map() берет на себя ряд преобразований элементов строки:

         >>> lst = list(map(float, input().split(';'))) 1;2;3;4;5;6;7;8;9 >>> print(lst) [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]      

Если бы функции map() не было, пришлось бы заниматься этими преобразованиями самостоятельно – с помощью спискового включения:

         sp = input().split(';') result = [float(i) for i in sp] print(result)      

Или с помощью цикла:

         sp = input().split(';') result = [] for i in sp:     result.append(float(i)) print(result)      

Встроенная функция map(), как и любая другая функция высшего порядка, может принимать любую функцию первого порядка и последовательно применять ее ко всем элементам в полученном наборе данных. В приведенном ниже примере встроенная map() принимает пользовательскую функцию divide():

         sp = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] def divide(x):     return 1 / x ** 0.5      result = list(map(divide, sp)) print(result)      

Вывод:

         [1.0, 0.7071067811865475, 0.5773502691896258, 0.5, 0.4472135954999579, 0.4082482904638631, 0.3779644730092272, 0.35355339059327373, 0.3333333333333333, 0.31622776601683794]     

Встроенная функция map() отличается гибкостью – точно такой же результат можно получить, если передать в нее анонимную лямбда-функцию:

         sp = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] result = list(map(lambda x: 1 / x ** 0.5, sp)) print(result)      

Напишем собственную функцию my_map(), которая будет принимать любую другую функцию первого порядка, например, my_function(), которая повторяет полученное число столько раз, чему оно равно:

         def my_function(n):     lst = [str(n) for i in range(1, n + 1)]     return(''.join(lst))           def my_map(function, lst):     result = []     for i in lst:         processed_item = function(i)         result.append(processed_item)     return result print(my_map(my_function, [4, 5, 6, 7]))     

Вывод:

         ['4444', '55555', '666666', '7777777']     

Как и встроенная map(), пользовательская my_map() может принимать анонимные функции:

         def my_map(function, lst):     result = []     for i in lst:         processed_item = function(i)         result.append(processed_item)     return result  print(my_map(lambda x: ''.join([str(x) for i in range(1, x + 1)]), [1, 2, 5, 9]     

Вывод:

         ['1', '22', '55555', '999999999']     

Замыкания и вложенные функции

В программировании существует концепция, называемая замыканием (closure), когда вложенная функция имеет доступ к локальным переменным функции более высокого порядка, после того, как внешняя функция уже завершила свою работу:

         def print_greetings(text):     def say_hello():         print(text)     return say_hello  print_hi = print_greetings('Привет, как дела?!') print_hi()     

Сохранение доступа к переменным функции более высокого порядка возможно благодаря своеобразному виртуальному контейнеру – стеку, принцип работы которого мы рассматривали в предыдущей статье. В приведенном ниже примере при вызове outer_function() в стеке сохраняется фрейм, в котором находятся вложенная функция inner_function() (как константа) и строка text_1 (как локальная переменная). Поскольку функция inner_function() ссылается на переменную text_1, значение переменной остается доступным после того, как функция высшего порядка уже завершила свою работу.

         def outer_function(text_1):     def inner_function():         text_2 = 'Это функция первого порядка - внутренняя'         print(text_2)         print(f'{text_1}, ee значение было сохранено во фрейме')     return inner_function  my_function = outer_function('Это функция высшего порядка - внешняя') my_function()      

Вывод:

         Это функция первого порядка - внутренняя Это функция высшего порядка - внешняя, ee значение было сохранено во фрейме      

На практике замыкания используются для инкапсуляции кода и скрытия важных данных. С помощью замыканий также можно избежать использования глобальных переменных.

Важно заметить, что не всякая вложенная функция автоматически выступает в качестве замыкания, то есть сохраняет значения всех переменных из доступной области видимости: если во вложенной функции нет ссылок на какие-то переменные из функции высшего порядка, они не будут сохранены, и в этом случае вложенная функция считается просто вложенной функцией, а не замыканием. В приведенном ниже примере интерпретатор Python демонстрирует, что именно он посчитал замыканием, а что – нет:

         >>> def higher_order():     text1 = 'Привет'     text2 = 'Учишь Python?'     def nested():         return text1     return nested  >>> my_function = higher_order() >>> my_function <function higher_order.<locals>.nested at 0x025B4390> >>> my_function.__closure__ (<cell at 0x02578B30: str object at 0x025B17A0>,) >>> my_function.__closure__[0].cell_contents 'Привет' >>> higher_order.__closure__ is None True      

Значение переменной text2 не было сохранено в __closure__, в отличие от использованного во вложенной функции nested() значения text1 – если выполнить команду my_function.__closure__[1].cell_contents, получим ошибку, так как никаких других значений в кортеже нет:

         Traceback (most recent call last):   File "<pyshell>", line 1, in <module> IndexError: tuple index out of range      

В этом можно также убедиться, выполнив команду по выводу имен переменных, ставших замыканиями:

         >>> higher_order.__code__.co_cellvars ('text1',)      

Практика

Задание 1

Напишите функцию высшего порядка, которая получает в качестве аргумента две функции первого порядка:

  • Функцию для преобразования текста сообщения в верхний регистр.
  • Функцию для преобразования текста сообщения в нижний регистр.

Пример вывода:

         РЕГИСТР ПРЕОБРАЗОВАН ФУНКЦИЕЙ, ПОЛУЧЕННОЙ В КАЧЕСТВЕ АРГУМЕНТА регистр преобразован функцией, полученной в качестве аргумента      

Решение:

         def greetings(function):      text = function('Регистр преобразован функцией, полученной в качестве аргумента')      print(text)    def uppercase(text):      return text.upper()       def lowcase(text):      return text.lower()            greetings(uppercase)  greetings(lowcase)      

Задание 2

Напишите функцию высшего порядка, которая может принимать функции float(), hex(), bin(), str() и содержит вложенную функцию первого порядка, которая конвертирует полученное от пользователя целое число n в соответствии с полученными функциями.

Пример вывода для n = 25:

         Преобразуем полученное число 25 в типы: float => 25.0 bin => 0b11001 hex => 0x19 str => 25      

Решение:

         def number_to(function):      def convert(n):         return function(n)        return convert      to_float = number_to(float)  to_bin = number_to(bin) to_hex = number_to(hex) to_str = number_to(str)  n = int(input()) print(f'Преобразуем полученное число {n} в типы:'       f'nfloat => {to_float(n)}'       f'nbin => {to_bin(n)}'       f'nhex => {to_hex(n)}'       f'nstr => {to_str(n)}'       )      

Задание 3

Напишите функцию высшего порядка, которая:

  • Определяет, состоит ли полученный от пользователя список из четного или нечетного количества чисел.
  • Возвращает функцию умножения элементов списка (в которой не используется math.prod()), если количество чисел четное.
  • Возвращает функцию суммирования (в которой не используется встроенная функция sum()), если количество чисел нечетное.

Пример ввода 1:

         8 9 3 5 1 3 8 2 9       

Вывод 1:

         Количество чисел нечетное, результат: 48     

Пример ввода 2:

         7 3 2 8 9 1 2 3 4 6     

Вывод 2:

         Количество чисел четное, результат: 435456     

Решение:

         def production(lst):      prod = 1     for i in lst:         prod *= i     return f'Количество чисел четное, результат: {prod}'  def summa(lst):      res = 0     for i in lst:         res += i     return f'Количество чисел нечетное, результат: {res}'  def higher_order(lst):     if len(lst) % 2 == 0:         return production     else:         return summa  sp = list(map(int, input().split())) result = higher_order(sp)  print(result(sp))      

Задание 4

Напишите собственный аналог функции filter(). Для отбора данных my_filter() должна, как и встроенная filter(), использовать функцию-предикат. Функция-предикат возвращает True или False в зависимости от критерия – в нашем случае это факт совпадения первой и последней букв слова в строке, полученной от пользователя.

Пример ввода:

         крюк арбуз торт абрикос кулак барабан рупор господин томат мадам       

Вывод:

         крюк торт кулак рупор томат мадам     

Решение 1:

         def equal_letters(word):     return word[0] == word[-1]  def my_filter(function, line):     result = []     for word in line:         if function(word):                     result.append(word)                 return result stroka = input().split() print(*my_filter(equal_letters, stroka))     

Решение 2:

         def my_filter(function, line):     result = []     for word in line:         if function(word):                     result.append(word)     return result  stroka = input().split() print(*my_filter(lambda x: x[0] == x[-1], stroka))     

Задание 5

Напишите собственный вариант функции-агрегатора reduce(). Функция при вызове должна получать:

  1. Функцию первого порядка для проведения операции умножения или сложения.
  2. Список чисел от пользователя.
  3. Начальное значение – 0 для операции суммирования, 1 для операции умножения.

Пример ввода:

         5 7 8 3 2 5 8 12 3 5 4 8 9     

Примеры вызова:

         print(my_reduce(add, my_list, 0)) print(my_reduce(mult, my_list, 1))      

Вывод:

         79 3483648000      

Решение:

         def my_reduce(operation, lst, init):     result = init     for i in lst:         result = operation(result, i)     return result  def add(x, y):     return x + y   def mult(x, y):     return x * y  my_list = list(map(int, input().split()))  print(my_reduce(add, my_list, 0)) print(my_reduce(mult, my_list, 1))     

Задание 6

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

  1. Группировки параллельных элементов списков с помощью самописной my_zip().
  2. Конкатенации элементов, сгрупированных my_zip().
  3. Сложения элементов, сгруппированных my_zip().

Примечание: следует учесть, что получаемые от пользователя списки могут быть разной длины. Как и встроенная zip(), my_zip() должна ограничивать размер возвращаемого списка длиной более короткого набора данных.

Пример ввода:

         5 8 9 8 3 12 3 5 5 4 0 9 6 1 23 6 12 30 4 5 6 2 3 2 5 9 4 12 9 3      

Вывод:

         (5, 4) (8, 5) (9, 6) (8, 2) (3, 3) (12, 2) (3, 5) (5, 9) (5, 4) (4, 12) (0, 9) (9, 3) 54 85 96 82 33 122 35 59 54 412 09 93 9 13 15 10 6 14 8 14 9 16 9 12      

Решение:

         def my_zip(lst1, lst2):     result = []     for i in range(min(len(lst1), len(lst2))):         result.append((lst1[i], lst2[i]))     return result  def concatenation(lst1, lst2):     result = []     for i, j in my_zip(lst1, lst2):         result.append(str(i) + str(j))     return result  def add(lst1, lst2):     result = []     for i, j in my_zip(lst1, lst2):         result.append(i + j)     return result  sp1 = list(map(int, input().split())) sp2 = list(map(int, input().split()))  def higher_order(function):     return function(sp1, sp2)  print(*higher_order(my_zip)) print(*higher_order(concatenation)) print(*higher_order(add))      

Задание 7

Напишите программу, которая:

  • получает от пользователя список слов и букву на отдельных строках;
  • с помощью самописной функции my_filter() определяет, какие слова начинаются с полученной буквы;
  • выводит индексы и слова отфильтрованного списка с помощью самописной функции my_enumerate().

Пример ввода:

         абрикос бюро газета банк коробка стол бобр ноутбук блокнот баланс абажур б      

Вывод:

         1-e слово нового списка - бюро 2-e слово нового списка - банк 3-e слово нового списка - бобр 4-e слово нового списка - блокнот 5-e слово нового списка – баланс      

Решение:

         def my_enumerate(lst, start=0):     for i in lst:         yield (start, i)         start += 1          def my_filter(function, items):     result = []     for item in items:         if function(item):                     result.append(item)       return result  my_list = input().split() letter = input()  for i, word in my_enumerate(my_filter(lambda x: x[0] == letter, my_list)):     print(f'{i + 1}-e слово нового списка - {word}')      

Задание 8

Напишите функцию высшего порядка, которая принимает две одноаргументные функции первого порядка и возвращает новую функцию. Эта функция принимает аргумент x и применяет к нему полученные функции в следующем порядке:

         function1(function2(x))     

К примеру, если передать в функцию высшего порядка эти функции:

         def add(x):     return x + 10  def multiply(x):     return x * 5      

И вызвать функцию так:

         print(super_function(add, float)('16')) print(super_function(tuple, multiply)((3, 4, 5))) print(super_function(str, multiply)('55')) print(super_function(list, multiply)((1, 2, 3)))      

Результат будет выглядеть следующим образом:

         26.0 (3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5) 5555555555 [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]      

Решение 1:

         def super_function(function1, function2):     def new(x):         return function1(function2(x))     return new      

Решение 2:

         def super_function(function1, function2):     return lambda x: function1(function2(x))      

Задание 9

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

Пример функции и вызова 1:

         @decorator_func def names_and_age(age1, age2, age3, name1, name2, name3):     return f'У меня есть три сестры: {name1}, ей {age1} лет; {name2}, ей {age2} лет; {name3} - ей {age3} летn' print(names_and_age(12, 15, 13, name1='Света', name2='Маша', name3='Ира'))      

Вывод 1:

         Функция получила позиционных аргументов: 3, именованных аргументов: 3 У меня есть три сестры: Света, ей 12 лет; Маша, ей 15 лет; Ира - ей 13 лет      

Пример функции и вызова 2:

         @decorator_func def position_and_salary(sal1, sal2, sal3, sal4, pos1, pos2, pos3, pos4):     return f'{pos1} получает {sal1} тыс, {pos2} получает {sal2} тыс, {pos3} получает {sal3} тыс, {pos4} получает {sal4} тысn' print(position_and_salary(320, 150, 230, 170, pos1='разработчик', pos2='тестировщик', pos3='девопс', pos4='сисадмин'))      

Вывод 2:

         Функция получила позиционных аргументов: 4, именованных аргументов: 4 разработчик получает 320 тыс, тестировщик получает 150 тыс, девопс получает 230 тыс, сисадмин получает 170 тыс      

Решение:

         def decorator_func(decorated_func):     def wrapper_func(*args, **kwargs):         print(f'Функция получила позиционных аргументов: {len(args)}, именованных аргументов: {len(kwargs)}')         return decorated_func(*args, **kwargs)     return wrapper_func      

Задание 10

Напишите декоратор, который будет измерять производительность функций, создающих список с помощью этих методов:

  • range()
  • списковое включение
  • append()
  • конкатенация

Среди показателей должны быть:

  1. Время работы функции.
  2. Текущее потребление памяти.
  3. Пиковое потребление памяти.

Пример вызова:

         print(make_list_with_range()) print(make_list_comprehension()) print(make_list_with_append()) print(make_list_concatenation())      

Вывод:

         Название функции: make_list_with_range Использованный метод: range() Текущее потребление памяти: 0.290164 мб  Пик использования памяти: 2.289118 мб  Операция заняла: 0.112532 секунд Функция make_list_with_range завершила работу  ------------------------------------------------ Название функции: make_list_comprehension Использованный метод: list comprehension Текущее потребление памяти: 0.000930 мб  Пик использования памяти: 1.947573 мб  Операция заняла: 0.085460 секунд Функция make_list_comprehension завершила работу  ------------------------------------------------ Название функции: make_list_with_append Использованный метод: append() Текущее потребление памяти: 0.000582 мб  Пик использования памяти: 1.947229 мб  Операция заняла: 0.100597 секунд Функция make_list_with_append завершила работу  ------------------------------------------------      

Решение:

         import tracemalloc from time import perf_counter from functools import wraps import inspect  def time_memory_used(function):     @wraps(function)     def wrapper(*args, **kwargs):         tracemalloc.start()         start = perf_counter()         result = function(*args, **kwargs)         current, peak = tracemalloc.get_traced_memory()         stop = perf_counter()         print(f'Название функции: {function.__name__}')         print(f'Использованный метод: {function.__doc__}')         print(f'Текущее потребление памяти: {current / 10**6:.6f} мб n'               f'Пик использования памяти: {peak / 10**6:.6f} мб ')         print(f'Операция заняла: {stop - start:.6f} секунд')         tracemalloc.stop()         return result     return wrapper   @time_memory_used def make_list_with_range():     'range()'     my_list = list(range(100000))     return f'Функция {inspect.stack()[0][3]} завершила работу n{"-" * 48}'  @time_memory_used def make_list_comprehension():     'list comprehension'     my_list = [l for l in range(100000)]     return f'Функция {inspect.stack()[0][3]} завершила работу n{"-" * 48}'    @time_memory_used def make_list_with_append():     'append()'     my_list = []     for item in range(100000):         my_list.append(item)     return f'Функция {inspect.stack()[0][3]} завершила работу n{"-" * 48}'      @time_memory_used def make_list_concatenation():     'конкатенация'     my_list = []     for item in range(100000):         my_list = my_list + [item]     return f'Функция {inspect.stack()[0][3]} завершила работу n{"-" * 48}'     print(make_list_with_range()) print(make_list_comprehension()) print(make_list_with_append()) print(make_list_concatenation())      

Подведем итоги

Функции высшего порядка помогают производить сложные вычисления, и делают код максимально абстрактным и компактным. Кроме того, с помощью функций высшего порядка реализуют декораторы, которые широко используются при разработке, и позволяют не писать с нуля часть функциональности, необходимой для работы проекта.

В следующей статье будем работать с файлами и файловой системой.

***

Содержание самоучителя

  1. Особенности, сферы применения, установка, онлайн IDE
  2. Все, что нужно для изучения Python с нуля – книги, сайты, каналы и курсы
  3. Типы данных: преобразование и базовые операции
  4. Методы работы со строками
  5. Методы работы со списками и списковыми включениями
  6. Методы работы со словарями и генераторами словарей
  7. Методы работы с кортежами
  8. Методы работы со множествами
  9. Особенности цикла for
  10. Условный цикл while
  11. Функции с позиционными и именованными аргументами
  12. Анонимные функции
  13. Рекурсивные функции
  14. Функции высшего порядка, замыкания и декораторы

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста» Интересно, перейти к каналу

  • 0 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close