New update added to the private methods.
Writing code is more of an art than a science, I must opine. It's important to write well-refined, logical, and robust engineering solutions to a problem. However, there seems to be a major challenge: making these solutions comprehensive and readable.
The focus of this article is how to best organise your Python classes to be both readable and clean. Python doesn't enforce strict rules when it comes to method organisation; for instance, you won't be penalised if you decide to put your __init__
method at the end of your class or if it comes first. However, adhering to widely accepted conventions not only makes collaboration easier but also fosters better documentation.
Order Methods Should Follow
- Magic Methods
- Private Methods
- Public Methods
- Helper Methods for Your Public Methods
1. Magic Methods
Magic methods, also known as dunder methods (due to their double underscores), provide special functionalities to your classes. These methods are predefined and allow objects to interact with Python’s built-in functions and operators. Placing magic methods at the top of your class definition makes it immediately clear how instances of the class will behave in common operations.
Example:
class ClassWithMagicMethod:
"""Example class"""
def __new__(self):
pass
def __init__(self, value: int):
"""This is the init method"""
self.value =value
def __str__(self):
return str(self.value)
def __repr__(self):
return f"<ExampleClass({self.value})>"
Notice that the __new__
method comes before the __init__
method? Its best practice to do it that way for proper and logical flow of your codebase
2. Private Methods
Private methods are intended for internal use within the class. These methods usually start with an dunderscore (__) and should be placed after the magic methods. Organising private methods together helps maintain a clear separation between the internal mechanisms of the class and the public interface.
Example:
class ClassWithPrivateMethod:
# magic methods goes here...
def __private_method(self):
# This is a private method
print("this is private method")
Attempting to access the __private_method
outside the class will raise this error:
c = ClassWithPrivateMethod()
c.__private_method()
--------------output-----------
ERROR!
Traceback (most recent call last):
File "<main.py>", line 12, in <module>
AttributeError: 'ClassWithPrivateMethod' object has no attribute '__private_method'
However, it can be accessed within the class:
class ClassWithPrivateMethod:
# magic methods goes here...
def __private_method(self):
# This is a private method
print("this is private method")
def another_method(self):
"""Method that accesses the private method"""
self.__private_method()
c = ClassWithPrivateMethod()
c.another_method()
------------output----------
this is private method
Though this method is considered private, Python still makes it possible to access it by using creating a helper method using the syntax: f"_{__class__.__name__}__{<method_name>}"
. Which now works.
c = ClassWithPrivateMethod()
c._ClassWithPrivateMethod__private_method()
-------------output---------------
this is private method
You will notice that Python created a helper method to access the private method; suggesting a syntax or pattern on how this should be used or accessed.
What about in the case of inheritance. It is pertinent to note that you cannot directly call a base class private method from a subclass. If you attempt to call __private_method
of ClassWithPrivateMethod
within ClassWithPrivateMethodSubClass
, Python will raise an AttributeError
because it cannot find the mangled name; _ClassWithPrivateMethodSubClass__private_method
in ClassWithPrivateMethodSubClass
but you will notice that _ClassWithPrivateMethod__private_method
is present. This means one thing, python creates the mangled name before accessing the value of the private method.
Example:
class ClassWithPrivateMethodSubClass(ClassWithPrivateMethod):
"""subclass of ClassWithPrivateMethod"""
pass
def method_to_access_private_method(self):
"""method attempting to access base"""
self.__private_method()
sc = ClassWithPrivateMethodSubClass()
sc.method_to_access_private_method()
-------------output-----------------
AttributeError: 'ClassWithPrivateMethodSubClass' object has no attribute '_ClassWithPrivateMethodSubClass__private_method'
To navigate through this, you can create a protected or helper method in the base class which can be accessed by the subclass.
Example:
class ClassWithPrivateMethod:
...
def _protected_method_for_private_method(self):
return self.__private_method()
def method_to_access_private_method(self):
"""method attempting to access base"""
self.__private_method()
class ClassWithPrivateMethodSubClass(ClassWithPrivateMethod):
...
def method_to_access_private_method(self):
return self._protected_method_for_private_method()
sc = ClassWithPrivateMethodSubClass()
sc.method_to_access_private_method()
----------------output-------------------------
this is private method
3. Public Methods
Public methods form the main interface of your class. These methods should be clearly defined and well-documented since they are intended for use by other classes or modules. Keeping public methods organised and distinct from private methods enhances readability and usability.
Example:
class ClassWithPublicMethod:
# magic methods go here...
# private methods next...
def public_method(self):
# This is a public method
return self.value
4. Helper Methods for Your Public Methods
Helper methods or supporting methods, which assist public methods in performing their tasks, should be defined immediately after the public methods they support. These methods often break down complex tasks into simpler, manageable pieces, promoting code reusability and clarity.
Example:
class ClassWithHelperMethod:
# All previous methods here...
def public_method(self):
# This is a public method
return self._helper_method()
def _helper_method(self):
# This is a helper method for the public method
return self.value * 2
def public_method_2(self):
"""This is another method"""
pass
Conclusion
By organising your methods in the suggested order —magic methods, private methods, public methods, and helper methods —you create a clear and readable structure for your Python classes. This approach not only facilitates easier collaboration and maintenance but also helps in creating a well-documented codebase that others can understand and use effectively. Which will also improve onboarding if you work in an organization. Remember, clean code is not just about making it work; it’s about making it understandable and maintainable for the long run.
Top comments (11)
I feel like you might be over-buying into the OO Python approach a bit? A lot of these are features that would be better communicated with a combination of docstrings, type hints, etc., rather than overloading internal representations.
Hi Cefeti,
Thank you for your comment.
Maybe you missed the overall goal of the post. You don't have to overload the internal representations. But in the case where you do, following the order expressed, helps your code to be better organised. The focus of this post is more around organising your python class methods. We will discuss writing better docstrings and type hinting in subsequent posts. I hope this clears your concerns
cheers !
Nice post! That's pretty neat approach. I wonder how would you organize your project directory tree, and app architecture :) Care to share?
Thank you Blazej for your comment.
Sure, I will create a subsequent discussing project organisation.
cheers mate !
Hello Pastor Emmanuel,
thank you for your article.
I've been programming with Python quite a bit lately.
Luckily, at this point in my usage, I have yet to program any classes in Python, but it is still quite insightful to see how this can be achieved in a clean manner.
Thank you Akin. Glad you find it insightful 😊
Thanks for this, good explanation and easy to see how you can organize your thoughts!
I'm glad you find it helpful Ben.
amazing post
Thank you for the comment
Nice, I like the public methods.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.