Python Notes
Master Python
🧠 Real-World Analogy — Superpowers
Think of a superhero suit — Tony Stark's Iron Man armor. The suit itself is an object. When Tony raises both hands, repulsors fire. When he raises one hand, a laser fires. Each specific gesture triggers a different ability. These special gestures are like Python's magic methods — special methods that automatically trigger when you use specific Python syntax or built-in functions. They start and end with double underscores (dunder = double underscore): __method__1️⃣ __init__ — Constructor / Initializer
class Book:
def __init__(self, title, author, pages, price):
"""Called automatically when Book(...) is created."""
self.title = title
self.author = author
self.pages = pages
self.price = price
self._id = id(self) # unique Python object id
# We'll add more dunders below...Already covered in detail in constructors.mdx. Here we focus on how it interacts with other magic methods.2️⃣ __str__ and __repr__
Output:
Rule of thumb:__repr__should return a string that, when passed toeval(), recreates the object.__str__is for end users.
3️⃣ __len__ — Length
Output:
| Empty | False |
| Len | 0 |
| Len | 3 |
| Empty | True |
4️⃣ __add__, __sub__, __mul__ — Arithmetic
class Fraction:
"""Rational number with full arithmetic support."""
from math import gcd as _gcd
def __init__(self, numerator, denominator=1):
if denominator == 0:
raise ZeroDivisionError("Denominator cannot be zero.")
from math import gcd
common = gcd(abs(numerator), abs(denominator))
sign = -1 if denominator < 0 else 1
self.num = sign * numerator // common
self.den = sign * denominator // common
def __add__(self, other): # self + other
if isinstance(other, int):
other = Fraction(other)
return Fraction(self.num * other.den + other.num * self.den,
self.den * other.den)
def __radd__(self, other): # other + self (e.g., 1 + Fraction)
return self.__add__(other)
def __sub__(self, other): # self - other
if isinstance(other, int):
other = Fraction(other)
return Fraction(self.num * other.den - other.num * self.den,
self.den * other.den)
def __mul__(self, other): # self * other
if isinstance(other, int):
other = Fraction(other)
return Fraction(self.num * other.num, self.den * other.den)
def __truediv__(self, other): # self / other
if isinstance(other, int):
other = Fraction(other)
return Fraction(self.num * other.den, self.den * other.num)
def __neg__(self): # -self
return Fraction(-self.num, self.den)
def __abs__(self): # abs(self)
return Fraction(abs(self.num), self.den)
def __float__(self): # float(self)
return self.num / self.den
def __str__(self):
if self.den == 1: return str(self.num)
return f"{self.num}/{self.den}"
def __repr__(self):
return f"Fraction({self.num}, {self.den})"
a = Fraction(1, 2) # 1/2
b = Fraction(1, 3) # 1/3
c = Fraction(3, 4) # 3/4
print(f"a = {a}")
print(f"b = {b}")
print(f"a + b = {a + b}") # 1/2 + 1/3 = 5/6
print(f"a - b = {a - b}") # 1/2 - 1/3 = 1/6
print(f"a * b = {a * b}") # 1/2 * 1/3 = 1/6
print(f"a / b = {a / b}") # 1/2 / 1/3 = 3/2
print(f"a + 1 = {a + 1}") # 1/2 + 1 = 3/2
print(f"1 + a = {1 + a}") # uses __radd__
print(f"-a = {-a}")
print(f"abs(-a) = {abs(-a)}")
print(f"float(a) = {float(a)}")
print(f"a + b + c = {a + b + c}") # chained additionOutput:
5️⃣ __eq__, __lt__ — Comparison (Rich Comparison)
Output:
| Arjun (GPA | 8.90) |
| Priya (GPA | 9.40) |
| Rahul (GPA | 7.50) |
| Sneha (GPA | 9.40) |
| Vikas (GPA | 8.10) |
| Sorted (high | low GPA): |
| Priya (GPA | 9.40) |
| Sneha (GPA | 9.40) |
| Arjun (GPA | 8.90) |
| Vikas (GPA | 8.10) |
| Rahul (GPA | 7.50) |
| Top student | Priya (GPA: 9.40) |
| Arjun (GPA | 8.90) == Priya (GPA: 9.40): False |
| Arjun (GPA | 8.90) < Priya (GPA: 9.40): True |
| Arjun (GPA | 8.90) > Priya (GPA: 9.40): False |
| Arjun (GPA | 8.90) <= Priya (GPA: 9.40): True |
| Arjun (GPA | 8.90) >= Priya (GPA: 9.40): False |
| Top students (GPA ≥ 9.0) | {Student('Priya', 9.4), Student('Sneha', 9.4)} |
6️⃣ __getitem__, __setitem__, __delitem__ — Container Protocol
Output:
7️⃣ __iter__ and __next__ — Iterator Protocol
Output:
| Even numbers | [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] |
| 10 in evens | True |
| 7 in evens | False |
| len(evens) | 10 |
| Countdown | 5 4 3 2 1 🚀 |
| Squares | [1, 4, 9, 16, 25] |
8️⃣ __call__ — Make Objects Callable
9️⃣ __enter__ and __exit__ — Context Manager
class DatabaseTransaction:
"""Context manager for database transactions."""
def __init__(self, db_name):
self.db_name = db_name
self._operations = []
self._committed = False
def __enter__(self):
print(f"🔓 BEGIN TRANSACTION on '{self.db_name}'")
return self # returned as 'as' variable
def execute(self, sql):
self._operations.append(sql)
print(f" 📝 Queued: {sql}")
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
# No exception — commit
print(f" ✅ COMMIT: {len(self._operations)} operations committed.")
self._committed = True
else:
# Exception occurred — rollback
print(f" ❌ ROLLBACK: {exc_type.__name__}: {exc_val}")
self._operations.clear()
print(f"🔒 END TRANSACTION on '{self.db_name}'")
return True # suppress exception (return False to propagate)
# Successful transaction
with DatabaseTransaction("users_db") as tx:
tx.execute("INSERT INTO users (name) VALUES ('Arjun')")
tx.execute("UPDATE balance SET amount = 5000 WHERE user_id = 1")
print()
# Failed transaction — auto rollback
with DatabaseTransaction("orders_db") as tx:
tx.execute("INSERT INTO orders VALUES (101, 'Laptop')")
raise ValueError("Payment gateway timeout!")
tx.execute("UPDATE inventory SET qty = qty - 1") # never reached
print("\nProgram continues after handled exception ✅")Output:
| 📝 Queued | INSERT INTO users (name) VALUES ('Arjun') |
| 📝 Queued | UPDATE balance SET amount = 5000 WHERE user_id = 1 |
| ✅ COMMIT | 2 operations committed. |
| 📝 Queued | INSERT INTO orders VALUES (101, 'Laptop') |
| ❌ ROLLBACK | ValueError: Payment gateway timeout! |
🏆 Complete Example — ShoppingCart
Output:
| 'USB-C Hub' in cart | True |
| cart[0] | Python Mastery Book ₹ 599.00 × 2 = ₹ 1198.00 |
| Most expensive | Mechanical Keyboard ₹ 3499.00 × 1 = ₹ 3499.00 |
🇮🇳 Hindi Explanation (हिंदी में समझें)
Magic methods kya hain? Magic methods (dunder methods) are special methods that Python calls automatically when you perform specific operations — like using+, callinglen(), or running aforloop. They let you define how your objects behave with Python's built-in syntax.
What is the difference between__str__and__repr__?__str__provides user-readable output (what shows up in print).__repr__provides developer-oriented technical output — ideally a string that can recreate the object viaeval().
Why do we use__iter__and__next__? By defining these, you can use your class objects inforloops,list(),sum()and everywhere else.__iter__returns the iterator,__next__yields one value at a time, and raisesStopIterationat the end.
@total_orderingkya karta hai? If you only define__eq__and__lt__,@total_orderingautomatically generates<=,>,>=for you. Very convenient!
❓ Interview Questions
Q1: What are dunder/magic methods? Give 5 examples.
Answer: Magic methods are special methods with double underscores (__method__) that Python calls automatically for specific operations:
__init__: object construction__str__: string representation (print())__len__: length (len())__add__: addition (+)__iter__: iteration (forloop)
Q2: What is the difference between __str__ and __repr__?
Answer:
__str__: User-friendly string — used byprint(),str(). Should be readable.__repr__: Developer-facing, unambiguous string — used byrepr(), REPL, list display. Should ideally recreate the object viaeval().
If __str__ is not defined, Python falls back to __repr__.
Q3: When do you need to define __hash__ along with __eq__?
Answer: When you define __eq__, Python automatically sets __hash__ = None, making objects unhashable (can't be used in sets or as dict keys). To allow hashing, explicitly define __hash__. A good rule: hash() should be consistent with equality: a == b ⟹ hash(a) == hash(b).
Q4: What does __call__ do? Give a use case.
Answer: __call__ makes an instance callable like a function (obj(args)). Use cases: callable objects with state (memoization, counters), function decorators as classes, strategy pattern objects.
Q5: Explain the context manager protocol (__enter__, __exit__).
Answer:
__enter__: Called whenwithblock starts. Returns the object bound toas.__exit__(exc_type, exc_val, exc_tb): Called when block exits (normally or via exception). ReturnTrueto suppress the exception;False/Noneto propagate it.
Use cases: file handling, database transactions, lock management.
Q6: What is @total_ordering?
Answer: A class decorator from functools. If you define __eq__ and one of (__lt__, __le__, __gt__, __ge__), @total_ordering automatically generates the remaining comparison methods. Saves writing 4 extra methods.
Q7: What is __contains__? How does it relate to __iter__?
Answer: __contains__ is called when you use item in obj. If not defined, Python falls back to iterating with __iter__ and checking each element. Define __contains__ for O(1) membership testing (e.g., hash-based lookup) instead of O(n) linear scan.
Q8: What happens if __iter__ returns self vs a new iterator?
Answer:
- Returns
self: The object is both iterable AND iterator. Can only be iterated once (callingforloop again restarts only if__iter__resets state). - Returns new object: Iterable produces fresh iterators each time — multiple independent loops work correctly.
Best practice for containers: __iter__ returns iter(self._data) (fresh iterator each call).
📊 Complete Magic Methods Reference
| Category | Method | Triggered by |
|---|---|---|
| Construction | __new__, __init__, __del__ | Class(), garbage collection |
| Representation | __str__, __repr__, __format__ | print(), repr(), f-strings |
| Numeric | __int__, __float__, __bool__, __abs__, __neg__ | int(), float(), bool(), abs(), unary - |
| Arithmetic | __add__, __sub__, __mul__, __truediv__, __mod__, __pow__ | +, -, *, /, %, ** |
| Comparison | __eq__, __ne__, __lt__, __le__, __gt__, __ge__ | ==, !=, <, <=, >, >= |
| Container | __len__, __getitem__, __setitem__, __delitem__, __contains__ | len(), obj[k], obj[k]=v, del obj[k], in |
| Iteration | __iter__, __next__ | for, iter(), next() |
| Context | __enter__, __exit__ | with statement |
| Callable | __call__ | obj(args) |
| Attribute | __getattr__, __setattr__, __delattr__ | obj.x, obj.x=v, del obj.x |
| Hashing | __hash__ | hash(), dict keys, sets |
🧪 Practice Exercises
- Money Class: Implement
Money(amount, currency)with__add__,__sub__,__mul__,__eq__,__lt__,__str__,__repr__. RaiseTypeErrorfor cross-currency ops. - Inventory System: Build
Inventorywith__getitem__(by name),__setitem__,__delitem__,__contains__,__iter__,__len__,__str__. - Polynomial: Create a
Polynomialclass with coefficient list. Implement__add__,__mul__,__call__(evaluate at x),__str__(format nicely like "3x² + 2x + 1"). - Lazy File Reader: Implement
LazyFileReader(filename)as an iterator —__iter__opens the file,__next__yields one line at a time, closes onStopIteration. - Rate Limiter (callable): Create a
RateLimiter(max_calls, period)callable class that allows at mostmax_callsinvocations perperiodseconds, raisingRuntimeErrorotherwise.
🎉 Congratulations! You've completed the entire OOP section of the Python Master Course. What's Next? → File I/O, Exception Handling, Decorators, Generators, and Python Standard Library!
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Magic Methods (Dunder Methods) in Python.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Python Master Course topic.
Search Terms
python-master-course, python master course, python, master, course, object, oriented, programming
Related Python Master Course Topics