Skip to content

Instantly share code, notes, and snippets.

@XoseLluis
Created August 6, 2023 09:18
Show Gist options
  • Save XoseLluis/f902cb16752439c8947bd1d9137a2ef8 to your computer and use it in GitHub Desktop.
Save XoseLluis/f902cb16752439c8947bd1d9137a2ef8 to your computer and use it in GitHub Desktop.
Delegation in Python
from typing import Callable
import inspect
from typing import Protocol
from abc import abstractmethod
def _create_method(to_method_name: str, to_attr_name: str):
# returns a closure that traps the name of the method to invoke and the attribute_name of the object that will act as receiver-self
def new_method(self, *args, **kargs):
inner_self = getattr(self, to_attr_name)
to_method = getattr(inner_self, to_method_name) # bound method (to inner_self)
return to_method(*args, **kargs)
return new_method
# decorator with parameters, so it has to return a function (that will be invoked with the class being decorated)
# we don't create a new class, we add functions to the existing class and return it
def delegate_methods(method_names: list[str], to_attr_name: str):
# decorator with parameters
# receives a list of method names to create and delegate from them to the corresponding method in the object indicated by to_attr_name
def add_methods(cls):
for method_name in method_names:
setattr(cls, method_name, _create_method(method_name, to_attr_name))
return cls
return add_methods
def delegate_interface(interface_like, to_attr_name: str):
# decorator with parameters
# receives an "interface" for which methods we will create "delegator methods" to delegate from them to the corresponding method in the object indicated by to_attr_name
def add_methods(cls):
method_names = [name for name, func in inspect.getmembers(interface_like, predicate=inspect.isfunction) if name != "__init__"]
for method_name in method_names:
setattr(cls, method_name, _create_method(method_name, to_attr_name))
return cls
return add_methods
#########################################
print("- Testing delegate_methods")
class Formatter:
def short_format(self, txt: str, prepend: str):
return f"{prepend}{txt}"
def long_format(self, txt: str, wrap: str):
return f"{wrap}{txt}{wrap}"
@delegate_methods(["short_format", "long_format"], "formatter")
class TextHelper:
def __init__(self, id_, formatter):
self.id = id_
self.formatter = formatter
def beautify(self, txt) -> str:
return f"beautifying {self}"
helper = TextHelper("aa", Formatter())
print(helper.long_format("hi", "||"))
print(helper.short_format("hi", "||"))
print("##################################")
print("- Testing delegate_interface")
class Formattable(Protocol):
@abstractmethod
def short_format(self, txt: str, prepend: str):
pass
@abstractmethod
def long_format(self, txt: str, wrap: str):
pass
class FormattableImp(Formattable):
def short_format(self, txt: str, prepend: str):
return f"{prepend}{txt}"
def long_format(self, txt: str, wrap: str):
return f"{wrap}{txt}{wrap}"
@delegate_interface(Formattable, "formatter")
class TextHelper2:
def __init__(self, id_, formatter):
self.id = id_
self.formatter = formatter
def beautify(self, txt) -> str:
return f"beautifying {self}"
helper = TextHelper2("aa", FormattableImp())
print(helper.long_format("hi", "||"))
print(helper.short_format("hi", "||"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment