Created
June 9, 2014 23:18
-
-
Save ntrrgc/654c8ee531236e21fcde to your computer and use it in GitHub Desktop.
Event library (unreliable outside of CPython)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Minimal library to create events | |
import inspect | |
from weakref import ref | |
from weakmethod import WeakMethod | |
class Event(object): | |
def __init__(self, handlers=[]): | |
self.handlers = set(handlers) | |
def __call__(self, *args, **kwargs): | |
lost_references = set() | |
for ref_handler in self.handlers: | |
try: | |
ref_handler()(*args, **kwargs) | |
except ReferenceError: | |
lost_references.add(handler) | |
for lost_ref in lost_references: | |
self.handlers.remove(lost_ref) | |
def __iadd__(self, handler): | |
# bound methods are garbage collected on themselves, not when its bound | |
# instance is destroyed. | |
# Use WeakMethod to avoid that. | |
if inspect.ismethod(handler): | |
handler = WeakMethod(handler, callback=self.handlers.remove) | |
else: | |
handler = ref(handler, callback=self.handlers.remove) | |
self.handlers.add(handler) | |
return self |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from event import Event | |
import unittest | |
class TestEvents(unittest.TestCase): | |
def setUp(self): | |
self.on_test = Event() | |
self.called_a = False | |
self.called_b = False | |
self.called_c = False | |
def a(self): | |
self.called_a = True | |
def b(self): | |
self.called_b = True | |
def test_event(self): | |
# Add event handlers | |
self.on_test += self.a | |
self.on_test += self.b | |
self.assertEqual(len(self.on_test.handlers), 2) | |
# Signal the event | |
self.on_test() | |
self.assertTrue(self.called_a) | |
self.assertTrue(self.called_b) | |
self.assertFalse(self.called_c) | |
def test_class(self): | |
# A class which uses the reference | |
class MyClass(object): | |
def __init__(self, test_object): | |
self.test_object = test_object | |
self.test_object.on_test += self.c | |
def c(self): | |
print("c") | |
self.test_object.called_c = True | |
# Add event handlers | |
self.on_test += self.a | |
self.on_test += self.b | |
instance = MyClass(self) # MyClass adds itself to the event | |
self.assertEqual(len(self.on_test.handlers), 3) | |
# Signal the event | |
self.on_test() | |
# All event handlers are called | |
self.assertTrue(self.called_a) | |
self.assertTrue(self.called_b) | |
self.assertTrue(self.called_c) | |
return instance | |
def test_weakref(self): | |
instance = self.test_class() | |
self.assertEqual(len(self.on_test.handlers), 3) | |
# Reset the flags | |
self.called_a = False | |
self.called_b = False | |
self.called_c = False | |
# Dispose of instance | |
instance = None | |
# Signal the event | |
self.on_test() | |
# The C handler should have been destroyed and thus not called | |
self.assertTrue(self.called_a) | |
self.assertTrue(self.called_b) | |
self.assertFalse(self.called_c) | |
# The handler should have been removed | |
self.assertEqual(len(self.on_test.handlers), 2) | |
if __name__ == "__main__": | |
unittest.main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import weakref | |
if hasattr(weakref, "WeakMethod"): | |
from weakref import WeakMethod | |
else: | |
from weakref import ref | |
class WeakMethod(ref): | |
""" | |
A custom `weakref.ref` subclass which simulates a weak reference to | |
a bound method, working around the lifetime problem of bound methods. | |
""" | |
__slots__ = "_func_ref", "_meth_type", "_alive", "__weakref__" | |
def __new__(cls, meth, callback=None): | |
try: | |
obj = meth.__self__ | |
func = meth.__func__ | |
except AttributeError: | |
raise TypeError("argument should be a bound method, not {}" | |
.format(type(meth))) | |
def _cb(arg): | |
# The self-weakref trick is needed to avoid creating a reference | |
# cycle. | |
self = self_wr() | |
if self._alive: | |
self._alive = False | |
if callback is not None: | |
callback(self) | |
self = ref.__new__(cls, obj, _cb) | |
self._func_ref = ref(func, _cb) | |
self._meth_type = type(meth) | |
self._alive = True | |
self_wr = ref(self) | |
return self | |
def __call__(self): | |
obj = super(WeakMethod, self).__call__() | |
func = self._func_ref() | |
if obj is None or func is None: | |
return None | |
return self._meth_type(func, obj) | |
def __eq__(self, other): | |
if isinstance(other, WeakMethod): | |
if not self._alive or not other._alive: | |
return self is other | |
return ref.__eq__(self, other) and self._func_ref == other._func_ref | |
return False | |
def __ne__(self, other): | |
if isinstance(other, WeakMethod): | |
if not self._alive or not other._alive: | |
return self is not other | |
return ref.__ne__(self, other) or self._func_ref != other._func_ref | |
return True | |
__hash__ = ref.__hash__ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment