แทนที่คอนโซลเอาต์พุตใน Python


106

ฉันสงสัยว่าฉันจะสร้างตัวนับคอนโซลที่ดีใน Python ได้อย่างไรในโปรแกรม C / C ++ - บางโปรแกรม

ฉันมีการวนซ้ำในการทำสิ่งต่างๆและผลลัพธ์ปัจจุบันอยู่ในแนวของ:

Doing thing 0
Doing thing 1
Doing thing 2
...

สิ่งที่น่าจะเป็นเพียงแค่การอัปเดตบรรทัดสุดท้าย

X things done.

ฉันเคยเห็นสิ่งนี้ในโปรแกรมคอนโซลหลายรายการและฉันสงสัยว่าฉันจะทำสิ่งนี้ใน Python ได้อย่างไร


3
คุณควรจะดูที่คำสาปแช่ง
Björn Pollex

2
เพียงใช้print: stackoverflow.com/a/8436827/1959808
Ioannis Filippidis

1
@ BjörnPollex cursesเป็น overkill (ดูคำตอบที่ยอมรับ)
Alexey

: ที่เกี่ยวข้องแถบความคืบหน้าข้อความในคอนโซล
Alexey

คำตอบ:


151

วิธีแก้ปัญหาง่ายๆคือเขียน"\r"ก่อนสตริงและไม่ต้องขึ้นบรรทัดใหม่ ถ้าสตริงไม่สั้นลงก็เพียงพอแล้ว ...

sys.stdout.write("\rDoing thing %i" % i)
sys.stdout.flush()

ที่ซับซ้อนกว่าเล็กน้อยคือแถบความคืบหน้า ... นี่คือสิ่งที่ฉันใช้:

def startProgress(title):
    global progress_x
    sys.stdout.write(title + ": [" + "-"*40 + "]" + chr(8)*41)
    sys.stdout.flush()
    progress_x = 0

def progress(x):
    global progress_x
    x = int(x * 40 // 100)
    sys.stdout.write("#" * (x - progress_x))
    sys.stdout.flush()
    progress_x = x

def endProgress():
    sys.stdout.write("#" * (40 - progress_x) + "]\n")
    sys.stdout.flush()

คุณเรียกว่าstartProgressผ่านคำอธิบายของการดำเนินการแล้วเปอร์เซ็นต์อยู่progress(x)ที่ไหนxและสุดท้ายendProgress()


2
จะเกิดอะไรขึ้นถ้าสตริงสั้นกว่าสตริงก่อนหน้า?
math2001

6
@ math2001 padding ด้วยช่องว่าง
felipsmartins

โหวตโค้ดเพียง 2 บรรทัดแรกเท่านั้น ส่วนแถบความคืบหน้าจะช้าลงในบางกรณี ยังไงก็ขอบคุณ @ 6502
WaterRocket8236

บางโปรแกรม ( restic, flatpak) สามารถอัพเดตเอาต์พุตคอนโซลได้หลายบรรทัด คุณรู้หรือไม่ว่าสิ่งนี้สามารถทำได้โดยบังเอิญหรือไม่?
Alexey

1
@Alexey: คุณสามารถใช้รหัสหนี ANSI เพื่อเลื่อนเคอร์เซอร์ไปรอบ ๆ ล้างส่วนของหน้าจอและเปลี่ยนสี ... ดูที่en.wikipedia.org/wiki/ANSI_escape_code
6502

39

วิธีแก้ปัญหาที่หรูหรากว่านี้อาจเป็น:

def progressBar(current, total, barLength = 20):
    percent = float(current) * 100 / total
    arrow   = '-' * int(percent/100 * barLength - 1) + '>'
    spaces  = ' ' * (barLength - len(arrow))

    print('Progress: [%s%s] %d %%' % (arrow, spaces, percent), end='\r')

เรียกใช้ฟังก์ชันนี้ด้วยvalueและendvalueผลลัพธ์ควรเป็น

Progress: [------------->      ] 69 %

หมายเหตุ: งูหลามรุ่น 2.x นี่


คุณควรใช้Haloสำหรับแถบความคืบหน้าและตัวหมุนที่ดีขึ้น
Aravind Voggu

18

ในpython 3คุณสามารถทำได้เพื่อพิมพ์ในบรรทัดเดียวกัน:

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

มีประโยชน์อย่างยิ่งในการติดตามการอัปเดตและความคืบหน้าล่าสุด

ฉันขอแนะนำ tqdm จากที่นี่หากต้องการดูความคืบหน้าของลูป มันพิมพ์การทำซ้ำปัจจุบันและการทำซ้ำทั้งหมดเป็นแถบความคืบหน้าพร้อมเวลาที่คาดว่าจะเสร็จสิ้น มีประโยชน์และรวดเร็วมาก ใช้ได้กับ python2 และ python3


8

สำหรับใครก็ตามที่สะดุดในหลายปีต่อมา (เช่นเดียวกับฉัน) ฉันปรับเปลี่ยนวิธีการของ 6502 เล็กน้อยเพื่อให้แถบความคืบหน้าลดลงและเพิ่มขึ้น มีประโยชน์ในกรณีอื่น ๆ เล็กน้อย ขอบคุณ 6502 สำหรับเครื่องมือที่ยอดเยี่ยม!

โดยทั่วไปความแตกต่างเพียงอย่างเดียวคือทั้งบรรทัดของ #s และ -s ถูกเขียนทุกครั้งที่มีการเรียกความคืบหน้า (x) และเคอร์เซอร์จะกลับไปที่จุดเริ่มต้นของแถบเสมอ

def startprogress(title):
    """Creates a progress bar 40 chars long on the console
    and moves cursor back to beginning with BS character"""
    global progress_x
    sys.stdout.write(title + ": [" + "-" * 40 + "]" + chr(8) * 41)
    sys.stdout.flush()
    progress_x = 0


def progress(x):
    """Sets progress bar to a certain percentage x.
    Progress is given as whole percentage, i.e. 50% done
    is given by x = 50"""
    global progress_x
    x = int(x * 40 // 100)                      
    sys.stdout.write("#" * x + "-" * (40 - x) + "]" + chr(8) * 41)
    sys.stdout.flush()
    progress_x = x


def endprogress():
    """End of progress bar;
    Write full bar, then move to next line"""
    sys.stdout.write("#" * 40 + "]\n")
    sys.stdout.flush()

1
ฉันพบว่าสิ่งนี้อาจทำให้เกิดการชะลอตัวหากมีการเรียกรหัสบ่อยเกินไปดังนั้นฉันเดาว่า YMMV
jat255

7

คำตอบอื่น ๆ อาจจะดีกว่า แต่นี่คือสิ่งที่ฉันทำ ก่อนอื่นฉันสร้างฟังก์ชันที่เรียกว่า progress ซึ่งจะพิมพ์อักขระ backspace:

def progress(x):
    out = '%s things done' % x  # The output
    bs = '\b' * 1000            # The backspace
    print bs,
    print out,

จากนั้นฉันเรียกมันเป็นวงในฟังก์ชั่นหลักของฉันดังนี้:

def main():
    for x in range(20):
        progress(x)
    return

แน่นอนว่านี่จะลบทั้งบรรทัด แต่คุณสามารถยุ่งกับมันเพื่อทำสิ่งที่คุณต้องการ ฉันลงเอยด้วยการสร้างแถบความคืบหน้าโดยใช้วิธีนี้


4
ใช้งานได้ แต่ถ้าบรรทัดก่อนหน้ามีอักขระมากกว่าบรรทัดถัดไปอักขระหลังสิ้นสุดบรรทัดใหม่จะยังคงอยู่จากบรรทัดก่อนหน้า: "บันทึกการตรวจสอบการสะกด 417/701 [serfice เปลี่ยนเป็นพื้นผิว] เมื่อ] uminescence] cence] shmentarianism] "
Lil 'Bits

6

ถ้าฉันเข้าใจดี (ไม่แน่ใจ) คุณต้องการพิมพ์โดยใช้<CR>และไม่<LR>?

หากเป็นไปได้ตราบใดที่เทอร์มินัลคอนโซลอนุญาต (มันจะพังเมื่อเอาต์พุต si เปลี่ยนทิศทางไปยังไฟล์)

from __future__ import print_function
print("count x\r", file=sys.stdout, end=" ")

6

ฉันเขียนสิ่งนี้เมื่อไม่นานมานี้และมีความสุขกับมันมาก อย่าลังเลที่จะใช้มัน

ใช้เวลาindexและtotalและเป็นทางเลือกtitleหรือbar_length. เมื่อเสร็จแล้วให้แทนที่แก้วชั่วโมงด้วยเครื่องหมายถูก

⏳ Calculating: [████░░░░░░░░░░░░░░░░░░░░░] 18.0% done

✅ Calculating: [█████████████████████████] 100.0% done

ฉันรวมตัวอย่างที่สามารถเรียกใช้เพื่อทดสอบได้

import sys
import time

def print_percent_done(index, total, bar_len=50, title='Please wait'):
    '''
    index is expected to be 0 based index. 
    0 <= index < total
    '''
    percent_done = (index+1)/total*100
    percent_done = round(percent_done, 1)

    done = round(percent_done/(100/bar_len))
    togo = bar_len-done

    done_str = '█'*int(done)
    togo_str = '░'*int(togo)

    print(f'\t⏳{title}: [{done_str}{togo_str}] {percent_done}% done', end='\r')

    if round(percent_done) == 100:
        print('\t✅')


r = 50
for i in range(r):
    print_percent_done(i,r)
    time.sleep(.02)

ฉันยังมีเวอร์ชันที่มีแถบความคืบหน้าที่ตอบสนองขึ้นอยู่กับความกว้างของเทอร์มินัลที่ใช้shutil.get_terminal_size()หากเป็นที่สนใจ


5

สามารถทำได้โดยไม่ต้องใช้ sys library หากเราดูที่print()ฟังก์ชัน

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

นี่คือรหัสของฉัน:

def update(n):
    for i in range(n):
        print("i:",i,sep='',end="\r",flush=True)
        #time.sleep(1)

4

เพิ่มฟังก์ชั่นเพิ่มเติมเล็กน้อยในตัวอย่างของAravind Voggu :

def progressBar(name, value, endvalue, bar_length = 50, width = 20):
        percent = float(value) / endvalue
        arrow = '-' * int(round(percent*bar_length) - 1) + '>'
        spaces = ' ' * (bar_length - len(arrow))
        sys.stdout.write("\r{0: <{1}} : [{2}]{3}%".format(\
                         name, width, arrow + spaces, int(round(percent*100))))
        sys.stdout.flush()
        if value == endvalue:     
             sys.stdout.write('\n\n')

ตอนนี้คุณสามารถสร้างแถบความคืบหน้าหลายรายการโดยไม่ต้องแทนที่แถบก่อนหน้า

ฉันยังเพิ่มnameเป็นค่าที่มีความกว้างคงที่

สำหรับสองลูปและสองครั้งการใช้progressBar()ผลลัพธ์จะมีลักษณะดังนี้:

ภาพเคลื่อนไหวแถบความคืบหน้า


-1

ด้านล่างรหัสจะนับข้อความจาก 0 ถึง 137 ทุกๆ 0.3 วินาทีแทนที่หมายเลขก่อนหน้า

Number of Symbol to backstage = จำนวนหลัก

stream = sys.stdout
for i in range(137):
    stream.write('\b' * (len(str(i)) + 10))
    stream.write("Message : " + str(i))
    stream.flush()
    time.sleep(0.3)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.