Функции и замыкание
Функции
Пространства имён: повторение
- Задание функции
Формальные и фактические параметры, return
- Duck typing: функция как формализация алгоритма
вызов функции как локальное пространство имён
globals() и locals()
сложный случай: определение локальности по связыванию, global
Использование Python Tutor
- функция как объект: именование, передача в качестве параметра
работа sort()/sorted() (а заодно и max()/min())
- Лямбда-функции (функции-выражения)
Распаковка (def fun(*args)) и запаковка (a = fun(*seq)) параметров — пока только позиционных
- функция с произвольным числом параметров
- Передача параметров с распаковкой последовательности
Лайф-хак вида print(*последовательность)
- Параметры функции по умолчанию и именованные параметры
1 >>> def fun(a, b=1, c=2, d=3): 2 ... print(a, c, b, d) 3 >>> fun(9, 8, 7, 6) 4 9 7 8 6 5 >>> fun(9, 8) 6 9 2 8 3 7 >>> fun(9) 8 9 2 1 3 9 >>> fun() 10 Traceback (most recent call last): 11 File "<stdin>", line 1, in <module> 12 TypeError: fun() missing 1 required positional argument: 'a' 13 >>> fun(100, d=123, b=0) 14 100 2 0 123 15
(to be continued… они теперь не равны, вообще см. понятие «параметр» и полный вид описания функции )
1 >>> def fun(a, b=1, /, c=2, *, d=3): 2 ... print(a, c, b, d) 3 >>> fun(1) 4 1 2 1 3 5 >>> fun(1, b=3) 6 Traceback (most recent call last): 7 File "<stdin>", line 1, in <module> 8 TypeError: fun() got some positional-only arguments passed as keyword arguments: 'b' 9 10 >>> fun(1, 3, 5) 11 1 5 3 3 12 >>> fun(1, 3, 5, 8) 13 Traceback (most recent call last): 14 File "<stdin>", line 1, in <module> 15 TypeError: fun() takes from 1 to 3 positional arguments but 4 were given
- Самодокументирование
1 >>> def fun(a, b): 2 ... '''Calculate a formula on a and b 3 ... 4 ... :param a: First parameter 5 ... :param b: Second parameter 6 ... :return: Resulting formula''' 7 ... return a * 2 + b 8 >>> fun(12, 56) 9 80 10 >>> print(fun.__doc__) 11 Calculate a formula on a and b 12 13 :param a: First parameter 14 :param b: Second parameter 15 :return: Resulting formula 16 17 >>> help(fun) 18 Help on function fun in module __main__: 19 20 fun(a, b) 21 Calculate a formula on a and b 22 23 :param a: First parameter 24 :param b: Second parameter 25 :return: Resulting formula 26 (END) 27
Про рекурсию
Рекурсия и цикл. Теория vs. практика. Гвидо, Python и хвостовой вызов
- ⇒ максимальная глубина рекурсии по умолчанию всего 1000
- ⇒ логарифмический критерий уместности рекурсии
Замещение рекурсии стеком (не успеем)
TL;DR: рекурсия в Python — это и есть стек вызовов, см. пример
TODO вынести в отдельную статью:
- пример примитивной рекурсии
- стек контекстов, хранящих связанные переменные (на pythontutor)
- разбор примера ниже
Разбор задачи. Есть ли среди натуральных чисел Seq такие, что в сумме дают S?
- Рекурсивный вариант.
- S — неизменяемая часть, Seq, Res — изменяемая, значит, их надо сохранять в стек.
- Рекурсия — это цикл, которые продолжается до тех пор, пока рекурсивные вызовы не кончились.
- здесь не тот порядок добавления, для полного соответствия надо в обратном
- вместо списковой сборки надо использовать генератор, но у нас их ещё не было ☺
Замыкание
- Функция — это объект
- Её можно изготовить внутри другой функции и вернуть
- …причём в зависимости от параметров этой другой функции!
- …в процессе чего некоторые объекты из ПИ создающей функции «залипают» в ПИ создаваемой
- только они там навсегда должны залипнуть, а не только на время вызова
⇒ .__closure__
- Это и есть замыкание!
Пример:
и
Also: nonlocal name — явное указание брать имя name из внешнего, но не глобального пространства имён
Модификатор nonlocal для доступа к пространству имён вызывающей функции:
Без nonlocal имя x оказалось бы локальным
Замыкание и позднее связывание
Вот этот код не работает так, как может показаться:
def create_adders(): adders = [] for i in range(10): def adder(x): return i + x adders.append(adder) return adders for adder in create_adders(): print(adder(1))
Обратите внимание на то, что все adder-ы работают одинаково!. Поскольку i для сгенерированных функций нелокальное, оно попадает в замыкания, и это один и тот же объект во всех adder-ах:
1 >>> c = create_adders()
2 >>> c[1]
3 <function create_adders.<locals>.adder at 0x7f272d2f93b0>
4 >>> c[1].__closure__
5 (<cell at 0x7f272d1c1510: int object at 0x7f272db36660>,)
6 >>> c[2].__closure__
7 (<cell at 0x7f272d1c1510: int object at 0x7f272db36660>,)
8 >>> c[2].__closure__[0].cell_contents
9 9
10 >>> c[1].__closure__[0].cell_contents
11 9
12
(Спасибо слушателю лекции 2023 года) Если имя i в функции create_adders() удалить непосредственно перед return adders, сформируется неработоспособное замыкание (проверьте!)
Связывание действительно позднее: по всей видимости, оно происходит при выходе из контекста внешней функции
Если мы хотели не этого, надо сделать так, чтобы при создании очередного adder-а его i именовало новый объект. Например, связывать это i отдельной переменной во время создания очередного adder-а:
При этом никакого замыкания не произойдёт, у каждого adder-а будет своё локальное j, инициализированное соответствующим значением i. (Если бы нам нужно было сильнее запутаться, мы могли бы написать i=i вместо j=i ☺ ).
Д/З
- Прочитать:
в Tutorial про функции
Про замыкания: Gabor Laszlo Hajba и Dmitry Soshnikov
Посмотреть, как оформлять задачи типа «написать функцию»
ВНИМАНИЕ! Три из четырёх домашних заданий к этой лекции именно такого типа!
EJudge: ShefferStroke 'Штрих Шеффера'
Написать функцию sheff(A, B), реализующую логическую операцию Штрих Шеффера A ↑ B по следующему принципу:
Если ровно один из операндов не пуст, возвращается этот операнд
Если оба операнда пусты, возвращается True
Если оба операнда непусты, возвращается False
print(sheff(1, 2)) print(sheff([], 1.1)) print(sheff((0, 0), ""))
False 1.1 (0, 0)
Это очень простая функция, так что необязательное упражнение: минимизировать количество символов (пробелы и переводы строки не в счёт). В моём решении их 47, и я уверен, что это далеко не предел!
EJudge: Det4x4 'Определитель матрицы 4×4'
Матрица 4×4 задаётся кортежем из 4 кортежей по 4 целых числа в каждом. Написать функцию det4(r0, r1, r2, r3), вычисляющую точный определитель матрицы (r0, r1, r2, r3). Пользоваться itertools нельзя. Числа содержат не более 66666 десятичных знаков.
print(det4((5, -4, 4, -7), (1, -2, 6, 0), (3, -8, -6, -4), (-1, 2, -9, 3)))
702
EJudge: FunCompose 'Суперпозиция функций'
Написать функцию (точнее, функционал) compose(f, g), которому на вход подаётся два объекта-функции: f(x, y) от двух параметров, и g(x₁, …, xₙ) от произвольного количества параметров. compose(f, g) должна возвращать функцию h() от n параметров, являющуюся результатом применения f() к g(x₁, …, xₙ) (в прямом порядке) и g(xₙ, …, x₁) (в обратном порядке)
from math import * print(compose(max, pow)(5, 6))
15625.0
( задача типа «написать программу»)
EJudge: AllProducts 'Разложения на сомножители'
Ввести произвольное натуральное число, не превосходящее 1000000000, и вывести (через «*») все его разложения на натуральные сомножители, превосходящие 1, без учёта перестановок. Сомножители в каждом разложении и сами разложения (как последовательности) при выводе должны быть упорядочены по возрастанию. Само число также считается разложением. Можно использовать рекурсию.
24
2*2*2*3 2*2*6 2*3*4 2*12 3*8 4*6 24