Inheritance can make debugging difficult because when you invoke a method on an object, it might be hard to figure out which method will be invoked.
Suppose you are writing a function that works with Hand objects. You would like it to work with all kinds of Hands, like PokerHands, BridgeHands, etc. If you invoke a method like
shuffle, you might get the one defined in
Deck, but if any of the subclasses override this method, you’ll get that version instead. This behavior is usually a good thing, but it can be confusing.
Any time you are unsure about the flow of execution through your program, the simplest solution is to add print statements at the beginning of the relevant methods. If
Deck.shuffle prints a message that says something like
Running Deck.shuffle, then as the program runs it traces the flow of execution.
As an alternative, you could use this function, which takes an object and a method name (as a string) and returns the class that provides the definition of the method:
def find_defining_class(obj, meth_name): for ty in type(obj).mro(): if meth_name in ty.__dict__: return ty
Here’s an example:
>>> hand = Hand() >>> find_defining_class(hand, 'shuffle') <class '__main__.Deck'>
shuffle method for this Hand is the one in
find_defining_class uses the
mro method to get the list of class objects (types) that will be searched for methods. “MRO” stands for “method resolution order”, which is the sequence of classes Python searches to “resolve” a method name.
Here’s a design suggestion: when you override a method, the interface of the new method should be the same as the old. It should take the same parameters, return the same type, and obey the same preconditions and postconditions. If you follow this rule, you will find that any function designed to work with an instance of a parent class, like a Deck, will also work with instances of child classes like a Hand and PokerHand.
If you violate this rule, which is called the “Liskov substitution principle”, your code will collapse like (sorry) a house of cards.