Функции и замыкание
Функции
Пространства имён: повторение
- Задание функции
Формальные и фактические параметры, return
- Duck typing: функция как формализация алгоритма
вызов функции как локальное пространство имён
Использование 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 оказалось бы локальным
Замыкание и позднее связывание
Вот этот код не работает так, как может показаться:
Выясняется, что все adder-ы работают одинаково — прибавляют 9! Как это работает:
При определении очередного adder()-а выясняется, что i — нелокальное имя, потому что среди локальных его нет, зато оно локально для create_adders()
Значит, надо сформировать специальное пространство имён, в котором это имя «залипнет» после выхода из create_adders()
В adder-ах будет сформировано замыкание, куда попадёт i из этого пространства имён
На момент выхода из create_adders() имя i указывает на 9
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
По сути, в Python любое превращение имени в объект — это позднее связывание. Какой именно объект именуется i или x, каждый из adder-ов решает только когда его вызовут
- Когда формируется замыкание, нелокальное имя может быть даже ещё не определено! Главное, чтобы оно появилось до выхода из контекста, которому принадлежит:
(Спасибо слушателю лекции 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: DivDigit 'Цифроделители'
Написать функцию divdigit(N), которой передаётся произвольное натуральное число N, а в ответ функция возвращает количество цифр этого числа, являющихся её делителями.
1 print(divdigit(312345))
4
EJudge: BinPow 'Бинарное возведение в степень'
Написать функцию BinPow(), которая принимает три параметра: python3-объект a, натуральное число 0<N<1000000, и некоторую ассоциативную бинарную функцию f(). Функция BinPow() реализует алгоритм бинарного возведения в степень (кроме нулевой степени). Результатом BinPow(a, n, f) будет применение f(x) к a n-1 раз.
8589934592 8589934592 SeSeSeSeSeSeSe
EJudge: FunVect 'Вектор функций'
Написать функцию superposition(funmod, funseq), которая принимает два параметра — функцию funmod() от одного переменного, и последовательность funseq[] функций от одного переменного. superposition() возвращает также список функций funres[], каждая из которых представляет собой суперпозицию вида funres[i](x) ≡ funmod(funseq[i](x))
0.8414709848078965 0.5403023058681398 0.9092974268256817 0.4161468365471424