Skip to content

Instantly share code, notes, and snippets.

@dhilst
Last active December 15, 2019 17:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dhilst/7435a09b4419da349bb4cc4ae855a451 to your computer and use it in GitHub Desktop.
Save dhilst/7435a09b4419da349bb4cc4ae855a451 to your computer and use it in GitHub Desktop.
Delegate methods in python
def delegate(to, *methods):
'''
Class decorator to delegate methods to another objects.
>>> @delegate('v', 'upper')
... @delegate('v', 'lower')
... @delegate('v', 'wrong_method')
... @delegate('not_an_attribute', 'wrong_attribute')
... class Foo:
... def __init__(self, v):
... self.v = v
>>>
>>> Foo('foo').upper()
'FOO'
>>> Foo('FOO').lower()
'foo'
>>> Foo('foo').wrong_method()
Traceback (most recent call last):
...
AttributeError: 'str' object has no attribute 'wrong_method'
>>> Foo('foo').wrong_attribute()
Traceback (most recent call last):
...
AttributeError: 'Foo' object has no attribute 'not_an_attribute'
You can use pass any number of methods to delegate
>>> @delegate('v', 'upper', 'lower')
... class Foo:
... def __init__(self, v):
... self.v = v
'''
def dec(klass):
def create_delegator(method):
def delegator(self, *args, **kwargs):
obj = getattr(self, to)
m = getattr(obj, method)
return m(*args, **kwargs)
return delegator
for m in methods:
setattr(klass, m, create_delegator(m))
return klass
return dec
class DelegateTo:
'''
DelegateTo descriptor let you delegate method calls
The argument name is the name of the method that you want to
delegate, for example.
>>> class Foo:
... upper = DelegateTo('v')
... __len__ = DelegateTo('l')
... __iter__ = DelegateTo('l')
... def __init__(self, v, l):
... self.v = v
... self.l = l
>>> foo = Foo('hello world', [1, 2, 3])
To call a method just call its delegator
>>> foo.upper()
'HELLO WORLD'
Magic methods are supported
>>> len(foo)
3
>>> [x*2 for x in foo]
[2, 4, 6]
The method name is discovered at the first call. This
is done by iterating over all the object's attributes.
Once found the method is cached and no search is
performed in the subsequent calls.
Still, if you need to avoid this iteration you can initialize
the method name with the same name of the attibute name.
For example
>>> class Foo:
... upper = DelegateTo('v', 'upper')
... def __init__(self, v):
... self.v = v
Also is possible to use this to create aliases
>>> class Foo:
... up = DelegateTo('v', 'upper')
... def __init__(self, v):
... self.v = v
>>> Foo('hello').up()
'HELLO'
In this context 'self' has a special meaning of
delegating a method to another method in the same
object. For example
>>> class Foo:
... foo = DelegateTo('self', 'bar')
... def bar(self):
... return 'bar'
>>> Foo().foo()
'bar'
'''
def __init__(self, to, method=None):
if to == 'self' and method is None:
raise ValueError("DelegateTo('self') is invalid, "
"provide 'method' too")
self.to = to
self.method = method
def __get__(self, obj, objtype):
if self.to == 'self':
return getattr(obj, self.method)
if self.method is not None:
return getattr(getattr(obj, self.to), self.method)
for method, v in obj.__class__.__dict__.items():
if v is self:
self.method = method
return getattr(getattr(obj, self.to), method)
if __name__ == '__main__':
import doctest
doctest.testmod()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment