9

Given that Python allows for multiple inheritance, what does idiomatic inheritance in Python look like?

In languages with single inheritance, like Java, inheritance would be used when you could say that one object "is-a" of an another object and you want to share code between the objects (from the parent object to the child object). For example, you could say that Dog is a Animal:

public class Animal {...}
public class Dog extends Animal {...}

But since Python supports multiple inheritance we can create an object by composing many other objects together. Consider the example below:

class UserService(object):
    def validate_credentials(self, username, password):
        # validate the user credentials are correct
        pass


class LoggingService(object):
    def log_error(self, error):
        # log an error
        pass


class User(UserService, LoggingService):
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def authenticate(self):
        if not super().validate_credentials(self.username, self.password):
            super().log_error('Invalid credentials supplied')
            return False
         return True

Is this an acceptable or good use of multiple inheritance in Python? Instead of saying inheritance is when one object "is-a" of another object, we create a User model composed of UserService and LoggingService.

All the logic for database or network operations can be kept separate from the User model by putting them in the UserService object and keep all the logic for logging in the LoggingService.

I see some problems with this approach are:

  • Does this create a God object? Since User inherits from, or is composed of, UserService and LoggingService is it really following the principle of single responsibility?
  • In order to access methods on a parent/next-in-line object (e.g., UserService.validate_credentials we have to use super. This makes it a little bit more difficult to see which object is going to handle this method and isn't as clear as, say, instantiating UserService and doing something like self.user_service.validate_credentials

What would be the Pythonic way to implement the above code?

0

2 Answers 2

9

Is Python's inheritance an “is-a” style of inheritance or a compositional style?

Python supports both styles. You are demonstrating the has-a relationship of composition, where a User has logging functionality from one source and credential validation from another source. The LoggingService and UserService bases are mixins: they provide functionality and are not intended to be instantiated by themselves.

By composing mixins in the type, you have a User that can Log, but who must add his own instantiation functionality.

This does not mean that one cannot stick to single inheritance. Python supports that too. If your ability to develop is hampered by the complexity of multiple inheritance, you can avoid it until you feel more comfortable or come to a point in design where you believe it to be worth the trade-off.

Does this create a God object?

The logging does seem somewhat tangential - Python does have its own logging module with a logger object, and the convention is that there is one logger per module.

But set the logging module aside. Perhaps this violates single-responsibility, but perhaps, in your specific context, it is crucial to defining a User. Descretizing responsibility can be controversial. But the broader principle is that Python allows the user to make the decision.

Is super less clear?

super is only necessary when you need to delegate to a parent in the Method Resolution Order (MRO) from inside of a function of the same name. Using it instead of hard-coding a call to the parent's method is a best-practice. But if you were not going to hard-code the parent, you don't need super.

In your example here, you only need to do self.validate_credentials. self is not more clear, from your perspective. They both follow the MRO. I would simply use each where appropriate.

If you had called authenticate, validate_credentials instead, you would have needed to use super (or hard-code the parent) to avoid a recursion error.

Alternative Code suggestion

So, assuming the semantics are OK (like logging), what I would do is, in class User:

    def validate_credentials(self): # changed from "authenticate" to 
                                    # demonstrate need for super
        if not super().validate_credentials(self.username, self.password):
            # just use self on next call, no need for super:
            self.log_error('Invalid credentials supplied') 
            return False
        return True
9
  • 1
    I disagree. Inheritance always creates a copy of the public interface of a class in its subclasses. This isn't a "has-a" relationship. This is subtyping, plain and simple, and therefore is inappropriate to the described application.
    – Jules
    May 23, 2016 at 18:24
  • 1
    @Jules What do you disagree with? I said a lot of things that are demonstrable, and made conclusions that logically follow. You are incorrect when you say, "Inheritance always creates a copy of the public interface of a class in its subclasses." In Python, there is no copy - the methods are looked up dynamically according to the C3 algorithm method resolution order (MRO).
    – Aaron Hall
    May 23, 2016 at 18:34
  • 1
    The point isn't about specific details of how the implementation works, it's about what the public interface of the class looks like. In the case of the example, User objects have in their interfaces not only the members defined in the User class, but also the ones defined in UserService and LoggingService. This isn't a "has-a" relationship, because the public interface is copied (albeit not via direct copying, but rather by an indirect lookup to the superclasses' interfaces).
    – Jules
    May 23, 2016 at 18:41
  • 1
    Has-a means Composition. Mixins are a form of Composition. The User class is not a UserService or LoggingService, but it has that functionality. I think Python's inheritance is more different from Java's than you realize.
    – Aaron Hall
    May 23, 2016 at 21:14
  • 1
    Mixins are a special case of inheritance that provides for composition. "is-a" would be the complementing cases of inheritance that would require an abstract base class or a base class that one could fully instantiate. This is internally consistent. Whether it is helpful to engage in these sorts of semantic hair-splitting is another matter entirely. What I want to emphasize is that composition by delegation is not the only way to do it.
    – Aaron Hall
    May 23, 2016 at 22:41
-1

Other than the fact that it allows multiple superclasses, Python's inheritance is not substantially different to Java's, i.e. members of a subclass are also members of each of their supertypes[1]. The fact that Python uses duck-typing makes no difference either: your subclass has all of the members of its superclasses, so is able to be used by any code that could use those subclasses. The fact that multiple inheritance is effectively implemented by using composition is a red herring: that automated copying of the properties of one class to another is the issue, and it doesn't matter whether it uses composition or just magically guesses how the members are supposed to work: having them is wrong.

Yes, this violates single responsibility, because you're giving your objects the ability to perform actions that are not logically part of what they're designed to do. Yes, it creates "god" objects, which is essentially another way of saying the same thing.

When designing object-oriented systems in Python, the same maxim that is preached by Java design books also applies: prefer composition to inheritance. The same is true of (most[2]) other systems with multiple inheritance.

[1]: you could call that an "is-a" relationship, although I don't personally like the term because it suggests the idea of modelling the real world, and object oriented modelling is not the same as the real world.

[2]: I'm not so sure about C++. C++ supports "private inheritance" which is essentially composition without needing to specify a field name when you want to use the inherited class's public members. It doesn't affect the public interface of the class at all. I don't like using it, but I can't see any good reason not to.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.