0

I have a few class hierarchies with a common method, where I want the subclasses to call the parent method before executing the subclassed method. But when writing the root classes, obviously there's no parent method to call.

It's not a big deal to add super().f(...) to the subclass methods, but it's annoying that I sometimes forget that a class is not a root class and miss that until I trace the calls to find where the call chain stops. Is there a decorator that will do that automatically?

I tried this:

def extend_super(orig_method):
  def decorated(*args, **kwargs):
    if len(args) >= 1:
      selfarg = args[0]
      selfarg_qualname = selfarg.__class__.__qualname__
      expect_qualname = f"{selfarg_qualname}.{orig_method.__name__}"
      if expect_qualname == orig_method.__qualname__:
        try:
          super_method = getattr(super(type(selfarg), selfarg), orig_method.__name__)
          super_method.__func__(*args, **kwargs)
        except AttributeError:
          pass
    return orig_method(*args, **kwargs)
  return decorated

It works - it will call the parent method, stop at the root, and will even tolerate decorating functions if you do that accidentally. But it seems awkward since it uses string operations to work. Is there an existing decorator that will do what I want, or is there a better way to make this one?

Edit:

Second edit to add that this doesn't work: explanation below.

I wanted to move the overhead of finding any parent class method out of the function call itself. I looked at gc to get a list of all references to the function passed to the decorator, but there are no classes in that list. This is because the decorator will always be called before the class has been created, so there is no class to find.

I found an example of a Python decorator which counts the number of times it's called. It does this by adding a field to the decorated function itself, and storing the count there (https://python-course.eu/advanced-python/decorators-decoration.php).

So I figured the decorator could find the superclass method the first time it's called, and save it. That part would have to be done once whatever method was used, but all subsequent calls would just used the saved method, so it can now be used efficiently. This is what it looks like:

def extend_super(orig_method):
  def decorated(*args, **kwargs):
    if not decorated.checked_super_method:
      decorated.checked_super_method = True
      if len(args) >= 1:
        selfarg = args[0]
        selfarg_qualname = selfarg.__class__.__qualname__
        expect_qualname = f"{selfarg_qualname}.{orig_method.__name__}"
        if expect_qualname == orig_method.__qualname__:
          super_class = super(type(selfarg), selfarg)
          try:
            decorated.super_method = getattr(super_class, orig_method.__name__)
          except AttributeError:
            pass

    if decorated.super_method is not None:
      decorated.super_method.__func__(*args, **kwargs)

    return orig_method(*args, **kwargs)

  decorated.checked_super_method = False
  decorated.super_method = None

  return decorated

I wasn't sure about adding attributes to a function, but it looks like that ability was added intentionally. Caching the result of an operation is an example use for it, which is what I'm doing here, so I'm satisfied with this.

Edit:

This doesn't work above the very lowest level, because the self arg will always be the class of the first call. super() will find the level above, and the code will call that, but that call will still have the original self arg, and super() will find the same level as before. This will repeat until a recursion level is reached.

A real solution might be able to manually take the original selfarg.__class__.__bases__ list and search it until the class of the current function call is found, or there are no more parent classes (s.__class__.__bases__[0] == type), but that is getting to be a lot more work than I want to do, to simplify something pretty minor that I thought would be easy.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
John Bayko
  • 746
  • 4
  • 7
  • I'm not sure how it's easier to apply a decorator than to put `super.f()` in the body of the method. You mention forgetting the latter, but isn't there also a risk of forgetting to apply the decorator? – Blckknght Aug 08 '23 at 03:50
  • @Blckknght It's mainly a matter of no longer having to remember which methods to add the parent call to and which to skip, I can just decorate them all and it gets figured out for me. Also learning decorators, but that's a side point. – John Bayko Aug 08 '23 at 03:56
  • I found this, which seems related: https://stackoverflow.com/questions/25921450/given-a-method-how-do-i-return-the-class-it-belongs-to-in-python-3-3-onward – John Bayko Aug 15 '23 at 02:55

0 Answers0