16

I have a Python program I am writing and I want it to be able to change text after it is printed. For example, let's say I want to print "hello" and erase one letter every second. How would I go about doing that?

Also, I heard about curses but I can't get that to work, and I do not want to simply create new lines until the old text is off the screen.

futurevilla216
  • 982
  • 3
  • 13
  • 29
  • 2
    Here's a tip: `'\r'` erases what has been printed on the current line, and put the cursor back to column one. Play with that. – Santa Mar 24 '11 at 23:00
  • No, using Mac OSX. Does `\r` erase the whole line or just the last character? – futurevilla216 Mar 24 '11 at 23:08
  • @TylerCrompton are you doing this in the Python shell? That's the mistake I made :) Also, are you using Windows or Mac? – futurevilla216 Mar 25 '11 at 12:14
  • @Lenny K, I figured it out. I was using Terminal on a Mac but I forgot to change the the `print()` function's `end` attribute to an empty string. Now I get this: `>>> print('tyler\r', end="") >>> r` I can't delete the 'r'. It's obviously doing something, though. What version of Python are you using? – Tyler Crompton Mar 25 '11 at 18:40
  • @Santa - `'\r'` doesn't erase the current line; it just repositions the cursor. You still have to print over stuff you want to change. – sapi Jun 07 '13 at 03:25

6 Answers6

16

Here's one way to do it.

print 'hello',
sys.stdout.flush()
...
print '\rhell ',
sys.stdout.flush()
...
print '\rhel ',
sys.stdout.flush()

You can probably also get clever with ANSI escapes. Something like

sys.stdout.write('hello')
sys.stdout.flush()
for _ in range(5):
    time.sleep(1)
    sys.stdout.write('\033[D \033[D')
    sys.stdout.flush()
Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • The output of that is "hello hell hel ". Does it matter I have Python 2.7 on my computer? And the output of the second one is "hello[D [D[D [D[D [D[D [D[D [D". – futurevilla216 Mar 24 '11 at 23:04
  • 2
    @Lenny K: I don't know why you are getting that output. The `\r` should push the cursor back to the start of the line before printing the truncated forms. If you are using Windows, then the second solution definitely won't work because that brain-dead piece of crap that Microsoft passes off as a command shell (cmd.exe) doesn't recognise ANSI escapes, though I would have expected the first version to work. – Marcelo Cantos Mar 24 '11 at 23:07
  • I've edited my answer to use `sys.stdout.flush()` on the first version. It doesn't address the problem @Lenny K describes in the above comments, but it works on my Mac. – Marcelo Cantos Mar 24 '11 at 23:14
  • 1
    I am using Mac OSX, not Windows. I got it to work, it wasn't working before because I was trying it in the Python shell :) :) :) How do you run the Python file from the Terminal? I opened it using "Python Launcher" which was already on my computer but what it the Terminal command for it? – futurevilla216 Mar 24 '11 at 23:16
4

For multi-line output, you can also clear the screen each time and reprint the entire thing:

from time import sleep
import os

def cls():
    os.system('cls' if os.name=='nt' else 'clear')

message = 'hello'
for i in range(len(message), 0, -1):
    cls()
    print message[:i]
    sleep(1)
Community
  • 1
  • 1
endolith
  • 25,479
  • 34
  • 128
  • 192
1

You could use this:

call these modules:

import time
import sys

Then copy this method:

    # Custom Print Method
    def custom_print(string, how = "normal", dur = 0, inline = True):

Copy just this part for the method to do typing

# string = the string to print & how = way to print & dur = time to print whole word or letter & inline = print on single line or not
if how == "typing": # if how is equal to typing then run this block of code
    letter = 1
    while letter <= len(string):
        new_string = string[0:letter]
        if inline: sys.stdout.write("\r")
        sys.stdout.write("{0}".format(new_string))
        if inline == False: sys.stdout.write("\n")
        if inline: sys.stdout.flush()
        letter += 1 
        time.sleep(float(dur))

OR just this part of the method for a string to print in reverse

if how == "reverse": # if how is equal to reverse then run this block of code
    new_string = string
    while len(new_string) > 0:
        if inline == True: sys.stdout.write("\r")
        sys.stdout.write('{message: <{fill}}'.format(message=new_string, fill=str(len(string))))
        if inline == False: sys.stdout.write("\n")
        if inline == True: sys.stdout.flush()
        new_string = new_string[0:len(new_string) - 1]
        time.sleep(float(dur))

OR just this part of the method for a normal string to print normally

if how == "normal": # if how is equal to normal then run this block of code
    sys.stdout.write("\r")
    sys.stdout.write(string)
    time.sleep(float(dur))
    sys.stdout.write("\n")

OR you can put all of it in the method for all the options

All you have to do is call custom_print() instead ofprint`

# custom_print("string", "howtoprint", seconds in int, inline:true or false)
custom_print("hello", "reverse", 1) # for reverse printing hello
custom_print("hello", "typing", 1) # for typing hello slowly
custom_print("hello", "normal", 0) # for just printing hello
custom_print("hello") # for just printing hello
1
import sys
import time
steps = 10
print("Total steps: "+str(steps),end=' ')
time.sleep(1)
for i in range(steps):
    sys.stdout.flush()
    print("\rStep "+str(i+1)+"/"+str(steps),end=' ')
    time.sleep(1)
print("")#to change the current line

Chris P
  • 2,059
  • 4
  • 34
  • 68
0

Here's something that seems to work well:

import time

text = '\rgood-bye'
for i in xrange(len(text), 0, -1):
    print text[0:i],
    time.sleep(1)
print ' '
martineau
  • 119,623
  • 25
  • 170
  • 301
0

You could use dynamical stdout with getch() characters and loop

Example: https://asciinema.org/a/238478

Code:

# Script make you able to edit printed text
# stdin and stdout at the same time
# https://asciinema.org/a/238478
# https://gist.github.com/SoleSensei/05a97bbe8b75cd2368a8e6d5e00d6047
import sys
from getch import getch

def flush_append(char):
    # just append char to the end
    sys.stdout.write(char)
    sys.stdout.flush()

def flush_write(line):
    # clear all and rewrite line
    sys.stdout.write(f"\r{' '*100}\r")
    sys.stdout.flush()
    sys.stdout.write(line)
    sys.stdout.flush()

def interactive_input(line):
    flush_write(line)
    c = getch()
    while ord(c) not in (13, 3): # 13 - Enter, 3 - Ctrl+C
        if ord(c) in (127, 8): # 127,8 - Backspace (Unix, Windows)
            line = line[:-1]
            flush_write(line)
        else:
            # decode to string if byte
            c = c.decode('ascii') if str(c)[0] == 'b' else c
            line += c
            flush_append(c)
        c = getch()
    print() # add EOL
    return line


s = interactive_input('stdout editable line')
Sole Sensei
  • 369
  • 3
  • 8