Форум сайта python.su
Я уже начал изучать модуль “requests”, но понял, что я и “tkinter” пока не до конца знаю!
Сегодня я был трижды шокирован!
Вот окно с кнопкой и текстовой меткой. При нажатии на кнопку должна запускаться функция “func”, которая зациклена сама на себя с интервалом 1 сек. Функция делает инкремент переменной “х” и подставляет ее в текстовую метку. Вот код:
from tkinter import * root = Tk() root.title('main') root.geometry("200x100-600-300") root.configure(bg='#CECECE') root.configure(bd = 5) x = 0 def func(): global x x += 1 lbl_1['text'] = x root.after(1000, func) # root.after(1000, func()) # RecursionError: maximum recursion depth exceeded lbl_1 = Label(root, text = '0', font = ('Arial Bold', 18), fg = 'black', bg = '#CECECE', padx = 20, pady = 5, wraplength = 300) lbl_1.grid(row = 0, column = 1, rowspan = 1, columnspan = 2) btn_1 = Button(root, text = 'text', bg = 'white', fg = 'black', padx = 5, pady = 5, font = ('Arial Bold', 14, 'bold'), relief = 'raised', command = func, state = 'normal') btn_1.grid(row = 0, column = 0, rowspan = 1, columnspan = 1) root.mainloop()
Отредактировано Olezhka (Янв. 5, 2023 01:17:38)
Офлайн
OlezhkaУ тебя получилась косвенная рекурсия. Это когда функция вызывает саму себя не напрямую, а косвенно, через какие-то другие функции. Например, функция f вызывает функцию g, функция g вызывает функцию m, а функция m вызывает функцию f. А функция f опять всё по цепочке вызывает, что опять приводит к её вызову. Таким образом эти функции бесконечно вызываются и остаются незавершёнными. При этом каждая такая функция сохраняет в памяти своё локальное состояние в своих локальных переменных. Память занимается все больше и больше и программа в итоге выпадает, так как операционная система не может выделять больше память этой программе. Всё остальное в операционной системе обычно тоже виснет из-за этого. В питоне есть ограничитель на случай возникновения бесконечной рекурсии в тысячу рекурсивных вызовов, чтобы операционная система не тормозила.
Потом я удалил скобки и понял, что причина самозапуска функции была в них. Но я так и не понял, ПОЧЕМУ?
OlezhkaТут получилась прямая рекурсия. Это когда функция вызывает саму себя напрямую. Если ты в функции f пишешь вызов функции f, то эта вызванная функция f снова вызовет функцию f, а та функция f снова вызовет функцию f. Когда оно доходит до тысячи таких вызовов, питон её вырубает и пишет это исключение.
счетчик за долю секунды досчитал до 1017 и программа выдала мне “RecursionError: maximum recursion depth exceeded”. Тоже не понимаю пока, что это такое и почему оно так получилось.
OlezhkaЗапускается всё больше и больше экземпляров функции и каждый из этих экземпляров действует на переменную x. Эти экземпляры также запускают ещё экземпляры. Если увеличишь интервал с тысячи до десяти тысяч, ты увидишь, что оно по другому себя ведёт.
если понажимать кнопку повторно несколько раз, счетчик начинает бежать в разы быстрее. Это что значит?
OlezhkaНадо делать многопоточными такие вещи, иначе у тебя кнопки будут залипать. Рекурсию нельзя делать. В Erlang'е (язык программирования) рекурсию можно делать, так как она автоматически оптимизируется и превращается в цикл. В питоне же такой возможности нет, поэтому любая рекурсия в питоне вызывает большие вопросы. Поэтому и ограничитель рекурсии стоит, так как не нужна она.
Не ругайте сильно за то, что я так до сих пор ничего не читал про потоки “threads”!
Отредактировано py.user.next (Янв. 6, 2023 14:50:45)
Офлайн
Спасибо большое за ответ!
py.user.nextА как завершить функцию? По инструкции “return”? Или ввести симафор
Таким образом эти функции бесконечно вызываются и остаются незавершёнными.
if stop == 0: root.after(1000, func) else: pass
py.user.nextТо есть я могу не использовать глобальные переменные? Если в теле функции объявляется внутренняя переменная, а в процессе выполнения функции значение этой переменной как-то меняется, то ее новое значение будет сохраняться где-то в памяти? А при каждом новом обращении к функции будет подставляться это сохраненное значение? Но КАК? Как, если при каждом вызове функции она будет считывать объявление этой переменной, при котором будет происходить ее инициализация с исходным значением (х = 0)?
При этом каждая такая функция сохраняет в памяти своё локальное состояние в своих локальных переменных. Память занимается все больше и больше
py.user.nextЯ знаю. По дефолту он равен 1000. Но в каком-то модуле, я знаю, есть инструкция, позволяющая задать на этот ограничитель любое другое число.
В питоне есть ограничитель на случай возникновения бесконечной рекурсии
py.user.nextА почему в “Пайтоне” не сделали так, чтобы перезапускалась одна функция, а не запускались ее новые экземпляры? Ведь, если я пропишу:
Запускается всё больше и больше экземпляров функции и каждый из этих экземпляров действует на переменную x
х = 0 х = 1 х = 2
py.user.nextКак? То есть инструкция “root.after” не перезапускает функцию, а запускает новый ее экземпляр? А старый так и остается висеть зацикленным в памяти?
Эти экземпляры также запускают ещё экземпляры
py.user.nextА приведи, пожалуйста, простенький пример рекурсии чего-то алгоритмического! Это как? Это цикл “while” чтоль?
Если тебе понадобилась рекурсия для повтора вызова функции, а не для чего-то алгоритмического
while stop == 0: x += 1
def func(): print('Hello!') func
def func(): print('Hello!') func()
py.user.nextПолучается, что когда мой таймер дотикает до 1000, будет выдано исключение “Max recursion depth”?
Когда оно доходит до тысячи таких вызовов, питон её вырубает и пишет это исключение
Отредактировано Olezhka (Янв. 5, 2023 23:44:24)
Офлайн
Ну да! Провел еще раз эксперимент самоучки :
def func(): print('Hello!') print(func()) print(type(func()))
def func(): print('Hello!') print(func) print(type(func))
Отредактировано Olezhka (Янв. 5, 2023 11:49:56)
Офлайн
Olezhka
Потом я удалил скобки и понял, что причина самозапуска функции была в них. Но я так и не понял, ПОЧЕМУ?
Офлайн
xam1816Спасибо! Я уже сам методом “научного тыка” до этого допетрил!
вот здесь такая же ошибкассылка
Офлайн
from tkinter import * root = Tk() root.title('main') root.geometry("200x100-600-300") root.configure(bg='#CECECE') root.configure(bd = 5) def func(): iv.set(iv.get()+1) root.after(1000, func) iv = IntVar() iv.set(0) lbl_1 = Label(root, textvariable= iv, font = ('Arial Bold', 18), fg = 'black', bg = '#CECECE', padx = 20, pady = 5, wraplength = 300) lbl_1.grid(row = 0, column = 1, rowspan = 1, columnspan = 2) btn_1 = Button(root, text = 'text', bg = 'white', fg = 'black', padx = 5, pady = 5, font = ('Arial Bold', 14, 'bold'), relief = 'raised', command = func, state = 'normal') btn_1.grid(row = 0, column = 0, rowspan = 1, columnspan = 1) root.mainloop()
Офлайн
xam1816Спасибо! Ты просто оптимизировал мой код?
Офлайн
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Только аргументы функции можешь использовать для передачи информации. Соответственно, в языке Erlang, например, в котором нет циклов вообще, для зацикливания используется рекурсия. Вся передача информации идёт через аргументы.
То есть я могу не использовать глобальные переменные? Если в теле функции объявляется внутренняя переменная, а в процессе выполнения функции значение этой переменной как-то меняется, то ее новое значение будет сохраняться где-то в памяти? А при каждом новом обращении к функции будет подставляться это сохраненное значение? Но КАК? Как, если при каждом вызове функции она будет считывать объявление этой переменной, при котором будет происходить ее инициализация с исходным значением (х = 0)?
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 экземпляра переменной “х” с разными значениями.x = 0 x = 1 x = 2
>>> 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 >>>
OlezhkaФункция висит в памяти, пока не завершится. А завершается она в примере выше - по условию. Завершается она тогда, когда перестаёт себя вызывать. Тогда она доходит до своего конца, до последней строки. А когда в последней строке ничего нет, там как бы есть return None невидимый. Поэтому функции без return на самом деле возвращают None всегда.
То есть инструкция “root.after” не перезапускает функцию, а запускает новый ее экземпляр? А старый так и остается висеть зацикленным в памяти?
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Круглые собки после имени говорят интерпретатору “возьми объект по этому имени и вызови этот объект как функцию”. Интерпретатор берёт имя, узнаёт адрес объекта в памяти, потом у объекта с этим адресом запускает метод __call__(). Если ты имя функции передаёшь куда-то без круглых скобок, то практически во всех сиподобных языках это означает, что ты хочешь взять адрес функции и куда-то передать этот адрес. Адрес функции можно сохранить куда-то. В языке C так можно массив функций держать и вызывать функции, перебирая этот массив просто.
Почему при “command = func()” функция именно самозапускается, а при “command = func” кнопке просто дается ссылка на эту функцию, как и положено? В чем магия пустых скобок“()”?
>>> 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Ключевое слово lambda в питоне просто определяет функцию.
Но тогда меня спасла “лямбда” - “command = lambda: func(arg)” Вот с “лямбдой” самозапуска функции уже не происходило. Интересно, почему?
>>> 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”?
Отредактировано py.user.next (Янв. 6, 2023 01:22:39)
Офлайн
py.user.nextА “f(n + 1)” здесь не есть ли тот самый рекурсивный метод самозацикливания функции, про который ты мне говорил, что лучше им не пользоваться, так как он быстро засирает память своими экземплярами? Или это засирание происходит только при использовании “root.after()”?
>>> def f(n):
… if n < 3:
… print(n)
… f(n + 1)
Офлайн