Python, aby wydrukować pasek stanu i procent

160

Aby zaimplementować pasek stanu, jak poniżej:

[==========                ]  45%
[================          ]  60%
[==========================] 100%

Chcę, aby to zostało wydrukowane na standardowe wyjście i ciągle je odświeżać, a nie drukować do innej linii. Jak to zrobić?

Stan
źródło
Opublikowałem nowy rodzaj paska postępu, który możesz wydrukować, zobaczyć przepustowość i eta, a nawet wstrzymać, oprócz bardzo fajnych animacji! Spójrz na: github.com/rsalmei/alive-progress ! żywy postęp
rsalmei

Odpowiedzi:

146

Istnieje moduł Pythona, który można pobrać z PyPI o nazwie, progressbarktóry implementuje taką funkcjonalność. Jeśli nie masz nic przeciwko dodaniu zależności, to jest dobre rozwiązanie. W przeciwnym razie przejdź do jednej z pozostałych odpowiedzi.

Prosty przykład, jak go używać:

import progressbar
from time import sleep
bar = progressbar.ProgressBar(maxval=20, \
    widgets=[progressbar.Bar('=', '[', ']'), ' ', progressbar.Percentage()])
bar.start()
for i in xrange(20):
    bar.update(i+1)
    sleep(0.1)
bar.finish()

Aby go zainstalować, możesz użyć easy_install progressbarlub, pip install progressbarjeśli wolisz, pip.

icktoofay
źródło
Wszystkie odpowiedzi są niesamowite, jednak moduł podoba mi się najbardziej. Dziękuję wszystkim.
Stan
8
Może ten przykład potrzebuje czegoś takiego bar.start()?
Jim Raynor
2
Nie działa na moim komputerze - trzeba dodaćbar.start()
Zach
1
Ten moduł nie był aktualizowany od ponad 2 lat. Nie używaj go do nowego oprogramowania!
Navin,
4
Instalacja: sudo -H pip install progressbar2.
Martin Thoma
254

'\r'Charakter (powrót karetki) resetuje kursor na początku linii i pozwala zapisać ponad to, co było wcześniej na linii.

from time import sleep
import sys

for i in range(21):
    sys.stdout.write('\r')
    # the exact output you're looking for:
    sys.stdout.write("[%-20s] %d%%" % ('='*i, 5*i))
    sys.stdout.flush()
    sleep(0.25)

Nie jestem w 100% pewien, czy jest to całkowicie przenośne we wszystkich systemach, ale działa przynajmniej na Linuksie i OSX.

Mark Rushakoff
źródło
7
Jest to lepsze niż zaznaczona odpowiedź, ponieważ działa również z Pythonem 3.
Gerhard Hagerer
masz jakiś pomysł, dlaczego czasami dostaję dziwne znaki na końcu wiersza, nawet jeśli kończę napisany ciąg znaków z wartością null?
Reservoirman
23
Jak napisano, kod nie wyjaśnia, jak dostosować to do dowolnego rozmiaru, po którym iterujesz (nie 21). Chciałbym pozwolić n=21, wymienić range(21)się range(n), a następnie dodać j = (i + 1) / nwewnątrz pętli i zastąpić writeoświadczenie z tej niewielkiej modyfikacji: sys.stdout.write("[%-20s] %d%%" % ('='*int(20*j), 100*j)). Teraz jedyną zmianą, jaką musisz wprowadzić, jest umieszczenie go n=21przed pętlą (bardziej prawdopodobne n=len(iterable)), a następnie wyliczenie na iterowalnym obiekcie. Poleciłem tę zmianę, ale została odrzucona; pozornie funkcjonalność „odbiega od pierwotnej intencji postu”.
Steven C. Howell
1
Adaptacyjny z dowolnym rozmiarem n sys.stdout.write("[{:{}}] {:.1f}%".format("="*i, n-1, (100/(n-1)*i)))
,,
3
@ Steven C. Jak więc dlaczego nie opublikujesz tego jako odpowiedzi, cytując Marka? jest to inna wersja i bardzo pomocne jest jej przygotowanie
GM
91

Znalazłem przydatną bibliotekę tqdm ( https://github.com/tqdm/tqdm/ , wcześniej: https://github.com/noamraph/tqdm ). Automatycznie szacuje czas ukończenia i może być używany jako iterator.

Stosowanie:

import tqdm
import time

for i in tqdm.tqdm(range(1000)):
    time.sleep(0.01)
    # or other long operations

Prowadzi do:

|####------| 450/1000  45% [elapsed: 00:04 left: 00:05, 99.15 iters/sec]

tqdm może zawijać dowolne iterowalne.

omikron
źródło
4
Projekt tqdm jest teraz obsługiwany przez nowy zespół programistów.
hałaśliwy
1
waw, ten tqdm jest po prostu niesamowity i łatwy w użyciu :).
Sidahmed
6
jest to najprostsze rozwiązanie
kd88
2
jest dostarczany w pakiecie z dystrybucją Anaconda! (co najmniej w przypadku Pythona 3.5 i nowszych)
Jacquot
Dzięki, niesamowite, proste i skuteczne
DavideL
23

Możesz użyć \r( powrót karetki ). Próbny:

import sys
total = 10000000
point = total / 100
increment = total / 20
for i in xrange(total):
    if(i % (5 * point) == 0):
        sys.stdout.write("\r[" + "=" * (i / increment) +  " " * ((total - i)/ increment) + "]" +  str(i / point) + "%")
        sys.stdout.flush()
Matthew Flaschen
źródło
Spróbuj totaljako 10, a otrzymasz komunikat o błędzieZeroDivisionError: long division or modulo by zero
unseen_rider
19

Tutaj możesz użyć następującego kodu jako funkcji:

def drawProgressBar(percent, barLen = 20):
    sys.stdout.write("\r")
    progress = ""
    for i in range(barLen):
        if i < int(barLen * percent):
            progress += "="
        else:
            progress += " "
    sys.stdout.write("[ %s ] %.2f%%" % (progress, percent * 100))
    sys.stdout.flush()

Przy użyciu .format:

def drawProgressBar(percent, barLen = 20):
    # percent float from 0 to 1. 
    sys.stdout.write("\r")
    sys.stdout.write("[{:<{}}] {:.0f}%".format("=" * int(barLen * percent), barLen, percent * 100))
    sys.stdout.flush()
Jacob CUI
źródło
U mnie pierwsza linia nie jest zmieniana przez kursor - tylko druga linia. Stąd wiele wierszy uzyskanych dla paska postępu, a jedna dla np.] percent * 100 %
unseen_rider
6

Bazując na powyższych odpowiedziach i innych podobnych pytaniach dotyczących paska postępu CLI, myślę, że uzyskałem ogólną wspólną odpowiedź na wszystkie z nich. Sprawdź to na https://stackoverflow.com/a/15860757/2254146

Oto kopia funkcji, ale zmodyfikowana w celu dopasowania do Twojego stylu:

import time, sys

# update_progress() : Displays or updates a console progress bar
## Accepts a float between 0 and 1. Any int will be converted to a float.
## A value under 0 represents a 'halt'.
## A value at 1 or bigger represents 100%
def update_progress(progress):
    barLength = 20 # Modify this to change the length of the progress bar
    status = ""
    if isinstance(progress, int):
        progress = float(progress)
    if not isinstance(progress, float):
        progress = 0
        status = "error: progress var must be float\r\n"
    if progress < 0:
        progress = 0
        status = "Halt...\r\n"
    if progress >= 1:
        progress = 1
        status = "Done...\r\n"
    block = int(round(barLength*progress))
    text = "\rPercent: [{0}] {1}% {2}".format( "="*block + " "*(barLength-block), progress*100, status)
    sys.stdout.write(text)
    sys.stdout.flush()

Wygląda jak

Procent: [====================] 99,0%

Brian Khuu
źródło
Pasek postępu nie pojawia się dla mnie
unseen_rider
Myślę, że nie potrzeba tylu miejsc po przecinku, czyli runda (postęp * 100,2)
Andrés Sánchez
4

Jeśli tworzysz interfejs wiersza poleceń, proponuję przyjrzeć się, clickktóry jest bardzo przyjemny:

import click
import time

for filename in range(3):
    with click.progressbar(range(100), fill_char='=', empty_char=' ') as bar:
        for user in bar:
            time.sleep(0.01)

Tutaj otrzymujesz wynik:

$ python test.py
  [====================================]  100%
  [====================================]  100%
  [=========                           ]   27%
nowox
źródło
4

Dalsze ulepszanie, używając funkcji jak:

import sys

def printProgressBar(i,max,postText):
    n_bar =10 #size of progress bar
    j= i/max
    sys.stdout.write('\r')
    sys.stdout.write(f"[{'=' * int(n_bar * j):{n_bar}s}] {int(100 * j)}%  {postText}")
    sys.stdout.flush()

przykład dzwonienia:

total=33
for i in range(total):
    printProgressBar(i,total,"blah")
    sleep(0.05)  

wynik:

[================================================  ] 96%  blah  
STG
źródło
idealny! i żadne moduły nie są potrzebne
yurividal
3

Natknąłem się na ten wątek dzisiaj i po wypróbowaniu tego rozwiązania od Marka Rushakoffa

from time import sleep
import sys

for i in range(21):
sys.stdout.write('\r')
# the exact output you're looking for:
sys.stdout.write("[%-20s] %d%%" % ('='*i, 5*i))
sys.stdout.flush()
sleep(0.25)

Mogę powiedzieć, że działa to dobrze na W7-64 z 64-bitowym Pythonem 3.4.3, ale tylko w natywnej konsoli. Jednak podczas korzystania z wbudowanej konsoli spyder 3.0.0dev podziały wierszy są nadal / ponownie obecne. Ponieważ zrozumienie tego zajęło mi trochę czasu, chciałbym zgłosić tę obserwację tutaj.

Panie Bum
źródło
2

Opierając się na niektórych odpowiedziach tutaj i gdzie indziej, napisałem tę prostą funkcję, która wyświetla pasek postępu i upływający / szacowany pozostały czas. Powinien działać na większości maszyn z systemem UNIX.

import time
import sys

percent = 50.0
start = time.time()
draw_progress_bar(percent, start)


def draw_progress_bar(percent, start, barLen=20):
sys.stdout.write("\r")
progress = ""
for i in range(barLen):
    if i < int(barLen * percent):
        progress += "="
    else:
        progress += " "

elapsedTime = time.time() - start;
estimatedRemaining = int(elapsedTime * (1.0/percent) - elapsedTime)

if (percent == 1.0):
    sys.stdout.write("[ %s ] %.1f%% Elapsed: %im %02is ETA: Done!\n" % 
        (progress, percent * 100, int(elapsedTime)/60, int(elapsedTime)%60))
    sys.stdout.flush()
    return
else:
    sys.stdout.write("[ %s ] %.1f%% Elapsed: %im %02is ETA: %im%02is " % 
        (progress, percent * 100, int(elapsedTime)/60, int(elapsedTime)%60,
         estimatedRemaining/60, estimatedRemaining%60))
    sys.stdout.flush()
    return
Sam Jordan
źródło
2

Jest to dość proste podejście, które można zastosować w przypadku dowolnej pętli.

#!/usr/bin/python
for i in range(100001):
    s =  ((i/5000)*'#')+str(i)+(' %')
    print ('\r'+s),
NILESH KUMAR
źródło
Co robi „#”?
unseen_rider
@unseen_rider Tutaj „#” to tylko symbol, który będzie powtarzany wraz ze wzrostem iteracji pętli.
NILESH KUMAR
2

Najłatwiej jest nadal

import sys
total_records = 1000
for i in range (total_records):
    sys.stdout.write('\rUpdated record: ' + str(i) + ' of ' + str(total_records))
    sys.stdout.flush()

Kluczem jest konwersja typu liczby całkowitej na łańcuch.

bfree67
źródło
2

Jak opisano w rozwiązaniu Marka Rushakoffa , można wyprowadzić znak powrotu karetki sys.stdout.write('\r'), aby zresetować kursor do początku wiersza. Aby uogólnić to rozwiązanie, jednocześnie implementując f-Strings w Pythonie 3 , możesz użyć

from time import sleep
import sys

n_bar = 50
iterable = range(33)  # for demo purposes
n_iter = len(iterable)
for i, item in enumerate(iterable):
    j = (i + 1) / n_iter

    sys.stdout.write('\r')
    sys.stdout.write(f"[{'=' * int(n_bar * j):{n_bar}s}] {int(100 * j)}%")
    sys.stdout.flush()

    sleep(0.05)  
    # do something with <item> here
Steven C. Howell
źródło
1

Wypróbuj PyProg. PyProg to biblioteka open source dla Pythona do tworzenia super konfigurowalnych wskaźników postępu i pasków.

Obecnie jest w wersji 1.0.2; jest hostowany na Github i dostępny na PyPI (linki poniżej). Jest kompatybilny z Pythonem 3 i 2 i może być również używany z Qt Console.

Jest naprawdę łatwy w użyciu. Poniższy kod:

import pyprog
from time import sleep

# Create Object
prog = pyprog.ProgressBar(" ", " ", total=34, bar_length=26, complete_symbol="=", not_complete_symbol=" ", wrap_bar_prefix=" [", wrap_bar_suffix="] ", progress_explain="", progress_loc=pyprog.ProgressBar.PROGRESS_LOC_END)
# Update Progress Bar
prog.update()

for i in range(34):
    # Do something
    sleep(0.1)
    # Set current status
    prog.set_stat(i + 1)
    # Update Progress Bar again
    prog.update()

# Make the Progress Bar final
prog.end()

wyprodukuje dokładnie to, czego chcesz (nawet długość paska!):

[===========               ] 45%
[===============           ] 60%
[==========================] 100%

Aby uzyskać więcej opcji dostosowywania paska postępu, przejdź do strony Github w tej witrynie.

Zrobiłem PyProg, ponieważ potrzebowałem prostej, ale super konfigurowalnej biblioteki paska postępu. Można łatwo zainstalować go z: pip install pyprog.

PyProg Github: https://github.com/Bill13579/pyprog
PyPI: https://pypi.python.org/pypi/pyprog/

Bill Kudo
źródło
1

Używając odpowiedzi @ Mark-Rushakoff, wypracowałem prostsze podejście, bez konieczności dzwonienia do biblioteki sys. Działa z Pythonem 3. Przetestowano w systemie Windows:

from time import sleep
for i in range(21):
    # the exact output you're looking for:
    print ("\r[%-20s] %d%%" % ('='*i, 5*i), end='')
    sleep(0.25)
Stein
źródło
Jak opisano w odpowiedzi na inne pytanie, print jest cienka otoka, która formatuje dane wejściowe i wywołuje funkcję zapisu danego obiektu. Domyślnie tym obiektem jest sys.stdout.
Steven C. Howell
0

Oto coś, co zrobiłem za pomocą rozwiązania autorstwa @ Mark-Rushakoff. Adaptacyjne dopasowanie do szerokości terminala.

from time import sleep
import os
import sys
from math import ceil

l = list(map(int,os.popen('stty size','r').read().split()))
col = l[1]
col = col - 6

for i in range(col):
    sys.stdout.write('\r')
    getStr = "[%s " % ('='*i)
    sys.stdout.write(getStr.ljust(col)+"]"+"%d%%" % (ceil((100/col)*i)))
    sys.stdout.flush()
    sleep(0.25)
print("")
swarnava112
źródło