Python อ่านอักขระหนึ่งตัวจากผู้ใช้


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 บางประเภท ทำไมไม่เรียก 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)

โดยทั่วไปจะอ่าน 1 ไบต์จาก STDIN

หากคุณต้องใช้วิธีการที่ไม่รอ\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) รอ a \ n, lol ขอบคุณสำหรับการส่งแม้ว่า
Evan Fosmark

3
อักขระหนึ่งตัวหรือหนึ่งไบต์? นั่นไม่เหมือนกัน
chryss

4
@Evan ว่าเป็นเพราะงูหลามอยู่ในสายโหมดบัฟเฟอร์โดยค่าเริ่มต้น
จอห์นลา Rooy

3
@EvanFosmark: ไม่จำเป็นว่า sys.stdin.read (1) รอ \ n นั่นคือโปรแกรมเทอร์มินัลที่ตัดสินใจเมื่อส่งอักขระอื่น ๆ ไปยังโปรแกรมของคุณจะไม่เขียนจนกว่าจะเห็น '\ n' คุณสามารถกด backspace และแก้ไขสิ่งที่คุณกำลังพิมพ์ได้หรือไม่ (คำตอบที่จริงจังคือสอนโปรแกรมไพ ธ อนให้ใช้การควบคุมสายเก็บบัฟเฟอร์ประมวลผลแบ็คสเปซ แต่นั่นเป็นโลกที่แตกต่างที่คุณอาจไม่ต้องการซื้อเมื่อเพียงแค่ "อ่านตัวอักษร" และอาจทำให้สายของคุณ การจัดการแตกต่างจากโปรแกรมอื่น ๆ ทั้งหมดในระบบของคุณ)
Tony Delroy

2
@Seismoid EAFP
vaultah

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

ทดสอบบน Windows และ Linux ด้วย Python 2.7

บน Windows, ปุ่มเดียวซึ่งแมปไปยังตัวอักษรหรือรหัสควบคุม ASCII ได้รับการสนับสนุน ( Backspace, Enter, Esc, Tab, Ctrl+ ตัวอักษร ) บน GNU / Linux (ขึ้นอยู่กับขั้วแน่นอนบางที?) คุณยังได้รับInsert, Delete, Pg Up, Pg Dn, Home, Endและคีย์ ... แต่แล้วก็มีปัญหาแยกปุ่มพิเศษเหล่านี้จากF nEsc

ข้อแม้: เช่นเดียวกับส่วนใหญ่ (? ทั้งหมด) คำตอบในที่นี่ปุ่มสัญญาณเช่นCtrl+ C, Ctrl+ DและCtrl+ Zจะถูกจับและกลับ (ตาม'\x03', '\x04'และ'\x1a'ตามลำดับ); โปรแกรมของคุณอาจเป็นเรื่องยากที่จะยกเลิก


3
ทำงานร่วมกับ Python 3 บน Linux ได้เช่นกัน ดีกว่า getch มากเพราะ readchar อนุญาตให้พิมพ์ stdout ขณะรอคีย์ (ผ่านเธรดหรือ asyncio)
wrobell

ทดสอบกับ Win10 + Python 3.5: ข้อผิดพลาด: root: 'ใน <string>' ต้องใช้สตริงเป็นตัวถูกดำเนินการด้านซ้ายไม่ใช่ไบต์ Traceback (การโทรล่าสุดครั้งล่าสุด): ไฟล์ ".. \ main.py", บรรทัด 184 ในผลลัพธ์ของ wrapper = func (* args, ** kwargs) ไฟล์ "C: \ GitHub \ Python-Demo \ demo \ day_hello.py", บรรทัด 41, ใน readch_eg พิมพ์ (readchar.readchar ()) ไฟล์ "C: \ Users \ ipcjs \ AppData \ Local \ Programs \ Python \ Python35 \ lib \ site-packages \ readchar \ readchar_windows.py ", บรรทัดที่ 14, ใน readchar ในขณะที่ ch ใน '\ x00 \ xe0': TypeError: 'ใน <string>' ต้องการสตริงเป็นตัวถูกดำเนินการด้านซ้าย ไม่ใช่ bytes
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

จากโพสต์บล็อกนี้


ดูเหมือนจะไม่ทำงานสำหรับฉัน - ส่งคืนสตริงว่างทันทีที่โทร บน Linux ที่มี Python 3.6
ริน

1
@Marein หากคุณต้องการที่จะปิดกั้น (รอสำหรับการป้อนข้อมูล) | os.O_NONBLOCKลบ มิฉะนั้นคุณสามารถใส่มันไว้ในลูป (แนะนำให้หลับสักนิดหน่อยเพื่อไม่ให้เกิดการหมุน)
Chris Gregg

ในหลามมันจะดีกว่าที่จะใช้แล้วwhile True while 1
ไม่ระบุชื่อ

10

รหัสนี้ซึ่งตั้งอยู่ที่นี่จะเพิ่ม KeyboardInterrupt และ EOFError อย่างถูกต้องหากกดCtrl+ CหรือCtrl+D

ควรทำงานบน 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. อ่านเพียงหนึ่งไบต์ (เช่นพวกเขาไม่ต้องรอขึ้นบรรทัดใหม่)
  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 หากคุณวางแผนที่จะอ่านอักขระหลายครั้งรหัสนั้นจะหลีกเลี่ยงค่าใช้จ่าย (เล็กน้อย) ของการทำซ้ำการนำเข้า Windows และการจัดการข้อยกเว้น ImportError บนระบบที่คล้าย Unix แม้ว่าคุณอาจจะกังวลเกี่ยวกับความสามารถในการอ่านโค้ดมากกว่าการปรับให้เหมาะสมเล็กน้อย แต่นี่เป็นอีกทางเลือกหนึ่ง (คล้ายกับคำตอบของ Louis แต่ getChar () มีอยู่ในตัวเอง) ซึ่งทำหน้าที่เหมือนกับรหัส ActiveState และสามารถอ่านได้มากขึ้น:

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 เป็นการไม่บล็อก (นั่นหมายความว่าคุณสามารถมีลูปสักครู่แล้วตรวจหาการกดปุ่มโดยไม่ต้องหยุด) และข้ามแพลตฟอร์ม

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

หรือคุณอาจจะใช้โมดูล getch จาก PyPi แต่สิ่งนี้จะบล็อกขณะที่ลูป


3

นี่ไม่ใช่การปิดกั้นอ่านคีย์และเก็บไว้ใน 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

ใน programm ของคุณ

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 manager กำลังทำงาน
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()ด้านหลังget(lossy=True)เป็นเช่นget()นั้นยกเว้นว่าจะส่งคืนคีย์ที่กดหลังจากโทรไปget()เท่านั้น ดังนั้นในตัวอย่างข้างต้นget()จะบล็อกจนกว่าผู้ใช้จะกด 'c' และถ้าคุณเรียกมันอีกครั้งมันจะบล็อกจนกว่าจะกดปุ่มอื่น

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()ยัง 3. เหตุการณ์ทั้งหมดจะถูกไล่ออกโดยใช้คีย์นั้นเป็นอินพุต 4. คีย์นั้นจะถูกเก็บไว้ในรายการของgetAsync()คีย์ซึ่งจะส่งคืนสิ่งทอลายทแยงนั้นและตั้งค่าเป็นรายการว่างในการโทรครั้งต่อไป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 ซึ่งเป็นสิ่งสำคัญสำหรับการใช้งาน Unix เพราะโดยทั่วไปคุณไม่ต้องการให้ ^ C ( KeyboardError) ถูกใช้โดย getchar คำตอบอื่น ๆ ส่วนใหญ่)

รายละเอียดที่สำคัญอีกอย่างคือหากคุณต้องการอ่านอักขระหนึ่งตัวและไม่ใช่หนึ่งไบต์คุณควรอ่าน 4 ไบต์จากอินพุตสตรีมเนื่องจากจำนวนไบต์สูงสุดที่อักขระหนึ่งตัวจะประกอบด้วยใน UTF-8 (Python 3+ ) การอ่านเพียงไบต์เดียวจะให้ผลลัพธ์ที่ไม่คาดคิดสำหรับอักขระหลายไบต์เช่นปุ่มลูกศร

นี่คือการดำเนินการเปลี่ยนแปลงของฉันสำหรับ 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)

2

ลองด้วย 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, "."

นั่นเป็นความคิดที่เนี้ยบ แต่มันไม่ทำงานในบรรทัดคำสั่ง: pygame.error: video system not initialized
dirkjot

2

สูตรของ ActiveState ดูเหมือนจะมีข้อบกพร่องเล็กน้อยสำหรับระบบ "posix" ที่ป้องกันไม่ให้Ctrl-Cขัดจังหวะ (ฉันใช้ Mac) ถ้าฉันใส่รหัสต่อไปนี้ในสคริปต์ของฉัน:

while(True):
    print(getch())

ฉันจะไม่สามารถยุติสคริปต์ด้วยCtrl-Cและฉันต้องฆ่า terminal ของฉันเพื่อหนี

ฉันเชื่อว่าบรรทัดต่อไปนี้เป็นสาเหตุและมันก็โหดร้ายเกินไป:

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

นอกเหนือจากนั้นแพคเกจttyไม่จำเป็นจริง ๆtermiosก็เพียงพอที่จะจัดการมัน

ด้านล่างเป็นรหัสที่ได้รับการปรับปรุงซึ่งใช้งานได้สำหรับฉัน ( 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()คำสั่งแทนซึ่งสามารถใช้งานได้ แต่คุณต้องระวังว่า curses เปลี่ยนการสิ้นสุดบรรทัดที่แนบกับเอาต์พุต

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

ถ้าฉันทำอะไรที่ซับซ้อนฉันจะใช้คำสาปเพื่ออ่านคีย์ แต่หลายครั้งฉันแค่ต้องการสคริปต์ 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

โซลูชันของฉันสำหรับ python3 ไม่ได้ขึ้นอยู่กับแพ็คเกจ pip ใด ๆ

# 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

คำตอบที่ยอมรับไม่ได้ผลดีสำหรับฉัน (ฉันจะถือกุญแจไม่มีอะไรจะเกิดขึ้นแล้วฉันจะกดปุ่มอื่นและมันจะทำงาน)

หลังจากเรียนรู้เกี่ยวกับโมดูลคำสาปมันดูเหมือนจะเป็นวิธีที่ถูกต้อง และตอนนี้ใช้ได้สำหรับ Windows ผ่านwindows-เคอร์เซอร์ (มีให้ผ่าน pip) ดังนั้นคุณสามารถตั้งโปรแกรมในลักษณะที่ไม่เชื่อเรื่องพระเจ้าบนแพลตฟอร์มได้ นี่คือตัวอย่างที่ได้รับแรงบันดาลใจจากบทช่วยสอนที่ดีบน 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

ตอบที่นี่: raw_input ใน python โดยไม่ต้องกด Enter

ใช้รหัสนี้ -

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

หากคุณต้องการลงทะเบียนกดปุ่มเพียงครั้งเดียวเพียงครั้งเดียวแม้ว่าผู้ใช้จะกดปุ่มมากกว่าหนึ่งครั้งหรือกดปุ่มค้างนานกว่า เพื่อหลีกเลี่ยงการรับอินพุตที่กดหลายครั้งให้ใช้ขณะลูป

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

หากคุณเพียงแค่ต้องการที่จะถือหน้าจอเพื่อให้คุณสามารถเห็นผลใน terminal เพียงแค่เขียน

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.