Beginner’s Guide to Python Dunder Methods

So that you’ve been studying Python and also you’ve in all probability observed some features have double underscores, whereas others don’t:
-
Why is that?
-
What do they imply?
-
Can you alter them?
-
Must you?
The fact is that these features management rather more of Python than most individuals understand. Use them appropriately and your code feels clear and pure. However use them improper, and issues break in complicated methods.
That’s why on this information, I’ll break down what these “magic” dunder strategies are, how they work, and use them.
Sidenote: For those who discover any of this complicated, or just desire a deep dive into Python, check out Andrei’s Python Coding course taken by 200,000+ people:
It’ll take you from an absolute newbie to understanding every little thing you’ll want to be employed ASAP.
Alternatively, should you’re already fairly good at Python and need to construct some attention-grabbing and helpful tasks, why not check out my course on Python Automation:
It’s going to present you automate all the boring or repetitive duties in your life – and makes for some fairly standout portfolio tasks as effectively!
With all that out of the best way, let’s get into this information.
What are ‘dunder’ strategies?
To be clear, dunder is only a shorter means of claiming ‘double-underscore’. (And to not be confused with ‘Down below’ and kangaroos!).
Mainly, any time you see one thing in Python with two underscores on all sides like __init__, __len__, or __str__, that is what folks imply once they say “dunder technique.”
So what do these double underscores imply?
The double underscores are there to mark the strategies whose names they encompass as particular, as a result of these aren’t common strategies you write your self.
In reality, they’re the code behind the code that makes Python work…
For instance
Once you write print(“Hey world”), Python calls the string’s __str__ technique to determine what to point out. Or once you add numbers with 5 + 10, it’s actually the __add__ technique doing its job.
So why ought to we care?
Effectively if we didn’t have dunder strategies, we would wish to manually write particular code every time for all of this.
Wish to add two numbers?
Effectively, you’d should spell it out each single time:
def add_numbers(a, b):
return a + b
print(add_numbers(5, 10)) # 15
Wish to print textual content? With out __str__, Python wouldn’t know flip “Hey world” into one thing it may show:
def stringify(textual content):
return textual content
print(stringify("Hey world")) # Hey world
Previously, with some older programming languages, you would need to write these features every time. And as you may think, that may get annoying fairly rapidly!
That’s why some folks name these dunder strategies “magic strategies” as a result of every little thing simply works ‘like magic’, and also you don’t should reinvent the fundamentals or account for all these further steps.
As a substitute, Python bakes all of this in for you. You simply write your code the best way you’d anticipate, and these dunder strategies deal with the heavy lifting within the background.
The 6 principal varieties of dunder strategies
Now that you realize what dunder strategies are, let’s discuss in regards to the sorts of issues they really management.
The quick model? Just about every little thing you already do in Python has a dunder technique working behind the scenes.
Most of them fall into just a few principal teams:
-
Object setup strategies management how one thing involves life in Python once you create or copy objects
-
Illustration strategies determine how your object reveals up once you print it, log it, or examine it in a shell
-
Comparability strategies deal with equality – higher than, lower than, and so forth
-
Container and iteration strategies make your objects behave like lists or dictionaries so you’ll be able to loop over them or seize objects by index
-
Arithmetic and operator strategies cowl the mathematics facet of issues: including, multiplying, dividing, and so forth
-
Particular habits strategies give objects further methods, like making them callable as in the event that they have been features or dealing with setup and cleanup when utilized in a with block
What most individuals don’t understand is that these strategies aren’t only for Python’s built-in varieties, however you should utilize them in your individual code too.
This could make your objects really feel extra pure to work with, in addition to make them able to issues akin to printing cleanly, evaluating correctly, looping neatly, and even performing like features once you want them to.
Fairly cool, proper?
So let’s undergo these one after the other, so you’ll be able to see what they’re, what they do, and the way they work.
Object setup strategies
Once you create one thing in Python, it doesn’t simply pop into existence absolutely shaped. Python runs particular dunder strategies behind the scenes that deal with the setup. These are what deliver your objects to life.
The 2 you’ll see most frequently are __new__ and __init__.
__new__ is the tactic that really creates an object in reminiscence. Consider it as Python carving out a spot for the brand new object. You normally don’t want to fret about this one, as a result of Python employs it mechanically until you’re doing one thing extra superior.
__init__ is the one dunder technique you’ve nearly actually seen already. This runs instantly after __new__, and its job is to initialize the item with no matter particulars you care about.
For instance
Think about you’re writing a program to trace pets at a shelter.
Every canine must have a reputation when it is added to the shelter recordsdata, in any other case this system wouldn’t know confer with them.
That’s the place __init__ is available in:
class Canine:
def __init__(self, identify):
self.identify = identify
fido = Canine("Fido")
rex = Canine("Rex")
luna = Canine("Luna")
print(fido.identify) # Fido
print(rex.identify) # Rex
print(luna.identify) # Luna
Python calls Canine.__new__ first to create the empty object, then it instantly calls Canine.__init__, which fills within the identify you handed in.
Tl;DR
__new__ builds the item, and __init__ customizes it with no matter particulars matter in your program. More often than not you’ll solely ever write __init__, however it helps to know __new__ is at all times working simply earlier than it.
Illustration strategies
Everytime you print one thing in Python, you anticipate it to point out up in a means that is smart; i.e., should you print a string, then you definitely anticipate to see the precise textual content. And if alternatively you print an inventory, you anticipate to see the objects inside sq. brackets.
The 2 principal dunder strategies that take care of this are __str__ and __repr__.
__str__ is what Python calls once you use print() on an object. Its job is to offer you a pleasant, readable model of that object. Consider it because the “pleasant” show.
__repr__ is what Python falls again on once you’re simply inspecting one thing in a shell, and what builders usually use once they want an unambiguous illustration of an object. Its job is to be exact, even when the output it produces appears to be like rather less polished.
For instance
Going again to our pet shelter program, let’s say we need to print out one of many canines’ names. With out __str__ or __repr__, printing canine simply offers us this bizarre reminiscence deal with:
class Canine:
def __init__(self, identify):
self.identify = identify
canine = Canine("Fido")
print(canine) # <__main__.Canine object at 0x0000023...>
See what I imply?
Canine object at 0x0000023
However, if we add a __str__, it then fixes the difficulty and makes use of the proper string:
class Canine:
def __init__(self, identify):
self.identify = identify
def __str__(self):
return f"Canine named {self.identify}"
canine = Canine("Fido")
print(canine) # Canine named Fido
Easy!
And if we needed a extra technical model for debugging, we may additionally add a __repr__.
class Canine:
def __init__(self, identify):
self.identify = identify
def __str__(self):
return f"Canine named {self.identify}"
def __repr__(self):
return f"Canine(identify='{self.identify}')"
canine = Canine("Fido")
print(canine) # Canine named Fido
canine # Canine(identify='Fido')
TL;DR
__str__ is the user-friendly view, and __repr__ is the developer-friendly view.
You’ll sometimes outline __str__ once you need your objects to print properly, and typically you’ll outline __repr__ too if you need extra readability when debugging.
Comparability strategies
A variety of the time once you’re coding, you’ll need to examine issues so you may make choices. Possibly you’re checking if two numbers are equal, seeing if one is larger than one other, or sorting an inventory.
That’s the place comparability dunder strategies are available in, and the commonest ones are:
__eq__ checks equality (==). You may use this to see if two canines have the identical ID
__lt__ checks less-than (<). You would use this to see which canine is youthful
__gt__ checks greater-than (>). Helpful for checking which one is older
__le__ and __ge__ covers the “or equal to” variations of those comparisons
Python already is aware of how to do that for its built-in varieties akin to numbers, strings, and lists. However once you create your individual objects, Python doesn’t know what “equal” or “lower than” means in relation to those until you inform it.
For instance
Let’s say that after we uploaded our canines’ data through our __init__ earlier, we saved each their names and their ages. Now we need to manage them by age.
With out a dunder technique, Python has no concept what to do:
class Canine:
def __init__(self, identify, age):
self.identify = identify
self.age = age
fido = Canine("Fido", 3)
rex = Canine("Rex", 5)
print(fido < rex) # TypeError: '<' not supported between cases of 'Canine' and 'Canine'
But when we give Python a rule by including __lt__, it immediately works:
class Canine:
def __init__(self, identify, age):
self.identify = identify
self.age = age
def __lt__(self, different):
return self.age < different.age
fido = Canine("Fido", 3)
rex = Canine("Rex", 5)
print(fido < rex) # True
See what’s occurring?
When Python sees fido < rex, it additionally sees their respective ages and asks itself: “Is 3 lower than 5?” Since that’s true, the result’s True. This allows you to type or filter canines based mostly on their age.
Easy!
We are able to additionally outline what “equal” means. Let’s say we need to test if two canines are the identical age:
class Canine:
def __init__(self, identify, age):
self.identify = identify
self.age = age
def __eq__(self, different):
return self.age == different.age
fido = Canine("Fido", 3)
luna = Canine("Luna", 3)
print(fido == luna) # True
On this case, __eq__ tells Python to match ages. Since each Fido and Luna are 3, the result’s True.
However keep in mind, we will use these comparisons for extra than simply numbers. For instance, what if we need to manage all of the canines’ names alphabetically?
Effectively, we may use the very same technique (__lt__) however give it a distinct that means.
class Canine:
def __init__(self, identify, age):
self.identify = identify
self.age = age
def __lt__(self, different):
return self.identify < different.identify
canines = [Dog("Rex", 5), Dog("Fido", 3), Dog("Luna", 2)]
sorted_dogs = sorted(canines)
for canine in sorted_dogs:
print(canine.identify)
# Fido
# Luna
# Rex
Right here, __lt__ is now not taking a look at ages. It’s evaluating names as an alternative:
def __lt__(self, different):
return self.identify < different.identify
When Python kinds the record, it checks issues like: “Is ‘Fido’ lower than ‘Luna’ alphabetically?” Since ‘F’ comes earlier than ‘L’, it says sure. That’s why the canines find yourself listed as Fido, Luna, and Rex in alphabetical order.
Container and iteration strategies
Once you’re coding, you’ll usually must hold monitor of teams of issues, not only one. That’s why Python has lists, dictionaries, and units. These are referred to as containers, as a result of they maintain a number of objects inside them.
More often than not, you don’t simply care that you’ve a gaggle, you really must work with it. However you may need to test what number of objects there are, have a look at all of them one-by-one, or seize a particular merchandise. And container dunder strategies make that potential.
The principle ones are:
__len__ tells Python what number of objects are in your object
__getitem__ permits you to use sq. brackets ([]) to seize a particular merchandise
__iter__ makes your object usable in a for loop
For instance
Let’s say you simply got here in in your weekend shift on the shelter. The primary stuff you need to know are:
-
What number of canines at the moment are within the shelter
-
Which canine joined most not too long ago, so you may make positive all of the paperwork is ready up (and provides them a hug)
-
In addition to fetching a full record of all present canines
Right here’s how container strategies make that potential.
To start with, let’s give our shelter these three container strategies so it really works extra like an inventory:
class Shelter:
def __init__(self, canines):
self.canines = canines
def __len__(self):
return len(self.canines)
def __getitem__(self, index):
return self.canines[index]
def __iter__(self):
return iter(self.canines)
shelter = Shelter(["Fido", "Rex", "Luna"])
Now we will use this to determine the solutions to our questions.
1. What number of canines are within the shelter?
Once you name len(shelter), Python runs the __len__ technique so we will see the whole variety of canines:
print(len(shelter))
Output:
3
That tells you there are three canines within the shelter proper now.
2. Who was the most recent canine to hitch?
With __getitem__ utilized to our container, we will use sq. brackets ([]) identical to with an inventory.
Right here shelter[-1] means “give me the final canine within the record”, as this might then present us the final to hitch the record i.e. the latest doggo on the shelter.
print(shelter[-1])
Output:
Luna
So Luna was the newest arrival.
3. What’s the complete record of canines proper now?
With __iter__ added, we will now run a for loop. This implies we may then use it to scan by means of each canine within the shelter and see who’s there, with out writing further code to step by means of manually.
for canine in shelter:
print(canine)
Output:
That means, you’ll be able to see the complete record of canines to verify who’s nonetheless there, and who may’ve been adopted.
Useful proper?
TL;DR
Container strategies let your objects act like collections. They energy len(), [], and for loops, making your customized objects really feel simply as straightforward to make use of as Python’s built-in lists.
Arithmetic and operator strategies
Typically you’ll need to do math along with your objects, and Python offers us just a few completely different dunder strategies to deal with this.
The principle ones are:
__add__ runs once you use +
__sub__ runs once you use –
__mul__ runs once you use *
__truediv__ runs once you use /
As I stated earlier, in the case of numbers and strings, Python already is aware of what these operators imply. 2 + 3 offers 5, and “hello” + ” there” offers “hello there”.
However in your personal objects, Python doesn’t know what “add” or “multiply” means until you inform it.
For instance
Let’s say every canine in our shelter eats 2kg of meals a day, and we’d prefer to rapidly work out the whole meals wanted every week, in order that we will mechanically place an order.
By default, Python received’t perceive this:
class Shelter:
def __init__(self, canines):
self.canines = canines
shelter = Shelter(["Fido", "Rex", "Luna"])
print(shelter * 2) # TypeError: unsupported operand kind(s)
See the error?
That occurs as a result of Python doesn’t know use * with objects created from our Shelter class. So let’s give it a rule with __mul__:
class Shelter:
def __init__(self, canines):
self.canines = canines
def __len__(self):
return len(self.canines)
def __mul__(self, food_per_dog):
return len(self) * food_per_dog
shelter = Shelter(["Fido", "Rex", "Luna"])
print(shelter * 2) # 6
Right here, when Python sees shelter * 2, it calls __mul__, which multiplies the variety of canines (len(self)) by food_per_dog. Since there are 3 canines and every eats 2kg, the result’s 6kgs required per day, or 42kgs per week.
Easy!
Arithmetic operators don’t simply work with numbers, although. You’ll be able to outline them to do no matter is smart in your objects.
For instance
Suppose that two shelters merge, and we need to mix their canine lists. We are able to use __add__ to inform Python deal with + on this particular case:
class Shelter:
def __init__(self, canines):
self.canines = canines
def __add__(self, different):
return Shelter(self.canines + different.canines)
shelter1 = Shelter(["Fido", "Rex"])
shelter2 = Shelter(["Luna", "Bella"])
merged = shelter1 + shelter2
print(len(merged.canines)) # 4
Right here, shelter1 + shelter2 produces a brand-new Shelter with all 4 canines, whereas behind the scenes Python calls __add__, which glues the 2 lists collectively
TL;DR
Arithmetic strategies let your objects deal with mathematical-type operations, numeric or in any other case. You simply must determine what +, –, *, or / means in your objects, so Python can deal with them identical to numbers.
Simple!
Different particular circumstances
Not all dunder strategies match neatly into setup, illustration, comparisons, containers, or arithmetic.
The commonest ones you’ll come throughout exterior these classes are __call__ and the context supervisor pair __enter__ / __exit__. Every of those unlocks very completely different behaviors, so let’s take them one after the other.
name
In Python, once you name a perform, you’re principally telling Python to run it. You try this by writing the perform’s identify, adopted by parentheses:
def greet():
return "Hey!"
print(greet()) # Hey!
The parentheses are the set off. They inform Python, “go forward and execute this”. With out them, nothing occurs:
print(greet) #
Up to now, so good. However typically you may want an object that may maintain knowledge or settings, and likewise behave like a perform once you name it.
Possibly:
-
A machine studying mannequin object that shops its coaching settings, but additionally makes predictions once you name it
-
A textual content cleaner that saves its guidelines, but additionally processes strings once you name it
-
A caching instrument that remembers outdated outcomes, however nonetheless runs new calculations once you name it
Usually, objects don’t work like that. They’re simply knowledge holders, so should you attempt to “name” one like a perform, Python will get confused:
canine = "Fido"
print(canine()) # TypeError: 'str' object shouldn't be callable
That’s the place the __call__ technique is available in. For those who outline this inside your class, you’re principally telling Python: “When this object is known as with parentheses, right here’s what to do”.
For instance
Let’s say that you simply’re engaged on a small program to trace your coaching for a marathon.
You log your distance in miles, however your good friend makes use of kilometers, and also you need a straightforward method to convert between the 2 each time you’ll want to share your outcomes with them.
Positive, you possibly can write a perform like def miles_to_km(x): return x * 1.609, however that solely works for one particular conversion. What if you need it the opposite means round? Or need to reuse the perform for one thing else in a while?
In eventualities like these you’d find yourself with a pile of separate features.
To keep away from that problem nonetheless, you possibly can merely make a Converter object with a __call__ technique. It could retailer the conversion price, and once you “name” it, it will additionally do the mathematics:
class Converter:
def __init__(self, price):
self.price = price
def __call__(self, worth):
return worth * self.price
miles_to_km = Converter(1.609)
print(miles_to_km(10)) # 16.09
print(miles_to_km(25)) # 40.225
Useful proper?
TL;DR
__call__ lets your objects act like features. It’s the way you make an object that each shops data and runs code once you name it.
enter and exit defined
Once you open a file in Python, you’re principally saying: “Maintain this file prepared for me, as a result of I need to use it”.
The factor is that when you’re finished, you even have to shut the file. as a result of should you don’t, then the information won’t save correctly.
Because of this the file can get locked so different applications can’t use it, and might also slowly waste system assets by merely being left open.
The difficulty is, folks neglect. Or worse, one thing surprising occurs.
Maybe your code crashes earlier than it ever reaches the road that closes the file. And so now you’ve obtained a file left hanging open, which is a type of irritating bugs that doesn’t present up till it causes an actual downside in a while.
All because of this Python has context managers.
A context supervisor is only a means of claiming: “After I begin utilizing this factor, do some setup; and once I’m finished, it doesn’t matter what occurs, clear it up correctly.”
In our code, we use with for this. Nevertheless, as you’ll be able to guess, with solely works as a result of recordsdata (and many different objects) implement two particular dunder strategies:
__enter__ runs initially, when the block begins
__exit__ runs on the finish, when the block finishes. Even when an error occurred within the center
For instance
Let’s say you’re preserving monitor of the shelter’s adoptions in a file. You open it, write a brand new canine’s identify, after which shut it:
f = open("adoptions.txt", "w")
f.write("Fido adopted!n")
# If an error occurs right here, the file by no means will get closed!
f.shut()
See the difficulty?
In case your program crashes earlier than f.shut() runs, the file may keep open and the replace won’t be saved.
So right here’s the safer model utilizing with (in order that we will apply __enter__ and __exit__ below the hood)
with open("adoptions.txt", "w") as f:
f.write("Fido adopted!n")
# Even when an error occurs right here, the file will nonetheless shut correctly
This second model now ensures that the file will likely be closed as soon as the code within the block executes, even when it throws an error. Which means your adoption file really makes it into the file, and also you don’t danger leaving it caught open.
TL;DR
__enter__ and __exit__ are the spine of context managers. They be sure that Python’s vital cleanup occurs for you, so that you don’t lose your knowledge or go away issues hanging open.
Time to offer these a strive for your self!
Hopefully this information has helped pull again the curtain and made dunder strategies rather less mysterious.
More often than not, you received’t be writing them your self; you’ll simply use the features that depend on them, like len(), print(), or sorted(). However sometimes, you’ll run right into a state of affairs the place including one is smart.
That may imply educating Python examine your objects, print them properly, and even letting them act like features. Figuring out how these hooks work implies that not solely will you not be caught when one thing doesn’t behave as anticipated, however that you simply’ll even have the instruments to repair it.
As normal although, the easiest way to actually perceive a coding idea is to strive it out! So go forward and choose a few dunder strategies, add them to a easy class in your Python code, and see how your objects change.
P.S.
Don’t neglect, if you wish to study extra and dive deep into Python, then make sure to take a look at Andrei’s Complete Python Developer course
It’ll take you from an absolute newbie and train you every little thing you’ll want to get employed ASAP and ace the tech interview.
That is the one Python course you want if you wish to go from a whole Python newbie to getting employed as a Python Developer this 12 months!
Alternatively, should you’re already fairly good at Python and need to construct some attention-grabbing and helpful tasks, why not check out my course on Python Automation:
It’s going to present you automate all the boring or repetitive duties in your life, and makes for some fairly stand out portfolio tasks!
Plus, as a part of your membership, you will get entry to each of those programs and others, and be capable to be part of me and 1,000s of different folks (some who’re alumni mentors and others who’re taking the identical programs that you’ll be) within the ZTM Discord.
Ask questions, assist others, or simply community with different Python Builders, college students, and tech professionals.
PDF Obtain Of This Study Python For Free Information
I get plenty of requests for a downloadable model of this information so to print it off and test issues off as you go or be capable to ship it to your Kindle/Telephone. Join under and Andrei will e-mail you this full information as a PDF!
No spam ever, unsubscribe anytime
Extra Python Tutorials
For those who loved this publish, take a look at my different Python tutorials:


