首页 > 解决方案 > 在 Flask 应用程序中更新键值对时出现“线程异常”错误

问题描述

我正在构建一个基本的聊天程序(来自 CS50s 网络编程的 Flack)。

我有一本字典,我将频道和消息存储为键值对。

消息在一个列表中,所以一个键值对看起来像:

{"channelExample" : ["msg1", "msg2"]}.

我还有另一个变量来跟踪用户正在发送消息的当前房间/频道,称为currentRoom

当用户提交消息时,我正在尝试通过执行以下操作来更新该通道中的消息(已经导入了emit并且我已经确认currentRoom和输入消息是字符串值):

@socketio.on("submit message")
def submitMessage(message):
    channels[currentRoom].append(message)
    emit("display message", message)

但是我被抛出一个“线程中的异常...... ”错误,channels[currentRoom].append(message)我不知道为什么。

我在 Flask 中的完整代码:

import os

from flask import Flask, session, render_template, url_for, request, flash, redirect, jsonify
from flask_socketio import SocketIO, send, emit, join_room, leave_room


app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
socketio = SocketIO(app)

currentRoom = None
channels = {}

@app.route("/")
def index():
    return render_template("welcome.html", channels=channels)

@socketio.on("new channel")
def newChannel(channelName):
    # Store new channel to keep track of it
    channels.update( {channelName : []} )

@socketio.on("retrieve channels") 
def retrieveChannels():
    channelNames = []

    for channel in channels:
        channelNames.append(channel)

        emit("providing channels", channelNames)

@socketio.on("retrieve messages")    
def loadMessages(channelName):
    currentRoom = channelName

    channelMessages = channels[currentRoom]

    emit("load messages", channelMessages)

@socketio.on("submit message")
def submitMessage(message):
    channels[currentRoom].append(message)
    emit("display message", message)

Javascript:

document.addEventListener('DOMContentLoaded', () => {

    // Connect to websocket
    var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);

    // When connected, 
    socket.on('connect', () => {

        var nameInput = document.querySelector("#usernameInput");
        var welcomeMessage = document.querySelector("#welcomeMessage");
        var createChannel = document.querySelector("#createChannel");
        var newChannelForm = document.querySelector("#newChannelForm");
        var newMessageForm = document.querySelector("#newMessageForm");

        function userExists() {
            // Check if user has come here before
            if (localStorage.getItem("username")) {
                // Display a welcome message
                welcomeMessage.innerHTML = `Welcome back ${localStorage.getItem("username")}!`;
                nameInput.style.display = "none";
                return true;
            }
            else {
                return false;
            }
        };

        function createChannelBtn(name) {
            // Create new channel & style it
            let newChannel = document.createElement("button");
            newChannel.id = name;
            newChannel.innerHTML = name;
            newChannel.className = "btn btn-block btn-outline-dark";
            newChannel.style.display = "block";

            // Attach to current list 
            document.querySelector("#channels").appendChild(newChannel);

            // When someone clicks the channel
            newChannel.onclick = () => {

                newChannel.classList.toggle("active");

                socket.emit("retrieve messages", newChannel.id);
                console.log("Retrieving messages!!!");

                socket.on("load messages", channelMessages => {
                    console.log("loading messages!");
                    for (let i = 0; i < channelMessages.length; i++) {
                        createMessage(channelMessages[i]);
                    }
                });

            };
        };

        function createMessage(messageContent) {
            let message = document.createElement("h6");
            message.innerHTML = messageContent;
            document.querySelector("#messageWindow").appendChild(message);
            console.log("Currently creating message!");
        };

        function loadChannels() {
            socket.emit("retrieve channels")

            socket.on("providing channels", channelNames => {
                for (let i = 0; i < channelNames.length; i++) {
                    createChannelBtn(channelNames[i]);
                }
            });
        };

        // Make sure the new channel form is not displayed until "Create channel" button is clicked
        newChannelForm.style.display = "none";

        // Check if user exists already in local storage
        userExists();

        loadChannels();

        // If someone submits a username...
        nameInput.addEventListener("click", () => {
            // if that username exists, do nothing
            if (userExists()) {
            }
            // else remember the username
            else {
                localStorage.setItem("username", document.querySelector("#user").value);
            }
        });

        // When someone wants to create a channel
        createChannel.addEventListener("click", () => {
            // Show form
            newChannelForm.style.display = "block";

            // When user inputs new channel name...
            newChannelForm.onsubmit = () => {

                // Retrieve their input
                var newChannelName = document.querySelector("#newChannel").value;

                // Create a new channel
                createChannelBtn(newChannelName);

                // Notify server to store new channel 
                socket.emit("new channel", newChannelName);

                // Clear input field
                document.querySelector("#newChannel").innerHTML = "";

                return false;

            };
        });

        newMessageForm.onsubmit = () => {
            let message = document.querySelector("#newMessage").value;
            console.log("You have entered " + message);

            socket.emit("submit message", message);
            console.log("Submitted message!");

            socket.on("display message", message => {
                createMessage(message);
                console.log("Displaying message!!");
            });

            return false;
        };

    });

    // DOM Ending Bracket
});

标签: javascriptpythonflasksocket.ioflask-socketio

解决方案


下一次,发布完整的回溯,以及所有必要的文件,因为这将使您的代码更容易调试。您说您验证了这currentRoom是一个字符串值,但可能仅在从浏览器接收到它时。您没有在您的submitMessage()函数中检查它,然后您尝试使用 访问您的频道列表None,如回溯所示,看起来像这样:

Exception in thread Thread-13:
Traceback (most recent call last):
  File "C:\Users\Michael\AppData\Local\Programs\Python\Python37-32\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "C:\Users\Michael\AppData\Local\Programs\Python\Python37-32\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "c:\Users\Michael\Desktop\sandbox\sandbox\lib\site-packages\socketio\server.py", line 640, in _handle_event_internal
    r = server._trigger_event(data[0], namespace, sid, *data[1:])
  File "c:\Users\Michael\Desktop\sandbox\sandbox\lib\site-packages\socketio\server.py", line 669, in _trigger_event
    return self.handlers[namespace][event](*args)
  File "c:\Users\Michael\Desktop\sandbox\sandbox\lib\site-packages\flask_socketio\__init__.py", line 280, in _handler
    *args)
  File "c:\Users\Michael\Desktop\sandbox\sandbox\lib\site-packages\flask_socketio\__init__.py", line 694, in _handle_event
    ret = handler(*args)
  File "c:\Users\Michael\Desktop\sandbox\sandbox.py", line 47, in submitMessage
    channels[currentRoom].append(message)
KeyError: None

这应该提醒您,您主要关心的是 a KeyError,这是因为您正在传递None给您的channelsdict ,这意味着它currentRoom一定不是。

那么为什么会这样呢?问题在于第 34 行:

32  @socketio.on("retrieve messages")    
33  def loadMessages(channelName):
34      currentRoom = channelName  # <--- Issue here
35  
36      channelMessages = channels[currentRoom]
37  
38      emit("load messages", channelMessages)

Python 变量遵循LEGB 规则,这意味着在解析变量名时,解释器将首先检查L本地范围,然后是E封闭范围,然后是全局范围,最后是内置范围。但是,当您在某个范围内分配变量时,Python 将在该范围内创建该变量。

因此,您现在已经创建了一个名为 的新变量currentRoom,它是一个独立于顶部定义的变量currentRoom,并且仅在loadMessages()函数中可见!那么,当你在一个函数中赋值时,你如何让 Python 知道你指的是那个全局变量呢?

您使用global关键字:

@socketio.on("retrieve messages")    
def loadMessages(channelName):
    global currentRoom  # <--- This tells Python that `currentRoom` is a global variable
    currentRoom = channelName  # Assign to the global variable

    channelMessages = channels[currentRoom]

    emit("load messages", channelMessages)

现在,当我们调用 时submitMessage()currentRoom现在有一个实际的文本值,而不仅仅是None,并且函数成功且没有任何错误。


推荐阅读