Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

sys.stdin.readline() reads without prompt, returning 'nothing in between'

I have a function that executes the following (among other things):

userinput = stdin.readline()
betAmount = int(userinput)

Is supposed to take input integer from stdin as a string and convert it to an integer.

When I call the function, however, it returns a single newline character (it doesn't even wait for me to input anything).

Earlier in the program I get some input in the form of:

stdin.read(1)

to capture a single character.

Could this have something to do with it? Am I somehow writing a newline character to the next line of stdin?

How can I fix this?

like image 500
lightandlight Avatar asked Jan 13 '14 08:01

lightandlight


People also ask

What does Sys stdin readline () do?

In Python, the readlines() method reads the entire stream, and then splits it up at the newline character and creates a list of each line. The above creates a list called lines, where each element will be a line (as determined by the end of line character).

Is SYS stdin readline faster than input?

stdin. readline() is the fastest one when reading strings and input() when reading integers.


2 Answers

stdin.read(1) reads one character from stdin. If there was more than one character to be read at that point (e.g. the newline that followed the one character that was read in) then that character or characters will still be in the buffer waiting for the next read() or readline().

As an example, given rd.py:

from sys import stdin

x = stdin.read(1)
userinput = stdin.readline()
betAmount = int(userinput)
print ("x=",x)
print ("userinput=",userinput)
print ("betAmount=",betAmount)

... if I run this script as follows (I've typed in the 234):

C:\>python rd.py
234
x= 2
userinput= 34

betAmount= 34

... so the 2 is being picked up first, leaving the 34 and the trailing newline character to be picked up by the readline().

I'd suggest fixing the problem by using readline() rather than read() under most circumstances.

like image 165
Simon Avatar answered Sep 23 '22 00:09

Simon


Simon's answer and Volcano's together explain what you're doing wrong, and Simon explains how you can fix it by redesigning your interface.

But if you really need to read 1 character, and then later read 1 line, you can do that. It's not trivial, and it's different on Windows vs. everything else.

There are actually three cases: a Unix tty, a Windows DOS prompt, or a regular file (redirected file/pipe) on either platform. And you have to handle them differently.

First, to check if stdin is a tty (both Windows and Unix varieties), you just call sys.stdin.isatty(). That part is cross-platform.

For the non-tty case, it's easy. It may actually just work. If it doesn't, you can just read from the unbuffered object underneath sys.stdin. In Python 3, this just means sys.stdin.buffer.raw.read(1) and sys.stdin.buffer.raw.readline(). However, this will get you encoded bytes, rather than strings, so you will need to call .decode(sys.stdin.decoding) on the results; you can wrap that all up in a function.

For the tty case on Windows, however, input will still be line buffered even on the raw buffer. The only way around this is to use the Console I/O functions instead of normal file I/O. So, instead of stdin.read(1), you do msvcrt.getwch().

For the tty case on Unix, you have to set the terminal to raw mode instead of the usual line-discipline mode. Once you do that, you can use the same sys.stdin.buffer.read(1), etc., and it will just work. If you're willing to do that permanently (until the end of your script), it's easy, with the tty.setraw function. If you want to return to line-discipline mode later, you'll need to use the termios module. This looks scary, but if you just stash the results of termios.tcgetattr(sys.stdin.fileno()) before calling setraw, then do termios.tcsetattr(sys.stdin.fileno(), TCSAFLUSH, stash), you don't have to learn what all those fiddly bits mean.

On both platforms, mixing console I/O and raw terminal mode is painful. You definitely can't use the sys.stdin buffer if you've ever done any console/raw reading; you can only use sys.stdin.buffer.raw. You could always replace readline by reading character by character until you get a newline… but if the user tries to edit his entry by using backspace, arrows, emacs-style command keys, etc., you're going to get all those as raw keypresses, which you don't want to deal with.

like image 44
abarnert Avatar answered Sep 25 '22 00:09

abarnert