Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
August 1, 2020 07:40 pm GMT

How to write better Python classes using Magic Methods

It is important to make the code we write clear and as easy to read as possible, so anyone unfamiliar with the codebase is able to understand what it does without too much hassle. When dealing with object-oriented Python code, using dunder methods - also known as magic methods - is one useful way to achieve this. They allow our user-defined classes to make use of Python's built-in and primitive constructs - such as +, *, / and other operators, and keywords such as new or len - which are often a lot more intuitive than chains of class methods.

What are dunder methods?

Chances are, if you have programmed in Python for a while, you might have come across strange methods whose names begin and end with pairs of underscores. If you've worked with Python classes, you might even be familiar with one specific method: __init__, which acts as a constructor and is called when a class is initialized.

__init__ is an example of a dunder (double underscore) method, also known as magic methods. They provide a way for us to 'tell' Python how to handle primitive operations, such as addition or equality check, done on our custom classes. For example, when you try to add two instances of a custom class using the '+' operator, the Python interpreter goes inside the definition of your class and looks for an implementation of the __add__ magic method. If it is implemented, it executes the code within, just like with any other 'regular' method or function.

Learning by Example

Let's implement a Time Period class and see how, using Python's Magic Methods, it can be made clearer and more readable. For simplicity, our class will only deal with hours and minutes and will provide a couple of basic functionalities like adding and comparing time periods.

A Basic TimePeriod class

A basic implementation of this class might look something like this:

class TimePeriod:    def __init__(self, hours=0, minutes=0):        self.hours = hours        self.minutes = minutes    def add(self, other):        minutes = self.minutes + other.minutes        hours = self.hours + other.hours        if minutes >= 60:            minutes -= 60            hours += 1        return TimePeriod(hours, minutes)    def greater_than(self, other):        if self.hours > other.hours:            return True        elif self.hours < other.hours:            return False        elif self.minutes > other.minutes:            return True        else:            return False

Then we can create instances of our class like this:

time_i_sleep = TimePeriod(9, 0)time_i_work = TimePeriod(0, 30)

And perform operations on them like this:

print(time_i_sleep.greater_than(time_i_work)) # Should print True

There's nothing functionally wrong with this code. It works exactly as intended and has no bugs. But what if we want to perform a more complex operation, like comparing the sum of two time periods with the sum of another two time periods?

time_i_sleep.add(time_i_watch_netflix).greater_than(time_i_work.add(time_i_do_chores))

Hmmm, doesn't look that great anymore, does it? The reader has to dive in and read each word to understand what the code does.

A smart developer might split the two sums into a pair of temporary variables and then make the comparison. This does indeed improve code clarity a bit:

time_spent_unproductively = time_i_sleep.add(time_i_watch_netflix)time_spent_productively = time_i_work.add(time_i_do_chores)time_spent_unproductively.greater_than(time_spent_productively)

A better TimePeriod class

But the best solution comes from implementing Pythons magic methods and moving our logic into them. Here, we'll implement the __add__ and __gt__ methods that correspond to the '+' and '>' operators. Let's try that now:

class TimePeriod:    def __init__(self, hours=0, minutes=0):        self.hours = hours        self.minutes = minutes    def __add__(self, other):        minutes = self.minutes + other.minutes        hours = self.hours + other.hours        if minutes >= 60:            minutes -= 60            hours += 1        return TimePeriod(hours, minutes)    def __gt__(self, other):        if self.hours > other.hours:            return True        elif self.hours < other.hours:            return False        elif self.minutes > other.minutes:            return True        else:            return False

Now, we can rewrite our complex operation as:

(time_i_sleep + time_i_watch_netflix) > (time_i_work + time_i_do_chores)

There we go! Much cleaner and easier on the eyes because we can now make use of well-recognized symbols ike '+' and '>'. Now, anyone who reads our code will, at a single glance, be able to discern exactly what it does.

Adding More Functionality

What if we want to compare two TimePeriod objects and check if they're equal using the '==' operator? We can simply implement the eq method, like below:

def __eq__(self, other):    return self.hours == other.hours and self.minutes == other.minutes

Python's magic methods aren't restricted to just arithmetic and comparison operations either. One of the most useful such methods, that you might come across quite often, is __str__, which allows you to create an easy-to-read string representation of your class.

def __str__(self):    return f"{self.hours} hours, {self.minutes} minutes"

You can get this string representation by calling str(time_period), useful in debugging and logging.

If we want, we can even turn our class into a dictionary of sorts!

def __getitem__(self, item):    if item == 'hours':        return self.hours    elif item == 'minutes':        return self.minutes    else:        raise KeyError()

This would allows us to use the dictionary access syntax [] to access the hours of the time period using ['hours'] and the minutes using ['minutes']. In all other cases, it raises a KeyError, indicating the key does not exist.

Other Useful Magic Methods

The full list of Python's dunder methods is available in the Python Language Reference. Here, I've made a list of some of the most interesting ones. Feel free to suggest additions in the comments.

  1. __new__:While the __init__ method (that we are already familiar with) is called when initializing an instance of a class, the __new__ method is called even earlier, when actually creating the instance. You can read more about it here.
  2. __call__:The __call__ method allows instances of our to be callable, just like methods or functions! Such callable classes are used extensively in Django, such as in middleware.
  3. __len__:This allows you to define an implementation of Python's builtin len() function.
  4. __repr__:This is similar to the __str__ magic method in that it allows you to define a string representation of your class. However, the difference is __str__ is targeted towards end-users and provides a more user-friendly, informal string, __repr__ on the other hand is targeted towards developers and may contain more complex information about the internal state of the class. You can read more about this distinction here.
  5. __setitem__:We have already taken a look at the __getitem__ method. __setitem__ is the other side of the same coin - while the former allows getting a value corresponding to a key, the latter allows setting a value.

In Conclusion

We've seen how a few minor tweaks to our code can bring great improvements in terms of clarity and readability, as well as give us access to powerful Python language features. In this post we have, of course, only scratched the surface of the capabilities magic methods can provide us, so I would recommend going through the links below to discover interesting new user-cases and possibilities for them.

If you see any errors in this article or would like to suggest any type of improvements, please feel free to write a comment below.

Image by Gerald Friedrich from Pixabay

References and Further Reading

Python Magic Methods Explained
A Guide to Python's Magic Methods
Magic Methods in Python, by example


Original Link: https://dev.to/bikramjeetsingh/how-to-write-better-python-classes-using-magic-methods-4166

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To