"Exception in thread" 在 Flask 应用程序中更新键值对时出错

"Exception in thread" error when updating key-value pair in a Flask app

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

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

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

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

我还有另一个变量,用于跟踪当前 room/channel 用户正在 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
});

下一次,post 完整的回溯以及所有必要的文件,因为这将使调试代码变得更加容易。你说你验证了 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

这应该提醒您,您主要关心的是 KeyError,这是因为您将 None 传递给 channels 字典,这意味着 currentRoom 必须是 none.

为什么会这样?问题出在第 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 rule 之后,这意味着在解析变量名时,解释器将首先检查 L 局部作用域,然后是 Enclosing 作用域,然后是 Global 作用域,最后是 Built-in 作用域。但是,当您 分配 特定范围内的变量时, 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,而且函数会成功,没有任何错误。