The Story That Explains Operator Overloading ➕Operator Overloading समझाने वाली कहानी ➕一个讲清运算符重载的故事 ➕
5 + 3 → 8 (arithmetic addition)"hello" + "world" → "helloworld" (string joining)[1, 2] + [3, 4] → [1, 2, 3, 4] (list joining)date(2026, 1, 1) + timedelta(days=7) → 8 January 2026 (calendar shift)Each time, Python secretly calls a different method:
(5).__add__(3), "hello".__add__("world"), and so on. Same operator — but the real behaviour lives inside each type.Operator overloading means giving your own classes that same power. Define
__add__ on a Vector class, and v1 + v2 just works. Define __eq__, and v1 == v2 does the right thing. Python's operator syntax becomes your syntax.5 + 3 → 8 (गणित वाला जोड़)"hello" + "world" → "helloworld" (strings जोड़ना)[1, 2] + [3, 4] → [1, 2, 3, 4] (lists जोड़ना)date(2026, 1, 1) + timedelta(days=7) → 8 जनवरी 2026 (तारीख़ आगे बढ़ाना)हर बार Python चुपचाप एक अलग method बुलाता है:
(5).__add__(3), "hello".__add__("world"), वग़ैरह। Operator वही — पर असली व्यवहार हर type के अंदर रहता है।Operator overloading का मतलब है अपनी classes को भी यही ताक़त देना।
Vector class पर __add__ define कीजिए, और v1 + v2 चल जाता है। __eq__ define कीजिए, और v1 == v2 सही काम करता है। Python का operator syntax आपका syntax बन जाता है।5 + 3 → 8 (算术加法)"hello" + "world" → "helloworld" (字符串拼接)[1, 2] + [3, 4] → [1, 2, 3, 4] (列表连接)date(2026, 1, 1) + timedelta(days=7) → 2026 年 1 月 8 日 (日期推移)每一次,Python 都在悄悄调用一个 不同的方法:
(5).__add__(3)、"hello".__add__("world") 等等。运算符相同 — 真正的行为却存在于各个类型内部。运算符重载 就是让你自己的类也拥有这种能力。在
Vector 类上定义 __add__,v1 + v2 就能用了。定义 __eq__,v1 == v2 就会做正确的事。Python 的运算符语法变成了你的语法。Here is the story as real code. The last two lines call the dunder methods directly — and give the same answers:
यही कहानी असली code में। आख़िरी दो lines dunder methods को सीधे बुलाती हैं — और वही जवाब देती हैं:
下面是这个故事的真实代码。最后两行直接调用 dunder 方法 — 结果完全相同:
from datetime import date, timedelta
print(5 + 3)
print("hello" + "world")
print([1, 2] + [3, 4])
print(date(2026, 1, 1) + timedelta(days=7))
# The same calls, written as dunder methods
print((5).__add__(3)) # brackets needed: 5.__add__ is a SyntaxError
print("hello".__add__("world"))
The original story wrote 5.__add__(3). That is a SyntaxError — Python reads 5. as the start of a decimal number like 5.0. Write (5).__add__(3) (or 5 .__add__(3) with a space) instead.
मूल कहानी में 5.__add__(3) लिखा था। यह SyntaxError है — Python 5. को 5.0 जैसे decimal number की शुरुआत समझता है। इसकी जगह (5).__add__(3) (या space के साथ 5 .__add__(3)) लिखें।
原故事写的是 5.__add__(3)。这是 SyntaxError — Python 会把 5. 当作 5.0 这样的小数的开头。应写成 (5).__add__(3)(或加空格 5 .__add__(3))。
Python operators — +, -, *, ==, < — and built-ins like abs() and print() are shortcuts for dunder methods ("double underscore" methods such as __add__, __eq__, __abs__, __str__). Define the right dunder on your class, and the operator works automatically.
Python operators — +, -, *, ==, < — और abs(), print() जैसे built-ins असल में dunder methods ("double underscore" methods, जैसे __add__, __eq__, __abs__, __str__) के shortcut हैं। अपनी class पर सही dunder define कीजिए, और operator अपने आप चलने लगता है।
Python 运算符 — +、-、*、==、< — 以及 abs()、print() 这样的内置函数,都是 dunder 方法("双下划线"方法,如 __add__、__eq__、__abs__、__str__)的快捷写法。在你的类上定义正确的 dunder,运算符就会自动生效。
Look for the interactive boxes: the Vector Playground (Section 03), the str / repr Mirror (05), the Dispatch Lab (06), the In-place Lab (07) and the Bug Doctor (11). Every result comes from real Python runs.
Interactive डिब्बे खोजें: Vector Playground (Section 03), str / repr Mirror (05), Dispatch Lab (06), In-place Lab (07) और Bug Doctor (11)। हर नतीजा असली Python runs से है।
请留意这些互动框:向量游乐场(第 03 节)、str / repr 镜子(05)、分派实验室(06)、原地运算实验室(07)和 Bug 医生(11)。每个结果都来自真实的 Python 运行。
How + Becomes a Method Call 🔁+ एक Method Call कैसे बनता है 🔁+ 如何变成一次方法调用 🔁
Every time Python sees a + b, it does not magically know how to add. It looks up a.__add__(b) and runs it. Built-in types already have these methods. For your own class, you write them.
जब भी Python a + b देखता है, उसे जादू से जोड़ना नहीं आता। वह a.__add__(b) ढूँढ़ता है और उसे चलाता है। Built-in types में ये methods पहले से हैं। अपनी class के लिए आप इन्हें लिखते हैं।
每当 Python 看到 a + b,它并不会凭空知道怎么相加。它查找 a.__add__(b) 并运行它。内置类型已经有这些方法;对于你自己的类,由你来编写。
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# -- Define what + means for two Vectors --
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# -- Define how a Vector prints --
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
# These two lines are IDENTICAL to Python
print(v1 + v2) # Vector(6, 8)
print(v1.__add__(v2)) # Vector(6, 8) <- same call under the hood
Every + - * == in Python is a shortcut for a dunder method. Define the dunder, and the operator works on your class.
Python का हर + - * == एक dunder method का shortcut है। Dunder define कीजिए, operator आपकी class पर चलेगा।
Python 里的每个 + - * == 都是某个 dunder 方法的简写。定义这个 dunder,运算符就能作用于你的类。
Arithmetic Operators 🧮Arithmetic Operators 🧮算术运算符 🧮
Each common arithmetic operator has a matching dunder. Define only the ones you need — no rule says every class must support every operator.
हर आम arithmetic operator का एक मिलता-जुलता dunder है। सिर्फ़ वही define करें जो चाहिए — कोई नियम नहीं कहता कि हर class हर operator सँभाले।
每个常用算术运算符都有对应的 dunder。只定义你需要的 — 没有规定每个类都必须支持所有运算符。
| OperatorOperator运算符 | Dunder methodDunder methodDunder 方法 | What it doesक्या करता है作用 |
|---|---|---|
a + b | __add__ | Additionजोड़加法 |
a - b | __sub__ | Subtractionघटाना减法 |
a * b | __mul__ | Multiplicationगुणा乘法 |
a / b | __truediv__ | Division (float result)भाग (float नतीजा)除法(结果为 float) |
a // b | __floordiv__ | Floor divisionFloor division整除(向下取整) |
a % b | __mod__ | Modulo (remainder)Modulo (शेषफल)取模(余数) |
a ** b | __pow__ | Powerघात幂运算 |
-a | __neg__ | Unary minusUnary minus取负 |
abs(a) | __abs__ | Absolute value / magnitudeAbsolute value / लंबाई绝对值 / 长度 |
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
# Scalar multiplication: Vector * number
return Vector(self.x * scalar, self.y * scalar)
def __neg__(self):
return Vector(-self.x, -self.y)
def __abs__(self):
# Magnitude - Euclidean length
return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(v1 - v2) # Vector(2, 2)
print(v1 * 3) # Vector(9, 12)
print(-v1) # Vector(-3, -4)
print(abs(v1)) # 5.0 (magnitude of a 3-4-5 triangle)
🎯 Simulator: Vector Playground🎯 Simulator: Vector Playground🎯 模拟器:向量游乐场
Drag the heads of a and b on the grid. Every expression on the right runs a different dunder of the complete Vector class from Section 08. Checked against Python on 400 random cases.
Grid पर a और b के सिरों को खींचिए। दाईं ओर का हर expression Section 08 की पूरी Vector class का एक अलग dunder चलाता है। 400 random cases पर Python से जाँचा गया।
在网格上拖动 a 和 b 的箭头。右侧每个表达式都会运行第 08 节完整 Vector 类中的一个不同 dunder。已在 400 个随机用例上与 Python 对照。
Comparison Operators ⚖️Comparison Operators ⚖️比较运算符 ⚖️
The six comparison operators map one-to-one to dunder methods. If you define __eq__, Python works out != for you. Define __lt__ and __eq__, add @functools.total_ordering, and you get the other comparisons for free.
छह comparison operators सीधे dunder methods से जुड़े हैं। __eq__ define करें तो Python != ख़ुद निकाल लेता है। __lt__ और __eq__ define करें, @functools.total_ordering लगाएँ, और बाक़ी comparisons मुफ़्त मिलते हैं।
六个比较运算符与 dunder 方法一一对应。定义了 __eq__,Python 会自动推出 !=。定义 __lt__ 和 __eq__,再加上 @functools.total_ordering,其余比较就免费得到了。
| OperatorOperator运算符 | Dunder methodDunder methodDunder 方法 | Notesनोट说明 |
|---|---|---|
a == b | __eq__ | Also gives you != automatically!= भी अपने आप देता है同时自动得到 != |
a != b | __ne__ | Rarely needed — __eq__ is enoughशायद ही चाहिए — __eq__ काफ़ी है很少需要 — __eq__ 就够了 |
a < b | __lt__ | Used by sorted()sorted() इसे इस्तेमाल करता हैsorted() 使用它 |
a <= b | __le__ | From @total_ordering@total_ordering से可由 @total_ordering 生成 |
a > b | __gt__ | From @total_ordering@total_ordering से可由 @total_ordering 生成 |
a >= b | __ge__ | From @total_ordering@total_ordering से可由 @total_ordering 生成 |
from functools import total_ordering
@total_ordering # fills in <=, >, >= from __lt__ + __eq__
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __abs__(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
# -- Two vectors are equal if both components match --
def __eq__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
# -- Order by magnitude --
def __lt__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return abs(self) < abs(other)
a = Vector(3, 4) # magnitude 5
b = Vector(3, 4) # magnitude 5 - equal
c = Vector(6, 8) # magnitude 10
print(a == b) # True
print(a != c) # True (Python derives this from __eq__)
print(a < c) # True (magnitude 5 < 10)
print(a >= b) # True (@total_ordering filled this in)
# Because __lt__ exists, sorted() works
print(sorted([c, a, Vector(0, 1)]))
When your __eq__ gets something that is not a Vector, return NotImplemented — the special built-in value, not False, and not a raised NotImplementedError. It tells Python "I don't know how to compare with that type — ask the other side." Returning False says "they are definitely not equal". That can make x == y and y == x disagree (the comparison is no longer symmetric).
जब आपके __eq__ को Vector के अलावा कुछ मिले, तो NotImplemented लौटाएँ — यह एक ख़ास built-in value है; False नहीं, और NotImplementedError raise भी नहीं। यह Python से कहता है "मुझे उस type से तुलना नहीं आती — दूसरी तरफ़ से पूछो।" False लौटाने का मतलब है "ये पक्का बराबर नहीं हैं"। इससे x == y और y == x के जवाब अलग हो सकते हैं (तुलना symmetric नहीं रहती)।
当你的 __eq__ 收到的不是 Vector 时,返回 NotImplemented — 这是一个特殊的内置值;不要返回 False,也不要抛出 NotImplementedError。它告诉 Python:"我不知道怎么和那个类型比较 — 去问另一边。"返回 False 则表示"它们肯定不相等",这会让 x == y 和 y == x 结果不一致(比较不再 对称)。
The original said returning False "breaks reflexivity". Reflexivity means x == x. The problem described — x == y and y == x giving different answers — is symmetry. The same fix is applied in the mistakes table.
मूल में लिखा था कि False लौटाने से "reflexivity टूटती है"। Reflexivity का मतलब है x == x। जो समस्या बताई गई — x == y और y == x के अलग जवाब — वह symmetry है। ग़लतियों वाली table में भी यही सुधार किया गया है।
原文说返回 False 会"破坏自反性"。自反性指的是 x == x。文中描述的问题 — x == y 和 y == x 结果不同 — 属于 对称性。常见错误表中也做了同样的修正。
__str__ vs __repr__ — Customising Display 🪞__str__ बनाम __repr__ — Display बदलना 🪞__str__ vs __repr__ — 自定义显示 🪞
These two dunders control how your object is shown. They serve different readers: __str__ is for end users (used by print() and str()); __repr__ is for developers (used by repr(), the interactive prompt, debuggers and error messages). If you define only one, define __repr__ — str() falls back to it automatically.
ये दो dunders तय करते हैं कि आपका object कैसा दिखेगा। ये अलग पाठकों के लिए हैं: __str__ end users के लिए (print() और str() इस्तेमाल करते हैं); __repr__ developers के लिए (repr(), interactive prompt, debuggers और error messages)। अगर एक ही define करें, तो __repr__ करें — str() अपने आप उस पर लौट आता है।
这两个 dunder 控制对象如何显示,面向不同的读者:__str__ 面向最终用户(print() 和 str() 使用它);__repr__ 面向开发者(repr()、交互式提示符、调试器和错误信息使用它)。如果只定义一个,就定义 __repr__ — str() 会自动退回使用它。
class Money:
def __init__(self, amount, currency="USD"):
self.amount = amount
self.currency = currency
# -- Developer-facing: clear, ideally could rebuild the object --
def __repr__(self):
return f"Money({self.amount!r}, {self.currency!r})"
# -- End-user-facing: pretty, readable --
def __str__(self):
return f"{self.currency} {self.amount:,.2f}"
m = Money(1234.5, "INR")
print(m) # INR 1,234.50 <- calls __str__
print(str(m)) # INR 1,234.50 <- calls __str__
print(repr(m)) # Money(1234.5, 'INR') <- calls __repr__
# Inside a list, print shows each item's repr - that's why it looks "debuggy"
print([m]) # [Money(1234.5, 'INR')]
🪞 Simulator: str / repr Mirror🪞 Simulator: str / repr Mirror🪞 模拟器:str / repr 镜子
Keep both methods, or remove one. Six ways of showing the same object tell you which method Python picked. Try 0.1 + 0.2: __str__ rounds it nicely, __repr__ shows the exact float. All 30 combinations are real Python outputs (memory addresses shortened to 0x7f…).
दोनों methods रखिए, या एक हटाइए। एक ही object को दिखाने के छह तरीक़े बताते हैं कि Python ने कौन-सा method चुना। 0.1 + 0.2 आज़माइए: __str__ इसे सुंदर तरह से round करता है, __repr__ असली float दिखाता है। सभी 30 combinations असली Python outputs हैं (memory addresses को 0x7f… तक छोटा किया गया)।
保留两个方法,或删掉其中一个。用六种方式显示同一个对象,看看 Python 选了哪个方法。试试 0.1 + 0.2:__str__ 会漂亮地四舍五入,__repr__ 显示精确的浮点数。全部 30 种组合都是真实的 Python 输出(内存地址缩写为 0x7f…)。
Reflected Operators — Your Object on the Right 🔄Reflected Operators — आपका Object दाईं ओर 🔄反射运算符 — 你的对象在右边 🔄
Think about 4 * v. Python first tries int.__mul__(4, v). int has no idea what a Vector is, so it returns NotImplemented. Then Python asks the right-hand object: "do you have a reflected version of this operator?" That is __rmul__. Every arithmetic operator has an r-version for exactly this case.
4 * v के बारे में सोचिए। Python पहले int.__mul__(4, v) आज़माता है। int को पता नहीं कि Vector क्या है, इसलिए वह NotImplemented लौटाता है। फिर Python दाईं ओर वाले object से पूछता है: "क्या तुम्हारे पास इस operator का reflected version है?" वह __rmul__ है। हर arithmetic operator का ठीक इसी काम के लिए एक r-version होता है।
想想 4 * v。Python 先尝试 int.__mul__(4, v)。int 不知道 Vector 是什么,于是返回 NotImplemented。接着 Python 问 右边 的对象:"你有这个运算符的 反射 版本吗?"那就是 __rmul__。每个算术运算符都有一个 r 版本,正是为这种情况准备的。
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __mul__(self, scalar):
print(" [__mul__ called]")
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar):
# Called when the vector is on the RIGHT of the *
print(" [__rmul__ called]")
return self.__mul__(scalar)
v = Vector(2, 3)
print(v * 4) # uses __mul__ -> Vector(8, 12)
print()
print(4 * v) # uses __rmul__ -> Vector(8, 12)
Every arithmetic operator has an r-version: __radd__, __rsub__, __rmul__… Same rule, opposite side.
हर arithmetic operator का एक r-version है: __radd__, __rsub__, __rmul__… वही नियम, उलटी तरफ़।
每个算术运算符都有 r 版本:__radd__、__rsub__、__rmul__…… 规则相同,方向相反。
The original diagram showed 3 + v1 using __radd__ and "adding 3 to both parts". The code above never defines __radd__, so 3 + v1 would raise TypeError. The diagram now shows 4 * v with __rmul__ — exactly the example above.
मूल diagram में 3 + v1 को __radd__ से "दोनों हिस्सों में 3 जोड़ते" दिखाया गया था। ऊपर के code में __radd__ कभी define नहीं है, इसलिए 3 + v1 पर TypeError आता। अब diagram 4 * v को __rmul__ के साथ दिखाता है — ठीक ऊपर वाला उदाहरण।
原图示展示的是 3 + v1 通过 __radd__"给两个分量都加 3"。但上面的代码从未定义 __radd__,所以 3 + v1 会抛出 TypeError。现在图示改为 4 * v 配合 __rmul__ — 正好是上面的例子。
🔬 Simulator: Dispatch Lab🔬 Simulator: Dispatch Lab🔬 模拟器:分派实验室
Pick a left operand, an operator and a right operand. Watch Python ask the left side, then the right side, then give up. It uses the complete Vector class (Section 08) or the Money class (Section 09). Every one of the 221 results was checked against the real operator.
बायाँ operand, operator और दायाँ operand चुनिए। देखिए Python पहले बाईं ओर से पूछता है, फिर दाईं ओर से, फिर हार मान लेता है। इसमें पूरी Vector class (Section 08) या Money class (Section 09) है। सभी 221 नतीजे असली operator से जाँचे गए।
选择左操作数、运算符和右操作数。观察 Python 先问左边、再问右边、最后放弃的过程。它使用完整的 Vector 类(第 08 节)或 Money 类(第 09 节)。全部 221 个结果都已与真实运算符核对。
In-place Operators — +=, -=, *= ♻️In-place Operators — +=, -=, *= ♻️原地运算符 — +=、-=、*= ♻️
When you write v += w, Python first tries v.__iadd__(w). If the class has no __iadd__, Python falls back to v = v + w. The difference matters: an in-place operator should change the object itself and return self. Immutable classes (like Money or a math Vector) should not define these — the fallback makes a new object, which is the right behaviour for them.
जब आप v += w लिखते हैं, Python पहले v.__iadd__(w) आज़माता है। अगर class में __iadd__ नहीं है, तो Python v = v + w पर लौट आता है। यह फ़र्क़ अहम है: in-place operator को object को ही बदलना चाहिए और return self करना चाहिए। Immutable classes (जैसे Money या गणित वाला Vector) को ये define नहीं करने चाहिए — fallback नया object बनाता है, जो उनके लिए सही व्यवहार है।
当你写 v += w 时,Python 先尝试 v.__iadd__(w)。如果类里没有 __iadd__,Python 退回到 v = v + w。区别很重要:原地运算符应该 修改对象本身 并 return self。不可变的类(如 Money 或数学上的 Vector)不 应定义这些方法 — 退回机制会创建新对象,这对它们才是正确的行为。
class Playlist:
"""Mutable - in-place operations modify self."""
def __init__(self, songs=None):
self.songs = songs or []
def __repr__(self):
return f"Playlist({self.songs})"
def __iadd__(self, song):
# Mutate in place, return self
self.songs.append(song)
return self
mix = Playlist(["Track 1"])
mix += "Track 2" # calls __iadd__
mix += "Track 3"
print(mix) # Playlist(['Track 1', 'Track 2', 'Track 3'])
Forgetting return self in __iadd__ makes x += y quietly set x to None. The rule: change the object, then return self. Every in-place dunder follows the same pattern.
__iadd__ में return self भूलने पर x += y चुपचाप x को None बना देता है। नियम: object बदलिए, फिर return self। हर in-place dunder यही pattern मानता है।
在 __iadd__ 中忘记 return self,会让 x += y 悄悄把 x 变成 None。规则:先修改对象,再 return self。每个原地 dunder 都遵循这个模式。
♻️ Simulator: In-place Lab♻️ Simulator: In-place Lab♻️ 模拟器:原地运算实验室
Two names, mix and alias, start on the same playlist. Step through three versions of the class and watch where each name points after +=. Every step is recorded from a real run.
दो नाम, mix और alias, एक ही playlist से शुरू होते हैं। Class के तीन versions में क़दम-दर-क़दम चलिए और देखिए += के बाद हर नाम कहाँ इशारा करता है। हर क़दम असली run से रिकॉर्ड है।
两个名字 mix 和 alias 一开始指向同一个播放列表。逐步运行类的三个版本,观察 += 之后每个名字指向哪里。每一步都来自真实运行的记录。
Complete Practical Example — Vector Class 📐पूरा Practical Example — Vector Class 📐完整实战示例 — Vector 类 📐
Everything together: arithmetic, the reflected __rmul__, unary operators, comparisons with @total_ordering, and __hash__ so vectors work in sets.
सब कुछ एक साथ: arithmetic, reflected __rmul__, unary operators, @total_ordering के साथ comparisons, और __hash__ ताकि vectors sets में चल सकें।
全部放在一起:算术运算、反射的 __rmul__、一元运算符、配合 @total_ordering 的比较,以及让向量能放进集合的 __hash__。
from functools import total_ordering
@total_ordering
class Vector:
"""An immutable 2D vector with a full operator suite."""
def __init__(self, x, y):
self.x, self.y = x, y
# -- Display ---------------------------------------------
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# -- Arithmetic ------------------------------------------
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __sub__(self, other):
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
return NotImplemented
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
# Handles: number * Vector
return self.__mul__(scalar)
def __truediv__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x / scalar, self.y / scalar)
return NotImplemented
# -- Unary -----------------------------------------------
def __neg__(self):
return Vector(-self.x, -self.y)
def __abs__(self):
# Euclidean magnitude
return (self.x ** 2 + self.y ** 2) ** 0.5
# -- Comparison (total_ordering fills the rest from these two) --
def __eq__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
def __lt__(self, other):
# Compare by magnitude
if not isinstance(other, Vector):
return NotImplemented
return abs(self) < abs(other)
# -- Hashable, so it works in sets/dicts (immutable class) --
def __hash__(self):
return hash((self.x, self.y))
# -- Every operator you defined is now usable --------------
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(v1 - v2) # Vector(2, 2)
print(v1 * 3) # Vector(9, 12)
print(3 * v1) # Vector(9, 12) <- via __rmul__
print(v1 / 2) # Vector(1.5, 2.0)
print(-v1) # Vector(-3, -4)
print(abs(v1)) # 5.0
print(v1 == Vector(3, 4)) # True
print(v1 > v2) # True (magnitude 5 > magnitude ~2.24)
# Sortable - because __lt__ is defined
vectors = [Vector(6, 8), v1, v2, Vector(0, 1)]
print(sorted(vectors))
# Hashable - usable in sets and dicts
print({Vector(1, 2), Vector(1, 2), Vector(3, 4)}) # duplicates removed
Every expression on the left goes through Python to the matching dunder on the right. Same object — many faces.
बाईं ओर का हर expression Python से होकर दाईं ओर के सही dunder तक जाता है। वही object — कई चेहरे।
左边的每个表达式都经由 Python 找到右边对应的 dunder。同一个对象 — 多副面孔。
v1 > v2 works even though the class never defines __gt__ — @total_ordering built it from __lt__ and __eq__. And sorted() only ever uses <, so __lt__ alone is enough for sorting.
v1 > v2 चलता है, जबकि class में __gt__ कभी define नहीं है — @total_ordering ने इसे __lt__ और __eq__ से बनाया। और sorted() सिर्फ़ < इस्तेमाल करता है, इसलिए sorting के लिए अकेला __lt__ काफ़ी है।
虽然类中从未定义 __gt__,v1 > v2 依然可用 — @total_ordering 用 __lt__ 和 __eq__ 生成了它。而 sorted() 只使用 <,所以排序只需要 __lt__。
Complete Practical Example — Money Class 💰पूरा Practical Example — Money Class 💰完整实战示例 — Money 类 💰
Money reads like plain numbers, but every operation checks the currency first. Try it in the Dispatch Lab (Section 06): switch to Money and pick m1 + m3 or m1 < m3.
Money सादे नंबरों जैसा पढ़ा जाता है, पर हर operation पहले currency जाँचता है। Dispatch Lab (Section 06) में आज़माइए: Money चुनिए और m1 + m3 या m1 < m3 लीजिए।
Money 读起来就像普通数字,但每个运算都会先检查货币。到分派实验室(第 06 节)里试试:切换到 Money,选择 m1 + m3 或 m1 < m3。
class Money:
"""Immutable currency-aware amount."""
def __init__(self, amount, currency="USD"):
self.amount = float(amount)
self.currency = currency
# -- Display ---------------------------------------------
def __repr__(self):
return f"Money({self.amount!r}, {self.currency!r})"
def __str__(self):
return f"{self.currency} {self.amount:,.2f}"
# -- Same-currency arithmetic ----------------------------
def _check_currency(self, other):
if self.currency != other.currency:
raise ValueError(
f"Cannot combine {self.currency} and {other.currency} directly"
)
def __add__(self, other):
if isinstance(other, Money):
self._check_currency(other)
return Money(self.amount + other.amount, self.currency)
return NotImplemented
def __sub__(self, other):
if isinstance(other, Money):
self._check_currency(other)
return Money(self.amount - other.amount, self.currency)
return NotImplemented
# -- Scalar multiplication (interest, tax, discount) -----
def __mul__(self, factor):
if isinstance(factor, (int, float)):
return Money(self.amount * factor, self.currency)
return NotImplemented
def __rmul__(self, factor):
return self.__mul__(factor)
# -- Comparison - only within the same currency ----------
def __eq__(self, other):
if not isinstance(other, Money):
return NotImplemented
return self.amount == other.amount and self.currency == other.currency
def __lt__(self, other):
if not isinstance(other, Money):
return NotImplemented
self._check_currency(other)
return self.amount < other.amount
# -- Use it naturally, like a built-in numeric type --------
salary = Money(50000, "INR")
bonus = Money(10000, "INR")
rent = Money(15000, "INR")
total = salary + bonus # uses __add__
remaining = total - rent # uses __sub__
with_tax = remaining * 1.05 # uses __mul__
with_tax2 = 1.05 * remaining # uses __rmul__ (same result)
print("Total: ", total) # INR 60,000.00
print("Remaining: ", remaining) # INR 45,000.00
print("With tax: ", with_tax) # INR 47,250.00
print("Equal? ", with_tax == with_tax2) # True
print("Bonus < Rent?", bonus < rent) # True
# Adding two currencies is caught at the arithmetic layer
try:
Money(100, "USD") + Money(100, "INR")
except ValueError as e:
print("Blocked:", e)
Every line — salary + bonus, remaining * 1.05, bonus < rent — reads like arithmetic on a built-in float. But underneath, the currency check runs on every operation. That is operator overloading at its best: clear syntax on top, safe logic underneath.
हर line — salary + bonus, remaining * 1.05, bonus < rent — built-in float पर गणित जैसी पढ़ी जाती है। पर नीचे हर operation पर currency की जाँच चलती है। यही operator overloading का सबसे अच्छा रूप है: ऊपर साफ़ syntax, नीचे सुरक्षित logic।
每一行 — salary + bonus、remaining * 1.05、bonus < rent — 读起来都像对内置 float 做运算。但在底层,每次运算都会检查货币。这正是运算符重载的最佳状态:上层语法清晰,底层逻辑安全。
__eq__ compares currency as part of equality, so Money(100, "USD") == Money(100, "INR") is simply False. __lt__ calls _check_currency, so comparing USD with INR raises ValueError — "less than" has no meaning across currencies.
__eq__ currency को बराबरी का हिस्सा मानकर तुलना करता है, इसलिए Money(100, "USD") == Money(100, "INR") बस False है। __lt__ _check_currency बुलाता है, इसलिए USD की INR से तुलना पर ValueError आता है — अलग currencies में "कम" का कोई मतलब नहीं।
__eq__ 把货币作为相等的一部分来比较,所以 Money(100, "USD") == Money(100, "INR") 只是 False。__lt__ 会调用 _check_currency,所以拿 USD 和 INR 比大小会抛出 ValueError — 不同货币之间"小于"没有意义。
Operator → Dunder Reference 📋Operator → Dunder Reference 📋运算符 → Dunder 速查表 📋
| Categoryश्रेणी类别 | Operator / functionOperator / function运算符 / 函数 | Dunder methodDunder methodDunder 方法 |
|---|---|---|
| ArithmeticArithmetic算术 | a + b | __add__ · __radd__ |
a - b | __sub__ · __rsub__ | |
a * b | __mul__ · __rmul__ | |
a / b | __truediv__ · __rtruediv__ | |
a // b | __floordiv__ · __rfloordiv__ | |
a % b | __mod__ · __rmod__ | |
a ** b | __pow__ · __rpow__ | |
| UnaryUnary一元 | -a | __neg__ |
+a | __pos__ | |
abs(a) | __abs__ | |
| ComparisonComparison比较 | a == b | __eq__ |
a != b | __ne__ (derived from __eq__)__ne__ (__eq__ से निकलता है)__ne__(由 __eq__ 推出) | |
a < b | __lt__ | |
a <= b | __le__ | |
a > b | __gt__ | |
a >= b | __ge__ | |
| In-placeIn-place原地 | a += b | __iadd__ |
a -= b | __isub__ | |
a *= b | __imul__ | |
a /= b | __itruediv__ | |
| DisplayDisplay显示 | print(a), str(a) | __str__ |
repr(a), interactive prompt, debuggersrepr(a), interactive prompt, debuggersrepr(a)、交互式提示符、调试器 | __repr__ | |
f"{a:.2f}" | __format__ (upcoming tutorial) (आने वाला tutorial) (后续教程) | |
| ContainerContainer容器 | len(a) | __len__ (upcoming tutorial) (आने वाला tutorial) (后续教程) |
a[i] | __getitem__ (upcoming tutorial) (आने वाला tutorial) (后续教程) | |
x in a | __contains__ (upcoming tutorial) (आने वाला tutorial) (后续教程) | |
for x in a | __iter__ (upcoming tutorial) (आने वाला tutorial) (后续教程) | |
| Otherअन्य其他 | bool(a) | __bool__ (upcoming tutorial) (आने वाला tutorial) (后续教程) |
hash(a) | __hash__ | |
a(...) | __call__ (upcoming tutorial) (आने वाला tutorial) (后续教程) |
Rows marked upcoming tutorial are listed here for completeness; they get their own lessons later.
आने वाला tutorial वाली rows यहाँ पूरी सूची के लिए हैं; उनके अपने पाठ बाद में आएँगे।
标有 后续教程 的行只是为了完整列出;它们会在之后的课程中单独讲解。
Common Mistakes (and Fixes) 🩹आम ग़लतियाँ (और उनके हल) 🩹常见错误(及修正)🩹
| Mistakeग़लती错误 | What goes wrongक्या बिगड़ता है会出什么问题 | Fixहल修正 |
|---|---|---|
Returning False from __eq__ for unrelated typesUnrelated types के लिए __eq__ से False लौटाना对无关类型在 __eq__ 中返回 False | Breaks symmetry — x == y and y == x may disagreeSymmetry टूटती है — x == y और y == x अलग हो सकते हैं破坏 对称性 — x == y 和 y == x 可能不一致 | Return NotImplemented — Python then asks the other sideNotImplemented लौटाएँ — तब Python दूसरी तरफ़ पूछता है返回 NotImplemented — Python 会去问另一边 |
Raising NotImplementedError instead of returning NotImplementedNotImplemented लौटाने की जगह NotImplementedError raise करना抛出 NotImplementedError 而不是返回 NotImplemented | Any mixed-type comparison crashes; Python cannot fall backहर mixed-type तुलना crash होती है; Python fallback नहीं कर पाता任何混合类型比较都会崩溃;Python 无法退回 | They look alike but do opposite things. Return the value, don't raiseदिखते एक जैसे हैं पर उलटा काम करते हैं। Value लौटाएँ, raise न करें两者看起来相似,作用却相反。返回这个值,不要抛出 |
Forgetting __hash__ after defining __eq____eq__ define करके __hash__ भूलना定义 __eq__ 后忘记 __hash__ | Python sets __hash__ to None — objects can't go in sets or be dict keysPython __hash__ को None कर देता है — objects sets या dict keys में नहीं जा सकतेPython 会把 __hash__ 设为 None — 对象不能放进集合或作字典键 | Define __hash__ too (only for immutable classes)__hash__ भी define करें (सिर्फ़ immutable classes के लिए)同时定义 __hash__(只用于不可变的类) |
Changing self and returning it in __add____add__ में self बदलकर लौटाना在 __add__ 中修改并返回 self | x + y should give a NEW object, not change xx + y को नया object देना चाहिए, x को बदलना नहींx + y 应该返回新对象,而不是修改 x | Return a new instance. Only __iadd__ changes in placeनया instance लौटाएँ। सिर्फ़ __iadd__ in place बदलता है返回新实例。只有 __iadd__ 原地修改 |
Forgetting return self in __iadd____iadd__ में return self भूलना在 __iadd__ 中忘记 return self | x += y quietly sets x to Nonex += y चुपचाप x को None बना देता हैx += y 悄悄把 x 设为 None | Change the object, then return selfObject बदलें, फिर return self先修改对象,再 return self |
| Overloading operators with no clear meaningबिना साफ़ मतलब वाले operators overload करना重载含义不明确的运算符 | Readers can't guess what employee + employee doesपढ़ने वाले अंदाज़ा नहीं लगा पाते कि employee + employee क्या करता है读者猜不出 employee + employee 是什么意思 | Overload only when the operator has an obvious real-world meaningतभी overload करें जब operator का साफ़ असली मतलब हो只有当运算符有明显的现实含义时才重载 |
Defining __lt__ but not __eq____lt__ define करना पर __eq__ नहीं定义了 __lt__ 却没定义 __eq__ | sorted() works, but == falls back to identity (is) — subtle bugssorted() चलता है, पर == identity (is) पर लौट आता है — छिपे bugssorted() 能用,但 == 会退回到身份比较(is)— 隐蔽的 bug | Define __eq__ with __lt__, and consider @total_ordering__lt__ के साथ __eq__ भी define करें, और @total_ordering पर विचार करें和 __lt__ 一起定义 __eq__,并考虑 @total_ordering |
Skipping __repr____repr__ छोड़ देना不写 __repr__ | Debug prints show <__main__.Vector object at 0x7f…> — uselessDebug prints में <__main__.Vector object at 0x7f…> दिखता है — बेकार调试输出显示 <__main__.Vector object at 0x7f…> — 毫无用处 | Add __repr__ as soon as your class holds meaningful dataClass में काम का data आते ही __repr__ जोड़ें类一有有意义的数据就加上 __repr__ |
🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生
Six of these mistakes, run for real. Buggy code on the left, fixed code on the right, changed lines marked.
इनमें से छह ग़लतियाँ, असली run में। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित।
其中六个错误的真实运行。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。
Golden Rules and Quiz 🏆सुनहरे नियम और Quiz 🏆黄金法则与小测验 🏆
a + b runs a.__add__(b). a == b runs a.__eq__(b). Define the dunder, and the operator works.a + b a.__add__(b) चलाता है। a == b a.__eq__(b) चलाता है। Dunder define कीजिए, operator चलने लगेगा।a + b 运行 a.__add__(b),a == b 运行 a.__eq__(b)。定义 dunder,运算符就能用。+ on Employee just because you can.Employee पर + सिर्फ़ इसलिए overload न करें कि कर सकते हैं।Employee 重载 +。__add__, __mul__) return a new object and leave the original alone. In-place operators (__iadd__) change the object and return self.__add__, __mul__) नया object लौटाते हैं और मूल को नहीं छूते। In-place operators (__iadd__) object बदलते हैं और return self करते हैं।__add__、__mul__)返回 新对象,不改动原对象。原地运算符(__iadd__)修改对象 并且 return self。NotImplemented — never False, never raise NotImplementedError. Python will then try the reflected method on the other side.NotImplemented लौटाएँ — कभी False नहीं, कभी NotImplementedError raise नहीं। तब Python दूसरी तरफ़ का reflected method आज़माएगा।NotImplemented — 绝不返回 False,也绝不抛出 NotImplementedError。Python 随后会尝试另一边的反射方法。__repr__ first. It is the first thing you need while debugging. Aim for text that could rebuild the object: Vector(3, 4), not <my vector>.__repr__ सबसे पहले define करें। Debugging में सबसे पहले यही चाहिए। ऐसा text रखें जिससे object फिर बन सके: Vector(3, 4), न कि <my vector>।__repr__。调试时最先需要它。目标是能重建对象的文本:Vector(3, 4),而不是 <my vector>。__eq__, decide about __hash__. Immutable classes should also define __hash__ so they work in sets and as dict keys. Mutable classes should keep __hash__ as None — Python does that automatically when you define __eq__.__eq__ define करें तो __hash__ के बारे में तय करें। Immutable classes __hash__ भी define करें ताकि sets और dict keys में चलें। Mutable classes __hash__ को None रहने दें — __eq__ define करने पर Python यह अपने आप करता है।__eq__,就要决定 __hash__。不可变类也应定义 __hash__,以便用于集合和字典键。可变类应让 __hash__ 保持 None — 定义 __eq__ 时 Python 会自动这样做。__lt__ and __eq__, then add @functools.total_ordering. You get <=, > and >= without writing three more methods.__lt__ और __eq__ define करें, फिर @functools.total_ordering लगाएँ। तीन और methods लिखे बिना <=, > और >= मिल जाते हैं।__lt__ 和 __eq__,再加上 @functools.total_ordering。不用多写三个方法就得到 <=、>、>=。__radd__, __rmul__) matter when your object is on the right. 3 * v1 fails unless Vector defines __rmul__. Note: when both sides are the same type, Python does not try the reflected method.__radd__, __rmul__) तब ज़रूरी हैं जब आपका object दाईं ओर हो। 3 * v1 fail होता है जब तक Vector में __rmul__ न हो। ध्यान दें: जब दोनों तरफ़ एक ही type हो, तो Python reflected method नहीं आज़माता।__radd__、__rmul__)就很重要。除非 Vector 定义了 __rmul__,否则 3 * v1 会失败。注意:两边是同一类型时,Python 不会尝试反射方法。Money(100, "USD") + Money(100, "INR") should raise, not quietly mix currencies. The dunder is the last gate before your rules are broken.Money(100, "USD") + Money(100, "INR") को raise करना चाहिए, चुपचाप currencies नहीं मिलानी चाहिए। आपके नियम टूटने से पहले dunder आख़िरी दरवाज़ा है।Money(100, "USD") + Money(100, "INR") 应该抛出错误,而不是悄悄混合货币。dunder 是你的规则被破坏前的最后一道关卡。a + b guess what happens?" If yes → overload it. If no → write a plain method such as a.combine(b). Clarity beats cleverness.a + b पढ़ने वाला Python programmer अंदाज़ा लगा लेगा कि क्या होगा?" हाँ → overload कीजिए। नहीं → a.combine(b) जैसा सादा method लिखिए। चतुराई से ज़्यादा साफ़ई ज़रूरी है।a + b 的 Python 程序员能猜到会发生什么吗?"能 → 重载它。不能 → 写一个普通方法,比如 a.combine(b)。清晰胜过花哨。Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题
Operators are dunder calls: a + b → a.__add__(b), and if that returns NotImplemented, Python tries b.__radd__(a). Return new objects from binary operators, return self from in-place ones, return NotImplemented for types you don't handle, always write __repr__, pair __eq__ with __hash__ for immutable classes, and overload only when the meaning is obvious.
Operators dunder calls हैं: a + b → a.__add__(b), और अगर वह NotImplemented लौटाए तो Python b.__radd__(a) आज़माता है। Binary operators से नए objects लौटाएँ, in-place वालों से return self, जिन types को नहीं सँभालते उनके लिए NotImplemented लौटाएँ, हमेशा __repr__ लिखें, immutable classes में __eq__ के साथ __hash__ रखें, और overload तभी करें जब मतलब साफ़ हो।
运算符就是 dunder 调用:a + b → a.__add__(b),如果它返回 NotImplemented,Python 会尝试 b.__radd__(a)。二元运算符返回新对象,原地运算符 return self,对不处理的类型返回 NotImplemented,始终编写 __repr__,不可变类让 __eq__ 与 __hash__ 配对,只在含义明显时才重载。