Python Delegator
Python Delegator
For the most part, it is like manually implementing inheritance, except that the superclass in injectable.
Anyway, one of the more annoying parts of implementing the Delegate Pattern is having to write all of the delegated calls. So I
decided to look into the possibility of of making a decorator that will do that for you for all the methods you don’t explicitly
implement.
First, we need a standard way of delegating an attribute to a delegate object’s attribute. We’ll use a descriptor for this. Its constructor
takes in the name of the delegate stored on the object as well as the name of the attribute to look up. Here’s the descriptor:
https://programmingideaswithjake.wordpress.com/2015/05/23/python-decorator-for-simplifying-delegate-pattern/ Page 1 of 8
Python Decorator for Simplifying Delegate Pattern | Programming Ideas With Jake 2/1/23, 4:06 PM
class DelegatedAttribute:
def __init__(self, delegate_name, attr_name):
self.attr_name = attr_name
self.delegate_name = delegate_name
def __str__(self):
return ""
I added a nice __str__ method to make it look a little less like a boring object and more like a feature when people do some REPL
diving. Normally, I don’t bother implementing the __delete__ method, but I felt it was appropriate here. I doubt it’ll ever be used,
but if someone wants to use del on an attribute, I want it to be effective.
Hmm, all those main methods have a repetitive part for getting the delegate object. Let’s extract a method and hopefully make them a
little bit more legible; nested getattr s, setattr s, and delattr s aren’t the easiest thing to look at.
class DelegatedAttribute:
def __init__(self, delegate_name, attr_name):
self.attr_name = attr_name
self.delegate_name = delegate_name
def __str__(self):
return "'
https://programmingideaswithjake.wordpress.com/2015/05/23/python-decorator-for-simplifying-delegate-pattern/ Page 2 of 8
Python Decorator for Simplifying Delegate Pattern | Programming Ideas With Jake 2/1/23, 4:06 PM
Advertisements
REPORT THIS ADPRIVACY
There, that’s a little easier to read. At the very least, we’ve removed some repetition. I wish we didn’t need the self reference to call
it, as that would make it even easier to look at, but trying to get around it would likely be a lot of work, just as ugly, or both.
class Foo:
def __init__(self):
self.bar = 'bar in foo'
def baz(self):
return 'baz in foo'
class Baz:
def __init__(self):
self.foo = Foo()
bar = DelegatedAttribute('foo', 'bar')
baz = DelegatedAttribute('foo', 'baz')
x = Baz()
print(x.bar) # prints 'bar in foo'
print(x.baz()) # prints 'baz in foo'
It’s as simple as making sure you have an object to delegate to and adding a DelegatedAttribute with the name of that object
and the attribute to delegate to. As you can see, it properly delegates both functions and object fields as we would want. It’s not only
useful for the Delegate pattern but really any situation in composition where a wrapper call simply delegates the call to the wrapped
object.
This is good, but we don’t want to do this by hand. Plus, I mentioned a decorator. Where’s that? Hold your horses; I’m getting there.
See? Now we’ve moved to the decorator. What is the decorator supposed to do? How should it work? First thing you should know is
that we created the DelegatedAttribute class for a reason. We’ll utilize that, assigning all the class attributes of the delegate
object to the new class. To do that, we’ll need a reference to the class. We’ll also need to know the name of the delegate object, so it can
be given to the DelegatedAttribute constructor. For now, we’ll hard code that. From this, we know that we need a
parameterized decorator, whose outermost signature looks like . Let’s get it started.
https://programmingideaswithjake.wordpress.com/2015/05/23/python-decorator-for-simplifying-delegate-pattern/ Page 3 of 8
Python Decorator for Simplifying Delegate Pattern | Programming Ideas With Jake 2/1/23, 4:06 PM
def delegate_as(delegate_cls):
# gather the attributes of the delegate class to add to the decorated class
attributes = delegate_cls.__dict__.keys()
def inner(cls):
# create property for storing the delegate
setattr(cls, 'delegate', SimpleProperty())
# set all the attributes
for attr in attributes:
setattr(cls, attr, DelegatedAttribute('delegate', attr))
return cls
return inner
You may have noticed the creation of something called a SimpleProperty , which won’t be shown. It is simply a descriptor that
holds and returns a value given to it, the most basic of properties.
The first thing it does is collect all the attributes from the given class so it knows what to add to the decorated class, preparing it for
the inner function that does the real work. The inner function is the real decorator, and it takes that sequence and adds those prepared
attributes to the decorated class using DelegatedAttribute s.
Advertisements
REPORT THIS ADPRIVACY
This may all seem alright, but it doesn’t actually work. The problem is that some of those attributes that we try to add on to the
decorated class are ones that come with every class and are read only.
So we need to skip over some attributes in the list. I had originally done this by creating a “blacklist” set of attribute names that come
standard with every class. But then I realized that implementing a different requirement of the decorator would take care of it already.
That requirement is that the decorator not write over any of the attributes that the class had explicitly implemented. So, if the
decorated class defines its own version of a method that the delegate has, the decorator should not add that method. Ignoring those
attributes works the same as ignoring the ones I would put into the blacklist, since they’re already defined on the class.
So here’s what the code looks like with that requirement added in.
https://programmingideaswithjake.wordpress.com/2015/05/23/python-decorator-for-simplifying-delegate-pattern/ Page 4 of 8
Python Decorator for Simplifying Delegate Pattern | Programming Ideas With Jake 2/1/23, 4:06 PM
def delegate_as(delegate_cls):
# gather the attributes of the delegate class to add to the decorated class
attributes = set(delegate_cls.__dict__.keys())
def inner(cls):
# create property for storing the delegate
setattr(cls, 'delegate', SimpleProperty())
# don't bother adding attributes that the class already has
attrs = attributes - set(cls.__dict__.keys())
# set all the attributes
for attr in attrs:
setattr(cls, attr, DelegatedAttribute('delegate', attr))
return cls
return inner
As you can see, I turned the original collection of keys into a set and did the same with the attributes from cls . This is because we
want to remove one set of keys from the other, and a really simple way to do that is with set ‘s subtraction. It could be done with
filter or or similar, but I like the cleanliness of this. It’s short, and its meaning is quite clear. I had to store the final result in
attrs instead of attributes because the outer call could be reused, so we don’t want to change attributes for those cases.
Okay, so we’re now able to automatically delegate any class-level attributes from a base implementation to the decorated class, but
that’s often not the only public attributes that an object of a class has. We need a way to include those attributes. We could get an
instance of the class and copy all the attributes that are preceded by a single underscore, or we could have the user supply the list of
attributes wanted. I personally prefer to take in a user-supplied list. So, we’ll add an additional parameter to the decorator called
include , which takes in a sequence of strings that are the names of attributes to delegate to.
Next, what if there are some attributes that will be automatically copied over that we don’t actually want? We’ll add another
parameter called ignore to our decorator. It’s similar to include , but is a blacklist instead of a whitelist.
Lastly, we want to get rid of that hard-coded attribute name, "delegate" . Why? Because you should almost never hard code
anything like that, and because there’s a chance that someone might try to double up on the delegate pattern, delegating to two
different objects of different classes. We’d end up with trying to store two different delegates in the same attribute. So we need to give
the user the option to make the name whatever they want. We’ll still default it to "delegate" though, so the user won’t have to set
it if they don’t need to.
https://programmingideaswithjake.wordpress.com/2015/05/23/python-decorator-for-simplifying-delegate-pattern/ Page 5 of 8
Python Decorator for Simplifying Delegate Pattern | Programming Ideas With Jake 2/1/23, 4:06 PM
def inner(cls):
# create property for storing the delegate
setattr(cls, to, SimpleProperty())
# don't bother adding attributes that the class already has
attrs = attributes - set(cls.__dict__.keys())
# set all the attributes
for attr in attrs:
setattr(cls, attr, DelegatedAttribute(to, attr))
return cls
return inner
Advertisements
REPORT THIS ADPRIVACY
I also made include and ignore have default values of an empty set . They’re frozenset s specifically, despite the fact that
the code doesn’t try to mutate them, as a precautionary measure to prevent any future changes from doing so. You might have noticed
that the checking of the default parameters is a little different than is typical. That’s because I didn’t make the default parameters
default to None . I made them default to something I can use. Also, I generally expect that people will be passing in list s or
tuple s more often than set s, so I just made sure that they ended up being a subclass of Set (the ABC, since neither
frozenset nor set are subclasses of the other, and users may use some nonstandard Set ) so I can do the union and subtraction
with them later. It also ensures no doubles anywhere.
Example Usages
The most basic usage is something simple, like this:
class BaseClass:
...
@delegate_as(BaseClass)
class SomeSubClass:
def __init__(self, delegate):
self.delegate = delegate
...
With these extremely simplified classes, it’s practically straight inheritance, but more difficult to instantiate
( SomeSubClass(BaseClass()) rather than just SomeSubClass() ). If the implementation works that way, you could nest
SomeSubClass es: SomeSubClass(SomeSubClass(BaseClass())) . Or, if BaseClass had another ‘subclass’,
https://programmingideaswithjake.wordpress.com/2015/05/23/python-decorator-for-simplifying-delegate-pattern/ Page 6 of 8
Python Decorator for Simplifying Delegate Pattern | Programming Ideas With Jake 2/1/23, 4:06 PM
OtherSubClass , which actually delegates just like SomeSubClass , the two could nest within each other as much as you could
want. The innermost delegate object MUST be a fully implemented class, though, since wrappers need another instance within them,
and passing None would cause the wrapper to delegate method calls to None , which causes errors.
class Base:
def __init__(self, field1, field2):
self.field1 = field1
self.field2 = field2
def method1(self):
...
def method3(self):
...
In this example, the ‘subclass’ changes the name of the attribute that the delegate will be stored in to base , instead of the default,
delegate . It also adds field1 as an instance attribute it will delegate. But not including field2 makes it so you can’t get that
directly from an instance of Sub . Lastly, it says to ignore the methods method1 and method2 , so those won’t be directly
accessible from Sub or its instances either.
Moving Forward
While I’ve shown a final working product, there’s still some refactoring that could be done to make the code cleaner. For instance, you
could take all those comments and use Extract Method to make the actual code say such things. If I were to do that, I’d probably
change the whole thing to a callable decorator class, rather than a decorator function.
Outro
That was a relatively long article. I could have just shown the final product to you and explained it, but I thought that going through a
thought process with it would be nice, too. Are there any changes that you would recommend? What do you guys think of this? Is it
worth it?
https://programmingideaswithjake.wordpress.com/2015/05/23/python-decorator-for-simplifying-delegate-pattern/ Page 7 of 8
Python Decorator for Simplifying Delegate Pattern | Programming Ideas With Jake 2/1/23, 4:06 PM
Reply
Pingback: Kotlin Month Post 2: Inheritance and Defaults | Programming Ideas With Jake
Reply
Jacob Zimmerman on 2016-06-02 at 12:50 pm said:
That’s fair. I’ll put up some.
Reply
Jacob Zimmerman on 2016-06-02 at 1:20 pm said:
Done
Reply
Pingback: The Clash of Template and Delegate Patterns | Programming Ideas With Jake
typo in DelegatedAttribute.str
return “‘
should be
return “” or return ”
Reply
Jacob Zimmerman on 2017-09-18 at 5:34 am said:
Good spot! It’s changed. Thank you.
Reply
Advertisements
Créez des images inédites avec l’outil… REPORT THIS ADPRIVACY
Masquage. LIRE LA SUITE
! " # $ ! # % & ' ( ) * ((+
+' # ) &
https://programmingideaswithjake.wordpress.com/2015/05/23/python-decorator-for-simplifying-delegate-pattern/ Page 8 of 8