Practical OOP in Python: Methods

Practical OOP in Python: Methods

Understanding the practical applications and most suited use-case of methods

ยท

7 min read

The class is the backbone of OOP in Python and methods are body parts of the class. Understanding the practical application of the methods is the key to take the most advantage of the class and eventually OOP in Python.

Let's touch on some theory in brief as this blog focuses on practicality

1. Brief theory

class: A class is a user-defined blueprint or prototype from which objects are created. It wraps all the similar methods (ideally by conventions).

methods: A glorified function that is a class member and will always remain bound to a class.

If you wish to understand the theory, then I would suggest you go through

Types of methods available in Python:

  1. True method:
    1. instance method
    2. class method
    3. static method
    4. property (not everyone will agree)
  2. Method by conventions (this is what something I have derived)
    1. private method
    2. strict private method

2. Practical use case

The beauty of OOPs in any programming language (that has its support obviously ๐Ÿ˜Ž) is the flexibility. There will be always more than one way to do any task at hand, but not all are best practice or practical or pythonic.

Note: I will only touch the syntax/theory part briefly with an assumption that the reader knows the theory part but to see their real-world practical use-case.

Let's begin by writing a sample class that will be used across the complete blog

@dataclass
class SampleClass:
    """This is a sample class to explain practical use-case of all methods
    """    
    xyz: int
    abc: int = 4

    def ins_method(self, no: int):
        """Sample instance method
        Args:
            no (int): any int number 
        """        
        print(self.xyz * no)

    @classmethod
    def cls_method(cls, no: int):
        """Sample class method
        Args:
            no (int): any int number 
        """
        print(cls.abc * no)

    @staticmethod
    def stc_method(no: int):
        """Sample static method
        Args:
            no (int): any int number 
        """
        print(SampleClass.xyz * no)

Now we'll see their practical use case one by one.

2.1 instance method

  • The plain, simple, regular method with any frills.
  • Practically speaking, this type of method is used 90% of the time. In fact, just using the instance method is what you are going to need all the time.
  • It has free access to all attributes & even other methods at the same object level. Due to this flexibility it is most widely used.
  • From the above SampleClass example ins_method() is the instance method.
@dataclass
class SampleClass:
    xyz: int
    abc: int = 4

    def ins_method(self, no: int):      
        print(self.xyz * no)

s = SampleClass(xyz=10)
s.ins_method(2)

# Output
20
  • Some key point to note here:
    • As instance method are bound to an object of the class, so first an object must be created.
    • The same object then can be used anywhere.

Factory function: Before moving to the next part, it is important to understand factory function) as this is one use-case where classmethod and staticmethod have wide practical application. Again here I will be focussing on practical.

2.2 classmethod

  • First, get this, classmethod is not a must have/use in Python OOP. Almost always plain instance method will do the job. But there is one use-case where it fits perfectly, i.e. if you want to use the factory function for accessing class attribute.
  • Unlike the instance method where we need to create an object of class first & then use the '.' notation to use it, classmethod can be used without creating an object as it is bound to the class itself and not to the object. Sound too technical let's see an example.
@dataclass
class SampleClass:
    xyz: int
    abc: int = 4

    @classmethod
    def cls_method(cls, no: int):
        print(cls.abc * no)

SampleClass(4).cls_method(2)

# Output
8
  • As you can see above we have not created any object for SampleClass but directly used '.' notation with 'SampleClass' itself as classmethod can be bound to class directly. If you compare it with the instance method, there we created an object s then the method ins_method() was being accessed as it is bound to s but not to Sampleclass
  • So what is the benefit of all this, not a lot but there are a few:
    • The one obvious, use it as a factory function where you don't want to specifically create an object of the class. Why? Maybe you fear the object size will be too large & you only want to use a specific method.
    • It is a way to tell your other fellow team member or other people that this method doesn't depend on the instance variable (value provided by the user) but on the class variable to which the user doesn't have access.
@dataclass
class SampleClass:
    xyz: int #This is instance variable
    abc: int = 4 # This is class variable

    @classmethod
    def cls_method(cls, no: int):
        print(cls.abc * no)

# Traditional way
class SampleClass:
    abc: int = 4 #This is class variable

    def __init__(self, xyz):
        self.xyz = xyz # This is instance variable

image.png

  • Here cls_method() only have access to abc which is class variable

image.png

  • Here ins_method() have access to everything.
  • A method that needs to use a class variable as well as user input.
@dataclass
class SampleClass:
    xyz: int
    abc: int = 4

    @classmethod
    def cls_method(cls, no: int):
        print(cls.abc * no)

SampleClass(4).cls_method(2) # Here cls_methos is using class variable - 'abc' as well as user input - 'no'

# Output
8

2.3 staticmethod

  • Similar to the 'classmethod' even 'staticmethod' is not a must have/use in Python OOP. In fact, its usage is even less than classmethod. Some dev argues that it is totally useless, see here.
  • It shares almost all properties from classmethod except access to neither class variable nor to instance variable as it does not use either self or cls.
  • Like the classmethod it can be used as a factory function, but should not. Because
    • We already have classmethod for it.
    • classmethod will work perfectly even if it is not using a class variable.
  • So where should it be used? There are no true practical use-case of it and can be skipped entirely. But I do use them in some rare occurrence or edge case, When you have some function outside the scope of a class but feel it is tightly related to the class, it can be included as staticmethod as it does not need any class/instance variable.
    • You want to mimic the private method which doesn't need a class/instance variable. But I would advise instead use a private method (more on this later).

2.4 property

  • This is a special type of methods, in fact formally it falls under descriptor. Follow this excellent blog if you want to understand the theory part

  • @property should be used when you want to return an attribute produced by some function/method.

  • As the name suggests the property object should always hold some kind of characteristic of the class. Python's builtin pathlib library is one of the best examples.

2.5 private method

  • It is a method to which external users do not have access & it is just used internally.
  • Python doesn't have a true private method (like in java). But we can implement it using generally agreed conventions.
  • The convention is to add '_' as a prefix to the instance method name. By seeing this name convention, the user will understand that this particular should not be touched.
@dataclass
class SampleClass:
    xyz: int
    abc: int = 4

    def ins_method(self, no: int):
        print(self._pvt_method(no) * no)

    def _pvt_method(self, no: int):
        return (self.abc * no)
  • But as mentioned earlier that Python doesn't have a true private method, so the user can still use it. Someone who doesn't the name convention might accidentally use it.

    sc = SampleClass(4)
    sc._pvt_method(4)
    
    # Output
    16
    

2.6 strict private method

  • You might not find this term anywhere formally. This is somewhat I have coined. As above mentioned limitation of the private method in python, if you want to enforce it we can hack name mangling feature of python by adding '__' or double underscore as a prefix to the instance method name.
@dataclass
class SampleClass:
    xyz: int
    abc: int = 4

    def ins_method(self, no: int):
        return (self.__strict_pvt_method(no) * no)

    def _pvt_method(self, no: int):
        return (self.abc * no)

    def __strict_pvt_method(self, no: int):
        return (self.abc * no)

# Output
sc.ins_method(4)
64
sc._pvt_method(4)
16
sc.__strict_pvt_method(4)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-31-102204654d54> in <module>
----> 1 sc.__strict_pvt_method(4)

AttributeError: 'SampleClass' object has no attribute '__strict_pvt_method'
  • Though it strict but not entirely 100% enforced rule as it can be still used by name mangling syntax
sc._SampleClass__strict_pvt_method(4)

# Output
16
  • Then why use it? Because it makes it really hard to use to outside the class as it is not that obvious to use.

Note: I will suggest to further read this thread

Did you find this article valuable?

Support Akash Desarda by becoming a sponsor. Any amount is appreciated!

ย