Cooperative class tree filtering

Ian Kelly ian.g.kelly at gmail.com
Mon Oct 16 22:20:40 EDT 2017


On Thu, Oct 12, 2017 at 5:07 PM, Alberto Berti <alberto at metapensiero.it> wrote:
> Now, what i ideally want is to get rid of that super() call at the end of
> the methods in classes B and c and to code that "fallback to what my
> superclass says" coded into A's implementation, where it kicks in if the
> method in the target instance returns None. So to write it in code, I
> would like some like:
>
>
> [SNIP]
>
>
> I've tried some variants of the 'super()' trick, that sometimes seems to
> change behavior if it's written like that or like super(type(self),
> self) in no clear (to me, and I failed to find extensive doc on
> super()'s behavior) way, with things that stop working if mixins are
> involved (even if the mixins do not reimplement the methods involved
> here). Eventually i ended implementing a decorator:
>
>   from functools import partial, wraps
>
>
>   class delegate_super:
>       """A method decorator that delegates part of the computation to the same
>       method on the ancestor class."""
>
>       _name = None
>       _owner = None
>
>       def __init__(self, meth):
>           self._meth = meth
>           @wraps(meth)
>           def wrapper(*args, **kwargs):
>               return self.delegator(*args, **kwargs)
>           self.wrapper = wrapper
>
>       def __get__(self, instance, owner):
>           return partial(self.wrapper, instance)
>
>       def __set_name__(self, owner, name):
>           self._owner = owner
>           self._name = name
>
>       def delegator(self, instance, *args, **kwargs):
>           result = self._meth(instance, *args, **kwargs)
>           if result is None:
>               result = getattr(super(self._owner, instance), self._name)(
>                   *args, **kwargs)
>           return result
>
>   class A:
>       def filter(self, element):
>           # the default implementation always returns True
>           return True
>
>
>   class B(A):
>       @delegate_super
>       def filter(self, element):
>           if element == 'foo':
>               return True
>           elif element == 'bar':
>               return False
>
>
>   class C(B):
>       @delegate_super
>       def filter(self, element):
>           if element == 'bar':
>               return True
>           else:
>               return super().filter(element)
>
>
>   def collect_elements(instance):
>       "A semplified element collect function"
>       all_elts = {'foo', 'bar', 'baz', 'zoo'}
>       filtered_elts = set(el for el in all_elts if instance.filter(el))
>       return filtered_elts


My initial reaction is: is this really worth it? This seems like an
awful lot of code and added complexity in order to do away with two
lines. It's a lot easier to reason about "return
super().filter(element)" and verify that it does the right thing than
for the complicated descriptor above.


> I would really like to find a way to do this that doesn't involve
> decorating the methods in A subclasses to free the final developer to
> remember to import the decorator and apply it, just like I don't want
> him (probably me six months from now) to have to remember to add an
> `else: super()...` to its computation...
>
> Has anyone done this before and has any suggestion about a better way to
> do it? Am I getting it wrong?


If you don't want an explicit super() call and you don't want the
decorator to be explicit either then most likely what you need is a
metaclass that will automatically wrap specific methods with your
decorator. Metaclasses are inherited, so all you have to do is set it
for A and it will automatically apply itselt to B and C as well.



More information about the Python-list mailing list