Jak nadpisać poprzedni wydruk na stdout w Pythonie?

113

Gdybym miał następujący kod:

for x in range(10):
     print x

Otrzymałbym wyjście

1
2
etc..

Chciałbym zamiast drukować znak nowej linii, chcę zastąpić poprzednią wartość i nadpisać ją nową wartością w tym samym wierszu.

ccwhite1
źródło
możliwy duplikat Pythona - Usuń i zamień wydrukowane elementy
Sven Marnach
Na marginesie, upewnij się, że otrzymałeś odpowiedź, która może opróżnić całą linię, jeśli poprzednia linia jest dłuższa niż bieżąca.
Owoce

Odpowiedzi:

119

Jednym ze sposobów jest użycie znaku powrotu karetki ( '\r'), aby powrócić na początek wiersza bez przechodzenia do następnego wiersza:

for x in range(10):
    print '{0}\r'.format(x),
print

Przecinek na końcu instrukcji print mówi mu, aby nie przechodził do następnego wiersza. Ostatnia instrukcja print przechodzi do następnego wiersza, więc znak zachęty nie nadpisze końcowego wyniku.

Aktualizacja

Teraz, gdy Python 2 to EOL, odpowiedź w Pythonie 3 ma większy sens. Dla Pythona 3.5 i wcześniejszych:

for x in range(10):
    print('{}\r'.format(x), end="")
print()

W Pythonie 3.6 i nowszych f-stringi czytają lepiej:

for x in range(10):
    print(f'{x}\r', end="")
print()
Mike DeSimone
źródło
To działa, ale nie rozumiem kilku części ... „{0}” i .format
ccwhite1
7
Zostało to wprowadzone w Pythonie 2.6 i (jak mi powiedziano) jest standardowym sposobem formatowania łańcuchów w Pythonie 3; %operator jest zalecane. Zasadniczo wszystkie ciągi mają teraz metodę formatowania . W tym przypadku {0}oznacza „pierwszy argument do format()” (liczenie zaczyna się od 0).
Mike DeSimone
Nie jest to opcja, jeśli musisz używać oprogramowania tylko dla systemu Windows i nie masz wystarczających umiejętności, aby uruchomić maszynę wirtualną.
Mike DeSimone
2
A jeśli chcesz być między terminalami, re.sub(r'\$<\d+>[/*]?', '', curses.tigetstr('el') or '')poda ci poprawny ciąg wymazywania do końca linii dla dowolnego terminala, który ma użytkownik. Pamiętaj, aby zainicjować cursescoś podobnego curses.setupterm(fd=sys.stdout.fileno())i użyć, sys.stdout.isatty()aby upewnić się, że dane wyjściowe nie są przekierowywane do pliku. Zobacz code.activestate.com/recipes/475116, aby zapoznać się z pełnym modułem Pythona z kontrolą kursora i obsługą kolorów.
Mike DeSimone
1
@PhilMacKay: Próbowałem tego w Pythonie na moim Macu: W [3]: print "Rzecz do usunięcia \ r",; time.sleep (1); print "-------------- \ r", -------------- Myślę, że twój problem dotyczy systemu Windows i jego różnych linii się kończy. Spróbuj tego: import curses; curses.setupterm(fd=sys.stdout.fileno()); print(hex(curses.tigetstr('cr')));Powinien wydrukować kody szesnastkowe sekwencji znaków, aby przejść na początek wiersza.
Mike DeSimone
107

Ponieważ znalazłem się tutaj przez Google, ale używam Pythona 3, oto jak to działałoby w Pythonie 3:

for x in range(10):
    print("Progress {:2.1%}".format(x / 10), end="\r")

Powiązana odpowiedź tutaj: Jak mogę pominąć znak nowej linii po instrukcji print?

Pascal
źródło
3
Próbowałem tego i w większości działa dobrze. Jedynym problemem jest to, że nie usuwa wcześniejszego wyjścia w tej linii, na przykład: jeśli twoja pierwsza pętla drukuje testingi druga pętla wydrukuje testwynik po drugim przebiegu nadal będzie testing- teraz widzę, że @ Nagasaki45 zwrócił na to uwagę
Eric Uldall
15
w notatniku ipython muszę zrobić:print("\r", message, end="")
grisaitis
1
To działa świetnie, nie usuwa linii, ale użycie wystarczającej ilości białych znaków w następującym wydruku załatwia sprawę (chociaż nie jest eleganckie). Jeden komentarz, który mógłbym dodać, to to, że nie działa to w konsoli debugowania VS Code, ale działa w terminalu.
PhilMacKay,
32

@Mike DeSimone odpowiedź prawdopodobnie będzie działać przez większość czasu. Ale...

for x in ['abc', 1]:
    print '{}\r'.format(x),

-> 1bc

Dzieje się tak, ponieważ '\r'jedyny wraca na początek wiersza, ale nie czyści wyjścia.

EDYCJA: Lepsze rozwiązanie (niż moja stara propozycja poniżej)

Jeśli obsługa POSIX jest dla Ciebie wystarczająca, poniższe czynności wyczyściłyby bieżący wiersz i pozostawił kursor na jego początku:

print '\x1b[2K\r',

Używa kodu ucieczki ANSI, aby wyczyścić linię terminala. Więcej informacji można znaleźć na Wikipedii oraz w tym wspaniałym wykładzie .

Stara odpowiedź

(Niezbyt dobre) rozwiązanie, które znalazłem, wygląda następująco:

last_x = ''
for x in ['abc', 1]:
    print ' ' * len(str(last_x)) + '\r',
    print '{}\r'.format(x),
    last_x = x

-> 1

Zaletą jest to, że będzie działać również w systemie Windows.

Nagasaki45
źródło
1
Zrobiłem funkcję ze
erb
25

Miałem to samo pytanie przed odwiedzeniem tego wątku. U mnie sys.stdout.write działał tylko wtedy, gdy prawidłowo opróżnię bufor tj

for x in range(10):
    sys.stdout.write('\r'+str(x))
    sys.stdout.flush()

Bez opróżniania wynik jest drukowany tylko na końcu skryptu

user2793078
źródło
Dobrym pomysłem jest również wypełnienie reszty łańcucha spacjami, jak stringvar.ljust(10,' ')w przypadku łańcuchów o zmiennej długości.
Annarfych
@chris, Don i in. to jest Python 2, większość pozostałych odpowiedzi to Python 3. Każdy powinien teraz używać Pythona 3; Python 2
dobiega
18

Pomiń znak nowej linii i drukuj \r.

print 1,
print '\r2'

lub napisz na stdout:

sys.stdout.write('1')
sys.stdout.write('\r2')
Ignacio Vazquez-Abrams
źródło
9

Spróbuj tego:

import time
while True:
    print("Hi ", end="\r")
    time.sleep(1)
    print("Bob", end="\r")
    time.sleep(1)

U mnie to zadziałało. end="\r"Część czyni go nadpisać poprzednią linię.

OSTRZEŻENIE!

Jeśli wydrukujesz hi, a następnie wydrukujesz za hellopomocą \r, otrzymasz, hilloponieważ wynik został napisany na poprzednich dwóch literach. Jeśli wydrukujesz hize spacjami (które nie pojawiają się tutaj), to wyjdzie hi. Aby to naprawić, wydrukuj spacje za pomocą \r.

Sam Bernstein
źródło
1
To nie działa na mnie. Skopiowałem ten kod, ale wynik jest Hi BobHi BobHi BobHi BobHi Bob. Python 3.7.1
PaulMag
1
Ale odpowiedź Rubixreda zadziałała. Dziwne. Dla mnie wyglądają na tę samą metodę. Jedyną różnicą, jaką widzę, jest to, że \rjest na początku zamiast na końcu.
PaulMag
7

Nie mogłem uzyskać żadnego z rozwiązań na tej stronie do pracy z IPythonem , ale niewielka zmiana w rozwiązaniu @ Mike-Desimone załatwiła sprawę: zamiast kończyć wiersz znakiem powrotu karetki, rozpocznij wiersz od powrotu karetki:

for x in range(10):
    print '\r{0}'.format(x),

Ponadto to podejście nie wymaga drugiej instrukcji print.

David Marx
źródło
Nawet to nie działa w konsoli PyDev, ani przecinek po nawiasie zamykającym print ().
Noumenon,
nie zadziałało dla mnie, po prostu zapisuje wynik końcowy (np. „Postęp pobierania 100%”) po zakończeniu procesu.
Alexandre
2
@Alexandre Działa dobrze w IPythonie lub jego następcy Jupyter. Musisz opróżnić bufor. stackoverflow.com/questions/17343688/…
marzec Ho
7
for x in range(10):
    time.sleep(0.5) # shows how its working
    print("\r {}".format(x), end="")

time.sleep (0.5) pokazuje, jak poprzedni wynik jest kasowany, a nowy wydruk jest drukowany "\ r". Kiedy jest na początku komunikatu drukowania, usunie poprzednie wyjście przed nowym wyjściem.

Rubixred
źródło
7

Działa to w systemie Windows i Pythonie 3.6

import time
for x in range(10):
    time.sleep(0.5)
    print(str(x)+'\r',end='')
Siddhant Sadangi
źródło
5

Przyjęta odpowiedź nie jest doskonała . Linia, która została wydrukowana jako pierwsza, pozostanie tam, a jeśli twój drugi wydruk nie pokryje całej nowej linii, skończysz ze śmieciowym tekstem.

Aby zilustrować problem, zapisz ten kod jako skrypt i uruchom go (lub po prostu spójrz):

import time

n = 100
for i in range(100):
    for j in range(100):
        print("Progress {:2.1%}".format(j / 100), end="\r")
        time.sleep(0.01)
    print("Progress {:2.1%}".format(i / 100))

Wynik będzie wyglądał mniej więcej tak:

Progress 0.0%%
Progress 1.0%%
Progress 2.0%%
Progress 3.0%%

To, co działa dla mnie, to wyczyszczenie linii przed pozostawieniem trwałego wydruku. Zapraszam do dostosowania się do konkretnego problemu:

import time

ERASE_LINE = '\x1b[2K' # erase line command
n = 100
for i in range(100):
    for j in range(100):
        print("Progress {:2.1%}".format(j / 100), end="\r")
        time.sleep(0.01)
    print(ERASE_LINE + "Progress {:2.1%}".format(i / 100)) # clear the line first

A teraz drukuje zgodnie z oczekiwaniami:

Progress 0.0%
Progress 1.0%
Progress 2.0%
Progress 3.0%
mimoralea
źródło
5

Oto bardziej przejrzysta, bardziej „plug-and-play” wersja odpowiedzi @ Nagasaki45. W przeciwieństwie do wielu innych odpowiedzi tutaj, działa poprawnie ze sznurkami o różnych długościach. Osiąga to poprzez wyczyszczenie wiersza z taką samą liczbą spacji, jak długość ostatniego wydrukowanego wiersza. Działa również w systemie Windows.

def print_statusline(msg: str):
    last_msg_length = len(print_statusline.last_msg) if hasattr(print_statusline, 'last_msg') else 0
    print(' ' * last_msg_length, end='\r')
    print(msg, end='\r')
    sys.stdout.flush()  # Some say they needed this, I didn't.
    print_statusline.last_msg = msg

Stosowanie

Po prostu użyj tego w ten sposób:

for msg in ["Initializing...", "Initialization successful!"]:
    print_statusline(msg)
    time.sleep(1)

Ten mały test pokazuje, że linie są usuwane prawidłowo, nawet przy różnych długościach:

for i in range(9, 0, -1):
    print_statusline("{}".format(i) * i)
    time.sleep(0.5)
erb
źródło
5
To świetne rozwiązanie, ponieważ elegancko czyści poprzedni wydruk do pełnej długości bez drukowania dowolnej liczby białych spacji (do czego wcześniej się uciekłem!). Dzięki
Toby Petty
2

Jestem trochę zaskoczony, że nikt nie używa znaku backspace. Oto jeden, który go używa.

import sys
import time

secs = 1000

while True:
    time.sleep(1)  #wait for a full second to pass before assigning a second
    secs += 1  #acknowledge a second has passed

    sys.stdout.write(str(secs))

    for i in range(len(str(secs))):
        sys.stdout.write('\b')
David Philipe Gil
źródło
upewnij się, że spłukujesz po użyciu tej metody, w przeciwnym razie nic nie zostanie wydrukowane na ekranie. sys.stdout.flush()
Gabriel Fair
2

(Python3) To właśnie zadziałało dla mnie. Jeśli użyjesz tylko \ 010, to pozostawi znaki, więc poprawiłem go trochę, aby upewnić się, że nadpisuje to, co tam było. Pozwala to również mieć coś przed pierwszym drukowanym elementem i usunąć tylko długość elementu.

print("Here are some strings: ", end="")
items = ["abcd", "abcdef", "defqrs", "lmnop", "xyz"]
for item in items:
    print(item, end="")
    for i in range(len(item)): # only moving back the length of the item
        print("\010 \010", end="") # the trick!
        time.sleep(0.2) # so you can see what it's doing
RenegadeJr
źródło
2

Jeszcze jedna odpowiedź oparta na wcześniejszych odpowiedziach.

Zawartość pbar.py: import sys, shutil, datetime

last_line_is_progress_bar=False


def print2(print_string):
    global last_line_is_progress_bar
    if last_line_is_progress_bar:
        _delete_last_line()
        last_line_is_progress_bar=False
    print(print_string)


def _delete_last_line():
    sys.stdout.write('\b\b\r')
    sys.stdout.write(' '*shutil.get_terminal_size((80, 20)).columns)
    sys.stdout.write('\b\r')
    sys.stdout.flush()


def update_progress_bar(current, total):
    global last_line_is_progress_bar
    last_line_is_progress_bar=True

    completed_percentage = round(current / (total / 100))
    current_time=datetime.datetime.now().strftime('%m/%d/%Y-%H:%M:%S')
    overhead_length = len(current_time+str(current))+13
    console_width = shutil.get_terminal_size((80, 20)).columns - overhead_length
    completed_width = round(console_width * completed_percentage / 100)
    not_completed_width = console_width - completed_width
    sys.stdout.write('\b\b\r')

    sys.stdout.write('{}> [{}{}] {} - {}% '.format(current_time, '#'*completed_width, '-'*not_completed_width, current,
                                        completed_percentage),)
    sys.stdout.flush()

Wykorzystanie skryptu:

import time
from pbar import update_progress_bar, print2


update_progress_bar(45,200)
time.sleep(1)

update_progress_bar(70,200)
time.sleep(1)

update_progress_bar(100,200)
time.sleep(1)


update_progress_bar(130,200)
time.sleep(1)

print2('some text that will re-place current progress bar')
time.sleep(1)

update_progress_bar(111,200)
time.sleep(1)

print('\n') # without \n next line will be attached to the end of the progress bar
print('built in print function that will push progress bar one line up')
time.sleep(1)

update_progress_bar(111,200)
time.sleep(1)
Krisz
źródło
1

Oto moje rozwiązanie! Windows 10, Python 3.7.1

Nie jestem pewien, dlaczego ten kod działa, ale całkowicie usuwa oryginalny wiersz. Skompilowałem to na podstawie poprzednich odpowiedzi. Inne odpowiedzi po prostu przywróciłyby linię na początek, ale gdybyś miał później krótszą linię, wyglądałaby na pomieszaną, jakby hellozamieniła się w byelo.

import sys
#include ctypes if you're on Windows
import ctypes
kernel32 = ctypes.windll.kernel32
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
#end ctypes

def clearline(msg):
    CURSOR_UP_ONE = '\033[K'
    ERASE_LINE = '\x1b[2K'
    sys.stdout.write(CURSOR_UP_ONE)
    sys.stdout.write(ERASE_LINE+'\r')
    print(msg, end='\r')

#example
ig_usernames = ['beyonce','selenagomez']
for name in ig_usernames:
    clearline("SCRAPING COMPLETE: "+ name)

Wyjście - każda linia zostanie przepisana bez żadnego starego tekstu pokazującego:

SCRAPING COMPLETE: selenagomez

Następna linia (przepisana całkowicie w tej samej linii):

SCRAPING COMPLETE: beyonce
kpossibles
źródło
0

Lepiej jest nadpisać całą linię, w przeciwnym razie nowa linia będzie mieszać się ze starymi, jeśli nowa linia będzie krótsza.

import time, os
for s in ['overwrite!', 'the!', 'whole!', 'line!']:
    print(s.ljust(os.get_terminal_size().columns - 1), end="\r")
    time.sleep(1)

Musiał użyć columns - 1w systemie Windows.

Carl Chang
źródło