【问题标题】:Python: Dictionary entries dissapear inside classPython:字典条目在类中消失
【发布时间】:2022-12-11 01:24:08
【问题描述】:

我在课堂上遇到了关于字典的挑战。我确定我正在监督某事,但我不确定具体是什么。情况如下:

我有一个名为NetMessageHandler 的类,它具有函数onmessage()rcvtcpmsg()。 函数onmessage() 允许其他类挂接到使用回调接收的特定网络消息,rcvtcpmsg() 由 TCP 客户端调用以将接收到的原始消息处理为 JSON 并通过系统传递。最后,_run_callbacks() 在收到消息时被调用。

当我调用onmessage() 时,回调存储在类中的字典中。当我在添加内容后打印()字典时,结果符合预期,例如:

{'systemdescription': [<function system_description_handler at 0x7f70a57ee0>]}
Length: 1

但是,当我真的想使用存储的回调进行回调时,字典突然为空,函数失败。我不知道这是怎么发生的,因为我没有清除/设置字典为新值。似乎在 onmessage() 函数完成后,字典被清空了。

{}
Length: 0

到目前为止,我的课程代码如下(仅相关部分):

class NetMessageHandler():
    def __init__(self):
        # Create an empty dictionary to store 
        self._callbacks = {}

    def _run_callbacks(self, type: str, data: dict[str, Any], origin: NetMessageOrigin):
        '''Runs when a message is received'''
        print(f'{self.__class__.__name__}: Running callbacks for {type}')
        print(self._callbacks)  # Added as a test, it returns an empty dictionary: '{}'
        
        # This part never runs as the dictionary is empty
        if type in self._callbacks:
            for c in self._callbacks[type]:
                c(type, data, origin)
   
    def rcvtcpmsg(self, msg: str, origin: 'TCPClient') -> None:
        '''Receive a TCP message and parse it to valid JSON, then run callbacks.'''
        data = self._parseMessage(msg)

        # Create an origin that represents the message sender
        origin = NetMessageOrigin(NetMessageOriginType.TCP, origin)

        # Run callbacks for the specific message type
        if "t" in data:
            self._run_callbacks(data["t"], data, origin)

    def onmessage(self, type:str, callback:Callable[[str, dict[str, Any], NetMessageOrigin], Any]):
        '''Adds a new callback for a specific message type.'''
        # Check if a callback for this message already exists
        if type not in self._callbacks:
            print(f'{self.__class__.__name__}: New callback array created for "{type}"')
            self._callbacks[type] = []

        if callback not in self._callbacks[type]:
            self._callbacks[type].append(callback)
            print(f'{self.__class__.__name__}: Callback added for message type "{type}"')
        else:
            print(f'{self.__class__.__name__}: Callback already existed for message type "{type}"')
        
        # The prints below output the expected value: {'systemdescription': [<function system_description_handler at 0x7f70a57ee0>]}
        print(self._callbacks)
        print("Length:", len(self._callbacks))

我检查了一切的顺序,回调是在第一条消息到达之前创建的,这里会发生什么?

【问题讨论】:

  • 我看不出有什么可疑之处。您应该检查是否触摸了显示代码“_callbacks”之外的任何地方。
  • 我犯了一个非常简单的错误,使用了两个单独的 NetMessageHandler 实例,在其余代码中循环。在一个实例中设置了回调,在另一个实例上调用了 rcvtcpmsg()。这个问题算是解决了,感谢您的回复!
  • @Stathis91 我怀疑可能是这种情况,并且已经开始写一个关于如何验证它并避免这种情况的答案。验证部分似乎不再相关,但让我知道是否值得完成和发布。
  • 知道将来如何避免此类情况肯定会很有用,所以如果您已经在准备一些东西,我绝对认为分享会很有用,谢谢!

标签: python


【解决方案1】:

由于提供的代码没有明显的原因,这很可能是调用/使用此类的代码的问题。最可能的原因是调用代码实例化了此类的多个实例,每个实例都有自己的 self._callbacks 字典,因此添加到一个实例不会影响另一个实例。考虑到类的编写方式,避免这种情况的最简单方法(尽管我不推荐,除非出于调试目的)是简单地删除 def __init__。通过使用 _callbacks 类属性(而不是实例属性),所有实例方法将只更新那个字典(类的),而不是它们自己的:

class NetMessageHandler():
    _callbacks = {}

    def _run_callbacks(..
        ...

由于这个类似乎没有真正的理由有多个实例,更好的方法是将其方法更改为类方法。不仅当方法打印时它们引用类 - self.__class__.__name__,而且通过将方法绑定到类,回调是否注册到 NetMessageHandler().onmessage()NetMessageHandler.onmessage() 也无关紧要。变化如下:

class NetMessageHandler():
    _callbacks = {}

    @classmethod
    def _run_callbacks(cls, type: str, data: dict[str, Any], origin: NetMessageOrigin):
        '''Runs when a message is received'''
        print(f'{cls.__name__}: Running callbacks for {type}')
        print(cls._callbacks)  # Added as a test, it returns an empty dictionary: '{}'

        # This part never runs as the dictionary is empty
        if type in cls._callbacks:
            for c in cls._callbacks[type]:
                c(type, data, origin)

    @classmethod
    def rcvtcpmsg(cls, msg: str, origin: 'TCPClient') -> None:
        '''Receive a TCP message and parse it to valid JSON, then run callbacks.'''
        data = cls._parseMessage(msg)

        # Create an origin that represents the message sender
        origin = NetMessageOrigin(NetMessageOriginType.TCP, origin)

        # Run callbacks for the specific message type
        if "t" in data:
            cls._run_callbacks(data["t"], data, origin)

    @classmethod
    def onmessage(cls, type:str, callback:Callable[[str, dict[str, Any], NetMessageOrigin], Any]):
        '''Adds a new callback for a specific message type.'''
        # Check if a callback for this message already exists
        if type not in cls._callbacks:
            print(f'{cls.__name__}: New callback array created for "{type}"')
            cls._callbacks[type] = []

        if callback not in cls._callbacks[type]:
            cls._callbacks[type].append(callback)
            print(f'{cls.__name__}: Callback added for message type "{type}"')
        else:
            print(f'{cls.__name__}: Callback already existed for message type "{type}"')

        # The prints below output the expected value: {'systemdescription': [<function system_description_handler at 0x7f70a57ee0>]}
        print(cls._callbacks)
        print("Length:", len(cls._callbacks))

这不是一个真实的singleton,但在进入元类之前,它可能与 python 所能达到的一样接近。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-12-23
    • 1970-01-01
    • 2019-04-04
    • 1970-01-01
    • 1970-01-01
    • 2015-04-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多