Senior Software Engineer Edition
Last Updated: November 2025 | Python 3.10+ Focus
Table of Contents
- Introduction
- Object Lifecycle Magic Methods
- Comparison Magic Methods
- Arithmetic Magic Methods
- Unary Operators and Functions
- Type Conversion Magic Methods
- Container Magic Methods
- Attribute Access Magic Methods
- Callable Objects
- Context Managers
- Descriptor Protocol
- Copying and Pickling
- String Representation
- Advanced Magic Methods
- Performance Optimization
- Type Hints & Protocols
- Metaclass Patterns
- Async Magic Methods
- Memory Management & Slots
- Pattern Matching (Python 3.10+)
- Production Best Practices
Introduction
Magic methods (also called dunder methods, from “double underscore”) are special methods in Python that allow you to define how objects of your class behave with built-in operations. They are always surrounded by double underscores (e.g., __init__, __str__).
Why Use Magic Methods?
- Intuitive Syntax: Make your objects work with Python’s built-in operators (+, -, *, [], etc.)
- Pythonic Code: Write code that feels natural and follows Python conventions
- Integration: Seamlessly integrate custom objects with Python’s standard library
- Operator Overloading: Define custom behavior for operators
- Protocol Implementation: Enable duck typing and structural subtyping
- Performance: Optimize object behavior at the C level
Senior Engineer Considerations
Type Safety: Always use type hints with magic methods for better IDE support and static analysis:
from typing import Self
from collections.abc import Iterator
class Vector:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __add__(self, other: Self) -> Self:
return self.__class__(self.x + other.x, self.y + other.y)PythonPerformance Impact: Magic methods are called frequently. Profile before optimizing:
import timeit
# Test performance of magic methods
setup = "class Point: ..."
code = "p1 + p2"
time = timeit.timeit(code, setup=setup, number=1000000)PythonProtocol-Oriented Design: Use typing.Protocol for structural subtyping:
from typing import Protocol
class Addable(Protocol):
def __add__(self, other: 'Addable') -> 'Addable': ...
def combine(a: Addable, b: Addable) -> Addable:
return a + b # Type-safe without inheritancePythonObject Lifecycle Magic Methods
__new__(cls, *args, **kwargs)
Controls the creation of a new instance. Called before __init__. Rarely used unless you need to control instance creation (e.g., implementing singleton pattern).
Thread-Safe Singleton Pattern (Production-Ready):
import threading
from typing import Optional, Any
class Singleton:
_instance: Optional['Singleton'] = None
_lock: threading.Lock = threading.Lock()
_initialized: bool = False
def __new__(cls, *args: Any, **kwargs: Any) -> 'Singleton':
if cls._instance is None:
with cls._lock:
# Double-checked locking pattern
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value: int) -> None:
# Prevent re-initialization
if not self._initialized:
with self._lock:
if not self._initialized:
self.value = value
self._initialized = True
# Test
s1 = Singleton(10)
s2 = Singleton(20)
print(s1 is s2) # True - same instance
print(s1.value) # 10 - not re-initializedPythonImmutable Objects with __new__ (like int, str):
from typing import Any
class PositiveInt(int):
"""An integer that must be positive."""
def __new__(cls, value: int) -> 'PositiveInt':
if value <= 0:
raise ValueError(f"Value must be positive, got {value}")
instance = super().__new__(cls, value)
return instance
# No __init__ needed - immutable types use __new__
num = PositiveInt(5)
print(num + 3) # 8
# PositiveInt(-5) # Raises ValueErrorPythonObject Pooling Pattern (Flyweight):
from typing import Dict, Any
import weakref
class Flyweight:
"""Memory-efficient object pooling for immutable objects."""
_pool: Dict[tuple, 'Flyweight'] = weakref.WeakValueDictionary()
def __new__(cls, *args: Any) -> 'Flyweight':
key = args
if key not in cls._pool:
instance = super().__new__(cls)
cls._pool[key] = instance
return cls._pool[key]
def __init__(self, x: int, y: int) -> None:
# Only initialize if not already done
if not hasattr(self, 'x'):
self.x = x
self.y = y
# Reuses instances for same arguments
p1 = Flyweight(1, 2)
p2 = Flyweight(1, 2)
print(p1 is p2) # True - same objectPython__init__(self, *args, **kwargs)
Initializes a newly created instance. This is the constructor you use most often.
Type-Safe Initialization with Validation:
from typing import Optional
import re
class Person:
"""Person class with validated initialization."""
def __init__(self, name: str, age: int, email: Optional[str] = None) -> None:
# Validation before assignment
if not isinstance(name, str) or not name.strip():
raise TypeError("Name must be a non-empty string")
if not isinstance(age, int) or age < 0:
raise ValueError("Age must be a non-negative integer")
if email and not re.match(r'^[\w.-]+@[\w.-]+\.\w+$', email):
raise ValueError(f"Invalid email format: {email}")
self._name = name
self._age = age
self._email = email
@property
def name(self) -> str:
return self._name
@property
def age(self) -> int:
return self._age
person = Person("Alice", 30, "alice@example.com")PythonAlternative: Use Dataclasses for Less Boilerplate (Recommended):
from dataclasses import dataclass, field
from typing import Optional, ClassVar
@dataclass(slots=True, frozen=False) # slots for memory efficiency
class Person:
"""Modern Person implementation with dataclass."""
name: str
age: int
email: Optional[str] = None
_id_counter: ClassVar[int] = 0
id: int = field(init=False)
def __post_init__(self) -> None:
"""Called after __init__ for validation."""
if self.age < 0:
raise ValueError("Age must be non-negative")
Person._id_counter += 1
object.__setattr__(self, 'id', Person._id_counter)
person = Person("Alice", 30)
print(person.id) # Auto-assigned IDPython__del__(self)
Called when an object is about to be destroyed. Use with caution as timing is unpredictable.
class FileHandler:
def __init__(self, filename):
self.filename = filename
self.file = open(filename, 'w')
def __del__(self):
if hasattr(self, 'file') and not self.file.closed:
self.file.close()
print(f"File {self.filename} closed")PythonComparison Magic Methods
These methods define how objects are compared using comparison operators.
__eq__(self, other) – Equality (==)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(2, 3)
print(p1 == p2) # True
print(p1 == p3) # FalsePython__ne__(self, other) – Not Equal (!=)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __ne__(self, other):
return not self.__eq__(other)Python__lt__(self, other) – Less Than (<)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __lt__(self, other):
if not isinstance(other, Person):
return NotImplemented
return self.age < other.age
people = [Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35)]
sorted_people = sorted(people)
for p in sorted_people:
print(f"{p.name}: {p.age}")
# Output: Bob: 25, Alice: 30, Charlie: 35Python__le__(self, other) – Less Than or Equal (<=)
def __le__(self, other):
if not isinstance(other, Person):
return NotImplemented
return self.age <= other.agePython__gt__(self, other) – Greater Than (>)
def __gt__(self, other):
if not isinstance(other, Person):
return NotImplemented
return self.age > other.agePython__ge__(self, other) – Greater Than or Equal (>=)
def __ge__(self, other):
if not isinstance(other, Person):
return NotImplemented
return self.age >= other.agePythonUsing @functools.total_ordering
Instead of defining all comparison methods, define __eq__ and one other, then use the decorator:
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def __eq__(self, other):
if not isinstance(other, Student):
return NotImplemented
return self.grade == other.grade
def __lt__(self, other):
if not isinstance(other, Student):
return NotImplemented
return self.grade < other.grade
s1 = Student("Alice", 85)
s2 = Student("Bob", 90)
print(s1 < s2) # True
print(s1 <= s2) # True (auto-generated)
print(s1 > s2) # False (auto-generated)
print(s1 >= s2) # False (auto-generated)PythonArithmetic Magic Methods
Binary Arithmetic Operators
__add__(self, other) – Addition (+)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2
print(v3) # Vector(4, 6)Python__sub__(self, other) – Subtraction (-)
def __sub__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x - other.x, self.y - other.y)Python__mul__(self, other) – Multiplication (*)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v = Vector(2, 3)
print(v * 3) # Vector(6, 9)Python__truediv__(self, other) – True Division (/)
def __truediv__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x / scalar, self.y / scalar)
return NotImplementedPython__floordiv__(self, other) – Floor Division (//)
def __floordiv__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x // scalar, self.y // scalar)
return NotImplementedPython__mod__(self, other) – Modulo (%)
class Number:
def __init__(self, value):
self.value = value
def __mod__(self, other):
if isinstance(other, Number):
return Number(self.value % other.value)
elif isinstance(other, (int, float)):
return Number(self.value % other)
return NotImplementedPython__pow__(self, other) – Exponentiation (**)
def __pow__(self, exponent):
if isinstance(exponent, (int, float)):
return Number(self.value ** exponent)
return NotImplementedPython__matmul__(self, other) – Matrix Multiplication (@)
class Matrix:
def __init__(self, data):
self.data = data
def __matmul__(self, other):
# Simplified 2x2 matrix multiplication
if not isinstance(other, Matrix):
return NotImplemented
# Implementation would do actual matrix multiplication
return Matrix([[0, 0], [0, 0]]) # PlaceholderPythonReflected Arithmetic Operators
These are called when the left operand doesn’t support the operation.
__radd__(self, other) – Reflected Addition
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __radd__(self, other):
# Called when other + self is evaluated and other doesn't have __add__
return self.__add__(other)PythonSimilar reflected methods exist:
__rsub__(self, other)– Reflected subtraction__rmul__(self, other)– Reflected multiplication__rtruediv__(self, other)– Reflected true division__rfloordiv__(self, other)– Reflected floor division__rmod__(self, other)– Reflected modulo__rpow__(self, other)– Reflected exponentiation__rmatmul__(self, other)– Reflected matrix multiplication
Augmented Assignment
__iadd__(self, other) – In-place Addition (+=)
class MutableVector:
def __init__(self, x, y):
self.x = x
self.y = y
def __iadd__(self, other):
if isinstance(other, MutableVector):
self.x += other.x
self.y += other.y
return self # Must return self
return NotImplemented
def __repr__(self):
return f"MutableVector({self.x}, {self.y})"
v1 = MutableVector(1, 2)
v2 = MutableVector(3, 4)
v1 += v2
print(v1) # MutableVector(4, 6)PythonSimilar augmented assignment methods:
__isub__(self, other)– In-place subtraction (-=)__imul__(self, other)– In-place multiplication (*=)__itruediv__(self, other)– In-place true division (/=)__ifloordiv__(self, other)– In-place floor division (//=)__imod__(self, other)– In-place modulo (%=)__ipow__(self, other)– In-place exponentiation (**=)__imatmul__(self, other)– In-place matrix multiplication (@=)
Unary Operators and Functions
__neg__(self) – Unary Negation (-)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __neg__(self):
return Vector(-self.x, -self.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v = Vector(3, 4)
print(-v) # Vector(-3, -4)Python__pos__(self) – Unary Plus (+)
def __pos__(self):
return Vector(+self.x, +self.y)Python__abs__(self) – Absolute Value
import math
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __abs__(self):
return math.sqrt(self.x**2 + self.y**2)
v = Vector(3, 4)
print(abs(v)) # 5.0Python__invert__(self) – Bitwise NOT (~)
class BinaryNumber:
def __init__(self, value):
self.value = value
def __invert__(self):
return BinaryNumber(~self.value)
def __repr__(self):
return f"BinaryNumber({self.value})"
b = BinaryNumber(5)
print(~b) # BinaryNumber(-6)Python__round__(self, n=0) – Rounding
class Decimal:
def __init__(self, value):
self.value = value
def __round__(self, n=0):
return Decimal(round(self.value, n))
d = Decimal(3.14159)
print(round(d, 2)) # Decimal(3.14)Python__floor__(self) – Floor Function
import math
class Decimal:
def __init__(self, value):
self.value = value
def __floor__(self):
return Decimal(math.floor(self.value))Python__ceil__(self) – Ceiling Function
def __ceil__(self):
return Decimal(math.ceil(self.value))Python__trunc__(self) – Truncation
def __trunc__(self):
return Decimal(math.trunc(self.value))PythonType Conversion Magic Methods
__int__(self) – Convert to Integer
class Fraction:
def __init__(self, numerator, denominator):
self.numerator = numerator
self.denominator = denominator
def __int__(self):
return self.numerator // self.denominator
f = Fraction(7, 2)
print(int(f)) # 3Python__float__(self) – Convert to Float
def __float__(self):
return self.numerator / self.denominator
f = Fraction(7, 2)
print(float(f)) # 3.5Python__complex__(self) – Convert to Complex
class ComplexNumber:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __complex__(self):
return complex(self.real, self.imag)Python__bool__(self) – Convert to Boolean
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __bool__(self):
return self.x != 0 or self.y != 0
v1 = Vector(0, 0)
v2 = Vector(1, 2)
print(bool(v1)) # False
print(bool(v2)) # True
if v2:
print("Vector is non-zero")Python__bytes__(self) – Convert to Bytes
class Data:
def __init__(self, value):
self.value = value
def __bytes__(self):
return str(self.value).encode('utf-8')
d = Data("Hello")
print(bytes(d)) # b'Hello'Python__format__(self, format_spec) – Custom Formatting
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __format__(self, format_spec):
if format_spec == 'polar':
import math
r = math.sqrt(self.x**2 + self.y**2)
theta = math.atan2(self.y, self.x)
return f"(r={r:.2f}, θ={theta:.2f})"
elif format_spec == 'cartesian' or format_spec == '':
return f"({self.x}, {self.y})"
else:
raise ValueError(f"Unknown format: {format_spec}")
p = Point(3, 4)
print(f"{p}") # (3, 4)
print(f"{p:cartesian}") # (3, 4)
print(f"{p:polar}") # (r=5.00, θ=0.93)Python__hash__(self) – Make Object Hashable
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
# Now can be used in sets and as dict keys
points = {Point(1, 2), Point(3, 4), Point(1, 2)}
print(len(points)) # 2 (duplicates removed)
point_data = {Point(1, 2): "Origin-ish", Point(3, 4): "Away"}Python__index__(self) – Convert to Index
class Index:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value
idx = Index(2)
my_list = [10, 20, 30, 40]
print(my_list[idx]) # 30PythonContainer Magic Methods
__len__(self) – Length
class Playlist:
def __init__(self):
self.songs = []
def add_song(self, song):
self.songs.append(song)
def __len__(self):
return len(self.songs)
playlist = Playlist()
playlist.add_song("Song 1")
playlist.add_song("Song 2")
print(len(playlist)) # 2Python__getitem__(self, key) – Indexing and Slicing
class Playlist:
def __init__(self):
self.songs = []
def add_song(self, song):
self.songs.append(song)
def __getitem__(self, index):
return self.songs[index]
def __len__(self):
return len(self.songs)
playlist = Playlist()
playlist.add_song("Song 1")
playlist.add_song("Song 2")
playlist.add_song("Song 3")
print(playlist[0]) # Song 1
print(playlist[-1]) # Song 3
print(playlist[0:2]) # ['Song 1', 'Song 2']
# Also enables iteration
for song in playlist:
print(song)Python__setitem__(self, key, value) – Item Assignment
class Playlist:
def __init__(self):
self.songs = []
def __getitem__(self, index):
return self.songs[index]
def __setitem__(self, index, value):
self.songs[index] = value
def __len__(self):
return len(self.songs)
playlist = Playlist()
playlist.songs = ["Song 1", "Song 2", "Song 3"]
playlist[1] = "New Song 2"
print(playlist[1]) # New Song 2Python__delitem__(self, key) – Item Deletion
def __delitem__(self, index):
del self.songs[index]
playlist = Playlist()
playlist.songs = ["Song 1", "Song 2", "Song 3"]
del playlist[1]
print(len(playlist.songs)) # 2Python__contains__(self, item) – Membership Test (in)
class Playlist:
def __init__(self):
self.songs = []
def add_song(self, song):
self.songs.append(song)
def __contains__(self, song):
return song in self.songs
playlist = Playlist()
playlist.add_song("Song 1")
playlist.add_song("Song 2")
print("Song 1" in playlist) # True
print("Song 3" in playlist) # FalsePython__iter__(self) – Iterator
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
self.current = self.start
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
for num in Countdown(5):
print(num) # 5, 4, 3, 2, 1Python__reversed__(self) – Reverse Iteration
class Playlist:
def __init__(self, songs):
self.songs = songs
def __reversed__(self):
return reversed(self.songs)
def __iter__(self):
return iter(self.songs)
playlist = Playlist(["Song 1", "Song 2", "Song 3"])
for song in reversed(playlist):
print(song) # Song 3, Song 2, Song 1Python__missing__(self, key) – Handling Missing Keys (for dict subclasses)
class DefaultDict(dict):
def __init__(self, default_value):
super().__init__()
self.default_value = default_value
def __missing__(self, key):
# Called when key is not found
self[key] = self.default_value
return self.default_value
dd = DefaultDict(0)
dd['a'] = 5
print(dd['a']) # 5
print(dd['b']) # 0 (auto-created)
print(dd) # {'a': 5, 'b': 0}PythonAttribute Access Magic Methods
__getattr__(self, name) – Attribute Access
Called when an attribute is not found through normal lookup.
class DynamicAttributes:
def __init__(self):
self.data = {'name': 'Alice', 'age': 30}
def __getattr__(self, name):
# Only called if attribute doesn't exist normally
if name in self.data:
return self.data[name]
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
obj = DynamicAttributes()
print(obj.name) # Alice
print(obj.age) # 30
# print(obj.missing) # Raises AttributeErrorPython__setattr__(self, name, value) – Attribute Assignment
Called on every attribute assignment.
class ValidatedAttributes:
def __setattr__(self, name, value):
if name == 'age' and (not isinstance(value, int) or value < 0):
raise ValueError("Age must be a non-negative integer")
# Use super() or __dict__ to avoid infinite recursion
super().__setattr__(name, value)
obj = ValidatedAttributes()
obj.name = "Alice"
obj.age = 30
# obj.age = -5 # Raises ValueError
# obj.age = "thirty" # Raises ValueErrorPython__delattr__(self, name) – Attribute Deletion
class ProtectedAttributes:
def __init__(self):
self.public = "Can delete"
self._protected = "Cannot delete"
def __delattr__(self, name):
if name.startswith('_'):
raise AttributeError(f"Cannot delete protected attribute '{name}'")
super().__delattr__(name)
obj = ProtectedAttributes()
del obj.public # OK
# del obj._protected # Raises AttributeErrorPython__getattribute__(self, name) – Unconditional Attribute Access
Called for every attribute access. Use with caution to avoid infinite recursion.
class LoggedAccess:
def __init__(self):
super().__setattr__('_log', [])
super().__setattr__('value', 42)
def __getattribute__(self, name):
# Avoid logging access to _log itself
if name != '_log':
log = super().__getattribute__('_log')
log.append(f"Accessed: {name}")
return super().__getattribute__(name)
obj = LoggedAccess()
print(obj.value) # 42
print(obj._log) # ['Accessed: value']Python__dir__(self) – Directory Listing
class CustomDir:
def __init__(self):
self.x = 1
self.y = 2
self._hidden = 3
def __dir__(self):
# Customize what dir() shows
return ['x', 'y', 'custom_method']
obj = CustomDir()
print(dir(obj)) # Includes 'x', 'y', 'custom_method'PythonCallable Objects
__call__(self, *args, **kwargs) – Make Object Callable
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return value * self.factor
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# Can be used as a function
numbers = [1, 2, 3, 4]
doubled = list(map(double, numbers))
print(doubled) # [2, 4, 6, 8]PythonFunction Decorators Using __call__
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call {self.count} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # Call 1 of greet
# Hello, Alice!
print(greet("Bob")) # Call 2 of greet
# Hello, Bob!PythonStateful Callable Objects
class RunningAverage:
def __init__(self):
self.total = 0
self.count = 0
def __call__(self, value):
self.total += value
self.count += 1
return self.total / self.count
avg = RunningAverage()
print(avg(10)) # 10.0
print(avg(20)) # 15.0
print(avg(30)) # 20.0PythonContext Managers
__enter__(self) and __exit__(self, exc_type, exc_val, exc_tb)
Used with the with statement for resource management.
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
print(f"Opening {self.filename}")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Closing {self.filename}")
if self.file:
self.file.close()
# Return False to propagate exceptions, True to suppress
if exc_type is not None:
print(f"Exception occurred: {exc_type.__name__}: {exc_val}")
return False # Don't suppress exceptions
# Usage
with FileManager('test.txt', 'w') as f:
f.write("Hello, World!")
# File is automatically closedPythonDatabase Transaction Context Manager
class DatabaseTransaction:
def __init__(self, connection):
self.connection = connection
def __enter__(self):
self.connection.begin_transaction()
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
# No exception, commit
self.connection.commit()
else:
# Exception occurred, rollback
self.connection.rollback()
return False # Propagate exceptions
# with DatabaseTransaction(db_conn) as conn:
# conn.execute("INSERT INTO ...")PythonTimer Context Manager
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.time()
self.elapsed = self.end - self.start
print(f"Elapsed time: {self.elapsed:.4f} seconds")
return False
with Timer():
# Code to time
sum(range(1000000))
# Elapsed time: 0.0234 secondsPythonSuppressing Exceptions
class SuppressException:
def __init__(self, *exceptions):
self.exceptions = exceptions
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# Return True to suppress the exception
return exc_type is not None and issubclass(exc_type, self.exceptions)
with SuppressException(FileNotFoundError, PermissionError):
with open('nonexistent.txt', 'r') as f:
data = f.read()
print("Continued execution") # This runs even if file doesn't existPythonDescriptor Protocol
Descriptors control attribute access on other objects. They implement __get__, __set__, and/or __delete__.
__get__(self, instance, owner) – Attribute Retrieval
class Descriptor:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name, None)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
class MyClass:
attr = Descriptor('attr')
obj = MyClass()
obj.attr = 42
print(obj.attr) # 42PythonValidated Descriptor
class TypedProperty:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError(
f"{self.name} must be {self.expected_type.__name__}, "
f"got {type(value).__name__}"
)
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
class Person:
name = TypedProperty('name', str)
age = TypedProperty('age', int)
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 30)
print(p.name, p.age) # Alice 30
# p.age = "thirty" # Raises TypeErrorPythonRead-Only Property Descriptor
class ReadOnly:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if self.name in instance.__dict__:
raise AttributeError(f"Cannot modify read-only attribute '{self.name}'")
instance.__dict__[self.name] = value
class Config:
api_key = ReadOnly('api_key')
def __init__(self, api_key):
self.api_key = api_key
config = Config("secret123")
print(config.api_key) # secret123
# config.api_key = "new" # Raises AttributeErrorPythonLazy Property Descriptor
class LazyProperty:
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, instance, owner):
if instance is None:
return self
# Compute value and cache it
value = self.func(instance)
setattr(instance, self.name, value)
return value
class DataProcessor:
def __init__(self, data):
self.data = data
@LazyProperty
def processed_data(self):
print("Processing data...")
return [x * 2 for x in self.data]
dp = DataProcessor([1, 2, 3, 4])
print(dp.processed_data) # Processing data... [2, 4, 6, 8]
print(dp.processed_data) # [2, 4, 6, 8] (no reprocessing)PythonCopying and Pickling
__copy__(self) – Shallow Copy
import copy
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __copy__(self):
print("Creating shallow copy")
return Point(self.x, self.y)
def __repr__(self):
return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = copy.copy(p1)
print(p2) # Point(1, 2)Python__deepcopy__(self, memo) – Deep Copy
class Node:
def __init__(self, value, children=None):
self.value = value
self.children = children or []
def __deepcopy__(self, memo):
print(f"Deep copying node {self.value}")
# Create new instance
new_node = Node(self.value)
# Deep copy children
new_node.children = copy.deepcopy(self.children, memo)
return new_node
def __repr__(self):
return f"Node({self.value})"
root = Node(1, [Node(2), Node(3)])
root_copy = copy.deepcopy(root)Python__getstate__(self) and __setstate__(self, state) – Pickling
import pickle
class Connection:
def __init__(self, host, port):
self.host = host
self.port = port
self.socket = None # Not picklable
def __getstate__(self):
# Return state to pickle (exclude socket)
state = self.__dict__.copy()
state['socket'] = None
return state
def __setstate__(self, state):
# Restore state from pickle
self.__dict__.update(state)
# Reconnect if needed
self.reconnect()
def reconnect(self):
print(f"Reconnecting to {self.host}:{self.port}")
conn = Connection("localhost", 8080)
data = pickle.dumps(conn)
conn2 = pickle.loads(data) # Reconnecting to localhost:8080Python__reduce__(self) – Advanced Pickling
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __reduce__(self):
# Return (callable, args) to recreate object
return (Point, (self.x, self.y))
p1 = Point(3, 4)
data = pickle.dumps(p1)
p2 = pickle.loads(data)
print(p2.x, p2.y) # 3 4PythonString Representation
__repr__(self) – Official String Representation
Should return a string that ideally could recreate the object.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(repr(p)) # Point(3, 4)
print(p) # Point(3, 4)Python__str__(self) – Informal String Representation
User-friendly string representation.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name={self.name!r}, age={self.age!r})"
def __str__(self):
return f"{self.name}, {self.age} years old"
p = Person("Alice", 30)
print(str(p)) # Alice, 30 years old
print(repr(p)) # Person(name='Alice', age=30)
print(p) # Alice, 30 years old (uses __str__)Python__bytes__(self) – Bytes Representation
class Data:
def __init__(self, content):
self.content = content
def __bytes__(self):
return self.content.encode('utf-8')
def __str__(self):
return self.content
d = Data("Hello")
print(bytes(d)) # b'Hello'PythonAdvanced Magic Methods
Bitwise Operators
__and__(self, other) – Bitwise AND (&)
class BitSet:
def __init__(self, value):
self.value = value
def __and__(self, other):
if isinstance(other, BitSet):
return BitSet(self.value & other.value)
return NotImplemented
def __repr__(self):
return f"BitSet({bin(self.value)})"
b1 = BitSet(0b1100)
b2 = BitSet(0b1010)
print(b1 & b2) # BitSet(0b1000)Python__or__(self, other) – Bitwise OR (|)
def __or__(self, other):
if isinstance(other, BitSet):
return BitSet(self.value | other.value)
return NotImplementedPython__xor__(self, other) – Bitwise XOR (^)
def __xor__(self, other):
if isinstance(other, BitSet):
return BitSet(self.value ^ other.value)
return NotImplementedPython__lshift__(self, other) – Left Shift (<<)
def __lshift__(self, n):
return BitSet(self.value << n)Python__rshift__(self, other) – Right Shift (>>)
def __rshift__(self, n):
return BitSet(self.value >> n)PythonReflected and In-place Bitwise Operators
Similar patterns exist for:
__rand__,__ror__,__rxor__,__rlshift__,__rrshift__(reflected)__iand__,__ior__,__ixor__,__ilshift__,__irshift__(in-place)
__sizeof__(self) – Memory Size
import sys
class CustomList:
def __init__(self, items):
self.items = items
def __sizeof__(self):
# Return size in bytes
return sys.getsizeof(self.items) + sys.getsizeof(self.__dict__)
cl = CustomList([1, 2, 3, 4, 5])
print(sys.getsizeof(cl)) # Uses __sizeof__Python__init_subclass__(cls, **kwargs) – Subclass Initialization
Called when a class is subclassed.
class PluginBase:
plugins = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.plugins.append(cls)
print(f"Registered plugin: {cls.__name__}")
class Plugin1(PluginBase):
pass # Registered plugin: Plugin1
class Plugin2(PluginBase):
pass # Registered plugin: Plugin2
print(PluginBase.plugins) # [<class 'Plugin1'>, <class 'Plugin2'>]Python__class_getitem__(cls, item) – Generic Class Syntax
Enables Class[type] syntax (like list[int] in type hints).
class GenericList:
def __class_getitem__(cls, item):
print(f"Creating generic list of {item}")
return cls
# Works with type hints
MyList = GenericList[int] # Creating generic list of <class 'int'>Python__prepare__(metacls, name, bases, **kwargs) – Metaclass Namespace
Returns the namespace dict for a new class.
class OrderedMeta(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
# Return OrderedDict to preserve definition order (not needed in Python 3.7+)
return {}
def __new__(metacls, name, bases, namespace, **kwargs):
result = super().__new__(metacls, name, bases, dict(namespace))
result.members = list(namespace.keys())
return result
class MyClass(metaclass=OrderedMeta):
a = 1
b = 2
c = 3
print(MyClass.members) # ['__module__', '__qualname__', 'a', 'b', 'c']Python__instancecheck__(self, instance) – Custom isinstance()
class AcceptAnything(type):
def __instancecheck__(cls, instance):
return True
class MyClass(metaclass=AcceptAnything):
pass
print(isinstance(42, MyClass)) # True
print(isinstance("hello", MyClass)) # TruePython__subclasscheck__(self, subclass) – Custom issubclass()
class MyMeta(type):
def __subclasscheck__(cls, subclass):
# Custom logic for subclass checking
return hasattr(subclass, 'special_method')
class MyBase(metaclass=MyMeta):
pass
class MySubclass:
def special_method(self):
pass
print(issubclass(MySubclass, MyBase)) # TruePython__set_name__(self, owner, name) – Descriptor Name Assignment
Called when a descriptor is assigned to a class attribute.
class NamedDescriptor:
def __set_name__(self, owner, name):
self.name = name
self.private_name = '_' + name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.private_name, None)
def __set__(self, instance, value):
setattr(instance, self.private_name, value)
class MyClass:
attr = NamedDescriptor()
obj = MyClass()
obj.attr = 42
print(obj.attr) # 42
print(obj._attr) # 42PythonPractical Examples
Example 1: Complex Number Class
import math
class Complex:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __add__(self, other):
if isinstance(other, Complex):
return Complex(self.real + other.real, self.imag + other.imag)
elif isinstance(other, (int, float)):
return Complex(self.real + other, self.imag)
return NotImplemented
def __sub__(self, other):
if isinstance(other, Complex):
return Complex(self.real - other.real, self.imag - other.imag)
elif isinstance(other, (int, float)):
return Complex(self.real - other, self.imag)
return NotImplemented
def __mul__(self, other):
if isinstance(other, Complex):
real = self.real * other.real - self.imag * other.imag
imag = self.real * other.imag + self.imag * other.real
return Complex(real, imag)
elif isinstance(other, (int, float)):
return Complex(self.real * other, self.imag * other)
return NotImplemented
def __abs__(self):
return math.sqrt(self.real**2 + self.imag**2)
def __eq__(self, other):
if isinstance(other, Complex):
return self.real == other.real and self.imag == other.imag
return NotImplemented
def __repr__(self):
sign = '+' if self.imag >= 0 else '-'
return f"{self.real}{sign}{abs(self.imag)}j"
def __str__(self):
return repr(self)
# Usage
c1 = Complex(3, 4)
c2 = Complex(1, 2)
print(c1 + c2) # 4+6j
print(c1 * c2) # -5+10j
print(abs(c1)) # 5.0PythonExample 2: Custom Dictionary with Default Values
class DefaultDict(dict):
def __init__(self, default_factory, *args, **kwargs):
super().__init__(*args, **kwargs)
self.default_factory = default_factory
def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
self[key] = value = self.default_factory()
return value
def __repr__(self):
return f"DefaultDict({self.default_factory}, {dict.__repr__(self)})"
# Usage
dd = DefaultDict(list)
dd['fruits'].append('apple')
dd['fruits'].append('banana')
dd['vegetables'].append('carrot')
print(dd) # DefaultDict(<class 'list'>, {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']})PythonExample 3: Chainable Query Builder
class Query:
def __init__(self, items=None):
self.items = items or []
def filter(self, predicate):
return Query([item for item in self.items if predicate(item)])
def map(self, func):
return Query([func(item) for item in self.items])
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
def __repr__(self):
return f"Query({self.items})"
# Usage
data = Query([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
result = data.filter(lambda x: x % 2 == 0).map(lambda x: x ** 2)
print(list(result)) # [4, 16, 36, 64, 100]PythonExample 4: Fluent Interface Builder
class EmailBuilder:
def __init__(self):
self._to = None
self._subject = None
self._body = None
def to(self, recipient):
self._to = recipient
return self
def subject(self, subject):
self._subject = subject
return self
def body(self, body):
self._body = body
return self
def __str__(self):
return f"To: {self._to}\nSubject: {self._subject}\n\n{self._body}"
def __repr__(self):
return f"EmailBuilder(to={self._to!r}, subject={self._subject!r})"
# Usage
email = (EmailBuilder()
.to("user@example.com")
.subject("Hello")
.body("This is a test email"))
print(email)PythonExample 5: Resource Pool with Context Manager
from queue import Queue
import time
class ConnectionPool:
def __init__(self, size):
self.size = size
self.pool = Queue(maxsize=size)
for i in range(size):
self.pool.put(f"Connection-{i}")
def acquire(self):
print("Acquiring connection...")
return self.pool.get()
def release(self, connection):
print(f"Releasing {connection}")
self.pool.put(connection)
def __enter__(self):
self.connection = self.acquire()
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
self.release(self.connection)
return False
# Usage
pool = ConnectionPool(3)
with pool as conn:
print(f"Using {conn}")
time.sleep(0.1)
# Connection automatically releasedPythonBest Practices
1. Always Return NotImplemented for Unsupported Operations
def __add__(self, other):
if isinstance(other, MyClass):
# Implementation
pass
return NotImplemented # Not TypeError or NotImplementedErrorPython2. Implement Both __eq__ and __hash__ for Hashable Objects
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))Python3. Make __repr__ Unambiguous and Useful
# Good
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
# Better (can recreate object)
def __repr__(self):
return f"Point({self.x}, {self.y})"Python4. Use __str__ for User-Friendly Output
def __repr__(self):
return f"Person(name={self.name!r}, age={self.age})"
def __str__(self):
return f"{self.name} ({self.age} years old)"Python5. Be Careful with __getattribute__
Avoid infinite recursion by using super().__getattribute__():
def __getattribute__(self, name):
# Don't do: return self.something (infinite recursion)
# Do:
return super().__getattribute__(name)Python6. Context Managers Should Always Clean Up
def __exit__(self, exc_type, exc_val, exc_tb):
# Always clean up, even if exception occurred
self.cleanup()
return False # Don't suppress exceptions unless intendedPython7. Make Augmented Assignment Return self
def __iadd__(self, other):
# Modify in place
self.value += other.value
return self # Must return selfPython8. Use Type Checking
def __add__(self, other):
if not isinstance(other, MyClass):
return NotImplemented
# ImplementationPython9. Document Magic Method Behavior
class Vector:
"""A 2D vector class.
Supports addition, subtraction, multiplication by scalar,
and comparison operations.
"""
def __add__(self, other):
"""Add two vectors component-wise."""
# ImplementationPython10. Consider Using @functools.total_ordering
from functools import total_ordering
@total_ordering
class Version:
def __eq__(self, other):
# Implementation
def __lt__(self, other):
# Implementation
# Other comparison methods auto-generatedPythonCommon Pitfalls
1. Modifying Mutable Default Arguments
# Bad
def __init__(self, items=[]):
self.items = items # All instances share same list!
# Good
def __init__(self, items=None):
self.items = items if items is not None else []Python2. Forgetting to Return self in Augmented Assignment
# Bad
def __iadd__(self, other):
self.value += other.value
# Forgot to return self!
# Good
def __iadd__(self, other):
self.value += other.value
return selfPython3. Raising Wrong Exception Types
# Bad
def __add__(self, other):
if not isinstance(other, MyClass):
raise TypeError("Cannot add")
# Good
def __add__(self, other):
if not isinstance(other, MyClass):
return NotImplementedPython4. Making Objects Unhashable Unintentionally
class Point:
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# Forgot __hash__! Now Point is unhashable
# Python sets __hash__ = None when __eq__ is definedPython5. Infinite Recursion in __setattr__
# Bad
def __setattr__(self, name, value):
self.name = value # Infinite recursion!
# Good
def __setattr__(self, name, value):
super().__setattr__(name, value)
# Or: self.__dict__[name] = valuePythonPerformance Optimization
__slots__ – Memory Optimization
Reduce memory footprint by 40-50% and speed up attribute access.
from typing import ClassVar
import sys
class WithoutSlots:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
class WithSlots:
__slots__ = ('x', 'y') # Define allowed attributes
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
# Memory comparison
w1 = WithoutSlots(1, 2)
w2 = WithSlots(1, 2)
print(sys.getsizeof(w1.__dict__)) # ~240 bytes
print(sys.getsizeof(w2)) # ~64 bytes
# Performance: attribute access is 10-15% faster with slotsPythonSlots Best Practices:
from typing import ClassVar, Any
class OptimizedClass:
"""Production-ready slots usage."""
__slots__ = ('_x', '_y', '__weakref__') # Include __weakref__ for weak references
_cache: ClassVar[dict] = {} # Class variables need ClassVar
def __init__(self, x: int, y: int) -> None:
self._x = x
self._y = y
def __getstate__(self) -> dict[str, Any]:
"""Needed for pickling with slots."""
return {slot: getattr(self, slot) for slot in self.__slots__ if hasattr(self, slot)}
def __setstate__(self, state: dict[str, Any]) -> None:
"""Needed for unpickling with slots."""
for slot, value in state.items():
setattr(self, slot, value)PythonLazy Evaluation Pattern
from typing import Optional, Callable, Any
from functools import wraps
class LazyAttribute:
"""Descriptor for lazy evaluation with caching."""
def __init__(self, func: Callable) -> None:
self.func = func
self.name = func.__name__
wraps(func)(self)
def __get__(self, instance: Any, owner: type) -> Any:
if instance is None:
return self
# Compute and cache
value = self.func(instance)
setattr(instance, self.name, value) # Replace descriptor with value
return value
class DataAnalyzer:
def __init__(self, data: list[int]) -> None:
self.data = data
@LazyAttribute
def expensive_calculation(self) -> float:
"""Only computed when first accessed."""
print("Computing...")
return sum(x**2 for x in self.data) / len(self.data)
analyzer = DataAnalyzer([1, 2, 3, 4, 5])
print(analyzer.expensive_calculation) # Computing... 11.0
print(analyzer.expensive_calculation) # 11.0 (no recomputation)PythonType Hints & Protocols
Structural Subtyping with Protocols
from typing import Protocol, TypeVar, runtime_checkable
from collections.abc import Iterator
T = TypeVar('T', bound='Comparable')
@runtime_checkable
class Comparable(Protocol):
"""Protocol for comparable objects."""
def __lt__(self, other: 'Comparable') -> bool: ...
def __le__(self, other: 'Comparable') -> bool: ...
def __gt__(self, other: 'Comparable') -> bool: ...
def __ge__(self, other: 'Comparable') -> bool: ...
def find_max(items: list[Comparable]) -> Comparable:
"""Works with any comparable type without inheritance."""
return max(items)
# Works with built-in types
print(find_max([1, 5, 3])) # 5
print(find_max(['a', 'z', 'b'])) # 'z'
# Works with custom types
class Score:
def __init__(self, value: int) -> None:
self.value = value
def __lt__(self, other: 'Score') -> bool:
return self.value < other.value
def __le__(self, other: 'Score') -> bool:
return self.value <= other.value
def __gt__(self, other: 'Score') -> bool:
return self.value > other.value
def __ge__(self, other: 'Score') -> bool:
return self.value >= other.value
print(isinstance(Score(10), Comparable)) # True (runtime check)PythonGeneric Magic Methods
from typing import Generic, TypeVar, Callable
from collections.abc import Iterator
T = TypeVar('T')
U = TypeVar('U')
class Container(Generic[T]):
"""Generic container with type-safe magic methods."""
def __init__(self, items: list[T]) -> None:
self._items = items
def __getitem__(self, index: int) -> T:
return self._items[index]
def __iter__(self) -> Iterator[T]:
return iter(self._items)
def __len__(self) -> int:
return len(self._items)
def map(self, func: Callable[[T], U]) -> 'Container[U]':
"""Type-safe map operation."""
return Container([func(item) for item in self._items])
# Type checker knows the types
int_container: Container[int] = Container([1, 2, 3])
str_container: Container[str] = int_container.map(str)
print(str_container._items) # ['1', '2', '3']PythonAsync Magic Methods
Async Context Managers
import asyncio
from typing import Optional, Any
class AsyncFileManager:
"""Async context manager for file operations."""
def __init__(self, filename: str, mode: str = 'r') -> None:
self.filename = filename
self.mode = mode
self.file: Optional[Any] = None
async def __aenter__(self) -> Any:
"""Async version of __enter__."""
print(f"Opening {self.filename}")
# Simulating async file open
await asyncio.sleep(0.1)
self.file = open(self.filename, self.mode)
return self.file
async def __aexit__(
self,
exc_type: Optional[type],
exc_val: Optional[Exception],
exc_tb: Optional[Any]
) -> bool:
"""Async version of __exit__."""
if self.file:
self.file.close()
print(f"Closed {self.filename}")
return False
# Usage
async def process_file():
async with AsyncFileManager('data.txt', 'w') as f:
f.write("Hello, async world!")
# asyncio.run(process_file())PythonAsync Iterators
import asyncio
from typing import AsyncIterator
class AsyncRange:
"""Async iterator example."""
def __init__(self, start: int, end: int, delay: float = 0.1) -> None:
self.start = start
self.end = end
self.delay = delay
self.current = start
def __aiter__(self) -> 'AsyncRange':
"""Return async iterator."""
return self
async def __anext__(self) -> int:
"""Async version of __next__."""
if self.current >= self.end:
raise StopAsyncIteration
await asyncio.sleep(self.delay)
value = self.current
self.current += 1
return value
# Usage
async def main():
async for num in AsyncRange(0, 5):
print(num)
# asyncio.run(main())PythonPattern Matching (Python 3.10+)
__match_args__ for Structural Pattern Matching
from dataclasses import dataclass
from typing import Union
@dataclass
class Point:
__match_args__ = ('x', 'y') # Enable positional matching
x: float
y: float
@dataclass
class Circle:
__match_args__ = ('center', 'radius')
center: Point
radius: float
@dataclass
class Rectangle:
__match_args__ = ('top_left', 'width', 'height')
top_left: Point
width: float
height: float
Shape = Union[Circle, Rectangle, Point]
def describe_shape(shape: Shape) -> str:
"""Use pattern matching with custom classes."""
match shape:
case Point(0, 0):
return "Origin point"
case Point(x, 0):
return f"Point on X-axis at {x}"
case Point(0, y):
return f"Point on Y-axis at {y}"
case Point(x, y):
return f"Point at ({x}, {y})"
case Circle(Point(0, 0), r):
return f"Circle at origin with radius {r}"
case Circle(center, radius):
return f"Circle at {center} with radius {radius}"
case Rectangle(Point(x, y), w, h):
return f"Rectangle at ({x}, {y}) with size {w}x{h}"
case _:
return "Unknown shape"
# Test pattern matching
print(describe_shape(Point(0, 0))) # Origin point
print(describe_shape(Circle(Point(0, 0), 5))) # Circle at origin with radius 5PythonProduction Best Practices
1. Defensive Magic Method Implementation
from typing import Any
import logging
logger = logging.getLogger(__name__)
class RobustClass:
"""Production-ready magic method implementation."""
def __init__(self, value: int) -> None:
self._value = value
def __add__(self, other: Any) -> 'RobustClass':
"""Defensive addition with logging."""
if not isinstance(other, (RobustClass, int, float)):
logger.warning(f"Unsupported type for addition: {type(other)}")
return NotImplemented
try:
if isinstance(other, RobustClass):
result = self._value + other._value
else:
result = self._value + other
return RobustClass(result)
except (OverflowError, ValueError) as e:
logger.error(f"Addition failed: {e}")
raise
def __repr__(self) -> str:
"""Safe repr that never raises."""
try:
return f"RobustClass({self._value!r})"
except Exception as e:
logger.error(f"__repr__ failed: {e}")
return f"<RobustClass object at {hex(id(self))}>"
def __eq__(self, other: Any) -> bool:
"""Type-safe equality."""
if not isinstance(other, RobustClass):
return NotImplemented
return self._value == other._value
def __hash__(self) -> int:
"""Consistent hash with __eq__."""
return hash(self._value)Python2. Thread-Safe Magic Methods
import threading
from typing import Any
from contextlib import contextmanager
class ThreadSafeCounter:
"""Thread-safe counter with magic methods."""
def __init__(self, initial: int = 0) -> None:
self._value = initial
self._lock = threading.RLock() # Reentrant lock
@contextmanager
def _synchronized(self):
"""Context manager for thread-safe operations."""
self._lock.acquire()
try:
yield
finally:
self._lock.release()
def __iadd__(self, other: int) -> 'ThreadSafeCounter':
with self._synchronized():
self._value += other
return self
def __int__(self) -> int:
with self._synchronized():
return self._value
def __repr__(self) -> str:
with self._synchronized():
return f"ThreadSafeCounter({self._value})"
# Usage in multithreaded environment
counter = ThreadSafeCounter()
def increment():
for _ in range(1000):
counter += 1
threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(int(counter)) # 10000 (always correct due to locking)Python3. Comprehensive Documentation Standards
from typing import Union
class Vector:
"""
Immutable 2D vector with comprehensive magic method implementation.
This class implements the full suite of arithmetic, comparison,
and container protocols for production use.
Attributes:
x: X-coordinate of the vector
y: Y-coordinate of the vector
Examples:
>>> v1 = Vector(1, 2)
>>> v2 = Vector(3, 4)
>>> v3 = v1 + v2
>>> print(v3)
Vector(4, 6)
>>> abs(v1)
2.23606797749979
Note:
All operations return new Vector instances (immutable).
"""
__slots__ = ('_x', '_y')
def __init__(self, x: float, y: float) -> None:
"""
Initialize a new Vector.
Args:
x: X-coordinate
y: Y-coordinate
Raises:
TypeError: If x or y are not numeric
"""
if not isinstance(x, (int, float)) or not isinstance(y, (int, float)):
raise TypeError(f"Coordinates must be numeric, got {type(x)}, {type(y)}")
object.__setattr__(self, '_x', float(x))
object.__setattr__(self, '_y', float(y))
def __add__(self, other: Union['Vector', tuple[float, float]]) -> 'Vector':
"""
Add two vectors or a vector and a tuple.
Args:
other: Vector or (x, y) tuple to add
Returns:
New Vector representing the sum
Raises:
TypeError: If other is not a Vector or tuple
Examples:
>>> Vector(1, 2) + Vector(3, 4)
Vector(4, 6)
"""
if isinstance(other, Vector):
return Vector(self._x + other._x, self._y + other._y)
elif isinstance(other, tuple) and len(other) == 2:
return Vector(self._x + other[0], self._y + other[1])
return NotImplemented
def __repr__(self) -> str:
"""Return unambiguous string representation."""
return f"Vector({self._x}, {self._y})"PythonSummary
Summary
Magic methods are powerful tools that allow you to:
- Customize object behavior with Python’s built-in operations
- Create intuitive APIs that feel natural to Python developers
- Integrate seamlessly with Python’s standard library
- Implement design patterns elegantly
- Optimize performance with slots and lazy evaluation
- Enable type safety with protocols and generics
- Support async operations with async magic methods
- Implement pattern matching with
__match_args__
Key categories:
- Lifecycle:
__init__,__new__,__del__,__post_init__ - Representation:
__repr__,__str__,__format__,__bytes__ - Comparison:
__eq__,__lt__,__gt__,__le__,__ge__,__ne__ - Arithmetic:
__add__,__sub__,__mul__,__truediv__,__floordiv__,__mod__,__pow__ - Containers:
__len__,__getitem__,__setitem__,__contains__,__iter__,__reversed__ - Attributes:
__getattr__,__setattr__,__delattr__,__getattribute__,__dir__ - Callables:
__call__ - Context Managers:
__enter__,__exit__,__aenter__,__aexit__ - Descriptors:
__get__,__set__,__delete__,__set_name__ - Async:
__aiter__,__anext__,__aenter__,__aexit__ - Memory:
__slots__,__sizeof__ - Pattern Matching:
__match_args__
Senior Engineer Checklist:
- ✅ Return
NotImplementedfor unsupported operations - ✅ Implement
__hash__when implementing__eq__ - ✅ Use
super()to avoid infinite recursion - ✅ Add comprehensive type hints
- ✅ Document all magic methods with examples
- ✅ Test edge cases thoroughly (including thread safety)
- ✅ Profile performance for hot paths
- ✅ Use
__slots__for memory-intensive classes - ✅ Implement protocols for structural subtyping
- ✅ Consider immutability (frozen dataclasses)
- ✅ Use async variants for I/O-bound operations
- ✅ Add logging for debugging
- ✅ Implement defensive coding practices
- ✅ Follow PEP 8 and type checking (mypy/pyright)
Additional Resources:
Mastering magic methods will help you write more Pythonic, elegant, performant, and maintainable code at scale!
Quick Reference Card
Object Lifecycle
__new__(cls, ...) # Object creation (before __init__)
__init__(self, ...) # Object initialization
__del__(self) # Object destruction (finalizer)
__post_init__(self) # Dataclass post-initializationPythonString Representation
__repr__(self) # Official representation (for developers)
__str__(self) # Informal representation (for users)
__format__(self, spec) # Custom formatting (f-strings)
__bytes__(self) # Bytes representationPythonComparison Operators
__eq__(self, other) # ==
__ne__(self, other) # !=
__lt__(self, other) # <
__le__(self, other) # <=
__gt__(self, other) # >
__ge__(self, other) # >=
__hash__(self) # hash() - needed for sets/dictsPythonArithmetic Operators
__add__(self, other) # +
__sub__(self, other) # -
__mul__(self, other) # *
__matmul__(self, other) # @ (matrix multiplication)
__truediv__(self, other) # /
__floordiv__(self, other)# //
__mod__(self, other) # %
__pow__(self, other) # **
__divmod__(self, other) # divmod()PythonReflected Operators (right-hand side)
__radd__, __rsub__, __rmul__, __rtruediv__, __rfloordiv__, __rmod__, __rpow__PythonAugmented Assignment
__iadd__(self, other) # +=
__isub__(self, other) # -=
__imul__(self, other) # *=
__itruediv__(self, other)# /=
__ifloordiv__(self, other)# //=
__imod__(self, other) # %=
__ipow__(self, other) # **=PythonUnary Operators
__neg__(self) # -x
__pos__(self) # +x
__abs__(self) # abs(x)
__invert__(self) # ~x
__round__(self, n) # round(x, n)
__floor__(self) # math.floor(x)
__ceil__(self) # math.ceil(x)
__trunc__(self) # math.trunc(x)PythonBitwise Operators
__and__(self, other) # &
__or__(self, other) # |
__xor__(self, other) # ^
__lshift__(self, other) # <<
__rshift__(self, other) # >>PythonType Conversion
__int__(self) # int(x)
__float__(self) # float(x)
__complex__(self) # complex(x)
__bool__(self) # bool(x), if x:
__index__(self) # operator.index(x)PythonContainer Emulation
__len__(self) # len(x)
__getitem__(self, key) # x[key]
__setitem__(self, key, value) # x[key] = value
__delitem__(self, key) # del x[key]
__contains__(self, item) # item in x
__iter__(self) # for i in x:
__reversed__(self) # reversed(x)
__missing__(self, key) # dict subclass, missing keyPythonAttribute Access
__getattr__(self, name) # x.name (when not found normally)
__setattr__(self, name, value) # x.name = value
__delattr__(self, name) # del x.name
__getattribute__(self, name) # x.name (always called)
__dir__(self) # dir(x)PythonDescriptors
__get__(self, instance, owner) # Descriptor getter
__set__(self, instance, value) # Descriptor setter
__delete__(self, instance) # Descriptor deleter
__set_name__(self, owner, name) # Descriptor name assignmentPythonCallable Objects
__call__(self, ...) # x(args)PythonContext Managers
__enter__(self) # with x:
__exit__(self, exc_type, exc_val, exc_tb) # End of with blockPythonAsync Context Managers & Iterators
__aenter__(self) # async with x:
__aexit__(self, ...) # End of async with block
__aiter__(self) # async for i in x:
__anext__(self) # Next item in async iterationPythonCopying & Serialization
__copy__(self) # copy.copy(x)
__deepcopy__(self, memo) # copy.deepcopy(x)
__getstate__(self) # pickle.dump(x)
__setstate__(self, state)# pickle.load()
__reduce__(self) # Advanced pickle control
__reduce_ex__(self, protocol) # Protocol-specific picklingPythonMemory & Introspection
__sizeof__(self) # sys.getsizeof(x)
__slots__ # Class attribute for memory optimizationPythonPattern Matching (3.10+)
__match_args__ # Class attribute for positional matchingPythonMetaclass Methods
__init_subclass__(cls, **kwargs) # Called when subclassed
__class_getitem__(cls, item) # Class[Type] syntax
__prepare__(mcs, name, bases) # Metaclass namespace creation
__instancecheck__(cls, instance) # isinstance() behavior
__subclasscheck__(cls, subclass) # issubclass() behaviorPythonCommon Patterns Quick Reference
Immutable Object:
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: floatPythonComparable Object:
from functools import total_ordering
@total_ordering
class Item:
def __eq__(self, other): ...
def __lt__(self, other): ...PythonHashable Object:
def __eq__(self, other): return self.x == other.x
def __hash__(self): return hash(self.x)PythonContext Manager:
def __enter__(self): return resource
def __exit__(self, *args): cleanup(); return FalsePythonIterator:
def __iter__(self): return self
def __next__(self):
if done: raise StopIteration
return next_itemPythonCallable:
def __call__(self, *args, **kwargs): return resultPythonEnd of Guide – Happy Pythoning! 🐍
Discover more from Altgr Blog
Subscribe to get the latest posts sent to your email.
