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

Функции

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

Про рекурсию

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

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

Замыкание и позднее связывание

Вот этот код не работает так, как может показаться:

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

Выясняется, что все adder-ы работают одинаково — прибавляют 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-ов решает только когда его вызовут

Если мы хотели не этого, надо сделать так, чтобы при создании очередного 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: DivDigit 'Цифроделители'

    Написать функцию divdigit(N), которой передаётся произвольное натуральное число N, а в ответ функция возвращает количество цифр этого числа, являющихся её делителями.

    Input:

       1 print(divdigit(312345))
    
    Output:

    4
  4. EJudge: BinPow 'Бинарное возведение в степень'

    Написать функцию BinPow(), которая принимает три параметра: python3-объект a, натуральное число 0<N<1000000, и некоторую ассоциативную бинарную функцию f(). Функция BinPow() реализует алгоритм бинарного возведения в степень (кроме нулевой степени). Результатом BinPow(a, n, f) будет применение f(x) к a n-1 раз.

    Input:

       1 print(BinPow(2, 33, lambda a, b: a * b), 2**33)
       2 print(BinPow("Se", 7, str.__add__))
    
    Output:

    8589934592 8589934592
    SeSeSeSeSeSeSe
  5. EJudge: FunVect 'Вектор функций'

    Написать функцию superposition(funmod, funseq), которая принимает два параметра — функцию funmod() от одного переменного, и последовательность funseq[] функций от одного переменного. superposition() возвращает также список функций funres[], каждая из которых представляет собой суперпозицию вида funres[i](x) ≡ funmod(funseq[i](x))

    Input:

       1 from math import *
       2 F, G = superposition(abs, (sin, cos))
       3 print(F(-1), G(-1), F(2), G(2))
    
    Output:

    0.8414709848078965 0.5403023058681398 0.9092974268256817 0.4161468365471424

LecturesCMC/PythonIntro2024/04_FunctionsClosure (последним исправлял пользователь FrBrGeorge 2024-09-29 20:44:06)