Is there a way of reading one single character from the user input? For instance, they press one key at the terminal and it is returned (sort of like getch()
). I know there's a function in Windows for it, but I'd like something that is cross-platform.

- 119,623
- 25
- 170
- 301

- 98,895
- 36
- 105
- 117
-
2On windows I ran into the same problem as in this [question](https://stackoverflow.com/questions/50069553/msvcrt-getch-detects-space-every-time/50069622#50069622). The solution is to replace the `msvcrt.getch` with `msvcrt.getwch`, as suggested there. – A. Roy Sep 08 '18 at 18:53
-
Solution is install getch module "pip install getch". For Python2 use command "pip2 install https://files.pythonhosted.org/packages/56/f7/cde35f44d267df7122005c40f1a15cf5e3c60ffc83a2ab00d11d99e9d8c4/getch-1.0-python2.tar.gz". This solution also works in Termux (Android). – Petr Mach Dec 17 '19 at 08:34
-
The simplest solution is to use [sshkeyboard](https://sshkeyboard.readthedocs.io/en/latest/). It requires less coding than getch, and it is a cross platform solution. – ilon Oct 28 '21 at 07:49
-
1I can't believe all these complicated answers. In Ruby: `input = STDIN.getch` That's literally it. – user938883 Jan 16 '22 at 11:46
-
@user93883 NameError: global name 'STDIN' is not defined . Maybe it's not all that straightforward after all. – Carl Smotricz Mar 14 '22 at 17:37
25 Answers
Here's a link to the ActiveState Recipes site that says how you can read a single character in Windows, Linux and OSX:
getch()-like unbuffered character reading from stdin on both Windows and Unix
class _Getch:
"""Gets a single character from standard input. Does not echo to the
screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
getch = _Getch()
-
25code seems short enough that you could just include it, but +1 for finding a good (cross-platform) answer so quickly. – John Mulder Feb 04 '09 at 07:24
-
5Does it handle non-latin (e.g., cyrillic) letters well? I am having a problem with that and can't figure out, if it is my mistake, or not. – Phlya Mar 29 '13 at 18:01
-
1@Ilya: look at how `getpass()` that supports non-ascii characters is implemented e.g., it uses [`getwch()` on Windows](http://hg.python.org/cpython/file/3.4/Lib/getpass.py#l98) – jfs Apr 06 '14 at 10:42
-
This is really helpful. If, instead of doing `return self.impl()`, you create a `for` loop and call `self.impl()` 3 times, you can get arrow keys and other escape sequences from the keyboard as well. – jfa Jun 30 '14 at 22:39
-
8I don't like how the `ImportError` exception is used like some kind of if-statement; why not call platform.system() to check the OS? – Seismoid Sep 21 '14 at 20:19
-
1Why does using this make print statements no longer output carriage returns properly? – ThorSummoner Jan 23 '15 at 08:26
-
11@Seismoid: Asking for forgiveness is generally considered better, see http://stackoverflow.com/questions/12265451/ask-forgiveness-not-permission-explain – dirkjot Apr 21 '15 at 11:18
-
4Doesn't work on OS X: "old_settings = termios.tcgetattr(fd)" "termios.error: (25, 'Inappropriate ioctl for device')" – Display Name Sep 13 '15 at 18:35
-
@Phlya My understanding is that this code is only accepting 1 Byte. However, UTF-8 allows a single character to be represented by 1-4 Bytes. This seems to pose a big problem for this solution. – nu everest Jan 24 '16 at 01:49
-
2Why are these classes that override `__call__` instead of ordinary methods??????? – jpmc26 Feb 17 '16 at 23:26
-
3The use of these pseudo classes that are just functions is horrible IMHO. Why not a simple function for each set of modules and one function that determines and returns the function to use? – BlackJack Apr 20 '17 at 12:32
-
@dirkjot That principle doesn't really apply here. There's no race condition that would be introduced by querying the operating system first. – scornwell May 26 '17 at 17:19
-
want to add my 2 cents. in unix implementation when moving to tty raw mode output looks terrible. i suggest the following fix to keep the output aligned: `old_settings = termios.tcgetattr(fd); tty.setraw(sys.stdin.fileno()); raw_settings = termios.tcgetattr(fd); raw_settings[1]=old_settings[1]; termios.tcsetattr(fd, termios.TCSADRAIN, raw_settings) `. this code keep the output flag the same as the original so no surprises when you print something to the screen – Idok Jun 12 '17 at 20:55
-
Does not work in MacOS. I pasted the script into a file and then called it from a second file - the call returned immediately without waiting for console input. – David Stein Dec 07 '17 at 18:50
-
This recipe from ActiveStates seems to contain a little bug for "posix" that it prevents Ctrl-C from working normally. See my answer below with improved code. https://stackoverflow.com/a/48136131/404271 – ibic Jan 07 '18 at 10:17
-
Does not work on Termux (Android). Command "tty.setraw(fd)" raise exception "termios.error: (13, 'Permission denied')" – Petr Mach Dec 17 '19 at 07:52
-
1
-
-
@jpmc26: Classes overriding `__call__` are known as "functors" or "[function objects](https://en.wikipedia.org/wiki/Function_object)"". They're useful because they allow construction of the function to to occur at runtime (and maintain an internal state between calls if desired), yet they can still be invoked or called as if they were ordinary functions — which is actually an elegant design-pattern IMO (and supported by many computer languages). – martineau May 09 '21 at 17:16
-
1@martineau None of those reason apply to this code. The classes in this answer do the rather ridiculous thing of sticking global state on a class instance. I've used `__call__` before, but not for something that won't change implementations depending on user input. Everything here is better achieved just by putting the different platform function definitions inside a `try`/`except` block. See [here](https://stackoverflow.com/a/21659588/1394393). Also, Python **always** "constructs" functions and binds them to a name at runtime (and allows the name to be reassigned). – jpmc26 May 09 '21 at 23:24
-
@jpmc26: There's no global state being saved. The `import`s are done the way they are to avoid injecting any OS-specific modules into the global namespace. The reason they're done in the `__init__()` is to cause an `ImportError` if they don't exist (i.e. EAFP). Yes, it could have been implemented another way, but that doesn't make the one chosen a bad one. – martineau May 09 '21 at 23:44
-
2@martineau Imports create a single module instance (that never gets recreated) and then bind the module object to a name in the current scope. The name is being unnecessarily localized to the `__call__` methods. Imports *should* be module level unless there's a good reason otherwise. "The reason they're done in the __init__() is to cause an ImportError if they don't exist" And this is better handled in a `try`/`except` block at the module scope; you can catch exceptions at the module level. Doing so to handle missing modules is a well known pattern in Python. This is unnecessary indirection. – jpmc26 May 10 '21 at 05:36
-
1@martineau [Zen of Python](https://www.python.org/dev/peps/pep-0020/) All of the following dictate a simpler approach: Simple is better than complex. Flat is better than nested. Readability counts. – jpmc26 May 10 '21 at 05:40
-
1@martineau Also notice that the instance of `_Getch` is a singleton stored globally in the module. It has an instance variable named `impl`, which stores the instance of the platform specific class holding a `__call__` method. This is two completely unneeded levels of indirection to access one single function globally, when the actual implementing function could have just been associated with the global name directly. The fact that you think there's no global state is a testament to the way this sort of over-engineering misleads people and makes the system more difficult to understand. – jpmc26 May 10 '21 at 18:59
-
@jpmc26: I'm familiar with how everything you've mentioned so far works as well as PEP 20 (some of which is arguable). I agree with you that the double importing is kinda sketchy, but don't wrt with your other criticisms including the latest one about the storing of an instance in the module — it was probably done to enable a single line `from module import getch` to save clients having to import and then instantiate an instance themselves. Since you think the code so bad, feel free to post your own sanitized version instead of disparaging this one (and arguing with any who disagree with you). – martineau May 10 '21 at 20:11
-
1@martineau "I agree with you that the double importing is kinda sketchy" I *never* claimed that or made any objection to it. Read carefully. "it was probably done to enable a single line from module import getch to save clients having to import and then instantiate an instance themselves." And none of this class nonsense is required for that. The fact is these classes don't provide any functional advantage, so they're just clutter and overhead. I don't need to post an answer because a much better one that does things in a far more normal way already exists; I even linked you to it before. – jpmc26 May 10 '21 at 22:16
-
@jpmc26: I assumed it was included in what you earlier about using `try/except` blocks. I see now you did link to another answer under another answer although not here under this one — apologies. Regardless, based on the number of up-votes this 12 year old answer has received (as has the 19 year old original on ActiveState), that I'm not the only one who thinks it's a good one, even if slightly " over-engineered". – martineau May 10 '21 at 22:38
-
1@martineau I linked it in [my first reply to you](https://stackoverflow.com/questions/510357/how-to-read-a-single-character-from-the-user/510364?noredirect=1#comment119242124_510364). It's not "slightly" over-engineered. It's a convoluted mess. Highly upvoted answers like this one frequently suffer from a high traffic effect where people who don't know or care about the quality of the code they're upvoting overwhelm people who do, not to mention the fact that this answer predates the other by 5 years creating a simple time lag effect. – jpmc26 May 11 '21 at 03:25
-
There's a problem here, that in windows, getch() returns bytes, but in `linux` it returns a `str` – kristianp Aug 21 '21 at 02:19
-
1In my use of this code, I changed the last line of the windows getch() to `return msvcrt.getch().decode("utf-8")`, so that it returns a string as well. – kristianp Aug 23 '21 at 00:28
-
The simplest solution is to use [sshkeyboard](https://sshkeyboard.readthedocs.io/en/latest/). Then you don't need to worry about decoding manually, it requires less coding than getch, and it is a cross platform solution. See my answer below. – ilon Oct 28 '21 at 07:48
sys.stdin.read(1)
will basically read 1 byte from STDIN.
If you must use the method which does not wait for the \n
you can use this code as suggested in previous answer:
class _Getch:
"""Gets a single character from standard input. Does not echo to the screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
getch = _Getch()
(taken from http://code.activestate.com/recipes/134892/)

- 300,191
- 65
- 633
- 696

- 161,610
- 92
- 305
- 395
-
55I find it odd that sys.stdin.read(1) waits for a \n, lol. Thanks for the submission, though. – Evan Fosmark Feb 04 '09 at 08:00
-
3
-
4
-
5@EvanFosmark: it's not necessarily that sys.stdin.read(1) waits for \n, it's that the terminal program deciding when to send other characters to your program doesn't write them until it sees '\n' - how else would you be able to press backspace and correct what you're typing? (the serious answer to that is - teach the python program to implement the line control, keep a buffer, process backspaces, but that's a different world you may not want to buy into when just "reading a character", and could make your line handling different from all the other programs on your system.) – Tony Delroy Feb 21 '14 at 06:49
-
I don't like how the `ImportError` exception is used like some kind of if-statement; why not call platform.system() to check the OS? – Seismoid Sep 21 '14 at 20:20
-
2@Seismoid [EAFP](http://stackoverflow.com/questions/11360858/what-is-the-eafp-principle-in-python) – vaultah Sep 27 '15 at 07:38
Worth trying is the readchar library, which is in part based on the ActiveState recipe mentioned in other answers (but has come a long way since).
Installation:
python -m pip install readchar
Usage:
import readchar
print('Reading a char:')
print(repr(readchar.readchar()))
print('Reading a key:')
print(repr(readchar.readkey()))
This was tested on Windows and Linux with Python 3.9. It should also work in PyCharm terminal.
Keycodes are not always the same between Windows and Linux, but the library provides platform-specific definitions like readchar.key.F1
to help with that.
Since Linux reports most special keys as escape sequences (starting with \x1b
), readkey()
gets confused if you hit the actual Escape key (reported by the terminal as a solitary \x1b
). This is unfortunately a common Unix problem, with no truly reliable solution.
Note that while readkey
raises KeyboardInterrupt
on Ctrl+C (see readchar.config
), other Linux signal keys (e.g. Ctrl+D and Ctrl+Z) are caught and returned (as '\x04'
and '\x1a'
respectively), which may or may not be desirable.
For an input prompt-like functionality similar to Python's input()
, consider this issue.

- 57,944
- 17
- 167
- 143

- 8,354
- 2
- 47
- 40
-
3Works with Python 3 on Linux as well. Much better than getch, because readchar allows printing to stdout while waiting for key (via threads or asyncio). – wrobell Jun 05 '15 at 20:54
-
Tested on Win10 + Python 3.5: ERROR:root:'in
' requires string as left operand, not bytes Traceback (most recent call last): File "..\main.py", line 184, in wrapper result = func(*args, **kwargs) File "C:\GitHub\Python-Demo\demo\day\_hello.py", line 41, in readch_eg print(readchar.readchar()) File "C:\Users\ipcjs\AppData\Local\Programs\Python\Python35\lib\site-packages\readchar\readchar_windows.py", line 14, in readchar while ch in '\x00\xe0': TypeError: 'in – ipcjs Nov 05 '16 at 20:46' requires string as left operand, not bytes -
1
-
2this is the best answer. adding a dependency to VS C++ library just for this functionality is crazy. – FistOfFury Dec 26 '17 at 14:54
-
For a full list of which sequences are returned for Control+... or things like backspace, you can look here: https://github.com/magmax/python-readchar/blob/master/readchar/key.py – Nathan F. Apr 20 '21 at 14:53
-
This libary got a few updates since this answer was created and fixed a lot of the limitaions/problem mentioned in the answer and comments – Cube Sep 11 '22 at 13:48
-
@FistOfFury I'm not sure how installing an additonal package from PyPI is better than importing a built-in module. Regardless, readchar also depends on msvcrt. – Big McLargeHuge Nov 06 '22 at 17:17
The ActiveState recipe quoted verbatim in two answers is over-engineered. It can be boiled down to this:
def _find_getch():
try:
import termios
except ImportError:
# Non-POSIX. Return msvcrt's (Windows') getch.
import msvcrt
return msvcrt.getch
# POSIX system. Create and return a getch that manipulates the tty.
import sys, tty
def _getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
return _getch
getch = _find_getch()

- 146,715
- 28
- 274
- 320
-
Nice. But this will also read first char of KeyboardInterrupt (Ctrl+C), and code has the possibility of exiting with `0`. – user3342816 Sep 22 '19 at 22:16
An alternative method:
import os
import sys
import termios
import fcntl
def getch():
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
while 1:
try:
c = sys.stdin.read(1)
break
except IOError: pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
return c
From this blog post.
-
Does not seem to work for me - returns empty string immediately upon calling. On Linux with Python 3.6. – Apr 06 '17 at 17:40
-
2@Marein If you want it to block (wait for input), remove the `| os.O_NONBLOCK`. Otherwise, you can put it in a loop (good idea to sleep for a bit in the loop to keep from spinning). – Chris Gregg Feb 04 '19 at 16:55
-
1
The (currently) top-ranked answer (with the ActiveState code) is overly complicated. I don't see a reason to use classes when a mere function should suffice. Below are two implementations that accomplish the same thing but with more readable code.
Both of these implementations:
- work just fine in Python 2 or Python 3
- work on Windows, OSX, and Linux
- read just one byte (i.e., they don't wait for a newline)
- don't depend on any external libraries
- are self-contained (no code outside of the function definition)
Version 1: readable and simple
def getChar():
try:
# for Windows-based systems
import msvcrt # If successful, we are on Windows
return msvcrt.getch()
except ImportError:
# for POSIX-based systems (with termios & tty support)
import tty, sys, termios # raises ImportError if unsupported
fd = sys.stdin.fileno()
oldSettings = termios.tcgetattr(fd)
try:
tty.setcbreak(fd)
answer = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, oldSettings)
return answer
Version 2: avoid repeated imports and exception handling:
[EDIT] I missed one advantage of the ActiveState code. If you plan to read characters multiple times, that code avoids the (negligible) cost of repeating the Windows import and the ImportError exception handling on Unix-like systems. While you probably should be more concerned about code readability than that negligible optimization, here is an alternative (it is similar to Louis's answer, but getChar() is self-contained) that functions the same as the ActiveState code and is more readable:
def getChar():
# figure out which function to use once, and store it in _func
if "_func" not in getChar.__dict__:
try:
# for Windows-based systems
import msvcrt # If successful, we are on Windows
getChar._func=msvcrt.getch
except ImportError:
# for POSIX-based systems (with termios & tty support)
import tty, sys, termios # raises ImportError if unsupported
def _ttyRead():
fd = sys.stdin.fileno()
oldSettings = termios.tcgetattr(fd)
try:
tty.setcbreak(fd)
answer = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, oldSettings)
return answer
getChar._func=_ttyRead
return getChar._func()
Example code that exercises either of the getChar() versions above:
from __future__ import print_function # put at top of file if using Python 2
# Example of a prompt for one character of input
promptStr = "Please give me a character:"
responseStr = "Thank you for giving me a '{}'."
print(promptStr, end="\n> ")
answer = getChar()
print("\n")
print(responseStr.format(answer))

- 362
- 4
- 9

- 429
- 5
- 7
-
2I ran into an issue with tty.setraw() when printing messages while simultaneously waiting for a key (multi-threaded). Long story short, I found that using tty.setcbreak() lets you get a single character without breaking all the other normal stuff. Long story in this [answer](http://stackoverflow.com/questions/12231794/python-in-raw-mode-stdin-print-adds-spaces/37358649#37358649) – TheDavidFactor May 21 '16 at 03:50
-
I ran into issues when using the PyCharm execution shell emulator with the following error: `... old_settings = termios.tcgetattr(fd) ... termios.error: (25, 'Inappropriate ioctl for device')` – iggy12345 Feb 01 '21 at 20:52
-
[This answer](https://stackoverflow.com/a/21659588/1394393) predates yours and is a better approach. – jpmc26 May 09 '21 at 23:33
-
Just if someone tries to tell you, `msvcrt.getch()` outputs a type bytes. To convert it into a normal string, use `str(msvcrt.getch())` – Joao-3 Sep 29 '22 at 11:39
This code, based off here, will correctly raise KeyboardInterrupt and EOFError if Ctrl+C or Ctrl+D are pressed.
Should work on Windows and Linux. An OS X version is available from the original source.
class _Getch:
"""Gets a single character from standard input. Does not echo to the screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self):
char = self.impl()
if char == '\x03':
raise KeyboardInterrupt
elif char == '\x04':
raise EOFError
return char
class _GetchUnix:
def __init__(self):
import tty
import sys
def __call__(self):
import sys
import tty
import termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
getch = _Getch()

- 2,522
- 4
- 26
- 44
-
It supports Unicode beyond ASCII (which was a concern for some other solutions) (and it works on linux) – Berry Tsakala Jan 04 '22 at 11:32
Try using this: http://home.wlu.edu/~levys/software/kbhit.py It's non-blocking (that means that you can have a while loop and detect a key press without stopping it) and cross-platform.
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 != []
An example to use this:
import kbhit
kb = kbhit.KBHit()
while(True):
print("Key not pressed") #Do something
if kb.kbhit(): #If a key is pressed:
k_in = kb.getch() #Detect what key was pressed
print("You pressed ", k_in, "!") #Do something
kb.set_normal_term()
Or you could use the getch module from PyPi. But this would block the while loop
-
Despite this being fairly far down the pecking order of solutions, it was the only one that didn't mess with my stdout on MacOS - thank you! – Dave Feb 19 '21 at 23:02
The answers here were informative, however I also wanted a way to get key presses asynchronously and fire off key presses in separate events, all in a thread-safe, cross-platform way. PyGame was also too bloated for me. So I made the following (in Python 2.7 but I suspect it's easily portable), which I figured I'd share here in case it was useful for anyone else. I stored this in a file named keyPress.py.
class _Getch:
"""Gets a single character from standard input. Does not echo to the
screen. From http://code.activestate.com/recipes/134892/"""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
try:
self.impl = _GetchMacCarbon()
except(AttributeError, ImportError):
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys, termios # import termios now or else you'll get the Unix version on the Mac
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
class _GetchMacCarbon:
"""
A function which returns the current ASCII key that is down;
if no ASCII key is down, the null string is returned. The
page http://www.mactech.com/macintosh-c/chap02-1.html was
very helpful in figuring out how to do this.
"""
def __init__(self):
import Carbon
Carbon.Evt #see if it has this (in Unix, it doesn't)
def __call__(self):
import Carbon
if Carbon.Evt.EventAvail(0x0008)[0]==0: # 0x0008 is the keyDownMask
return ''
else:
#
# The event contains the following info:
# (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
#
# The message (msg) contains the ASCII char which is
# extracted with the 0x000000FF charCodeMask; this
# number is converted to an ASCII character with chr() and
# returned
#
(what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
return chr(msg & 0x000000FF)
import threading
# From https://stackoverflow.com/a/2022629/2924421
class Event(list):
def __call__(self, *args, **kwargs):
for f in self:
f(*args, **kwargs)
def __repr__(self):
return "Event(%s)" % list.__repr__(self)
def getKey():
inkey = _Getch()
import sys
for i in xrange(sys.maxint):
k=inkey()
if k<>'':break
return k
class KeyCallbackFunction():
callbackParam = None
actualFunction = None
def __init__(self, actualFunction, callbackParam):
self.actualFunction = actualFunction
self.callbackParam = callbackParam
def doCallback(self, inputKey):
if not self.actualFunction is None:
if self.callbackParam is None:
callbackFunctionThread = threading.Thread(target=self.actualFunction, args=(inputKey,))
else:
callbackFunctionThread = threading.Thread(target=self.actualFunction, args=(inputKey,self.callbackParam))
callbackFunctionThread.daemon = True
callbackFunctionThread.start()
class KeyCapture():
gotKeyLock = threading.Lock()
gotKeys = []
gotKeyEvent = threading.Event()
keyBlockingSetKeyLock = threading.Lock()
addingEventsLock = threading.Lock()
keyReceiveEvents = Event()
keysGotLock = threading.Lock()
keysGot = []
keyBlockingKeyLockLossy = threading.Lock()
keyBlockingKeyLossy = None
keyBlockingEventLossy = threading.Event()
keysBlockingGotLock = threading.Lock()
keysBlockingGot = []
keyBlockingGotEvent = threading.Event()
wantToStopLock = threading.Lock()
wantToStop = False
stoppedLock = threading.Lock()
stopped = True
isRunningEvent = False
getKeyThread = None
keyFunction = None
keyArgs = None
# Begin capturing keys. A seperate thread is launched that
# captures key presses, and then these can be received via get,
# getAsync, and adding an event via addEvent. Note that this
# will prevent the system to accept keys as normal (say, if
# you are in a python shell) because it overrides that key
# capturing behavior.
# If you start capture when it's already been started, a
# InterruptedError("Keys are still being captured")
# will be thrown
# Note that get(), getAsync() and events are independent, so if a key is pressed:
#
# 1: Any calls to get() that are waiting, with lossy on, will return
# that key
# 2: It will be stored in the queue of get keys, so that get() with lossy
# off will return the oldest key pressed not returned by get() yet.
# 3: All events will be fired with that key as their input
# 4: It will be stored in the list of getAsync() keys, where that list
# will be returned and set to empty list on the next call to getAsync().
# get() call with it, aand add it to the getAsync() list.
def startCapture(self, keyFunction=None, args=None):
# Make sure we aren't already capturing keys
self.stoppedLock.acquire()
if not self.stopped:
self.stoppedLock.release()
raise InterruptedError("Keys are still being captured")
return
self.stopped = False
self.stoppedLock.release()
# If we have captured before, we need to allow the get() calls to actually
# wait for key presses now by clearing the event
if self.keyBlockingEventLossy.is_set():
self.keyBlockingEventLossy.clear()
# Have one function that we call every time a key is captured, intended for stopping capture
# as desired
self.keyFunction = keyFunction
self.keyArgs = args
# Begin capturing keys (in a seperate thread)
self.getKeyThread = threading.Thread(target=self._threadProcessKeyPresses)
self.getKeyThread.daemon = True
self.getKeyThread.start()
# Process key captures (in a seperate thread)
self.getKeyThread = threading.Thread(target=self._threadStoreKeyPresses)
self.getKeyThread.daemon = True
self.getKeyThread.start()
def capturing(self):
self.stoppedLock.acquire()
isCapturing = not self.stopped
self.stoppedLock.release()
return isCapturing
# Stops the thread that is capturing keys on the first opporunity
# has to do so. It usually can't stop immediately because getting a key
# is a blocking process, so this will probably stop capturing after the
# next key is pressed.
#
# However, Sometimes if you call stopCapture it will stop before starting capturing the
# next key, due to multithreading race conditions. So if you want to stop capturing
# reliably, call stopCapture in a function added via addEvent. Then you are
# guaranteed that capturing will stop immediately after the rest of the callback
# functions are called (before starting to capture the next key).
def stopCapture(self):
self.wantToStopLock.acquire()
self.wantToStop = True
self.wantToStopLock.release()
# Takes in a function that will be called every time a key is pressed (with that
# key passed in as the first paramater in that function)
def addEvent(self, keyPressEventFunction, args=None):
self.addingEventsLock.acquire()
callbackHolder = KeyCallbackFunction(keyPressEventFunction, args)
self.keyReceiveEvents.append(callbackHolder.doCallback)
self.addingEventsLock.release()
def clearEvents(self):
self.addingEventsLock.acquire()
self.keyReceiveEvents = Event()
self.addingEventsLock.release()
# Gets a key captured by this KeyCapture, blocking until a key is pressed.
# There is an optional lossy paramater:
# If True all keys before this call are ignored, and the next pressed key
# will be returned.
# If False this will return the oldest key captured that hasn't
# been returned by get yet. False is the default.
def get(self, lossy=False):
if lossy:
# Wait for the next key to be pressed
self.keyBlockingEventLossy.wait()
self.keyBlockingKeyLockLossy.acquire()
keyReceived = self.keyBlockingKeyLossy
self.keyBlockingKeyLockLossy.release()
return keyReceived
else:
while True:
# Wait until a key is pressed
self.keyBlockingGotEvent.wait()
# Get the key pressed
readKey = None
self.keysBlockingGotLock.acquire()
# Get a key if it exists
if len(self.keysBlockingGot) != 0:
readKey = self.keysBlockingGot.pop(0)
# If we got the last one, tell us to wait
if len(self.keysBlockingGot) == 0:
self.keyBlockingGotEvent.clear()
self.keysBlockingGotLock.release()
# Process the key (if it actually exists)
if not readKey is None:
return readKey
# Exit if we are stopping
self.wantToStopLock.acquire()
if self.wantToStop:
self.wantToStopLock.release()
return None
self.wantToStopLock.release()
def clearGetList(self):
self.keysBlockingGotLock.acquire()
self.keysBlockingGot = []
self.keysBlockingGotLock.release()
# Gets a list of all keys pressed since the last call to getAsync, in order
# from first pressed, second pressed, .., most recent pressed
def getAsync(self):
self.keysGotLock.acquire();
keysPressedList = list(self.keysGot)
self.keysGot = []
self.keysGotLock.release()
return keysPressedList
def clearAsyncList(self):
self.keysGotLock.acquire();
self.keysGot = []
self.keysGotLock.release();
def _processKey(self, readKey):
# Append to list for GetKeyAsync
self.keysGotLock.acquire()
self.keysGot.append(readKey)
self.keysGotLock.release()
# Call lossy blocking key events
self.keyBlockingKeyLockLossy.acquire()
self.keyBlockingKeyLossy = readKey
self.keyBlockingEventLossy.set()
self.keyBlockingEventLossy.clear()
self.keyBlockingKeyLockLossy.release()
# Call non-lossy blocking key events
self.keysBlockingGotLock.acquire()
self.keysBlockingGot.append(readKey)
if len(self.keysBlockingGot) == 1:
self.keyBlockingGotEvent.set()
self.keysBlockingGotLock.release()
# Call events added by AddEvent
self.addingEventsLock.acquire()
self.keyReceiveEvents(readKey)
self.addingEventsLock.release()
def _threadProcessKeyPresses(self):
while True:
# Wait until a key is pressed
self.gotKeyEvent.wait()
# Get the key pressed
readKey = None
self.gotKeyLock.acquire()
# Get a key if it exists
if len(self.gotKeys) != 0:
readKey = self.gotKeys.pop(0)
# If we got the last one, tell us to wait
if len(self.gotKeys) == 0:
self.gotKeyEvent.clear()
self.gotKeyLock.release()
# Process the key (if it actually exists)
if not readKey is None:
self._processKey(readKey)
# Exit if we are stopping
self.wantToStopLock.acquire()
if self.wantToStop:
self.wantToStopLock.release()
break
self.wantToStopLock.release()
def _threadStoreKeyPresses(self):
while True:
# Get a key
readKey = getKey()
# Run the potential shut down function
if not self.keyFunction is None:
self.keyFunction(readKey, self.keyArgs)
# Add the key to the list of pressed keys
self.gotKeyLock.acquire()
self.gotKeys.append(readKey)
if len(self.gotKeys) == 1:
self.gotKeyEvent.set()
self.gotKeyLock.release()
# Exit if we are stopping
self.wantToStopLock.acquire()
if self.wantToStop:
self.wantToStopLock.release()
self.gotKeyEvent.set()
break
self.wantToStopLock.release()
# If we have reached here we stopped capturing
# All we need to do to clean up is ensure that
# all the calls to .get() now return None.
# To ensure no calls are stuck never returning,
# we will leave the event set so any tasks waiting
# for it immediately exit. This will be unset upon
# starting key capturing again.
self.stoppedLock.acquire()
# We also need to set this to True so we can start up
# capturing again.
self.stopped = True
self.stopped = True
self.keyBlockingKeyLockLossy.acquire()
self.keyBlockingKeyLossy = None
self.keyBlockingEventLossy.set()
self.keyBlockingKeyLockLossy.release()
self.keysBlockingGotLock.acquire()
self.keyBlockingGotEvent.set()
self.keysBlockingGotLock.release()
self.stoppedLock.release()
The idea is that you can either simply call keyPress.getKey()
, which will read a key from the keyboard, then return it.
If you want something more than that, I made a KeyCapture
object. You can create one via something like keys = keyPress.KeyCapture()
.
Then there are three things you can do:
addEvent(functionName)
takes in any function that takes in one parameter. Then every time a key is pressed, this function will be called with that key's string as it's input. These are ran in a separate thread, so you can block all you want in them and it won't mess up the functionality of the KeyCapturer nor delay the other events.
get()
returns a key in the same blocking way as before. It is now needed here because the keys are being captured via the KeyCapture
object now, so keyPress.getKey()
would conflict with that behavior and both of them would miss some keys since only one key can be captured at a time. Also, say the user presses 'a', then 'b', you call get()
, the user presses 'c'. That get()
call will immediately return 'a', then if you call it again it will return 'b', then 'c'. If you call it again it will block until another key is pressed. This ensures that you don't miss any keys, in a blocking way if desired. So in this way it's a little different than keyPress.getKey()
from before
If you want the behavior of getKey()
back, get(lossy=True)
is like get()
, except that it only returns keys pressed after the call to get()
. So in the above example, get()
would block until the user presses 'c', and then if you call it again it will block until another key is pressed.
getAsync()
is a little different. It's designed for something that does a lot of processing, then occasionally comes back and checks which keys were pressed. Thus getAsync()
returns a list of all the keys pressed since the last call to getAsync()
, in order from oldest key pressed to most recent key pressed. It also doesn't block, meaning that if no keys have been pressed since the last call to getAsync()
, an empty []
will be returned.
To actually start capturing keys, you need to call keys.startCapture()
with your keys
object made above. startCapture
is non-blocking, and simply starts one thread that just records the key presses, and another thread to process those key presses. There are two threads to ensure that the thread that records key presses doesn't miss any keys.
If you want to stop capturing keys, you can call keys.stopCapture()
and it will stop capturing keys. However, since capturing a key is a blocking operation, the thread capturing keys might capture one more key after calling stopCapture()
.
To prevent this, you can pass in an optional parameter(s) into startCapture(functionName, args)
of a function that just does something like checks if a key equals 'c' and then exits. It's important that this function does very little before, for example, a sleep here will cause us to miss keys.
However, if stopCapture()
is called in this function, key captures will be stopped immediately, without trying to capture any more, and that all get()
calls will be returned immediately, with None if no keys have been pressed yet.
Also, since get()
and getAsync()
store all the previous keys pressed (until you retrieve them), you can call clearGetList()
and clearAsyncList()
to forget the keys previously pressed.
Note that get()
, getAsync()
and events are independent, so if a key is pressed:
- One call to
get()
that is waiting, with lossy on, will return that key. The other waiting calls (if any) will continue waiting. - That key will be stored in the queue of get keys, so that
get()
with lossy off will return the oldest key pressed not returned byget()
yet. - All events will be fired with that key as their input
- That key will be stored in the list of
getAsync()
keys, where that lis twill be returned and set to empty list on the next call togetAsync()
If all this is too much, here is an example use case:
import keyPress
import time
import threading
def KeyPressed(k, printLock):
printLock.acquire()
print "Event: " + k
printLock.release()
time.sleep(4)
printLock.acquire()
print "Event after delay: " + k
printLock.release()
def GetKeyBlocking(keys, printLock):
while keys.capturing():
keyReceived = keys.get()
time.sleep(1)
printLock.acquire()
if not keyReceived is None:
print "Block " + keyReceived
else:
print "Block None"
printLock.release()
def GetKeyBlockingLossy(keys, printLock):
while keys.capturing():
keyReceived = keys.get(lossy=True)
time.sleep(1)
printLock.acquire()
if not keyReceived is None:
print "Lossy: " + keyReceived
else:
print "Lossy: None"
printLock.release()
def CheckToClose(k, (keys, printLock)):
printLock.acquire()
print "Close: " + k
printLock.release()
if k == "c":
keys.stopCapture()
printLock = threading.Lock()
print "Press a key:"
print "You pressed: " + keyPress.getKey()
print ""
keys = keyPress.KeyCapture()
keys.addEvent(KeyPressed, printLock)
print "Starting capture"
keys.startCapture(CheckToClose, (keys, printLock))
getKeyBlockingThread = threading.Thread(target=GetKeyBlocking, args=(keys, printLock))
getKeyBlockingThread.daemon = True
getKeyBlockingThread.start()
getKeyBlockingThreadLossy = threading.Thread(target=GetKeyBlockingLossy, args=(keys, printLock))
getKeyBlockingThreadLossy.daemon = True
getKeyBlockingThreadLossy.start()
while keys.capturing():
keysPressed = keys.getAsync()
printLock.acquire()
if keysPressed != []:
print "Async: " + str(keysPressed)
printLock.release()
time.sleep(1)
print "done capturing"
It is working well for me from the simple test I made, but I will happily take others feedback as well if there is something I missed.
I posted this here as well.

- 18,769
- 10
- 104
- 133

- 4,217
- 3
- 22
- 34
You could use click. It's well-tested and works on Linux, Mac & Windows.
import click
print('Continue? [yn] ')
c = click.getchar() # Gets a single character
if c == 'y':
print('We will go on')
elif c == 'n':
print('Abort!')
else:
print('Invalid input :(')

- 551
- 7
- 9
This might be a use case for a context manager. Leaving aside allowances for Windows OS, here's my suggestion:
#!/usr/bin/env python3
# file: 'readchar.py'
"""
Implementation of a way to get a single character of input
without waiting for the user to hit <Enter>.
(OS is Linux, Ubuntu 14.04)
"""
import tty, sys, termios
class ReadChar():
def __enter__(self):
self.fd = sys.stdin.fileno()
self.old_settings = termios.tcgetattr(self.fd)
tty.setraw(sys.stdin.fileno())
return sys.stdin.read(1)
def __exit__(self, type, value, traceback):
termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_settings)
def test():
while True:
with ReadChar() as rc:
char = rc
if ord(char) <= 32:
print("You entered character with ordinal {}."\
.format(ord(char)))
else:
print("You entered character '{}'."\
.format(char))
if char in "^C^D":
sys.exit()
if __name__ == "__main__":
test()

- 2,312
- 4
- 19
- 25

- 59
- 1
- 1
-
You could also return `self ` in `__enter__` and have a `read` method that returns `sys.stdin.read(1)`, then you could read multiple characters in one context. – L3viathan Apr 12 '17 at 06:46
The ActiveState's recipe seems to contain a little bug for "posix" systems that prevents Ctrl-C
from interrupting (I'm using Mac). If I put the following code in my script:
while(True):
print(getch())
I will never be able to terminate the script with Ctrl-C
, and I have to kill my terminal to escape.
I believe the following line is the cause, and it's also too brutal:
tty.setraw(sys.stdin.fileno())
Asides from that, package tty
is not really needed, termios
is enough to handle it.
Below is the improved code that works for me (Ctrl-C
will interrupt), with the extra getche
function that echo the char as you type:
if sys.platform == 'win32':
import msvcrt
getch = msvcrt.getch
getche = msvcrt.getche
else:
import sys
import termios
def __gen_ch_getter(echo):
def __fun():
fd = sys.stdin.fileno()
oldattr = termios.tcgetattr(fd)
newattr = oldattr[:]
try:
if echo:
# disable ctrl character printing, otherwise, backspace will be printed as "^?"
lflag = ~(termios.ICANON | termios.ECHOCTL)
else:
lflag = ~(termios.ICANON | termios.ECHO)
newattr[3] &= lflag
termios.tcsetattr(fd, termios.TCSADRAIN, newattr)
ch = sys.stdin.read(1)
if echo and ord(ch) == 127: # backspace
# emulate backspace erasing
# https://stackoverflow.com/a/47962872/404271
sys.stdout.write('\b \b')
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, oldattr)
return ch
return __fun
getch = __gen_ch_getter(False)
getche = __gen_ch_getter(True)
References:

- 608
- 1
- 11
- 16
If I'm doing something complicated I'll use curses to read keys. But a lot of times I just want a simple Python 3 script that uses the standard library and can read arrow keys, so I do this:
import sys, termios, tty
key_Enter = 13
key_Esc = 27
key_Up = '\033[A'
key_Dn = '\033[B'
key_Rt = '\033[C'
key_Lt = '\033[D'
fdInput = sys.stdin.fileno()
termAttr = termios.tcgetattr(0)
def getch():
tty.setraw(fdInput)
ch = sys.stdin.buffer.raw.read(4).decode(sys.stdin.encoding)
if len(ch) == 1:
if ord(ch) < 32 or ord(ch) > 126:
ch = ord(ch)
elif ord(ch[0]) == 27:
ch = '\033' + ch[1:]
termios.tcsetattr(fdInput, termios.TCSADRAIN, termAttr)
return ch

- 944
- 7
- 7
-
1This was exactly what I was looking for, simple, and uses the default python libs, thank you!!! – Mr PizzaGuy Nov 05 '20 at 01:51
-
This is NON-BLOCKING, reads a key and and stores it in keypress.key.
import Tkinter as tk
class Keypress:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('300x200')
self.root.bind('<KeyPress>', self.onKeyPress)
def onKeyPress(self, event):
self.key = event.char
def __eq__(self, other):
return self.key == other
def __str__(self):
return self.key
in your programm
keypress = Keypress()
while something:
do something
if keypress == 'c':
break
elif keypress == 'i':
print('info')
else:
print("i dont understand %s" % keypress)

- 16,142
- 12
- 62
- 82
-
1@ThorSummoner: This code has a number of problems — so _no_, it won't work for command line applications. – martineau Aug 31 '15 at 00:29
-
It runs for a command line application, given that the windows manager is running. – Davoud Taghawi-Nejad Aug 31 '15 at 17:36
-
No, it doesn't run in headless OS. But it does run in a command line window. – Davoud Taghawi-Nejad Nov 17 '15 at 15:43
Try this with pygame:
import pygame
pygame.init() // eliminate error, pygame.error: video system not initialized
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
d = "space key"
print "You pressed the", d, "."

- 19,069
- 5
- 54
- 72

- 39
- 1
-
That is a neat idea, but it doesn't work on the command line: `pygame.error: video system not initialized` – dirkjot Apr 21 '15 at 11:15
A comment in one of the other answers mentioned cbreak mode, which is important for Unix implementations because you generally don't want ^C (KeyboardError
) to be consumed by getchar (as it will when you set the terminal to raw mode, as done by most other answers).
Another important detail is that if you're looking to read one character and not one byte, you should read 4 bytes from the input stream, as that's the maximum number of bytes a single character will consist of in UTF-8 (Python 3+). Reading only a single byte will produce unexpected results for multi-byte characters such as keypad arrows.
Here's my changed implementation for Unix:
import contextlib
import os
import sys
import termios
import tty
_MAX_CHARACTER_BYTE_LENGTH = 4
@contextlib.contextmanager
def _tty_reset(file_descriptor):
"""
A context manager that saves the tty flags of a file descriptor upon
entering and restores them upon exiting.
"""
old_settings = termios.tcgetattr(file_descriptor)
try:
yield
finally:
termios.tcsetattr(file_descriptor, termios.TCSADRAIN, old_settings)
def get_character(file=sys.stdin):
"""
Read a single character from the given input stream (defaults to sys.stdin).
"""
file_descriptor = file.fileno()
with _tty_reset(file_descriptor):
tty.setcbreak(file_descriptor)
return os.read(file_descriptor, _MAX_CHARACTER_BYTE_LENGTH)

- 1,329
- 11
- 21
TL;DR: here is your no-dependencies-crossplatform maximum density copy-pasta
I know I was looking for that ☝️. You came here from google and want something that will just work without pip install this-and-that? I'm fairly sure this solution will continue working for a long time.
Example use
>>> getch_but_it_actually_works() # just normal key like a
'a'
>>> getch_but_it_actually_works() # a but its shift or capslock
'A'
>>> getch_but_it_actually_works() # just bare enter
'\r'
>>> getch_but_it_actually_works() # literal ESC key
'\x1b'
>>> getch_but_it_actually_works() # one of the arrow keys on linux
'\x1b[A'
>>> getch_but_it_actually_works() # one of the arrow keys on windows
'àK'
>>> getch_but_it_actually_works() # some really obscure key-combo. still works.
'\x1b[19;6~'
Crossplatform solution, no external dependencies
Scroll for more detailed answer at the end with sane indentation and commenting. This is maximum density preview for easy copy-pasting. Just call getch_but_it_actually_works()
import os
def _read_one_wide_char_win(): return msvcrt.getwch()
def _char_can_be_escape_win(char): return True if char in ("\x00", "à") else False
def _dump_keyboard_buff_win():
try: msvcrt.ungetwch("a")
except OSError: return msvcrt.getwch()
else: _ = msvcrt.getwch(); return ""
def _read_one_wide_char_nix():
old_settings = termios.tcgetattr(sys.stdin.fileno()); tty.setraw(sys.stdin.fileno())
wchar = sys.stdin.read(1)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings); return wchar
def _char_can_be_escape_nix(char): return True if char == "\x1b" else False
def _dump_keyboard_buff_nix():
old_settings = termios.tcgetattr(sys.stdin.fileno())
tty.setraw(sys.stdin.fileno()); os.set_blocking(sys.stdin.fileno(), False)
buffer_dump = ""
while char := sys.stdin.read(1): buffer_dump += char
os.set_blocking(sys.stdin.fileno(), True); termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
if buffer_dump: return buffer_dump
else: return ""
if os.name == "nt":
import msvcrt
read_one_wdchar, char_can_escape, dump_key_buffer = _read_one_wide_char_win, _char_can_be_escape_win, _dump_keyboard_buff_win
if os.name == "posix":
import termios, tty, sys
read_one_wdchar, char_can_escape, dump_key_buffer = _read_one_wide_char_nix, _char_can_be_escape_nix, _dump_keyboard_buff_nix
def getch_but_it_actually_works():
wchar = read_one_wdchar()
if char_can_escape(wchar): dump = dump_key_buffer(); return wchar + dump
else: return wchar
The long answer, code with comments and sane indentation
Here is the long answer with all the comments. Still no dependencies.
This will in all likelihood work for long time on both linux and windows. No external dependencies, only built-ins.
It will also deal with edge cases like hitting arrow keys or something obscure like <ctrl + shift + f12> which will produce long ANSI escape sequence on linux and something else on windows. It will capture things like <ctrl+x> or <ctrl+z> or tab or F1-12 as single input
I have come back to this post literally dozens of times of the years so now it is time for me to give back the two cents and interest. Below is the fully commented code.
The example is somewhat long but you can skip reading most of it. The relevant bit is at the very end, you can just copy-paste the entire thing.
import os
def _read_one_wide_char_win():
"""Wait keyhit return chr. Get only 1st chr if multipart key like arrow"""
return msvcrt.getwch()
def _char_can_be_escape_win(char):
"""Return true if char could start a multipart key code (e.g.: arrows)"""
return True if char in ("\x00", "à") else False # \x00 is null character
def _dump_keyboard_buff_win():
"""If piece of multipart keycode in buffer, return it. Else return None"""
try: # msvcrt.kbhit wont work with msvcrt.getwch
msvcrt.ungetwch("a") # check buffer status by ungetching wchr
except OSError: # ungetch fails > something in buffer so >
return msvcrt.getwch() # return the buffer note: win multipart keys
else: # are always 2 parts. if ungetwch does not fail
_ = msvcrt.getwch() # clean up and return empty string
return ""
def _read_one_wide_char_nix():
"""Wait keyhit return chr. Get only 1st chr if multipart key like arrow"""
old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings
tty.setraw(sys.stdin.fileno()) # set raw mode to catch raw key w/o enter
wchar = sys.stdin.read(1)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
return wchar
def _char_can_be_escape_nix(char):
"""Return true if char could start a multipart key code (e.g.: arrows)"""
return True if char == "\x1b" else False # "\x1b" is literal esc-key
def _dump_keyboard_buff_nix():
"""If parts of multipart keycode in buffer, return them. Otherwise None"""
old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings
tty.setraw(sys.stdin.fileno()) # raw to read single 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 ""
if os.name == "nt":
import msvcrt
read_one_wdchar = _read_one_wide_char_win
char_can_escape = _char_can_be_escape_win
dump_key_buffer = _dump_keyboard_buff_win
if os.name == "posix":
import termios
import tty
import sys
read_one_wdchar = _read_one_wide_char_nix
char_can_escape = _char_can_be_escape_nix
dump_key_buffer = _dump_keyboard_buff_nix
def getch_but_it_actually_works():
"""Returns a printable character or a keycode corresponding to special key
like arrow or insert. Compatible with windows and linux, no external libs
except for builtins. Uses different builtins for windows and linux.
This function is more accurately called:
"get_wide_character_or_keycode_if_the_key_was_nonprintable()"
e.g.:
* returns "e" if e was pressed
* returns "E" if shift or capslock was on
* returns "x1b[19;6~'" for ctrl + shift + F8 on unix
You can use string.isprintable() if you need to sometimes print the output
and sometimes use it for menu control and such. Printing raw ansi escape
codes can cause your terminal to do things like move cursor three rows up.
Enter will return "\ r" on all platforms (without the space seen here)
as the enter key will produce carriage return, but windows and linux
interpret it differently in different contexts on higher level
"""
wchar = read_one_wdchar() # get first char from key press or key combo
if char_can_escape(wchar): # if char is escapecode, more may be waiting
dump = dump_key_buffer() # dump buffer to check if more were waiting.
return wchar + dump # return escape+buffer. buff could be just ""
else: # if buffer was empty then we return a single
return wchar # key like "e" or "\x1b" for the ESC button

- 310
- 1
- 7
The curses
package in python can be used to enter "raw" mode for character input from the terminal with just a few statements. Curses' main use is to take over the screen for output, which may not be what you want. This code snippet uses print()
statements instead, which are usable, but you must be aware of how curses changes line endings attached to output.
#!/usr/bin/python3
# Demo of single char terminal input in raw mode with the curses package.
import sys, curses
def run_one_char(dummy):
'Run until a carriage return is entered'
char = ' '
print('Welcome to curses', flush=True)
while ord(char) != 13:
char = one_char()
def one_char():
'Read one character from the keyboard'
print('\r? ', flush= True, end = '')
## A blocking single char read in raw mode.
char = sys.stdin.read(1)
print('You entered %s\r' % char)
return char
## Must init curses before calling any functions
curses.initscr()
## To make sure the terminal returns to its initial settings,
## and to set raw mode and guarantee cleanup on exit.
curses.wrapper(run_one_char)
print('Curses be gone!')

- 311
- 1
- 8
-
Since you're importing curses, you might as well use https://docs.python.org/3/library/curses.html#curses.window.getch. – Big McLargeHuge Nov 06 '22 at 17:27
I believe that this is one the most elegant solution.
import os
if os.name == 'nt':
import msvcrt
def getch():
return msvcrt.getch().decode()
else:
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
def getch():
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
and then use it in the code:
if getch() == chr(ESC_ASCII_VALUE):
print("ESC!")

- 5,577
- 11
- 68
- 110
Simplest cross platform solution is sshkeyboard. Install with pip install sshkeyboard
,
then write script such as:
from sshkeyboard import listen_keyboard
def press(key):
print(f"'{key}' pressed")
def release(key):
print(f"'{key}' released")
listen_keyboard(
on_press=press,
on_release=release,
)
And it will print:
'a' pressed
'a' released
When A
key is pressed. ESC
key ends the listening by default.
It requires less coding than for example curses, tkinter and getch.

- 171
- 1
- 5
My solution for python3, not depending on any pip packages.
# precondition: import tty, sys
def query_yes_no(question, default=True):
"""
Ask the user a yes/no question.
Returns immediately upon reading one-char answer.
Accepts multiple language characters for yes/no.
"""
if not sys.stdin.isatty():
return default
if default:
prompt = "[Y/n]?"
other_answers = "n"
else:
prompt = "[y/N]?"
other_answers = "yjosiá"
print(question,prompt,flush= True,end=" ")
oldttysettings = tty.tcgetattr(sys.stdin.fileno())
try:
tty.setraw(sys.stdin.fileno())
return not sys.stdin.read(1).lower() in other_answers
except:
return default
finally:
tty.tcsetattr(sys.stdin.fileno(), tty.TCSADRAIN , oldttysettings)
sys.stdout.write("\r\n")
tty.tcdrain(sys.stdin.fileno())

- 1
The accepted answer didn't perform that well for me (I'd hold a key, nothing would happen, then I'd press another key and it would work).
After learning about the curses module, it really seems like the right way to go. And it's now available for Windows through windows-cursors (available through pip), so you can program in a platform agnostic manner. Here's an example inspired by this nice tutorial on YouTube:
import curses
def getkey(stdscr):
curses.curs_set(0)
while True:
key = stdscr.getch()
if key != -1:
break
return key
if __name__ == "__main__":
print(curses.wrapper(getkey))
Save it with a .py
extension, or run curses.wrapper(getkey)
in interactive mode.

- 497
- 7
- 21
Answered here: raw_input in python without pressing enter
Use this code-
from tkinter import Tk, Frame
def __set_key(e, root):
"""
e - event with attribute 'char', the released key
"""
global key_pressed
if e.char:
key_pressed = e.char
root.destroy()
def get_key(msg="Press any key ...", time_to_sleep=3):
"""
msg - set to empty string if you don't want to print anything
time_to_sleep - default 3 seconds
"""
global key_pressed
if msg:
print(msg)
key_pressed = None
root = Tk()
root.overrideredirect(True)
frame = Frame(root, width=0, height=0)
frame.bind("<KeyRelease>", lambda f: __set_key(f, root))
frame.pack()
root.focus_set()
frame.focus_set()
frame.focus_force() # doesn't work in a while loop without it
root.after(time_to_sleep * 1000, func=root.destroy)
root.mainloop()
root = None # just in case
return key_pressed
def __main():
c = None
while not c:
c = get_key("Choose your weapon ... ", 2)
print(c)
if __name__ == "__main__":
__main()
Reference: https://github.com/unfor19/mg-tools/blob/master/mgtools/get_key_pressed.py

- 2,870
- 1
- 24
- 34
If you want to register only one single key press even if the user pressed it for more than once or kept pressing the key longer. To avoid getting multiple pressed inputs use the while loop and pass it.
import keyboard
while(True):
if(keyboard.is_pressed('w')):
s+=1
while(keyboard.is_pressed('w')):
pass
if(keyboard.is_pressed('s')):
s-=1
while(keyboard.is_pressed('s')):
pass
print(s)

- 877
- 8
- 15
-
From which which package is the `keyboard` module supposed to come? And what makes you write `if():`? This is not C :) – flaschbier Nov 27 '21 at 13:16
The build-in raw_input should help.
for i in range(3):
print ("So much work to do!")
k = raw_input("Press any key to continue...")
print ("Ok, back to work.")

- 9
- 1