At my work this summer I’ve had the privilege to work on a code base that was very well-designed. As a result, I feel I have substantially grown as a developer. One of the neatest parts of the project I’ve been working on is that there are these magical things called delegates that glue the code together. These magical entities are what I want to talk about here.
Before we begin however, I want to define something called a functor. For our purposes, a functor is any kind of callable object: a function, class method, or even a class with the
__call__() magic method defined.
Suppose you have a piece of software that looks like the following:
Delegates are what handle the flow of data from one component to the next. All we do with the implementation here is set up each individual component and then choose who is subscribed to what. Then all we do is pipe data in and watch it come out the other side.
To be sure, delegates have other use cases, but this is what I was exposed to, and it was nifty enough that I wanted to take a crack at my own implementation. It was simple enough that, not including the substantial amount time I spent on a trivial misunderstanding, I only spent an hour on the implementation, plus another for some polish.
Before I show you the implementation, I want to show you how it’s used. Here’s a simple example that calculates the squares of the first ten even numbers. Delegates are overkill for such a simple task, but I wanted something easy to understand. Here’s a diagram of what exactly is happening:
The end result is that of applying the function . Here’s the code
#!/usr/bin/python3 from modifiers import Multiplier, Exponentiator, Incrementer from observer import Observer def main(): # Modifiers that act on numbers. incrementer = Incrementer(1) doubler = Multiplier(2) squarer = Exponentiator(2) # An observer to view intermediate and/or final values in the delegate chain. observer = Observer() # Subscribe the doubler modifier to the output of the incrementer. incrementer.subscribe(doubler) # Subscribe the squarer modifier to the output of the doubler. doubler.subscribe(squarer) # Subscribe the observer to the output of the squarer. squarer.subscribe(observer) for i in range(10): # Call the first modifier in the chain, and let the delegates handle the rest incrementer(i) # Unsubscribe functors. incrementer.unsubscribe(doubler) doubler.unsubscribe(squarer) squarer.unsubscribe(observer) if __name__ == '__main__': main()
I’ve included an
Observer object in the chain because we need some way to view the result of our calculations, which are as follows:
~ $ ./main-simple.py Observed value(s): 4 Observed value(s): 16 Observed value(s): 36 Observed value(s): 64 Observed value(s): 100 Observed value(s): 144 Observed value(s): 196 Observed value(s): 256 Observed value(s): 324 Observed value(s): 400
Now, as it stands, each of the modifier classes are implemented as callable objects. This is not necessary, I chose to do so for the convenience. We could do something like the following:
#!/usr/bin/python3 from delegate import Delegated from modifiers import Exponentiator, Incrementer from observer import Observer class Doubler(Delegated): """A delegated class to double numbers""" def __init__(self): super().__init__() def double(self, val): """A method to double the given value""" self.delegate(2 * val) def main(): incrementer = Incrementer(1) # Equivalent to Multiplier(2) doubler = Doubler() squarer = Exponentiator(2) observer = Observer() incrementer.subscribe(doubler.double) doubler.subscribe(squarer) squarer.subscribe(observer) for i in range(10): # Call the first modifier in the chain, and let the delegates handle the rest incrementer(i) incrementer.unsubscribe(doubler.double) doubler.unsubscribe(squarer) squarer.unsubscribe(observer) if __name__ == '__main__': main()
Now for the implementation. I think we’ll start with the simplest object: the
Observer inherits from
Delegated, which provides a base constructor,
unsubscribe(). We call the base class constructor, and then we define the
__call__() takes in arbitrary arguments, prints them, and the passes them on unchanged to the delegate.
from delegate import Delegated class Observer(Delegated): """An Observer callable object to observe values in a Delegate chain.""" def __init__(self): super().__init__() def __call__(self, *things): """Observes given values and passes them along with no changes.""" print('Observed value(s):', *things) self.delegate(*things)
The next thing to look at is the modifier classes:
from delegate import Delegated class Multiplier(Delegated): """A Multiplier modifier to multiply numbers""" def __init__(self, multiplier=2): """Construct a Multiplier object with an optional multiplier. Defaults to 2.""" super().__init__() self.multiplier = multiplier def __call__(self, num): """Multiplies a given number by a predetermined multiplier""" self.delegate(num * self.multiplier) class Incrementer(Delegated): """An Incrementer modifier to increment numbers""" def __init__(self, increment_value=1): """Construct an Incrementer object with an optional increment value. Defaults to 1.""" super().__init__() self.increment_value = increment_value def __call__(self, num): """Increment num by a predetermined value""" self.delegate(num + self.increment_value) class Exponentiator(Delegated): """An Exponentiator modifier to exponentiate numbers""" def __init__(self, power=2): """Construct an Exponentiator object with an optional power. Defaults to 2.""" super().__init__() self.power = power def __call__(self, num): """Exponentiates a given number by a predetermine value""" self.delegate(num ** self.power)
These are fairly self explanatory: instead of passing on what they are given unchanged, they modify their input somehow before calling their delegates on the values.
Now for the implementation of the
Delegated base class. A
Delegated class has two methods:
unsubscribe() to, appropriately, subscribe and unsubscribe from its output.
class Delegate(object): """A class to implement delegates""" def __init__(self): self.functors = list() def subscribe(self, functor): """Subscribe a given functor to this delegate.""" # Verify that functor is callable. If not, don't subscribe. if callable(functor): self.functors.append(functor) return self def unsubscribe(self, functor): """Unsubscribe a given functor to this delegate.""" try: self.functors.remove(functor) # Functor wasn't subscribed. except ValueError: pass return self def __call__(self, *args): """Call each of the subscribed functors with the given values""" for functor in self.functors: functor(*args) class Delegated(object): """A base class for delegated classes""" def __init__(self): self.delegate = Delegate() def subscribe(self, functor): """Subscribes the given functor to the output of self""" self.delegate.subscribe(functor) def unsubscribe(self, functor): """Unsubscribes the given functor from the output of self""" self.delegate.unsubscribe(functor)
Delegate class is where the magic happens. What I’ve defined a
Delegate to be is a list of functors, all of which will be called on the given value(s) when the delegate is called. This way a
Delegated class has one delegate, but an arbitrary number of subscribers.
I’ve shown how delegates can be used in a very simple manner, but please realize, the magic between the input and the output could be extremely complex. Perhaps each component in a piece of software is in fact complex enough to require subcomponents glued together with delegates.
One of the neat things about delegates is it allows you to insert an arbitrary component in the middle of a complex system with minimal wrangling.
I hope you enjoyed reading this, I certainly enjoyed writing it!