Print text before input() prompt in python

2019-01-12 05:18发布

问题:

In Python, is it possible to request user input with input() in the console while simultaneously printing out text in the line BEFORE the prompt? It should look something like this:

Text 1
Text 2
Text 3
Please enter something: abc

Whenever new text is printed, it should be printed after the previous text and before the input() prompt. Also, it should not interrupt the user entering the text.

Therefore, after printing "Text 4" the console should look like this:

Text 1
Text 2
Text 3
Text 4
Please enter something: abc

Is that possible to do in Python without using any external libraries?

I have already tried using \r, \b and similar codes as well as threading. I also know that I will probably need to have one thread printing out the text while I have another one requesting the user input.

回答1:

Here's another approach using ANSI/VT100 terminal control escape sequences.

We tell the terminal to only scroll the upper region of the terminal, where the output data is printed, so that the lower region, where the input prompt is printed, stays fixed in place. When we exit the program (using Ctrl C) we need to restore the default scrolling settings.

This program first clears the screen and then sits in a loop prompting the user for a number n and then prints the numbers in range(n), one per line, with a small time delay to make it easier to see what's happening. The output for each range follows on from the previous range. If the user enters a non-integer the prompt is re-printed. The readline module is imported so that editing and history are available at the input prompt; this module may not be available on all platforms.

First, a Python 2 version.

''' Print text in a scrolling region of the terminal above a fixed line for input

    Written by PM 2Ring 2016.05.29

    Python 2 version
'''

from __future__ import print_function
from time import sleep
import readline

# Some ANSI/VT100 Terminal Control Escape Sequences
CSI = '\x1b['
CLEAR = CSI + '2J'
CLEAR_LINE = CSI + '2K'
SAVE_CURSOR = CSI + 's'
UNSAVE_CURSOR = CSI + 'u'

def emit(*args):
    print(*args, sep='', end='')

def set_scroll(n):
    return CSI + '0;%dr' % n

# Height of scrolling region
height = 40

GOTO_INPUT = CSI + '%d;0H' % (height + 1)

emit(CLEAR, set_scroll(height))

try:
    while True:
        #Get input
        emit(SAVE_CURSOR, GOTO_INPUT, CLEAR_LINE)
        try:
            n = int(raw_input('Number: '))
        except ValueError:
            continue
        finally:
            emit(UNSAVE_CURSOR)

        #Display some output
        for i in range(n):
            print(i)
            sleep(0.1)

except KeyboardInterrupt:
    #Disable scrolling, but leave cursor below the input row
    emit(set_scroll(0), GOTO_INPUT, '\n')

And here's a version that runs on Python 2 and Python 3. When run on Python 3 this script calls shutil.get_terminal_size() to set the height of the scrolling region. It is possible to get the terminal size in Python 2, but the code is rather messy, so I opted for a fixed height.

I should mention that both versions of this script don't cope well if the terminal size is changed while they are running; handling that properly is left as an exercise for the reader. :)

''' Print text in a scrolling region of the terminal above a fixed line for input

    Written by PM 2Ring 2016.05.29

    Python 2 / 3 version
'''

from __future__ import print_function
import sys
import readline
from time import sleep

if sys.version_info > (3,):
    # Get the (current) number of lines in the terminal
    import shutil
    height = shutil.get_terminal_size().lines - 1

    stdout_write_bytes = sys.stdout.buffer.write
else:
    height = 40
    input = raw_input
    stdout_write_bytes = sys.stdout.write


# Some ANSI/VT100 Terminal Control Escape Sequences
CSI = b'\x1b['
CLEAR = CSI + b'2J'
CLEAR_LINE = CSI + b'2K'
SAVE_CURSOR = CSI + b's'
UNSAVE_CURSOR = CSI + b'u'

GOTO_INPUT = CSI + b'%d;0H' % (height + 1)

def emit(*args):
    stdout_write_bytes(b''.join(args))

def set_scroll(n):
    return CSI + b'0;%dr' % n

emit(CLEAR, set_scroll(height))

try:
    while True:
        #Get input
        emit(SAVE_CURSOR, GOTO_INPUT, CLEAR_LINE)
        try:
            n = int(input('Number: '))
        except ValueError:
            continue
        finally:
            emit(UNSAVE_CURSOR)

        #Display some output
        for i in range(n):
            print(i)
            sleep(0.1)

except KeyboardInterrupt:
    #Disable scrolling, but leave cursor below the input row
    emit(set_scroll(0), GOTO_INPUT, b'\n')


回答2:

On Linux you can do it this way:

#!/usr/bin/python

CURSOR_UP_ONE = '\x1b[1A'
ERASE_LINE = '\x1b[2K'

print('Text 1\nText 2\nText 3')

inp = ''
while inp != 'END':
    inp = raw_input('Please enter something:')
    print(CURSOR_UP_ONE + ERASE_LINE + inp)

The point is in moving the cursor one line up every time and erasing the line Please enter something:. Then you can write the input on its place and write the notice about input again, if needed.


Special strings:

CURSOR_UP_ONE and ERASE_LINE are strings with special meaning. The complete list if those Terminal escape control sequences you can find HERE.


Use of ANSI Terminal escape control sequences on Windows:

To some extent it's possible to use this sequences on Windows too. E.g. those questions deals with this topic:

  • https://superuser.com/questions/413073/windows-console-with-ansi-colors-handling/1050078#1050078
  • How to make win32 console recognize ANSI/VT100 escape sequences?

Workflow of this script



回答3:

You could mimic what you want by doing something like this:

import os

def clear(): os.system('cls')

screen = ""
prompt = ""

def printToScreen(s):
    global screen, prompt
    clear()
    screen += (s + '\n')
    print(screen)
    print(prompt)

def promptToScreen(p):
    global screen, prompt
    clear()
    print(screen)
    s = input(p)
    prompt = p + s
    return s

#test:

while True:
    s = promptToScreen("Enter anything -- or press 'Enter' to quit: ")
    if len(s) > 0:
        printToScreen(s)
    else:
        break

The following two screen shots shows how it works:

then:

This is a Windows solution. In other OS's -- use their command to clear the screen. You could make it more portable by writing clear() so that it issues different commands depending on the OS. The above is mostly proof of concept. The key idea is to maintain a string which represent how you want the terminal to look and then write your own print function which modifies the string, clears the terminal, then prints that screen to the command prompt. Unless you are doing something strange, the amount of overhead is negligible and the clear/update is invisible to the user.



标签: python cmd