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

使用pynput结合线程实现键鼠监听器时遭遇NotImplementedError及TypeError问题求助

pynput结合线程实现键鼠监听器时遭遇NotImplementedError及TypeError问题求助

问题场景与代码复现

我在实现剪贴板滚动工具的键鼠监听器时,遇到了未被自定义异常捕获的内部错误,程序启动后会触发NotImplementedError,后续引发TypeError: '_thread._ThreadHandle' object is not callable,且自定义的logging.error未记录到相关错误。以下是核心代码与错误日志:

键鼠监听器核心代码

def mouse_listener(scroller):
    """Listens for mouse scroll events to scroll clipboard history."""
    logging.info("Started mouse listener.")
    def on_scroll(x, y, dx, dy):
        try:
            if dy > 0:
                scroller.scroll_clipboard_history("up")
            else:
                scroller.scroll_clipboard_history("down")
        except Exception as e:
            logging.error(f"Error in mouse_listener in on_scroll: {e}")
    try:
        with mouse.Listener(on_scroll=on_scroll) as listener:
            listener.join()
    except Exception as e:
        logging.error(f"Error in mouse_listener (join()): {e}")
    logging.info("Stopped mouse listener.")

def keyboard_listener(scroller):
    """Listens for keyboard events."""
    logging.info("Started keyboard listener.")
    def on_press(key):
        try:
            if key == keyboard.Key.f9:  # Stop the program
                stop_clipboard_scroller()
                return False  # Stops the listener when F9 is pressed
            elif key == keyboard.Key.ctrl and not stop_flag.is_set():  # Show ghost on Ctrl press
                scroller.show_ghost()
            elif key == keyboard.Key.fromchar('v') and not stop_flag.is_set():  # Check for Ctrl+V
                scroller.show_ghost()  # Show ghost on Ctrl+V press
        except Exception as e:
            logging.error(f"Error in keyboard_listener in on_press: {e}")

错误日志

INFO - Started mouse listener.
INFO - Started keyboard listener.
ERROR - Unhandled exception in listener callback
Traceback (most recent call last):
  File "...\pynput\_util\win32.py", line 386, in _handler
    converted = self._convert(code, msg, lpdata)
  File "...\pynput\_util\win32.py", line 401, in _convert
    raise NotImplementedError()
NotImplementedError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "...\pynput\_util\__init__.py", line 229, in inner
    return f(self, *args, **kwargs)
  File "...\pynput\_util\win32.py", line 390, in _handler
    self._handle(code, msg, lpdata)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
TypeError: '_thread._ThreadHandle' object is not callable
2024-12-07 03:01:12,374 - INFO - Stopped mouse listener.

问题分析

  1. 自定义异常捕获范围有限:你的try-except仅包裹了回调函数(on_scroll/on_press)的业务逻辑,但错误发生在pynput监听器的内部事件处理线程中,属于框架级别的异常,无法被你的局部捕获。
  2. 键盘监听器实现不完整:你的keyboard_listener函数仅定义了on_press回调,但缺少启动监听器的核心代码(类似鼠标监听器的with keyboard.Listener(...) as listener: listener.join()),这会导致监听器线程未正确初始化,引发资源异常。
  3. 组合键检测逻辑错误:当前代码将Ctrl和V的按下事件分开处理,无法正确识别Ctrl+V组合键,反而会在单独按V时触发操作,同时这类错误的事件处理可能触发pynput未兼容的事件转换。
  4. 线程停止逻辑存在隐患:stop_clipboard_scroller()和stop_flag的实现可能未正确同步线程资源,导致监听器在终止时出现线程句柄调用异常。

解决方案

1. 补全键盘监听器的启动代码

在keyboard_listener函数末尾添加监听器启动与阻塞逻辑,确保线程正确初始化:

def keyboard_listener(scroller):
    """Listens for keyboard events."""
    logging.info("Started keyboard listener.")
    def on_press(key):
        try:
            if key == keyboard.Key.f9:  # Stop the program
                stop_clipboard_scroller()
                return False  # Stops the listener when F9 is pressed
            # 正确检测Ctrl+V组合键
            elif hasattr(key, 'char') and key.char == 'v' and keyboard.is_pressed(keyboard.Key.ctrl) and not stop_flag.is_set():
                scroller.show_ghost()
            elif key == keyboard.Key.ctrl and not stop_flag.is_set():  # Show ghost on Ctrl press
                scroller.show_ghost()
        except Exception as e:
            logging.error(f"Error in keyboard_listener in on_press: {e}")

    # 补全监听器启动代码
    try:
        with keyboard.Listener(on_press=on_press) as listener:
            listener.join()
    except Exception as e:
        logging.error(f"Error in keyboard_listener (join()): {e}")
    logging.info("Stopped keyboard listener.")

2. 全局捕获pynput框架异常

通过添加全局异常钩子,捕获框架级别的异常并记录日志:

import sys
import traceback

def pynput_exception_hook(exctype, value, tb):
    if exctype == NotImplementedError:
        logging.warning("Pynput encountered an unhandled system event, ignoring...")
        logging.debug(traceback.format_exc())
    elif "_thread._ThreadHandle" in str(value):
        logging.error("Listener thread handle error, likely due to improper shutdown.")
        logging.debug(traceback.format_exc())
    else:
        # 其他异常按原有逻辑处理
        sys.__excepthook__(exctype, value, tb)

# 注册全局异常钩子
sys.excepthook = pynput_exception_hook

3. 修复组合键检测逻辑

使用pynput的HotKey类来正确检测Ctrl+V组合键,避免单独按键的误触发:

def keyboard_listener(scroller):
    """Listens for keyboard events."""
    logging.info("Started keyboard listener.")

    # 定义Ctrl+V热键回调
    def on_ctrl_v():
        if not stop_flag.is_set():
            scroller.show_ghost()

    # 定义Ctrl按下回调
    def on_ctrl_press():
        if not stop_flag.is_set():
            scroller.show_ghost()

    # 注册热键和普通按键监听
    hotkey = keyboard.HotKey(keyboard.HotKey.parse('<ctrl>+v'), on_ctrl_v)
    def on_press(key):
        try:
            if key == keyboard.Key.f9:
                stop_clipboard_scroller()
                return False
            elif key == keyboard.Key.ctrl:
                on_ctrl_press()
            # 更新热键状态
            hotkey.press(key)
        except Exception as e:
            logging.error(f"Error in keyboard_listener in on_press: {e}")

    def on_release(key):
        try:
            hotkey.release(key)
        except Exception as e:
            logging.error(f"Error in keyboard_listener in on_release: {e}")

    try:
        with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
            listener.join()
    except Exception as e:
        logging.error(f"Error in keyboard_listener (join()): {e}")
    logging.info("Stopped keyboard listener.")

4. 优化线程停止逻辑

确保stop_clipboard_scroller()正确设置stop_flag并终止所有监听器线程,避免资源泄漏:

def stop_clipboard_scroller():
    logging.info("Stopping clipboard scroller...")
    stop_flag.set()
    # 主动终止所有pynput监听器
    global mouse_listener, keyboard_listener
    if 'mouse_listener' in globals() and hasattr(mouse_listener, 'stop'):
        mouse_listener.stop()
    if 'keyboard_listener' in globals() and hasattr(keyboard_listener, 'stop'):
        keyboard_listener.stop()
    logging.info("Clipboard scroller stopped.")

5. 升级pynput版本

部分旧版本的pynput在Windows环境下存在事件转换的bug,升级到最新稳定版可以解决部分NotImplementedError:

pip install --upgrade pynput

备注:内容来源于stack exchange,提问作者E-Gamma-102

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 16:39:33