[3.14] gh-156234: Fix and rewrite the curses documentation on reading (GH-156235) (GH-156280)

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/9b7eb47c0766fb9c62ee47d0ad0d16d877154fae
commit: 9b7eb47c0766fb9c62ee47d0ad0d16d877154fae
branch: 3.14
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-23T12:20:30Z
summary:

[3.14] gh-156234: Fix and rewrite the curses documentation on reading (GH-156235) (GH-156280)

Fix wrong types: instr() and getstr() return a bytes object, not a str, and
their n limits the number of bytes; getkey() returns a str; unctrl() returns
a bytes object.  Make clear whether an integer standing for a character is an
encoded byte or a character code.

Rewrite the documentation of getch(), get_wch(), getkey(), getstr() and
instr(), following X/Open Curses.

(cherry picked from commit be87bfa8a1a68516cedc618c7cf5bb7b0b251678)

files:
M Doc/library/curses.rst
M Modules/_cursesmodule.c
M Modules/clinic/_cursesmodule.c.h

diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst
index 614e2fa20d03da..cd157403adf9ca 100644
--- a/Doc/library/curses.rst
+++ b/Doc/library/curses.rst
@@ -30,6 +30,8 @@ Linux and the BSD variants of Unix.
 
    Whenever the documentation mentions a *character* it can be specified
    as an integer, a one-character Unicode string or a one-byte byte string.
+   An integer is the code of a single encoded byte, optionally combined with
+   attributes and a color pair, as returned by :meth:`window.inch`.
 
    Whenever the documentation mentions a *character string* it can be specified
    as a Unicode string or a byte string.
@@ -505,8 +507,8 @@ The module :mod:`!curses` defines the following functions:
 .. function:: putp(str)
 
    Equivalent to ``tputs(str, 1, putchar)``; emit the value of a specified
-   terminfo capability for the current terminal.  Note that the output of :func:`putp`
-   always goes to standard output.
+   terminfo capability, a bytes object, for the current terminal.
+   Note that the output of :func:`putp` always goes to standard output.
 
    :func:`setupterm` (or :func:`initscr`) must be called first.
 
@@ -677,7 +679,7 @@ The module :mod:`!curses` defines the following functions:
 .. function:: tparm(str[, ...])
 
    Instantiate the bytes object *str* with the supplied parameters, where *str* should
-   be a parameterized string obtained from the terminfo database.  For example,
+   be a parameterized byte string obtained from the terminfo database.  For example,
    ``tparm(tigetstr("cup"), 5, 3)`` could result in ``b'\033[6;4H'``, the exact
    result depending on terminal type.  Up to nine integer parameters may be supplied.
 
@@ -698,7 +700,8 @@ The module :mod:`!curses` defines the following functions:
 
 .. function:: unctrl(ch)
 
-   Return a bytes object which is a printable representation of the character *ch*.
+   Return a bytes object which is a printable representation of the character *ch*;
+   any attributes and color pair are ignored.
    Control characters are represented as a caret followed by the character, for
    example as ``b'^C'``. Printing characters are left as they are.
 
@@ -707,6 +710,9 @@ The module :mod:`!curses` defines the following functions:
 
    Push *ch* so the next :meth:`~window.getch` will return it.
 
+   *ch* may be an integer (a key code or the code of an encoded byte), a byte,
+   or a string of length 1 which encodes to a single byte.
+
    .. note::
 
       Only one *ch* can be pushed before :meth:`!getch` is called.
@@ -724,6 +730,9 @@ The module :mod:`!curses` defines the following functions:
 
    Push *ch* so the next :meth:`~window.get_wch` will return it.
 
+   *ch* may be an integer (a character code, not a key code) or a string of
+   length 1.
+
    .. note::
 
       Only one *ch* can be pushed before :meth:`!get_wch` is called.
@@ -1004,27 +1013,58 @@ Window objects
 
 .. method:: window.getch([y, x])
 
-   Get a character. Note that the integer returned does *not* have to be in ASCII
-   range: function keys, keypad keys and so on are represented by numbers higher
-   than 255.  In no-delay mode, return ``-1`` if there is no input, otherwise
-   wait until a key is pressed.
+   Read a key press, after moving the cursor to *y*, *x* if specified,
+   and return it as an integer.
+   The window is refreshed first if it is not a pad and was modified since
+   the last refresh.
+   Wait until a key is pressed, or return ``-1`` if the read is non-blocking
+   or times out (see :meth:`nodelay` and :meth:`timeout`).
+
+   An ordinary key is returned as the code of a single byte of its encoding
+   in the current locale,
+   so a character encoded with several bytes takes several calls.
+   For example, in a UTF-8 locale ``'é'`` is read as ``195``, then ``169``.
+   Use :meth:`get_wch` to read it as a single character.
+
+   In keypad mode (see :meth:`keypad`) function keys and other special keys
+   are returned as one of the :ref:`KEY_* constants <curses-key-constants>`,
+   which cannot be mistaken for an ordinary key.
+   Otherwise, or if their escape sequence does not arrive in time
+   (see :meth:`notimeout` and :func:`set_escdelay`),
+   their bytes are returned one at a time.
+
+   In echo mode (see :func:`echo`) the key is added to the window as by
+   :meth:`addch`; special keys are not echoed.
 
 
 .. method:: window.get_wch([y, x])
 
-   Get a wide character. Return a character for most keys, or an integer for
-   function keys, keypad keys, and other special keys.
-   In no-delay mode, raise an exception if there is no input.
+   Read a key press, after moving the cursor to *y*, *x* if specified,
+   and return it as a one-character :class:`str`.
+   The window is refreshed first if it is not a pad and was modified since
+   the last refresh.
+   Wait until a key is pressed, or raise :exc:`error` if the read is
+   non-blocking or times out (see :meth:`nodelay` and :meth:`timeout`).
+
+   In keypad mode (see :meth:`keypad`) function keys and other special keys
+   are returned as one of the :ref:`KEY_* constants <curses-key-constants>`,
+   an integer.
+   Otherwise, or if their escape sequence does not arrive in time
+   (see :meth:`notimeout` and :func:`set_escdelay`),
+   their characters are returned one at a time.
+
+   In echo mode (see :func:`echo`) the key is added to the window as by
+   :meth:`addch`; special keys are not echoed.
 
    .. versionadded:: 3.3
 
 
 .. method:: window.getkey([y, x])
 
-   Get a character, returning a string instead of an integer, as :meth:`getch`
-   does. Function keys, keypad keys and other special keys return a multibyte
-   string containing the key name.  In no-delay mode, raise an exception if
-   there is no input.
+   Read a key press as :meth:`getch` does, but return it as a :class:`str`:
+   an ordinary key as a one-character string, the byte decoded as Latin-1,
+   and a special key as its name, such as ``'KEY_UP'`` (see :func:`keyname`).
+   Raise :exc:`error` instead of returning ``-1`` if there is no input.
 
 
 .. method:: window.getmaxyx()
@@ -1044,8 +1084,11 @@ Window objects
             window.getstr(y, x)
             window.getstr(y, x, n)
 
-   Read a bytes object from the user, with primitive line editing capacity.
-   At most *n* characters are read;
+   Read a line of input from the user, with primitive line editing capacity,
+   after moving the cursor to *y*, *x* if specified.
+   Return it as a bytes object, in the encoding of the current locale
+   and without the terminating newline.
+   At most *n* bytes are read;
    *n* defaults to and cannot exceed 2047.
 
    .. versionchanged:: 3.14
@@ -1147,12 +1190,11 @@ Window objects
 .. method:: window.instr([n])
             window.instr(y, x[, n])
 
-   Return a bytes object of characters, extracted from the window starting at the
-   current cursor position, or at *y*, *x* if specified, and stopping at the end
-   of the line. Attributes and color information are stripped
-   from the characters.  If *n* is specified, :meth:`instr` returns a string
-   at most *n* characters long (exclusive of the trailing NUL).
-   The maximum value for *n* is 2047.
+   Read the text of the window from the current cursor position,
+   or from *y*, *x* if specified, to the end of the line,
+   and return it as a bytes object, in the encoding of the current locale.
+   Attributes and color pairs are stripped.
+   At most *n* bytes are read; *n* defaults to and cannot exceed 2047.
 
    .. versionchanged:: 3.14
       The maximum value for *n* was increased from 1023 to 2047.
@@ -1176,6 +1218,8 @@ Window objects
    If *flag* is ``True``, escape sequences generated by some keys (keypad,  function keys)
    will be interpreted by :mod:`!curses`. If *flag* is ``False``, escape sequences will be
    left as is in the input stream.
+   Keypad mode is disabled by default, but :func:`wrapper` enables it for the
+   main window.
 
 
 .. method:: window.leaveok(flag)
@@ -1528,6 +1572,8 @@ by some methods.
 |                         | color-pair field information  |
 +-------------------------+-------------------------------+
 
+.. _curses-key-constants:
+
 Keys are referred to by integer constants with names starting with  ``KEY_``.
 The exact keycaps available are system dependent.
 
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index 48c53fdc68b1f2..6008802b81c68a 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -1547,18 +1547,25 @@ _curses.window.getch
     ]
     /
 
-Get a character code from terminal keyboard.
+Read a key press and return it as an integer.
 
-The integer returned does not have to be in ASCII range: function
-keys, keypad keys and so on return numbers higher than 256.  In
-no-delay mode, -1 is returned if there is no input, else getch()
-waits until a key is pressed.
+Wait until a key is pressed, or return -1 if the read is
+non-blocking or times out.
+
+An ordinary key is returned as the code of a single byte of its
+encoding in the current locale, so a character encoded with several
+bytes takes several calls.  Use get_wch() to read it as a single
+character.
+
+In keypad mode function keys and other special keys are returned as
+one of the KEY_* constants, which cannot be mistaken for an ordinary
+key.  Otherwise their bytes are returned one at a time.
 [clinic start generated code]*/
 
 static PyObject *
 _curses_window_getch_impl(PyCursesWindowObject *self, int group_right_1,
                           int y, int x)
-/*[clinic end generated code: output=e1639e87d545e676 input=0dc5ff40e079787a]*/
+/*[clinic end generated code: output=e1639e87d545e676 input=882ddab9b41afbbd]*/
 {
     int rtn;
 
@@ -1595,18 +1602,18 @@ _curses.window.getkey
     ]
     /
 
-Get a character (string) from terminal keyboard.
+Read a key press and return it as a str.
 
-Returning a string instead of an integer, as getch() does.  Function
-keys, keypad keys and other special keys return a multibyte string
-containing the key name.  In no-delay mode, an exception is raised
-if there is no input.
+Read as getch() does, but return an ordinary key as a one-character
+string, the byte decoded as Latin-1, and a special key as its name,
+such as 'KEY_UP'.  Raise curses.error instead of returning -1 if
+there is no input.
 [clinic start generated code]*/
 
 static PyObject *
 _curses_window_getkey_impl(PyCursesWindowObject *self, int group_right_1,
                            int y, int x)
-/*[clinic end generated code: output=8490a182db46b10f input=bd24a7da1ed9c73b]*/
+/*[clinic end generated code: output=8490a182db46b10f input=f054cf034c69e879]*/
 {
     int rtn;
 
@@ -1655,16 +1662,20 @@ _curses.window.get_wch
     ]
     /
 
-Get a wide character from terminal keyboard.
+Read a key press and return it as a one-character str.
+
+Wait until a key is pressed, or raise curses.error if the read is
+non-blocking or times out.
 
-Return a character for most keys, or an integer for function keys,
-keypad keys, and other special keys.
+In keypad mode function keys and other special keys are returned as
+one of the KEY_* constants, an integer.  Otherwise their characters
+are returned one at a time.
 [clinic start generated code]*/
 
 static PyObject *
 _curses_window_get_wch_impl(PyCursesWindowObject *self, int group_right_1,
                             int y, int x)
-/*[clinic end generated code: output=9f4f86e91fe50ef3 input=dd7e5367fb49dc48]*/
+/*[clinic end generated code: output=9f4f86e91fe50ef3 input=77eb2da426ebe71f]*/
 {
     int ct;
     wint_t rtn;
@@ -1736,14 +1747,14 @@ curses_clinic_parse_optional_xy_n(PyObject *args,
 
 PyDoc_STRVAR(_curses_window_getstr__doc__,
 "getstr([[y, x,] n=2047])\n"
-"Read a string from the user, with primitive line editing capacity.\n"
+"Read a line of input and return it as a bytes object.\n"
 "\n"
 "  y\n"
 "    Y-coordinate.\n"
 "  x\n"
 "    X-coordinate.\n"
 "  n\n"
-"    Maximal number of characters.");
+"    Maximal number of bytes.");
 
 static PyObject *
 PyCursesWindow_getstr(PyObject *op, PyObject *args)
@@ -1974,21 +1985,19 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
 
 PyDoc_STRVAR(_curses_window_instr__doc__,
 "instr([y, x,] n=2047)\n"
-"Return a string of characters, extracted from the window.\n"
+"Return the text of the window as a bytes object.\n"
 "\n"
 "  y\n"
 "    Y-coordinate.\n"
 "  x\n"
 "    X-coordinate.\n"
 "  n\n"
-"    Maximal number of characters.\n"
+"    Maximal number of bytes.\n"
 "\n"
-"Return a string of characters, extracted from the window starting\n"
-"at the current cursor position, or at y, x if specified, and\n"
-"stopping at the end of the line.  Attributes and color\n"
-"information are stripped from the characters.  If n is specified,\n"
-"instr() returns a string at most n characters long (exclusive of\n"
-"the trailing NUL).");
+"Read from the current cursor position, or from y, x if specified, to\n"
+"the end of the line, and return the text in the encoding of the\n"
+"current locale, with attributes and color pairs stripped.  At most n\n"
+"bytes are read.");
 
 static PyObject *
 PyCursesWindow_instr(PyObject *op, PyObject *args)
@@ -4802,15 +4811,16 @@ _curses.unctrl
     ch: object
     /
 
-Return a string which is a printable representation of the character ch.
+Return a bytes object which is a printable representation of ch.
 
-Control characters are displayed as a caret followed by the character,
-for example as ^C.  Printing characters are left as they are.
+Control characters are displayed as a caret followed by the
+character, for example as ^C.  Printing characters are left as they
+are.  Any attributes and color pair are ignored.
 [clinic start generated code]*/
 
 static PyObject *
 _curses_unctrl(PyObject *module, PyObject *ch)
-/*[clinic end generated code: output=8e07fafc430c9434 input=cd1e35e16cd1ace4]*/
+/*[clinic end generated code: output=8e07fafc430c9434 input=6732d59733d3ed5b]*/
 {
     chtype ch_;
 
diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h
index afcfd407190c93..f647328cb9f590 100644
--- a/Modules/clinic/_cursesmodule.c.h
+++ b/Modules/clinic/_cursesmodule.c.h
@@ -758,17 +758,24 @@ _curses_window_getbkgd(PyObject *self, PyObject *Py_UNUSED(ignored))
 
 PyDoc_STRVAR(_curses_window_getch__doc__,
 "getch([y, x])\n"
-"Get a character code from terminal keyboard.\n"
+"Read a key press and return it as an integer.\n"
 "\n"
 "  y\n"
 "    Y-coordinate.\n"
 "  x\n"
 "    X-coordinate.\n"
 "\n"
-"The integer returned does not have to be in ASCII range: function\n"
-"keys, keypad keys and so on return numbers higher than 256.  In\n"
-"no-delay mode, -1 is returned if there is no input, else getch()\n"
-"waits until a key is pressed.");
+"Wait until a key is pressed, or return -1 if the read is\n"
+"non-blocking or times out.\n"
+"\n"
+"An ordinary key is returned as the code of a single byte of its\n"
+"encoding in the current locale, so a character encoded with several\n"
+"bytes takes several calls.  Use get_wch() to read it as a single\n"
+"character.\n"
+"\n"
+"In keypad mode function keys and other special keys are returned as\n"
+"one of the KEY_* constants, which cannot be mistaken for an ordinary\n"
+"key.  Otherwise their bytes are returned one at a time.");
 
 #define _CURSES_WINDOW_GETCH_METHODDEF    \
     {"getch", (PyCFunction)_curses_window_getch, METH_VARARGS, _curses_window_getch__doc__},
@@ -806,17 +813,17 @@ _curses_window_getch(PyObject *self, PyObject *args)
 
 PyDoc_STRVAR(_curses_window_getkey__doc__,
 "getkey([y, x])\n"
-"Get a character (string) from terminal keyboard.\n"
+"Read a key press and return it as a str.\n"
 "\n"
 "  y\n"
 "    Y-coordinate.\n"
 "  x\n"
 "    X-coordinate.\n"
 "\n"
-"Returning a string instead of an integer, as getch() does.  Function\n"
-"keys, keypad keys and other special keys return a multibyte string\n"
-"containing the key name.  In no-delay mode, an exception is raised\n"
-"if there is no input.");
+"Read as getch() does, but return an ordinary key as a one-character\n"
+"string, the byte decoded as Latin-1, and a special key as its name,\n"
+"such as \'KEY_UP\'.  Raise curses.error instead of returning -1 if\n"
+"there is no input.");
 
 #define _CURSES_WINDOW_GETKEY_METHODDEF    \
     {"getkey", (PyCFunction)_curses_window_getkey, METH_VARARGS, _curses_window_getkey__doc__},
@@ -856,15 +863,19 @@ _curses_window_getkey(PyObject *self, PyObject *args)
 
 PyDoc_STRVAR(_curses_window_get_wch__doc__,
 "get_wch([y, x])\n"
-"Get a wide character from terminal keyboard.\n"
+"Read a key press and return it as a one-character str.\n"
 "\n"
 "  y\n"
 "    Y-coordinate.\n"
 "  x\n"
 "    X-coordinate.\n"
 "\n"
-"Return a character for most keys, or an integer for function keys,\n"
-"keypad keys, and other special keys.");
+"Wait until a key is pressed, or raise curses.error if the read is\n"
+"non-blocking or times out.\n"
+"\n"
+"In keypad mode function keys and other special keys are returned as\n"
+"one of the KEY_* constants, an integer.  Otherwise their characters\n"
+"are returned one at a time.");
 
 #define _CURSES_WINDOW_GET_WCH_METHODDEF    \
     {"get_wch", (PyCFunction)_curses_window_get_wch, METH_VARARGS, _curses_window_get_wch__doc__},
@@ -4217,10 +4228,11 @@ PyDoc_STRVAR(_curses_unctrl__doc__,
 "unctrl($module, ch, /)\n"
 "--\n"
 "\n"
-"Return a string which is a printable representation of the character ch.\n"
+"Return a bytes object which is a printable representation of ch.\n"
 "\n"
-"Control characters are displayed as a caret followed by the character,\n"
-"for example as ^C.  Printing characters are left as they are.");
+"Control characters are displayed as a caret followed by the\n"
+"character, for example as ^C.  Printing characters are left as they\n"
+"are.  Any attributes and color pair are ignored.");
 
 #define _CURSES_UNCTRL_METHODDEF    \
     {"unctrl", (PyCFunction)_curses_unctrl, METH_O, _curses_unctrl__doc__},
@@ -4471,4 +4483,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
 #ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
     #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
 #endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */
-/*[clinic end generated code: output=8f8629fba6d86b33 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b267dfb3db6b6f56 input=a9049054013a1b77]*/

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.