Skip to content

Instantly share code, notes, and snippets.

@Midnighter
Forked from mikeckennedy/watch_this.py
Last active August 7, 2020 19:20
Show Gist options
  • Save Midnighter/e0051e00e3467f447f487a7f399334d7 to your computer and use it in GitHub Desktop.
Save Midnighter/e0051e00e3467f447f487a7f399334d7 to your computer and use it in GitHub Desktop.
Add C# += / -= event subscriptions to Python using the Events package
import functools
from events import Events # Note you must pip install events
class set_event:
"""Define a class-based setter method decorator."""
def __init__(self, func, event):
super().__init__()
functools.update_wrapper(self, func)
self._func = func
self._event = event
self._last = None
def __call__(self, *args, **kwargs):
if len(args) == 2:
attr = args[1]
elif "value" in kwargs:
attr = kwargs["value"]
else:
raise RuntimeError("Not a simple property assignment.")
self._func(*args, **kwargs)
if attr != self._last:
self._event(args[0])
self._last = attr
def add_events(fields: dict):
"""Define a class decorator with arguments."""
def decorator(klass):
original_init = klass.__init__
@functools.wraps(original_init)
def init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
self.__dict__["_events"] = events = Events(tuple(fields.values()))
for event_name in fields.values():
self.__dict__[event_name] = getattr(events, event_name)
for field, event_name in fields.items():
class_field = type(self).__dict__[field]
# Attempt to decorate the instance property...
self.__dict__[field] = property(
class_field.fget,
set_event(class_field.fset, getattr(events, event_name)),
class_field.fdel,
)
klass.__init__ = init
return klass
return decorator
@add_events(
{"age": "on_age_changed", "name": "on_name_changed", "city": "on_location_changed"}
)
class Person:
def __init__(self, name: str, age: int, city: str):
self.__name = name
self.__age = age
self.__city = city
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
self.__age = value
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
self.__name = value
@property
def city(self):
return self.__city
@city.setter
def city(self, value):
self.__city = value
def main():
p1 = Person("Michael", 47, "Portland")
p2 = Person("Zoe", 50, "Orlando")
p1.on_name_changed += lambda p: print(f"Person changed name to {p.name}.")
p1.on_location_changed += location_changed
p2.on_location_changed += location_changed
# Two moves for Zoe
p2.city = "Seattle"
p2.city = "LA"
# One move for Michael
p1.city = "Vancouver"
# One name change for Michael to Mike
p1.name = "Mike"
# No longer watching for moves for Michael
p1.on_location_changed -= location_changed
p1.city = "Portland"
def location_changed(person: Person):
print(f"Looks like {person.name} has moved to {person.city}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment