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

Python客户端聊天程序:打字时消息输入输出混乱问题求助

解决Python聊天程序输入与消息显示混乱问题

问题根源

两个独立线程(输入线程、消息接收线程)同时操作终端输出,接收线程打印消息时会打断输入行的正常显示,导致输入内容与消息交织。

方案一:使用curses库(推荐,专业终端UI处理)

curses可以将终端划分为不同区域,分离消息显示区和输入区,从根本上避免冲突。

实现步骤

  1. 初始化curses窗口,将屏幕分为上下两部分:上方为消息显示区,底部一行作为输入区。
  2. 消息接收线程负责往消息显示区追加新消息,并刷新显示。
  3. 输入线程在输入区获取用户输入,发送完成后清空输入区,保持输入区始终在底部。

代码示例片段

import curses
import threading
import time

# 全局变量存储聊天消息
chat_messages = []

def receive_messages(stdscr):
    # 替换为实际socket接收逻辑
    while True:
        time.sleep(2)
        new_msg = "[message] New server message"
        chat_messages.append(new_msg)
        # 更新消息显示区
        stdscr.clear()
        # 消息区预留最后一行给输入
        for idx, msg in enumerate(chat_messages[-curses.LINES+2:]):
            stdscr.addstr(idx, 0, msg)
        stdscr.refresh()

def input_thread(stdscr):
    input_win = curses.newwin(1, curses.COLS, curses.LINES-1, 0)
    input_win.nodelay(False)
    while True:
        input_win.clear()
        input_win.addstr(0, 0, "> ")
        input_win.refresh()
        # 获取用户输入
        user_input = input_win.getstr().decode('utf-8')
        if user_input.strip():
            # 替换为实际socket发送逻辑
            chat_messages.append(f"-> {user_input}")
            # 刷新主窗口显示发送的消息
            stdscr.clear()
            for idx, msg in enumerate(chat_messages[-curses.LINES+2:]):
                stdscr.addstr(idx, 0, msg)
            stdscr.refresh()

def main(stdscr):
    curses.curs_set(1)
    # 启动接收线程(后台运行)
    threading.Thread(target=receive_messages, args=(stdscr,), daemon=True).start()
    input_thread(stdscr)

if __name__ == "__main__":
    curses.wrapper(main)

方案二:手动用ANSI终端控制码处理(无需额外库)

通过终端控制序列保存/恢复光标位置,在接收消息时临时清空输入行,打印消息后恢复输入状态。

实现步骤

  1. 维护全局变量存储用户正在输入的内容。
  2. 替换input()为逐字符读取(避免阻塞,支持实时输入中断),用sys.stdin.read(1)实现。
  3. 接收线程打印消息前,先保存光标位置、清空当前输入行,打印消息后恢复光标位置并重新显示正在输入的内容。

代码示例片段

import sys
import threading
import time

# 全局变量存储正在输入的内容
current_input = ""
# 线程锁确保终端操作原子性
print_lock = threading.Lock()

def receive_messages():
    global current_input
    # 替换为实际socket接收逻辑
    while True:
        time.sleep(2)
        new_msg = "[message] New server message"
        with print_lock:
            # 保存光标位置
            sys.stdout.write("\033[s")
            # 移动到行首并清空当前行
            sys.stdout.write("\033[G\033[K")
            # 打印新消息
            print(new_msg)
            # 恢复光标位置并重新显示输入内容
            sys.stdout.write("\033[u")
            sys.stdout.write(current_input)
            sys.stdout.flush()

def input_thread():
    global current_input
    sys.stdout.write("> ")
    sys.stdout.flush()
    while True:
        # 逐字符读取输入
        char = sys.stdin.read(1)
        if char == "\n":
            # 按下回车发送消息
            with print_lock:
                print(f"\n-> {current_input}")
                current_input = ""
                sys.stdout.write("> ")
                sys.stdout.flush()
        elif char == "\x7f":  # 处理退格键
            if current_input:
                current_input = current_input[:-1]
                with print_lock:
                    sys.stdout.write("\b \b")
                    sys.stdout.flush()
        else:
            current_input += char
            with print_lock:
                sys.stdout.write(char)
                sys.stdout.flush()

if __name__ == "__main__":
    print("Start typing...")
    threading.Thread(target=receive_messages, daemon=True).start()
    input_thread()

关键说明

  • print_lock线程锁用于避免多线程同时修改终端输出,保证操作的原子性。
  • ANSI转义码作用:
    • \033[s:保存当前光标位置
    • \033[u:恢复之前保存的光标位置
    • \033[G:移动光标到当前行首
    • \033[K:清除从光标到行尾的内容

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 23:48:22