TL;DR
If you don't care about type hints, name them whatever you like. Otherwise, ABC
s are your API for other developers. Their names should be clean without any clutter. The concrete implementation should tell you why it is special.
Call your abstract class User
and the implementing classes ServerUser
and LocalUser
Why would we need such a convention in the first place?
The first one should ask themselves is, why would we care about naming of classes?
In statically typed languages (which python is not), the names of your Interfaces represent an API through which other developers interact with your code. So the caller of a function should quickly understand the meaning of parameters and return types. An example from Java (where prefixing Interfaces has fallen out of fashion some time ago..) would be:
public void signupUsers(Collection<User> users) {
// TODO: do something with users
}
Here we want the name to be as pure as possible. We accept any Collection
and any User
. It might be an ArrayList
, HashSet
, LocalUser
or a ServerUser
, we don't care.
Why is there no such standard in Python?
This was not relevant in python, since it is a dynamically typed language. In the following code, you don't care how the classes are called:
def signup_users(users):
pass # TODO: do something with users
The name would have only been relevant when creating a class and checking its type, which are not common enough to justify a full blown naming convention
Why would someone care about naming of classes in Python nowadays?
But things are changing, because of the introduction of the typing module. You can now explicitly state types. If we want to embrace this new feature, we should consider the naming of our classes.
So what names should our classes get?
By the same logic as to why other languages are ditching the prefixes for interfaces, I believe ABC
s (if they play a similar role to interfaces) should have a clean name. A name like User
is much nicer to read than IUser
. If you have a concrete implementation of your user, the name of that implementation should tell you what is special about it. Is it a user which data is updated with data from a server? Call it ServerUser
. Do you store the user data locally? Call it LocalUser
.
def signup_users(users: Iterable[User]) -> None:
pass # TODO: do something with users
The "ugly" code which cares about the implementation details would look like this:
lovelace = LocalUser("Aida", "Lovelace", 1815)
babbage = ServerUser("htts://some_service.com/users/charles_babbage")
signup_users([aida, babbage])
This puts the burden of dealing with convoluted names on the caller who has to deal with the implementation - as it should