You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用pynput开发TUI时VSCode捕获按键写入代码的问题

自定义TUI按键输入异常问题:VSCode捕获终端按键写入Python文件

我正在开发一个基于pynput的自定义TUI网格游戏,核心功能是通过方向键控制角色移动,但遇到严重异常:运行代码时,终端输入的按键(包括系统终端的按键)会被VSCode捕获并直接写入我的Python源码文件。怀疑是键盘监听器未正确关闭,但因首次使用pynput且VSCode拥有系统级权限,无法确定问题根源。

相关代码

import math
from pynput import keyboard
from IPython.display import clear_output

open_field = " _ "
wall = " X "

'''
The function, "coordinate_field" creates a grid like the one below, and also prints the location and movement of characters within the grid as the code runs. In future iterations, I intend to make the walls impassable, add momentum and battle functionality.

 X  X  X  X  X  X  X  X  X  X  X  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  _  _  _  _  _  _  _  _  _  _  X 
 X  X  X  X  X  X  X  X  X  X  X  X 
'''
def coordinate_field (p1_location, p2_location, p1_direction, p2_direction):
    x_coordinate = 0
    y_coordinate = 1
    field = ""
    ''' 
    The grid is 12*12.
    156 is 12*12+12 because adding each line break takes an
    iteration as well.
    '''
    for i in range(156):
        x_coordinate += 1
        coordinate_point = [x_coordinate, y_coordinate]
        if x_coordinate > 12:
            field += "\n"
            x_coordinate -= 13
            y_coordinate += 1
        elif y_coordinate in [1,12] or x_coordinate in [1,12]:
            field += wall
        # The following elifs add the characters.
        elif coordinate_point == p1_location:
            field += p1_direction
        elif coordinate_point == p2_location:
            field += p2_direction
        else:
            field += open_field
    clear_output(wait=True)
    print(field)

p1_location = [4, 7]

'''
This following list will be joined before going in to the function. I think that it needs to be a list at this stage
because p1_direction += "<" and such didn't seem to work in this context.
'''
p1_direction = ["-","-","-"]

'''
on_press is probably the source of the issue, unless the 
keyboard.Listener is. I don't really understand either, and got 
both from ChatGPT.
'''
def on_press(key):
    try:
        if key == keyboard.Key.enter:
            return False  # Stop listener
        elif key == keyboard.Key.left:
            p1_direction.append("<")
        elif key == keyboard.Key.right:
            p1_direction.append(">")
        elif key == keyboard.Key.up:
            p1_direction.append("^")
        elif key == keyboard.Key.down:
            p1_direction.append("v")
    except AttributeError:
        pass

def movement (p1_direction):
    print ("Type three moves: ")

    with keyboard.Listener(on_press=on_press) as listener:
        listener.join()
    
    # This loop guarantees that only the last three directional
    # inputs are counted.
    while len(p1_direction) > 3:
        p1_direction.remove(p1_direction[0])
    
    # This translates the inputs into motion.
    for i in range(len(p1_direction)):
        if p1_direction[i] == "^":
            p1_location[1] -= 1
        if p1_direction[i] == "v":
            p1_location[1] += 1
        if p1_direction[i] == "<":
            p1_location[0] -= 1
        if p1_direction[i] == ">":
            p1_location[0] += 1
    
    # Finally, the list is joined, used as an argument in the 
    # coordinate_field function, and separated again.
    p1_direction = "".join(p1_direction)
    coordinate_field (p1_location, [7,5], p1_direction, "^^<")
    p1_direction = list(p1_direction)
    return p1_direction

# All this code will someday be part of a larger game, but for
# now, I run it endlessly to test it.
while True:
    p1_direction = movement(p1_direction)

解决方案

  • 替换全局监听器为局部终端监听
    pynput的keyboard.Listener默认是全局键盘监听,会捕获整个系统的按键事件,这是导致VSCode拦截输入的核心原因。改用pynput.keyboard.Events做局部监听,仅捕获当前终端的按键:

    from pynput.keyboard import Events, Key
    
    def movement(p1_direction):
        print("Type three moves: ")
        moves = []
        with Events() as events:
            while len(moves) < 3:
                event = events.get()
                if isinstance(event, Events.Press):
                    if event.key == Key.left:
                        moves.append("<")
                    elif event.key == Key.right:
                        moves.append(">")
                    elif event.key == Key.up:
                        moves.append("^")
                    elif event.key == Key.down:
                        moves.append("v")
                    elif event.key == Key.enter:
                        break
        # 保留最近3次输入,不足补全默认值
        p1_direction = moves[-3:] if len(moves)>=3 else moves + ["-"]*(3-len(moves))
        
        # 移动逻辑不变
        for i in range(len(p1_direction)):
            if p1_direction[i] == "^":
                p1_location[1] -= 1
            if p1_direction[i] == "v":
                p1_location[1] += 1
            if p1_direction[i] == "<":
                p1_location[0] -= 1
            if p1_direction[i] == ">":
                p1_location[0] += 1
        
        p1_direction_str = "".join(p1_direction)
        coordinate_field(p1_location, [7,5], p1_direction_str, "^^<")
        return list(p1_direction_str)
    
  • 确保监听器彻底终止
    原代码中虽通过return False终止监听器,但循环调用可能导致残留。在with块结束后显式调用stop():

    with keyboard.Listener(on_press=on_press) as listener:
        listener.join()
        listener.stop()  # 强制终止监听器
    
  • 调整VSCode终端设置
    关闭VSCode的Terminal: Allow Chained Commands选项,或改用系统外部终端运行代码,避免VSCode终端拦截全局按键事件。

  • 消除全局变量依赖
    原代码中on_press直接修改全局p1_direction,易引发状态混乱。改用类封装角色状态,或通过回调传递参数,减少全局变量使用。

内容的提问来源于stack exchange,提问作者Porter Williams

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 12:05:04