How I Organise My Python Code

Pastor Emmanuel - May 29 - - Dev Community

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.

.