Функции и замыкание

Функции

Пространства имён: повторение

Про рекурсию

Замещение рекурсии стеком (не успеем)

TL;DR: рекурсия в Python — это и есть стек вызовов, см. пример

TODO вынести в отдельную статью:

Разбор задачи. Есть ли среди натуральных чисел Seq такие, что в сумме дают S?

Замыкание

Замыкание_(программирование)

  1. Функция — это объект
  2. Её можно изготовить внутри другой функции и вернуть
  3. …причём в зависимости от параметров этой другой функции!
  4. …в процессе чего некоторые объекты из ПИ создающей функции «залипают» в ПИ создаваемой
    • только они там навсегда должны залипнуть, а не только на время вызова
    • .__closure__

  5. Это и есть замыкание!

Пример:

   1 def f1(x):
   2     def f2():
   3         return x
   4     return f2

pythontutor this

и

   1 def f1(x):
   2     def f2():
   3         def f3():
   4             return x
   5         return f3
   6     return f2

pythontutor this

Also: nonlocal name — явное указание брать имя name из внешнего, но не глобального пространства имён

Примеры: 1 и 2

Модификатор nonlocal для доступа к пространству имён вызывающей функции:

   1 def f(x):
   2     def g(y):
   3         nonlocal x
   4         x = 2 * y + 1
   5         return x
   6     g(2)
   7     return 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-а:

   1 def create_adders():
   2     adders = []
   3     for i in range(10):
   4         def adder(x, j=i):
   5             return j + x
   6         adders.append(adder)
   7     return adders

При этом никакого замыкания не произойдёт, у каждого adder-а будет своё локальное j, инициализированное соответствующим значением i. (Если бы нам нужно было сильнее запутаться, мы могли бы написать i=i вместо j=i ☺ ).

   1 >>> c = create_adders()
   2 >>> c[1].__closure__
   3 >>> print(c[1].__closure__)
   4 None

Д/З

  1. Прочитать:
    • в Tutorial про функции

    • Про замыкания: Gabor Laszlo Hajba и Dmitry Soshnikov

    • Посмотреть, как оформлять задачи типа «написать функцию»

      • ВНИМАНИЕ! Три из четырёх домашних заданий к этой лекции именно такого типа!

  2. EJudge: ShefferStroke 'Штрих Шеффера'

    Написать функцию sheff(A, B), реализующую логическую операцию Штрих Шеффера A ↑ B по следующему принципу:

    • Если ровно один из операндов не пуст, возвращается этот операнд

    • Если оба операнда пусты, возвращается True

    • Если оба операнда непусты, возвращается False

    Input:

    print(sheff(1, 2))
    print(sheff([], 1.1))
    print(sheff((0, 0), ""))
    Output:

    False
    1.1
    (0, 0)
    • <!> Это очень простая функция, так что необязательное упражнение: минимизировать количество символов (пробелы и переводы строки не в счёт). В моём решении их 47, и я уверен, что это далеко не предел!

  3. EJudge: Det4x4 'Определитель матрицы 4×4'

    Матрица 4×4 задаётся кортежем из 4 кортежей по 4 целых числа в каждом. Написать функцию det4(r0, r1, r2, r3), вычисляющую точный определитель матрицы (r0, r1, r2, r3). Пользоваться itertools нельзя. Числа содержат не более 66666 десятичных знаков.

    Input:

    print(det4((5, -4, 4, -7), (1, -2, 6, 0), (3, -8, -6, -4), (-1, 2, -9, 3)))
    Output:

    702
  4. EJudge: FunCompose 'Суперпозиция функций'

    Написать функцию (точнее, функционал) compose(f, g), которому на вход подаётся два объекта-функции: f(x, y) от двух параметров, и g(x₁, …, xₙ) от произвольного количества параметров. compose(f, g) должна возвращать функцию h() от n параметров, являющуюся результатом применения f() к g(x₁, …, xₙ) (в прямом порядке) и g(xₙ, …, x₁) (в обратном порядке)

    Input:

    from math import *
    print(compose(max, pow)(5, 6))
    Output:

    15625.0
  5. ( (!) задача типа «написать программу»)

    EJudge: AllProducts 'Разложения на сомножители'

    Ввести произвольное натуральное число, не превосходящее 1000000000, и вывести (через «*») все его разложения на натуральные сомножители, превосходящие 1, без учёта перестановок. Сомножители в каждом разложении и сами разложения (как последовательности) при выводе должны быть упорядочены по возрастанию. Само число также считается разложением. Можно использовать рекурсию.

    Input:

    24
    Output:

    2*2*2*3
    2*2*6
    2*3*4
    2*12
    3*8
    4*6
    24

LecturesCMC/PythonIntro2023/04_FunctionsClosure (последним исправлял пользователь FrBrGeorge 2023-10-04 16:43:49)