Уведомления

Группа в Telegram: @pythonsu

#1 Янв. 5, 2023 01:04:47

Olezhka
Зарегистрирован: 2022-12-13
Сообщения: 118
Репутация: +  0  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

Я уже начал изучать модуль “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()
1) Сперва, не видя ничего криминального, я прописал в свойствах кнопки “command = func()”, подумав, а какая разница между “func” и “func()”? Наверное, никакой, поскольку там не заданы никакие аргументы. Запустив программу, я долго удивлялся и долго искал причину того, что функция начинала работать сама собой и без нажатия кнопки. Потом я удалил скобки и понял, что причина самозапуска функции была в них. Но я так и не понял, ПОЧЕМУ?
2) Затем я ради эксперимента подрисовал двойные пустые скобки в инструкцию “root.after(1000, func())”. Нажал на кнопку… счетчик за долю секунды досчитал до 1017 и программа выдала мне "RecursionError: maximum recursion depth exceeded". Тоже не понимаю пока, что это такое и почему оно так получилось.
3) На видео видно “Ютуб”, что если понажимать кнопку повторно несколько раз, счетчик начинает бежать в разы быстрее. Это что значит? В памяти после каждого нажатия запускается новый экземпляр самозацикленной функции?
P.S. Я все еще в шоке…
Заранее спасибо за нравоучения и наставления! Не ругайте сильно за то, что я так до сих пор ничего не читал про потоки “threads”!

Отредактировано Olezhka (Янв. 5, 2023 01:17:38)

Офлайн

#2 Янв. 5, 2023 02:30:31

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9874
Репутация: +  854  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

Olezhka
Потом я удалил скобки и понял, что причина самозапуска функции была в них. Но я так и не понял, ПОЧЕМУ?
У тебя получилась косвенная рекурсия. Это когда функция вызывает саму себя не напрямую, а косвенно, через какие-то другие функции. Например, функция f вызывает функцию g, функция g вызывает функцию m, а функция m вызывает функцию f. А функция f опять всё по цепочке вызывает, что опять приводит к её вызову. Таким образом эти функции бесконечно вызываются и остаются незавершёнными. При этом каждая такая функция сохраняет в памяти своё локальное состояние в своих локальных переменных. Память занимается все больше и больше и программа в итоге выпадает, так как операционная система не может выделять больше память этой программе. Всё остальное в операционной системе обычно тоже виснет из-за этого. В питоне есть ограничитель на случай возникновения бесконечной рекурсии в тысячу рекурсивных вызовов, чтобы операционная система не тормозила.

Olezhka
счетчик за долю секунды досчитал до 1017 и программа выдала мне “RecursionError: maximum recursion depth exceeded”. Тоже не понимаю пока, что это такое и почему оно так получилось.
Тут получилась прямая рекурсия. Это когда функция вызывает саму себя напрямую. Если ты в функции f пишешь вызов функции f, то эта вызванная функция f снова вызовет функцию f, а та функция f снова вызовет функцию f. Когда оно доходит до тысячи таких вызовов, питон её вырубает и пишет это исключение.

Olezhka
если понажимать кнопку повторно несколько раз, счетчик начинает бежать в разы быстрее. Это что значит?
Запускается всё больше и больше экземпляров функции и каждый из этих экземпляров действует на переменную x. Эти экземпляры также запускают ещё экземпляры. Если увеличишь интервал с тысячи до десяти тысяч, ты увидишь, что оно по другому себя ведёт.

Olezhka
Не ругайте сильно за то, что я так до сих пор ничего не читал про потоки “threads”!
Надо делать многопоточными такие вещи, иначе у тебя кнопки будут залипать. Рекурсию нельзя делать. В Erlang'е (язык программирования) рекурсию можно делать, так как она автоматически оптимизируется и превращается в цикл. В питоне же такой возможности нет, поэтому любая рекурсия в питоне вызывает большие вопросы. Поэтому и ограничитель рекурсии стоит, так как не нужна она.

Если тебе понадобилась рекурсия для повтора вызова функции, а не для чего-то алгоритмического, то значит ты что-то делаешь не так.



Отредактировано py.user.next (Янв. 6, 2023 14:50:45)

Офлайн

#3 Янв. 5, 2023 10:42:22

Olezhka
Зарегистрирован: 2022-12-13
Сообщения: 118
Репутация: +  0  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

Спасибо большое за ответ!

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
, то в итоге у меня переменная “х” будет равна 2. Ведь не создастся же 3 экземпляра переменной “х” с разными значениями. А почему же с функциями все по-другому? Ведь функция - это такой же объект, как и переменная. Или я не прав?
py.user.next
Эти экземпляры также запускают ещё экземпляры
Как? То есть инструкция “root.after” не перезапускает функцию, а запускает новый ее экземпляр? А старый так и остается висеть зацикленным в памяти?
py.user.next
Если тебе понадобилась рекурсия для повтора вызова функции, а не для чего-то алгоритмического
А приведи, пожалуйста, простенький пример рекурсии чего-то алгоритмического! Это как? Это цикл “while” чтоль?
Кстати! А что если внутри такой функции использовать цикл с симафором:
 while stop == 0:
    x += 1
Но тогда вопрос. А как заставить этот цикл повторяться с определенным интервалом времени? Инструкцией “sleep”? Но она мне не нравится! Она заставляет зависать весь GUI-интерфейс “tkinter” (Не отвечает) Какие еще есть варианты? “timer” из “threads”?
Слушай, друг! Извини, не знаю, как тебя зовут! Меня Олег. Спасибо тебе за подсказки, но ты мне так и не объяснил, в чем же магия двойных пустых скобок в параметре кнопки? Почему при “command = func()” функция именно самозапускается, а при “command = func” кнопке просто дается ссылка на эту функцию, как и положено? В чем магия пустых скобок“()”?
Хотя… Я тут провел эксперимент. Вот так функция не запускается:
 def func():
    print('Hello!')
func
А вот так запускается:
 def func():
    print('Hello!')
func()
Кстати, та же проблема с самозапуском функции у меня была, когда я в функцию пытался передать аргумент “command = func(arg)”. Но тогда меня спасла “лямбда” - “command = lambda: func(arg)” Вот с “лямбдой” самозапуска функции уже не происходило. Интересно, почему?
py.user.next
Когда оно доходит до тысячи таких вызовов, питон её вырубает и пишет это исключение
Получается, что когда мой таймер дотикает до 1000, будет выдано исключение “Max recursion depth”?

Отредактировано Olezhka (Янв. 5, 2023 23:44:24)

Офлайн

#4 Янв. 5, 2023 11:49:08

Olezhka
Зарегистрирован: 2022-12-13
Сообщения: 118
Репутация: +  0  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

Ну да! Провел еще раз эксперимент самоучки :

 def func():
    print('Hello!')
print(func())
print(type(func()))
Вывод:
Hello!
None
Hello!
<class ‘NoneType’>

Получается, в инструкции “print(func())” функция сперва отработала, а потом инструкция “print” напечатала то, что функция вернула, то есть “None”.
И второй раз во время инструкции “print(type(func()))” то же самое - функция сперва еще раз выполнилась, а потом отработала инструкция “type”
А без скобок функция не выполняется, а становится просто… экземпляром класса? Я правильно сказал?
 def func():
    print('Hello!')
print(func)
print(type(func))
Вывод:
<function func at 0x0000024AF0C051C0>
<class ‘function’>

Отредактировано Olezhka (Янв. 5, 2023 11:49:56)

Офлайн

#5 Янв. 5, 2023 14:35:01

xam1816
Зарегистрирован: 2020-05-11
Сообщения: 1355
Репутация: +  119  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

Olezhka
Потом я удалил скобки и понял, что причина самозапуска функции была в них. Но я так и не понял, ПОЧЕМУ?

вот здесь такая же ошибкассылка

Офлайн

#6 Янв. 5, 2023 14:57:27

Olezhka
Зарегистрирован: 2022-12-13
Сообщения: 118
Репутация: +  0  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

xam1816
вот здесь такая же ошибкассылка
Спасибо! Я уже сам методом “научного тыка” до этого допетрил!

Офлайн

#7 Янв. 5, 2023 21:56:09

xam1816
Зарегистрирован: 2020-05-11
Сообщения: 1355
Репутация: +  119  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

  
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()

Офлайн

#8 Янв. 5, 2023 23:45:08

Olezhka
Зарегистрирован: 2022-12-13
Сообщения: 118
Репутация: +  0  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

xam1816
Спасибо! Ты просто оптимизировал мой код?

Офлайн

#9 Янв. 6, 2023 01:16:31

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9874
Репутация: +  854  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

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
А почему в “Пайтоне” не сделали так, чтобы перезапускалась одна функция, а не запускались ее новые экземпляры?
  
x = 0
x = 1
x = 2
, то в итоге у меня переменная “х” будет равна 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”?
Он ещё раньше выдаст, потому что они все там всё время вызывают новые экземпляры. И в каждом из этих экземпляров ещё и таймер вызывается и все эти экземпляры ждут истечения своих таймеров.



Отредактировано py.user.next (Янв. 6, 2023 01:22:39)

Офлайн

#10 Янв. 6, 2023 13:22:21

Olezhka
Зарегистрирован: 2022-12-13
Сообщения: 118
Репутация: +  0  -
Профиль   Отправить e-mail  

Кнопка "Tkinter.Button" нажимается сама собой

py.user.next
>>> def f(n):
… if n < 3:
… print(n)
… f(n + 1)
А “f(n + 1)” здесь не есть ли тот самый рекурсивный метод самозацикливания функции, про который ты мне говорил, что лучше им не пользоваться, так как он быстро засирает память своими экземплярами? Или это засирание происходит только при использовании “root.after()”?

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version