Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Python classes support events like other languages?

I'm working on my first Python project, and I'm already missing events in my classes. Perhaps it's not even called events in Python, but I would like to create "groups" in my classes to which function references can be added. At some point in my class all function references in my group would execute.

Is this built into Python? (I'm using 2.7 at the moment)

like image 752
Hubro Avatar asked May 27 '11 23:05

Hubro


2 Answers

Python doesn't have any sort of event system built-in, but it's could be implemented pretty simply. For example:

class ObjectWithEvents(object):
    callbacks = None

    def on(self, event_name, callback):
        if self.callbacks is None:
            self.callbacks = {}

        if event_name not in self.callbacks:
            self.callbacks[event_name] = [callback]
        else:
            self.callbacks[event_name].append(callback)

    def trigger(self, event_name):
        if self.callbacks is not None and event_name in self.callbacks:
            for callback in self.callbacks[event_name]:
                callback(self)

class MyClass(ObjectWithEvents):
    def __init__(self, contents):
        self.contents = contents

    def __str__(self):
        return "MyClass containing " + repr(self.contents)

def echo(value): # because "print" isn't a function...
    print value

o = MyClass("hello world")
o.on("example_event", echo)
o.on("example_event", echo)
o.trigger("example_event") # prints "MyClass containing \"Hello World\"" twice
like image 155
Jeremy Avatar answered Oct 25 '22 15:10

Jeremy


While Jeremy Banks' answer works just fine, it's not what most would call "pythonic". Since this question comes up quite easily through search engines, here's an alternative answer that attemps to use the best conventions from my experience:

class Event:
    def __init__(self):
        self.listeners = []

    def __iadd__(self, listener):
        """Shortcut for using += to add a listener."""
        self.listeners.append(listener)
        return self

    def notify(self, *args, **kwargs):
        for listener in self.listeners:
            listener(*args, **kwargs)

To use it you simply create an Event object and then register listener callbacks by either manipulating the listeners list directly, or using the += shortcut. You then use the notify() method to call all the listeners. Any arguments and keyword arguments passed to the notify() method will be forwarded to the listeners.

Here's a full example:

>>> my_event = Event()
>>> def print_person_info(name, age, sex):
...     print("Hello! I am {}, I'm a {}-year-old {}".format(name, age, sex))
...
>>> my_event += print_person_info
>>> my_event.notify('Markus', 23, 'male')
Hello! I am Markus, I'm a 23-year-old male

These event objects can easily be added to a class or an instance as well:

class Soldier:
    # An event on a class level.
    # Listening to just this will notify you of *any* person dying. 
    e_death = Event()

    def __init__(self, name, health):
        self.name = name
        self.health = health

        # Instance level event.
        # Using this you need to listen to each person separately.
        self.e_eat = Event()

    def eat(self, amount):
        self.health += amount
        self.e_eat.notify(self, amount=amount)

    def hurt(self, damage):
        self.health -= damage
        if self.health <= 0:
            Soldier.e_death.notify(self)

Of course it's usually a bad idea to mix class and instance level events like this, I've only done if for demonstration purposes. If unsure, use the instance level events.

like image 36
Markus Meskanen Avatar answered Oct 25 '22 15:10

Markus Meskanen