Reputation: 679
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
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
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 sys, time, threading
# 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')
def animated_loading():
chars = "/—\|"
for char in chars:
sys.stdout.write('\r'+'loading...'+char)
time.sleep(.1)
sys.stdout.flush()
the_process = threading.Thread(name='process', target=the_process_function)
the_process.start()
animated_loading()
functionwhile the_process.isAlive():
animated_loading()
The main steps are outlined in the commented out line.
Upvotes: 31
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
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
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
Reputation: 14744
Getting inspiration from the accepted answer, here's a useful class I wrote, printing a loader à la nodejs cli:
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
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
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
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
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
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