【问题标题】:Python Decorated Function Can't Update Attribute from Another ClassPython 装饰函数无法从另一个类更新属性
【发布时间】:2021-03-22 00:15:51
【问题描述】:

在我创建的类中使用导入包中的装饰器时遇到问题。我创建了两个类和一个main()class A 的实例在main() 中创建,class B 的实例在class A 中创建。 Class B 需要更新在main() 中创建的实例的属性。此外,我需要使用导入包中的装饰器。

我不知道如何解决无法从class B 实例中的装饰confirm_connect 函数引用main() 中创建的实例的属性的问题。我让它工作的唯一方法是将main()中创建的实例声明为global并删除class B中的所有self引用。但是,将其设为 global 会导致我的应用程序出现其他问题。 (在class B 中将socketio 的实例设置为global 是可以容忍的,但我也不想拥有它。)

函数confirm_connect 接收来自服务器的消息。如果我将函数定义为def conform_connect(self, data),则会收到错误消息connect_confirm() missing 1 required positional argument: 'data'/。如果我从声明中删除self,则会收到错误NameError: name 'self' is not defined

这是我的脚本。我怎样才能让我的脚本做我需要它做的事情?

import socketio

class A():

    def __init__(self):
        self.pin = None
        
    def connect_to_server(self):
        self.io = B()
        self.io.connect_to_server()

    def start_the_process(self):
        self.b = B(self)
        self.b.connect_to_server()
        
    def do_something_with_pin(self):
        print(self.pin)

class B():
    
    global sio
    sio = socketio.Client()
    
    def __init__(self, a):
        self.a = a
        
    def connect_to_server(self):    
        sio.connect('http://my_url_is_this.org')    
        sio.emit('manual_connection_parameter', {'foo': 'bar'})
            
    @sio.event
    def connect_confirm(data):
        self.a.pin = data
        self.a.do_something_with_pin()
        
def main():
    a = A()
    a.start_the_process()

if __name__ == '__main__':
    main()

【问题讨论】:

  • 停止思考类和实例。我们称其为面向对象的编程,而不是面向的编程,这是有原因的。您的B instance 是由您的A 实例的方法创建的,它需要能够与该A instance 进行交互;因此,它需要知道它是哪一个。因此,它需要某种参考。比如你传递给B.__init__的那个,当你做self.b = B(self)时。该系统应该可以正常工作。只需始终如一地应用它;有什么问题?
  • 发现你遇到的实际问题是在定义connect_confirm(是connect_confirm,还是confirm_connect?你需要注意这些事情;如果您在谈论代码时给函数起不同的名称,而不是在实际编写代码时,这表明您的注意力不集中)以符合socketio 模块的期望的方式。
  • 那么 - 当您阅读文档时发生了什么?似乎它期望@sio.event 装饰一个普通的函数,而不是一个实例方法。它有没有说什么?它是否提出了任何解决方法?
  • global sio 在类定义中没有任何意义。
  • 现在我已经写了一个答案,我意识到这实际上是一个关于一般技术的非常好的问题,答案是“只有在你是荷兰人时才明显”。它可能出现在很多其他情况下,这让我想以更一般的方式来写它,所以我有一个很好的重复目标....

标签: python socket.io decorator


【解决方案1】:

如果你了解装饰器的工作原理,那么你就明白了

@sio.event
def connect_confirm(self, data):
    self.a.pin = data
    self.a.do_something_with_pin()

只是

的语法糖
def connect_confirm(self, data):
    self.a.pin = data
    self.a.do_something_with_pin()
connect_confirm = sio.event(connect_confirm)

报告的问题是 sio.event 需要一个 1 参数的普通回调函数,它将接收 data;因此,使用 self 参数时,它不能满足这些期望(并且没有 self 参数,该方法的期望不满足)。

洞察力是(因为 3.x;2.x 在底层做了不同的事情)在类中定义的方法只是一个函数;正是 从实例中查找该方法的过程 使得方法使用 self 完成它们所做的特殊事情。

所以当你装饰方法时,你最终将完全错误的东西注册为回调。 socketio.Client 对您的 B 实例一无所知,无论您做什么,都无法使用它。

解决方案是使用你的实例的绑定实例方法作为回调,这需要我们手动调用装饰器,如开头所述。

__init__ 中,我们可以这样做:

def __init__(self, a):
    self.a = a
    sio.event(self.connect_confirm)

然后我们就可以正常定义那个方法了:

def connect_confirm(self, data):
    self.a.pin = data
    self.a.do_something_with_pin()

注意在 __init__ 上下文中,我们现在可以在进行“装饰”时编写 self.,因此我们告诉 socketio.Client 使用此实例的 connect_confirm 回调。我们不需要对“装饰”的结果做任何事情,所以我们不会将它分配到任何地方。


值得考虑从 API 的角度来看这种东西是什么样子的。 socketio.Client 类实现大概包括以下内容:

class Client:
    # ... lots of other stuff...
    def event(self, callback):
        self._event_callback = callback
        return callback
    def _some_internal_logic(self):
        if _special_situation_occurs():
            self._event_callback(self._get_data_for_callback())

如果实现没有return callback,那么很明显你需要在你的情况下做什么,因为装饰器语法不可用。 (好吧,您可以使用任何东西作为装饰器;但是返回None 或其他非函数在大多数情况下并不是很有用。)

【讨论】:

  • 您还应该认真考虑如何管理socketio.Client 的实例。对于每个B 实例来说,创建自己的socketio.Client 或将一个从A 传递给__init__ 可能更有意义,后者又可以根据您的需要创建自己的或接收一个。从长远来看,只是扔全局变量会咬你;阅读dependency injection
  • 很好的解释!这完美!现在每个 SocketIO 侦听器都有一个类。在A 中创建一个socketio.Client 对象,并传递到每个侦听器的__init__
猜你喜欢
  • 2021-12-26
  • 2011-02-06
  • 1970-01-01
  • 2017-01-06
  • 2021-12-08
  • 2017-11-16
  • 2010-09-23
  • 2015-08-23
  • 2021-12-19
相关资源
最近更新 更多