0% found this document useful (0 votes)
53 views

Python Delegator

The document describes a Python decorator that simplifies implementing the delegate pattern. It does this by automatically delegating unimplemented methods and attributes of a delegate class to a decorated class. The decorator works by first collecting the attributes of the delegate class. It then defines a "delegate" property on the decorated class to hold the delegate instance. For each unimplemented attribute, it adds a DelegatedAttribute descriptor that delegates getter, setter, and deleter calls to the corresponding attribute on the delegate instance. This allows the decorated class to inherit the interface of the delegate class without manually writing all the delegated calls.

Uploaded by

sem
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
53 views

Python Delegator

The document describes a Python decorator that simplifies implementing the delegate pattern. It does this by automatically delegating unimplemented methods and attributes of a delegate class to a decorated class. The decorator works by first collecting the attributes of the delegate class. It then defines a "delegate" property on the decorated class to hold the delegate instance. For each unimplemented attribute, it adds a DelegatedAttribute descriptor that delegates getter, setter, and deleter calls to the corresponding attribute on the delegate instance. This allows the decorated class to inherit the interface of the delegate class without manually writing all the delegated calls.

Uploaded by

sem
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

Python Decorator for Simplifying Delegate Pattern | Programming Ideas With Jake 2/1/23, 4:06 PM

Programming Ideas With Jake

Jake explores ideas in Java and Python programming

Python Decorator for Simplifying Delegate Pattern

Posted by Jacob Zimmerman on 2015-05-23


Posted in: Descriptors, Design Patterns, Meta, Python. Tagged: composition, decorator, default parameters, delegate, python. 8
Comments
Recently, I posted about how you can use composition instead of inheritance
(https://programmingideaswithjake.wordpress.com/2015/05/09/replacing-inheritance-with-composition/), and in that article, I
mentioned the Delegate Pattern. As a quick description, the pattern has you inherit from the parent interface (or, in Python, you can
just implement the protocol), but all the methods either redirect to calling the method on an injected implementation of
interface/protocol, possibly surrounding the call in its own code, or it completely fills the method with its own implementation.

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.

Standard Attribute Delegation

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 __get__(self, instance, owner):


if instance is None:
return self
else:
# return instance.delegate.attr
return getattr(getattr(instance, self.delegate_name), self.attr_name)

def __set__(self, instance, value):


# instance.delegate.attr = value
setattr(getattr(instance, self.delegate_name), self.attr_name, value)

def __delete__(self, instance):


delattr(getattr(instance, self.delegate_name), self.attr_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 __get__(self, instance, owner):


if instance is None:
return self
else:
# return instance.delegate.attr
return getattr(self.delegate(instance), self.attr_name)

def __set__(self, instance, value):


# instance.delegate.attr = value
setattr(self.delegate(instance), self.attr_name, value)

def __delete__(self, instance):


delattr(self.delegate(instance), self.attr_name)

def delegate(self, instance):


return getattr(instance, self.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.

Let’s see an example of this in action:

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.

Starting the Decorator

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.

Skipping Some Attributes

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.

Some More Parameters

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.

All this leaves us with the final product!

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 delegate_as(delegate_cls, to='delegate', include=frozenset(), ignore=frozenset()):


# turn include and ignore into sets, if they aren't already
if not isinstance(include, Set):
include = set(include)
if not isinstance(ignore, Set):
ignore = set(ignore)
delegate_attrs = set(delegate_cls.__dict__.keys())
attributes = include | delegate_attrs - ignore

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.

A more complex example can be seen here:

class Base:
def __init__(self, field1, field2):
self.field1 = field1
self.field2 = field2

def method1(self):
...

def method2(self, param):


...

def method3(self):
...

delegate_as(Base, to='base', include={'field1'}, ignore={'method1', 'method2'})


class Sub:
def __init__(self, base):
self.base = base

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?

8 comments on “Python Decorator for Simplifying Delegate Pattern”

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

JZA on 2015-05-26 at 5:25 pm said:


Reblogged this on Ideas, thoughts and Free Software and commented:

Need to study more on patterns as an implementation examples.

Reply
Pingback: Kotlin Month Post 2: Inheritance and Defaults | Programming Ideas With Jake

Thomas A on 2016-06-02 at 12:35 pm said:


It would be nice to see examples of the delegate_as decorator in use.

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

Keith on 2017-09-18 at 4:00 am said:


RE section: Standard Attribute Delegation
using 3.6

typo in DelegatedAttribute.str
return “‘
should be
return “” or return ”

AttributeError: DelegatedAttribute instance has no call method

Continuing with the rest of the article

Reply
Jacob Zimmerman on 2017-09-18 at 5:34 am said:
Good spot! It’s changed. Thank you.

Reply

Create a free website or blog at WordPress.com.

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

You might also like