I am attempting to read from keyboard input without waiting for input. The purpose is to be used in an "infinite" loop aka while True:
.
Thus far I've been trying to manipulate the readchar
library https://pypi.python.org/pypi/readchar/0.6 , but with no luck. While it doesn't wait for Enter
, it still waits for some input. I don't want it to wait for input, but simply to check and return ""
or some placeholder if there is no input.
Here is what I've been working with:
def readchar():
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
def main():
while True:
current = readchar()
if current == "some letter":
print("things happen")
The POSIX I/O functions that ultimately underlie Python's file objects have two different modes, blocking and non-blocking, controlled by a flag named O_NONBLOCK
. In particular, the read
function says:
When attempting to read a file … that … has no data currently available … If
O_NONBLOCK
is set, read() shall return -1 and set errno to[EAGAIN]
.
In Python, this flag is available in the os
module.
sys.stdin
is already open, so you can't just pass O_NONBLOCK
to the os.open
function, so… what do you do? Well, you actually might want to open /dev/tty
instead; it kind of depends on what you're actually doing. In that case, the answer is obvious. But let's assume that you don't. So, you want to change the flags of the already-open file. That's exactly what fcntl
is for. You use the F_GETFL
operation to read the current flags, or in the bit for O_NONBLOCK
, and F_SETFL
the result. You can remember the current flags for later if you want to restore things, of course.
In Python, the fcntl
function, and the operation constants, are available in the fcntl
module.
One last problem: sys.stdin
isn't a raw file object, it's a TextIOWrapper
that does Unicode decoding on top of a BufferedReader
, which itself adds buffering on top of a FileIO
. So, sys.stdin.read()
isn't directly calling the POSIX read
function. In order to do that, you need to use sys.stdin.buffer.raw
. And you may also need to do lots of careful flushing if you want to go back and forth between raw and normal input. (Note that this means you're giving up Unicode and instead getting a single-byte bytes
object, which could be, say, half of a UTF-8 character or a quarter of a terminal escape character. Hopefully you're expecting that.)
Now, what does FileIO.read
return when nothing is available? Well, it's an implementation of RawIOBase
, and RawIOBase.read
says:
If 0 bytes are returned, and size was not 0, this indicates end of file. If the object is in non-blocking mode and no bytes are available,
None
is returned.
In other words, you're going to get None
if nothing is available, b''
for EOF, or a single-byte bytes
for anything else.
So, putting it all together:
old_settings = termios.tcgetattr(fd)
old_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
try:
tty.setraw(fd)
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags | os.O_NONBLOCK)
return sys.stdin.buffer.raw.read(1)
finally:
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags)
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
One last thing: Is there a way to tell if input is ready without reading it? Yes, but not portably. Depending on your platform, select
, poll
, epoll
, and/or kqueue
may be available and may work on regular files. read(0)
may be guaranteed to return b''
instead of None
. And so on. You can read your local man pages. But a simpler solution is the same thing C's stdio does: add a 1-byte-max buffer, and use it to implement your own read
and peek
or read
and unread
wrappers.