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