I wrote a little python3 script, that runs another program with Popen and processes its output to create a little dashboard for it. The script generates a long string with information about the other program, clears the terminal and prints it. Everytime the screen refreshes, the whole terminal flickers.
here are the important parts of my script:
def screen_clear():
if os.name == 'posix':
os.system('clear')
else:
os.system('cls')
def display(lines):
# lines as a list of, well, lines i guess
s=''
for line in lines:
s=s + '\n' + str(line)
screen_clear()
print(s)
I bet theres a more elegant way without flickering to this, right?
Thanks for any help in advance!
the only solution to try out I can think of would be using print(s, end='\r') instead of clearing the screen first and printing again. The \r marker tells the console to override the last line.
In the end I'm sorry to say that consoles are simply not made for using them as a dashboard with permanently changing values. If the aforementioned solution doesn't work, maybe try implementing your dashboard in another way, python offers lots of solutions for that.
Related
I was testing some code on IDLE for Python which I haven't used in a while and stumbled on an unusual error.
I was attempting to run this simple code:
for i in range(10):
print(i)
print('Done')
I recall that the shell works on a line by line basis, so here is what I did first:
>>> for i in range(10):
print(i)
print('Done')
This resulted in a indent error, shown by the picture below:
I tried another way, as it might be that the next statement needed to be at the start perhaps, like this:
>>> for i in range(10):
print(i)
print('Done')
But this gave a syntax error, oddly:
This is quite odd to me the way IDLE works.
Take Note:
I am actually testing a much more complex program and didn't want to create a small Python file for a short test. After all, isn't IDLE's shell used for short tests anyways?
Why is multi-line coding causing this issue? Thanks.
Just hit return once or twice after the print(i), until you get the >>> prompt again. Then you can type the print('Done'). What's going on is that python is waiting for you to tell it that you're done working inside that for. And you do that by hitting return.
(You'll see, though, that the for loop is executed right away.)
So the question is how to have an input function or stdin.readline function waiting for an input, while having an updating prompt i.e. The prompt contains the time in format HH:MM:SS and is refreshing every second like:
while 1:
sys.stdout.write('\r' + time.strftime(TIME_FORMAT) + ' :>')
time.sleep(1.0)
But as soon as you add an input there, of course the program waits until you write some input. The version of python I am using is 3.5.
I know I should use curses, but I am planing to write a cross-platform program and the only module I have found is clint, but it didn't have anything in the documentation on the updating prompt.
I have found something that got pretty close but has different problems:
def input_thread(L):
x = input()
L.append(x)
L = []
thread = threading.Thread(target=input_thread, args=(L,))
thread.start()
while 1:
sys.stdout.write('\r' + time.strftime(TIME_FORMAT) + '>:')
time.sleep(1.0)
sys.stdout.flush()
Now the problem remains is that when you type the input but do not press ENTER, the input on the next iteration remains but when you write something, the previous input gets replaced by the current one. Of course the previous inputs are still there in the argument L, so there is no lost input. I hope I didn't describe this to confusing.
If there is no easy way of doing this as it could be done with curses, I'm open to similar cross open tools. Thanks for your time and answers!
So, I figured out what I wanted, a few months ago but didn't answered my own quesiton.
To have a prompt with updating time, you need a separate threads that:
Updates the prompt with additional input
Catches your key strokes
The second thread handles every key-stroke and you can program it how to handle the pressed keys.
When pressing a key, the thread that updates the prompt adds the pressed keys. Also you have to configure some shortcuts for Return, Backspace, etc... to work as expected
I've got it to work and unfortunately... I don't like it. If anyone would like to see the code I will happily provide.
I'd like to print a string to command line / terminal in Windows and then edit / change the string and read it back. Anyone knows how to do it? Thanks
print "Hell"
Hello! <---Edit it on the screen
s = raw_input()
print s
Hello!
You could do some ANSI trickery to make it look like you are editing on screen. Check out this link (also similar to this SO post on colors).
This would only work on certain terminals and configurations. ymmv.
This python script worked in my Cygwin terminal on Win7:
print 'hell'
print '\033[1A\033[4CO!'
Ends up printing hellO! on one line. The 2nd print moves the cursor up one line (Esc[1A) then over 4 characters (Esc[4C]) and then prints the 'O!'.
It wouldn't let you read it back though... only a 1/2 answer.
I had this same use-case for a command-line application.
Finally found a hack to do this.
# pip install pyautogui gnureadline
import pyautogui
import readline
from threading import Thread
def editable_input(text):
Thread(target=pyautogui.write, args=(text,)).start()
modified_input = input()
return modified_input
a = editable_input("This is a random text")
print("Received input : ", a)
The trick here is use pyautogui to send the text from keyboard. But we want to do this immediately after the input(). Since input() is a blocking call, we can run the pyautogui command in a different thread. And have an input function immediately after that in the main thread.
gnureadline is for making sure we can press left and right arrow keys to move the cursor in a terminal without printing escape characters.
Tested this on Ubuntu 20, python 3.7
raw_input accepts a parameter for a "prompt message", so use that to output the message, and then prepend it to what you get back. However, this won't allow you to backspace into the prompt, because it's a prompt and not really part of the input.
s = "Hell" + raw_input("Hell")
print s
os.sys.stdout is write only, but you can erase some characters of the last line with \b or the whole line with \r, as long as you did not write a carriage return.
(however, see also my question about limitations to the standard python console/terminal)
I once made some output exercise (including a status bar) to write,erase or animate if you will, perhaps it is helpfull:
from __future__ import print_function
import sys, time
# status generator
def range_with_status(total):
n=0
while n<total:
done = '#'*(n+1)
todo = '-'*(total-n-1)
s = '<{0}>'.format(done+todo)
if not todo:
s+='\n'
if n>0:
s = '\r'+s
sys.stdout.write(s)
sys.stdout.flush()
yield n
n+=1
print ('doing something ...')
for i in range_with_status(10):
time.sleep(0.1)
print('ready')
time.sleep(0.4)
print ('And now for something completely different ...')
time.sleep(0.5)
msg = 'I am going to erase this line from the console window.'
sys.stdout.write(msg); sys.stdout.flush()
time.sleep(1)
sys.stdout.write('\r' + ' '*len(msg))
sys.stdout.flush()
time.sleep(0.5)
print('\rdid I succeed?')
time.sleep(4)
If it's for your own purposes, then here's a dirty wee hack using the clipboard without losing what was there before:
def edit_text_at_terminal(text_to_edit):
import pyperclip
# Save old clipboard contents so user doesn't lose them
old_clipboard_contents = pyperclip.paste()
#place text you want to edit in the clipboard
pyperclip.copy(text_to_edit)
# If you're on Windows, and ctrl+v works, you can do this:
import win32com.client
shell = win32com.client.Dispatch("WScript.Shell")
shell.SendKeys("^v")
# Otherwise you should tell the user to type ctrl+v
msg = "Type ctrl+v (your old clipboard contents will be restored):\n"
# Get the new value, the old value will have been pasted
new_value= str(raw_input(msg))
# restore the old clipboard contents before returning new value
pyperclip.copy(old_clipboard_contents )
return new_value
Note that ctrl+v doesn't work in all terminals, notably the Windows default (there are ways to make it work, though I recommend using ConEmu instead).
Automating the keystrokes for other OSs will involve a different process.
Please remember this is a quick hack and not a "proper" solution. I will not be held responsible for loss of entire PhD dissertations momentarily stored on your clipboard.
For a proper solution there are better approaches such as curses for Linux, and on Windows it's worth looking into AutHotKey (perhaps throw up an input box, or do some keystrokes/clipboard wizardry).
Certain applications like hellanzb have a way of printing to the terminal with the appearance of dynamically refreshing data, kind of like top().
Whats the best method in python for doing this? I have read up on logging and curses, but don't know what to use. I am creating a reimplementation of top. If you have any other suggestions I am open to them as well.
The simplest way, if you only ever need to update a single line (for instance, creating a progress bar), is to use '\r' (carriage return) and sys.stdout:
import sys
import time
for i in range(10):
sys.stdout.write("\r{0}>".format("="*i))
sys.stdout.flush()
time.sleep(0.5)
If you need a proper console UI that support moving the pointer etc., use the curses module from the standard library:
import time
import curses
def pbar(window):
for i in range(10):
window.addstr(10, 10, "[" + ("=" * i) + ">" + (" " * (10 - i )) + "]")
window.refresh()
time.sleep(0.5)
curses.wrapper(pbar)
It's highly advisable to use the curses.wrapper function to call your main function, it will take care of cleaning up the terminal in case of an error, so it won't be in an unusable state afterwards.
If you create a more complex UI, you can create multiple windows for different parts of the screen, text input boxes and mouse support.
As most of the answers have already stated, you really have little option on Linux but to use ncurses. But what if you aren't on Linux, or want something a little more high-level for creating your terminal UI?
I personally found the lack of a modern, cross-platform terminal API in Python frustrating, so wrote asciimatics to solve this. Not only does it give you a simple cross-platform API, it also provides a lot of higher level abstractions for UI widgets and animations which could be easily used to create a top-like UI.
Sending output to the terminal via the print() command can be done without scrolling if you use the attribute "end".
The default is end='\n' which is a new line.
To suppress scrolling and overwrite the whole previous line, you can use the RETURN escape which is '\r'.
If you only want to rewrite the last four characters, you can use a few back-spaces.
print(value, "_of_", total, end='\r')
NOTE
This works for the standard system terminal. The terminal emulator in some tools like IDLE has an error and the '\r' does not work properly, the output is simply concatenated with some non-printable character between.
BONUS INFORMATION FOR print()
In the example above, the spaces on each side of "of" are meant to insure white-space between my values and the word "of". However, the default separater of the print() is a " " (space) so we end up with white space between the value and underscore of "_of_".
>> print (value, "_of_", total, end='\r')
8 _of_ 17
The sepparator attribute, sep, can be used to set character between printed items. In my example, I will change it to a null string ('') to make my output suit my needs.
>> print (value, "_of_", total, sep='', end='\r')
8_of_17
I hacked this script using curses. Its really a ad-hoc solution I did for a fun. It does not support scrolling but I think its a good starting point if you are looking to build a live updating monitor with multiple rows on the terminal.
https://gist.github.com/tpandit/b2bc4f434ee7f5fd890e095e79283aec
Here is the main:
if __name__ == "__main__":
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
curses.start_color()
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(4, curses.COLOR_CYAN, curses.COLOR_BLACK)
try:
while True:
resp = get_data()
report_progress(get_data())
time.sleep(60/REQUESTS_PER_MINUTE)
finally:
curses.echo()
curses.nocbreak()
curses.endwin()
When I do this in shell scripts on Unix, I tend to just use the clear program. You can use the Python subprocess module to execute it. It will at least get you what you're looking for quickly.
import time
for i in range(10):
print('\r{}>'.format('='*i), end='')
time.sleep(0.5)
I don't think that including another libraries in this situation is really good practice. So, solution:
print("\rCurrent: %s\t%s" % (str(<value>), <another_value>), end="")
I have a Python-based app that can accept a few commands in a simple read-eval-print-loop. I'm using raw_input('> ') to get the input. On Unix-based systems, I also import readline to make things behave a little better. All this is working fine.
The problem is that there are asynchronous events coming in, and I'd like to print output as soon as they happen. Unfortunately, this makes things look ugly. The "> " string doesn't show up again after the output, and if the user is halfway through typing something, it chops their text in half. It should probably redraw the user's text-in-progress after printing something.
This seems like it must be a solved problem. What's the proper way to do this?
Also note that some of my users are Windows-based.
TIA
Edit: The accepted answer works under Unixy platforms (when the readline module is available), but if anyone knows how to make this work under Windows, it would be much appreciated!
Maybe something like this will do the trick:
#!/usr/bin/env python2.6
from __future__ import print_function
import readline
import threading
PROMPT = '> '
def interrupt():
print() # Don't want to end up on the same line the user is typing on.
print('Interrupting cow -- moo!')
print(PROMPT, readline.get_line_buffer(), sep='', end='')
def cli():
while True:
cli = str(raw_input(PROMPT))
if __name__ == '__main__':
threading.Thread(target=cli).start()
threading.Timer(2, interrupt).start()
I don't think that stdin is thread-safe, so you can end up losing characters to the interrupting thread (that the user will have to retype at the end of the interrupt). I exaggerated the amount of interrupt time with the time.sleep call. The readline.get_line_buffer call won't display the characters that get lost, so it all turns out alright.
Note that stdout itself isn't thread safe, so if you've got multiple interrupting threads of execution, this can still end up looking gross.
Why are you writing your own REPL using raw_input()? Have you looked at the cmd.Cmd class? Edit: I just found the sclapp library, which may also be useful.
Note: the cmd.Cmd class (and sclapp) may or may not directly support your original goal; you may have to subclass it and modify it as needed to provide that feature.
run this:
python -m twisted.conch.stdio
You'll get a nice, colored, async REPL, without using threads. While you type in the prompt, the event loop is running.
look into the code module, it lets you create objects for interpreting python code also (shameless plug) https://github.com/iridium172/PyTerm lets you create interactive command line programs that handle raw keyboard input (like ^C will raise a KeyboardInterrupt).
It's kind of a non-answer, but I would look at IPython's code to see how they're doing it.
I think you have 2 basic options:
Synchronize your output (i.e. block until it comes back)
Separate your input and your (asyncronous) output, perhaps in two separate columns.