DEV Community

Cover image for How I Organise My Python Code
Pastor Emmanuel
Pastor Emmanuel

Posted on • Edited on

How I Organise My Python Code

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

  1. Magic Methods
  2. Private Methods
  3. Public Methods
  4. 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})>"
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
ebcefeti profile image
E. B. Cefeti

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.

Collapse
 
pastorenuel profile image
Pastor Emmanuel

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 !

Collapse
 
sein_digital profile image
Błażej Adamczyk

Nice post! That's pretty neat approach. I wonder how would you organize your project directory tree, and app architecture :) Care to share?

Collapse
 
pastorenuel profile image
Pastor Emmanuel

Thank you Blazej for your comment.

Sure, I will create a subsequent discussing project organisation.

cheers mate !

Collapse
 
incrementis profile image
Akin C.

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.

Collapse
 
pastorenuel profile image
Pastor Emmanuel

Thank you Akin. Glad you find it insightful 😊

Collapse
 
linkbenjamin profile image
Ben Link

Thanks for this, good explanation and easy to see how you can organize your thoughts!

Collapse
 
pastorenuel profile image
Pastor Emmanuel

I'm glad you find it helpful Ben.

Collapse
 
noscrubs profile image
No Scrubs

amazing post

Collapse
 
pastorenuel profile image
Pastor Emmanuel

Thank you for the comment

Collapse
 
edward_thomas profile image
Edward Thomas

Nice, I like the public methods.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.