Class structure in Python
tl;dr: If it overrides some default or expected behavior, put it at the top.
General overview of a good python class
- Class level variables
- Other special Python method overrides (__str__, __eq__, etc.)
- superclass overrides
- instance methods
- private methods
Why is this an optimal structure for a python class?
- Class level variables
Class level variables come in one of usually two forms, constants and default instance variables. For the first (constants) they are important to be seen by a developer so that instead of using a magic number or string they will know which constants are available. It also helps when managing large number of constants by tying them to their related class. For the second (default instance variables) they’re important to be seen first as well because it usually means they’re overridden either by the initialization of the class or have some special meaning (like when defining fields on a Django model).
The initialization function of a class is the most important function since it actually constructs the class and will most likely be what developers call when wanting to interact with the class.
Properties are similar to instance variables on a class and can behave in different ways depending on if only a getter is defined or if a setter and/or deleter methods are defined. It’s important for these to go directly after the init because this will reduce confusion when needing to determine if a field is an actual instance variable or just a property.
4. Other special Python method overrides
Python allows for overriding internal methods, denoted by the double underscore or “dunderscore” to enable using complex objects in a similar way to “dumb” objects. Such as allowing us to add two dogs together in the same way we would add 2 integers together. These are important to have towards the top of the class because it changes the way a developer knows an object works and behaves.
5. Superclass overrides
When overriding methods of the super class, they make the subclass behave in a way that is different than originally intended. For example, let’s say a developer was familiar with the Animal class above and they expect the make_sound method to behave in a certain way. They see the Dog class inherits from it and then tries to use the make_sound method on a dog. However it does something completely unexpected. This is why it’s important to put these after the special python method overrides, so it’s clear what other behavior is being overridden.
6. Instance methods
After all the fun stuff is overridden, we come to the fun part of why object-oriented programming is so powerful. Instance methods are at the core of OOO and that’s why they should come next after all the default behavior is overridden.
Classmethods, like the name suggest, are tightly bound to the class however do not change an instance of the class. They can be used as alternate constructors or for other purposes that need some attributes on a class (like constants). Because of this tight coupling to the class, they should come after instance methods.
Staticmethods, while similar to classmethods, may be bound in some way to the class but not in a direct way. While it’s important for them to be seen, it’s ok for them to be more towards the bottom of the class.
9. Private methods
While Python doesn’t have true private methods, per se, the underscore in front of a method name indicates that it is “private” and should only be called within another method in the class. For this reason, they should be at the bottom of the class. For a developer, while it may be useful to see how these private methods work, shouldn’t need to see them immediately when looking at a class. In the scenario where a private method does need to be used, an instance method should be created that calls the private one.
Does it matter? Maybe, maybe not. I was interested in this topic and hadn’t seen any articles written on it in the Python community so I thought I’d give it a shot and see if I can start the discussion. I don’t think there’s necessarily a right or wrong answer for how to properly structure your classes in Python, but I hope this can at least be guide for those starting out with an empty class.
The most important thing I believe, when looking at a class, is that a developer should be able to determine how to properly interact with it at a quick glance.
While modern IDEs give us the ability to jump to method definitions, proper class structure can be especially useful when reviewing code changes outside of an IDE.