1

I would like to create a class so that code is executed both before and after the __init__ of subclasses (defined by users of my class). I don't want to mandate to those users that they have to explicitly call superclass methods; it should happen automatically.

Here is the solution I have come up with, using __init_subclass__:

# my class
class C:
    def __init_subclass__(cls):
        cls.__old_init__ = cls.__init__
        cls.__init__ = cls.__init_wrapper__
        cls.__init__.__doc__ = cls.__old_init__.__doc__

    def __init_wrapper__(self, *args, **kwargs):
        print('before __init__')
        self.__old_init__(*args, **kwargs)
        print('after __init__')

# user's class
class D (C):
    def __init__(self, x):
        "init D object"

This seems to work.

What I don't like about this solution is that when I call help(D), all my awkward machinery is exposed:

Help on class D in module __main__:

class D(C)
 |  D(*args, **kwargs)
 |  
 |  Method resolution order:
 |      D
 |      C
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  __init__ = __init_wrapper__(self, *args, **kwargs)
 |  
 |  __old_init__ = __init__(self, x)
 |      init D object
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from C:
 |  
 |  __init_wrapper__(self, *args, **kwargs)
 |      init D object
 |  
 |  ----------------------------------------------------------------------
 |  Class methods inherited from C:
 |  
 |  __init_subclass__() from builtins.type
 |      This method is called when a class is subclassed.
 |      
 |      The default implementation does nothing. It may be
 |      overridden to extend subclasses.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from C:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Moreover, the copied docstring shows up on __init_wrapper__ (and of course __old_init__) but not on the patched __init__, and the call signature of __init__ = __init_wrapper__ is uninformative.

Is there a better way?

A. Donda
  • 8,381
  • 2
  • 20
  • 49
  • "Is there a better way?" Tough to know without knowing your use-case - what are you hoping to achieve? What's your ideal output from `help()`? – Cameron McFee Mar 13 '21 at 00:47
  • @CameronMcFee, I explained in my answer: As a minimum, `__init__` should be listed in `help()` with the relevant docstring and call signature. Optimally, `__old_init__`, `__init_wrapper__`, and `__init_subclass__()` shouldn't show up at all. I think that isn't possible with a wrapper-based approach, so I'm asking whether there is another one. – A. Donda Mar 13 '21 at 00:50
  • 1
    The place you probably want to start is with `type` - that will let you set the class dictionary and you can set the `__doc__` parameters there. Overload `__new__` and return a new class that you define programmatically with `type` lets you control the class dictionary which is what `help()` is introspecting. See https://stackoverflow.com/questions/4056983/how-do-i-programmatically-set-the-docstring – Cameron McFee Mar 13 '21 at 00:55
  • This is going to break any sub-subclasses. – user2357112 Mar 13 '21 at 01:16
  • @user2357112supportsMonica care to explain why & how? – A. Donda Mar 13 '21 at 16:17
  • @A.Donda: [Infinite recursion bugs](https://ideone.com/9EGMBn) from calling the wrong `__init__` methods. – user2357112 Mar 14 '21 at 01:47

0 Answers0