Davis Diercks
Davis Diercks

Reputation: 679

Python how to make simple animated loading while process is running

This is pretty much what I have right now:

import time
import sys

done = 'false'
#here is the animation
def animate():
    while done == 'false':
        sys.stdout.write('\rloading |')
        time.sleep(0.1)
        sys.stdout.write('\rloading /')
        time.sleep(0.1)
        sys.stdout.write('\rloading -')
        time.sleep(0.1)
        sys.stdout.write('\rloading \\')
        time.sleep(0.1)
    sys.stdout.write('\rDone!     ')

animate()
#long process here
done = 'false'

and I want to get it so that the "while" script would function independently, and it continues to the process, while the animation is going on until the end of the process signals the variable "done" to be 'false', stopping the animation and replacing it with "Done!". This method would be essentially running two scripts at once; is there a way to do it?

Upvotes: 43

Views: 120875

Answers (11)

Florijn Terstal
Florijn Terstal

Reputation: 1

I like the one that @ted made, i made it more flexible and extensible loader system where you can easily add more animation styles. Its modular design makes it more scalable.

from threading import Thread, Lock
from time import sleep

class BaseLoader:
    """
    Base class for console loaders.
    Provides the basic structure and methods to start, animate, and stop the loader.
    """

    ANIMATION_STEPS = []
    VALID_POSITIONS = ["front", "end"]
    
    def __init__(self, desc: str="Loading...", end: str="Done!", timeout: float=0.1, position:str ="front") -> None:
        """
        Initialize the loader with given parameters.
        
        Args:
            desc (str): The description of the loader.
            end (str): Message to display after the animation ends.
            timeout (float): Duration (in seconds) to wait between animation frames.
            position (str): Position of the animation relative to the description. Either "front" or "end".
        """
        
        self._config = {
            "desc": desc,
            "end": end,
            "timeout": timeout,
            "position": position
        }
        self._done = False
        self._lock = Lock() # Introduce a lock for thread safety
        
        if self._config["position"] not in self.VALID_POSITIONS:
            raise ValueError(f"Invalid position: {self._config['position']}. Choose either 'front' or 'end'.")
        if not self.ANIMATION_STEPS:
            raise ValueError("ANIMATION_STEPS must be defined in derived classes and cannot be empty.")


    def __enter__(self) -> None:
        """
        Start the animation thread when the context is entered.

        This method initializes and starts a daemonized thread that runs 
        the `_animate` method. By setting it as a daemon, it ensures the thread 
        will automatically exit when the main program finishes. The `_done` flag 
        is set to False, indicating the animation is active.
        """
        # Ensure thread-safety when modifying the _done flag.
        with self._lock:  
            self._done = False
            
        # Initialize and start the daemonized animation thread.
        self._thread = Thread(target=self._animate, daemon=True)
        self._thread.start()
               
            
    def _animate(self) -> None:
        """Handles the core animation logic for the loader."""
        
        # Initialize the step counter for animation frames
        step_count = 0

        try:
            while True:
                # Using a lock to safely check the _done flag
                with self._lock:
                    if self._done:
                        break

                # Check the position configuration to determine the order of the animation and description.
                prefix, suffix = (
                    (self.ANIMATION_STEPS[step_count], self._config['desc']) 
                    if self._config["position"] == "front" 
                    else (self._config['desc'], self.ANIMATION_STEPS[step_count])
                )
                print(f"\r{prefix} {suffix}", flush=True, end="")

                
                # Pause the execution for a specified duration (timeout) before the next animation frame
                sleep(self._config["timeout"])
                
                # Increment the step counter, and wrap-around if it exceeds the total number of animation steps
                step_count = (step_count + 1) % len(self.ANIMATION_STEPS)
                
        except KeyboardInterrupt:
            pass  # Allow the user to interrupt the animation
        except Exception as e:
            print(f"Animation thread error: {e}")



    def __exit__(self, *args) -> None:
        """
        Gracefully stop the animation thread when the context is exited.

        This method ensures thread-safety when updating the `_done` flag.
        Additionally, it clears any ongoing animation from the console and prints 
        the configured end message. It waits for the animation thread to finish 
        before exiting to ensure no lingering threads.
        """
        # Safely set the _done flag to stop the animation thread.
        with self._lock:  
            self._done = True
            
        # Clear the ongoing animation from the console.
        print("\r" + " " * (len(self._config["desc"]) + len(self.ANIMATION_STEPS[0]) + 1), end="", flush=True)
        
        # Print the end message
        print(f"\r{self._config['end']}", flush=True)
        
        # Wait for the animation thread to finish
        self._thread.join()
        

class LineLoader(BaseLoader):
    ANIMATION_STEPS = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"]

class GrowthLoader(BaseLoader):
    ANIMATION_STEPS = ["·", "•", "●", "•", "·"]

class CircleLoader(BaseLoader):
    ANIMATION_STEPS = ["◓", "◑", "◒", "◐"]

class PulseLoader(BaseLoader):
    ANIMATION_STEPS = ["•", "○", "•", "·", "●", "·"]

class BounceLoader(BaseLoader):
    ANIMATION_STEPS = ["<• ", "<•>", " •>", " • "]

class DotLoader(BaseLoader):
    ANIMATION_STEPS = ["·", "•", "••", "•••", "••••", "•••", "••", "•"]

class BartLoader(BaseLoader):
    ANIMATION_STEPS = ["_", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]


# Testing the refactored loaders
if __name__ == "__main__":
    with LineLoader("Line loading..."):
        for _ in range(15):
            sleep(0.25)


    loader = BounceLoader("Loading Bounce loader object...", "Super fast!", timeout=0.15, position="end")
    with loader:
        for _ in range(15):
            sleep(0.25)

Upvotes: 0

Jansen Simanullang
Jansen Simanullang

Reputation: 2205

I see this is a threading problem and not just an animated loading problem. Most the answers provided in this QA thread provides only a pseudocode and left the reader on their own.

Here is an answer I am trying to give using a working example of threading and animated loading.

The reader may modify in accordance to their needs.

Import python packages

import sys, time, threading

Define your process

 # Here is an example of the process function:

def the_process_function():
    n = 20
    for i in range(n):
        time.sleep(1)
        sys.stdout.write('\r'+'loading...  process '+str(i)+'/'+str(n)+' '+ '{:.2f}'.format(i/n*100)+'%')
        sys.stdout.flush()
    sys.stdout.write('\r'+'loading... finished               \n')

Define your animated characters function

def animated_loading():
    chars = "/—\|" 
    for char in chars:
        sys.stdout.write('\r'+'loading...'+char)
        time.sleep(.1)
        sys.stdout.flush() 

Define name dan target of your thread

the_process = threading.Thread(name='process', target=the_process_function)

Start the thread

the_process.start()

while the process is alive, call the animated_loading() function

while the_process.isAlive():
    animated_loading()

The main steps are outlined in the commented out line.

Upvotes: 31

Devadut S Balan
Devadut S Balan

Reputation: 66

from time import sleep
k='#'
j=0
k='#'
def fixed_space(i,array):
    g=(' '*len(str(len(array))))
    g=g.replace(' ','',len(str(int(i))))
    return g
def ani(i,array):
    global k
    #For accessing the global variables that are defined out of the function
    global j
    per=((i+1)*100)//len(array)
    #To calculate percentage of completion of loop
    c=per//5
    #Integer division (the value 5 decides the length of the bar)
    if c!=j:
    #When ever the values of these 2 variables change add one # to the global variable k
        k+='#'
    y='['+k+'                     '+']'
    #20 empty spaces (100/5) 
    y=y.replace(' ','',len(k))
    #To make the size of the bar fixed ever time the length of k increases one ' ' will be removed
    g=fixed_space(per,array)
    #To fix at the same position
    f=fixed_space(i,array)
    print('Status : ',y,g+str(per)+'%',' ('+f+str(i+1)+' / '+str(len(array))+' ) ',end='\r')
    #That same '\r' to clear previous text
    j=c

array = range(100)
for i in array:
    ani(i,array)
    sleep(0.1)

my code is little bit messed up so feel free to update it

Upvotes: 1

Rohan Koirala
Rohan Koirala

Reputation: 1

Try it:

import threading, sys, time, webbrowser

def search_yt(inp_yt):
    webbrowser.open(f"https://www.youtube.com/results?search_query={inp_yt}")

def loading(message="", round=1, _sleep=0.7, mark='-'):
    no = round
    hyphen = mark
    space = ["", " ", "  ", "   ", "    ", "     ", "      ", "       ", "        ", "         "]
    for loop in range(0, 10):
        sys.stdout.write(f"\r{message} [{hyphen}{space[9 - loop]}]")
        time.sleep(_sleep)
        hyphen += mark
    if no != 1:
        loading(message, no -1 , _sleep, mark)

t1 = threading.Thread(target=loading, args=("hello",1, 0.5,"=",))#loading()
t2 = threading.Thread(target=search_yt, args=("python",))

t1.start()
t2.start()

Upvotes: 0

Devadut S Balan
Devadut S Balan

Reputation: 66

a simple one

from time import sleep #for delay
from itertools import cycle #for infinite cycling through a list
for i in cycle(["|", "/", "-", "\\"]):
    print(i,end='\r') # '\r' clears the previous output
    sleep(0.2)

Upvotes: 1

ted
ted

Reputation: 14744

Getting inspiration from the accepted answer, here's a useful class I wrote, printing a loader à la nodejs cli:

enter image description here

from itertools import cycle
from shutil import get_terminal_size
from threading import Thread
from time import sleep


class Loader:
    def __init__(self, desc="Loading...", end="Done!", timeout=0.1):
        """
        A loader-like context manager

        Args:
            desc (str, optional): The loader's description. Defaults to "Loading...".
            end (str, optional): Final print. Defaults to "Done!".
            timeout (float, optional): Sleep time between prints. Defaults to 0.1.
        """
        self.desc = desc
        self.end = end
        self.timeout = timeout

        self._thread = Thread(target=self._animate, daemon=True)
        self.steps = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"]
        self.done = False

    def start(self):
        self._thread.start()
        return self

    def _animate(self):
        for c in cycle(self.steps):
            if self.done:
                break
            print(f"\r{self.desc} {c}", flush=True, end="")
            sleep(self.timeout)

    def __enter__(self):
        self.start()

    def stop(self):
        self.done = True
        cols = get_terminal_size((80, 20)).columns
        print("\r" + " " * cols, end="", flush=True)
        print(f"\r{self.end}", flush=True)

    def __exit__(self, exc_type, exc_value, tb):
        # handle exceptions with those variables ^
        self.stop()


if __name__ == "__main__":
    with Loader("Loading with context manager..."):
        for i in range(10):
            sleep(0.25)

    loader = Loader("Loading with object...", "That was fast!", 0.05).start()
    for i in range(10):
        sleep(0.25)
    loader.stop()

Also if you're willing to use an external library you might want to look into rich's console.status

from time import sleep
from rich.console import Console

console = Console()
tasks = [f"task {n}" for n in range(1, 11)]

with console.status("[bold green]Working on tasks...") as status:
    while tasks:
        task = tasks.pop(0)
        sleep(1)
        console.log(f"{task} complete")

Upvotes: 47

Antonio
Antonio

Reputation: 71

import sys, time, threading

def your_function_name() :
    # do something here

def loadingAnimation(process) :
    while process.isAlive() :
        chars = "/—\|" 
        for char in chars:
            sys.stdout.write('\r'+'loading '+char)
            time.sleep(.1)
            sys.stdout.flush()

loading_process = threading.Thread(target=your_function_name)
loading_process.start()

loadingAnimation(loading_process)
loading_process.join()

Upvotes: 4

Python Pro
Python Pro

Reputation: 111

Here is my code:

import time
import sys
print("Loading:")


#animation = ["10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"]
animation = ["[■□□□□□□□□□]","[■■□□□□□□□□]", "[■■■□□□□□□□]", "[■■■■□□□□□□]", "[■■■■■□□□□□]", "[■■■■■■□□□□]", "[■■■■■■■□□□]", "[■■■■■■■■□□]", "[■■■■■■■■■□]", "[■■■■■■■■■■]"]

for i in range(len(animation)):
    time.sleep(0.2)
    sys.stdout.write("\r" + animation[i % len(animation)])
    sys.stdout.flush()

print("\n")

Upvotes: 11

Jack
Jack

Reputation: 1

Its all done with a few lines of code:

import time
import os

anime = ["|", "/", "-", "\\"]

done = False

while done is False:
    for i in anime:
        print('Please wait while system is Loading...', i)
        os.system('clear')
        time.sleep(0.1)
        

Tested and working successfully in the terminal.

Upvotes: -2

Alfred George
Alfred George

Reputation: 136

Try this one

import time
import sys


animation = "|/-\\"

for i in range(100):
    time.sleep(0.1)
    sys.stdout.write("\r" + animation[i % len(animation)])
    sys.stdout.flush()
    #do something
print("End!")

Upvotes: 3

Andrew Clark
Andrew Clark

Reputation: 208545

Use a thread:

import itertools
import threading
import time
import sys

done = False
#here is the animation
def animate():
    for c in itertools.cycle(['|', '/', '-', '\\']):
        if done:
            break
        sys.stdout.write('\rloading ' + c)
        sys.stdout.flush()
        time.sleep(0.1)
    sys.stdout.write('\rDone!     ')

t = threading.Thread(target=animate)
t.start()

#long process here
time.sleep(10)
done = True

I also made a couple of minor modifications to your animate() function, the only really important one was adding sys.stdout.flush() after the sys.stdout.write() calls.

Upvotes: 58

Related Questions