파이썬은 사용자로부터 단일 문자를 읽습니다


261

사용자 입력에서 하나의 단일 문자를 읽는 방법이 있습니까? 예를 들어 터미널에서 하나의 키를 누르면 반환됩니다 (정렬 한 정렬 getch()). Windows에 기능이 있다는 것을 알고 있지만 플랫폼 간 무언가를 원합니다.


1
Windows에서는이 질문 과 같은 문제가 발생했습니다 . 이 솔루션은 대체하는 msvcrt.getch과를 msvcrt.getwch가 제안.
A. Roy

해결책은 getch 모듈 "pip install getch"를 설치하는 것입니다. Python2의 경우 "pip2 install files.pythonhosted.org/packages/56/f7/… " 명령을 사용하십시오 . 이 솔루션은 Termux (Android)에서도 작동합니다.
Petr Mach

답변:


189

다음은 Windows, Linux 및 OSX에서 단일 문자를 읽는 방법을 알려주는 사이트 링크입니다. http://code.activestate.com/recipes/134892/

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()

18
코드는 코드를 포함 할 수있을 정도로 짧아 보이지만 좋은 (교차 플랫폼 간) 답변을 빨리 찾기위한 +1입니다.
John Mulder

4
비 라틴 문자 (예 : 키릴 문자)를 잘 처리합니까? 나는 그것에 문제가 있고 그것이 내 실수인지 아닌지를 알아낼 수 없다.
Phlya

7
나는 ImportError예외가 if-statement와 같은 방식 으로 사용되는 것을 좋아하지 않는다 . 왜 OS를 확인하기 위해 platform.system ()을 호출하지 않습니까?
Seismoid

10
@Seismoid : 용서를 구하는 것이 일반적으로 더 나은 것으로 간주됩니다. stackoverflow.com/questions/12265451/…
dirkjot

4
OS X에서 작동하지 않습니다 : "old_settings = termios.tcgetattr (fd)" "termios.error : (25, '장치에 부적절한 ioctl')"
표시 이름

79
sys.stdin.read(1)

기본적으로 STDIN에서 1 바이트를 읽습니다.

기다리지 않는 방법을 사용해야하는 경우 \n이전 답변에서 제안한대로이 코드를 사용할 수 있습니다.

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()

( http://code.activestate.com/recipes/134892/ 에서 가져옴 )


33
sys.stdin.read (1)가 \ n, lol을 기다리는 것이 이상하다고 생각합니다. 제출해 주셔서 감사합니다.
Evan Fosmark

3
하나의 문자 또는 하나의 바이트? 동일하지 않습니다.
chryss

4
파이썬은 기본적으로 라인 버퍼 모드에 있기 때문에 @Evan, 그건
존 라 Rooy

3
@EvanFosmark : sys.stdin.read (1)가 반드시 \ n을 기다릴 필요는 없습니다. 터미널 문자가 프로그램에 다른 문자를 보낼 때를 결정하는 터미널 프로그램은 '\ n'이 보일 때까지 문자를 쓰지 않습니다. 백 스페이스 키를 누르고 입력중인 내용을 수정할 수 있습니까? (정답은 파이썬 프로그램이 라인 제어를 구현하고, 버퍼를 유지하고, 백 스페이스를 처리하도록 가르치는 것이지만, "캐릭터를 읽을 때"구입하고 싶지 않은 다른 세계입니다. 시스템의 다른 모든 프로그램과 다른 처리.)
Tony Delroy

2
@Seismoid EAFP
볼트

70

두 가지 답변으로 구두 인용 된 ActiveState 레시피 가 과도하게 엔지니어링되었습니다. 다음과 같이 정리할 수 있습니다.

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()

좋은. 그러나 이것은 또한 KeyboardInterrupt의 첫 번째 문자 (Ctrl + C)를 읽으며 코드는로 끝날 가능성이 있습니다 0.
user3342816

51

readchar 라이브러리도 시도해 볼 가치가 있습니다.이 라이브러리는 부분적으로 다른 답변에서 언급 한 ActiveState 레시피를 기반으로합니다.

설치:

pip install readchar

용법:

import readchar
print("Reading a char:")
print(repr(readchar.readchar()))
print("Reading a key:")
print(repr(readchar.readkey()))

Python 2.7을 사용하여 Windows 및 Linux에서 테스트되었습니다.

Windows에서 문자 또는 ASCII 제어 코드에 매핑에만 키가 지원됩니다 ( Backspace, Enter, Esc, Tab, Ctrl+ 문자 ). GNU / 리눅스 (아마, 정확한 터미널에 따라?) 당신은 또한 얻을 Insert, Delete, Pg Up, Pg Dn, Home, End및 키 ...하지만 다음에,에서 이러한 특수 키를 분리 문제가있다 .F nEsc

주의 사항 : 대부분의 (모두?) 답변과 마찬가지로 Ctrl+ C, Ctrl+ DCtrl+ 와 같은 신호 키 Z는 잡히고 반환됩니다 ( 각각 '\x03', '\x04''\x1a'). 프로그램이 중단되기 어려울 수 있습니다.


3
Linux에서 Python 3 과도 작동합니다. readchar를 사용하면 키 또는 스레드 또는 비동기를 통해 키를 기다리는 동안 stdout으로 인쇄 할 수 있으므로 getch보다 훨씬 낫습니다.
wrobell

Win10 + Python 3.5에서 테스트되었습니다. func (* args, ** kwargs) 파일 "C : \ GitHub \ Python-Demo \ demo \ day_hello.py", 41 행, readch_eg print (readchar.readchar ()) 파일 "C : \ Users \ ipcjs \ AppData '\ x00 \ xe0'의 ch 동안 readchar의 \ Local \ Programs \ Python \ Python35 \ lib \ site-packages \ readchar \ readchar_windows.py ", 14 번째 줄 : readchar에서 '\ x00 \ xe0'의 ch 동안 : TypeError : 'in <string>'에는 왼쪽 피연산자로 문자열이 필요합니다 바이트가 아님
ipcjs

@ipcjs는 버그를 관리자에게보고하십시오
Melih Yıldız '

1
이것이 가장 좋은 대답입니다. 이 기능에 대해서만 VS C ++ 라이브러리에 대한 종속성을 추가하는 것은 미친 짓입니다.
FistOfFury

18

다른 방법 :

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

에서 이 블로그 게시물 .


나를 위해 작동하지 않는 것-호출 즉시 빈 문자열을 반환합니다. Python 3.6이 설치된 Linux에서.
Marein

1
@Marein 차단하려는 경우 (입력 대기)을 제거하십시오 | os.O_NONBLOCK. 그렇지 않으면 루프에 넣을 수 있습니다 (루프에서 비트가 회전하지 않도록 잠자기하는 것이 좋습니다).
Chris Gregg

파이썬에서는 while Truethen 을 사용하는 것이 좋습니다 while 1.
익명

10

here에 기반한이 코드 는 Ctrl+ C또는 Ctrl+ D를 누르면 KeyboardInterrupt 및 EOFError를 올바르게 발생 시킵니다.

Windows 및 Linux에서 작동해야합니다. 원본 소스에서 OS X 버전을 사용할 수 있습니다.

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()

7

(현재) 최상위 답변 (ActiveState 코드 포함)은 지나치게 복잡합니다. 단순한 함수로 충분할 때 클래스를 사용해야 할 이유가 없습니다. 아래는 동일하지만 더 읽기 쉬운 코드로 달성하는 두 가지 구현입니다.

이 두 가지 구현 모두 :

  1. Python 2 또는 Python 3에서 잘 작동합니다.
  2. Windows, OSX 및 Linux에서 작업
  3. 1 바이트 만 읽습니다 (즉, 개행을 기다리지 않습니다)
  4. 외부 라이브러리에 의존하지 않습니다
  5. 자체 포함 (함수 정의 외부의 코드 없음)

버전 1 : 읽기 쉽고 간단

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

버전 2 : 반복되는 가져 오기 및 예외 처리를 피하십시오.

[편집] ActiveState 코드의 장점 중 하나를 놓쳤습니다. 문자를 여러 번 읽으려는 경우 해당 코드는 Unix와 유사한 시스템에서 Windows 가져 오기 및 ImportError 예외 처리를 반복하는 데 드는 비용을 무시할 수 있습니다. 무시할 수있는 최적화보다 코드 가독성에 더 관심이 있어야하지만 ActiveState 코드와 동일하게 작동하고 더 읽기 쉬운 대안이 있습니다 (Louis의 대답과 비슷하지만 getChar ()는 독립적입니다).

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()

위의 getChar () 버전 중 하나를 실행하는 예제 코드 :

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))

2
키를 기다리는 동안 메시지를 인쇄 할 때 tty.setraw ()에 문제가 발생했습니다 (멀티 스레드). 간단히 말해, tty.setcbreak ()를 사용하면 다른 모든 일반 항목을 손상시키지 않고 단일 문자를 얻을 수 있습니다. 이 답변의
TheDavidFactor

4

컨텍스트 관리자의 사용 사례 일 수 있습니다. Windows OS에 대한 수당을 제외하고 다음은 제 제안입니다.

#!/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()

또한 반환 self 하고 를 반환 __enter__하는 read메서드를 가질 sys.stdin.read(1)수 있으며 한 컨텍스트에서 여러 문자를 읽을 수 있습니다.
L3viathan

4

이것을 사용하십시오 : http://home.wlu.edu/~levys/software/kbhit.py 그것은 비 블로킹입니다 (즉, while 루프를 가지고 멈추지 않고 키를 누를 수 있음을 의미합니다).

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 != []

이것을 사용하는 예 :

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()

또는 PyPigetch 모듈을 사용할 수 있습니다 . 그러나 이것은 while 루프를 차단합니다.


3

NON-BLOCKING이며 키를 읽고 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

당신의 프로그램에서

keypress = Keypress()

while something:
   do something
   if keypress == 'c':
        break
   elif keypress == 'i': 
       print('info')
   else:
       print("i dont understand %s" % keypress)

1
@ThorSummoner :이 코드는 많은 문제가 없다 - 그래서 어떤 명령 줄 응용 프로그램을위한, 그것은하지 않습니다 일을.
martineau

Windows 관리자가 실행중인 경우 명령 행 응용 프로그램에 대해 실행됩니다.
Davoud Taghawi-Nejad

아니요, 헤드리스 OS에서는 실행되지 않습니다. 그러나 명령 행 창에서 실행됩니다.
Davoud Taghawi-Nejad

3

여기 에 대한 답변 은 유익한 것이었지만 스레드를 안전하게 교차하는 플랫폼 방식으로 키 누름을 비동기식으로 가져오고 별도의 이벤트에서 키 누름을 해제하는 방법을 원했습니다. PyGame도 나에게 너무 부풀었다. 그래서 다음을 만들었습니다 (Python 2.7에서는 쉽게 이식 할 수 있다고 생각합니다). 다른 사람에게 유용 할 때를 위해 여기에서 공유 할 것이라고 생각했습니다. 이것을 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()

아이디어는 간단히 전화 keyPress.getKey()하면 키보드에서 키를 읽은 다음 반환 할 수 있다는 것입니다.

그 이상을 원한다면 나는 KeyCapture물건을 만들었습니다 . 당신은 같은 것을 통해 하나를 만들 수 있습니다 keys = keyPress.KeyCapture().

그런 다음 세 가지 작업을 수행 할 수 있습니다.

addEvent(functionName)하나의 매개 변수를 취하는 모든 함수를받습니다. 그런 다음 키를 누를 때마다 해당 키의 문자열이 입력 될 때이 함수가 호출됩니다. 이들은 별도의 스레드에서 실행되므로 원하는 것을 모두 차단할 수 있으며 KeyCapturer의 기능을 망가 뜨리거나 다른 이벤트를 지연시키지 않습니다.

get()이전과 동일한 차단 방식으로 키를 반환합니다. 이제 KeyCapture객체 를 통해 키를 캡처하고 있기 때문에 여기에서 필요하므로 keyPress.getKey()해당 동작과 충돌 할 수 있으며 한 번에 하나의 키만 캡처 할 수 있기 때문에 둘 다 일부 키를 놓치게됩니다. 또한 사용자가 'a'를 누른 다음 'b'를 get()누르고 전화를 걸면 사용자가 'c'를 누릅니다. 이 get()호출은 즉시 'a'를 반환하고 다시 호출하면 'b'를 반환 한 다음 'c'를 반환합니다. 다시 호출하면 다른 키를 누를 때까지 차단됩니다. 이를 통해 원하는 경우 차단 방식으로 키를 놓치지 않을 수 있습니다. 이런 식으로 keyPress.getKey()이전과 조금 다릅니다

getKey()back 의 동작을 원하면 호출 누른 키만 반환한다는 점을 제외하고는와 get(lossy=True)같습니다 . 따라서 위의 예 에서 사용자가 'c'를 누를 때까지 차단 한 다음 다시 호출하면 다른 키를 누를 때까지 차단됩니다.get()get()get()

getAsync()조금 다릅니다. 많은 처리를 수행하는 작업을 위해 설계되었으며 때로는 되돌아 와서 어떤 키를 눌렀는지 확인합니다. 따라서 가장 오래된 키부터 가장 최근에 누른 키 순으로 getAsync()마지막 호출 이후에 누른 모든 키 목록을 반환합니다 getAsync(). 또한 차단되지 않습니다. 즉, 마지막 호출 이후에 키를 누르지 getAsync()않으면 빈 []값이 반환됩니다.

실제로 키를 캡처를 시작하려면 호출 할 필요가 keys.startCapture()당신과 함께 keys객체 위했다. startCapture비 차단이며, 단순히 키 누름을 기록하는 하나의 스레드와 해당 키 누름을 처리하는 다른 스레드를 시작합니다. 키 누름을 기록하는 스레드가 어떤 키도 놓치지 않도록하기 위해 두 개의 스레드가 있습니다.

키 캡처를 중지하려면 전화를 걸어 keys.stopCapture()키 캡처를 중지합니다. 그러나 키 캡처는 차단 작업이므로 스레드 캡처 키는을 호출 한 후 키를 하나 더 캡처 할 수 있습니다 stopCapture().

이를 방지하기 위해 startCapture(functionName, args)키가 'c'인지 확인한 다음 종료하는 것과 같은 기능을 수행하는 함수에 선택적 매개 변수를 전달할 수 있습니다 . 예를 들어, 여기서 잠을 자면 키가 빠질 수 있으므로이 기능은 거의 수행하지 않는 것이 중요합니다.

그러나이 stopCapture()기능에서 get()호출되면 더 이상 캡처하지 않고 키 캡처가 즉시 중지 되며, 아직 키를 누르지 않은 경우 없음으로 모든 호출이 즉시 반환됩니다.

또한, 이후 get()getAsync()(당신이 그들을 검색 할 때까지), 당신이 호출 할 수 있습니다 이전의 모든 키를 누르면 저장 clearGetList()하고 clearAsyncList()이전에 누른 키를 잊어.

그 주 get(), getAsync()키를 누르면 그렇다면, 이벤트는 독립적 인 : 1. 하나의 호출 get()즉에 손실로, 기다리고, 그 키를 반환합니다. 다른 대기 통화 (있는 경우)는 계속 대기합니다. 2. 해당 키는 get 키 대기열에 저장되므로 get()손실이 발생하지 않으면 get()아직 반환되지 않은 가장 오래된 키를 반환합니다 . 3. 모든 이벤트는 해당 키를 입력으로 사용하여 시작됩니다. 4. 해당 키는 getAsync()키 목록에 저장되며 다음 번 호출시 lis twill이 반환되고 빈 목록으로 설정됩니다.getAsync()

이 모든 것이 너무 많으면 다음 사용 사례가 있습니다.

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"

그것은 내가 한 간단한 테스트에서 나에게 잘 작동하지만, 내가 놓친 것이 있으면 행복하게도 다른 사람들의 피드백을받을 것입니다.

나는 이것을 여기에 게시했다 .


3

다른 답변 중 하나의 주석은 cbreak 모드를 언급했습니다. 유닉스 구현에 중요합니다. 일반적으로 ^ C ( KeyboardError)를 getchar에 의해 소비 하지 않기 때문에 (터미널을 원시 모드로 설정 할 때와 마찬가지로) 대부분의 다른 답변).

또 다른 중요한 세부 사항은 1 바이트가 아닌 하나의 문자 를 읽으려는 경우 단일 문자가 UTF-8 (Python 3+)로 구성된 최대 바이트 수이므로 입력 스트림에서 4 바이트를 읽어야한다는 것입니다 ). 단일 바이트 만 읽으면 키패드 화살표와 같은 멀티 바이트 문자에 예기치 않은 결과가 발생합니다.

유닉스에 대한 변경된 구현은 다음과 같습니다.

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)

2

파이 게임으로 이것을 시도하십시오 :

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, "."

깔끔한 아이디어이지만, 명령 행에서는 작동하지 않습니다 : pygame.error: video system not initialized
dirkjot

2

ActiveState의 레시피에는 "posix"시스템에 대한 약간의 버그가 포함 된 것으로 보입니다 Ctrl-C(Mac을 사용하고 있습니다). 스크립트에 다음 코드를 넣으면

while(True):
    print(getch())

로 스크립트를 종료 할 수 없으며 Ctrl-C탈출하려면 터미널을 종료 해야합니다.

다음 줄이 원인이라고 생각하며 너무 잔인합니다.

tty.setraw(sys.stdin.fileno())

그 외에도 패키지 tty는 실제로 필요하지 않으며 termios처리하기에 충분합니다.

아래는 입력 할 때 char를 에코하는 Ctrl-C추가 getche기능 과 함께 나를 위해 작동하는 향상된 코드입니다 ( 중단됩니다) .

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)

참고 문헌 :


1

curses파이썬 의 패키지는 터미널에서 문자 입력을위한 "원시"모드로 들어가는 데 몇 가지 문장만으로도 사용할 수 있습니다. Curses의 주된 용도는 출력을 위해 화면을 인계하는 것입니다. 이 코드 스 니펫은 print()대신 사용할 수있는 명령문을 사용하지만 출력에 첨부 된 줄 끝을 curs가 변경하는 방법을 알고 있어야합니다.

#!/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!')

1

복잡한 작업을 수행하는 경우 curses를 사용하여 키를 읽습니다. 그러나 많은 경우 표준 라이브러리를 사용하고 화살표 키를 읽을 수있는 간단한 Python 3 스크립트를 원하므로 다음과 같이하십시오.

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

0

pip 패키지에 의존하지 않고 python3에 대한 내 솔루션.

# 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())

0

나는 이것이 가장 우아한 해결책이라고 생각합니다.

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

그런 다음 코드에서 사용하십시오.

if getch() == chr(ESC_ASCII_VALUE):
    print("ESC!")

0

받아 들인 대답이 저에게 잘 수행되지 않았습니다 (키를 누르고 아무 일도 일어나지 않으면 다른 키를 누르면 작동합니다).

curses 모듈 에 대해 배운 후에는 실제로 올바른 방법으로 보입니다. 이제 Windows 커서 (pip를 통해 사용 가능)를 통해 Windows에서 사용할 수 있으므로 플랫폼에 상관없이 프로그래밍 할 수 있습니다. 다음 은 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))

.py확장명으로 저장 하거나 curses.wrapper(getkey)대화식 모드에서 실행 하십시오.


0

여기에 대답 : Enter 키를 누르지 않고 파이썬에서 raw_input

이 코드를 사용하십시오

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()

참조 : https://github.com/unfor19/mg-tools/blob/master/mgtools/get_key_pressed.py


0

하나의 단일 키만 등록하려면 사용자가 키를 두 번 이상 누르거나 키를 길게 누른 경우에도 누르십시오. 여러 입력을받지 않으려면 while 루프를 사용하여 전달하십시오.

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)

0

화면을 잡고 싶다면 터미널에서 결과를 볼 수 있습니다.

input()

코드의 끝에 그리고 화면을 개최합니다


-1

기본 제공 raw_input이 도움이됩니다.

for i in range(3):
    print ("So much work to do!")
k = raw_input("Press any key to continue...")
print ("Ok, back to work.")

6
raw_input은 키 입력을 기다리고 있습니다
vac
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.