Reputation: 7912
I'd like to bind a class method to the object instance so that when the method is invoke as callback it can still access the object instance. I am using an event emitter to generate and fire events.
This is my code:
#!/usr/bin/env python3
from pyee import EventEmitter
class Component(object):
_emiter = EventEmitter()
def emit(self, event_type, event):
Component._emiter.emit(event_type, event)
def listen_on(event):
def listen_on_decorator(func):
print("set event")
Component._emiter.on(event, func)
def method_wrapper(*args, **kwargs):
return func(*args, **kwargs)
return method_wrapper
return listen_on_decorator
class TestComponent(Component):
@listen_on('test')
def on_test(self, event):
print("self is " + str(self))
print("FF" + str(event))
if __name__ == '__main__':
t = TestComponent()
t.emit('test', { 'a': 'dfdsf' })
If you run this code, an error is thrown :
File "component.py", line 29, in <module> [0/1889]
t.emit('test', { 'a': 'dfdsf' })
File "component.py", line 8, in emit
Component._emiter.emit('test', event)
File "/Users/giuseppe/.virtualenvs/Forex/lib/python3.4/site-packages/pyee/__init__.py", line 117, in emit
f(*args, **kwargs)
File "component.py", line 14, in method_wrapper
return func(*args, **kwargs)
TypeError: on_test() missing 1 required positional argument: 'event'
This is caused by the missing self when the method on_test
is called.
Upvotes: 1
Views: 3074
Reputation: 110301
Based on OP's extra requierements presented on the comments to the other answer, there is this alternative approach.
Here, the decorator is used only to mark which methods will work as emitters - and the actual registry of emitters is done only when the class is actually instantiated, based on the methods that were marked.
#!/usr/bin/env python3
from pyee import EventEmitter
class Component(object):
_emiter = EventEmitter()
def __init__(self):
for attr_name in dir(self):
method = getattr(self, attr_name)
if hasattr(method, "_component_emitter_on"):
for event in method._component_emitter_on:
self._emiter.on(event, method)
self.attr_name = method
def emit(self, event_type, event):
Component._emiter.emit(event_type, event)
def listen_on(event):
def listen_on_decorator(func):
print("set event")
func._component_emitter_on = getattr(func, "_component_emitter_on", []) + [event]
return func
return listen_on_decorator
class TestComponent(Component):
@listen_on('test')
def on_test(self, event):
print("self is " + str(self))
print("FF" + str(event))
if __name__ == '__main__':
t = TestComponent()
t.emit('test', { 'a': 'dfdsf' })
(Note I had also removed a redundant indirection level on your decorator - if decorators won't replace the callable itself, just make annotations to it (or with it), they don't need to create another callable)
Upvotes: 1
Reputation: 110301
The instance-bound method does not exist, as it is hard to imagine otherwise, at the time the class body is parsed - which is when decorators are applied.
That means your on_test
method behaves just like a function, not being "aware" of it's class or instance at that point. When a method is retrieved from an object instance, Python does create an special callable (with "method" type) that essentially will insert the self
parameter in a call to the original function.
One way to make it work is to decorate this callable (the bound method itself) instead of the raw function. Of course, it only exists at the time the class is instantiated.
Fortunately, in Python, decorators are mostly a syntactic shortcut to a function call passing the decorated function as a parameter - so you can just rewrite your TestComponent
class more or less like this:
class TestComponent(Component):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.on_test = listen_on('test')(self.on_test)
def on_test(self, event):
print("self is " + str(self))
print("FF" + str(event))
Note that as we re-assign the self.on_test
instance member after decoration, it ceases to behave as a method when called through the instance - it will be a mere function call, without magic inserting of self
- however, the self
parameter is already bound to that callable on the moment self.on_test
is retrieved and passed as a parameter to the decorator, on the right side of that line.
** alternative ** The comments suggests the above use does not look like the decorator is used - one can rewrite that to actually use the decorator syntax, just doing this - although this will hide the method from static code checkers, such as IDE autocompletion engines, and linters:
class TestComponent(Component):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
@listen_on('test')
def on_test(self, event):
print("self is " + str(self))
print("FF" + str(event))
self.on_test = on_test
Upvotes: 1