label.configure works sometimes why?
Question: Part of my code is as follows:

    
    
    def get_songs():
        label6.configure(text='Wait')
        os.system('/home/norman/my-startups/grabsongs')
        label6.configure(text='Done')
    

The label is not updated at the first `.configure()` but is at the second one.
Except if I cause a deliberate error immediately after the first one at which
point it is updated and then the program terminates. The system call takes
about 2 minutes to complete so it isn't as if there isn't time to display the
first one.

I am using Python 2.7.6

Does anyone know why please?


Answer: I'm going to guess you're using `Tkinter`. If so, as @albert just suggested,
you'll want to call `label.update_idletasks()` or `label.update()` to tell
Tkinter to refresh the display.

As a very crude example to reproduce your problem, let's make a program that
will:

  1. Wait 1 second
  2. Do something (sleep for 2 seconds) and update the text to "wait"
  3. Display "done" afterwards

For example:

    
    
    import Tkinter as tk
    import time
    
    root = tk.Tk()
    label = tk.Label(root, text='Not waiting yet')
    label.pack()
    
    def do_stuff():
        label.configure(text='Wait')
        time.sleep(2)
        label.configure(text='Done')
    
    label.after(1000, do_stuff)
    tk.mainloop()
    

Notice that "Wait" will never be displayed.

To fix that, let's call `update_idletasks()` after initially setting the text:

    
    
    import Tkinter as tk
    import time
    
    root = tk.Tk()
    label = tk.Label(root, text='Not waiting yet')
    label.pack()
    
    def do_stuff():
        label.configure(text='Wait')
        label.update_idletasks()
        time.sleep(2)
        label.configure(text='Done')
    
    label.after(1000, do_stuff)
    tk.mainloop()
    

* * *

As far as why this happens, it actually is because Tkinter doesn't have time
to update the label.

Calling `configure` doesn't automatically force a refresh of the display, it
just queues one the next time things are idle. Because you immediately call
something that will halt execution of the mainloop (calling an executable and
forcing python to halt until it finishes), Tkinter never gets a chance to
process the changes to the label.

Notice that while the gui displays "Wait" (while your process/sleep is
running) it won't respond to resizing, etc. Python has halted execution until
the other process finishes running.

To get around this, consider using `subprocess.Popen` (or something similar)
instead of `os.system`. You'll then need to perodically poll the returned pipe
to see if the subprocess has finished.

As an example (I'm also moving this into a class to keep the scoping from
getting excessively confusing):

    
    
    import Tkinter as tk
    import subprocess
    
    class Application(object):
        def __init__(self, parent):
            self.parent = parent
            self.label = tk.Label(parent, text='Not waiting yet')
            self.label.pack()
            self.parent.after(1000, self.do_stuff)
    
        def do_stuff(self):
            self.label.configure(text='Wait')
            self._pipe = subprocess.Popen(['/bin/sleep', '2'])
            self.poll()
    
        def poll(self):
            if self._pipe.poll() is None:
                self.label.after(100, self.poll)
            else:
                self.label.configure(text='Done')
    
    root = tk.Tk()
    app = Application(root)
    tk.mainloop()
    

The key difference here is that we can resize/move/interact with the window
while we're waiting for the external process to finish. Also note that we
never needed to call `update_idletasks`/`update`, as Tkinter now does have
idle time to update the display.

