从终端检测脚本中键盘输入的最简单方法是什么?

我有一个简单的 python 脚本,它有一些循环运行的函数(我正在获取传感器读数)。

while True:
print "Doing a function"

如果键盘被按下,我想打印“按键”。

在 Python 中最简单的方法是什么?我到处都找过了。我已经知道如何使用 pygame 了,但是我宁愿不使用它。如果我必须使用 pygame,是否有可能没有一个单独的应用程序窗口?:

import pygame, time
from pygame.locals import *


pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption('Pygame Keyboard Test')
pygame.mouse.set_visible(0)




while True:


print "doing a function"


for event in pygame.event.get():
if (event.type == KEYUP) or (event.type == KEYDOWN):
print "key pressed"
time.sleep(0.1)
239403 次浏览

The Python Documentation provides this snippet to get single characters from the keyboard:

import termios, fcntl, sys, os
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)
if c:
print("Got character", repr(c))
except IOError: pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)

You can also use the PyHook module to get your job done.

You can use methods from http://docs.python.org/2/library/msvcrt.html if you are on Windows.

import msvcrt
....
while True:
print "Doing a function"
if msvcrt.kbhit():
print "Key pressed: %s" % msvcrt.getch()

These functions, based on the above, seem to work well for getting characters from the keyboard (blocking and non-blocking):

import termios, fcntl, sys, os


def get_char_keyboard():
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)


c = None
try:
c = sys.stdin.read(1)
except IOError: pass


termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)


return c


def get_char_keyboard_nonblock():
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)


c = None


try:
c = sys.stdin.read(1)
except IOError: pass


termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)


return c

Edit:

I've thought about this problem a lot, and there are a few different behaviors one could want. I've been implementing most of them for Unix and Windows, and will post them here once they are done.

Synchronous/Blocking key capture:

  1. A simple input or raw_input, a blocking function which returns text typed by a user once they press a newline.
  2. A simple blocking function that waits for the user to press a single key, then returns that key

Asynchronous key capture:

  1. A callback that is called with the pressed key whenever the user types a key into the command prompt, even when typing things into an interpreter (a keylogger)
  2. A callback that is called with the typed text after the user presses enter (a less realtime keylogger)
  3. A callback that is called with the keys pressed when a program is running (say, in a for loop or while loop)

Polling:

  1. The user simply wants to be able to do something when a key is pressed, without having to wait for that key (so this should be non-blocking). Thus they call a poll() function and that either returns a key, or returns None. This can either be lossy (if they take too long to between poll they can miss a key) or non-lossy (the poller will store the history of all keys pressed, so when the poll() function requests them they will always be returned in the order pressed).

  2. The same as 1, except that poll only returns something once the user presses a newline.

Robots:

These are something that can be called to programmatically fire keyboard events. This can be used alongside key captures to echo them back out to the user

Implementations

Synchronous/Blocking key capture:

A simple input or raw_input, a blocking function which returns text typed by a user once they press a newline.

typedString = raw_input()

A simple blocking function that waits for the user to press a single key, then returns that key

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)




def getKey():
inkey = _Getch()
import sys
for i in xrange(sys.maxint):
k=inkey()
if k<>'':break


return k

Asynchronous key capture:

A callback that is called with the pressed key whenever the user types a key into the command prompt, even when typing things into an interpreter (a keylogger)

A callback that is called with the typed text after the user presses enter (a less realtime keylogger)

Windows:

它使用下面给出的Windows机器人,将脚本命名为keypress.py

# Some if this is from http://nullege.com/codes/show/src@e@i@einstein-HEAD@Python25Einstein@Lib@subprocess.py/380/win32api.GetStdHandle
# and
# http://nullege.com/codes/show/src@v@i@VistA-HEAD@Python@Pexpect@winpexpect.py/901/win32console.GetStdHandle.PeekConsoleInput


from ctypes import *
import time
import threading


from win32api import STD_INPUT_HANDLE, STD_OUTPUT_HANDLE


from win32console import GetStdHandle, KEY_EVENT, ENABLE_WINDOW_INPUT, ENABLE_MOUSE_INPUT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT


import keyPress




class CaptureLines():
def __init__(self):
self.stopLock = threading.Lock()


self.isCapturingInputLines = False


self.inputLinesHookCallback = CFUNCTYPE(c_int)(self.inputLinesHook)
self.pyosInputHookPointer = c_void_p.in_dll(pythonapi, "PyOS_InputHook")
self.originalPyOsInputHookPointerValue = self.pyosInputHookPointer.value


self.readHandle = GetStdHandle(STD_INPUT_HANDLE)
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)


def inputLinesHook(self):


self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)
inputChars = self.readHandle.ReadConsole(10000000)
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT)


if inputChars == "\r\n":
keyPress.KeyPress("\n")
return 0


inputChars = inputChars[:-2]


inputChars += "\n"


for c in inputChars:
keyPress.KeyPress(c)


self.inputCallback(inputChars)


return 0




def startCapture(self, inputCallback):
self.stopLock.acquire()


try:
if self.isCapturingInputLines:
raise Exception("Already capturing keystrokes")


self.isCapturingInputLines = True
self.inputCallback = inputCallback


self.pyosInputHookPointer.value = cast(self.inputLinesHookCallback, c_void_p).value
except Exception as e:
self.stopLock.release()
raise


self.stopLock.release()


def stopCapture(self):
self.stopLock.acquire()


try:
if not self.isCapturingInputLines:
raise Exception("Keystrokes already aren't being captured")


self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)


self.isCapturingInputLines = False
self.pyosInputHookPointer.value = self.originalPyOsInputHookPointerValue


except Exception as e:
self.stopLock.release()
raise


self.stopLock.release()

A callback that is called with the keys pressed when a program is running (say, in a for loop or while loop)

Windows:

import threading
from win32api import STD_INPUT_HANDLE
from win32console import GetStdHandle, KEY_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT




class KeyAsyncReader():
def __init__(self):
self.stopLock = threading.Lock()
self.stopped = True
self.capturedChars = ""


self.readHandle = GetStdHandle(STD_INPUT_HANDLE)
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)






def startReading(self, readCallback):
self.stopLock.acquire()


try:
if not self.stopped:
raise Exception("Capture is already going")


self.stopped = False
self.readCallback = readCallback


backgroundCaptureThread = threading.Thread(target=self.backgroundThreadReading)
backgroundCaptureThread.daemon = True
backgroundCaptureThread.start()
except:
self.stopLock.release()
raise


self.stopLock.release()




def backgroundThreadReading(self):
curEventLength = 0
curKeysLength = 0
while True:
eventsPeek = self.readHandle.PeekConsoleInput(10000)


self.stopLock.acquire()
if self.stopped:
self.stopLock.release()
return
self.stopLock.release()




if len(eventsPeek) == 0:
continue


if not len(eventsPeek) == curEventLength:
if self.getCharsFromEvents(eventsPeek[curEventLength:]):
self.stopLock.acquire()
self.stopped = True
self.stopLock.release()
break


curEventLength = len(eventsPeek)






def getCharsFromEvents(self, eventsPeek):
callbackReturnedTrue = False
for curEvent in eventsPeek:
if curEvent.EventType == KEY_EVENT:
if ord(curEvent.Char) == 0 or not curEvent.KeyDown:
pass
else:
curChar = str(curEvent.Char)
if self.readCallback(curChar) == True:
callbackReturnedTrue = True




return callbackReturnedTrue


def stopReading(self):
self.stopLock.acquire()
self.stopped = True
self.stopLock.release()

Polling:

The user simply wants to be able to do something when a key is pressed, without having to wait for that key (so this should be non-blocking). Thus they call a poll() function and that either returns a key, or returns None. This can either be lossy (if they take too long to between poll they can miss a key) or non-lossy (the poller will store the history of all keys pressed, so when the poll() function requests them they will always be returned in the order pressed).

Windows and OS X (and maybe Linux):

global isWindows


isWindows = False
try:
from win32api import STD_INPUT_HANDLE
from win32console import GetStdHandle, KEY_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT
isWindows = True
except ImportError as e:
import sys
import select
import termios




class KeyPoller():
def __enter__(self):
global isWindows
if isWindows:
self.readHandle = GetStdHandle(STD_INPUT_HANDLE)
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)


self.curEventLength = 0
self.curKeysLength = 0


self.capturedChars = []
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)


return self


def __exit__(self, type, value, traceback):
if isWindows:
pass
else:
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)


def poll(self):
if isWindows:
if not len(self.capturedChars) == 0:
return self.capturedChars.pop(0)


eventsPeek = self.readHandle.PeekConsoleInput(10000)


if len(eventsPeek) == 0:
return None


if not len(eventsPeek) == self.curEventLength:
for curEvent in eventsPeek[self.curEventLength:]:
if curEvent.EventType == KEY_EVENT:
if ord(curEvent.Char) == 0 or not curEvent.KeyDown:
pass
else:
curChar = str(curEvent.Char)
self.capturedChars.append(curChar)
self.curEventLength = len(eventsPeek)


if not len(self.capturedChars) == 0:
return self.capturedChars.pop(0)
else:
return None
else:
dr,dw,de = select.select([sys.stdin], [], [], 0)
if not dr == []:
return sys.stdin.read(1)
return None

Simple use case:

with KeyPoller() as keyPoller:
while True:
c = keyPoller.poll()
if not c is None:
if c == "c":
break
print c

The same as above, except that poll only returns something once the user presses a newline.

Robots:

These are something that can be called to programmatically fire keyboard events. This can be used alongside key captures to echo them back out to the user

Windows:

# Modified from http://stackoverflow.com/a/13615802/2924421


import ctypes
from ctypes import wintypes
import time


user32 = ctypes.WinDLL('user32', use_last_error=True)


INPUT_MOUSE    = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2


KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP       = 0x0002
KEYEVENTF_UNICODE     = 0x0004
KEYEVENTF_SCANCODE    = 0x0008


MAPVK_VK_TO_VSC = 0


# C struct definitions
wintypes.ULONG_PTR = wintypes.WPARAM


SendInput = ctypes.windll.user32.SendInput


PUL = ctypes.POINTER(ctypes.c_ulong)


class KEYBDINPUT(ctypes.Structure):
_fields_ = (("wVk",         wintypes.WORD),
("wScan",       wintypes.WORD),
("dwFlags",     wintypes.DWORD),
("time",        wintypes.DWORD),
("dwExtraInfo", wintypes.ULONG_PTR))


class MOUSEINPUT(ctypes.Structure):
_fields_ = (("dx",          wintypes.LONG),
("dy",          wintypes.LONG),
("mouseData",   wintypes.DWORD),
("dwFlags",     wintypes.DWORD),
("time",        wintypes.DWORD),
("dwExtraInfo", wintypes.ULONG_PTR))


class HARDWAREINPUT(ctypes.Structure):
_fields_ = (("uMsg",    wintypes.DWORD),
("wParamL", wintypes.WORD),
("wParamH", wintypes.WORD))


class INPUT(ctypes.Structure):
class _INPUT(ctypes.Union):
_fields_ = (("ki", KEYBDINPUT),
("mi", MOUSEINPUT),
("hi", HARDWAREINPUT))
_anonymous_ = ("_input",)
_fields_ = (("type",   wintypes.DWORD),
("_input", _INPUT))


LPINPUT = ctypes.POINTER(INPUT)


def _check_count(result, func, args):
if result == 0:
raise ctypes.WinError(ctypes.get_last_error())
return args


user32.SendInput.errcheck = _check_count
user32.SendInput.argtypes = (wintypes.UINT, # nInputs
LPINPUT,       # pInputs
ctypes.c_int)  # cbSize


def KeyDown(unicodeKey):
key, unikey, uniflag = GetKeyCode(unicodeKey)
x = INPUT( type=INPUT_KEYBOARD, ki= KEYBDINPUT( key, unikey, uniflag, 0))
user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))


def KeyUp(unicodeKey):
key, unikey, uniflag = GetKeyCode(unicodeKey)
extra = ctypes.c_ulong(0)
x = INPUT( type=INPUT_KEYBOARD, ki= KEYBDINPUT( key, unikey, uniflag | KEYEVENTF_KEYUP, 0))
user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))


def KeyPress(unicodeKey):
time.sleep(0.0001)
KeyDown(unicodeKey)
time.sleep(0.0001)
KeyUp(unicodeKey)
time.sleep(0.0001)




def GetKeyCode(unicodeKey):
k = unicodeKey
curKeyCode = 0
if k == "up": curKeyCode = 0x26
elif k == "down": curKeyCode = 0x28
elif k == "left": curKeyCode = 0x25
elif k == "right": curKeyCode = 0x27
elif k == "home": curKeyCode = 0x24
elif k == "end": curKeyCode = 0x23
elif k == "insert": curKeyCode = 0x2D
elif k == "pgup": curKeyCode = 0x21
elif k == "pgdn": curKeyCode = 0x22
elif k == "delete": curKeyCode = 0x2E
elif k == "\n": curKeyCode = 0x0D


if curKeyCode == 0:
return 0, int(unicodeKey.encode("hex"), 16), KEYEVENTF_UNICODE
else:
return curKeyCode, 0, 0

OS X:

#!/usr/bin/env python


import time
from Quartz.CoreGraphics import CGEventCreateKeyboardEvent
from Quartz.CoreGraphics import CGEventPost


# Python releases things automatically, using CFRelease will result in a scary error
#from Quartz.CoreGraphics import CFRelease


from Quartz.CoreGraphics import kCGHIDEventTap


# From http://stackoverflow.com/questions/281133/controlling-the-mouse-from-python-in-os-x
# and from https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/index.html#//apple_ref/c/func/CGEventCreateKeyboardEvent




def KeyDown(k):
keyCode, shiftKey = toKeyCode(k)


time.sleep(0.0001)


if shiftKey:
CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, 0x38, True))
time.sleep(0.0001)


CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, keyCode, True))
time.sleep(0.0001)


if shiftKey:
CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, 0x38, False))
time.sleep(0.0001)


def KeyUp(k):
keyCode, shiftKey = toKeyCode(k)


time.sleep(0.0001)


CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, keyCode, False))
time.sleep(0.0001)


def KeyPress(k):
keyCode, shiftKey = toKeyCode(k)


time.sleep(0.0001)


if shiftKey:
CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, 0x38, True))
time.sleep(0.0001)


CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, keyCode, True))
time.sleep(0.0001)


CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, keyCode, False))
time.sleep(0.0001)


if shiftKey:
CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, 0x38, False))
time.sleep(0.0001)






# From http://stackoverflow.com/questions/3202629/where-can-i-find-a-list-of-mac-virtual-key-codes


def toKeyCode(c):
shiftKey = False
# Letter
if c.isalpha():
if not c.islower():
shiftKey = True
c = c.lower()


if c in shiftChars:
shiftKey = True
c = shiftChars[c]
if c in keyCodeMap:
keyCode = keyCodeMap[c]
else:
keyCode = ord(c)
return keyCode, shiftKey


shiftChars = {
'~': '`',
'!': '1',
'@': '2',
'#': '3',
'$': '4',
'%': '5',
'^': '6',
'&': '7',
'*': '8',
'(': '9',
')': '0',
'_': '-',
'+': '=',
'{': '[',
'}': ']',
'|': '\\',
':': ';',
'"': '\'',
'<': ',',
'>': '.',
'?': '/'
}




keyCodeMap = {
'a'                 : 0x00,
's'                 : 0x01,
'd'                 : 0x02,
'f'                 : 0x03,
'h'                 : 0x04,
'g'                 : 0x05,
'z'                 : 0x06,
'x'                 : 0x07,
'c'                 : 0x08,
'v'                 : 0x09,
'b'                 : 0x0B,
'q'                 : 0x0C,
'w'                 : 0x0D,
'e'                 : 0x0E,
'r'                 : 0x0F,
'y'                 : 0x10,
't'                 : 0x11,
'1'                 : 0x12,
'2'                 : 0x13,
'3'                 : 0x14,
'4'                 : 0x15,
'6'                 : 0x16,
'5'                 : 0x17,
'='                 : 0x18,
'9'                 : 0x19,
'7'                 : 0x1A,
'-'                 : 0x1B,
'8'                 : 0x1C,
'0'                 : 0x1D,
']'                 : 0x1E,
'o'                 : 0x1F,
'u'                 : 0x20,
'['                 : 0x21,
'i'                 : 0x22,
'p'                 : 0x23,
'l'                 : 0x25,
'j'                 : 0x26,
'\''                : 0x27,
'k'                 : 0x28,
';'                 : 0x29,
'\\'                : 0x2A,
','                 : 0x2B,
'/'                 : 0x2C,
'n'                 : 0x2D,
'm'                 : 0x2E,
'.'                 : 0x2F,
'`'                 : 0x32,
'k.'                : 0x41,
'k*'                : 0x43,
'k+'                : 0x45,
'kclear'            : 0x47,
'k/'                : 0x4B,
'k\n'               : 0x4C,
'k-'                : 0x4E,
'k='                : 0x51,
'k0'                : 0x52,
'k1'                : 0x53,
'k2'                : 0x54,
'k3'                : 0x55,
'k4'                : 0x56,
'k5'                : 0x57,
'k6'                : 0x58,
'k7'                : 0x59,
'k8'                : 0x5B,
'k9'                : 0x5C,


# keycodes for keys that are independent of keyboard layout
'\n'                : 0x24,
'\t'                : 0x30,
' '                 : 0x31,
'del'               : 0x33,
'delete'            : 0x33,
'esc'               : 0x35,
'escape'            : 0x35,
'cmd'               : 0x37,
'command'           : 0x37,
'shift'             : 0x38,
'caps lock'         : 0x39,
'option'            : 0x3A,
'ctrl'              : 0x3B,
'control'           : 0x3B,
'right shift'       : 0x3C,
'rshift'            : 0x3C,
'right option'      : 0x3D,
'roption'           : 0x3D,
'right control'     : 0x3E,
'rcontrol'          : 0x3E,
'fun'               : 0x3F,
'function'          : 0x3F,
'f17'               : 0x40,
'volume up'         : 0x48,
'volume down'       : 0x49,
'mute'              : 0x4A,
'f18'               : 0x4F,
'f19'               : 0x50,
'f20'               : 0x5A,
'f5'                : 0x60,
'f6'                : 0x61,
'f7'                : 0x62,
'f3'                : 0x63,
'f8'                : 0x64,
'f9'                : 0x65,
'f11'               : 0x67,
'f13'               : 0x69,
'f16'               : 0x6A,
'f14'               : 0x6B,
'f10'               : 0x6D,
'f12'               : 0x6F,
'f15'               : 0x71,
'help'              : 0x72,
'home'              : 0x73,
'pgup'              : 0x74,
'page up'           : 0x74,
'forward delete'    : 0x75,
'f4'                : 0x76,
'end'               : 0x77,
'f2'                : 0x78,
'page down'         : 0x79,
'pgdn'              : 0x79,
'f1'                : 0x7A,
'left'              : 0x7B,
'right'             : 0x7C,
'down'              : 0x7D,
'up'                : 0x7E
}

I wrote a more easy-to-use implementation for @enrico.bacis's answer. It supports both Linux(python2.7 and python3.5) and Windows(python2.7). It may support Mac OS, but I didn't test it. If you tried it on Mac, please tell me the result.

'''
Author: Yu Lou
Date: 2017-02-23


Based on the answer by @enrico.bacis in http://stackoverflow.com/a/13207724/4398908
and @Phylliida in http://stackoverflow.com/a/31736883/4398908
'''


# Import modules
try:
try:
import termios, fcntl, sys, os, curses # Import modules for Linux
except ImportError:
import msvcrt # Import module for Windows
except ImportError:
raise Exception('This platform is not supported.')


class KeyGetterLinux:
'''
Implemented kbhit(), getch() and getchar() in Linux.


Tested on Ubuntu 16.10(Linux 4.8.0), Python 2.7.12 and Python 3.5.2
'''
def __init__(self):
self.buffer = '' # A buffer to store the character read by kbhit
self.started = False # Whether initialization is complete


def kbhit(self, echo = False):
'''
Return whether a key is hitten.
'''
if not self.buffer:
if echo:
self.buffer = self.getchar(block = False)
else:
self.buffer = self.getch(block = False)


return bool(self.buffer)


def getch(self, block = True):
'''
Return a single character without echo.
If block is False and no input is currently available, return an empty string without waiting.
'''
try:
curses.initscr()
curses.noecho()
return self.getchar(block)
finally:
curses.endwin()


def getchar(self, block = True):
'''
Return a single character and echo.
If block is False and no input is currently available, return an empty string without waiting.
'''
self._start()
try:
return self._getchar(block)
finally:
self._stop()


def _getchar(self, block = True):
'''
Return a single character and echo.
If block is False and no input is currently available, return a empty string without waiting.
Should be called between self._start() and self._end()
'''
assert self.started, ('_getchar() is called before _start()')


# Change the terminal setting
if block:
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.old_flags & ~os.O_NONBLOCK)
else:
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.old_flags | os.O_NONBLOCK)


if self.buffer: # Use the character in buffer first
result = self.buffer
self.buffer = ''
else:
try:
result = sys.stdin.read(1)
except IOError: # In python 2.7, using read() when no input is available will result in IOError.
return ''


return result


def _start(self):
'''
Initialize the terminal.
'''
assert not self.started, '_start() is called twice'


self.fd = sys.stdin.fileno()


self.old_attr = termios.tcgetattr(self.fd)


new_attr = termios.tcgetattr(self.fd)
new_attr[3] = new_attr[3] & ~termios.ICANON
termios.tcsetattr(self.fd, termios.TCSANOW, new_attr)


self.old_flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)


self.started = True


def _stop(self):
'''
Restore the terminal.
'''
assert self.started, '_start() is not called'


termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_attr)
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.old_flags)


self.started = False


# Magic functions for context manager
def __enter__(self):
self._start()
self.getchar = self._getchar # No need for self._start() now
return self


def __exit__(self, type, value, traceback):
self._stop()
return False


class KeyGetterWindows:
'''
kbhit() and getchar() in Windows.


Tested on Windows 7 64 bit, Python 2.7.1
'''
def kbhit(self, echo):
return msvcrt.kbhit()


def getchar(self, block = True):
if not block and not msvcrt.kbhit():
return ''
return msvcrt.getchar()


def getch(self, block = True):
if not block and not msvcrt.kbhit():
return ''
return msvcrt.getch()


_getchar = getchar


# Magic functions for context manager
def __enter__(self):
return self


def __exit__(self, type, value, traceback):
return False


try:
import termios
KeyGetter = KeyGetterLinux # Use KeyGetterLinux if termios exists
except ImportError:
KeyGetter = KeyGetterWindows # Use KeyGetterWindows otherwise

This is an example(assume that you saved the codes above in 'key_getter.py'):

from key_getter import KeyGetter
import time


def test1(): # Test with block=False
print('test1')


k = KeyGetter()
try:
while True:
if k.kbhit():
print('Got', repr(k.getch(False)))
print('Got', repr(k.getch(False)))
else:
print('Nothing')


time.sleep(0.5)
except KeyboardInterrupt:
pass
print(input('Enter something:'))


def test2(): # Test context manager with block=True
print('test2')


with KeyGetter() as k:
try:
while True:
if k.kbhit():
print('Got', repr(k.getchar(True)))
print('Got', repr(k.getchar(True)))
else:
print('Nothing')


time.sleep(0.5)
except KeyboardInterrupt:
pass
print(input('Enter something:'))


test1()
test2()

One of the simplest way I found is to use pynput module.can be found here with nice examples as well

from pynput import keyboard


def on_press(key):
try:
print('alphanumeric key {0} pressed'.format(
key.char))
except AttributeError:
print('special key {0} pressed'.format(
key))


def on_release(key):
print('{0} released'.format(
key))
if key == keyboard.Key.esc:
# Stop listener
return False


# Collect events until released
with keyboard.Listener(
on_press=on_press,
on_release=on_release) as listener:
listener.join()

above is the example worked out for me and to install, go
for python 2:

    pip install pynput

for python 3:

    pip3 install pynput

Inspired from code found above (credits), the simple blocking (aka not CPU consuming) macOS version I was looking for:

import termios
import sys
import fcntl
import os


def getKeyCode(blocking = True):
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)
if not blocking:
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
return ord(sys.stdin.read(1))
except IOError:
return 0
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
if not blocking:
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)


def getKeyStroke():
code  = getKeyCode()
if code == 27:
code2 = getKeyCode(blocking = False)
if code2 == 0:
return "esc"
elif code2 == 91:
code3 = getKeyCode(blocking = False)
if code3 == 65:
return "up"
elif code3 == 66:
return "down"
elif code3 == 68:
return "left"
elif code3 == 67:
return "right"
else:
return "esc?"
elif code == 127:
return "backspace"
elif code == 9:
return "tab"
elif code == 10:
return "return"
elif code == 195 or code == 194:
code2 = getKeyCode(blocking = False)
return chr(code)+chr(code2) # utf-8 char
else:
return chr(code)




while True:
print getKeyStroke()

2017-11-09, EDITED: Not tested with Python 3

This worked for me on macOS Sierra and Python 2.7.10 and 3.6.3

import sys,tty,os,termios
def getkey():
old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
try:
while True:
b = os.read(sys.stdin.fileno(), 3).decode()
if len(b) == 3:
k = ord(b[2])
else:
k = ord(b)
key_mapping = {
127: 'backspace',
10: 'return',
32: 'space',
9: 'tab',
27: 'esc',
65: 'up',
66: 'down',
67: 'right',
68: 'left'
}
return key_mapping.get(k, chr(k))
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
try:
while True:
k = getkey()
if k == 'esc':
quit()
else:
print(k)
except (KeyboardInterrupt, SystemExit):
os.system('stty sane')
print('stopping.')
import turtle


wn = turtle.Screen()
turtle = turtle.Turtle()


def printLetter():
print("a")


turtle.listen()
turtle.onkey(printLetter, "a")

This needs run as root: (Warning, this is a system-wide keylogger)

#!/usr/bin/python3


import signal
import keyboard
import time
import os




if not os.geteuid() == 0:
print("This script needs to be run as root.")
exit()


def exitNice(signum, frame):
global running
running = False


def keyEvent(e):
global running
if e.event_type == "up":
print("Key up: " + str(e.name))
if e.event_type == "down":
print("Key down: " + str(e.name))
if e.name == "q":
exitNice("", "")
print("Quitting")


running = True
signal.signal(signal.SIGINT, exitNice)
keyboard.hook(keyEvent)


print("Press 'q' to quit")
fps = 1/24
while running:
time.sleep(fps)


Well, since the date of this question post, a Python library addressed this topic. pynput library, from Moses Palmer, is GREAT to catch keyboard and mouse events in a very simple way.

(mind the missing 'i' in pynput - I missed it too... ;-) )

from time import sleep
import keyboard
        

flag = True
        

def main():
global flag
while flag:
print('sleeping')
sleep(2)
        

def changeFlag():
global flag
flag = False
print('stop')
        

keyboard.on_press_key("r", lambda _:changeFlag())
        

main()