Welcome, guest | Sign In | My Account | Store | Cart

Chaining constructor and destructor calls together during object creation/destruction.

Python, 27 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def __init__(self):
    """Loop on all base classes, and invoke their constructors.
    Protect against diamond inheritance."""
    for base in self.__class__.__bases__:
        # Avoid problems with diamond inheritance.
        basekey = 'init_' + str(base)
        if not hassattr(self, basekey):
            setattr(self, basekey, 1)
        else:
            continue
        # Call this base class' constructor if it has one.
        if hasattr(base, "__init__"):
            base.__init__(self)

def __del__(self):
    """Loop on all base classes, and invoke their destructors.
    Protect against diamond inheritance."""
    for base in self.__class__.__bases__:
        # Avoid problems with diamond inheritance.
        basekey = 'del_' + str(base)
        if not hasattr(self, basekey):
            setattr(self, basekey, 1)
        else:
            continue
        # Call this base class' destructor if it has one.
        if hasattr(base, "__del__"):
            base.__del__(self)

Often, Python programmers hardcode calls to the parent's constructor and destructor. While some may prefer this, it arguably creates maintenance problems, since changing the base classes requires changes to these chaining calls. It is preferrable to have the base classes' constructors and destructors called generically, so that they never need be touched again. Since Python permits multiple inheritance, we must also protect against "diamond inheritance", as we don't wish to call any constructor or destructor twice.

3 comments

Alexander Semenov 21 years, 8 months ago  # | flag

incorrect behaviour - you must only worry about you direct ancestors. Why call constructors/destructors of grandancestors? Either it has been done by direct ancestors or this behavior is undesirable.

Michael Soulier (author) 21 years, 8 months ago  # | flag

Only direct ancestors are being called. Immediate ancestors are the only base classes being called. We must screen against diamond inheritance in the case of this technique being used by all classes up the inheritance chain. If it is not, it will still work, with only a small amount of additional bookkeeping that is unnecessary.

Unless you are suggesting that grand-ancestors should not be called in general in OOP, which is not strictly a Python question.

B T 20 years, 1 month ago  # | flag

Use issubclass? Would it be better to use issubclass instead of the setattr technique you are using? So you can call __init__ on all self.__class__.__bases__ except those which are superclasses of other bases?

I haven't tested this though:

def __init__(self): for base in self.__class__.__bases__: skip = False for b2 in self.__class__.__bases__ if (b2 is not base): if issubclass(b2,base): skip = True break if (not skip): try: base.__init__(self) except AttributeError: pass

Also it would be nice to perhaps create a metaclass that automatically chains certain methods transparently.

Created by Michael Soulier on Sat, 24 Aug 2002 (PSF)
Python recipes (4591)
Michael Soulier's recipes (3)

Required Modules

  • (none specified)

Other Information and Tasks