How I Organise My Python Code
Tags: python, webdev, cleancode
Published: 2024-05-29 16:54:23

_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:

```python
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:

```python
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:

```python
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:

```python
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.

```python
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:

```python
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:

```python
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:

```python
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:

```python
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.