0

So what I want to do is that there will be a function from script B which will be called inside the script A. So that function will be displaying some output on the console via print, but what I want is that the output should also be displayed on a widget, and the script A is the one where all my GUI operations happen.

So what I did was that I passed a function X as a argument to the function Y in script B, thus, when that output was printed in the function Y, I simply referenced that into a variable and passed that variable into the function X of script A... which I passed as argument. Thus, that function simply inserted the output on the text widget.

But the problem is that the function Y was called after a button click, and due to some reason the button freezes for some time and then displays the final output all at once. But this is not what I want. I want that the output to be displayed at the same time as it is displayed in the console, one after another, that way. But it seems like that the button widget resumes when the entire function passed in the command argument has finished running.

To solve this I tried using sleep and after functions but they don't seem to be helping me that much. Thus, I have tried to recreate my problem in a more simpler way and I have tried it doing it via sleep and after, but none of em seems to work for me.

So the codes are below, though they don't exactly match my problem but I hope they are able to explain my question more clearly.

So say we have two scripts A and B

In the script A -

from time import sleep

# will output a number every 1 second on the console
def Show(number, Function):    
    while(number < 5):
        sleep(1)            # Wait specified time
        number += 1         # Some random operation, here incrementing the number by 1

        print(number)       # On console
        Function(number)    # On widget

In the script B -

import A
import tkinter as tk

number = 0

root = tk.Tk()

# Function which will be passed as an argument
def Print(number):
    label = tk.Label(root, text=number)
    label.pack()

# Will be used for the after method [ OPTIONAL ]
def Delay(number, Print):
    root.after(5000, test.Show(number, Print))

# Below I recommend to comment either one of the button initializations, in order to test each ways

# Using sleep method
button = tk.Button(root, text='Start', command=lambda: A.Show(number, Print))

                             #OR

# Using after method
button = tk.Button(root, text='Start', command=lambda: Delay(number, Print))


button.pack()

tk.mainloop()

So my point is that, I want to show the numbers on any widget ( in my real problem it is a text widget ) at the same time as it is actually happening, i.e displaying on the console.

UPDATE: This is not the actual problem, it is just a simplified version of my actual problem. So don't assume that I am trying to make this code over complicated. As I am training a classifier with a trained NN so the output is printed every iteration on the console. So what I want to achieve is that the output be printed on the text widget at the same time as well during the ongoing loop.

UPDATE 2: It is finally working as I wanted it to be. The answer is to use threading as described by Mike :D

Arpit Srivastava
  • 85
  • 1
  • 1
  • 6

1 Answers1

2

sleep() and tkinter do not get along. Nor does while. The problem with sleep and while is that they block the main loop until they are complete and thus nothing updates in your GUI until they have finished. That said I think you are making this code more complex then it needs to be. You have 2 functions for something that can simply be done in one and you are passing functions to functions. Way more complex than need be.

You are also packing a new label every time print is called. Please try to keep to the PEP8 standard for naming your function. all_low_with_underscores for standard functions and variables.

The method after() is specifically designed to handle timed events within tkinter and is mainly used to replace sleep within the GUI.

Here is your code simplified and using after():

import tkinter as tk


def delay_and_print():
    global number
    if number < 5:
        print(number)
        label.config(text=number)
        number += 1
        root.after(1000, delay_and_print)


root = tk.Tk()
number = 0
tk.Button(root, text='Start', command=delay_and_print).pack()
label = tk.Label(root, text='')
label.pack()
root.mainloop()

Here is an example using threading:

import tkinter as tk
import threading
from time import sleep


def delay_and_print():
    global number
    if number < 100:
        print(number)
        label.config(text=number)
        number += 1
        sleep(2)
        delay_and_print()


def start_thread():
    thread = threading.Thread(target=delay_and_print)
    thread.start()


def do_something():
    print('Something')


root = tk.Tk()
number = 0
tk.Button(root, text='Start', command=start_thread).pack()
tk.Button(root, text='some other button to test if app is responsive while loop is going!', command=do_something).pack()
label = tk.Label(root, text='')
label.pack()
root.mainloop()

Result:

enter image description here

Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • No I have to use a loop. Remember I said I am using a very simplified version of my problem. Because in my problem I have a script which is training a classifier using trained neural network and in that script after every iteration the result is printed out on console. So I want that result to be printed on a text widget. Yes I know I used a label in my example, but it was just to quickly demonstrate my problem. Obviously I'm not gonna do this bad :) – Arpit Srivastava Oct 14 '19 at 13:36
  • If you have to use a loop then you will need to either incorporate `after()` or if you must use some kind of while statement or some process intensive loop then you need to use threading. Also it is not obvious as many many post are much worse lol. – Mike - SMT Oct 14 '19 at 13:37
  • Thanks, the threading example is really helpful. I think I can figure this out. Will update once my actual code is working as expected. – Arpit Srivastava Oct 14 '19 at 13:49
  • @mike-smt calling `tkinter` from thread other than the where you created the `tk.Tk` is bad practice as in some cases, it can crash `tkinter` – TheLizzard Feb 01 '23 at 13:51
  • @TheLizzard I am not sure what you are referring to. I do not call tk from the threaded function. If you are referring to updating a label or using after against root those should be fine as long as tkinter's mainloop is active. I am aware that tkinter is "not thread safe" but I have been using it where needed to manage heavy loads where needed as to not block the mainloop for years now without issues. In fact my most successful application uses threading quite a bit to manage SQL connections and data manipulation with zero reports of failure or crashing in the past 3 years. – Mike - SMT Feb 01 '23 at 14:04
  • @Mike-SMT I am referring to the `.config`. It is true that the problems that I am referring to are rare, but when they happen, `python`'s interpreter crashes without a traceback. Also your threaded approach will raise `RecursionError` if the loop is extended. – TheLizzard Feb 01 '23 at 14:21
  • @TheLizzard What do you mean about extended? I tested these before posting and did not receive any RecursionError. I have added a screen shot to show it completing all 100 loops of the function. Since the number is managed by a global number it actually doesn't even matter how many times you hit start in this instance. Once the number is 100 the function never loops again. So really there should never be a Recursion issue. – Mike - SMT Feb 01 '23 at 14:37
  • Right now `delay_and_print` calls itself, therefore if the loop is extended to loop 1020 times instead of 100 times, it will raise a `RecursionError`. Btw that problem doesn't exist in the `.after` approach because of the way `tkinter` handles `.after` – TheLizzard Feb 01 '23 at 14:42
  • @TheLizzard I see. Is 1020 significant or is that just a limitation based on each computers memory capacity? As in the limit of recursion differs from pc to pc. – Mike - SMT Feb 01 '23 at 14:47
  • 1
    @Mike-SMT The recursion limits depends on your python installation (for more details look [here](https://stackoverflow.com/q/3323001/11106801)). For some reason when I tested it, it crashed on the 1012th time for some reason, so I rounded it upwards. – TheLizzard Feb 01 '23 at 14:56