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)
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 wrote this bit of code to clarify my issue... I keep getting ValueError: I/O operation on closed file.
None of the child threads read from stdin. The loop works great until I start a child thread... can someone tell me how to keep the file descriptor from closing?
import threading
from threadtest2 import Threadtest
import termios, sys, tty
import time
def getchar():
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
tt2 = Threadtest()
stop = threading.Event()
t1 = threading.Thread(target=tt2.thread1, args=[stop, ])
t2 = threading.Thread(target=tt2.thread2, args=[stop, ])
try:
while 1:
while not stop.isSet():
try:
c = getchar()
except IOError: pass
if c == "q":
stop.set()
if c == "x":
stop.set()
exit()
if c == "1":
print "starting t1"
t1.start()
if c == "2":
print "starting t2"
t2.start()
while len(threading.enumerate()) > 1:
print 'waiting for ' + str(len(threading.enumerate()) - 1) + ' threads to close\r'
time.sleep(1)
stop.clear()
print "stop has been triggered and reset... restart"
finally:
print "done!"
there have been a few other threads (pardon the pun) that touched on this, but I haven't found one that directly addresses it and have been flailing for a while.
FYI, the child threads just wait for stop to be set and sleep...
I made small changes to your code to run it standalone. The following does not generate the error for me on a Linux machine. Do you still see the error with it? If so, I'd be glad to improve the answer - please give a few more details about how you are running the code such the operating system in use.
import threading
import termios, sys, tty
import time
def getchar():
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
class Threadtest:
def thread1(self, stop):
stop.wait()
print "returning from thread1"
def thread2(self, stop):
stop.wait()
print "returning from thread2"
tt2 = Threadtest()
stop = threading.Event()
try:
while 1:
t1 = threading.Thread(target=tt2.thread1, args=[stop, ])
t2 = threading.Thread(target=tt2.thread2, args=[stop, ])
while not stop.isSet():
try:
c = getchar()
except IOError: pass
if c == "q":
stop.set()
if c == "x":
stop.set()
sys.exit()
if c == "1":
print "starting t1"
t1.start()
if c == "2":
print "starting t2"
t2.start()
print "waiting for {} threads to close".format(threading.active_count() - 1)
for t in [t1, t2]:
t.join()
stop.clear()
print "stop has been triggered and reset... restart"
finally:
print "done!"
I have a script that looks like (I have imported all required modules.):
def _find_getch():
try:
import termios
except ImportError:
# Non-POSIX. Return msvcrt's (Windows') getch.
import msvcrt
return msvcrt.getch
# POSIX system. Create and return a getch that manipulates the tty.
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
getch = _find_getch()
#keypress for CTRL+X is 88
if len(sys.argv) == 1:
controlPressed = False
file = ''
while not controlPressed:
latest = raw_input("> ")
key = getch
if key == '<function _getch at 0x7f9602558e60>':
controlPressed = True
sys.exit()
I run this script in the terminal and it never senses when I press CTRL+X. How would I sense CTRL+X while raw_input is running?
The _getch function didn't work properly.
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()
I am using this command to disable echo and fetch user input using sys.stdin.read(1)
tty.setcbreak(sys.stdin.fileno())
However during the course of my program I need to again enable and disable console echo. I tried
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
termios.tcsetattr(fd, termios.TCSADRAIN, old)
But that aint working. How can I elegantly enable echo?
ps: I am using code from Python nonblocking console input by mizipzor
Heres the code:
import sys
import select
import tty
import termios
import time
def is_number(s):
try:
float(s)
return True
except ValueError:
return False
def calc_time(traw):
tfactor = {
's': 1,
'm': 60,
'h': 3600,
}
if is_number(g[:-1]):
return float(g[:-1]) * tfactor.get(g[-1])
else:
return None
def isData():
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
old_settings = termios.tcgetattr(sys.stdin)
try:
tty.setcbreak(sys.stdin.fileno())
i = 0
while 1:
print i
i += 1
time.sleep(.1)
if isData():
c = sys.stdin.read(1)
if c:
if c == 'p':
print """Paused. Use the Following commands now:
Hit 'n' to skip and continue with next link.
Hit '5s' or '3m' or '2h' to wait for 5 secs, 3 mins or 3 hours
Hit Enter to continue from here itself.
Hit Escape to quit this program"""
#expect these lines to enable echo back again
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
old[3] = old[3] & termios.ECHO
termios.tcsetattr(fd, termios.TCSADRAIN, old)
g = raw_input("(ENABLE ECHO HERE):")
if g == '\x1b':
print "Escaping..."
break
if g == 'n':
#log error
continue
elif g[-1] in ['s','m','h']:
tval = calc_time(g)
if tval is not None:
print "Waiting for %s seconds."%(tval)
time.sleep(tval)
continue
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
If you take a look at the docs, there's an example there:
http://docs.python.org/library/termios.html#module-termios
You are missing the setting on of the echo flag:
old[3] = old[3] | termios.ECHO
So, the whole thing is:
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
old[3] = old[3] | termios.ECHO
termios.tcsetattr(fd, termios.TCSADRAIN, old)
Writing this:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
Instead of the above 4 lines solved it.