What Are Magic Methods In Python? (Dunder Methods)

Magic Methods In Python

Python has a few special methods that begin and end with double underscores, such as __init__ or __len__. These special methods are often called magic methods or dunder methods (short for ‘double underscore’). They’re not magic, despite the name (though some of them may feel that way). But they are used with power – behind the scenes, they help define what an object does in a number of contexts. 

Understanding magic methods can help you write more flexible code. You’ll see them all over the place, from the construction of your objects to operator overloading, and even to making your classes behave like built-in types, especially when used in a loop.

If you’re facing any issues while working on your Python coding homework, you can always get Python Homework assistance from our experts online.

Summary of the article:

  • What magic methods are, and why they are useful in Python.
  • Magic methods you will commonly use, like __init__, __str__, __add__ and others. 
  • Using magic methods to make your classes cleaner and smarter. 
  • Examples of real-life use cases and tips

Let’s dive in!

What Are Magic Methods? Read Below

Magic methods, also referred to as dunder methods (short for double underscore methods), are special methods in Python, where the name of the method starts and ends with two underscores. You have probably seen a method like __init__, __str__, or __len__ before and thought it was just arbitrary naming conventions, but it’s not — it relates to Python’s internal operation of how objects work.

So, what do magic methods do?

You can think of the magic methods as shortcuts that allow you to determine for yourself how your objects behave. When you print an object, or access its attributes, or use an operator such as +, Python checks to see if you have defined a magic method for that behaviour. If we have, Python will use that magic method.

For example:

  • __init__ runs automatically when you create an object.
  • __str__ defines what gets printed when you call print.
  • __add__ allows you to use the + symbol with your custom object.

Magic methods are never explicitly called; Python calls them when needed. They allow your classes and objects to act more like built-in types, which makes your code a little cleaner and more consistent.

Commonly Used Magic Methods

Magic methods (sometimes referred to as dunder, referring to double underscore) are special functions, only in Python they have the double underscore to the left and right of the name, like __init__ or __str__. Magic methods allow you to define the default behavior of your objects when certain events occur, such as when you print an Object, when you compare two Objects, or when you use an Object in a for loop.

Let’s move next to examples of some of the most-used magic methods and some simple examples.

1. The initiation and string representation of an object

  • __init__: Object Constructor

This method is automatically called when an object is instantiated. It is used to initialize the object with its initial state, such as initializing numbers or performing calculations.

				
					class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

				
			

So in this example, whenever you create a Book object, you can pass in the title and author for the object upon creation. 

  •  __str__ and __repr__: String Representations

String methods are used to indicate what is provided when you print an object or visualize the state of an object in your IDE or console.

				
					class Book:
    def __init__(self, title):
        self.title = title

    def __str__(self):
        return f"Book Title: {self.title}"

    def __repr__(self):
        return f"Book('{self.title}')"
				
			

In this example, __str__ is used when you are printing out the book object, while __repr__ is more directed towards our use as a developer and provides the official representation.

2. Operator Overloading

You can leverage magic methods to control how your objects respond to mathematical operations or comparisons.

  • __add__: Add

				
					class Wallet:
    def __init__(self, amount):
        self.amount = amount


    def __add__(self, other):
        return Wallet(self.amount + other.amount)



				
			

This allows adding two Wallet objects with the + operator, and will return a new Wallet with the sum.

  • __eq__, __lt__, __gt__: Comparisons

				
					class Wallet:
    def __init__(self, amount):
        self.amount = amount


    def __eq__(self, other):
        return self.amount == other.amount


    def __lt__(self, other):
        return self.amount < other.amount


				
			

3. Attribute Access and Attribute Management

These methods will control what happens when attributes are accessed or changed.

				
					class Person:
    def __init__(self, name):
        self.name = name
    def __getattr__(self, item):
        return f"{item} not found"


    def __setattr__(self, key, value):
        super().__setattr__(key, value.upper())


				
			

__getattr__ runs if someone tries to call an attribute that does not exist. __setattr__ changes any assignment to uppercase.

4. Making Objects Callable: __call__

You can make your object work like a function…

				
					class Greeter:
    def __init__(self, name):
        self.name = name


    def __call__(self):
        return "Hello, {0}".format(self.name)


				
			

Real-World Uses of Magic Methods

At first, magic methods may seem a little abstract, but they are what drive a lot of the everyday code you see and interact with in Python. Let’s have a look at a couple of practical examples where magic methods have allowed our classes to behave in a more intuitive and concise way.

1. Custom Data Structures

Imagine you are creating your data container—perhaps a simplified version of a list or dictionary that operates with some custom rules. Magic methods will allow you to define how that object acts when accessed, printed, or looped over. Custom data containers that process strings or characters.

				
					class CustomList:
    def __init__(self, items):
        self.items = items
 
    def __len__(self):
        return len(self.items)
 
    def __getitem__(self, index):
        return self.items[index]
 
    def __str__(self):
        return f'Custom


				
			

With the implementation of __len__, __getitem__, and __str__, now this class can be used in a for loop, passed to len(), or printed in a friendly way – just like the built-in types.

2. Resource Management with Context Managers

If you’re working with resources, whether they are files, network connections, or database connections, being able to open them and close them is something that you have to do. Luckily, there are magic methods that are used for doing this, they are called __enter__ and __exit__.

				
					class DummyConnection:
    def __enter__(self):
        print("Opening connection")
        return self


    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Closing connection")


				
			

Now, when used in a with block, we can rely on the context manager to open and close the resource for us, even if there is an error in the with block. This is safer and makes the code easier to read.

3. Operator Overloading for Domain-Specific Logic

If your domain involves data types such as vectors, dates, or money, operator overloading can exhibit the same behavior as built-in types for your classes.

				
					class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    def __add__(self, other):
        return Temperature(self.celsius + other.celsius)

    def __str__(self):
        return f"{self.celsius}°C"


				
			

Best Practices and Common Pitfalls

Magic methods are considered “magical” because they provide power and flexibility, but with power comes great responsibilities. Below are some reasoning or principles and common misconceptions you should keep in mind when using magic methods in your Python classes. Understanding common error types helps avoid pitfalls when customizing attribute access

1. Implement only what you need 

Just because there is a magic method does not mean it needs to be used. For example, if your class does not implement arithmetic operations, then there is little reason to implement any arbitrary arithmetic magic method __add__, __sub__, etc. Implement only those magic methods that seem useful for the object/class you are implementing.

Example:

If your class doesn’t implement a concept of iteration, there is no reason to implement __iter__ or __next__.

2. Maintain expected behavior or semantics

The implementations of your magic methods should behave in the way users would reasonably expect. For example, if you provide your own implementation of __eq__, you should undoubtedly make it reflect equality of objects, and not use equality comparisons for unrelated logic (size of object, id, or some other measure).

Tip: If you provide an implementation for __eq__, it is often a good idea to provide an implementation for __ne__ (!=) so that it remains symmetric.

3. Magic methods should not have side effects 

Magic methods should implement their intended behavior. For example, __str__ should return a string, and not write the string/value to an external file. Magic methods should be clean and readable. If you want a method to have side effects, write the side effect method normally!

Bad practice:

				
					def __str__(self):
    with open('log.txt', 'a') as f:
        f.write('Object printed')
    return "This is a bad idea"
				
			

Conclusion

Magic methods, or dunder methods, are what make your custom Python objects act like built-in types. Defining __init__ and __str__, __add__, __eq__, and countless other magic methods makes for cleaner syntax, better compatibility with Python core features, and more expressive code.

By now, you have learned:

  • What magic methods are, and why they are important.
  • How to implement commonly used magic methods, including object creation and operator overloading.
  • How to employ them in the real world, such as when managing resources, or creating intuitive APIs.
  • What to be cautious of, so that your code is readable and robust.

The best way to get good at magic methods is to simply start using them. Try writing a few small classes and overriding __str__, __add__, or __iter__, and experiment to see what happens. You will begin to appreciate how “just works” and how you can apply this logic to your own code.