I have an issue using a decorator from a imported package in a class I created. I created two classes and a main()
. An instance of class A
is created in main()
and an instance of class B
is created in class A
. Class B
needs to update attributes of the instance created in main()
. Furthermore, I need to use decorators from the imported package.
I don’t know how to get around the inability to reference the attributes of the the instance created in main()
from the decorated confirm_connect
function in the instance of class B
. The only way I have made it work is to declare the instance created in main()
as global
and remove all the self
references in class B
. However, making it global
causes other issues in my application. (Making the instance of socketio
as global
within class B
is tolerable, but I prefer not having it either.)
The function confirm_connect
receives a message from the server. If I define the function as def conform_connect(self, data)
, I get the error message connect_confirm() missing 1 required positional argument: 'data'/
. If I remove self
from the declaration, then I get the error NameError: name 'self' is not defined
.
Here is my script. How can I make my script do what I need it to do?
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()
If you understand how decorators work, then you understand that
@sio.event
def connect_confirm(self, data):
self.a.pin = data
self.a.do_something_with_pin()
is simply syntactic sugar for
def connect_confirm(self, data):
self.a.pin = data
self.a.do_something_with_pin()
connect_confirm = sio.event(connect_confirm)
And the reported problem is that sio.event
expects a 1-argument, plain callback function that will receive the data
; so with a self
parameter it doesn't meet those expectations (and without a self
parameter, the expectations of the method aren't met).
The insight is that (since 3.x; 2.x did things differently under the hood) a method defined in a class is just a function; it's the process of looking up that method from an instance that makes methods do the special things with self
that they do.
So when you decorate the method, you end up registering totally the wrong thing as a callback. The socketio.Client
doesn't know anything about your B instance and can't work with it, no matter what you do.
The solution is to instead use the bound instance method of your instance for the callback, which requires us to invoke the decorator manually as described at the beginning.
In the __init__
, we can do something like:
def __init__(self, a):
self.a = a
sio.event(self.connect_confirm)
And then we can define that method normally:
def connect_confirm(self, data):
self.a.pin = data
self.a.do_something_with_pin()
Notice how in the __init__
context we can now write self.
when we do the "decoration", so we tell the socketio.Client
to use the connect_confirm
of this instance as a callback. We don't need to do anything with the "decorated" result, so we don't assign it anywhere.
It's worth considering what this kind of thing looks like from the API's point of view. The socketio.Client
class implementation presumably includes something like:
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())
If the implementation didn't return callback
, it would be obvious what you needed to do in your situation, since the decorator syntax wouldn't have been available. (Well, you can use anything as a decorator; but getting back None
or another non-function is not very useful most of the time.)