Как создать вращающийся курсор командной строки?

Есть ли способ напечатать вращающийся курсор в терминале, используя Python?

24 ответа

Решение

Примерно так, при условии, что ваш терминал обрабатывает \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('\b')

Простой в использовании API (он запускает spinner в отдельном потоке):

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('\b')
            sys.stdout.flush()

    def start(self):
        self.busy = True
        threading.Thread(target=self.spinner_task).start()

    def stop(self):
        self.busy = False
        time.sleep(self.delay)

В любом месте кода, где это необходимо:

spinner = Spinner()
spinner.start()
# ... some long-running operations
# time.sleep(3) 
spinner.stop()

Хороший питонский способ - использовать itertools.cycle:

import itertools, sys
spinner = itertools.cycle(['-', '/', '|', '\\'])
while True:
    sys.stdout.write(spinner.next())  # write the next character
    sys.stdout.flush()                # flush stdout buffer (actual character display)
    sys.stdout.write('\b')            # erase the last written char

Кроме того, вы можете использовать многопоточность для отображения счетчика во время длинного вызова функции, как в http://www.interclasse.com/scripts/spin.php

пример

Для полноты картины хочу добавить отличный пакетный ореол. Он предлагает множество предустановленных спиннеров и параметры настройки более высокого уровня.

Выписка из их ридми

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()

Решение:

import sys
import time

print "processing...\\",
syms = ['\\', '|', '/', '-']
bs = '\b'

for _ in range(10):
    for sym in syms:
        sys.stdout.write("\b%s" % sym)
        sys.stdout.flush()
        time.sleep(.5)

Ключ должен использовать символ возврата \ 'b' и сбросить стандартный вывод.

Улучшенная версия от @Victor Moyseenko, поскольку в исходной версии было мало проблем

  1. покидал персонажей прядильщика после завершения вращения
  2. и иногда приводит к удалению первого символа следующего вывода
  3. позволяет избежать редкого состояния гонки, помещая threading.Lock() в вывод
  4. возвращается к более простому выводу, когда 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('\b')
                self.spinner_visible = False
                if cleanup:
                    sys.stdout.write(' ')       # overwrite spinner with blank
                    sys.stdout.write('\r')      # 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('\r')

пример использования класса Spinner выше:


with Spinner("just waiting a bit.. "):

        time.sleep(3)

загрузил код на https://github.com/Tagar/stuff/blob/master/spinner.py

Красиво, просто и чисто...

while True:
    for i in '|\\-/':
        print('\b' + i, end='')

Я нашел пакет py-spin на GitHub. У него много приятных стилей вращения. Вот несколько примеров того, как использовать,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"\r{0}".format(spin.next()), end="")
    sys.stdout.flush()
    time.sleep(0.1)

Другие стили в гифке ниже.

https://i.stack.imgur.com/zBraY.gif

Конечно, это возможно. Это просто вопрос печати символа возврата (\b) между четырьмя символами, что бы "курсор" выглядел так, как будто он вращается (-, \, |, /).

Хватай удивительный progressbar модуль - http://code.google.com/p/python-progressbar/ use RotatingMarker,

Ты можешь написать '\r\033[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'\r{next(spinner)} Downloading...'
    print(content, end="")
    time.sleep(0.1)
    print('\r\033[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 = `\r${spinner.next().value} Downloading...`;
    process.stdout.write(content);
    await sleep(0.1);
    process.stdout.write('\r\033[K');
  }
})();

Я создал общий синглтон, общий для всего приложения

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("\r{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("\r", end="")

        print("\r", end="")

if __name__ == '__main__':
    import time
    # Testing
    spinner = Spinner()
    spinner.start("Downloading")
    # Make actions
    time.sleep(5) # Simulate a process
    #
    spinner.stop()

модуль проклятий. Я бы взглянул на функции addstr() и addch(). Никогда не использовал это все же.

Для более сложных манипуляций с консолью в Unix вы можете использовать модуль Python curses, а в Windows вы можете использовать WConio, который обеспечивает эквивалентную функциональность библиотеки curses.

#!/usr/bin/env python

import sys

chars = '|/-\\'

for i in xrange(1,1000):
    for c in chars:
        sys.stdout.write(c)
        sys.stdout.write('\b')
        sys.stdout.flush()

ПРЕДУПРЕЖДЕНИЯ. По моему опыту, это работает не во всех терминалах. Более надежный способ сделать это в Unix/Linux, будь он более сложным, - это использовать модуль curses, который не работает под Windows. Возможно, вы захотите немного замедлить его, как с реальной обработкой, которая происходит в фоновом режиме.

Предлагаю решение с помощью декораторов

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("\r{message} {symbol}".format(message=message, symbol=symbol), end="")
            time.sleep(0.3)

    def stop():
        global spinner_event
        spinner_event = False
        print("\r", 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)

Я только начал с 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('\b')
            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()

Сказав все, что я сказал, и написал все, что написал, я занимаюсь питоном всего неделю. Если есть более чистые способы сделать это или я пропустил некоторые передовые практики, я хотел бы изучить. Спасибо.

Вот и я - просто и понятно.

import sys
import time

idx = 0
cursor = ['|','/','-','\\'] #frames of an animated cursor

while True:
    sys.stdout.write(cursor[idx])
    sys.stdout.write('\b')
    idx = idx + 1

    if idx > 3:
        idx = 0

    time.sleep(.1)

Грубое, но простое решение:

import sys
import time
cursor = ['|','/','-','\\']
for count in range(0,1000):
  sys.stdout.write('\b{}'.format(cursor[count%4]))
  sys.stdout.flush()
  # replace time.sleep() with some logic
  time.sleep(.1)

Есть очевидные ограничения, но опять же, грубые.

import sys
def DrowWaitCursor(self, 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('\b') 

Это может быть также другое решение, использующее функцию с параметром.

Что-то очень простое: если вы знаете окончательную метрику, то она также напечатает прогресс и время.

      from datetime import datetime
import itertools

def progress(title: str, total):
    moon = itertools.cycle(["", "", "", "", "", "", "", ""])
    start_time = datetime.now()

def show(current):
    curr_time = datetime.now()
    time_taken = curr_time - start_time
    print("  ", next(moon), title, current, "/", total,
          " progress: ", round((100.0 * current) / total, 2), "%",
          " eta: ", (time_taken * (total - current)) / current, end="\r ")

    return show
import requests
import time
import sys

weathercity = input("What city are you in? ")
weather = requests.get('http://api.openweathermap.org/data/2.5/weather?q='+weathercity+'&appid=886705b4c1182eb1c69f28eb8c520e20')
url = ('http://api.openweathermap.org/data/2.5/weather?q='+weathercity+'&appid=886705b4c1182eb1c69f28eb8c520e20')




def spinning_cursor():
    while True:
        for cursor in '|/-\\':
            yield cursor


data = weather.json()

temp = data['main']['temp']
description = data['weather'][0]['description']
weatherprint ="In {}, it is currently {}°C with {}."
spinner = spinning_cursor()
for _ in range(25):
    sys.stdout.write(next(spinner))
    sys.stdout.flush()
    time.sleep(0.1)
    sys.stdout.write('\b')

convert = int(temp - 273.15)
print(weatherprint.format(weathercity, convert, description))

Здравствуйте, вот простейший счетчик загрузки для Python.

Время импорта

Spin=["загрузка ......", "|", "/","-", "\"]

Для i в Spin:Print("\b"+i, end= "")Time.sleep (0.2)

Выход: загрузка ...... |

Другие вопросы по тегам