Question: So I'm trying to implement something akin to C# events in Python as a decorator for methods: from __future__ import annotations from typing import * import functools def event(function: Callable) -> EventDispatcher: return EventDispatcher(function) class EventDispatcher: def __init__(self, function: Callable) -> None: functools.update_wrapper(self, function) self._instance: Any | None = None self._function: Callable = function self._callbacks: Set[Callable] = set() def __get__(self, instance: Any, _: Any) -> EventDispatcher: self._instance = instance return self def __iadd__(self, callback: Callable) -> EventDispatcher: self._callbacks.add(callback) return self def __isub__(self, callback: Callable) -> EventDispatcher: self._callbacks.remove(callback) return self def __call__(self, *args: Any, **kwargs: Any) -> Any: for callback in self._callbacks: callback(*args, **kwargs) return self._function(self._instance, *args, **kwargs)  But when I decorate a class method with @event and later on call the decorated method, the method will be invoked on the incorrect instance in some cases. Good Case: class A: @event def my_event(self) -> None: print(self) class B: def __init__(self, a: A) -> None: a.my_event += self.my_callback def my_callback(self) -> None: pass a0 = A() a1 = A() # b = B(a0) a1.my_event() a0.my_event()  The above code will result in the output: <__main__.A object at 0x00000170AA15FCA0> <__main__.A object at 0x00000170AA15FC70>  Evidently the function my_event() is called twice, each time with a different instance as expected. Bad Case: Taking the code from the good case and commenting in the line # b = B(a0) results in the output: <__main__.A object at 0x000002067650FCA0> <__main__.A object at 0x000002067650FCA0>  Now the method my_event() is called twice, too. But on the same instance. Question: I think the issue boils down to EventDispatcher.__get__() not being called in the bad case. So my question is, why is EventDispatcher.__get__() not called and how do I fix my implementation?

Answer: The problem lies in the initializer of B: a.my_event += self.my_callback  Note that the += operator is not just a simple call to __iadd__, but actually equivalent to: a.my_event = a.my_event.__iadd__(self.my_callback)  This is also the reason why your __iadd__ method needs to return self. Because the class EventDispatcher has only __get__ but no __set__, the result will be written to the instance's attribute during assignment, so the above statement is equivalent to: a.__dict__['my_event'] = A.__dict__['my_event'].__get__(a, A).__iadd__(self.my_callback)  Simple detection: print(a0.__dict__) b = B(a0) print(a0.__dict__)  Output: {} {'my_event': <__main__.EventDispatcher object at 0x00000195218B3FD0>}  When a0 calls my_event on the last line, it only takes the instance of EventDispatcher from a0.__dict__ (instance attribute access takes precedence over non data descriptors, refer to invoking descriptor), and does not trigger the __get__ method. Therefore, A.__dict__['my_event']._instance will not be updated.  The simplest repair way is to add an empty __set__ method to the definition of EventDispatcher: class EventDispatcher: ... def __set__(self, instance, value): pass ...  Output: <__main__.A object at 0x000002A36B9B3D30> <__main__.A object at 0x000002A36B9B3D00>