Input() prompt doesn't always go to stdout

Can we fix input()?

I was surprised and baffled when I went to debug and turned on verbose logging.DEBUG output so I could trace the flow post mortem. The application seemed to hang because an input prompt ended up in the log instead of the terminal.

On Linux and Mac I found the behavior to be:

stdout redirected? stder redirected? readline imported prompt location
yes yes no stdout
yes yes yes stdout
no yes no stderr
no yes yes stdout
yes no no stdout
yes no yes stdout

Prior discussion on core development

Personally, I think the suggestion from Please add argument to override stdin/out/err in the input builtin

def input(prompt, /, *, fin=sys.stdin, fout=sys.stdout, ferr=sys.stderr):

would be most straightforward and PEP8 explicit is betterish, but if there’s not consensus on that we should at least update the documentation:

input()

input(prompt, /)

If the prompt argument is present, it is written to standard output without a trailing newline. The function then reads a line from input, converts it to a string (stripping a trailing newline), and returns that. When EOF is read, EOFError is raised. Example:

s = input('--> ')
--> Monty Python's Flying Circus
s
"Monty Python's Flying Circus"
1 Like

When suggesting changes, it’s helpful to be explicit. Can you succinctly say what is wrong with input now? Don’t ask me to read a table and infer the problem.

If you want to change the docs, instead of pasting the paragraph as it is now, tell us how you want to change it.

Based on my reading of the linked-to thread, the real problem is inconsistency. When you call input("prompt: "), does the prompt get written to stderr or stdout?

I’m not sure what the solution is, but IMO the three extra kwargs is not a solution. The input function is simple and shouldn’t need to be micromanaged - if you need full control, do the writing and reading manually.

1 Like

I don’t see it as simple because I don’t know where the prompt is going to end up.

1 Like

The docs says the prompt goes to stdout. That’s not always true.

It’s simple in purpose. It should do one thing and do it well. Personally I would be fine with it sticking to the documented “always write to stdout”, but I don’t think there should be a set of parameters to change what it writes to.

2 Likes

If there are to be a parameters, seems to me they should be “where to send the prompt” and “where to read the input from”. Having and params doesn’t do anything to clear up the confusion if the confusion is over whether the prompt is sent to stdout or stderr!

3 Likes

I think this is a docs issue, but we should make a bolder change: the input function asks the user for data; the mechanism is a detail. Not just stdout/stderr – using stdio streams is an implementation detail.
I’ve seen Web REPLs and GUIs where a dialog box pops up, and I think that’s perfectly fine (as long as you know the usual issues around monkey patching – do this in a framework, not a library).

Here’s my suggestion: [docs preview]
(PR #146161)

6 Likes

Yes, that’s what I mean by it being very simple in purpose. It issues a prompt and asks for input.

TBH that’s a bit of a concern with readline already, but it’s still broadly fine.

I like this. Up front and center is the purpose of the function, and everything else is implementation. Suggestion: To lessen the long string of “If this, it’ll do this; if this, it’ll do this” in the CPython box, I would remove the details about readline and instead say something like: “Imported modules, such as readline, may replace this with more sophisticated implementations providing similar functionality. Systems lacking input/output streams will also override this as required.” (Combining it into the last paragraph.)

Should the “raises EOFError if there’s no input” part be an implementation detail, or should that be a documented feature? I’m thinking feature; that is to say, any implementation of input() that has a concept of “there is no input to provide” should signal this by raising EOFError, and not (say) by returning None. A dialog box might do this if the user has no GUI available, perhaps.

2 Likes

I’m thinking detail. IMO, input should give you the input; failing to do that is an error which can be signaled using any appropriate exception.
Expecting the user to press Ctrl+D/Ctrl+Z to trigger EOF is not very friendly. If you’re sure you want that, and especially if you tell them to do that, you should probably reach for sys.stdin.

:+1: What other exceptions would be appropriate? I literally cannot think of anything other than EOFError that would potentially be a “natural” exception for some form of input query.

The doc update would be an improvement, but it is a lot for what is conceptually a simple function.

Given input() is one of a small set of “batteries included” built-ins, IMHO it should be simple for a Python user on their first day or week, so “if readline is installed is a bit much”.

1 Like

That’s the price of transparency.

I would rather have documentation which tells no lies. Otherwise, readers of the documentation form an incorrect mental model of behaviors and are surprised – negatively – when that model betrays them.

Let the reference docs be reference docs, with all of the unglamorous details. Tutorials can then gloss over those details to provide a more curated experience for beginners.

3 Likes

That’s great, but remember, the docs are trying to handle a function that is meant to be monkeypatched away. It’s impossible for the docs to explain in detail what could happen; import readline replaces it, and other interfaces could do the same. So it should accurately describe the vanilla input() function, and give its intent (which monkeypatchers should maintain), but it shouldn’t be necessary to go into intense detail about “if you import readline, here’s what happens”, beyond simply saying that importing readline will change the UI.

Seems to me the fact that the readline version of writes the prompt to stderr instead of stdout is a bug that should be fixed. It breaks the documented behaviour of the function it’s intended to be a drop-in replacement for.

2 Likes

I was more focused on the suggestion that the docs should try explicitly to target beginners at the expense of being accurate. Reference docs are the wrong place to make that tradeoff.

This reads to me as fundamentally different from what @encukou’s proposed text says.[1]

The proposed text says, in summary: “as an implementation detail of CPython, the standard library readline module replaces the input() builtin.” It doesn’t assert that patching out input() is part of the expectation/contract around input().

I have little enough knowledge about what really is accurate in this case that I’m happy to defer to folks with a bit more relevant experience. But it seems to me that you’re asserting something significantly different from the suggested text, so that’s worth hashing out.

I wouldn’t call it a bug. It’s an intentional change. Writing prompts to stderr is a pretty common CLI behavior, so that a tool can be redirected and still prompt for input.

If we wanted to say that one of the two implementations is wrong, I could point at input() itself, for using stdout.

But I think the ship has long since sailed on this front. Would it really be worthwhile to make input() write to stderr in Python 3.16? A decent quantity of code would break as a result. And applications which want stability in the presence of readline and similar patchers still need to avoid using input().
IMO this is the kind of mild inconsistency which it’s better to live with.


  1. I’m not saying either is incorrect. They are compatible. ↩︎

Applications won’t usually be replacing input, but frameworks absolutely can. In the standard library, import readline will significantly change the behaviour of input() (though technically this isn’t done with monkeypatching I believe, it’s actually implemented at a lower level inside the C implementation). However, it retains the purpose of the function, which is to prompt the user for input in the most logical way available.

By that argument, the builtin should write the prompt to stderr too. Either way, to me it looks like an undesirable inconsistency that should either be straightened out or documented.

Yes.

But I was careful not to suggest that. The whole point of readline replacing input() is to change its behavior. So we shouldn’t expect the two versions to be the same.

I agree that the readline interaction should be documented well.

I think that the prompt should be written to terminal (this is already so on Windows). If only one of stdout or stderr are terminal streams, it should be choosen for output, the question is what to do if both are terminals or none are.

getpass() opens /dev/tty and writes the prompt to it. In glibc, if the terminal cannot be opened, it writes the prompt to stderr. So, I think that stderr should have preference if none of stdout and stderr are terminal streams.