Because nodelay is literally no delay. And unless your keyboard repeat rate is really high, there will be some iterations of the loop when the getch times out and yields no input, which would be normal.

For me I get:

999742 iterations with no input
258 iterations with input

Which seems reasonable for 11 seconds. There is no way my keyboard would repeat a key 1 million times in 11 seconds, and if it did it would be impossible to use the keyboard for anything but hitting all getches in this program, since it would have to count ~99 000 keys per second, which would make typing really painful. So, in short, your numbers are normal.

Answer from Carl on Stack Overflow
🌐
Python documentation
docs.python.org › 3 › howto › curses.html
Curses Programming with Python — Python 3.14.3 documentation
Individual characters are returned ... wait for the user using the nodelay() window method. After nodelay(True), getch() and getkey() for the window become non-blocking....
🌐
Python
docs.python.org › 3 › library › curses.html
curses — Terminal handling for character-cell displays
Set blocking or non-blocking read behavior for the window. If delay is negative, blocking read is used (which will wait indefinitely for input). If delay is zero, then non-blocking read is used, and getch() will return -1 if no input is waiting.
Discussions

Non-Blacking input ncurses
Apologies, I just realised that I should include a quick runnable example bit of code so here goes: #include int main() { initscr(); int ch; cbreak(); for (;;) { ch = getch(); printw("%c", ch); } endwin; } You'll notice that if you hold down a key, then begin holding another at the same time, the previous will stop being acknowledged after you let got of the new one. More on reddit.com
🌐 r/learnprogramming
5
1
September 27, 2020
python - non-blocking getch() in curses - Stack Overflow
14 How do I use getch from curses without clearing the screen? ... Why is the Wigner rotation ambiguous when comparing composed non-collinear boosts to a single effective boost? More on stackoverflow.com
🌐 stackoverflow.com
April 30, 2022
linux - Non-blocking getch(), ncurses - Stack Overflow
I'm having some problems getting ncurses' getch() to block. Default operation seems to be non-blocking (or have I missed some initialization)? I would like it to work like getch() in Windows. I have More on stackoverflow.com
🌐 stackoverflow.com
python - Non-blocking console input? - Stack Overflow
I am trying to make a simple IRC client in Python (as kind of a project while I learn the language). I have a loop that I use to receive and parse what the IRC server sends me, but if I use raw_inp... More on stackoverflow.com
🌐 stackoverflow.com
🌐
Raspberry Pi Forums
forums.raspberrypi.com › board index › programming › python
python how to stop getch() ? - Raspberry Pi Forums
I have wrote a program in python using getch() function to recieve inputs from user and I want to stop this input in specific times How can I achieve that? example => key_passwrd = getch.getch() Also , curses.endwin() works fine ,if I use this one: ... control = curses.initscr() curses.noecho() control.nodelay(1) # set getch() non-blocking ...
🌐
Reddit
reddit.com › r/learnprogramming › non-blacking input ncurses
r/learnprogramming on Reddit: Non-Blacking input ncurses
September 27, 2020 -

I've seen many people ask a question like this, but they all seem to mean something different than what I do so I'll define what I mean by blocking:

When I hold a key on my keyboard, I want to be able to hold another, and have its input override the previous one, but when you let go of this new said key, I want the input of the other key that I was holding before to come back.

With that said I'll now explain my problem: I'm currently Programming a rogue-like in C using the ncurses library. My issue is that I cannot figure out how to get non-blocking (see above definition) input. My current method of getting input is a while loop with a switch statement that detects input using the getch() function provided by curses. Anyone got any tips, should I be taking input completely differently? I don't know how to use threads as I'm new but I'm willing to learn if it will work and run better. I'll attach a gitlab link with the code for my controls.

Controls The actual loop in question is around line 38

This isn't too urgent, I appreciate any advice and if there's anything else that I should include let me know.

Top answer
1 of 2
1
Apologies, I just realised that I should include a quick runnable example bit of code so here goes: #include int main() { initscr(); int ch; cbreak(); for (;;) { ch = getch(); printw("%c", ch); } endwin; } You'll notice that if you hold down a key, then begin holding another at the same time, the previous will stop being acknowledged after you let got of the new one.
2 of 2
1
First, a note on terminology: non-blocking input in curses, refers to the fact that the following code "waits" at the input line: for (;;) { ch = getch(); // <-- This line "blocks" printw("%c", ch); } What this means, is that your program does not continue running until a keyboard event comes in. In other words, the getch() line "blocks" your program from continuing on to the printw() call below. If you want "non-blocking" input in curses, you should call (w)timeout with a non-negative integer argument. Non-blocking means that the program does not wait for the input. I.e. the input does not "block" the program from continuing to run, so the loop above will continuously and quickly call printw with whatever getch() returns. Note that in non-blocking mode, you will usually want your input loop to have some kind of small timing delay built in. Otherwise you will end up with a very tight loop of continuously checking for input events, and that will normally cause the program to consume 100% CPU, and as a side effect it will cause laptop fans to turn up, drain the battery more quickly than needed, and so on. A short delay of 1 ms or less normally fixes such problems. See the napms function ("take a nap for n milliseconds") in curses. Now, as for solving the problem you seem to be describing (detecting multiple keys pressed at a time), you might want to have a look at this SO post which describes exactly this problem. This snippet assumes you have enabled non-blocking mode, though, so first you have to do that: https://gamedev.stackexchange.com/questions/144558/key-released-multiple-keys-how-to-go-around-ncurses-limitations Finally, keep in mind that certain key combinations are not detectable on all keyboards, as a hardware limitation. For example, all keyboards can detect the Shift key combined with any of the letter keys (at least 2 keys at a time held down), but very few keyboards can detect (say) all of the letter keys held down at once. If you test your keyboard systematically, you'll find that certain combinations simply are not detectable. I think 2 keys at once should not generally be a problem, but when it gets up to 4 or 5 at once, you might encounter some difficulties on some keyboards.
🌐
Stack Overflow
stackoverflow.com › questions › 72072217 › non-blocking-getch-in-curses
python - non-blocking getch() in curses - Stack Overflow
April 30, 2022 - def non_block_getch(): old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings tty.setraw(sys.stdin.fileno()) # raw to read single raw key w/o enter os.set_blocking(sys.stdin.fileno(), False) # dont block for empty buffer buffer_dump = "" while char := sys.stdin.read(1): buffer_dump += char os.set_blocking(sys.stdin.fileno(), True) # restore normal settings termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings) if buffer_dump: return buffer_dump else: return ""
🌐
Gtk.Box
valadoc.org › curses › Curses.timeout.html
Curses.timeout – curses
If delay is zero, then non-blocking read is used, and -1 will be returned by Curses.getch if no input is waiting.
🌐
Readthedocs
docspy3zh.readthedocs.io › en › latest › howto › curses.html
Curses Programming with Python — Python 3 文档(简体中文) 3.2.2 documentation
You can optionally specify a coordinate to which the cursor should be moved before pausing. It’s possible to change this behavior with the method nodelay(). After nodelay(1), getch() for the window becomes non-blocking and returns curses.ERR (a value of -1) when no input is ready.
Find elsewhere
🌐
Linux Man Pages
linux.die.net › man › 3 › nodelay
nodelay(3): curses input options - Linux man page
The nodelay option causes getch to be a non-blocking call. If no input is ready, getch returns ERR. If disabled (bf is FALSE), getch waits until a key is pressed.
🌐
GitHub
gist.github.com › deadPix3l › c576d5d4dfab42a34551cb674185a095
Curses cheatsheet · GitHub
Curses cheatsheet. GitHub Gist: instantly share code, notes, and snippets.
🌐
Davesteele
davesteele.github.io › development › 2021 › 08 › 29 › python-asyncio-curses
Using Curses with Python Asyncio - Dave Steele's Blog
August 29, 2021 - The curses getch() call can operate in both a blocking and a non-blocking mode. Either will work in asyncio, with at least one caveat - if the blocking getch() is called in a separate thread via an asyncio executor, it will not return some events, notably KEY_RESIZE.
🌐
Steven
steven.codes › blog › cs10 › curses-tutorial
Curses Tutorial • steven.codes
October 10, 2016 - If Python gets to that line and the user hasn't typed anything since last time, getch will return -1, which doesn't match any key. What if the user managed to type more than one character since the last time getch was called? All of those characters will start to build up, and getch will return ...
🌐
GitHub
gist.github.com › 3a32513f26bdc58fd3bd
Non blocking and no echo getch() on Linux · GitHub
Man page says: The nodelay option causes getch to be a non-blocking call. If no input is ready, getch returns ERR.
🌐
OMZ Software
omz-software.com › editorial › docs › howto › curses.html
Curses Programming with Python — Editorial Documentation
You can optionally specify a coordinate to which the cursor should be moved before pausing. It’s possible to change this behavior with the method nodelay(). After nodelay(1), getch() for the window becomes non-blocking and returns curses.ERR (a value of -1) when no input is ready.
🌐
Wordpress
liutheprogrammer.wordpress.com › 2018 › 08 › 11 › python-curses-module-getch-and-timeout
Private Site
August 11, 2018 - Build a website. Sell your stuff. Write a blog. And so much more · This site is currently private. Log in to WordPress.com to request access
🌐
Python
docs.python.org › 3.3 › library › curses.html
16.11. curses — Terminal handling for character-cell displays — Python 3.3.7 documentation
Set blocking or non-blocking read behavior for the window. If delay is negative, blocking read is used (which will wait indefinitely for input). If delay is zero, then non-blocking read is used, and -1 will be returned by getch() if no input is waiting.
Top answer
1 of 15
83

For Windows, console only, use the msvcrt module:

import msvcrt

num = 0
done = False
while not done:
    print(num)
    num += 1

    if msvcrt.kbhit():
        print "you pressed",msvcrt.getch(),"so now i will quit"
        done = True

For Linux, this article describes the following solution, it requires the termios module:

import sys
import select
import tty
import termios

def isData():
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

old_settings = termios.tcgetattr(sys.stdin)
try:
    tty.setcbreak(sys.stdin.fileno())

    i = 0
    while 1:
        print(i)
        i += 1

        if isData():
            c = sys.stdin.read(1)
            if c == '\x1b':         # x1b is ESC
                break

finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

For cross platform, or in case you want a GUI as well, you can use Pygame:

import pygame
from pygame.locals import *

def display(str):
    text = font.render(str, True, (255, 255, 255), (159, 182, 205))
    textRect = text.get_rect()
    textRect.centerx = screen.get_rect().centerx
    textRect.centery = screen.get_rect().centery

    screen.blit(text, textRect)
    pygame.display.update()

pygame.init()
screen = pygame.display.set_mode( (640,480) )
pygame.display.set_caption('Python numbers')
screen.fill((159, 182, 205))

font = pygame.font.Font(None, 17)

num = 0
done = False
while not done:
    display( str(num) )
    num += 1

    pygame.event.pump()
    keys = pygame.key.get_pressed()
    if keys[K_ESCAPE]:
        done = True
2 of 15
54

This is the most awesome solution1 I've ever seen. Pasted here in case link goes down:

#!/usr/bin/env python
'''
A Python class implementing KBHIT, the standard keyboard-interrupt poller.
Works transparently on Windows and Posix (Linux, Mac OS X).  Doesn't work
with IDLE.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as 
published by the Free Software Foundation, either version 3 of the 
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

'''

import os

# Windows
if os.name == 'nt':
    import msvcrt

# Posix (Linux, OS X)
else:
    import sys
    import termios
    import atexit
    from select import select


class KBHit:

    def __init__(self):
        '''Creates a KBHit object that you can call to do various keyboard things.
        '''

        if os.name == 'nt':
            pass

        else:

            # Save the terminal settings
            self.fd = sys.stdin.fileno()
            self.new_term = termios.tcgetattr(self.fd)
            self.old_term = termios.tcgetattr(self.fd)

            # New terminal setting unbuffered
            self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)

            # Support normal-terminal reset at exit
            atexit.register(self.set_normal_term)


    def set_normal_term(self):
        ''' Resets to normal terminal.  On Windows this is a no-op.
        '''

        if os.name == 'nt':
            pass

        else:
            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)


    def getch(self):
        ''' Returns a keyboard character after kbhit() has been called.
            Should not be called in the same program as getarrow().
        '''

        s = ''

        if os.name == 'nt':
            return msvcrt.getch().decode('utf-8')

        else:
            return sys.stdin.read(1)


    def getarrow(self):
        ''' Returns an arrow-key code after kbhit() has been called. Codes are
        0 : up
        1 : right
        2 : down
        3 : left
        Should not be called in the same program as getch().
        '''

        if os.name == 'nt':
            msvcrt.getch() # skip 0xE0
            c = msvcrt.getch()
            vals = [72, 77, 80, 75]

        else:
            c = sys.stdin.read(3)[2]
            vals = [65, 67, 66, 68]

        return vals.index(ord(c.decode('utf-8')))


    def kbhit(self):
        ''' Returns True if keyboard character was hit, False otherwise.
        '''
        if os.name == 'nt':
            return msvcrt.kbhit()

        else:
            dr,dw,de = select([sys.stdin], [], [], 0)
            return dr != []


# Test    
if __name__ == "__main__":

    kb = KBHit()

    print('Hit any key, or ESC to exit')

    while True:

        if kb.kbhit():
            c = kb.getch()
            if ord(c) == 27: # ESC
                break
            print(c)

    kb.set_normal_term()

1 Made by Simon D. Levy, part of a compilation of software he has written and released under the Gnu Lesser General Public License.