I've been browsing a long time searching an answer to this.
I'm using Python 2.7 in Unix.
I have a continuous while loop and I need an option where user could interrupt it, do something and after that the loop would continue.
Like:
while 2 > 1:
for items in hello:
if "world" in items:
print "hello"
else:
print "world"
time.sleep(5)
here user could interrupt the loop with pressing "u" etc. and modify elements inside he loop.
I started testing out with raw_input, but since it prompts me out every cycle, it's something that I don't need.
I tried methods mentioned here:
Keyboard input with timeout in Python
couple of times, but none of those seem to work how I wish.
>>> try:
... print 'Ctrl-C to end'
... while(True):
... pass
... except KeyboardInterrupt, e:
... print 'Stopped'
... raise
...
Ctrl-C to end
Stopped
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyboardInterrupt
>>>
Obviously you need to replace pass with whatever you are doing and print with the aftermath.
Here's how to do it by polling stdin:
import select, sys
p = select.poll()
p.register(sys.stdin, 1) #select the file descriptor,
#in this case stdin, which you want the
#poll object to pay attention to. The 1 is a bit-mask
#indicating that we only care about the POLLIN
#event, which indicates that input has occurred
while True:
for items in hello:
if "world" in items:
print "hello"
else:
print "world"
result = p.poll(5) #this handles the timeout too
if len(result) > 0: #see if anything happened
read_input = sys.stdin.read(1)
while len(read_input) > 0:
if read_input == "u":
#do stuff!
read_input = sys.stdin.read(1) #keep going
#until you've read all input
Note: This probably wouldn't work in Windows.
You could do nested while loops, something structured like this:
while true:
go = True
while go:
for items in hello:
if "u" in items:
go = False
else if "world" in items:
print "hello"
else:
print "world"
#Here you parse input to modify things in the nested loop, include a condition to set
#go back to true to reenter the loop
import sys
import os
import time
import termios
import tty
import fcntl
import errno
KEY = "u"
def read_characters(f):
fd = f.fileno()
# put descriptor in non-blocking mode
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
try:
while 1:
try:
yield f.read(1)
except IOError as e:
if e.errno == errno.EAGAIN:
# no more characters to read
# right now
break
finally:
# restore blocking mode
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
def main():
fd = sys.stdin.fileno()
# save current termios settings
old_settings = termios.tcgetattr(fd)
# tty sets raw mode using termios module
tty.setraw(fd)
try:
while True:
time.sleep(1)
for c in read_characters(sys.stdin):
if c == KEY: break
else:
c = None
if c == KEY: break
sys.stdout.write("still going\r\n")
finally:
# restore terminal settings
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
if __name__ == "__main__":
main()
Related
What I am trying to do is make a simple pi memorization game in Python. What I need is a way to get input from the user without having to press 'enter' after every character. It sounds like I need something like getch, but I can't get it to work. I got a getch-like function from here: https://gist.github.com/chao787/2652257#file-getch-py. I don't really understand anything that's in there. When I do 'x = getch.getch()' it says "AttributeError: '_Getch' object has no attribute 'getch'". It looks like msvcrt can do it for Windows, but I have a Mac. It also looks like curses is a thing that has getch, but it says I need to do initscr first, but then I get the error "File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/curses/__init__.py", line 30, in initscr
fd=_sys.__stdout__.fileno())
_curses.error: setupterm: could not find terminal".
This is my file just using input, where you would have to press enter every time (I actually put in 1000 digits, not an ellipsis).
pi = '3.1415926535...'
def main():
print('Welcome to PiGame!')
pigame()
while True:
yn = input('Play again? y/n ')
if yn == 'y':
pigame()
else: return
def pigame():
n=0
print('Go!')
while n<=1000:
x = input()
if x == pi[n]:
n += 1
else:
print('I\'m sorry. The next digit was '+pi[n]+'.')
print('You got to '+str(n)+' digits!')
return
print('You got to 1000! Hooray!')
You can define your own version of getch using the termios, sys and tty packages:
def getch():
import termios
import sys, tty
def _getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
return _getch()
This is a tested (on RPi, Py 3) code that can read a specified length of chars without need to hit Enter button
But consider one thing :
This must run on terminal otherwise raises an error
import termios, sys , tty
def _getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1) #This number represents the length
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
getch = _getch()
print(getch)
I have this script
import sys, os, termios, tty
home = os.path.expanduser("~")
history = []
if os.path.exists(home+"/.incro_repl_history"):
readhist = open(home+"/.incro_repl_history", "r+").readlines()
findex = 0
for j in readhist:
if j[-1] == "\n":
readhist[findex] = j[:-1]
else:
readhist[findex] = j
findex += 1
history = readhist
del readhist, findex
class _Getch:
def __call__(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(3)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
while True:
try:
cur = raw_input("> ")
key = _Getch()
print key
if key == "\x1b[A":
print "\b" * 1000
print history[0]
history.append(cur)
except EOFError:
sys.stdout.write("^D\n")
history.append("^D")
except KeyboardInterrupt:
if not os.path.exists(home+"/.incro_repl_history"):
histfile = open(home+"/.incro_repl_history", "w+")
for i in history:
histfile.write(i+"\n")
else:
os.remove(home+"/.incro_repl_history")
histfile = open(home+"/.incro_repl_history", "w+")
for i in history:
histfile.write(i+"\n")
sys.exit("")
When run, it get's the contents of /home/bjskistad/.incro_repl_history, reads the lines, and removes the newspace character, and then defines the _Getch class/function. Then, it runs the main loop of the script. It trys to set cur to raw_input(). I then try to sense the up arrow using the _Getch class defined. This is where I am having trouble. I can't sense the up arrow using my _Getch class. How can I sense the up arrow with my current code?
The raw_input function always read a string until ENTER, not a single character (arrow, etc.)
You need to define your own getch function, see: Python read a single character from the user.
Then you can re-implement your "input" function with a loop using getch` function.
Here is a simple usage:
while True:
char = getch()
if char == '\x03':
raise SystemExit("Bye.")
elif char in '\x00\xe0':
next_char = getch()
print("special: {!r}+{!r}".format(char, next_char))
else:
print("normal: {!r}".format(char))
Under Windows, with the following keys: Hello<up><down><left><right><ctrl+c>, you'll get:
normal: 'H'
normal: 'e'
normal: 'l'
normal: 'l'
normal: 'o'
special: '\xe0'+'H'
special: '\xe0'+'P'
special: '\xe0'+'K'
special: '\xe0'+'M'
So arrow corresponds to the combining characters: "\xe0H".
Maybe I wrote smth strange in a question title, I'l try to explain.
I'm trying to do password input as in linux-based systems (no symbols shows while you type).
I found one function that did it.
def getchar():
import tty, termios, sys
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
pwd = ''
print('Type password:', end=' ') # HERE IS THE PROBLEM
while True:
ch = getchar()
if ch == '\r':
break
pwd += ch
print(pwd)
If I do not use end argument it looks like that:
But with end argument:
Line 'Type password:' will appear after while loop end.
Why is this and what can I do?
By default, sys.stdout is line buffered, which means that anything written to it is buffered up until a newline is seen.
Because you replaced the standard end='\n' with a space, no newline is seen yet and the buffer is not flushed. Set flush=True to force a buffer flush anyway:
print('Type password:', end=' ', flush=True)
What I am trying to do is make a simple pi memorization game in Python. What I need is a way to get input from the user without having to press 'enter' after every character. It sounds like I need something like getch, but I can't get it to work. I got a getch-like function from here: https://gist.github.com/chao787/2652257#file-getch-py. I don't really understand anything that's in there. When I do 'x = getch.getch()' it says "AttributeError: '_Getch' object has no attribute 'getch'". It looks like msvcrt can do it for Windows, but I have a Mac. It also looks like curses is a thing that has getch, but it says I need to do initscr first, but then I get the error "File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/curses/__init__.py", line 30, in initscr
fd=_sys.__stdout__.fileno())
_curses.error: setupterm: could not find terminal".
This is my file just using input, where you would have to press enter every time (I actually put in 1000 digits, not an ellipsis).
pi = '3.1415926535...'
def main():
print('Welcome to PiGame!')
pigame()
while True:
yn = input('Play again? y/n ')
if yn == 'y':
pigame()
else: return
def pigame():
n=0
print('Go!')
while n<=1000:
x = input()
if x == pi[n]:
n += 1
else:
print('I\'m sorry. The next digit was '+pi[n]+'.')
print('You got to '+str(n)+' digits!')
return
print('You got to 1000! Hooray!')
You can define your own version of getch using the termios, sys and tty packages:
def getch():
import termios
import sys, tty
def _getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
return _getch()
This is a tested (on RPi, Py 3) code that can read a specified length of chars without need to hit Enter button
But consider one thing :
This must run on terminal otherwise raises an error
import termios, sys , tty
def _getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1) #This number represents the length
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
getch = _getch()
print(getch)
Is there a way to have a timeout when calling IPython.embed() in a python script? Say I have a script that stops for a IPython.embed() at various places, how can I let the script go on with its business when the user doesn't respond in time?
This is not quite an answer to the question, but it is adequate for what I wanted.
I used some cross-platform keypress timeout code from
https://stackoverflow.com/a/23098294/1490584, and
https://stackoverflow.com/a/5047058/1490584
to let the user decide within a time window if a IPython.embed() should be called:
import time
import IPython
try:
from msvcrt import kbhit
except ImportError:
import termios, fcntl, sys, os
def kbfunc():
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
while True:
try:
c = sys.stdin.read(1)
return c.decode()
except IOError:
return False
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
else:
import msvcrt
def kbfunc():
#this is boolean for whether the keyboard has bene hit
x = msvcrt.kbhit()
if x:
#getch acquires the character encoded in binary ASCII
ret = msvcrt.getch()
return ret.decode()
else:
return False
def wait_for_interrupt(waitstr=None, exitstr=None, countdown=True, delay=3):
if waitstr is not None: print(waitstr)
for i in range(delay*10):
if countdown and i%10 ==0 : print('%d'%(i/10 + 1), end='')
elif countdown and (i+1)%10 ==0: print('.')
elif countdown : print('.', end='')
key = kbfunc()
if key: return key
time.sleep(0.1)
if exitstr is not None: print(exitstr)
return False
if __name__ == "__main__":
#wait_for_interrupt example test
if wait_for_interrupt('wait_for_interrupt() Enter something in the next 3 seconds', '... Sorry too late'):
IPython.embed()
#begin the counter
number = 1
#interrupt a loop
while True:
#acquire the keyboard hit if exists
x = kbfunc()
#if we got a keyboard hit
if x != False and x == 'i':
#we got the key!
IPython.embed()
#break loop
break
else:
#prints the number
print(number)
#increment, there's no ++ in python
number += 1
#wait half a second
time.sleep(0.5)