在Python中查找箭头键的值:为什么它们是三元组?

41

我正在尝试找到本地系统在Python中分配给箭头键的值,我正在使用以下脚本进行此操作:

import sys,tty,termios
class _Getch:       
    def __call__(self):
            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

def get():
    inkey = _Getch()
    while(1):
            k=inkey()
            if k!='':break
    print 'you pressed', ord(k)

def main():
    for i in range(0,25):
        get()

if __name__=='__main__':
    main()

然后我运行了这个脚本,按了上下左右箭头,得到了以下输出:

$ python getchar.py 
you pressed 27
you pressed 91
you pressed 65
you pressed 27
you pressed 91
you pressed 66
you pressed 27
you pressed 91
you pressed 67
you pressed 27
you pressed 91
you pressed 68

这是异常的,因为它表明箭头键在我的系统上被注册为某种三元组(27-91-6x),因为每次按下箭头键都会占用三个get()实例。相比之下,按a、b、c和CTRL-C键则会产生:

you pressed 97
you pressed 98
you pressed 99
you pressed 3

有人可以解释一下为什么我的箭头键的值似乎被存储为三元组吗?为什么会这样?这在所有平台上都是一样的吗?(我正在使用Debian Linux。)如果不是,那么我应该如何存储箭头键的值呢?

最终目标是编写一个程序,需要正确识别箭头键并根据按下的箭头键执行相应的操作。


我建议使用ANSI转义码,尽管它没有明确说明从键盘发送的转义码部分;但从程序发送到终端的格式与键盘发送的格式相同。 - Dan D.
这似乎与此相关:https://dev59.com/o2855IYBdhLWcg3w0H13 - Newb
为什么你有一个带有__call__方法的_Getch类,而不是只有一个getch函数? - user2357112
@user2357112是因为我需要调用它的实例吗?它不能作为一个函数工作。 - Newb
2
它作为一个函数完美地工作。只需将 __call__ 方法转换为顶级 getch 函数,移除 self 参数,并调用 getch() 而不是创建 inkey 实例并调用 inkey()。即使您需要将其作为对象传递,也可以直接使用 getch 函数进行操作。 - user2357112
显示剩余4条评论
3个回答

49

我想我弄清楚了。

我从这里学到,每个箭头键都由唯一的ANSI转义码表示。然后我了解到,ANSI转义码因系统和应用程序而异:在我的终端中,按下cat并按上箭头会得到^[[A,在C语言中似乎是\033[A等等。后面的[A保持不变,但前面的Escape代码可以是十六进制(以x开头),八进制(以0开头)或十进制(没有前导数字)。

然后我打开了Python控制台,并插入之前收到的三元组,试图找到它们的字符值。结果发现,chr(27)给出了\x1bchr(91)给出了[,对65,66,67,68调用chr分别返回A,B,C,D。然后就清楚了:\x1b是转义码!

然后我注意到,一个箭头键在ANSI中表示为一个三元组,当然也由三个字符表示,所以我需要修改代码以每次读取三个字符。以下是结果:

import sys,tty,termios
class _Getch:
    def __call__(self):
            fd = sys.stdin.fileno()
            old_settings = termios.tcgetattr(fd)
            try:
                tty.setraw(sys.stdin.fileno())
                ch = sys.stdin.read(3)
            finally:
                termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
            return ch

def get():
        inkey = _Getch()
        while(1):
                k=inkey()
                if k!='':break
        if k=='\x1b[A':
                print "up"
        elif k=='\x1b[B':
                print "down"
        elif k=='\x1b[C':
                print "right"
        elif k=='\x1b[D':
                print "left"
        else:
                print "not an arrow key!"

def main():
        for i in range(0,20):
                get()

if __name__=='__main__':
        main()

使用循环在主函数中是个好主意,这样你可以传递多个ASCII值(转义和后续字符)到getch()函数。不过,如果你将for循环放在_Getch函数中会更有效率。 - Jacqlyn
这个能否扩展以便被Ctrl+C(和其他信号)中断? - ThorSummoner
@newb 我完全赞同易用性,你会建议什么更加简单? - ThorSummoner
1
@ThorSummoner 在 raw 模式下,ctrl c 的控制字符被视为字符输入。为了保持 ctrl+c 的“默认”行为,需要设置 cbreak 模式(tty.setcbreak(...))。在我的经验中,ctrl+c 是有效的,但我不能确定其他控制序列是否有效。 - h7r
1
所以...这里有一个跟进问题:您能区分产生单个\x27的ESC _key_和期望进一步字符的转义序列 _code_(也是\x27)之间的区别吗? - user4805123
显示剩余2条评论

8
我正在使用Mac,我使用了以下代码,它运行良好: 我的箭头键值为0、1、2、3(上、下、左、右): 记住27代表ESC键也是很好的。 最好的祝福!
while True:
    key = cv2.waitKey(1) & 0xFF

    # if the 'ESC' key is pressed, Quit
    if key == 27:
        quit()
    if key == 0:
        print "up"
    elif key == 1:
        print "down"
    elif key == 2:
        print "left"
    elif key == 3:
        print "right"
    # 255 is what the console returns when there is no key press...
    elif key != 255:
        print(key)

2
这对于openCV脚本很好,但是没有cv2窗口,key可能会得到一个无限的255 - Amarth Gûl
在Ubuntu上,箭头键对应的数值是 向上箭头=82,向下箭头=84,向左箭头=81,向右箭头=83。相信这也适用于其他操作系统。 - Nav

1

这是解决方案的一个小变种,它可以同时读取普通字符和箭头键。

import sys
import select
import tty
import termios

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

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

 
    while 1:
        if isData():
            k = sys.stdin.read(1)
            if k == 'q':  
                break
            elif k=='\x1b':
                kk = sys.stdin.read(2)
                if kk == 'OA':
                    print("up")
                elif kk=='OB':
                    print("down")
                elif kk=='OC':
                    print("right")
                elif kk=='OD':
                    print("left")
                else:
                    print("-->", kk.encode('unicode_escape'))
            else:
                print("char pressed: ", k)
                    
finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

用户发现仅包含代码的答案难以理解。请添加一些描述,说明它是如何解决问题的,或在源代码中适当的位置添加注释。 - Azhar Khan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接