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.
'Waiting' animation in command prompt (Python) - Stack Overflow
Python terminal animation - Code Review Stack Exchange
[2022 Day 5 #1] Small terminal Python animation for part 1 of Day 5, never tried "drawing" on terminal before but quite proud of the result ! (didn't put the whole thing because it's a bit long and we get the idea)
This is great - you don't even need to struggle to parse the input, just wrap a crane around it!
More on reddit.comI made a a tool for creating animations in the terminal with python!
Videos
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.
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")
ยป pip install animation
Use \r and print-without-newline (that is, suffix with a comma):
animation = "|/-\\"
idx = 0
while thing_not_complete():
print(animation[idx % len(animation)], end="\r")
idx += 1
time.sleep(0.1)
For Python 2, use this print syntax:
print animation[idx % len(animation)] + "\r",
Just another pretty variant
import time
bar = [
" [= ]",
" [ = ]",
" [ = ]",
" [ = ]",
" [ = ]",
" [ =]",
" [ = ]",
" [ = ]",
" [ = ]",
" [ = ]",
]
i = 0
while True:
print(bar[i % len(bar)], end="\r")
time.sleep(.2)
i += 1
ยป pip install asciimatics
You can build up the string like this:
def animation(counter, length):
stage = counter % (length * 2 + 2)
if stage < length + 1:
left_spaces = stage
else:
left_spaces = length * 2 - 1 - stage
return '[' + ' ' * left_spaces + '=' + ' ' * (length - left_spaces) + ']'
for i in range(100):
sys.stdout.write('\b\b\b')
sys.stdout.write(animation(i, 6))
sys.stdout.flush()
time.sleep(0.2)
Alternatively store the animation strings in a tuple or list:
animation_strings = ('[= ]', '[ = ]', '[ = ]', '[ = ]',
'[ = ]', '[ = ]', '[ =]', '[ =]',
'[ = ]', '[ = ]', '[ = ]', '[ = ]',
'[ = ]', '[= ]')
for i in range(100):
sys.stdout.write('\b\b\b')
sys.stdout.write(animation_strings[i % len(animation_strings)])
sys.stdout.flush()
time.sleep(0.2)
You can replace the animation function with cycle from itertools:
import sys, time
from itertools import cycle
animation = cycle('[= ]', '[ = ]', '[ = ]', '[ = ]',
'[ = ]', '[ = ]', '[ =]', '[ =]',
'[ = ]', '[ = ]', '[ = ]', '[ = ]',
'[ = ]', '[= ]')
# alternatively:
# animation = cycle('[' + ' ' * n + '=' + ' ' * (6 - n) + ']'
# for n in range(7) + range(6, -1, -1))
for _ in range(100):
sys.stdout.write('\b\b\b')
sys.stdout.write(animation.next())
sys.stdout.flush()
time.sleep(0.2)
Finally, you could make your own generator function.
def animation_generator(length):
while True:
for n in range(length + 1):
yield '[' + ' ' * n + '=' + ' ' * (length - n) + ']'
for n in range(length + 1):
yield '[' + ' ' * (length - n) + '=' + ' ' * n + ']'
animation = animation_generator(6)
for _ in range(100):
sys.stdout.write('\b\b\b')
sys.stdout.write(animation.next())
sys.stdout.flush()
time.sleep(0.2)
EDIT: made the above suggestions less reliant on global variables
Notes:
- Don't use global variables without a good (an extremely good) justification. A function is (should be) a black box that gets values and returns values (unless you have unavoidable side-effects to perform, for example reading a file or printing to the screen).
- It's very cumbersome and inflexible to write every possible string of the progressbar by hand, use code instead to build them.
I'd write:
import sys
import time
def render(size, position):
return "[" + (" " * position) + "=" + (" " * (size - position - 1)) + "]"
def draw(size, iterations, channel=sys.stdout, waittime=0.2):
for index in range(iterations):
n = index % (size*2)
position = (n if n < size else size*2 - n - 1)
bar = render(size, position)
channel.write(bar + '\r')
channel.flush()
time.sleep(waittime)
if __name__ == '__main__':
draw(6, 100, channel=sys.stdout)
This is a little project I've been working on for a bit now. It's not super advanced, but it's way more complicated than anything I had done before and I'm really happy with it.
Anteater (not sure why I decided to name it that but oh well) is written 100% in python with only core modules. It uses curses to interact with the terminal while inside the program, and it uses ANSI escape codes to print in the rendered version of the animation.
Currently it supports:
-
selecting
-
drawing
-
filling
-
changing color
-
changing the character used to draw
-
erasing
-
copying & pasting
-
exporting
-
saving and importing
Github: https://github.com/BrewingWeasel/anteater