class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f'Point({self.x}, {self.y})'
p = Point(1, 2)
print(p) # Point(1, 2) -- uses __repr__
Topics
20
Async Generators & Async Context Managers
Command-Line Interfaces (argparse)
Comprehensions & Generators
Concurrency (asyncio/threading/multiprocessing)
Data Types & Structures
Dataclasses & NamedTuples
Debugging & Profiling
Decorators
Descriptors & Properties
Exception Handling
File I/O & Context Managers
Functions & Scope
functools & Functional Programming Tools
Iterators & the Iterator Protocol
Logging
Magic Methods & Operator Overloading
Memory Management & Garbage Collection
Metaclasses & Class Customization
Modules & Packaging
Multiple Inheritance & MRO
Magic Methods & Operator Overloading
15 questions found
Magic methods are special methods surrounded by double underscores (e.g., __init__, __str__, __add__) that Python calls implicitly in response to built-in operations like construction, printing, arithmetic, or comparisons. They're called 'dunder' methods (double underscore) and let custom classes integrate with Python's built-in syntax and functions.
Real-world example
A custom Money class defines __add__, __eq__, and __repr__ so instances can be added and compared with natural + and == syntax instead of explicit method calls.
Data Types & Structures;Dataclasses & NamedTuples
__str__ should return a readable, user-facing string (used by print() and str()), while __repr__ should return an unambiguous, ideally eval-able representation useful for debugging (used by the REPL and repr()). If __str__ is not defined, Python falls back to __repr__ for str() and print().
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f'Point(x={self.x}, y={self.y})'
def __str__(self):
return f'({self.x}, {self.y})'
p = Point(1, 2)
print(str(p)) # (1, 2)
print(repr(p)) # Point(x=1, y=2)
Real-world example
In a debugging session, a list of custom objects printed in the console shows each item's __repr__, making it crucial to implement a clear, informative __repr__ for every domain model class.
Magic Methods & Operator Overloading;Debugging & Profiling
Defining __add__(self, other) lets instances of your class respond to the + operator, and similarly __sub__, __mul__, __truediv__, etc. for other operators. Python calls left.__add__(right) first; if that returns NotImplemented, it tries right.__radd__(left) as a fallback, supporting mixed-type operations.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f'Vector({self.x}, {self.y})'
print(Vector(1,2) + Vector(3,4)) # Vector(4, 6)
Real-world example
A physics simulation's Vector3D class overloads +, -, *, and dot() so simulation code reads like natural math instead of verbose method calls.
Data Types & Structures;Dataclasses & NamedTuples
What do __eq__ and __hash__ need to satisfy together, and what happens if you only override __eq__?
IntermediateIf two objects are equal (__eq__ returns True), they must have the same hash value so they behave consistently in sets and dict keys. By default, defining __eq__ without __hash__ makes the class unhashable (Python sets __hash__ to None automatically), which raises TypeError if you try to put instances in a set or use them as dict keys.
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
{Point(1,2)} # works because __hash__ is defined
Real-world example
A caching layer uses custom key objects as dict keys, so their class must define both __eq__ and __hash__ consistently or lookups will fail unpredictably.
Data Types & Structures;Dataclasses & NamedTuples
How do __len__, __getitem__, and __setitem__ let a custom class act like a sequence or mapping?
Intermediate__len__ enables len(obj); __getitem__ enables obj[key] (and, historically, iteration and 'in' checks via integer index fallback); __setitem__ enables obj[key] = value. Implementing these lets custom container classes integrate naturally with built-in syntax, slicing, and functions expecting sequence-like or mapping-like behavior.
class Deck:
def __init__(self):
self._cards = list(range(52))
def __len__(self):
return len(self._cards)
def __getitem__(self, i):
return self._cards[i]
d = Deck()
print(len(d), d[0], d[-1])
Real-world example
A custom Matrix class implements __getitem__ and __setitem__ to support matrix[row, col] indexing via tuple keys, mimicking NumPy-style access.
Iterators & the Iterator Protocol;Data Types & Structures
How do context manager magic methods __enter__ and __exit__ work, and what does __exit__'s return value control?
Advanced__enter__(self) runs at the start of a with block and its return value is bound to the as target. __exit__(self, exc_type, exc_val, exc_tb) runs on block exit, receiving exception info if one occurred; returning True suppresses the exception, while returning False (or None) lets it propagate normally.
class Transaction:
def __enter__(self):
print('BEGIN')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print('ROLLBACK')
return False
print('COMMIT')
with Transaction():
print('doing work')
Real-world example
A database Transaction class uses __enter__/__exit__ to automatically COMMIT on success and ROLLBACK if any exception occurs inside the with block, without repeating try/finally everywhere.
File I/O & Context Managers;Exception Handling
Defining __call__(self, *args, **kwargs) makes instances callable with parentheses, e.g., obj(x, y), which Python translates to obj.__call__(x, y). This is used for stateful callables like decorators-as-classes, memoized functions, or configurable strategy objects that need to remember state between calls.
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
print(double(5)) # 10 -- called like a function
Real-world example
A machine learning preprocessing pipeline defines transform classes with __call__ so each transform can be applied like a function while still holding configuration state (e.g., normalization parameters).
Decorators;Descriptors & Properties
__new__(cls, ...) is a static method that actually creates and returns the new instance (allocating memory), called before __init__. __init__(self, ...) then initializes that already-created instance and returns None. You override __new__ for cases __init__ can't handle: controlling instance creation for immutable types (like subclassing tuple or str), implementing singletons, or returning an instance of a different class.
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
a, b = Singleton(), Singleton()
print(a is b) # True
Real-world example
A configuration manager implements __new__ to enforce a single shared instance across the entire application, ensuring all code reads the same in-memory config object.
Metaclasses & Class Customization;Multiple Inheritance & MRO
How do comparison magic methods like __lt__, __le__, __gt__, and __ge__ support custom sorting?
IntermediateImplementing these lets instances be compared with <, <=, >, >= respectively, which sorted() and list.sort() rely on by default (via < comparisons) when no key function is given. Using @functools.total_ordering, you can define just __eq__ and one of these to get all six comparison methods automatically.
class Task:
def __init__(self, priority):
self.priority = priority
def __lt__(self, other):
return self.priority < other.priority
tasks = [Task(3), Task(1), Task(2)]
tasks.sort() # sorts using __lt__
Real-world example
A priority queue of custom Task objects relies on __lt__ so heapq.heappush/heappop can order tasks without needing a separate key function.
functools & Functional Programming Tools;Data Types & Structures
How do __iadd__ and other in-place operator methods (__isub__, __imul__) differ from their non-in-place counterparts?
Advanced__iadd__(self, other) implements the += operator and can mutate self in place and return self, avoiding creating a new object (unlike __add__, which must return a new object). If __iadd__ isn't defined, Python falls back to __add__ followed by reassignment (x = x + y), which is less efficient for mutable, resizable containers.
class Bag:
def __init__(self, items=None):
self.items = items or []
def __iadd__(self, other):
self.items.extend(other)
return self # mutate and return self
b = Bag([1,2])
b += [3,4]
print(b.items) # [1,2,3,4]
Real-world example
A custom mutable Matrix class implements __iadd__ so accumulating large matrices with += avoids allocating a brand-new matrix object on every update, improving performance in tight loops.
Data Types & Structures;Memory Management & Garbage Collection
Showing 1–10 of 15