Skip to content

Instantly share code, notes, and snippets.

@RWJMurphy
Created February 10, 2017 16:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RWJMurphy/8606642bd1af2011fa47898b882b0620 to your computer and use it in GitHub Desktop.
Save RWJMurphy/8606642bd1af2011fa47898b882b0620 to your computer and use it in GitHub Desktop.
python delegate class decorator
"""A class decorator to enable classes to delegate to an attribute."""
def delegate(delegate_class, delegate_name):
"""
Delegating class decorator.
.. code-block:: python
class Location():
def __init__(self, x, y):
self.x = x
self.y = y
def move(self, dx, dy):
self.x += dx
self.y += dy
@delegate(Location, 'location')
class Mob():
def __init__(self, name, location):
self.name = name
self.location = location
def __str__(self):
return "A {name}, at {x},{y}".format(
name=self.name, x=self.x, y=self.y)
mob = Mob('rat', Location(10, 20))
print(mob)
# A rat, at 10,20
mob.move(1, 1)
print(mob)
# A rat, at 11,21
:param delegate_class: the class of the delegate
:type delegate_class: type
:param delegate_name: name of the attribute to delegate to
:type delegate_name: str
"""
def delegate_property(delegate_name, attr_name, doc=None):
"""
A :class:`property` for delegating to an attribute of `self`.
:type delegate_name: str
:type attr_name: str
:type doc: str or None
"""
return property(
lambda self: getattr(getattr(self, delegate_name), attr_name),
lambda self, value: setattr(
getattr(self, delegate_name), attr_name, value),
lambda self: delattr(getattr(self, delegate_name), attr_name),
doc=doc)
def with_delegating_attributes(parent_class):
"""
Add delegating attributes to a class.
:param parent_class: class to decorate
:type parent_class: type
"""
old_getattr = getattr(parent_class, '__getattr__', None)
old_delattr = parent_class.__delattr__
old_dir = parent_class.__dir__
def __getattr__(self, name):
if hasattr(
getattr(self,
delegate_name), name) and not name.startswith('_'):
# [maniacal laughter]
setattr(self.__class__, name, delegate_property(delegate_name,
name))
return getattr(self, name)
elif old_getattr:
return old_getattr(self, name)
else:
raise AttributeError(
"'{cls}' object has no attribute {name!r}".format(
cls=self.__class__.__name__,
name=name))
def __delattr__(self, name):
if hasattr(
getattr(self,
delegate_name), name) and not name.startswith('_'):
delattr(getattr(self, delegate_name), name)
else:
old_delattr(self, name)
def __dir__(self):
return super(object, self).__dir__() + [
attr for attr in old_dir(self) if not attr.startswith('_')
]
parent_class.__getattr__ = __getattr__
parent_class.__delattr__ = __delattr__
parent_class.__dir__ = __dir__
for name in dir(delegate_class):
if hasattr(parent_class, name) or name.startswith('_'):
continue
setattr(parent_class, name,
delegate_property(delegate_name, name,
getattr(delegate_class, name).__doc__))
return parent_class
return with_delegating_attributes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment