On the beginning, I know the bound methods attributes does not exist in Python 3 (according to this topic: Why does setattr fail on a bound method)
I'm trying to write a pseudo 'reactive' Python framework. Maybe I'm missing something and maybe, that what I'm trying to do is somehow doable. Lets look at the code:
from collections import defaultdict
class Event:
def __init__(self):
self.funcs = []
def bind(self, func):
self.funcs.append(func)
def __call__(self, *args, **kwargs):
for func in self.funcs:
func(*args, **kwargs)
def bindable(func):
events = defaultdict(Event)
def wrapper(self, *args, **kwargs):
func(self, *args, **kwargs)
# I'm doing it this way, because we need event PER class instance
events[self]()
def bind(func):
# Is it possible to somehow implement this method "in proper way"?
# to capture "self" somehow - it has to be implemented in other way than now,
# because now it is simple function not connected to an instance.
print ('TODO')
wrapper.bind = bind
return wrapper
class X:
# this method should be bindable - you should be able to attach callback to it
@bindable
def test(self):
print('test')
# sample usage:
def f():
print('calling f')
a = X()
b = X()
# binding callback
a.test.bind(f)
a.test() # should call f
b.test() # should NOT call f
Of course all classes, like Event were simplified for this example. Is there any way to fix this code to work? I want simply to be able to use bindable decorator to make a method (not a function!) bindable and be able to later "bind" it to a callback - in such way, that if somebody calls the method, the callback will be called automatically.
Is there any way in Python 3 to do it?
Ou yeah! :D I've found an answer - a little creazy, but working fast. If somebody has a comment or better solution, I would be very interested in seeing it. Following code is working for methods AND functions:
# ----- test classes -----
class Event:
def __init__(self):
self.funcs = []
def bind(self, func):
self.funcs.append(func)
def __call__(self, *args, **kwargs):
message = type('EventMessage', (), kwargs)
for func in self.funcs:
func(message)
# ----- implementation -----
class BindFunction:
def __init__(self, func):
self.func = func
self.event = Event()
def __call__(self, *args, **kwargs):
out = self.func(*args, **kwargs)
self.event(source=None)
return out
def bind(self, func):
self.event.bind(func)
class BindMethod(BindFunction):
def __init__(self, instance, func):
super().__init__(func)
self.instance = instance
def __call__(self, *args, **kwargs):
out = self.func(self.instance, *args, **kwargs)
self.event(source=self.instance)
return out
class Descriptor(BindFunction):
methods = {}
def __get__(self, instance, owner):
if not instance in Descriptor.methods:
Descriptor.methods[instance] = BindMethod(instance, self.func)
return Descriptor.methods[instance]
def bindable(func):
return Descriptor(func)
# ----- usage -----
class list:
def __init__(self, seq=()):
self.__list = [el for el in seq]
@bindable
def append(self, p_object):
self.__list.append(p_object)
def __str__(self):
return str(self.__list)
@bindable
def x():
print('calling x')
# ----- tests -----
def f (event):
print('calling f')
print('source type: %s' % type(event.source))
def g (event):
print('calling g')
print('source type: %s' % type(event.source))
a = list()
b = list()
a.append.bind(f)
b.append.bind(g)
a.append(5)
print(a)
b.append(6)
print(b)
print('----')
x.bind(f)
x()
and the output:
calling f
source type: <class '__main__.list'>
[5]
calling g
source type: <class '__main__.list'>
[6]
----
calling x
calling f
source type: <class 'NoneType'>
The trick is to use Python's descriptors to store current instance pointer.
As a result we are able to bind a callback to any python function. The execution overhead is not too big - the empty function execution is 5 - 6 times slower than without this decorator. This overhead is caused by needed function chain and by event handling.
When using the "proper" event implementation (using weak references), like this one: Signal slot implementation, we are getting the overhead of 20 - 25 times the base function execution, which still is good.
EDIT:
According to Hyperboreus question, I updated the code to be able to read from the callback methods the source object from whic the callbacks were called. They are now accessible by event.source variable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With