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
andLoggingService
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 usesuper
. 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, instantiatingUserService
and doing something likeself.user_service.validate_credentials
What would be the Pythonic way to implement the above code?
2 Answers 2
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
-
1I 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– Jules2016年05月23日 18:24:50 +00:00Commented 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– Aaron Hall2016年05月23日 18:34:25 +00:00Commented May 23, 2016 at 18:34
-
1The 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 theUser
class, but also the ones defined inUserService
andLoggingService
. 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– Jules2016年05月23日 18:41:00 +00:00Commented May 23, 2016 at 18:41 -
1Has-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– Aaron Hall2016年05月23日 21:14:14 +00:00Commented May 23, 2016 at 21:14
-
1Mixins 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– Aaron Hall2016年05月23日 22:41:53 +00:00Commented May 23, 2016 at 22:41
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.
Explore related questions
See similar questions with these tags.