如何创建旋转的命令行光标?
- 2025-04-17 09:00:00
- admin 原创
- 15
问题描述:
有没有办法使用 Python 在终端中打印旋转光标?
解决方案 1:
类似这样,假设你的终端处理 \b
import sys
import time
def spinning_cursor():
while True:
for cursor in '|/-\\':
yield cursor
spinner = spinning_cursor()
for _ in range(50):
sys.stdout.write(next(spinner))
sys.stdout.flush()
time.sleep(0.1)
sys.stdout.write('')
解决方案 2:
易于使用的 API(这将在单独的线程中运行微调器):
import sys
import time
import threading
class Spinner:
busy = False
delay = 0.1
@staticmethod
def spinning_cursor():
while 1:
for cursor in '|/-\\': yield cursor
def __init__(self, delay=None):
self.spinner_generator = self.spinning_cursor()
if delay and float(delay): self.delay = delay
def spinner_task(self):
while self.busy:
sys.stdout.write(next(self.spinner_generator))
sys.stdout.flush()
time.sleep(self.delay)
sys.stdout.write('')
sys.stdout.flush()
def __enter__(self):
self.busy = True
threading.Thread(target=self.spinner_task).start()
def __exit__(self, exception, value, tb):
self.busy = False
time.sleep(self.delay)
if exception is not None:
return False
现在with
在代码中的任意位置的块中使用它:
with Spinner():
# ... some long-running operations
# time.sleep(3)
解决方案 3:
一个不错的 Python 方式是使用 itertools.cycle:
import itertools, sys
spinner = itertools.cycle(['-', '/', '|', '\\'])
while True:
sys.stdout.write(next(spinner)) # write the next character
sys.stdout.flush() # flush stdout buffer (actual character display)
sys.stdout.write('') # erase the last written char
此外,您可能希望使用线程在长函数调用期间显示微调器,如http://www.interclasse.com/scripts/spin.php
解决方案 4:
为了完整性,我想添加halo这个很棒的软件包。它提供了许多预设的旋转器和更高级别的自定义选项。
摘自他们的自述文件
from halo import Halo
spinner = Halo(text='Loading', spinner='dots')
spinner.start()
# Run time consuming work here
# You can also change properties for spinner as and when you want
spinner.stop()
或者,您可以将 halo 与 Python 的 with 语句一起使用:
from halo import Halo
with Halo(text='Loading', spinner='dots'):
# Run time consuming work here
最后,您可以使用 halo 作为装饰器:
from halo import Halo
@Halo(text='Loading', spinner='dots')
def long_running_function():
# Run time consuming work here
pass
long_running_function()
解决方案 5:
解决方案:
import sys
import time
print "processing...\\\",
syms = ['\\', '|', '/', '-']
bs = ''
for _ in range(10):
for sym in syms:
sys.stdout.write("%s" % sym)
sys.stdout.flush()
time.sleep(.5)
关键是使用退格字符“\b”并刷新标准输出。
解决方案 6:
@Victor Moyseenko 改进的版本,因为原始版本几乎没有问题
旋转完成后,旋转器的角色会离开
有时也会导致删除以下输出的第一个字符
通过在输出上放置 threading.Lock() 来避免罕见的竞争条件
当没有可用的 tty 时(无旋转),返回到更简单的输出
import sys
import threading
import itertools
import time
class Spinner:
def __init__(self, message, delay=0.1):
self.spinner = itertools.cycle(['-', '/', '|', '\\'])
self.delay = delay
self.busy = False
self.spinner_visible = False
sys.stdout.write(message)
def write_next(self):
with self._screen_lock:
if not self.spinner_visible:
sys.stdout.write(next(self.spinner))
self.spinner_visible = True
sys.stdout.flush()
def remove_spinner(self, cleanup=False):
with self._screen_lock:
if self.spinner_visible:
sys.stdout.write('')
self.spinner_visible = False
if cleanup:
sys.stdout.write(' ') # overwrite spinner with blank
sys.stdout.write('
') # move to next line
sys.stdout.flush()
def spinner_task(self):
while self.busy:
self.write_next()
time.sleep(self.delay)
self.remove_spinner()
def __enter__(self):
if sys.stdout.isatty():
self._screen_lock = threading.Lock()
self.busy = True
self.thread = threading.Thread(target=self.spinner_task)
self.thread.start()
def __exit__(self, exception, value, tb):
if sys.stdout.isatty():
self.busy = False
self.remove_spinner(cleanup=True)
else:
sys.stdout.write('
')
上面 Spinner 类的使用示例:
with Spinner("just waiting a bit.. "):
time.sleep(3)
将代码上传至https://github.com/Tagar/stuff/blob/master/spinner.py
解决方案 7:
漂亮、简单、干净……
while True:
for i in '|\\-/':
print('' + i, end='')
解决方案 8:
当然可以。只需在四个字符之间打印一个退格符 ( ),就能让“光标”看起来像在旋转(
-
, `,
|,
/`)。
解决方案 9:
我在 GitHub 上找到了py-spin包。它有很多很棒的旋转样式。以下是一些使用示例,样式Spin1
如下-/
:
from __future__ import print_function
import time
from pyspin.spin import make_spin, Spin1
# Choose a spin style and the words when showing the spin.
@make_spin(Spin1, "Downloading...")
def download_video():
time.sleep(10)
if __name__ == '__main__':
print("I'm going to download a video, and it'll cost much time.")
download_video()
print("Done!")
time.sleep(0.1)
也可以手动控制旋转:
from __future__ import print_function
import sys
import time
from pyspin.spin import Spin1, Spinner
# Choose a spin style.
spin = Spinner(Spin1)
# Spin it now.
for i in range(50):
print(u"
{0}".format(spin.next()), end="")
sys.stdout.flush()
time.sleep(0.1)
其他风格见下面的 gif。
解决方案 10:
抓住这个很棒的progressbar
模块 - http://code.google.com/p/python-progressbar/
使用RotatingMarker
。
解决方案 11:
我构建了一个通用的单例,供整个应用程序共享
from itertools import cycle
import threading
import time
class Spinner:
__default_spinner_symbols_list = ['|-----|', '|#----|', '|-#---|', '|--#--|', '|---#-|', '|----#|']
def __init__(self, spinner_symbols_list: [str] = None):
spinner_symbols_list = spinner_symbols_list if spinner_symbols_list else Spinner.__default_spinner_symbols_list
self.__screen_lock = threading.Event()
self.__spinner = cycle(spinner_symbols_list)
self.__stop_event = False
self.__thread = None
def get_spin(self):
return self.__spinner
def start(self, spinner_message: str):
self.__stop_event = False
time.sleep(0.3)
def run_spinner(message):
while not self.__stop_event:
print("
{message} {spinner}".format(message=message, spinner=next(self.__spinner)), end="")
time.sleep(0.3)
self.__screen_lock.set()
self.__thread = threading.Thread(target=run_spinner, args=(spinner_message,), daemon=True)
self.__thread.start()
def stop(self):
self.__stop_event = True
if self.__screen_lock.is_set():
self.__screen_lock.wait()
self.__screen_lock.clear()
print("
", end="")
print("
", end="")
if __name__ == '__main__':
import time
# Testing
spinner = Spinner()
spinner.start("Downloading")
# Make actions
time.sleep(5) # Simulate a process
#
spinner.stop()
解决方案 12:
你可以用`'
[K'`下面的命令清除当前行。以下是@nos修改的示例。
import sys
import time
def spinning_cursor():
while True:
for cursor in '|/-\\':
yield cursor
spinner = spinning_cursor()
for _ in range(1, 10):
content = f'
{next(spinner)} Downloading...'
print(content, end="")
time.sleep(0.1)
print('
[K', end="")
对于任何对 nodejs 感兴趣的人,我还写了一个 nodejs 示例。
function* makeSpinner(start = 0, end = 100, step = 1) {
let iterationCount = 0;
while (true) {
for (const char of '|/-\\') {
yield char;
}
}
return iterationCount;
}
async function sleep(seconds) {
return new Promise((resolve) => {
setTimeout(resolve, seconds * 1000);
});
}
(async () => {
const spinner = makeSpinner();
for (let i = 0; i < 10; i++) {
content = `
${spinner.next().value} Downloading...`;
process.stdout.write(content);
await sleep(0.1);
process.stdout.write('
[K');
}
})();
解决方案 13:
curses 模块。我想看看 addstr() 和 addch() 函数。不过我从未用过。
解决方案 14:
对于更高级的控制台操作,在 unix 上您可以使用curses python 模块,在 windows 上,您可以使用WConio,它提供与 curses 库等效的功能。
解决方案 15:
#!/usr/bin/env python
import sys
chars = '|/-\\'
for i in xrange(1,1000):
for c in chars:
sys.stdout.write(c)
sys.stdout.write('')
sys.stdout.flush()
注意事项:
根据我的经验,这并非在所有终端上都有效。在 Unix/Linux 下,更稳健的方法是使用curses模块,尽管这种方法更复杂,但在 Windows 下无法使用。你可能需要通过后台实际处理来降低速度。
解决方案 16:
就是这样——简单而清晰。
import sys
import time
idx = 0
cursor = ['|','/','-','\\'] #frames of an animated cursor
while True:
sys.stdout.write(cursor[idx])
sys.stdout.write('')
idx = idx + 1
if idx > 3:
idx = 0
time.sleep(.1)
解决方案 17:
粗鲁但简单的解决方案:
import sys
import time
cursor = ['|','/','-','\\']
for count in range(0,1000):
sys.stdout.write('{}'.format(cursor[count%4]))
sys.stdout.flush()
# replace time.sleep() with some logic
time.sleep(.1)
存在明显的局限性,但同样很粗糙。
解决方案 18:
我提出使用装饰器的解决方案
from itertools import cycle
import functools
import threading
import time
def spinner(message, spinner_symbols: list = None):
spinner_symbols = spinner_symbols or list("|/-\\\")
spinner_symbols = cycle(spinner_symbols)
global spinner_event
spinner_event = True
def start():
global spinner_event
while spinner_event:
symbol = next(spinner_symbols)
print("
{message} {symbol}".format(message=message, symbol=symbol), end="")
time.sleep(0.3)
def stop():
global spinner_event
spinner_event = False
print("
", end="")
def external(fct):
@functools.wraps(fct)
def wrapper(*args):
spinner_thread = threading.Thread(target=start, daemon=True)
spinner_thread.start()
result = fct(*args)
stop()
spinner_thread.join()
return result
return wrapper
return external
简单使用
@spinner("Downloading")
def f():
time.sleep(10)
解决方案 19:
import sys
def DrowWaitCursor(counter):
if counter % 4 == 0:
print("/",end = "")
elif counter % 4 == 1:
print("-",end = "")
elif counter % 4 == 2:
print("\\\",end = "")
elif counter % 4 == 3:
print("|",end = "")
sys.stdout.flush()
sys.stdout.write('')
这也可以是使用带有参数的函数的另一种解决方案。
解决方案 20:
我大约一周前刚开始学习 Python,然后发现了这篇帖子。我结合了这里找到的一些知识以及我在其他地方学到的关于线程和队列的知识,提出了一个我认为更好的实现方案。在我的解决方案中,屏幕写入由一个检查队列内容的线程负责。如果队列中有内容,光标旋转线程就知道该停止。另一方面,光标旋转线程使用队列作为锁,这样打印线程就知道在旋转器代码完整执行之前不会打印。这可以避免竞争条件,以及人们为了保持控制台整洁而使用的大量冗余代码。
见下文:
import threading, queue, itertools, sys, time # all required for my version of spinner
import datetime #not required for spinning cursor solution, only my example
console_queue = queue.Queue() # this queue should be initialized before functions
screenlock = queue.Queue() # this queue too...
def main():
threading.Thread(target=spinner).start()
threading.Thread(target=consoleprint).start()
while True:
# instead of invoking print or stdout.write, we just add items to the console_queue
# The next three lines are an example of my code in practice.
time.sleep(.5) # wait half a second
currenttime = "[" + datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S") + "] "
console_queue.put(currenttime) # The most important part. Substitute your print and stdout functions with this.
def spinner(console_queue = console_queue, screenlock = screenlock):
spinnerlist = itertools.cycle(['|', '/', '-', '\\'])
while True:
if console_queue.empty():
screenlock.put("locked")
sys.stdout.write(next(spinnerlist))
sys.stdout.flush()
sys.stdout.write('')
sys.stdout.flush()
screenlock.get()
time.sleep(.1)
def consoleprint(console_queue = console_queue, screenlock = screenlock):
while True:
if not console_queue.empty():
while screenlock.empty() == False:
time.sleep(.1)
sys.stdout.flush()
print(console_queue.get())
sys.stdout.flush()
if __name__ == "__main__":
main()
说了这么多,也写了这么多,但我才用 Python 做了一个星期。如果有更简洁的方法,或者我错过了一些最佳实践,我很乐意学习。谢谢。
解决方案 21:
非常简单:如果您知道最终指标,那么这也会打印进度和预计到达时间。
from datetime import datetime
import itertools
def progress(title: str, total):
扫码咨询,免费领取项目管理大礼包!