標籤:

python 能否print到console固定一行?

輸出到固定位置,新的輸出覆蓋舊的,不換行

多謝馮昱堯 同學,跟howto 走下來寫了小小的例子:

import curses
import time
stdscr = curses.initscr()
stdscr.addstr(5,10,"abv",curses.A_REVERSE)
while 1:
c = stdscr.getch()
if c == ord("b"):break
for i in range(100,110):
time.sleep(1)
stdscr.refresh()
stdscr.addstr(10,10,chr(i)*10)
curses.endwin()


上邊說的flush()的方法雖然實用但是很不方便,只能固定到最後一行。這類對於console的操作操作系統一般都會提供相應的介面。

python標準庫裡邊自帶一個叫Curses的庫,在Unix平台下可以完成對於命令行輸出的一些操作,包括指定到某一行輸出或者改變輸出顏色以及一些更強大的功能。比如像下邊這個:

pad = curses.newpad(100, 100)
# These loops fill the pad with letters; this is
# explained in the next section
for y in range(0, 100):
for x in range(0, 100):
try: pad.addch(y,x, ord("a") + (x*x+y*y) % 26 )
except curses.error: pass

# Displays a section of the pad in the middle of the screen
pad.refresh( 0,0, 5,5, 20,75)

(上述示例代碼摘自 Python 官方文檔)

詳細的教程可以看Curses Programming with Python

至於Windows下的類似操作,標準庫裡邊沒有相應的封裝,不過 Python 官方文檔里推薦了一個第三方庫:

Windows Console Driver

用起來蠻方便的,比如下邊這個例子:

import Console

c = Console.getconsole()

c.title("Console Example")

# 輸出到指定位置
c.text(0, 0, "here"s some white text on white background", 0x1f)
c.text(10, 5, "line five, column ten")


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# tankywoo@2013-05-24

import sys
from time import sleep

for i in range(1,5):
print "
Hello, Gay! ", i,
sys.stdout.flush()
sleep(1)

按照你的需求,你需要的其實就是一個回車 以及 一個 flush刷新屏幕

參考:官方文檔對轉義字元的介紹 2. Lexical analysis


: ASCII Carriage Return (CR)


看到這個問題還是有點激動的 因為幾天前我剛好研究過 (這應該是我在知乎第一個乾貨向的回答)

先說結論: 個人比較推薦ANSI這個方式 但如果只想讓最後一行在用戶輸入前不斷更新內容的話用原生的print()更好

--------------------[正文開始]--------------------

實現這個效果可以用如下幾種方案

PS 代碼基於python3.6.2 且親測可用

1. print的特殊用法

#print函數的定義如下(只是近似 很抱歉我偷懶沒有去翻文檔)
def print(info, *more, sep=" ", end="
", file=sys.stdout, flush=False)

顯而易見的 只要使用如下結構就可以滿足要求

print("information", end="
")
#如果出現屏幕沒有刷新的現象 把Flush設為True即可
#但是在實際使用的時候並沒有出現這個問題 所以我就沒有改變默認值

這個方法的優點是簡潔且使用方便而且可以零成本跨平台 如果只是想顯示個進度條之類的東西簡直再合適不過 但是 這個方法有個很大的限制 -- input() 在用戶輸入完成並按回車後 會強制的換行 這個時候"
"就沒有用了(注1) 我們來看下一個方法

2. ANSI.SYS

先講個笑話: 命令提示符是有游標的 在吐槽"這不是廢話么"之前 請先想想 既然存在一個游標 那麼有沒有方法控制它呢 因為只要控制了游標就可以隨心所欲的控制輸出文本的位置 答案是 沒有! 沒有任何一個內部命令與之相關(注2) (所以說那個游標的意義何在) 不過呢 我們其實還可以用"ANSI轉義符"做到這點

這個東西發明出來主要為了控制終端的游標(以及顯示文本的顏色之類的)(注3) 以使得一些硬體的文本終端可以有更好的外觀(比如公告牌系統) 但並不是M$發明的 微軟只是在MS-DOS中支持了這一特性(而且還是在Win10 TH2 版本後)

ANSI.SYS 一看就是一個DOS的驅動 是我們接下來要做的事情的核心(Win下) 當然ANSI不僅僅能控制終端的游標(而且ANSI本身也可以指代其他的許多東西 在這裡特指"ANSI轉義碼") 但其他的和本回答無關 所以直接跳過(舉個栗子 各種寬字元的cp叫"ANSI字符集"也是ANSI(美國國家標準協會)的標準之一)

PS ANSI的最新標準是ISO/IEC 6429

既然是轉義符 那麼用起來就很方便了 只要像"

"之類的轉義符那樣插在文本里print()或者dump()到屏幕上就行 具體方法如下:

ANSI序列以 "ESC字元"+"[" 起始(在純DOS下雙擊"ESC"鍵可獲得"ESC字元"*1) 在Python中"ESC字元"可以用"x1b"來表示 在這之後接具體的控制碼即可 是不是特別方便(部分代碼如下)

x1b[nA 游標上移

x1b[nB 游標下移

x1b[nC 游標右移

x1b[nD 游標左移

(n 為行數/字元數)

x1b[2J 清屏(把2換成其他數字會有不同的清屏效果)

x1b[x;yH 調整屏幕坐標(x,y的單位是字元)

x1b?25l 隱藏游標

x1b?25h 顯示游標

注: 這些轉義符統統大小寫敏感 另外 如果想獲取完整的列表或者想了解更高級的使用方法可以去看wiki(用的好的話甚至可以做到按rgb調整顏色)

注2: 所有的指令的生效區間都是字元級的 也就是說甚至可以做到每個字元用不同的顏色和特效(在那個"黑框框"里!!!)

在題主的情況下可以這麼用(舉個假想的毛栗子)

#甚至不需要import
while True:
{
print("Pls input a number:", end="")
num = input()
if num.isdigit(): #判斷輸入是否是純數字(不包括"-")
{
num=int(num)
break
}
else:
{
print("x1b[1Ax1b[2K"+"
***** "+"x1b[1;31m"+"Not A Number!"+"x1b[0m"
+" *****", end="")
time.sleep(3) #這個記得import time
print("x1b[2K
")
#1 游標移到到上一行頭部並清空游標所在行的所有字元
#2 游標回到頭部 並列印"***** "
#3 改變前景色為亮紅色
#4 列印"Not A Number!"
#5 重置所有顏色為默認值
#6 列印" *****"
#7 延遲後再次清空行 並使游標回到頭部
}
}

這段代碼的效果是如果用戶沒有輸入一個純數字就會用一條中間部分紅色高亮的錯誤提示覆蓋用戶輸入的那行 並在延遲3秒後再重置到接受輸入的狀態

(原諒我不會縮進 把字元串分開也只是為了看起來方便)

這個方法有個很神奇的操作

print("x1b[%dA" % (n))
#甚至
print("x1b[%d%s" % (n,ope))

但是這個方法同樣有缺點 那就是不同的終端對他的支持的程度不同 不過大體上都差不多啦

3. 使用Curses

Curses是一個第三方的Python的CLI庫 如果除了本題的要求以外還希望做出其他效果 可以考慮使用(前提是不在Win下用 因為Win下目前沒有能好好用的實現)

4. 利用MS-DOS的一個BUG

用批處理做遊戲的那群大神比較喜歡用這個技術 本質上是利用一個溢出緩存的bug使游標上移n行 代碼如下

print("
"+"
"*((n+1)*10)+2, end="")

這個方法有個相當致命的缺點 就是這個公式的曲值很大程度上依賴於當前的游標位置 已經窗口的大小(我給的這個例子僅在游標在行首且窗口寬度120的情形下有用)

PS 具體的關係已經有大神做過分析了 請參閱這個

注我之後會補上


Windows Console Driver 這個東西已經很久沒更新了,python3也不支持

其實可以通過Windows API來實現

網上的都是改Win控制台顏色的,但是控制位置的API也是有的

直接上代碼:

import ctypes

class COORD(ctypes.Structure):
_fields_ = [("X", ctypes.c_short), ("Y", ctypes.c_short)]

STD_OUTPUT_HANDLE= -11
std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
dwCursorPosition = COORD()
dwCursorPosition.X = 5
dwCursorPosition.Y = 2
ctypes.windll.kernel32.SetConsoleCursorPosition(std_out_handle,dwCursorPosition)
i=1
while True:
print(i)
i += 1
ctypes.windll.kernel32.SetConsoleCursorPosition(std_out_handle,dwCursorPosition)
exit()

SetConsoleCursorPosition function (Windows)


請使用ncurses


#!/usr/bin/python3

import time

for i in range(101):
string = "loading... " + str(i) + "%"
print(string, end="") # 不換行
print("" * len(string), end="", flush=True) # 刪除前面列印的字元
time.sleep(0.2)

在 Automate the Boring Stuff with Python 見過,利用 ""


是我火星了?我一般都是

print("xxx", end="
")


推薦閱讀:

期權的高頻交易回測平台怎麼編寫?
有沒有什麼軟體能夠在Win10系統下將電腦重複的工作自動實現?
python 函數中傳值,傳的是引用還是複製一份傳給另一個函數,會修改調用函數裡面的值嘛?
Mixin是什麼概念?
關於 Python 字典的 values() 方法返回值的順序?

TAG:Python | Linux |