Olezhka
А как завершить функцию? По инструкции “return”? Или ввести симафор
Если функция рекурсивная, то её завершение делается через остановку продолжения вызовов. Для этого обычно используется условный оператор. Пока соблюдается условие, функция вызывается снова, а когда условие не соблюдается, то функция просто не вызывается.
Пример
>>> def f(n):
... if n < 3:
... print(n)
... f(n + 1)
...
>>> f(0)
0
1
2
>>>
>>> f(2)
2
>>>
>>> f(1)
1
2
>>> f(-10)
-10
-9
-8
-7
-6
-5
-4
-3
-2
-1
0
1
2
>>>
Olezhka
То есть я могу не использовать глобальные переменные? Если в теле функции объявляется внутренняя переменная, а в процессе выполнения функции значение этой переменной как-то меняется, то ее новое значение будет сохраняться где-то в памяти? А при каждом новом обращении к функции будет подставляться это сохраненное значение? Но КАК? Как, если при каждом вызове функции она будет считывать объявление этой переменной, при котором будет происходить ее инициализация с исходным значением (х = 0)?
Только аргументы функции можешь использовать для передачи информации. Соответственно, в языке Erlang, например, в котором нет циклов вообще, для зацикливания используется рекурсия. Вся передача информации идёт через аргументы.
В примере выше показано, как аргумент n используется для передачи информации. На каждом вызове мы его просто меняем как-то и из-за этого поведение функции при каждом вызове может тоже оставаться прежним или меняться.
Olezhka
Я знаю. По дефолту он равен 1000. Но в каком-то модуле, я знаю, есть инструкция, позволяющая задать на этот ограничитель любое другое число.
>>> import sys
>>>
>>> sys.getrecursionlimit()
1000
>>> sys.setrecursionlimit(10)
>>> sys.getrecursionlimit()
10
>>> sys.setrecursionlimit(10000)
>>> sys.getrecursionlimit()
10000
>>> sys.setrecursionlimit(1000)
>>> sys.getrecursionlimit()
1000
>>>
Но для того он и задан, чтобы его не трогали вообще никогда. Если тебе нужно трогать лимит рекурсии, значит что-то не то происходит и ты просто неправильно программу написал. В обычных условиях тебе никогда не понадобится это делать.
Olezhka
А почему в “Пайтоне” не сделали так, чтобы перезапускалась одна функция, а не запускались ее новые экземпляры?
, то в итоге у меня переменная “х” будет равна 2. Ведь не создастся же 3 экземпляра переменной “х” с разными значениями.
В питоне нет понятия переменная. Переменная - это понятие из программирования. В питоне переменные реализованы в виде объектов и имён для объектов. Поэтому в питоне можно привязать имя то к одному объекту, то к другому объекту. В других языках, в которых переменные реализованы как переменные, ты не сможешь так сделать.
>>> int('123')
123
>>> int = 4
>>> int
4
>>> int('123')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>>
Во всех языках функция вызывается всегда заново и в питоне тоже так же происходит. Функция вызывается, создаются её локальные переменные, у них есть значения, всё это сохраняется в памяти. Пока вызов функции не завершён, эти переменные этой функции хранятся в памяти. Когда рекурсивно запускается эта же функция, создаётся новая копия этой функции, которая так же кладёт все свои локальные переменные в память во второе место, а потом снова вызывает эту же функцию, из-за чего снова запускается эта функция, которая создаёт третью копию этой функции и кладёт переменный этой третьей функции в третью память. Так память занимается. Это во всех языках так.
Тебе надо смотреть на рекурсию, как на вызов абсолютно разных функций, которые похожи друг на друга. Но эта похожесть настолько сильная, что эти абсолютно разные функции выглядят абсолютно одинаково. Тогда ты поймёшь, что у всех этих разных функций абсолютно разные переменные внутри. У каждой функции свои переменные. И тогда ты не будешь думать, что переменная x из одной функции каким-то боком имеет отношение к переменной x из другой функции. Да, функции выглядят похоже; да, они выглядят очень похоже; да, они вообще идентичны по своей похожести; но это разные функции. Разные функции разное пространство в памяти занимают и у разных функций разное время жизни или время выполнения. Одна функция может стоять на первой своей строке, а другая функция в это время может стоять на десятой своей строке. Поэтому их похожесть, одинаковость внешняя никакой роли не играет. Они работают вообще по-разному.
Olezhka
То есть инструкция “root.after” не перезапускает функцию, а запускает новый ее экземпляр? А старый так и остается висеть зацикленным в памяти?
Функция висит в памяти, пока не завершится. А завершается она в примере выше - по условию. Завершается она тогда, когда перестаёт себя вызывать. Тогда она доходит до своего конца, до последней строки. А когда в последней строке ничего нет, там как бы есть return None невидимый. Поэтому функции без return на самом деле возвращают None всегда.
Olezhka
А приведи, пожалуйста, простенький пример рекурсии чего-то алгоритмического!
Вывод ёлки с подставкой на экран
>>> def f(n):
... if n < 10:
... print(n * '*')
... f(n + 1)
... if n == 3:
... print(n * '=')
...
>>> f(1)
*
**
***
****
*****
******
*******
********
*********
===
>>>
Вот во многих играх растения рисуются с помощью рекурсии. Ты идёшь по джунглям, а там растения всякие. Ты думаешь, что они все рисуются, как картинки, а на самом деле там вот такая рекурсия ходит по этим растениям и обрисовывает их каким-то алгоритмом. В итоге получаются узоры какие-то. Это очень частая фигня. Никто не будет столько картинок закладывать. Достаточно написать одну рекурсивную функцию, которая дёргает другую рекурсивную функцию. Одна ходит по растению по всем его веткам, другая ходит по его листьям на ветках и раскрашивает кажую складку на листе.
Как видишь, для этих вещей не нужно тысячи вызовов. Достаточно двадцати, тридцати, ста.
Olezhka
Это цикл “while” чтоль?
Там и циклы в них бывают, и всё что угодно.
Пример другой подставки для ёлки
>>> def f(n):
... if n < 10:
... print(n * '*')
... f(n + 1)
... if n == 3:
... x = 2
... while x > 0:
... print(x * '=')
... x -= 1
...
>>> f(1)
*
**
***
****
*****
******
*******
********
*********
==
=
>>>
Olezhka
А как заставить этот цикл повторяться с определенным интервалом времени?
Какие еще есть варианты? “timer” из “threads”?
Надо программу сделать многопоточной. А уже во втором потоке ты можешь делать задержки временные каким угодно образом. Но обычно библиотеки эти включают в себя таймеры, уже сделанные. Но можно и свой таймер делать.
Olezhka
Почему при “command = func()” функция именно самозапускается, а при “command = func” кнопке просто дается ссылка на эту функцию, как и положено? В чем магия пустых скобок“()”?
Круглые собки после имени говорят интерпретатору “возьми объект по этому имени и вызови этот объект как функцию”. Интерпретатор берёт имя, узнаёт адрес объекта в памяти, потом у объекта с этим адресом запускает метод __call__(). Если ты имя функции передаёшь куда-то без круглых скобок, то практически во всех сиподобных языках это означает, что ты хочешь взять адрес функции и куда-то передать этот адрес. Адрес функции можно сохранить куда-то. В языке C так можно массив функций держать и вызывать функции, перебирая этот массив просто.
Пример в питоне
>>> def f1(x):
... return x * 2
...
>>> def f2(x):
... return x * 4
...
>>> def f3(x):
... return x + 10
...
>>> t = (f1, f2, f3, f3, f2, f1, f1)
>>>
>>> for i in t:
... print(i(7))
...
14
28
17
17
28
14
14
>>>
Olezhka
Но тогда меня спасла “лямбда” - “command = lambda: func(arg)” Вот с “лямбдой” самозапуска функции уже не происходило. Интересно, почему?
Ключевое слово lambda в питоне просто определяет функцию.
Пример с определениями функций
>>> def f1(x):
... return x * 2
...
>>> f2 = lambda x: x * 2
>>>
>>> f1(4)
8
>>> f2(4)
8
>>>
Это как бы одно и то же.
Поэтому ты можешь даже вот так сделать
>>> (lambda x: x * 2)(4)
8
>>> (lambda x: x * 3)(4)
12
>>>
Просто эти лямбды нужны, чтобы функции на лету определять и вообще даже имён им не давать. Определил, использовал и выкинул сразу. Вот в чём их смысл.
Olezhka
Получается, что когда мой таймер дотикает до 1000, будет выдано исключение “Max recursion depth”?
Он ещё раньше выдаст, потому что они все там всё время вызывают новые экземпляры. И в каждом из этих экземпляров ещё и таймер вызывается и все эти экземпляры ждут истечения своих таймеров.