Skip to content

Commit f27f8c7

Browse files
pablogsalambvmgmacias95lysnikolaouhugovk
authored
gh-111201: A new Python REPL (GH-111567)
Co-authored-by: Łukasz Langa <[email protected]> Co-authored-by: Marta Gómez Macías <[email protected]> Co-authored-by: Lysandros Nikolaou <[email protected]> Co-authored-by: Hugo van Kemenade <[email protected]> Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 40cc809 commit f27f8c7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+5328
-170
lines changed

.github/workflows/mypy.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
pull_request:
99
paths:
1010
- ".github/workflows/mypy.yml"
11+
- "Lib/_pyrepl/**"
1112
- "Lib/test/libregrtest/**"
1213
- "Tools/build/generate_sbom.py"
1314
- "Tools/cases_generator/**"
@@ -35,8 +36,9 @@ jobs:
3536
strategy:
3637
matrix:
3738
target: [
39+
"Lib/_pyrepl",
3840
"Lib/test/libregrtest",
39-
"Tools/build/",
41+
"Tools/build",
4042
"Tools/cases_generator",
4143
"Tools/clinic",
4244
"Tools/jit",

.github/workflows/reusable-macos.yml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
HOMEBREW_NO_INSTALL_CLEANUP: 1
2323
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
2424
PYTHONSTRICTEXTENSIONBUILD: 1
25+
TERM: linux
2526
strategy:
2627
fail-fast: false
2728
matrix:

.github/workflows/reusable-ubuntu.yml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
FORCE_COLOR: 1
1818
OPENSSL_VER: 3.0.13
1919
PYTHONSTRICTEXTENSIONBUILD: 1
20+
TERM: linux
2021
steps:
2122
- uses: actions/checkout@v4
2223
- name: Register gcc problem matcher

Doc/glossary.rst

+10-4
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ Glossary
99
.. glossary::
1010

1111
``>>>``
12-
The default Python prompt of the interactive shell. Often seen for code
13-
examples which can be executed interactively in the interpreter.
12+
The default Python prompt of the :term:`interactive` shell. Often
13+
seen for code examples which can be executed interactively in the
14+
interpreter.
1415

1516
``...``
1617
Can refer to:
1718

18-
* The default Python prompt of the interactive shell when entering the
19+
* The default Python prompt of the :term:`interactive` shell when entering the
1920
code for an indented code block, when within a pair of matching left and
2021
right delimiters (parentheses, square brackets, curly braces or triple
2122
quotes), or after specifying a decorator.
@@ -620,7 +621,8 @@ Glossary
620621
execute them and see their results. Just launch ``python`` with no
621622
arguments (possibly by selecting it from your computer's main
622623
menu). It is a very powerful way to test out new ideas or inspect
623-
modules and packages (remember ``help(x)``).
624+
modules and packages (remember ``help(x)``). For more on interactive
625+
mode, see :ref:`tut-interac`.
624626

625627
interpreted
626628
Python is an interpreted language, as opposed to a compiled one,
@@ -1084,6 +1086,10 @@ Glossary
10841086

10851087
See also :term:`namespace package`.
10861088

1089+
REPL
1090+
An acronym for the "read–eval–print loop", another name for the
1091+
:term:`interactive` interpreter shell.
1092+
10871093
__slots__
10881094
A declaration inside a class that saves memory by pre-declaring space for
10891095
instance attributes and eliminating instance dictionaries. Though

Doc/tutorial/appendix.rst

+22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,28 @@ Appendix
1010
Interactive Mode
1111
================
1212

13+
There are two variants of the interactive :term:`REPL`. The classic
14+
basic interpreter is supported on all platforms with minimal line
15+
control capabilities.
16+
17+
On Unix-like systems (e.g. Linux or macOS) with :mod:`curses` and
18+
:mod:`readline` support, a new interactive shell is used by default.
19+
This one supports color, multiline editing, history browsing, and
20+
paste mode. To disable color, see :ref:`using-on-controlling-color` for
21+
details. Function keys provide some additional functionality.
22+
:kbd:`F1` enters the interactive help browser :mod:`pydoc`.
23+
:kbd:`F2` allows for browsing command-line history without output nor the
24+
:term:`>>>` and :term:`...` prompts. :kbd:`F3` enters "paste mode", which
25+
makes pasting larger blocks of code easier. Press :kbd:`F3` to return to
26+
the regular prompt.
27+
28+
When using the new interactive shell, exit the shell by typing :kbd:`exit`
29+
or :kbd:`quit`. Adding call parentheses after those commands is not
30+
required.
31+
32+
If the new interactive shell is not desired, it can be disabled via
33+
the :envvar:`PYTHON_BASIC_REPL` environment variable.
34+
1335
.. _tut-error:
1436

1537
Error Handling

Doc/using/cmdline.rst

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ additional methods of invocation:
4242
* When called with standard input connected to a tty device, it prompts for
4343
commands and executes them until an EOF (an end-of-file character, you can
4444
produce that with :kbd:`Ctrl-D` on UNIX or :kbd:`Ctrl-Z, Enter` on Windows) is read.
45+
For more on interactive mode, see :ref:`tut-interac`.
4546
* When called with a file name argument or with a file as standard input, it
4647
reads and executes a script from that file.
4748
* When called with a directory name argument, it reads and executes an
@@ -1182,6 +1183,15 @@ conflict.
11821183

11831184
.. versionadded:: 3.13
11841185

1186+
.. envvar:: PYTHON_BASIC_REPL
1187+
1188+
If this variable is set to ``1``, the interpreter will not attempt to
1189+
load the Python-based :term:`REPL` that requires :mod:`curses` and
1190+
:mod:`readline`, and will instead use the traditional parser-based
1191+
:term:`REPL`.
1192+
1193+
.. versionadded:: 3.13
1194+
11851195
.. envvar:: PYTHON_HISTORY
11861196

11871197
This environment variable can be used to set the location of a

Doc/whatsnew/3.13.rst

+28
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,34 @@ New typing features:
102102
New Features
103103
============
104104

105+
A Better Interactive Interpreter
106+
--------------------------------
107+
108+
On Unix-like systems like Linux or macOS, Python now uses a new
109+
:term:`interactive` shell. When the user starts the :term:`REPL`
110+
from a tty, and both :mod:`curses` and :mod:`readline` are available,
111+
the interactive shell now supports the following new features:
112+
113+
* colorized prompts;
114+
* multiline editing with history preservation;
115+
* interactive help browsing using :kbd:`F1` with a separate command
116+
history;
117+
* history browsing using :kbd:`F2` that skips output as well as the
118+
:term:`>>>` and :term:`...` prompts;
119+
* "paste mode" with :kbd:`F3` that makes pasting larger blocks of code
120+
easier (press :kbd:`F3` again to return to the regular prompt);
121+
* ability to issue REPL-specific commands like :kbd:`help`, :kbd:`exit`,
122+
and :kbd:`quit` without the need to use call parentheses after the
123+
command name.
124+
125+
If the new interactive shell is not desired, it can be disabled via
126+
the :envvar:`PYTHON_BASIC_REPL` environment variable.
127+
128+
For more on interactive mode, see :ref:`tut-interac`.
129+
130+
(Contributed by Pablo Galindo Salgado, Łukasz Langa, and
131+
Lysandros Nikolaou in :gh:`111201` based on code from the PyPy project.)
132+
105133
Improved Error Messages
106134
-----------------------
107135

Lib/_pyrepl/__init__.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2000-2008 Michael Hudson-Doyle <[email protected]>
2+
# Armin Rigo
3+
#
4+
# All Rights Reserved
5+
#
6+
#
7+
# Permission to use, copy, modify, and distribute this software and
8+
# its documentation for any purpose is hereby granted without fee,
9+
# provided that the above copyright notice appear in all copies and
10+
# that both that copyright notice and this permission notice appear in
11+
# supporting documentation.
12+
#
13+
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
14+
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15+
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
16+
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
17+
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
18+
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
19+
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Lib/_pyrepl/__main__.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import os
2+
import sys
3+
4+
CAN_USE_PYREPL = True
5+
6+
def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
7+
global CAN_USE_PYREPL
8+
if not CAN_USE_PYREPL:
9+
return sys._baserepl()
10+
11+
startup_path = os.getenv("PYTHONSTARTUP")
12+
if pythonstartup and startup_path:
13+
import tokenize
14+
with tokenize.open(startup_path) as f:
15+
startup_code = compile(f.read(), startup_path, "exec")
16+
exec(startup_code)
17+
18+
# set sys.{ps1,ps2} just before invoking the interactive interpreter. This
19+
# mimics what CPython does in pythonrun.c
20+
if not hasattr(sys, "ps1"):
21+
sys.ps1 = ">>> "
22+
if not hasattr(sys, "ps2"):
23+
sys.ps2 = "... "
24+
#
25+
run_interactive = None
26+
try:
27+
import errno
28+
if not os.isatty(sys.stdin.fileno()):
29+
raise OSError(errno.ENOTTY, "tty required", "stdin")
30+
from .simple_interact import check
31+
if err := check():
32+
raise RuntimeError(err)
33+
from .simple_interact import run_multiline_interactive_console
34+
run_interactive = run_multiline_interactive_console
35+
except Exception as e:
36+
print(f"warning: can't use pyrepl: {e}", file=sys.stderr)
37+
CAN_USE_PYREPL = False
38+
if run_interactive is None:
39+
return sys._baserepl()
40+
return run_interactive(mainmodule)
41+
42+
if __name__ == "__main__":
43+
interactive_console()

Lib/_pyrepl/_minimal_curses.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Minimal '_curses' module, the low-level interface for curses module
2+
which is not meant to be used directly.
3+
4+
Based on ctypes. It's too incomplete to be really called '_curses', so
5+
to use it, you have to import it and stick it in sys.modules['_curses']
6+
manually.
7+
8+
Note that there is also a built-in module _minimal_curses which will
9+
hide this one if compiled in.
10+
"""
11+
12+
import ctypes
13+
import ctypes.util
14+
15+
16+
class error(Exception):
17+
pass
18+
19+
20+
def _find_clib():
21+
trylibs = ["ncursesw", "ncurses", "curses"]
22+
23+
for lib in trylibs:
24+
path = ctypes.util.find_library(lib)
25+
if path:
26+
return path
27+
raise ModuleNotFoundError("curses library not found", name="_pyrepl._minimal_curses")
28+
29+
30+
_clibpath = _find_clib()
31+
clib = ctypes.cdll.LoadLibrary(_clibpath)
32+
33+
clib.setupterm.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
34+
clib.setupterm.restype = ctypes.c_int
35+
36+
clib.tigetstr.argtypes = [ctypes.c_char_p]
37+
clib.tigetstr.restype = ctypes.POINTER(ctypes.c_char)
38+
39+
clib.tparm.argtypes = [ctypes.c_char_p] + 9 * [ctypes.c_int] # type: ignore[operator]
40+
clib.tparm.restype = ctypes.c_char_p
41+
42+
OK = 0
43+
ERR = -1
44+
45+
# ____________________________________________________________
46+
47+
48+
def setupterm(termstr, fd):
49+
err = ctypes.c_int(0)
50+
result = clib.setupterm(termstr, fd, ctypes.byref(err))
51+
if result == ERR:
52+
raise error("setupterm() failed (err=%d)" % err.value)
53+
54+
55+
def tigetstr(cap):
56+
if not isinstance(cap, bytes):
57+
cap = cap.encode("ascii")
58+
result = clib.tigetstr(cap)
59+
if ctypes.cast(result, ctypes.c_void_p).value == ERR:
60+
return None
61+
return ctypes.cast(result, ctypes.c_char_p).value
62+
63+
64+
def tparm(str, i1=0, i2=0, i3=0, i4=0, i5=0, i6=0, i7=0, i8=0, i9=0):
65+
result = clib.tparm(str, i1, i2, i3, i4, i5, i6, i7, i8, i9)
66+
if result is None:
67+
raise error("tparm() returned NULL")
68+
return result

0 commit comments

Comments
 (0)