This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: REPL: exit when the user types exit instead of asking them to explicitly type exit()
Type: enhancement Stage: patch review
Components: Library (Lib) Versions: Python 3.11
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: DiddiLeija, FFY00, asmeurer, eric.araujo, eryksun, gregory.p.smith, jack__d, p-ganssle, pablogsal, steven.daprano, terry.reedy, theacodes, tlalexander, veky
Priority: normal Keywords: patch

Created on 2021-07-12 03:15 by theacodes, last changed 2022-04-11 14:59 by admin.

Pull Requests
URL Status Linked Edit
PR 27096 open pablogsal, 2021-07-12 14:19
Messages (55)
msg397273 - (view) Author: Stargirl Flowers (theacodes) Date: 2021-07-12 03:15
Presently, when using REPL if a user types simply "exit", they are greeted with a message instructing them to do it "correctly":

>>> exit
Use exit() or Ctrl-Z plus Return to exit

It comes across as a little surprising that (1) the program knows what I meant and (2) the program told me it wouldn't do it unless I request it in a very specific way. This isn't very user-friendly behavior.

Further surprising is the inconsistent behavior of the other built-ins described on interpreter start-up. "copyright" and "credits" work fine without being invoked as a function, whereas "help" and "license" rebuff the user. 

I know there are compelling technical reasons for the current behavior, however, this behavior is a bit off-putting for newcomers. Knowing the technical reasons behind this behavior made me *more* frustrated than less frustrated.

Python is a lovely language and I think we should do what we can to be friendlier to users. I propose a few changes to fix this specific issue:

(1) Include "exit" in the interpreter startup message, making it: Type "help", "copyright", "credits" or "license" for more information, and type "exit" to quit Python.

(2) Make the interactive interpreter exit when the user types simply "exit" or "quit.

To address some possible edge cases an objections:

- if (2) proves too controversial, we should at least do (1) with the slight modification of "exit" to "exit()".
- if there is a concern about accidentally exiting the interpreter, there are several strategies we can use. First, we can only activate this behavior if, and only if, Python is being used as an interactive interpreter. From what I can tell, main() is already distinguishing this case. Second, if absolutely necessary we could ask the user to confirm that they want to exit. For example:

>>> exit
Are you sure you want to exit Python? (yes/no): 

For what it's worth, I am willing to do this work, however, I might need a little guidance to find the right bits.
msg397275 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 03:40
Thanks, Stargirl for opening this issue and for the thorough description and proposals. I am sympathetic with the idea and the general proposal and (unsurprisingly) I agree with (1).

For (2) there are some challenges here to consider. The most important one is that the mechanism to show those messages is really the repr() of the exit() built-in the one showing the message:

>>> x = exit
>>> repr(x)
'Use exit() or Ctrl-D (i.e. EOF) to exit'

There is no other mechanism in the interpreter that triggers anything when the user inputs that. The most straightforward way is to raise SystemExit from the repr() of the built-in but that has some obvious problems. As printing anything where the exit function lives will also raise SystemExit (for instance printing the builtins module or objects in the GC). 

For these reasons I propose to go with (1) with the slight modification of "exit" to "exit()".
msg397276 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 03:46
For reference, this behaviour lives here:

https://github.com/python/cpython/blob/e14d5ae5447ae28fc4828a9cee8e9007f9c30700/Lib/_sitebuiltins.py#L13-L26
msg397293 - (view) Author: Vedran Čačić (veky) * Date: 2021-07-12 09:43
Of course, the "license" mention should be changed in the same way (in the same message).
msg397294 - (view) Author: Taylor Alexander (tlalexander) Date: 2021-07-12 09:52
Hello all. Curious issue. Thanks Stargirl for opening it.

Would it be possible for the __repr__ function to examine the calling commands and determine if the origin is the special case where exit is typed in the REPL? Then only when Quitter repr is called would the special case be checked. I’m not too familiar with Python internals but I know for example when an exception occurs a stack trace would include information like that. Probably performance of Quitter repr isn’t critical we just don’t want it to have the wrong behavior. But if there’s any way to determine in that call if we’re in this one special case it seems it would be safe to exit.
msg397296 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 10:15
Unfortunately, I don't know how that can help because the stack trace is the same in these two cases:

>>> import traceback
>>> class A:
...   def __repr__(self):
...      traceback.print_stack()

>>> A()
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __repr__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __repr__ returned non-string (type NoneType)

>>> print([A()])
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __repr__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __repr__ returned non-string (type NoneType)

This also becomes a bit tricky if __repr__ is called from C code or from native threads.

In general, the underlying problem is that __repr__ should not have side effects.
msg397297 - (view) Author: Taylor Alexander (tlalexander) Date: 2021-07-12 10:47
Makes sense. Thanks for taking a look.
msg397301 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2021-07-12 12:16
I strongly oppose this change. Merely printing an object should not have a side-effect of this magnitude. Standard Python behaviour is that an object's repr should return a useful string, not exit the interpreter.

This is a backwards-incompatible change: right now, it is perfectly safe to print the builtins namespace in the interactive interpreter:

    vars(builtins)

Doing so gives you are rather large dict, but it is safe and useful. If we make exit actually exit the interpreter instead of print a human readable string, printing the builtins will no longer be safe. It will surprisingly exit in the middle of printing the dict.

`exit` is not magic syntax, it is an actual object. It exists in the builtins namespace. We can put it in lists, we can introspect it and treat it like any other object.

And most importantly, we can print it and get a useful string.

It is not user-friendly to introduce magical special cases. To call a function, Python always requires parentheses. In the builtins and stdlib there are no special cases of functions that magically perform some task just by trying to view it in the interactive interpreter.

(Of course third party code can do anything. If somebody wants the `+` operator to exit the interpreter, or len(obj) to erase their hard drive, Python will allow it.)

Making a handful of objects -- help, exit (quit), license -- behave differently to everything else is not user friendly. Consistency is more important than convenience, especially when it comes to something like exit where the side-effect is to exit the interpreter.

What of copyright and credits? I think their design is a mistake, especially copyright. With 11 lines of output, the copyright object seriously uglifies printing the builtins. But at least it doesn't exit the interpreter.
msg397302 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2021-07-12 12:20
This is a backwards-incompatible change, at the very least it needs an okay from the core devs (and possibly even a PEP) not just a patch.

Stargirl Flowers suggested:

> we could ask the user to confirm that they want to exit

Please, no, that is the very opposite of a user-friendly change! If I call exit() in the REPL, I want to exit.
msg397305 - (view) Author: Stargirl Flowers (theacodes) Date: 2021-07-12 13:08
I don't think we should completely write off the possibility of doing this just because the *current* implementation is counter-intuitive. As I expressed in the original post, the explanation of this behavior is rather unsatisfying to newcomers.

Also @steven.daprano, please do not confuse one recommendation for implementation for the concept.

I agree that printing the Quitter object should not exit the interpreter. However, I disagree that "exit" should not be a special case. Specifically, when using the interactive interpreter the behavior (regardless of implementation strategy) would ideally be:

>>> exit
(interpreter exit)
>>> exit()
(interpreter exit)
>>> print(exit)
Call "exit()" to quit Python. When using the interactive interpreter you can simply type "exit".

This behavior closely matches IPython's behavior, and even a cursory search reveals not only individual users running into this and being frustrated, but even threads where this behavior has reached "meme status": https://twitter.com/0xabad1dea/status/1414204661360472065?s=19
msg397306 - (view) Author: Stargirl Flowers (theacodes) Date: 2021-07-12 13:09
Also, if a PEP is recommended, I will be happy to author it.
msg397307 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 13:26
> However, I disagree that "exit" should not be a special case. 

But a special case of *what*? How would you implement this in a backwards-compatible way?
msg397308 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 13:32
IPython and other reprs are an entire abstraction layer on top of Python, which allows them to do a lot of extra things like implement new commands and alters a lot of behaviours, but the CPython REPL is just the interpreter evaluating commands, and this is very coupled with the regular machinery, at to the point that is the tokenizer the one consuming input (lazily!) from standard input.

Unless I am missing anything, the only way to do the desired behaviour is to re-architect part of how interactive mode works or to directly make exit a keyword, which is backwards incompatible. 

I may be missing simpler ways of course, but in general, my opinion is that anything that involves modifying the compiler pipeline to somehow special case this is too much cost for the advantage.
msg397309 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 13:43
For reference, IPython has an entire interception + filtering mechanism to auto call certain functions:

https://github.com/ipython/ipython/blob/0e4d6390b2174fb1b352a082b72ad387ae696e87/IPython/core/prefilter.py#L414-L425

where exit is one instance of this:

https://github.com/ipython/ipython/blob/81b87f20aa8836b42fbb2b1dee7b662d8d5d46c6/IPython/core/autocall.py#L51-L57

As you can see, this requires an entirely different execution abstraction and a new layer in the middle that filters/intercepts the user input **after** it has been transformed into some intermediate representation.
msg397310 - (view) Author: Paul Ganssle (p-ganssle) * (Python committer) Date: 2021-07-12 13:55
If we want to confine the behavior to just the repl, we could possibly have the repl set an environment variable or something of that nature for interactive sessions, so that `__repr__` of `exit` can tell the difference between being invoked in a REPL and not — though I suppose it could cause some pretty frustrating and confusing behavior if some library function is doing something like this behind the scenes:

```
def get_all_reprs():
    return {
      v: repr(obj) for v, obj in globals()
    ]
```

You could invoke some function and suddenly your shell quits for no apparent reason. And if it only happens when triggered in a REPL, you'd be doubly confused because you can't reproduce it with a script.

I do think the "type exit() to exit" is a papercut. The ideal way to fix it would be in the REPL layer by special-casing `exit`, but I realize that that may introduce unnecessary complexity that isn't worth it for this one thing.

> Second, if absolutely necessary we could ask the user to confirm that they want to exit.

A thought occurs: we could simply re-word the message to make it seem like we're asking for confirmation:

```
>>> exit
Do you really want to exit? Press Ctrl+Z to confirm, or type exit() to exit without confirmation.
```

Then it won't seem as much like we know what you meant to do but aren't doing it, despite the fact that the behavior is exactly the same 😅.
msg397311 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 14:03
> would be in the REPL layer by special-casing `exit`

Unfortunately, there is no REPL *layer* as my previous comments mentioned.

There is a few details that change for interactive mode but fundamentally the pipeline is the same as reading from a file, except that the file is stdin and it has some special logic in the parser to do that in a lazy way and fail fast. But there is no semantic information that separates REPL for non-REPL.
msg397313 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 14:19
One thing we *could* do which is not super invasive, is to match a single AST node of type NAME at the end of Python run. This seems to work but is a bit inelegant:

>>> print(exit)
Use exit() or Ctrl-D (i.e. EOF) to exit
>>> [exit]
[Use exit() or Ctrl-D (i.e. EOF) to exit]
>>> exit
bye!

I have opened a draft with this in PR27096
msg397314 - (view) Author: Stargirl Flowers (theacodes) Date: 2021-07-12 14:26
I do want to be cautious of saying that we can't do it because of the way the REPL is currently implemented- which appears to be an implementation driven by convenience more than necessity.

I also find pushing against special-case behavior in the REPL strange. The REPL already has special-case behavior: printing the header, the __interactivehook__ that configures readline, heck, the `>>>` are unique the REPL and plainly copy-pasting a REPL session into a file won't work.
msg397315 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 14:39
> I do want to be cautious of saying that we can't do it because of the way the REPL is currently implemented- which appears to be an implementation driven by convenience more than necessity.

Apologies if I have not been clear on this. Is not that we can't do it, is just the balance between complexity and the benefits of the change.

> I also find pushing against special-case behavior in the REPL strange. The REPL already has special-case behavior: printing the header, the __interactivehook__ that configures readline, heck, the `>>>` are unique the REPL and plainly copy-pasting a REPL session into a file won't work.

But that is just printing, not semantic behaviour. What we are discussing here is to give a different semantic behaviour to exit NAME only in interactive mode. This is fundamentally different that just printing or make the parser show ">>>" every time it asks for a new line, as those are not changing the *meaning* of Python code.
msg397316 - (view) Author: Stargirl Flowers (theacodes) Date: 2021-07-12 14:42
Fair point about semantic behavior and complexity, but hopefully we can come up with a solution that's easier for users.

I do like the PR suggested.
msg397317 - (view) Author: Paul Ganssle (p-ganssle) * (Python committer) Date: 2021-07-12 14:52
I'm +1 for Pablo's approach. That's approximately what I meant by "special-case it in the REPL layer" anyway.

Are there any downsides to doing it this way? It seems tightly scoped and with minimal overhead.
msg397331 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 15:27
> Are there any downsides to doing it this way? It seems tightly scoped and with minimal overhead.

We also need to support quit(), if we go this route.

It makes parsing in the REPL a bit slower because it needs to check for this at every command and is a bit "floating" in the middle of the parser and the compiler (but that's a consequence that we don't have any defined layer for this). We also need to check that this also works with piping input.

Other than that, only arguments based on the purity of the language, but I think having this working is far more important.
msg397334 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-12 16:22
Since Paul is +1 if another core dev (or devs) are +1 as well with the approach in PR27096 I would feel confident to proceed with this. Alternatively, we could discuss this more generally in python-dev if someone feels that we should have a more open discussion about the tradeoffs.
msg397372 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2021-07-13 00:47
Please don't do this.

On Mon, Jul 12, 2021 at 02:19:58PM +0000, Pablo Galindo Salgado wrote:

> >>> exit
> bye!

This is a user-hostile and unfriendly UI for Python. The Python REPL is 
not a shell like bash etc, it should be safe to evaluate any builtin 
object at the interactive interpreter to view its repr without 
side-effects.

Especially not major side-effects such as exiting the interpreter with 
its total loss of program state.

The only motivation of this change is pure laziness to avoid typing 
parentheses when you want to call an object. I know that the creator of 
Perl famously says that laziness and hubris are virtues for programmers, 
but I disagree. Pandering to laziness in language design is not a 
virtue.

This does not add any new and improved functionality, or make the 
language better, or more friendly for beginners exploring things at the 
REPL. It is a useability regression, making it more hostile and 
unfriendly for people expecting to be able to view objects by entering 
them at the REPL without catastrophic side-effects, and the only benefit 
is to save two characters.

Having said that Pablo, I don't dislike your hack to make the exit repr 
pretend to be a confirmation message anywhere near as much. I don't 
think it is necessary, I think it looks even stranger when displaying 
the builtin namespace dict, but at least it is not a useability 
regression.
msg397373 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2021-07-13 00:53
> Other than that, only arguments based on the purity of the language, 
> but I think having this working is far more important.

Having this "working" is not important at all. This is precisely the 
sort of user-hostile anti-feature that we should avoid, not compromise 
the execution model just to save typing two characters.

I mean, seriously, we're talking about typing *two characters*. If 
people care so much about minimizing the amount of typing when they exit 
the REPL, they can use Ctrl-D or just close the terminal window.
msg397374 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-07-13 01:03
Thanks Steven for your input and your comments and for expressing your concerns. I will hold the PR then until there is consensus on how to proceed and all concerns are addressed (eventually closing it if there isn't consensus).

I'm any case, I think we should at least proceed with the uncontroversial part of the proposal:

> (1) Include "exit" in the interpreter startup message, making it: Type "help()", "copyright", "credits" or "license" for more information, or type "exit()" to quit Python.

This is, including exit in the message and using the form exit() and help() instead of exit and help.
msg397377 - (view) Author: Stargirl Flowers (theacodes) Date: 2021-07-13 01:22
@steven.daprano I appreciate your perspective but you laid out a lot of strong opinions as if they're incontrovertible truths.

The motivation here isn't laziness- I created this bug because I saw actual people across various skill levels that are bugged by this behavior. I don't think that I can accept your declaration that changing this behavior would be user hostile in the face of multiple pieces of user feedback that says the existing behavior is hostile and changing it would be an improvement.

It's not about saving two characters. It's about doing what the user actual means and expects- based on feedback it seems that almost everyone expects "exit" to exit. Your point about being able to inspect objects falls a bit flat when the current behavior is:

>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit

If I weren't an experienced Python developer, I would have no idea that "exit" is actually an object and its __repr__ is what's showing there. To those who haven't ascended to language experts, this just seems like the program chiding us - like responding to "can I have some water" with "*may* you have some water". I didn't type "exit" to view the exit object, I typed it to leave the interpreter. If I *do* want to inspect it, well, there's dir() and help(), which are far, far more useful than the __repr__.
msg397379 - (view) Author: Taylor Alexander (tlalexander) Date: 2021-07-13 01:58
Am I correct in thinking that the proposed change only affects the use case where a user types exit in to the REPL and hits return? And that any other case is unaffected?

I can only imagine that the majority of users who type exit in to the interpreter are expecting the REPL to exit.

Stargirl do I recall you mentioning (perhaps on twitter) that there are threads online of users expressing frustration with this feature? It would be helpful to see what users have said about it.

I would push back against the idea that this is about laziness. It sounds like this is about reducing user confusion.
msg397382 - (view) Author: Vedran Čačić (veky) * Date: 2021-07-13 05:52
> based on feedback it seems that almost everyone expects "exit" to exit

I don't, and I don't remember being asked.

Let me be clear: if exit were a Python keyword, then maybe I would expect that. Or at least, I could convince myself to expect that. But exit is _not_ a keyword, it is not even a builtin, and there is a _strong_ reason for that. One of Guido's many valuable insights about language design is that you shouldn't preclude other uses of names you find convenient for your builtins. See the last line of Zen of Python.

And yes, I _have_ used exit as a name for my objects (e.g. as a flag in Pygame, set exit = False at the beginning and exit = True somewhere in the loop when I find out that the game is over). Although I don't recall ever actually typing `exit` into Python REPL to inspect its value, I really think that scenario is plausible (for example, if a game exited prematurely, and I wanted to see whether exit was set to True prematurely, or there was some other reason), and it would _annoy me immensely_ if instead of showing me the status of the flag it would drop me out of Python.

In fact, you're proposing to use exit as a keyword, but lying about it to the users. If it were really so important, then it _should_ be a keyword, and at least I'd know that I can't use it for my variables anymore. (It's not the first time such a thing would happen. The same thing happened with `async` a few years ago.) But please don't introduce those "keywords just in a particular context", they are horrible from the perspective of usability.
msg397403 - (view) Author: Paul Ganssle (p-ganssle) * (Python committer) Date: 2021-07-13 14:20
> In fact, you're proposing to use exit as a keyword, but lying about it to the users. If it were really so important, then it _should_ be a keyword, and at least I'd know that I can't use it for my variables anymore. (It's not the first time such a thing would happen. The same thing happened with `async` a few years ago.) But please don't introduce those "keywords just in a particular context", they are horrible from the perspective of usability.

We already have so-called "soft keywords", e.g. `match`, so the horse is out of the barn at this point.

I'm not sure why this is closed as rejected — I don't see any decision one way or the other in this thread or on the PR, did I miss it?

I am struggling to understand how this is a user-hostile change; it is not unreasonable for a REPL to have some commands for interacting with the REPL which are not Python functions. I have accidentally typed `exit` instead of `exit()` many times, and one of the reasons I and many others like IPython is that `exit` exits the REPL. It has never once caused a problem for me, as far as I can tell. I cannot imagine that it is a common scenario for someone to type "exit" in order to inspect the "exit" object — it doesn't even have a useful repr!

The only reason you'd do this would be if you knew what it does and were demonstrating it, or if you were exploring what the various builtins are (which I think is very rare, and you'd probably only have to learn that lesson once).

Vedran's point, however, that you could do `exit = some_func()` and then type `exit` to try and inspect the `exit` object is a solid one. That said, I think we can get around this fairly easily (albeit with the cost of some additional complexity in the "handle the exit keyword" function) — if there's a single AST node that is a name and the name is "exit" or "quit", the REPL inspects locals and globals to see if the object referred to is a Quitter, and if so it exits, otherwise pass through the command as normal (possibly raising a warning like, "Did you mean to exit? You have shadowed the `exit` builtin, so use Ctrl-Z/Ctrl-D to exit or delete your `exit` object and try again").

I understand the arguments for purity and explicability and I'm often one of the first people to argue for keeping things consistent and understandable, but this is one of those things where we could significantly improve user experience for no practical cost. We can identify with very high certainty the situations in which a user intended to exit the REPL, we should go ahead and do it to provide a more intuitive REPL experience.
msg397404 - (view) Author: Paul Ganssle (p-ganssle) * (Python committer) Date: 2021-07-13 14:21
At this point I think we should probably start a thread on python-dev to see how people feel about it. I'd be happy to author or co-author a PEP for this if need be.
msg397422 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2021-07-13 16:01
Related 2005 python-dev discussion: https://mail.python.org/archives/list/python-dev@python.org/thread/VNGY2DLML4QJUXE73JLVBIH5WFBZNIKG/
msg397425 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2021-07-13 16:16
and the related issue: https://bugs.python.org/issue1446372
msg397427 - (view) Author: Paul Ganssle (p-ganssle) * (Python committer) Date: 2021-07-13 16:26
Re-opening this because I think the discussion is not done and I don't see any reason why this was rejected.

> Related 2005 python-dev discussion: https://mail.python.org/archives/list/python-dev@python.org/thread/VNGY2DLML4QJUXE73JLVBIH5WFBZNIKG/

@Mark Thanks for digging these up! From what I can tell, that discussion ended up with a combination of there not being quite enough enthusiasm for the idea to drive it forward and no one coming up with a good way to localize the effect to just the case where we know the person was trying to type "exit" in a REPL.

I think Pablo's patch shows that a very limited addition to the "REPL layer" is actually plausible, and we *could* implement this without taking on an enormous amount of additional complexity or affecting non-interactive use cases.

Fernando's point about it being dangerous to generalize this additional layer of "interactive-use only" keywords is a good one (see: https://mail.python.org/archives/list/python-dev@python.org/message/L37RD7SG26IOBETPI7TETKFGHPAPC75Q/), though it seems that it was this thread that prompted him to add exit/quit as auto-call magic keywords to IPython, and I think that has worked out in the intervening 16 years. I don't think there's much danger of us wanting to generalize this concept, since the only really compelling argument for doing it this way for exit/quit is that almost everyone seems to think it *should* work this way (despite it never having worked this way, and there not being any equivalents), and gets tripped up when it doesn't.

> and the related issue: https://bugs.python.org/issue1446372

Looks to me like that is an issue for adding the message when you type "exit". There's no additional discussion disqualifying the use of "exit" as an interactive-only keyword.
msg397458 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2021-07-14 02:54
Agreed with Paul, this is a good idea UX wise for interactive interpreters.  IPython proved that well over a decade ago.  Thanks for the historical links!
msg397461 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2021-07-14 04:26
On Tue, Jul 13, 2021 at 01:58:30AM +0000, Taylor Alexander wrote:

> I would push back against the idea that this is about laziness. It 
> sounds like this is about reducing user confusion.

Users aren't *confused* by the instructions, which are clear and simple: 
call the object using parentheses. Nobody says that they don't 
understand what it means to "use exit() to exit".

They are *annoyed* that they have to type the parentheses. This is not 
confusion, and it is disengenious to claim that people are "confused".

Especially when people have said that they understand the technical 
reasons for why exit behaves as it does, and that makes them "more 
frustrated". So they understand the reasoning why having repr(exit) kill 
the interpreter is a bad idea, and rather than satisfying them, they get 
even more annoyed.
msg397462 - (view) Author: Jack DeVries (jack__d) * Date: 2021-07-14 04:30
I wonder if the middle ground here is to let it be a teachable moment, and to inform the user by having the string returned by __repr__ be a bit more descriptive. Currently, it is:

> Use exit() or Ctrl-Z plus Return to exit

I propose:

> exit is the function that closes Python when called. To call a Python function, add parenthesis! For example, "exit()".

To share a personal anecdote, Python was my first programming language. I can remember this specific case of REPL-stubbornness being instrumental in teaching me about referencing versus calling a function. Special cases cause confusion, and a shortcut that removes two characters at the expense of skirting past an essential understanding is not the right choice. The place we should be *most* careful about breaking language idioms are in the spots that are exposed to beginners and newcomers to the language.
msg397505 - (view) Author: Aaron Meurer (asmeurer) Date: 2021-07-14 20:10
When talking about making exit only work when typed at the interpreter, something to consider is the confusion that it can cause when there is a mismatch between the interactive interpreter and noninteractive execution, especially for novice users. I've seen beginner users add exit() to the bottom of Python scripts, presumably because the interpreter "taught" them that you have to end with that. 

Now imagine someone trying to use exit as part of control flow 

if input("exit now? ") == "yes":
    exit

Unless exit is a full blown keyword, that won't work. And the result is yet another instance in the language where users become confused if they run across it, because it isn't actually consistent in the language model. 

There are already pseudo-keywords in the language, in particular, super(), but that's used to implement something which would be impossible otherwise. Exiting is not impossible otherwise, it just requires typing (). But that's how everything in the language works. I would argue it's a good thing to reinforce the idea that typing a variable by itself with no other surrounding syntax does nothing. This helps new users create the correct model of the language in their heads.
msg397507 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2021-07-14 20:54
> the confusion that it can cause when there is a mismatch between the interactive interpreter and noninteractive execution

I've witnessed similar confusion when teaching, using IPython. After discovering that you can do

    In [1]: import pandas as pd
    In [2]: cd datafiles
    /Users/mdickinson/Desktop/datafiles
    In [3]: df = pd.read_csv("experiment0023.csv")

it's then a common error to copy those lines to a script and expect them to work. We learned to recommend that IPython's automagic always be turned off, so that at least we could easily explain that "if it starts with a %, it's a magic command interpreted by the IPython layer; otherwise it's passed to Python".

If Python grew a similar interpreter layer (which seems like one possible solution here), I think we'd have the same issue of making it easy for users to distinguish "commands" intended for the interactive interpreter from those interpreted by the Python core. I could imagine ending up with users typing "%exit" or "!exit" to exit, but one you're adding an extra sigil to distinguish your commands from plain old references to Python objects it doesn't seem so different from having to type "exit()".
msg397512 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2021-07-14 22:39
On Wed, Jul 14, 2021 at 08:10:51PM +0000, Aaron Meurer wrote:

> There are already pseudo-keywords in the language, in particular, 
> super()

super is not a pseudo-keyword. It's a regular builtin object that 
interacts with some (quite clever) compiler magic that occurs when 
classes are created.

https://stackoverflow.com/questions/19608134/why-is-python-3-xs-super-magic

The big difference here is that the magic behind super helps to prevent 
serious bugs in user code. super's magic isn't to reduce typing, it is 
to solve a number of real problems with the way people use inheritence.
msg397516 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2021-07-14 23:13
Thanks Mark, that's a good real world experience example from the IPython side.
msg397685 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2021-07-17 03:32
This has been proposed and rejected before.  So I think a pydev discussion and steering council decision would be needed to change.

The current rule in interactive mode is that typing an expression statement echoes the representation of the resulting object.  The proposal is make a rather startling exception to the rule.  The premise is that "the program knows what I meant".

However, that is a too-successful illusion.  'The program' has no idea what one meant other than to print the repr(ob).  Some coredevs had the idea to *guess* what was meant and to give two functions an exceptional representation with a message that would be correct when the guess is correct.

In the other hand, special-casing 'quit\n' and 'exit\n' could be seen as analogous to special-casing '^Z\n'.  And the patch restricts the special casing, without complicated code, to exactly those sequences of keystrokes.
msg397691 - (view) Author: Vedran Čačić (veky) * Date: 2021-07-17 03:49
> In the other hand, special-casing 'quit\n' and 'exit\n' could be seen as analogous to special-casing '^Z\n'

Terry, there is a big difference between special-casing 'exit\n' and special-casing '^Z\n': 'exit' is a perfectly valid identifier (and people use it regularly), ^Z is not. Especially if 'exit\n' exited unconditionally, I think many people would be very frustrated and surprised when trying to inspect the variable called 'exit'.

I'd have no objection if something like '!exit' was special-cased, but then there is not much difference between adding a bang before and adding the parentheses after. Alternatively, exit can be proclaimed a keyword, but I really think this is overkill.

And please don't think this process that you're starting now will stop at these two words. Much more beginners, according to my experience, try to type `pip install something` inside Python REPL. If we do this, it will be a powerful precedent, and almost surely we will have the "magic mess" later.
msg397698 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2021-07-17 04:44
I agree that turning 'exit' and 'quit' into semi-keywords is not acceptible.  I added this to my PR review.

>>> exit = 3
>>> exit

f:\dev\3x>
msg397699 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2021-07-17 04:59
Another issue: exit() and quit() work unconditionally when called, regardless of the context:  "a = (3, exit(), 'abc')".  The abbreviated versions will not.

An alternative change is to revise the representation.  Perhaps tell the truth first by giving the standard representation -- <class '_sitebuiltins.Quitter'> --  so that people might recognize that they are just seeing a printed string.  Then add on the next line, "If you want to exit, enter 'exit' ...".
msg402590 - (view) Author: Filipe Laíns (FFY00) * (Python triager) Date: 2021-09-24 22:18
One technical argument on why it would be beneficial to have custom handling like this for exit is that exit is a site-builtin, not a builtin.

$ python -S
Python 3.9.7 (default, Aug 31 2021, 13:28:12)
[GCC 11.1.0] on linux
>>> exit()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'exit' is not defined
>>> import sys
>>> sys.exit()
msg402605 - (view) Author: Vedran Čačić (veky) * Date: 2021-09-25 05:44
> why it would be beneficial to have custom handling like this for exit is that exit is a site-builtin, not a builtin.

In my view, that's exactly why it _shouldn't_ have a special treatment. After all, site can add many more builtins. Do you want all of them to have autocall?
msg402620 - (view) Author: Filipe Laíns (FFY00) * (Python triager) Date: 2021-09-25 13:18
> In my view, that's exactly why it _shouldn't_ have a special treatment. After all, site can add many more builtins. Do you want all of them to have autocall?

No, and I did not suggest anything of the sort. I just want the exit because of its integral job of... exiting the REPL. Typing `import sys; sys.exit()` every time I want to test something quick on the REPL is awful UX.
msg402623 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-09-25 13:56
> Typing `import sys; sys.exit()` every time I want to test something quick on the REPL is awful UX.

Without disagreeing with the general sentiment, just note that you can always do Ctrl-D.
msg402644 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2021-09-25 20:00
Running the REPL with -S is unusual, so having to use sys.exit() or `raise SystemExit` in that case shouldn't be an issue. 

A user who wants custom behavior for `exit` could override sys.displayhook() in the PYTHONSTARTUP file. For example:

    import sys
    import builtins

    def displayhook(obj, prev_displayhook=sys.displayhook):
        exit = getattr(builtins, 'exit', None)
        if obj is exit and callable(exit):
            exit()
        else:
            prev_displayhook(obj)

    sys.displayhook = displayhook

> just note that you can always do Ctrl-D.

For the Windows console, Ctrl-D is not usually supported. It's supported when pyreadline is installed. Otherwise one has to type Ctrl-Z and enter. In IDLE it's Ctrl-D even in Windows, in which case the `exit` repr is wrong, as determined by setquit() in Lib/site.py.
msg402655 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2021-09-26 04:49
> Typing `import sys; sys.exit()` every time I want to test something 
> quick on the REPL is awful UX.

It truly is awful. So why do you do it that way?

There are at least four other ways to cleanly exit the REPL.

1. raise SystemExit

2. exit()

3. quit()

4. Ctrl-D (posix systems) or Ctrl-Z ENTER (Windows systems)

Even if you are running the REPL without the site module (so that exit 
and quit are not available) the import sys solution is surely the worst 
of the lot, UX-wise.
msg402664 - (view) Author: Filipe Laíns (FFY00) * (Python triager) Date: 2021-09-26 15:58
> Without disagreeing with the general sentiment, just note that you can always do Ctrl-D.

That is true, but there are a couple setups where that doesn't work (those keypresses are consumed by something else). I may not be a good data point though.

> Running the REPL with -S is unusual, so having to use sys.exit() or `raise SystemExit` in that case shouldn't be an issue. 

Yes, it is unusual, however I have found myself asking people to do that when debugging, and I always have to tell people "oh, btw, exit() doesn't work, you have to do ...", which is not nice.

> Even if you are running the REPL without the site module (so that exit 
and quit are not available) the import sys solution is surely the worst 
of the lot, UX-wise.

Two of the solutions you gave (exit and quit) don't work with -S, and Ctrl-D/Ctrl-Z doesn't work in all setups. raise SystemExit is the only real alternative I would consider when I am explaining things to people and want to avoid issues. I standardized in `import sys; sys.exit()` as it's generally easier for people starting out, even though it's 5 more keypresses.

---

Anyway, my point is simply that exiting on python -S does not have a great UX, something that I think should be considered when looking at this proposal. If you think the use-case is relevant enough to not warrant a change like this, that is a valid opinion.

My personal opinion is that we should optimize the UX for the people who are not that knowledgeable, as those are the ones that will have most trouble, while keeping it usable for the rest of users.
I think this change is a net positive considering those parameters, and I think the arguments against this proposal have not properly taken them into account.

FWIW, I consider myself a reasonably knowledgeable user, but still end up making this mistake myself a staggering amount of times.

$ rg 'exit\(\)$' ~/.python_history | wc -l
553
$ rg 'exit$' ~/.python_history | wc -l
132
msg402667 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2021-09-26 16:24
> That is true, but there are a couple setups where that doesn't work 
> (those keypresses are consumed by something else). I may not be a good 
> data point though.

Can you give an example of a setup where Ctrl-D is consumed but "import 
sys ENTER sys.exit() ENTER" is not?
msg402701 - (view) Author: Vedran Čačić (veky) * Date: 2021-09-27 11:58
Just wanted to say that 
"raise SystemExit" is shorter than 
"import sys; sys.exit()", has no special characters (just letters and space) and is really much quicker to write. Yes, it doesn't work if someone rebound SystemExit, but if that's your problem, you have weird coworkers. ;-)
msg403359 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2021-10-07 08:08
Steven's list left out the standard way of closing *any* windowed app -- click the close button on the title bar.  Works on all major systems.

Its does a little too much when python is started on a command line (by closing the console), but beginners, at least on Windows, usually click an icon to start Python.
History
Date User Action Args
2022-04-11 14:59:47adminsetgithub: 88769
2021-10-07 08:08:05terry.reedysetmessages: + msg403359
2021-09-27 11:58:22vekysetmessages: + msg402701
2021-09-26 16:24:28steven.dapranosetmessages: + msg402667
2021-09-26 15:58:29FFY00setmessages: + msg402664
2021-09-26 04:49:25steven.dapranosetmessages: + msg402655
2021-09-25 20:00:38eryksunsetnosy: + eryksun
messages: + msg402644
2021-09-25 13:56:37pablogsalsetmessages: + msg402623
2021-09-25 13:18:16FFY00setmessages: + msg402620
2021-09-25 05:44:59vekysetmessages: + msg402605
2021-09-24 22:18:26FFY00setmessages: + msg402590
2021-08-24 01:34:00DiddiLeijasetnosy: + DiddiLeija
2021-08-20 09:36:10mark.dickinsonsetnosy: - mark.dickinson
2021-07-17 04:59:21terry.reedysetmessages: + msg397699
2021-07-17 04:44:23terry.reedysetmessages: + msg397698
2021-07-17 03:49:43vekysetmessages: + msg397691
2021-07-17 03:32:43terry.reedysetnosy: + terry.reedy
messages: + msg397685
2021-07-14 23:13:09gregory.p.smithsetmessages: + msg397516
2021-07-14 22:39:41steven.dapranosetmessages: + msg397512
2021-07-14 20:54:53mark.dickinsonsetmessages: + msg397507
2021-07-14 20:10:51asmeurersetnosy: + asmeurer
messages: + msg397505
2021-07-14 04:30:18jack__dsetnosy: + jack__d
messages: + msg397462
2021-07-14 04:26:41steven.dapranosetmessages: + msg397461
2021-07-14 02:54:34gregory.p.smithsettype: behavior -> enhancement
stage: patch review
2021-07-14 02:54:12gregory.p.smithsetnosy: + gregory.p.smith
messages: + msg397458
2021-07-14 00:58:57eric.araujosetnosy: + eric.araujo
components: + Library (Lib), - Demos and Tools
2021-07-13 16:26:22p-gansslesetstage: resolved -> (no value)
2021-07-13 16:26:07p-gansslesetstatus: closed -> open
resolution: rejected ->
messages: + msg397427
2021-07-13 16:16:50mark.dickinsonsetmessages: + msg397425
2021-07-13 16:01:52mark.dickinsonsetnosy: + mark.dickinson
messages: + msg397422
2021-07-13 14:21:19p-gansslesetmessages: + msg397404
2021-07-13 14:20:17p-gansslesetmessages: + msg397403
2021-07-13 06:01:12theacodessetstatus: open -> closed
resolution: rejected
stage: resolved
2021-07-13 05:52:34vekysetmessages: + msg397382
2021-07-13 01:58:30tlalexandersetmessages: + msg397379
2021-07-13 01:22:38theacodessetmessages: + msg397377
2021-07-13 01:03:52pablogsalsetmessages: + msg397374
2021-07-13 00:53:58steven.dapranosetmessages: + msg397373
2021-07-13 00:47:24steven.dapranosetmessages: + msg397372
2021-07-12 16:22:15pablogsalsetmessages: + msg397334
2021-07-12 15:27:40pablogsalsetmessages: + msg397331
2021-07-12 14:52:52p-gansslesetmessages: + msg397317
2021-07-12 14:42:48theacodessetmessages: + msg397316
2021-07-12 14:39:05pablogsalsetmessages: + msg397315
2021-07-12 14:26:54theacodessetmessages: + msg397314
2021-07-12 14:19:58pablogsalsetmessages: + msg397313
stage: patch review -> (no value)
2021-07-12 14:19:50pablogsalsetkeywords: + patch
stage: patch review
pull_requests: + pull_request25644
2021-07-12 14:03:12pablogsalsetmessages: + msg397311
2021-07-12 13:55:12p-gansslesetnosy: + p-ganssle
messages: + msg397310
2021-07-12 13:43:51pablogsalsetmessages: + msg397309
2021-07-12 13:32:17pablogsalsetmessages: + msg397308
2021-07-12 13:26:04pablogsalsetmessages: + msg397307
2021-07-12 13:09:10theacodessetmessages: + msg397306
2021-07-12 13:08:46theacodessetmessages: + msg397305
2021-07-12 12:20:45steven.dapranosetstage: needs patch -> (no value)
messages: + msg397302
versions: + Python 3.11
2021-07-12 12:16:45steven.dapranosetnosy: + steven.daprano
messages: + msg397301
2021-07-12 10:47:43tlalexandersetmessages: + msg397297
2021-07-12 10:15:11pablogsalsetmessages: + msg397296
2021-07-12 09:52:07tlalexandersetnosy: + tlalexander
messages: + msg397294
2021-07-12 09:43:17vekysetnosy: + veky
messages: + msg397293
2021-07-12 08:10:34FFY00setnosy: + FFY00

type: behavior
stage: needs patch
2021-07-12 03:46:25pablogsalsetmessages: + msg397276
2021-07-12 03:40:03pablogsalsetnosy: + pablogsal
messages: + msg397275
2021-07-12 03:15:45theacodescreate