Python Advance 📂 Important concepts · 1 of 6 304 min read

Type Hints & Annotations in Python

Master Python's type hints from the ground up. Learn variable, argument, and return annotations, then advance to Optional, Union, Callable, dataclass, TypedDict, NamedTuple, TypeVar generics, and Protocol structural typing. Includes a visual mypy pipeline diagram, an anatomy-of-a-typed-function chart, a real weather-API client with fully typed data flow, common pitfalls, and eight golden rules for team codebases.

🌐
SectionSection第 01 节

The Story That Explains Type Hints 🧁Type Hints समझाने वाली कहानी 🧁一个讲清类型提示的故事 🧁

The Recipe Card Without Measurementsबिना माप वाला Recipe Card没有分量的菜谱卡
Imagine you inherit a family recipe. It says: "add flour, then sugar, then eggs, mix and bake." No amounts. No units. Cups? Grams? One egg or twelve? You guess, and half the time the cake fails. Every new cook makes the same mistakes you did.

Now the same recipe rewritten: "250 g flour, 180 g sugar, 3 large eggs." A newcomer bakes the cake correctly on day one. The recipe still makes the same cake — but now anyone can read it and trust it quickly.

That is type hints. Python still runs your code the same way, but every function now says what it wants and what it returns. Editors autocomplete. Many bugs show up before you press "run". Teammates understand your code without experimenting.
मान लीजिए आपको परिवार की एक recipe मिलती है। उसमें लिखा है: "आटा डालो, फिर चीनी, फिर अंडे, मिलाओ और bake करो।" कोई मात्रा नहीं। कोई इकाई नहीं। कप? ग्राम? एक अंडा या बारह? आप अंदाज़ा लगाते हैं, और आधी बार cake बिगड़ जाता है। हर नया रसोइया वही ग़लतियाँ दोहराता है।

अब वही recipe दोबारा लिखी गई: "250 g आटा, 180 g चीनी, 3 बड़े अंडे।" नया व्यक्ति पहले ही दिन सही cake बना लेता है। Recipe अब भी वही cake बनाती है — पर अब कोई भी इसे जल्दी पढ़ और भरोसा कर सकता है।

यही type hints हैं। Python आपका code वैसे ही चलाता है, पर अब हर function बताता है कि उसे क्या चाहिए और वह क्या लौटाता है। Editors autocomplete करते हैं। कई bugs "run" दबाने से पहले दिख जाते हैं। साथी बिना प्रयोग किए आपका code समझ लेते हैं।
想象你继承了一份家传菜谱,上面写着:"加面粉,再加糖,再加鸡蛋,搅拌后烘烤。"没有分量,没有单位。杯?克?一个鸡蛋还是十二个?你只能 猜,结果一半的时候蛋糕都会失败。每个新来的厨师都会犯同样的错误。

现在同一份菜谱被改写成:"250 克面粉,180 克糖,3 个大鸡蛋。"新手第一天就能做出正确的蛋糕。菜谱做出的蛋糕没变 — 但现在 任何人都能很快读懂并信任它。

这就是 类型提示。Python 依旧以同样的方式运行代码,但现在每个函数都写明了它需要什么、返回什么。编辑器能自动补全。许多 bug 在你按下"运行"之前就会暴露。队友不用反复试验就能读懂你的代码。

Type hints write down what kind of value each variable, argument and return value should be. Python stores them but does not enforce them when the code runs. Tools such as mypy, pyright, PyCharm and VS Code read them to catch bugs, power autocompletion and document your code.

Type hints लिखते हैं कि हर variable, argument और return value किस तरह की value होनी चाहिए। Python उन्हें रखता है पर code चलते समय उन्हें लागू नहीं करता। mypy, pyright, PyCharm और VS Code जैसे tools उन्हें पढ़कर bugs पकड़ते हैं, autocompletion चलाते हैं और आपके code का दस्तावेज़ बनाते हैं।

类型提示 写明了每个变量、参数和返回值应该是什么类型的值。Python 会保存它们,但在代码运行时 不会 强制检查。mypy、pyright、PyCharm 和 VS Code 等工具会读取它们来捕获 bug、支持自动补全并生成文档。

🧠
The Core Insightमूल बात核心要点

Type hints do not change what your code does, and they do not make it slower in any way you would notice. They are checked documentation: a tool verifies them for you. The result is code that is easier to read, safer to change and simpler to refactor.

Type hints आपके code के काम को नहीं बदलते, और उसे किसी महसूस होने वाले ढंग से धीमा नहीं करते। वे जाँचा हुआ दस्तावेज़ हैं: एक tool आपके लिए उन्हें जाँचता है। नतीजा ऐसा code है जो पढ़ने में आसान, बदलने में सुरक्षित और refactor करने में सरल है।

类型提示不会改变代码的行为,也不会让代码明显变慢。它们是 经过检查的文档:由工具替你验证。结果是代码更易读、改起来更安全、重构更简单。

▶️
Five Simulators and a Quizपाँच Simulators और एक Quiz五个模拟器和一个小测验

Look for the interactive boxes: the Type Compatibility Explorer (Section 04), the Narrowing Stepper (06), Who Catches It? (07), the Mutable Default Trap (12) and the Bug Doctor (15). Every mypy message on this page is the real output of mypy 2.4.1 on Python 3.13.16.

Interactive डिब्बे खोजें: Type Compatibility Explorer (Section 04), Narrowing Stepper (06), कौन पकड़ता है? (07), Mutable Default Trap (12) और Bug Doctor (15)। इस page का हर mypy message Python 3.13.16 पर mypy 2.4.1 का असली output है।

请留意这些互动框:类型兼容性探索器(第 04 节)、类型收窄单步演示(06)、谁能抓住它?(07)、可变默认值陷阱(12)和 Bug 医生(15)。本页每条 mypy 信息都是 mypy 2.4.1 在 Python 3.13.16 上的真实输出。


SectionSection第 02 节

The Problem — Untyped Python at Scale 📚समस्या — बड़े पैमाने पर बिना Types का Python 📚问题 — 大规模无类型 Python 📚

Here is the same function twice. The first is written the way most beginners write Python. The second has type hints. Both do exactly the same thing at runtime — but the second one lets a tool catch the wrong call.

यहाँ एक ही function दो बार है। पहला वैसे लिखा है जैसे ज़्यादातर beginners Python लिखते हैं। दूसरे में type hints हैं। Runtime पर दोनों बिल्कुल एक जैसा करते हैं — पर दूसरा एक tool को ग़लत call पकड़ने देता है।

下面是同一个函数的两个版本。第一个是大多数初学者写 Python 的方式,第二个带有类型提示。两者在运行时的行为完全相同 — 但第二个能让工具抓住错误的调用。

❌ Untyped — unclearबिना Types — अस्पष्ट无类型 — 含糊
Question a reader asksपाठक का सवाल读者会问Answerजवाब答案
What is user?user क्या है?user 是什么?Anyone's guessकोई भी अंदाज़ा全靠猜
Does it return None?क्या यह None लौटाता है?会返回 None 吗?Read the whole bodyपूरा body पढ़िए得读完整个函数体
Can I pass a dict?क्या dict दे सकते हैं?能传字典吗?Try it and seeआज़माकर देखिए试试看
Does autocomplete work?क्या autocomplete चलता है?自动补全有用吗?No — the editor cannot knowनहीं — editor नहीं जान सकता没用 — 编辑器无从知道
✅ Typed — self-documentingTypes के साथ — ख़ुद बताने वाला有类型 — 自带说明
Question a reader asksपाठक का सवाल读者会问Answerजवाब答案
What is user?user क्या है?user 是什么?A User objectएक User object一个 User 对象
Does it return None?क्या यह None लौटाता है?会返回 None 吗?No — it returns strनहीं — str लौटाता है不会 — 返回 str
Can I pass a dict?क्या dict दे सकते हैं?能传字典吗?mypy flags it before you runचलाने से पहले mypy पकड़ता हैmypy 在运行前就会标出来
Does autocomplete work?क्या autocomplete चलता है?自动补全有用吗?Yes — full type-aware suggestionsहाँ — पूरे type-aware सुझाव有用 — 完整的类型感知提示
from dataclasses import dataclass

@dataclass
class User:
    name: str

# BEFORE - untyped
def greet_untyped(user):
    return "Hello " + user.name

# AFTER - typed
def greet(user: User) -> str:
    return f"Hello {user.name}"

print(greet(User("Ada")))

try:
    greet({"name": "Ada"})                 # typed: mypy flags this line
except AttributeError as e:
    print("typed version crashed:", e)

print(greet_untyped({"name": "Ada"}))      # untyped: nobody warns us
OUTPUT
$ python demo.py Hello Ada typed version crashed: 'dict' object has no attribute 'name' Traceback (most recent call last): File "demo.py", line 22, in <module> print(greet_untyped({"name": "Ada"})) # untyped: nobody warns us ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ File "demo.py", line 9, in greet_untyped return "Hello " + user.name ^^^^^^^^^ AttributeError: 'dict' object has no attribute 'name' $ mypy demo.py demo.py:18: error: Argument 1 to "greet" has incompatible type "dict[str, str]"; expected "User" [arg-type] Found 1 error in 1 file (checked 1 source file)

Look at both halves of the output. At runtime both versions crash on a dict. But only the typed call is flagged by mypy (line 18) — mypy says nothing about greet_untyped, because by default it does not check functions that have no annotations at all.

Output के दोनों हिस्से देखिए। Runtime पर दोनों versions dict पर crash करते हैं। पर mypy सिर्फ़ typed call को पकड़ता है (line 18) — greet_untyped के बारे में mypy कुछ नहीं कहता, क्योंकि default रूप से वह बिना किसी annotation वाले functions को जाँचता ही नहीं।

看看输出的两部分。运行时,两个 版本传入字典都会崩溃。但只有带类型的调用被 mypy 标了出来(第 18 行)— mypy 对 greet_untyped 一言不发,因为默认情况下它根本不检查没有任何注解的函数。

⚠️
The Hidden Cost of Untyped Codeबिना Types वाले Code की छिपी क़ीमत无类型代码的隐藏代价

In a 10,000-line untyped codebase, changing a function signature means reading every caller by hand. In a typed codebase, mypy points at every mismatch at once.

10,000 lines के बिना-types वाले codebase में function signature बदलने का मतलब है हर caller को हाथ से पढ़ना। Types वाले codebase में mypy हर mismatch को एक साथ दिखा देता है।

在一个 10,000 行的无类型代码库中,修改一个函数签名意味着 手动阅读每一个调用者。在有类型的代码库中,mypy 会立刻指出每一处不匹配。

🔧
Fixed: "Every Serious Team Types Everything" Is an Overstatementसुधार: "हर गंभीर टीम सब कुछ Type करती है" बढ़ा-चढ़ाकर कहा गया已修正:"每个认真的团队都给所有代码加类型"言过其实

The original said every serious Python team since about 2020 — Instagram, Dropbox, Meta, Google — "types everything they ship". Large companies such as Dropbox and Instagram have written publicly about adding types to big codebases step by step, and type checking is now common. But that is not the same as everything being typed, and many good projects are only partly typed. Typing gradually, starting with the most important code, is normal.

मूल में कहा गया कि लगभग 2020 से हर गंभीर Python टीम — Instagram, Dropbox, Meta, Google — "जो भी ship करती है सब type करती है"। Dropbox और Instagram जैसी बड़ी कंपनियों ने बड़े codebases में धीरे-धीरे types जोड़ने के बारे में सार्वजनिक रूप से लिखा है, और type checking अब आम है। पर यह सब कुछ typed होने जैसा नहीं है, और कई अच्छे projects सिर्फ़ आंशिक रूप से typed हैं। सबसे ज़रूरी code से शुरू करके धीरे-धीरे types जोड़ना सामान्य है।

原文说大约 2020 年以来,每个认真的 Python 团队 — Instagram、Dropbox、Meta、Google —"给发布的所有代码都加了类型"。Dropbox 和 Instagram 等大公司确实公开介绍过如何逐步给大型代码库添加类型,类型检查如今也很普遍。但这并不等于所有代码都有类型,许多优秀的项目也只是部分有类型。从最重要的代码开始,逐步添加类型,是很正常的做法。


SectionSection第 03 节

The Three Places Type Hints Live 🏠Type Hints की तीन जगहें 🏠类型提示的三个位置 🏠

🏷️
1 — Variable annotations1 — Variable annotations1 — 变量注解
name: type = value
Attach a type to a variable. Used for module-level constants, class attributes, and locals whose type is not obvious.
count: int = 0
Variable के साथ type जोड़िए। Module-level constants, class attributes, और ऐसे locals के लिए जिनका type साफ़ न हो।
count: int = 0
给变量加上类型。用于模块级常量、类属性,以及类型不明显的局部变量。
count: int = 0
🛠️
2 — Argument annotations2 — Argument annotations2 — 参数注解
def fn(x: type):
Say what each parameter must be. Callers who pass the wrong thing get flagged by mypy before the code runs.
def upper(s: str)
बताइए हर parameter क्या होना चाहिए। ग़लत चीज़ देने वाले callers code चलने से पहले mypy से पकड़े जाते हैं।
def upper(s: str)
说明每个参数必须是什么。传错东西的调用者会在代码运行前被 mypy 标出。
def upper(s: str)
↩️
3 — Return annotations3 — Return annotations3 — 返回值注解
-> type:
Say what the function gives back. -> None for functions that return nothing. Callers get autocompletion on the result.
-> list[str]
बताइए function क्या लौटाता है। कुछ न लौटाने वाले functions के लिए -> None। Callers को result पर autocompletion मिलता है।
-> list[str]
说明函数返回什么。什么都不返回的函数写 -> None。调用者对结果能获得自动补全。
-> list[str]

Anatomy of a fully typed functionपूरी तरह typed function की बनावट完整带类型函数的结构

Figure 1 — The Parts of a Typed Functionचित्र 1 — Typed Function के हिस्से图 1 — 带类型函数的各个部分
Parts of a typed functiondef total(prices: list[float], tax: float = 0.0) -> float:keywordfunction nameargument typedefault valuereturn typeevery colon (:) introduces a type · the arrow (->) comes before the return typeहर colon (:) एक type शुरू करता है · arrow (->) return type से पहले आता है每个冒号(:)后面跟一个类型 · 箭头(->)后面是返回类型

Each colon introduces a type; the arrow introduces the return type.

हर colon एक type शुरू करता है; arrow return type शुरू करता है।

每个冒号引出一个类型;箭头引出返回类型。

def total(prices: list[float], tax: float = 0.0) -> float:
    return sum(prices) * (1 + tax)

# Callable with confidence:
result: float = total([9.99, 14.50, 3.20], tax=0.08)
print(result)
print(round(result, 4))
OUTPUT
29.905200000000004 29.9052
🔧
Fixed: The Real Output Is 29.905200000000004सुधार: असली Output 29.905200000000004 है已修正:真实输出是 29.905200000000004

The original showed 29.9052. Python actually printed 29.905200000000004, because decimal fractions like 9.99 cannot be stored exactly in binary floating point. The type float is right — it just says nothing about rounding. Use round(result, 4) for display, or decimal.Decimal for money.

मूल में 29.9052 दिखाया गया था। Python ने असल में 29.905200000000004 print किया, क्योंकि 9.99 जैसे दशमलव भिन्न binary floating point में ठीक-ठीक नहीं रखे जा सकते। Type float सही है — बस वह rounding के बारे में कुछ नहीं कहता। दिखाने के लिए round(result, 4), या पैसों के लिए decimal.Decimal लीजिए।

原文显示的是 29.9052。Python 实际打印的是 29.905200000000004,因为像 9.99 这样的十进制小数无法在二进制浮点数中精确存储。类型 float 没有错 — 只是它对舍入问题只字不提。显示时用 round(result, 4),涉及金额时用 decimal.Decimal。

Python stores hints — it does not enforce themPython hints रखता है — लागू नहीं करताPython 保存提示 — 但不强制执行

def total(prices: list[float], tax: float = 0.0) -> float:
    return sum(prices) * (1 + tax)

print(total.__annotations__)          # hints are stored, not enforced
print(total(["9.99"], tax=0))         # wrong type - Python runs it anyway
OUTPUT
$ python demo.py {'prices': list[float], 'tax': <class 'float'>, 'return': <class 'float'>} Traceback (most recent call last): File "demo.py", line 5, in <module> print(total(["9.99"], tax=0)) # wrong type - Python runs it anyway ~~~~~^^^^^^^^^^^^^^^^^ File "demo.py", line 2, in total return sum(prices) * (1 + tax) ~~~^^^^^^^^ TypeError: unsupported operand type(s) for +: 'int' and 'str' $ mypy demo.py demo.py:5: error: List item 0 has incompatible type "str"; expected "float" [list-item] Found 1 error in 1 file (checked 1 source file)
🔍
Not Quite "Ignored"पूरी तरह "अनदेखा" नहीं并非完全"忽略"

Python keeps the hints in __annotations__, and some tools use them while the program runs — dataclasses, TypedDict and NamedTuple (Section 09) read them to build classes. But Python never checks them: total(["9.99"]) ran until sum() crashed. Only mypy warned in advance.

Python hints को __annotations__ में रखता है, और कुछ tools program चलते समय उन्हें इस्तेमाल करते हैं — dataclasses, TypedDict और NamedTuple (Section 09) classes बनाने के लिए उन्हें पढ़ते हैं। पर Python उन्हें कभी जाँचता नहीं: total(["9.99"]) तब तक चला जब तक sum() crash नहीं हुआ। सिर्फ़ mypy ने पहले से चेताया।

Python 会把提示保存在 __annotations__ 中,有些工具会在程序运行时使用它们 — dataclasses、TypedDict 和 NamedTuple(第 09 节)会读取它们来构建类。但 Python 从不 检查 它们:total(["9.99"]) 一直运行到 sum() 崩溃。只有 mypy 提前发出了警告。


SectionSection第 04 节

Basic Types — Your Everyday Toolkit 🧰बुनियादी Types — रोज़ का औज़ार 🧰基本类型 — 日常工具箱 🧰

TypeType类型Example valueउदाहरण示例值When to useकब लें何时使用
int42Whole numbers — counts, indexes, IDsपूर्ण संख्याएँ — गिनती, indexes, IDs整数 — 计数、索引、ID
float3.14Decimals — prices, measurements, ratiosदशमलव — दाम, माप, अनुपात小数 — 价格、测量值、比率
str"hello"Any textकोई भी text任意文本
boolTrueTrue/False flagsTrue/False flags真/假标志
bytesb"raw"Binary data, file contents, network buffersBinary data, file contents, network buffers二进制数据、文件内容、网络缓冲区
NoneNoneNo value — usually in X | None or -> Noneकोई value नहीं — आम तौर पर X | None या -> None में没有值 — 通常写成 X | None 或 -> None
list[X][1, 2, 3]Ordered, changeable, every item of type Xक्रम वाली, बदलने योग्य, हर item type X का有序、可变,每个元素都是 X 类型
tuple[X, Y](1, "a")Fixed size; each position has its own typeतय size; हर स्थान का अपना type固定长度;每个位置有自己的类型
dict[K, V]{"a": 1}Keys of type K mapped to values of type VType K की keys, type V की values键为 K 类型,值为 V 类型
set[X]{1, 2, 3}Unique, unordered, every item of type Xअनोखे, बिना क्रम, हर item type X का唯一、无序,每个元素都是 X 类型
# Variables
name: str = "Ada"
age: int = 30
tags: list[str] = ["dev", "python"]
scores: dict[str, float] = {"math": 91.5, "english": 88.0}
coord: tuple[float, float] = (51.5074, -0.1278)   # exactly 2 floats
unique_ids: set[int] = {101, 102, 103}

# Function
def describe(person: dict[str, str]) -> str:
    return f"{person['name']} ({person['role']})"

print(describe({"name": name, "role": tags[0]}))
age = "thirty"                    # Python allows it...
coord = (1.0, 2.0, 3.0)           # ...and this too
print(age, coord)
OUTPUT
$ python demo.py Ada (dev) thirty (1.0, 2.0, 3.0) $ mypy demo.py demo.py:14: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] demo.py:15: error: Incompatible types in assignment (expression has type "tuple[float, float, float]", variable has type "tuple[float, float]") [assignment] Found 2 errors in 1 file (checked 1 source file)
💡
Python 3.9+ SyntaxPython 3.9+ SyntaxPython 3.9+ 语法

Write list[int], not List[int]; dict[str, int], not Dict[str, int]. The lowercase built-in forms work from Python 3.9 and are the modern standard.

List[int] नहीं, list[int] लिखिए; Dict[str, int] नहीं, dict[str, int]। Lowercase built-in रूप Python 3.9 से चलते हैं और आधुनिक मानक हैं।

写 list[int],而不是 List[int];写 dict[str, int],而不是 Dict[str, int]。小写的内置形式从 Python 3.9 起可用,是现代标准写法。

🔧
Fixed: Callable and Iterator Have a Better Homeसुधार: Callable और Iterator का बेहतर घर已修正:Callable 和 Iterator 有更好的出处

The original said to import from typing things like Callable and Iterator because they have no built-in equivalent. Since Python 3.9 the recommended place is collections.abc (Callable, Iterable, Iterator, Sequence, …); the typing versions are kept only as deprecated aliases. Things that exist only in typing — Protocol, TypedDict, TypeVar, Any — still come from typing.

मूल में कहा गया कि Callable और Iterator जैसी चीज़ें typing से import कीजिए क्योंकि उनका कोई built-in रूप नहीं। Python 3.9 से सुझाई गई जगह collections.abc है (Callable, Iterable, Iterator, Sequence, …); typing वाले versions सिर्फ़ deprecated aliases के रूप में रखे गए हैं। जो सिर्फ़ typing में हैं — Protocol, TypedDict, TypeVar, Any — वे अब भी typing से आते हैं।

原文说 Callable、Iterator 这类没有内置等价物的东西要从 typing 导入。从 Python 3.9 起,推荐的位置是 collections.abc(Callable、Iterable、Iterator、Sequence 等);typing 中的版本只是作为已弃用的别名保留。只存在于 typing 中的东西 — Protocol、TypedDict、TypeVar、Any — 仍然从 typing 导入。

🧩 Simulator: Type Compatibility Explorer🧩 Simulator: Type Compatibility Explorer🧩 模拟器:类型兼容性探索器

Which values can go into which annotation? Every cell is mypy's real verdict on x: ANNOTATION = VALUE (80 checks). Click any cell — some answers are surprising.

कौन-सी values किस annotation में जा सकती हैं? हर cell x: ANNOTATION = VALUE पर mypy का असली फ़ैसला है (80 जाँच)। किसी भी cell पर click कीजिए — कुछ जवाब हैरान करने वाले हैं।

哪些值可以赋给哪种注解?每个格子都是 mypy 对 x: 注解 = 值 的真实判定(共 80 项检查)。点击任意格子 — 有些答案会让你意外。

Type Compatibility ExplorerType Compatibility Explorer类型兼容性探索器80 real mypy checks80 असली mypy जाँच80 次真实 mypy 检查

SectionSection第 05 节

Optional Values — Handling None Safely 🕳️Optional Values — None को सुरक्षित संभालना 🕳️可选值 — 安全地处理 None 🕳️

A value that may be either a real value or None is one of the most common patterns in Python. Type hints make you say so — and mypy makes you check before you use it.

ऐसी value जो या तो असली value हो या None, Python के सबसे आम patterns में से है। Type hints आपसे यह बताने को कहते हैं — और mypy इस्तेमाल से पहले जाँच करवाता है।

一个值 要么 是真实的值,要么 是 None,这是 Python 中最常见的模式之一。类型提示让你把这一点写明 — 而 mypy 要求你在使用前先检查。

from dataclasses import dataclass

@dataclass
class User:
    name: str

db: dict[int, User] = {42: User("Ada")}

# Modern syntax (Python 3.10+) - preferred
def find_user(user_id: int) -> User | None:
    return db.get(user_id)           # returns User OR None

def show(user_id: int) -> None:
    user = find_user(user_id)
    if user is None:
        print("not found")
    else:
        print(user.name)             # mypy KNOWS user is User here

def show_unsafe(user_id: int) -> None:
    user = find_user(user_id)
    print(user.name)                 # no None check!

show(42); show(7)
show_unsafe(7)
OUTPUT
$ python demo.py Ada not found Traceback (most recent call last): File "demo.py", line 25, in <module> show_unsafe(7) ~~~~~~~~~~~^^^ File "demo.py", line 22, in show_unsafe print(user.name) # no None check! ^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'name' $ mypy demo.py demo.py:22: error: Item "None" of "User | None" has no attribute "name" [union-attr] Found 1 error in 1 file (checked 1 source file)

The runtime crash only happened for id 7 — a test with id 42 would pass. mypy found the unsafe line without running anything.

Runtime crash सिर्फ़ id 7 पर हुआ — id 42 वाला test pass हो जाता। mypy ने कुछ चलाए बिना असुरक्षित line पकड़ ली।

运行时只有 id 7 才会崩溃 — 用 id 42 的测试会顺利通过。mypy 没有运行任何代码就找到了不安全的那一行。

📝
Two Spellings, One Meaningदो लिखावट, एक मतलब两种写法,同一含义

User | None (Python 3.10+) and Optional[User] (from typing, available since 3.5) mean the same thing. Prefer X | None in new code. On Python 3.9 or older, the | form works inside annotations only if the file starts with from __future__ import annotations.

User | None (Python 3.10+) और Optional[User] (typing से, 3.5 से उपलब्ध) का मतलब एक ही है। नए code में X | None लीजिए। Python 3.9 या पुराने पर | वाला रूप annotations में तभी चलता है जब file from __future__ import annotations से शुरू हो।

User | None(Python 3.10+)和 Optional[User](来自 typing,自 3.5 起可用)含义相同。新代码优先用 X | None。在 Python 3.9 或更早版本中,只有文件开头写了 from __future__ import annotations,注解里才能使用 | 形式。

⚠️
The Billion-Dollar Mistake, Caught Earlyअरबों डॉलर की ग़लती, जल्दी पकड़ी गई"价值十亿美元的错误",提前抓住

Null-reference bugs are among the most common crashes in real systems. With mypy's default settings you cannot use an attribute of an X | None value without checking it first — it reports Item "None" of "User | None" has no attribute "name". That removes a whole class of crashes before they ship.

Null-reference bugs असली systems के सबसे आम crashes में से हैं। mypy की default settings के साथ आप X | None value का attribute पहले जाँचे बिना इस्तेमाल नहीं कर सकते — यह Item "None" of "User | None" has no attribute "name" report करता है। इससे crashes की एक पूरी श्रेणी ship होने से पहले हट जाती है।

空引用 bug 是真实系统中最常见的崩溃原因之一。在 mypy 的默认设置下,你不能在未检查之前就使用 X | None 值的属性 — 它会报告 Item "None" of "User | None" has no attribute "name"。这在上线前就消除了一整类崩溃。


SectionSection第 06 节

Union Types — When a Value Could Be Several Things 🔀Union Types — जब Value कई तरह की हो सकती है 🔀联合类型 — 当值可能是几种类型之一 🔀

Sometimes an argument can really be one of several types. Write them with | (or Union[...] on older Python). Inside the function, an isinstance check tells mypy which type you have — this is called narrowing.

कभी-कभी एक argument सच में कई types में से एक हो सकता है। उन्हें | से लिखिए (पुराने Python पर Union[...])। Function के अंदर isinstance जाँच mypy को बताती है कि आपके पास कौन-सा type है — इसे narrowing कहते हैं।

有时一个参数确实可能是几种类型之一。用 | 把它们写出来(旧版 Python 用 Union[...])。在函数内部,isinstance 检查会告诉 mypy 你手里是哪种类型 — 这叫做 类型收窄(narrowing)。

def to_int(value: int | str | float) -> int:
    return int(value)

# Narrowing with isinstance - mypy learns the type inside each branch
def describe_id(x: int | str) -> str:
    if isinstance(x, int):
        return f"numeric id: {x:06d}"       # mypy: x is int here
    return f"named id:   {x.upper()}"       # mypy: x is str here

print(describe_id(42))
print(describe_id("ada"))
print(to_int("7"), to_int(3.9))
print(to_int("3.5"))                       # types are fine - the VALUE is not
OUTPUT
$ python demo.py numeric id: 000042 named id: ADA 7 3 Traceback (most recent call last): File "demo.py", line 13, in <module> print(to_int("3.5")) # types are fine - the VALUE is not ~~~~~~^^^^^^^ File "demo.py", line 2, in to_int return int(value) ValueError: invalid literal for int() with base 10: '3.5' $ mypy demo.py Success: no issues found in 1 source file
⚠️
Right Type, Wrong Valueसही Type, ग़लत Value类型对了,值却错了

mypy said Success, yet to_int("3.5") crashed with ValueError. Types check the kind of value, not its content: "3.5" really is a str. Also note to_int(3.9) gave 3 — int() cuts off the decimals. Values still need validation and tests.

mypy ने Success कहा, फिर भी to_int("3.5") ValueError से crash हुआ। Types value का प्रकार जाँचते हैं, उसकी सामग्री नहीं: "3.5" सच में str है। यह भी देखिए कि to_int(3.9) ने 3 दिया — int() दशमलव काट देता है। Values को अब भी validation और tests चाहिए।

mypy 说 Success,可 to_int("3.5") 还是因 ValueError 崩溃了。类型检查的是值的 种类,而不是内容:"3.5" 确实是 str。另外注意 to_int(3.9) 得到的是 3 — int() 会直接截掉小数。值本身仍然需要校验和测试。

🔎 Simulator: Narrowing Stepper🔎 Simulator: Narrowing Stepper🔎 模拟器:类型收窄单步演示

Pick a pattern and step through it. The badge shows the type mypy believes the variable has on that line — every type here comes from a real reveal_type() call.

एक pattern चुनिए और step-by-step चलिए। Badge दिखाता है कि mypy उस line पर variable का कौन-सा type मानता है — यहाँ हर type असली reveal_type() call से आया है।

选一种模式,逐步查看。徽章显示 mypy 认为该变量 在这一行 是什么类型 — 这里的每个类型都来自真实的 reveal_type() 调用。

Narrowing StepperNarrowing Stepper类型收窄单步演示types from real reveal_type()असली reveal_type() से types类型来自真实的 reveal_type()

SectionSection第 07 节

The Type Checker Pipeline — How mypy Works ⚙️Type Checker Pipeline — mypy कैसे काम करता है ⚙️类型检查流水线 — mypy 如何工作 ⚙️

Figure 2 — How mypy Turns Annotations Into Errorsचित्र 2 — mypy annotations को errors में कैसे बदलता है图 2 — mypy 如何把注解变成错误报告
mypy pipelineYOUR .PY FILEआपकी .PY FILE你的 .py 文件demo.pyPARSEPARSE解析syntax treeINFER + CHECKINFER + CHECK推断 + 检查types of every lineREPORTREPORT报告errors / SuccessYOUR PROGRAM NEVER RUNS DURING THISइस दौरान आपका PROGRAM कभी नहीं चलता这一过程中,你的程序从不运行mypy reads the code like a careful reviewer · it runs before python, not inside itmypy एक सावधान reviewer की तरह code पढ़ता है · यह python से पहले चलता है, उसके अंदर नहींmypy 像一位细心的审阅者那样阅读代码 · 它在 python 之前运行,而不是在其内部

mypy is a static analyzer: it reads the file, it does not execute it. Your program does not get slower.

mypy एक static analyzer है: यह file पढ़ता है, चलाता नहीं। आपका program धीमा नहीं होता।

mypy 是静态分析器:它读取文件,而不执行文件。你的程序不会因此变慢。

Running mypymypy चलाना运行 mypy

# Install once
pip install mypy

# Check a single file
mypy my_script.py

# Check a whole project - strict mode turns on many extra checks
mypy --strict src/

# In CI: mypy exits with code 1 when it finds errors, which fails the build
mypy --strict src/ tests/
🔧
Fixed: mypy Already Fails the Build — No "|| exit 1" Neededसुधार: mypy पहले से Build fail करता है — "|| exit 1" की ज़रूरत नहीं已修正:mypy 本身就会让构建失败 — 不需要 "|| exit 1"

The original CI line was mypy --strict src/ tests/ || exit 1. mypy already exits with status 1 when it finds type errors (and 2 for crashes or bad options), so CI fails without the extra part. Also, --strict enables a large set of extra checks — not literally "everything", and it cannot catch value bugs or mutable defaults (Sections 06 and 12).

मूल CI line mypy --strict src/ tests/ || exit 1 थी। Type errors मिलने पर mypy पहले से status 1 के साथ exit करता है (और crash या ग़लत options पर 2), इसलिए अतिरिक्त हिस्से के बिना भी CI fail होता है। साथ ही, --strict अतिरिक्त जाँचों का बड़ा सेट चालू करता है — सचमुच "सब कुछ" नहीं, और यह value bugs या mutable defaults नहीं पकड़ सकता (Sections 06 और 12)।

原文的 CI 命令是 mypy --strict src/ tests/ || exit 1。mypy 发现类型错误时本来就会以状态码 1 退出(崩溃或选项错误时为 2),所以不加后半部分 CI 也会失败。此外,--strict 开启的是一大批额外检查 — 并非字面意义上的"捕获一切",它抓不到值错误,也抓不到可变默认值(第 06 和 12 节)。

Here is a real mypy report. Notice it says nothing about runtime — it only reads the file:

यह असली mypy report है। देखिए यह runtime के बारे में कुछ नहीं कहता — यह सिर्फ़ file पढ़ता है:

这是一份真实的 mypy 报告。注意它对运行时只字不提 — 它只是读取文件:

from dataclasses import dataclass

@dataclass
class User:
    name: str

def greet(name: str) -> str:
    return "Hello " + name

def find(uid: int) -> User | None:
    return User("Ada") if uid == 1 else None

greet(42)                              # wrong argument type
print(find(2).name)                    # may be None
OUTPUT — mypy
$ mypy demo.py demo.py:13: error: Argument 1 to "greet" has incompatible type "int"; expected "str" [arg-type] demo.py:14: error: Item "None" of "User | None" has no attribute "name" [union-attr] Found 2 errors in 1 file (checked 1 source file)

🕵️ Simulator: Who Catches It — Python or mypy?🕵️ Simulator: कौन पकड़ता है — Python या mypy?🕵️ 模拟器:谁能抓住它 — Python 还是 mypy?

Eight tiny programs. For each one you see what Python did when it ran and what mypy said without running it. They catch different bugs — which is why you want both, plus tests.

आठ छोटे programs। हर एक के लिए देखिए कि चलाने पर Python ने क्या किया और बिना चलाए mypy ने क्या कहा। दोनों अलग-अलग bugs पकड़ते हैं — इसीलिए आपको दोनों चाहिए, और tests भी।

八个小程序。每个都能看到 Python 运行时做了什么,以及 mypy 不运行就说了什么。两者抓到的 bug 不同 — 所以两者都需要,再加上测试。

Who Catches It?कौन पकड़ता है?谁能抓住它?real runs · real mypyअसली runs · असली mypy真实运行 · 真实 mypy
$ python demo.py$ python demo.py$ python demo.py
$ mypy demo.py$ mypy demo.py$ mypy demo.py

SectionSection第 08 节

Callable, Iterable, Iterator — Function and Sequence Types 🔁Callable, Iterable, Iterator — Function और Sequence Types 🔁Callable、Iterable、Iterator — 函数与序列类型 🔁

from collections.abc import Callable, Iterable, Iterator

# A parameter that is a function itself
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
    return fn(a, b)
#              ^ argument types  ^ return type

print(apply(lambda x, y: x + y, 3, 4))

# Any iterable - lists, tuples, generators, sets, ...
def total(numbers: Iterable[float]) -> float:
    return sum(numbers)

print(total([1.5, 2.5]), total((1, 2, 3)), total(x / 2 for x in range(4)))

# A generator that yields ints - Iterator[int] is usually enough
def count_up(n: int) -> Iterator[int]:
    for i in range(n):
        yield i

print(list(count_up(4)))
apply(len, 3, 4)                       # len does not take (int, int)
OUTPUT
$ python demo.py 7 4.0 6 3.0 [0, 1, 2, 3] Traceback (most recent call last): File "demo.py", line 22, in <module> apply(len, 3, 4) # len does not take (int, int) ~~~~~^^^^^^^^^^^ File "demo.py", line 5, in apply return fn(a, b) TypeError: len() takes exactly one argument (2 given) $ mypy demo.py demo.py:22: error: Argument 1 to "apply" has incompatible type "Callable[[Sized], int]"; expected "Callable[[int, int], int]" [arg-type] Found 1 error in 1 file (checked 1 source file)

Callable[[int, int], int] reads as "a function that takes two ints and returns an int". mypy rejected apply(len, 3, 4) because len takes one argument, not two ints. At runtime that line would raise TypeError.

Callable[[int, int], int] को ऐसे पढ़िए: "एक function जो दो ints लेता है और int लौटाता है"। mypy ने apply(len, 3, 4) को ठुकराया क्योंकि len एक argument लेता है, दो ints नहीं। Runtime पर वह line TypeError देती।

Callable[[int, int], int] 读作"一个接收两个 int、返回 int 的函数"。mypy 拒绝了 apply(len, 3, 4),因为 len 只接收一个参数,而不是两个 int。运行时这一行会抛出 TypeError。

💡
Accept Iterable, Return listIterable लीजिए, list लौटाइए参数用 Iterable,返回用 list

Accept the widest reasonable type; return the most specific. total(numbers: Iterable[float]) worked above with a list, a tuple and a generator. But a function that returns a list should say -> list[int], so callers can index it and append to it.

सबसे चौड़ा उचित type लीजिए; सबसे ख़ास type लौटाइए। ऊपर total(numbers: Iterable[float]) list, tuple और generator तीनों के साथ चला। पर list लौटाने वाले function को -> list[int] लिखना चाहिए, ताकि callers उसे index और append कर सकें।

参数接受尽可能宽泛的合理类型;返回值给出最具体的类型。上面的 total(numbers: Iterable[float]) 对列表、元组 和 生成器都适用。但返回列表的函数应写 -> list[int],这样调用者才能对它索引和追加。

📦
Where to Import Fromकहाँ से Import करें从哪里导入

As fixed in Section 04: Callable, Iterable, Iterator and Sequence come from collections.abc. Note that float also accepts int — that is why total((1, 2, 3)) passes the check.

जैसा Section 04 में सुधारा गया: Callable, Iterable, Iterator और Sequence collections.abc से आते हैं। ध्यान दें कि float int भी स्वीकार करता है — इसीलिए total((1, 2, 3)) जाँच पास करता है।

如第 04 节所修正:Callable、Iterable、Iterator 和 Sequence 都从 collections.abc 导入。注意 float 也接受 int — 所以 total((1, 2, 3)) 能通过检查。


SectionSection第 09 节

Structured Data — dataclass, TypedDict, NamedTuple 🗂️Structured Data — dataclass, TypedDict, NamedTuple 🗂️结构化数据 — dataclass、TypedDict、NamedTuple 🗂️

Real programs pass around structured data. Type hints let the checker verify the shape of that data, field by field.

असली programs structured data इधर-उधर भेजते हैं। Type hints checker को उस data का आकार, field-दर-field जाँचने देते हैं।

真实程序会到处传递结构化数据。类型提示让检查器能够逐个字段地验证数据的 形状。

📦
dataclass
object-shaped dataobject जैसा data对象形态的数据
A mutable record with methods and defaults. It writes __init__, __repr__ and __eq__ for you. First choice for domain objects.
Methods और defaults वाला बदलने योग्य record। यह आपके लिए __init__, __repr__ और __eq__ लिखता है। Domain objects के लिए पहली पसंद।
带方法和默认值的可变记录。它会替你生成 __init__、__repr__ 和 __eq__。领域对象的首选。
📂
TypedDict
dict with declared keysतय keys वाला dict声明了键的字典
A plain dict whose keys and value types are known ahead of time. Good for JSON API responses and settings.
एक सामान्य dict जिसकी keys और value types पहले से पता हों। JSON API responses और settings के लिए अच्छा।
一个普通的 dict,其键和值的类型事先已知。适合 JSON API 响应和配置。
📍
NamedTuple
immutable, positionalन बदलने योग्य, स्थान-आधारित不可变、按位置
A tiny, immutable, tuple-shaped record. Ideal for coordinates, points and pairs you do not want changed.
छोटा, न बदलने योग्य, tuple जैसा record। Coordinates, points और ऐसे जोड़ों के लिए बढ़िया जिन्हें आप बदलना नहीं चाहते।
小巧、不可变、元组形态的记录。非常适合坐标、点以及不希望被修改的数据对。
from dataclasses import dataclass, field
from typing import TypedDict, NamedTuple

# 1) dataclass - the workhorse
@dataclass
class User:
    id: int
    name: str
    email: str
    active: bool = True
    tags: list[str] = field(default_factory=list)

u = User(id=1, name="Ada", email="ada@x.io")
print(u)

# 2) TypedDict - for JSON-shaped data you get from an API
class WeatherPayload(TypedDict):
    city: str
    temp_c: float
    humidity: int

def summarize(w: WeatherPayload) -> str:
    return f"{w['city']}: {w['temp_c']}°C"

print(summarize({"city": "London", "temp_c": 14.2, "humidity": 67}))

# 3) NamedTuple - tiny, immutable, positional
class Point(NamedTuple):
    x: float
    y: float

p = Point(1.0, 2.0)
print(p.x, p.y)
OUTPUT
$ python demo.py User(id=1, name='Ada', email='ada@x.io', active=True, tags=[]) London: 14.2°C 1.0 2.0 $ mypy --strict demo.py Success: no issues found in 1 source file
🔍
Here the Hints Do Work at Runtimeयहाँ Hints Runtime पर काम करते हैं这里的提示在运行时确实起作用

This is the exception mentioned in Section 03: @dataclass and NamedTuple read the annotations to build __init__ — remove name: str and the field disappears. They still do not check types: User(id="one", …) would run. A TypedDict is just a normal dict at runtime.

यह Section 03 में बताया गया अपवाद है: @dataclass और NamedTuple __init__ बनाने के लिए annotations पढ़ते हैं — name: str हटाइए और field ग़ायब हो जाता है। फिर भी वे types जाँचते नहीं: User(id="one", …) चल जाएगा। TypedDict runtime पर बस एक सामान्य dict है।

这就是第 03 节提到的例外:@dataclass 和 NamedTuple 会读取注解来生成 __init__ — 删掉 name: str,这个字段就消失了。但它们仍然不 检查 类型:User(id="one", …) 照样能运行。TypedDict 在运行时只是一个普通字典。


SectionSection第 10 节

Generics — Functions That Keep the Type 🧬Generics — Type बनाए रखने वाले Functions 🧬泛型 — 保留类型的函数 🧬

Some functions are type-preserving: whatever type goes in, the same type comes out. A TypeVar tells mypy "this is the same type in and out". reveal_type() asks mypy to print what it inferred.

कुछ functions type बनाए रखते हैं: जो type अंदर जाता है, वही बाहर आता है। TypeVar mypy को बताता है "अंदर और बाहर यही एक type है"। reveal_type() mypy से उसका अनुमान print करवाता है।

有些函数是 保留类型 的:传进去什么类型,出来就是同一类型。TypeVar 告诉 mypy"进出是同一个类型"。reveal_type() 让 mypy 打印它推断出的类型。

from collections.abc import Sequence
from typing import TypeVar, reveal_type

T = TypeVar("T")

def first(items: Sequence[T]) -> T | None:
    return items[0] if items else None

x = first([1, 2, 3])
y = first(["a", "b", "c"])
reveal_type(x)          # ask mypy what it thinks
reveal_type(y)
print(x, y)
OUTPUT
$ python demo.py Runtime type is 'int' Runtime type is 'str' 1 a $ mypy demo.py demo.py:11: note: Revealed type is "int | None" demo.py:12: note: Revealed type is "str | None" Success: no issues found in 1 source file

Python and mypy see different things: at runtime reveal_type() prints the actual class (int), while mypy reports the declared type int | None.

Python और mypy अलग चीज़ें देखते हैं: runtime पर reveal_type() असली class (int) print करता है, जबकि mypy घोषित type int | None बताता है।

Python 和 mypy 看到的东西不同:运行时 reveal_type() 打印实际的类(int),而 mypy 报告声明的类型 int | None。

Figure 3 — How a TypeVar Flows Through a Functionचित्र 3 — TypeVar function से कैसे गुज़रता है图 3 — TypeVar 如何贯穿函数
TypeVar flowinputlist[int]T = intoutputint | Noneinputlist[str]T = stroutputstr | Nonefirst(items:Sequence[T]) -> T | Noneone generic function · the type flows through · no Any, no castएक generic function · type आर-पार बहता है · न Any, न cast一个泛型函数 · 类型贯穿始终 · 不用 Any,不用 cast

mypy fills T from the argument, then uses the same T for the result.

mypy argument से T भरता है, फिर result के लिए वही T लेता है।

mypy 根据参数确定 T,再把同一个 T 用于返回值。

Python 3.12+ syntaxPython 3.12+ syntaxPython 3.12+ 语法

from collections.abc import Sequence
from typing import reveal_type

# Python 3.12+ syntax - no TypeVar import needed
def first[T](items: Sequence[T]) -> T | None:
    return items[0] if items else None

z = first((1.5, 2.5))
reveal_type(z)
print(z)
OUTPUT
$ python demo.py Runtime type is 'float' 1.5 $ mypy demo.py demo.py:9: note: Revealed type is "float | None" Success: no issues found in 1 source file
🔧
Fixed: Two "first" Functions in One File Is an Errorसुधार: एक File में दो "first" Functions एक Error है已修正:同一文件里定义两个 "first" 是错误

The original put the TypeVar version and the def first[T] version one after the other in the same snippet. mypy reports that as Name "first" already defined. They are alternatives — pick one style per function. We split them into two files above. The [T] syntax needs Python 3.12+.

मूल में TypeVar version और def first[T] version एक ही snippet में एक के बाद एक थे। mypy इसे Name "first" already defined बताता है। वे विकल्प हैं — हर function के लिए एक शैली चुनिए। ऊपर हमने उन्हें दो files में बाँटा है। [T] syntax के लिए Python 3.12+ चाहिए।

原文把 TypeVar 版本和 def first[T] 版本前后放在同一段代码里。mypy 会报告 Name "first" already defined。它们是二选一的写法 — 每个函数选一种风格即可。上面我们把它们拆成了两个文件。[T] 语法需要 Python 3.12+。


SectionSection第 11 节

Protocols — Duck Typing, Verified 🦆Protocols — Duck Typing, जाँचा हुआ 🦆Protocol — 经过验证的鸭子类型 🦆

Python's motto is "if it walks like a duck and quacks like a duck, it is a duck". A Protocol lets mypy check that style: any class with the right method shape is accepted — no inheritance needed.

Python का मूल मंत्र है "अगर वह बत्तख की तरह चलता और बोलता है, तो वह बत्तख है"। Protocol mypy को यह शैली जाँचने देता है: सही method आकार वाली कोई भी class स्वीकार होती है — inheritance की ज़रूरत नहीं।

Python 的信条是"走起来像鸭子、叫起来像鸭子,那就是鸭子"。Protocol 让 mypy 能检查这种风格:任何具有正确方法 形状 的类都会被接受 — 不需要继承。

from typing import Protocol

class SupportsWrite(Protocol):
    def write(self, data: str) -> int: ...

def log(msg: str, sink: SupportsWrite) -> None:
    sink.write(msg + "\n")

# Any object with a matching .write() method is accepted
#   - no inheritance required, no registration required
import sys
log("starting", sys.stdout)             # works - stdout has .write

from io import StringIO
buf = StringIO()
log("buffered", buf)                    # also works - StringIO has .write
print("buffer holds:", repr(buf.getvalue()))

class Silent:
    def write(self, data: str) -> int:
        return len(data)                # our own class also matches

log("via silent", Silent())             # works

class Wrong:
    def write(self, data: bytes) -> int:
        return 0

log("oops", Wrong())                    # wrong method shape
OUTPUT
$ python demo.py starting buffer holds: 'buffered\n' $ mypy demo.py demo.py:29: error: Argument 2 to "log" has incompatible type "Wrong"; expected "SupportsWrite" [arg-type] demo.py:29: note: Following member(s) of "Wrong" have conflicts: demo.py:29: note: Expected: demo.py:29: note: def write(self, data: str) -> int demo.py:29: note: Got: demo.py:29: note: def write(self, data: bytes) -> int Found 1 error in 1 file (checked 1 source file)

Three unrelated classes passed. Wrong also has a write method, but it takes bytes, so mypy rejected it and showed the expected and actual signatures. Note: at runtime even Wrong ran without error — only mypy noticed.

तीन असंबंधित classes पास हुईं। Wrong में भी write method है, पर वह bytes लेता है, इसलिए mypy ने उसे ठुकराया और अपेक्षित व असली signatures दिखाए। ध्यान दें: runtime पर Wrong भी बिना error चला — सिर्फ़ mypy ने ध्यान दिया।

三个毫不相关的类都通过了。Wrong 也有 write 方法,但它接收的是 bytes,所以 mypy 拒绝了它,并列出了期望的和实际的签名。注意:运行时连 Wrong 都没有报错 — 只有 mypy 发现了问题。

Figure 4 — Structural Typing (Protocol Matching)चित्र 4 — Structural Typing (Protocol मिलान)图 4 — 结构化类型(Protocol 匹配)
Protocol matchingSupportsWritewrite(self, data: str) -> intsys.stdoutwrite(str) ✓StringIOwrite(str) ✓Silentwrite(str) ✓Wrongwrite(bytes) ✗no shared parent class · only the method shape is comparedकोई साझा parent class नहीं · सिर्फ़ method का आकार मिलाया जाता है没有共同的父类 · 只比较方法的形状

Classes match the Protocol just by having the right method shape; a different parameter type breaks the match.

Classes सिर्फ़ सही method आकार होने से Protocol से मेल खाती हैं; अलग parameter type मेल तोड़ देता है।

类只要方法形状正确就匹配 Protocol;参数类型不同就会匹配失败。


SectionSection第 12 节

Common Pitfalls (and How to Avoid Them) 🕳️आम ग़लतियाँ (और उनसे कैसे बचें) 🕳️常见陷阱(以及如何避免)🕳️

Mistakeग़लती错误What happensक्या होता है会发生什么Fixसुधार修正
Using Any everywhereहर जगह Any到处用 AnyTurns checking off silentlyजाँच चुपचाप बंद悄悄关闭了检查Use specific types; use object if truly unknownख़ास types लीजिए; सच में अनजान हो तो object使用具体类型;确实未知时用 object
Mutable default: tags: list[str] = []Mutable default: tags: list[str] = []可变默认值:tags: list[str] = []One list shared by every call — and mypy does not warnहर call एक ही list साझा करता है — और mypy चेतावनी नहीं देता每次调用共享同一个列表 — 而且 mypy 不会 警告Default to None, make the list insideDefault None रखिए, list अंदर बनाइए默认值设为 None,在函数内创建列表
Mixing List and listList और list मिलाना混用 List 和 listOld imports mixed with 3.9+ syntaxपुराने imports 3.9+ syntax के साथ旧式导入与 3.9+ 语法混在一起Use list[X] everywhereहर जगह list[X]统一用 list[X]
Forgetting the return annotationReturn annotation भूलना忘记写返回值注解Result is Any; callers lose checks and autocompleteResult Any बनता है; callers की जाँच और autocomplete खो जाते हैं结果变成 Any;调用方失去检查和自动补全Always add -> Type (or -> None)हमेशा -> Type (या -> None) जोड़िए总是加上 -> Type(或 -> None)
name: str = Nonename: str = Nonename: str = Nonemypy error: Incompatible default (see below)mypy error: Incompatible default (नीचे देखें)mypy 报错:Incompatible default(见下文)Write str | None = Nonestr | None = None लिखिए写成 str | None = None
Not narrowing before useइस्तेमाल से पहले narrowing नहीं使用前未收窄类型Item "None" of "User | None" has no attribute "name"Item "None" of "User | None" has no attribute "name"Item "None" of "User | None" has no attribute "name"Add an if x is not None: checkif x is not None: जाँच जोड़िए加上 if x is not None: 检查
Circular imports just for typesसिर्फ़ types के लिए circular imports仅为类型而产生的循环导入ImportError at runtimeRuntime पर ImportError运行时出现 ImportErrorImport inside if TYPE_CHECKING:if TYPE_CHECKING: के अंदर import在 if TYPE_CHECKING: 中导入
🔧
Fixed: "Unknown" Is Not a Python Type, and mypy Misses Mutable Defaultsसुधार: "Unknown" Python Type नहीं है, और mypy Mutable Defaults नहीं पकड़ता已修正:"Unknown" 不是 Python 类型,mypy 也抓不到可变默认值

The original table suggested object "or Unknown". There is no Unknown type in Python — it is a word some editors show in tooltips. Use object. The table also implied type hints protect you from the mutable-default bug; the real run below shows mypy --strict reports Success for it. The "not narrowing" row now shows mypy's real message.

मूल table में object "या Unknown" सुझाया गया था। Python में कोई Unknown type नहीं है — यह कुछ editors के tooltips में दिखने वाला शब्द है। object लीजिए। Table से यह भी लगता था कि type hints mutable-default bug से बचाते हैं; नीचे का असली run दिखाता है कि mypy --strict इसके लिए Success देता है। "narrowing नहीं" वाली row अब mypy का असली message दिखाती है।

原表格建议用 object"或 Unknown"。Python 中没有 Unknown 类型 — 那只是某些编辑器在提示框里显示的词。请用 object。表格还暗示类型提示能防住可变默认值 bug;下面的真实运行显示 mypy --strict 对它报告的是 Success。"未收窄"那一行现在展示的是 mypy 的真实信息。

# WRONG - mutable default
def add_tag_bad(item: str, tags: list[str] = []) -> list[str]:
    tags.append(item)
    return tags        # the SAME list persists across every call!

print(add_tag_bad("a"))
print(add_tag_bad("b"))      # expected ['b']

# RIGHT - None sentinel
def add_tag(item: str, tags: list[str] | None = None) -> list[str]:
    tags = list(tags) if tags is not None else []
    tags.append(item)
    return tags

print(add_tag("a"))
print(add_tag("b"))
OUTPUT
$ python demo.py ['a'] ['a', 'b'] ['a'] ['b'] $ mypy --strict demo.py Success: no issues found in 1 source file

🪤 Simulator: The Mutable Default Trap🪤 Simulator: Mutable Default का जाल🪤 模拟器:可变默认值陷阱

Step through four calls. Watch the one default list that Python created when def ran — every call that relies on the default changes the same object. The ids come from a real run.

चार calls step-by-step देखिए। उस एक default list पर नज़र रखिए जो Python ने def चलते समय बनाई — default पर निर्भर हर call उसी object को बदलता है। ids असली run से हैं।

逐步查看四次调用。注意 Python 在执行 def 时创建的那 一个 默认列表 — 每次依赖默认值的调用改的都是同一个对象。id 来自真实运行。

Mutable Default TrapMutable Default का जाल可变默认值陷阱ids from a real runअसली run के idsid 来自真实运行

What mypy does catch: a None default and a missing returnmypy क्या पकड़ता है: None default और छूटा returnmypy 能抓到的:None 默认值和缺失的 return

def greet(name: str = None) -> str:      # "None" default on a plain str
    return "Hello " + (name or "guest")

def find(xs: list[int], target: int) -> int:
    for i, x in enumerate(xs):
        if x == target:
            return i
    # forgot: return -1
OUTPUT — mypy
$ mypy demo.py demo.py:1: error: Incompatible default for parameter "name" (default has type "None", parameter has type "str") [assignment] demo.py:1: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True demo.py:1: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase demo.py:4: error: Missing return statement [return] Found 2 errors in 1 file (checked 1 source file)
💡
Implicit Optional Is GoneImplicit Optional अब नहीं隐式 Optional 已被取消

Old mypy versions quietly treated name: str = None as str | None. Modern mypy reports it, as you see. Write the | None yourself.

पुराने mypy versions name: str = None को चुपचाप str | None मान लेते थे। आधुनिक mypy इसे report करता है, जैसा आप देख रहे हैं। | None ख़ुद लिखिए।

旧版 mypy 会悄悄把 name: str = None 当作 str | None。如你所见,现代 mypy 会报告它。请自己写上 | None。


SectionSection第 13 节

Real-World Example — Typed Weather API Client 🌦️असली उदाहरण — Typed Weather API Client 🌦️实战示例 — 带类型的天气 API 客户端 🌦️

A small but production-shaped program: fetch weather from an API, turn it into a typed model, and print a report. Every step is annotated. (It talks to a local copy of the Open-Meteo API so the output is repeatable.)

एक छोटा पर production जैसा program: API से मौसम लाइए, उसे typed model में बदलिए, और report print कीजिए। हर step annotated है। (यह Open-Meteo API की local copy से बात करता है ताकि output दोहराया जा सके।)

一个小巧但接近生产环境的程序:从 API 获取天气,转换成带类型的模型,再打印报告。每一步都有注解。(它连接的是 Open-Meteo API 的本地副本,所以输出可复现。)

from __future__ import annotations
from dataclasses import dataclass
from collections.abc import Iterable
from typing import TypedDict
import requests

API = "http://127.0.0.1:8765/v1/forecast"    # local stand-in for api.open-meteo.com

# 1) Raw shape of the API response
class CurrentWeather(TypedDict):
    temperature: float
    windspeed: float
    weathercode: int

class WeatherResponse(TypedDict):
    current_weather: CurrentWeather
    timezone: str

# 2) Domain model - clean, immutable, typed
@dataclass(frozen=True)
class CityWeather:
    city: str
    temp_c: float
    wind_kmh: float
    timezone: str

# 3) Fetch -> parse -> return a typed object
def fetch_weather(city: str, lat: float, lon: float) -> CityWeather | None:
    params = {"latitude": lat, "longitude": lon, "current_weather": "true"}
    r = requests.get(API, params=params, timeout=5)
    if r.status_code != 200:
        return None
    data: WeatherResponse = r.json()
    cw = data["current_weather"]
    return CityWeather(
        city=city,
        temp_c=cw["temperature"],
        wind_kmh=cw["windspeed"],
        timezone=data["timezone"],
    )

# 4) Consume - mypy checks every step
def format_report(entries: Iterable[CityWeather]) -> str:
    lines: list[str] = ["City         Temp     Wind"]
    for e in entries:
        lines.append(f"{e.city:12s} {e.temp_c:5.1f}°C  {e.wind_kmh:4.1f} km/h")
    return "\n".join(lines)

CITIES: list[tuple[str, float, float]] = [
    ("Mumbai",   19.0760,  72.8777),
    ("New York", 40.7128, -74.0060),
    ("London",   51.5074,  -0.1278),
]

results: list[CityWeather] = [
    w for c, lat, lon in CITIES
    if (w := fetch_weather(c, lat, lon)) is not None
]
print(format_report(results))
OUTPUT
$ python demo.py City Temp Wind Mumbai 28.4°C 12.3 km/h New York 15.1°C 8.7 km/h London 11.6°C 6.2 km/h $ mypy --strict demo.py demo.py:30: error: Argument "params" to "get" has incompatible type "dict[str, object]"; expected "SupportsItems[...] | tuple[...] | Iterable[...] | str | bytes | None" (long type shortened here) [arg-type] Found 1 error in 1 file (checked 1 source file)
🔧
Fixed: The Original Client Fails mypy --strictसुधार: मूल Client mypy --strict में Fail होता है已修正:原客户端通不过 mypy --strict

The original said mypy "would catch every common mistake" — but the code itself does not pass mypy --strict, as the run above shows. mypy infers params as dict[str, object] (a mix of floats and a bool), which requests.get does not accept. Annotating it fixes this: params: dict[str, float | str] = …. We also send "true" as text: requests would turn Python's True into the text True anyway.

मूल में कहा गया कि mypy "हर आम ग़लती पकड़ लेगा" — पर जैसा ऊपर का run दिखाता है, code ख़ुद mypy --strict पास नहीं करता। mypy params को dict[str, object] मानता है (floats और bool का मिश्रण), जिसे requests.get स्वीकार नहीं करता। Annotation इसे ठीक करता है: params: dict[str, float | str] = …। हम "true" भी text के रूप में भेजते हैं: requests वैसे भी Python के True को text True बना देता।

原文说 mypy"能抓住每一个常见错误"— 但如上面的运行所示,这段代码本身就通不过 mypy --strict。mypy 把 params 推断为 dict[str, object](浮点数和布尔值混在一起),而 requests.get 不接受这种类型。加上注解就能解决:params: dict[str, float | str] = …。我们还把 "true" 以文本形式发送:反正 requests 也会把 Python 的 True 变成文本 True。

from __future__ import annotations
from dataclasses import dataclass
from collections.abc import Iterable
from typing import TypedDict
import requests

API = "http://127.0.0.1:8765/v1/forecast"    # local stand-in for api.open-meteo.com

# 1) Raw shape of the API response
class CurrentWeather(TypedDict):
    temperature: float
    windspeed: float
    weathercode: int

class WeatherResponse(TypedDict):
    current_weather: CurrentWeather
    timezone: str

# 2) Domain model - clean, immutable, typed
@dataclass(frozen=True)
class CityWeather:
    city: str
    temp_c: float
    wind_kmh: float
    timezone: str

# 3) Fetch -> parse -> return a typed object
def fetch_weather(city: str, lat: float, lon: float) -> CityWeather | None:
    params: dict[str, float | str] = {"latitude": lat, "longitude": lon, "current_weather": "true"}
    r = requests.get(API, params=params, timeout=5)
    if r.status_code != 200:
        return None
    data: WeatherResponse = r.json()
    cw = data["current_weather"]
    return CityWeather(
        city=city,
        temp_c=cw["temperature"],
        wind_kmh=cw["windspeed"],
        timezone=data["timezone"],
    )

# 4) Consume - mypy checks every step
def format_report(entries: Iterable[CityWeather]) -> str:
    lines: list[str] = ["City         Temp     Wind"]
    for e in entries:
        lines.append(f"{e.city:12s} {e.temp_c:5.1f}°C  {e.wind_kmh:4.1f} km/h")
    return "\n".join(lines)

CITIES: list[tuple[str, float, float]] = [
    ("Mumbai",   19.0760,  72.8777),
    ("New York", 40.7128, -74.0060),
    ("London",   51.5074,  -0.1278),
]

results: list[CityWeather] = [
    w for c, lat, lon in CITIES
    if (w := fetch_weather(c, lat, lon)) is not None
]
print(format_report(results))
OUTPUT — mypy (fixed version)
$ mypy --strict demo.py Success: no issues found in 1 source file
⚠️
r.json() Is Not Checkedr.json() की जाँच नहीं होतीr.json() 不会被检查

r.json() returns Any, so data: WeatherResponse = r.json() is a promise, not a check. If the API changes a key, mypy cannot know — you get a KeyError at runtime. For untrusted data, validate it at the boundary (by hand, or with a validation library) before you trust the type.

r.json() Any लौटाता है, इसलिए data: WeatherResponse = r.json() एक वादा है, जाँच नहीं। अगर API कोई key बदल दे, mypy नहीं जान सकता — आपको runtime पर KeyError मिलेगा। अविश्वसनीय data को type पर भरोसा करने से पहले सीमा पर validate कीजिए (हाथ से, या किसी validation library से)।

r.json() 返回 Any,所以 data: WeatherResponse = r.json() 只是一个 承诺,而不是检查。如果 API 改了某个键,mypy 无从得知 — 你会在运行时遇到 KeyError。对于不可信的数据,在信任其类型之前,先在边界处校验(手写校验或使用校验库)。

🏆
What Type Hints Bought Us Hereयहाँ Type Hints से क्या मिला类型提示在这里带来了什么

Passing a str where a float is expected, forgetting to check the None return, a typo like "tempature" in a TypedDict key, returning the raw dict instead of CityWeather — mypy catches all of these while you write the code. (Try the typo in the Bug Doctor below.)

float की जगह str देना, None return जाँचना भूलना, TypedDict key में "tempature" जैसी typo, CityWeather की जगह raw dict लौटाना — mypy code लिखते समय ही ये सब पकड़ लेता है। (Typo वाला bug नीचे Bug Doctor में आज़माइए।)

在需要 float 的地方传了 str、忘了检查返回的 None、TypedDict 键写成 "tempature" 这样的拼写错误、返回原始字典而不是 CityWeather — 这些 mypy 都能在你写代码时就抓住。(可以在下面的 Bug 医生里试试拼写错误的例子。)


SectionSection第 14 节

When to Use Type Hints — and When to Skip 🧭Type Hints कब लगाएँ — और कब छोड़ें 🧭何时使用类型提示 — 何时可以不用 🧭

✅
Library / API boundariesLibrary / API की सीमाएँ库 / API 边界
public functions, class methodspublic functions, class methods公共函数、类方法
Any function called from outside its module should be fully typed. Callers read the annotations instead of the code.
अपने module के बाहर से call होने वाले हर function को पूरा typed होना चाहिए। Callers code की जगह annotations पढ़ते हैं।
任何会被模块外部调用的函数都应完整标注类型。调用者看注解就够了,不必读代码。
✅
Team codebasesटीम के codebases团队代码库
web apps, backend services, SDKsweb apps, backend services, SDKsWeb 应用、后端服务、SDK
Hints are documentation that mypy checks in CI, so it cannot quietly go out of date. Very useful when several people touch the code.
Hints ऐसा दस्तावेज़ हैं जिसे mypy CI में जाँचता है, इसलिए वह चुपचाप पुराना नहीं हो सकता। जब कई लोग code छूते हैं तब बहुत उपयोगी।
类型提示是 mypy 在 CI 中检查的文档,所以不会悄悄过时。多人协作时非常有用。
✅
Long-lived projectsलंबे चलने वाले projects长期维护的项目
production, mature codebasesproduction, mature codebases生产环境、成熟代码库
Anything that will be refactored later. mypy points at every affected call site at once.
जो कुछ भी बाद में refactor होगा। mypy हर प्रभावित call site एक साथ दिखा देता है।
任何以后要重构的代码。mypy 会一次性指出所有受影响的调用点。
❌
Throwaway scriptsएक बार के scripts一次性脚本
one-off automation, quick REPLएक बार का automation, quick REPL一次性自动化、REPL 试验
A 20-line script you will never run again does not need annotations. Save your energy for code that will last.
20 lines का script जिसे आप दोबारा नहीं चलाएँगे, उसे annotations नहीं चाहिए। अपनी ऊर्जा टिकने वाले code के लिए बचाइए।
一个再也不会运行的 20 行脚本不需要注解。把精力留给会长期存在的代码。
❌
Jupyter explorationJupyter में खोजबीनJupyter 探索
notebooks, prototypingnotebooks, prototypingNotebook、原型
Fluid data exploration is faster untyped. Add types later, when the code moves into a module.
बहती data खोजबीन बिना types के तेज़ होती है। Types बाद में जोड़िए, जब code module में जाए।
灵活的数据探索不加类型会更快。等代码挪进模块时再加类型。
❌
Highly dynamic metaprogrammingबहुत dynamic metaprogramming高度动态的元编程
plugin systems, ORMsplugin systems, ORMs插件系统、ORM
Code that returns different types based on runtime settings is hard to type. Sometimes Any is the honest answer — keep it small and local.
Runtime settings के आधार पर अलग types लौटाने वाला code type करना कठिन है। कभी-कभी Any ही ईमानदार जवाब है — उसे छोटा और सीमित रखिए।
根据运行时配置返回不同类型的代码很难标注。有时 Any 才是诚实的答案 — 但要让它范围小、局部化。

SectionSection第 15 节

Bug Doctor, Golden Rules and Quiz 🏆Bug Doctor, सुनहरे नियम और Quiz 🏆Bug 医生、黄金法则与小测验 🏆

🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生

Six real typing mistakes. Buggy code on the left, fixed code on the right, changed lines marked. Each side shows what python printed and what mypy reported.

छह असली typing ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर तरफ़ दिखता है कि python ने क्या print किया और mypy ने क्या बताया।

六个真实的类型错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每一边都显示 python 打印了什么、mypy 报告了什么。

Bug DoctorBug DoctorBug 医生6 bugs · real python + mypy6 bugs · असली python + mypy6 个 bug · 真实 python + mypy
❌ BuggyBuggy有 bug
✅ Fixedठीक किया已修正
🔧 Type Hints — Rules to RememberType Hints — याद रखने वाले नियम类型提示 — 必须记住的规则
1
Type every public function: all arguments and the return value. If you annotate the arguments but not the return, mypy treats the result as Any and callers lose the checks. A function with no annotations at all is not checked by default.
हर public function को type कीजिए: सारे arguments और return value। Arguments annotate करें पर return नहीं, तो mypy result को Any मानता है और callers की जाँच खो जाती है। बिना किसी annotation वाला function default रूप से जाँचा ही नहीं जाता।
给每个公共函数加类型:所有参数和返回值。如果只注解参数、不注解返回值,mypy 会把结果当作 Any,调用方就失去了检查。完全没有注解的函数默认不会被检查。
2
Use modern syntax: list[int], dict[str, float], str | None. Use List/Dict only for Python 3.8 or older, and Optional[X] only if you must run on Python 3.9 or older without from __future__ import annotations.
आधुनिक syntax लीजिए: list[int], dict[str, float], str | None। List/Dict सिर्फ़ Python 3.8 या पुराने के लिए, और Optional[X] तभी जब आपको from __future__ import annotations के बिना Python 3.9 या पुराने पर चलाना हो।
使用现代语法:list[int]、dict[str, float]、str | None。只有 Python 3.8 或更早版本才用 List/Dict;只有必须在 Python 3.9 或更早版本上运行、且不使用 from __future__ import annotations 时才用 Optional[X]。
3
Prefer Iterable[X] or Sequence[X] for inputs; return concrete types like list[X]. Accept widely, promise precisely.
Inputs के लिए Iterable[X] या Sequence[X] लीजिए; list[X] जैसे ठोस types लौटाइए। चौड़ा स्वीकारिए, सटीक वादा कीजिए।
输入优先用 Iterable[X] 或 Sequence[X];返回 list[X] 这样的具体类型。接收要宽,承诺要准。
4
Avoid Any unless you truly cannot describe the type — it turns checking off. When you must accept anything, use object, which forces you to narrow before use.
जब तक type सच में बताया न जा सके, Any से बचिए — यह जाँच बंद कर देता है। जब कुछ भी स्वीकारना हो, object लीजिए, जो इस्तेमाल से पहले narrowing करवाता है।
除非真的无法描述类型,否则避免使用 Any — 它会关闭检查。必须接受任何值时,用 object,它会强制你在使用前先收窄类型。
5
Run mypy in CI (with --strict for new code) so a type error fails the build. Types you do not check are types you do not have.
CI में mypy चलाइए (नए code के लिए --strict के साथ) ताकि type error पर build fail हो। जो types जाँचे नहीं जाते, वे असल में होते ही नहीं।
在 CI 中运行 mypy(新代码加上 --strict),让类型错误导致构建失败。不检查的类型,等于没有类型。
6
Use dataclass for domain objects, TypedDict for API-shaped dicts, NamedTuple for tiny immutable records, and Protocol for "anything with this method".
Domain objects के लिए dataclass, API जैसे dicts के लिए TypedDict, छोटे न बदलने वाले records के लिए NamedTuple, और "इस method वाली कोई भी चीज़" के लिए Protocol।
领域对象用 dataclass,API 形态的字典用 TypedDict,小型不可变记录用 NamedTuple,"任何拥有某个方法的东西"用 Protocol。
7
Never use mutable defaults — def f(x: list[int] = []) is a bug even when typed, and mypy will not warn. Use None and create the list inside.
Mutable defaults कभी मत लीजिए — def f(x: list[int] = []) typed होने पर भी bug है, और mypy चेतावनी नहीं देगा। None लीजिए और list अंदर बनाइए।
永远不要用可变默认值 — def f(x: list[int] = []) 即使有类型也是 bug,而且 mypy 不会警告。用 None,在函数内部创建列表。
8
If an import is needed only for type hints and causes a circular import, put it under if TYPE_CHECKING:. On Python 3.13 and older, also add from __future__ import annotations (or quote the name); Python 3.14 evaluates annotations lazily, so this is no longer needed there.
अगर कोई import सिर्फ़ type hints के लिए चाहिए और circular import बनाता है, उसे if TYPE_CHECKING: के नीचे रखिए। Python 3.13 और पुराने पर from __future__ import annotations भी जोड़िए (या नाम को quotes में लिखिए); Python 3.14 annotations को देर से evaluate करता है, इसलिए वहाँ इसकी ज़रूरत नहीं।
如果某个导入只为类型提示而存在、又会引起循环导入,就把它放进 if TYPE_CHECKING:。在 Python 3.13 及更早版本中,还要加上 from __future__ import annotations(或给名字加引号);Python 3.14 会延迟求值注解,因此不再需要。
🔧
Fixed: Rules 1, 2 and 8 Correctedसुधार: नियम 1, 2 और 8 सुधारे गए已修正:规则 1、2 和 8 已修正

Rule 1: a missing return annotation gives Any only when the function has other annotations; a fully unannotated function is skipped by default (Section 02). Rule 2: str | None needs Python 3.10+ at runtime, not 3.9. Rule 8: since Python 3.14 (PEP 649/749) annotations are evaluated lazily, so forward references work without the __future__ import; TYPE_CHECKING is still the tool for import cycles.

नियम 1: return annotation न होने पर Any तभी मिलता है जब function में दूसरे annotations हों; पूरी तरह बिना annotation वाला function default रूप से छोड़ दिया जाता है (Section 02)। नियम 2: str | None को runtime पर Python 3.10+ चाहिए, 3.9 नहीं। नियम 8: Python 3.14 (PEP 649/749) से annotations देर से evaluate होते हैं, इसलिए forward references __future__ import के बिना चलते हैं; import cycles के लिए TYPE_CHECKING अब भी सही औज़ार है।

规则 1:只有当函数还有其他注解时,缺少返回值注解才会得到 Any;完全没有注解的函数默认会被跳过(第 02 节)。规则 2:str | None 在运行时需要 Python 3.10+,而不是 3.9。规则 8:从 Python 3.14(PEP 649/749)起,注解会延迟求值,因此前向引用不再需要 __future__ 导入;处理循环导入仍然要用 TYPE_CHECKING。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

Type hints are checked documentation. Python stores them but does not enforce them; mypy reads them before the code runs and catches wrong arguments, unchecked None, missing returns and key typos. It cannot catch bad values or mutable defaults, and it trusts Any — so keep tests, validate outside data, and run mypy in CI.

Type hints जाँचा हुआ दस्तावेज़ हैं। Python उन्हें रखता है पर लागू नहीं करता; mypy code चलने से पहले उन्हें पढ़कर ग़लत arguments, बिना जाँचा None, छूटे returns और key typos पकड़ता है। यह ग़लत values या mutable defaults नहीं पकड़ सकता, और Any पर भरोसा कर लेता है — इसलिए tests रखिए, बाहरी data validate कीजिए, और CI में mypy चलाइए।

类型提示是经过检查的文档。Python 保存但不强制执行它们;mypy 在代码运行前读取它们,抓住错误的参数、未检查的 None、缺失的 return 和键名拼写错误。它抓不到错误的值或可变默认值,并且会无条件信任 Any — 所以要保留测试、校验外部数据,并在 CI 中运行 mypy。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.