What's going on with 'quit' in an interactive CPython session (as of 3.12)

August 26, 2024

We're probably all been there at some time or the other:

$ python
[...]
>>> quit
Use quit() or Ctrl-D (i.e. EOF) to exit

It's an infamous and frustrating 'error' message and we've probably all seen it (there's a similar one for 'exit'). Today I was reminded of this CPython behavior by a Fediverse conversation and as I was thinking about it, the penny belatedly dropped on what is going on here in CPython.

Let's start with this:

>>> type(quit)
<class '_sitebuiltins.Quitter'>

In CPython 3.12 and earlier, the CPython interactive interpreter evaulates Python statements; as far as I know, it has little to no special handling of what you type to it, it just evaluates things and then prints the result under appropriate circumstances. So 'quit' is not special syntax recognized by the interpreter, but instead a Python object. The message being printed is not special handling but instead a standard CPython interpreter feature to helpfully print the representation of objects, which the _sitebuiltins.Quitter class has customized to print this message. You can see all of this in Lib/_sitebuiltins.py, along with classes used for some other, related things.

(Then the 'quit' and 'exit' instances are created and wired up in Lib/site.py, along with a number of other things.)

This is changing in Python 3.13 (via), which defaults to using a new interactive shell, which I believe is called 'pyrepl' (see Libs/_pyrepl). Pyrepl has specific support for commands like 'quit', although this support actually reuses the _sitebuiltins code (see REPL_COMMANDS in Lib/_pyrepl/simple_interact.py). Basically, pyrepl knows to call some objects instead of printing their repr() if they're entered alone on a line, so you enter 'quit' and it winds up being the same as if you'd said 'quit()'.


Comments on this page:

By Ewen McNeill at 2024-08-27 07:45:06:

If they'd wanted to make a bare "quit" automagically work, and didn't mind its "repr()" having side effects, it'd have been relatively easy to implement as a special case that was only useful in the repl:

ewen@parthenon:~$ python3.9
Python 3.9.5 (default, Nov 23 2021, 15:27:38) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> quit
Use quit() or Ctrl-D (i.e. EOF) to exit
>>> type(quit)
<class '_sitebuiltins.Quitter'>
>>> class Quitter:
...   def __init__(_):
...     pass
...   def __repr__(_):
...     import sys 
...     sys.exit()
... 
>>> quit = Quitter()
>>> type(quit)
<class '__main__.Quitter'>
>>> quit
ewen@parthenon:~$ 

Presumably there was some "software purity" reason that the Python core developers decided against making "getting the representation of quit" have the side effect of "exiting the program before printing anything". But I'm not convinced that effectively printing a "you're doing it wrong" message was the right UX choice for new users.

Ewen

PS: from some quick testing the above seems to work back through at least Python 2.7.

I would not define repr(quit) to call sys.exit(), because it causes

>>> import _sitebuiltins
>>>_sitebuiltins.__dict__
$
By Ewen McNeill at 2024-08-27 16:58:14:

TIL that calling/printing __dict__ also triggers repr() :-) So that's an amusing, and undesirable, side effect.

But there's no particular need for quit and exit to be in the site builtins anyway. They could just be injected into the REPL namespace when the REPL starts, as custom stand alone objects (ie, quit and exit). It'd mean attempting to inspect those custom objects would also exit, but that's a much smaller edge case. (Also I'd expect there's 100x more user trying to use bare quit or bare exit in the REPL, than there are trying to print out the site builtins dict!)

Ewen

In Common Lisp, I realized this would be a symbol macro after a moment of thought:

(DEFINE-SYMBOL-MACRO QUIT (QUIT))
Written on 26 August 2024.
« How to talk to a local IPMI under FreeBSD 14
Some reasons why we mostly collect IPMI sensor data locally »

Page tools: View Source, View Normal.
Search:
Login: Password:

Last modified: Mon Aug 26 21:33:17 2024
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.