The Story That Explains Decorators 🎁Decorators समझाने वाली कहानी 🎁一个讲清装饰器的故事 🎁
That is exactly what a decorator does in Python. It takes a function (the "gift"), wraps extra behaviour around it (logging, timing, login checks, caching) and gives back the wrapped version — without changing the original function's code. One decorator can wrap dozens of different functions.
Python में decorator ठीक यही करता है। वह एक function (यानी "gift") लेता है, उसके चारों ओर अतिरिक्त काम लपेटता है (logging, timing, login check, caching) और लिपटा हुआ version लौटाता है — मूल function का code बदले बिना। एक ही decorator दर्जनों अलग functions को लपेट सकता है।
这正是 Python 中 装饰器 所做的事。它接收一个函数("礼物"),在外面包上额外的行为(日志、计时、登录检查、缓存),然后返回包装后的版本 — 完全不改动原函数的代码。同一个装饰器可以包装几十个不同的函数。
In Python, functions are first-class objects. You can pass them to other functions, return them from functions and store them in variables. A decorator is simply a function (or a class) that takes a function and returns a new one. The new one usually calls the original, with something extra added before, after or around it.
Python में functions first-class objects हैं। आप उन्हें दूसरे functions को दे सकते हैं, functions से लौटा सकते हैं और variables में रख सकते हैं। Decorator बस एक function (या class) है जो एक function लेता है और नया लौटाता है। नया वाला आमतौर पर मूल को ही बुलाता है, बस उसके पहले, बाद में या चारों ओर कुछ अतिरिक्त जोड़कर।
在 Python 中,函数是 一等对象(first-class objects)。你可以把它们传给别的函数、从函数中返回它们、把它们存进变量。装饰器 就是一个接收函数并返回新函数的函数(或类)。新函数通常会调用原函数,只是在它之前、之后或周围加了点额外的东西。
A decorator lets you add reusable behaviour — logging, timing, access control, caching, retries — to any function by writing @my_decorator on the line above it. One line. Zero changes inside the function. It keeps each concern in its own place (separation of concerns).
Decorator से आप किसी भी function में दोबारा काम आने वाला व्यवहार — logging, timing, access control, caching, retries — जोड़ सकते हैं, बस उसके ऊपर @my_decorator लिखकर। एक line। Function के अंदर शून्य बदलाव। हर काम अपनी जगह रहता है (separation of concerns)।
只需在函数上方写一行 @my_decorator,装饰器就能给 任何 函数加上可复用的行为 — 日志、计时、权限控制、缓存、重试。只要一行,函数内部零改动。每件事各归其位(关注点分离)。
Look for the interactive boxes: the @wraps Lab (Section 05), Decorator Step by Step (06), the Retry Lab and Cache Race (08), the Stack Lab (11), the Login Gate (12) and the Bug Doctor (14). Every result comes from real Python runs.
Interactive डिब्बे खोजें: @wraps Lab (Section 05), Decorator क़दम-दर-क़दम (06), Retry Lab और Cache Race (08), Stack Lab (11), Login Gate (12) और Bug Doctor (14)। हर नतीजा असली Python runs से है।
请留意这些互动框:@wraps 实验室(第 05 节)、装饰器逐步演示(06)、重试实验室 和 缓存赛跑(08)、堆叠实验室(11)、登录关卡(12)以及 Bug 医生(14)。每个结果都来自真实的 Python 运行。
The Foundation — Functions Are Objects 🧱नींव — Functions भी Objects हैं 🧱基础 — 函数也是对象 🧱
Before decorators make sense, accept one fact: in Python a function is just another object. You can give it a new name, put it in a list, pass it as an argument or return it from another function.
Decorators समझने से पहले एक बात मान लीजिए: Python में function भी बस एक object है। आप उसे नया नाम दे सकते हैं, list में रख सकते हैं, argument की तरह भेज सकते हैं या दूसरे function से लौटा सकते हैं।
在理解装饰器之前,先接受一个事实:在 Python 中,函数也只是一个对象。你可以给它起新名字、放进列表、作为参数传递,或者从另一个函数中返回它。
def greet(name):
return f"Hello, {name}!"
# 1. Assign the function to a new name (no parentheses!)
say_hi = greet
print(say_hi("Mohit")) # -> Hello, Mohit!
# 2. Pass a function as an argument
def shout(fn, name):
return fn(name).upper()
print(shout(greet, "Mohit")) # -> HELLO, MOHIT!
# 3. Return a function from another function
def make_multiplier(n):
def multiplier(x):
return x * n
return multiplier
double = make_multiplier(2)
print(double(7)) # -> 14
Notice how multiplier remembered n even after make_multiplier finished. This is a closure — an inner function that keeps the variables of the function around it. Every function-based decorator in this tutorial relies on it: the wrapper remembers fn. (Class-based decorators in Section 09 keep fn in self.fn instead.)
देखिए, make_multiplier ख़त्म होने के बाद भी multiplier को n याद रहा। यही closure है — अंदर का function जो बाहर वाले function के variables साथ रखता है। इस tutorial का हर function-based decorator इसी पर टिका है: wrapper को fn याद रहता है। (Section 09 के class-based decorators fn को self.fn में रखते हैं।)
注意,make_multiplier 执行完之后,multiplier 仍然记得 n。这就是 闭包(closure) — 一个保留外层函数变量的内部函数。本教程中每个 基于函数的 装饰器都依赖它:wrapper 记住了 fn。(第 09 节的基于类的装饰器则把 fn 存在 self.fn 里。)
Your First Decorator — Written by Hand ✍️आपका पहला Decorator — हाथ से लिखा ✍️你的第一个装饰器 — 手写版 ✍️
Let us build a decorator that prints a message before and after any function runs. First without the @ syntax, so you see what really happens.
चलिए एक decorator बनाते हैं जो किसी भी function के चलने से पहले और बाद में message print करे। पहले @ syntax के बिना, ताकि आप देखें असल में क्या होता है।
我们来写一个装饰器,在任何函数运行前后各打印一条消息。先不用 @ 语法,这样你能看清到底发生了什么。
def announce(fn): # takes a function
def wrapper(): # builds a new function
print("-- before --")
result = fn() # call the original
print("-- after --")
return result
return wrapper # returns the wrapper
def say_hello():
print("Hello!")
# Manually wrap it
say_hello = announce(say_hello)
say_hello()
Now the same thing with the @ syntax. It is syntactic sugar for say_hello = announce(say_hello). The last line shows that the name say_hello now points to wrapper:
अब वही काम @ syntax से। यह say_hello = announce(say_hello) का syntactic sugar है। आख़िरी line दिखाती है कि नाम say_hello अब wrapper की ओर इशारा करता है:
现在用 @ 语法做同样的事。它是 say_hello = announce(say_hello) 的 语法糖。最后一行显示,名字 say_hello 现在指向的是 wrapper:
def announce(fn):
def wrapper():
print("-- before --")
result = fn()
print("-- after --")
return result
return wrapper
@announce # the magic line
def say_hello():
print("Hello!")
say_hello()
print(say_hello.__name__) # the name now points to wrapper
When Python sees @announce above a function, it builds the function and then runs say_hello = announce(say_hello) for you. That is the entire trick. Decorators are not a new kind of object — they are a short way to write this re-assignment.
जब Python किसी function के ऊपर @announce देखता है, तो वह function बनाता है और फिर आपके लिए say_hello = announce(say_hello) चलाता है। बस इतनी-सी बात है। Decorators कोई नई चीज़ नहीं — यह re-assignment लिखने का छोटा तरीक़ा है।
当 Python 看到函数上方的 @announce 时,它会先创建函数,然后替你执行 say_hello = announce(say_hello)。整个诀窍就是这样。装饰器不是新的对象类型 — 它只是这种重新赋值的简写。
The original said Python runs say_hello = announce(say_hello) "immediately after the definition". That is the right picture, with two small differences. (1) The decorator line itself is evaluated first, before the function is built — this matters for @repeat(3) in Section 07. (2) With @, the name say_hello never points to the undecorated function, not even for a moment. Real run:
मूल में कहा गया था कि Python "definition के तुरंत बाद" say_hello = announce(say_hello) चलाता है। तस्वीर सही है, बस दो छोटे फ़र्क़ हैं। (1) Decorator वाली line पहले evaluate होती है, function बनने से पहले — यह Section 07 के @repeat(3) में मायने रखता है। (2) @ के साथ नाम say_hello एक पल के लिए भी बिना-decorate वाले function की ओर कभी इशारा नहीं करता। असली run:
原文说 Python 在"定义之后紧接着"执行 say_hello = announce(say_hello)。这个图景是对的,只有两点小差别。(1) 装饰器那一行本身会 先被求值,早于函数的创建 — 这对第 07 节的 @repeat(3) 很重要。(2) 使用 @ 时,名字 say_hello 从不 指向未装饰的函数,哪怕一瞬间也没有。真实运行:
def deco(fn):
print("2. deco() runs, it receives", fn.__name__)
return fn
def pick():
print("1. the decorator expression is evaluated first")
return deco
@pick()
def task():
pass
print("3. task is ready")
Handling Arguments — *args and **kwargs 📦Arguments सँभालना — *args और **kwargs 📦处理参数 — *args 与 **kwargs 📦
Real functions take arguments. To make your decorator work with any function, whatever its parameters, use *args, **kwargs in the wrapper. It collects every positional and keyword argument and passes them on unchanged.
असली functions arguments लेते हैं। अपने decorator को किसी भी function के साथ चलाने के लिए, चाहे उसके parameters कुछ भी हों, wrapper में *args, **kwargs लिखिए। यह हर positional और keyword argument इकट्ठा करके बिना बदले आगे भेज देता है।
真实的函数都有参数。要让装饰器适用于 任何 函数(不管参数是什么),就在 wrapper 中使用 *args, **kwargs。它会收集所有位置参数和关键字参数,并原样转发。
def announce(fn):
def wrapper(*args, **kwargs):
print(f"Calling {fn.__name__} with {args} {kwargs}")
result = fn(*args, **kwargs)
print(f"{fn.__name__} returned {result!r}")
return result
return wrapper
@announce
def add(a, b):
return a + b
@announce
def greet(name, greeting="Hi"):
return f"{greeting}, {name}!"
add(3, 4)
greet("Mohit", greeting="Namaste")
A common bug: forgetting return result inside the wrapper. If the original function returns something, the wrapper must return it too — otherwise the decorated function quietly returns None. There is no error message, so it is hard to find. (See it live in the Bug Doctor, Section 14.)
आम bug: wrapper में return result भूल जाना। अगर मूल function कुछ लौटाता है, तो wrapper को भी लौटाना होगा — वरना decorated function चुपचाप None लौटाता है। कोई error message नहीं आता, इसलिए इसे ढूँढना मुश्किल है। (Section 14 के Bug Doctor में इसे चलता देखिए।)
常见 bug:忘了在 wrapper 里 return result。如果原函数有返回值,wrapper 也必须把它返回 — 否则被装饰的函数会悄悄返回 None。不会有任何报错,所以很难发现。(可在第 14 节的 Bug 医生中实际看到。)
Keeping the Name and Docstring — functools.wraps 🏷️नाम और Docstring बचाना — functools.wraps 🏷️保留名字和文档 — functools.wraps 🏷️
When you decorate a function, the name now points to the wrapper. So __name__, __doc__ and the signature you see are the wrapper's, not the original's. This confuses debuggers, IDEs, help() and documentation tools. The fix is one line: @functools.wraps(fn).
Function को decorate करने पर नाम अब wrapper की ओर इशारा करता है। इसलिए जो __name__, __doc__ और signature दिखते हैं, वे wrapper के हैं, मूल के नहीं। इससे debuggers, IDEs, help() और documentation tools भ्रमित होते हैं। इलाज एक line है: @functools.wraps(fn)।
装饰一个函数后,这个名字指向的是 wrapper。因此你看到的 __name__、__doc__ 和函数签名都是 wrapper 的,而不是原函数的。这会让调试器、IDE、help() 和文档工具产生混乱。解决办法只要一行:@functools.wraps(fn)。
| AttributeAttribute属性 | ValueValue值 |
|---|---|
add.__name__ | 'wrapper' |
add.__doc__ | None |
add.__wrapped__ | AttributeError |
help(add) | Shows wrapper(*args, **kwargs)wrapper(*args, **kwargs) दिखाता है显示 wrapper(*args, **kwargs) |
| AttributeAttribute属性 | ValueValue值 |
|---|---|
add.__name__ | 'add' |
add.__doc__ | 'Add two numbers.' |
add.__wrapped__ | the original addमूल add原来的 add |
help(add) | Shows add(a, b)add(a, b) दिखाता है显示 add(a, b) |
from functools import wraps
def announce(fn):
@wraps(fn) # preserves metadata
def wrapper(*args, **kwargs):
print(f"Calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapper
@announce
def add(a, b):
"""Add two numbers."""
return a + b
print(add.__name__) # -> add (not 'wrapper')
print(add.__doc__) # -> Add two numbers.
print(add.__wrapped__) # the original, undecorated add
Every decorator you write for real code should use @functools.wraps(fn) on its wrapper. It costs nothing and prevents confusing tracebacks, misleading logs and broken documentation.
असली code के लिए लिखे हर decorator के wrapper पर @functools.wraps(fn) लगाइए। इसकी कोई क़ीमत नहीं और यह उलझे tracebacks, भ्रामक logs और टूटे documentation से बचाता है।
你为实际项目写的每个装饰器,都应该在 wrapper 上加 @functools.wraps(fn)。它没有任何成本,却能避免令人困惑的 traceback、误导性的日志和损坏的文档。
The original said wraps "preserves" __wrapped__. More exactly: it copies __name__, __qualname__, __doc__, __module__, __annotations__ and __dict__ from fn to the wrapper, and it adds a new attribute __wrapped__ that points to the original. Without wraps, __wrapped__ does not exist at all. Because of __wrapped__, inspect.signature() and help() show the real parameters.
मूल में कहा गया कि wraps __wrapped__ को "बचाता" है। सही बात: यह fn से wrapper में __name__, __qualname__, __doc__, __module__, __annotations__ और __dict__ copy करता है, और एक नया attribute __wrapped__ जोड़ता है जो मूल की ओर इशारा करता है। wraps के बिना __wrapped__ होता ही नहीं। __wrapped__ की वजह से inspect.signature() और help() असली parameters दिखाते हैं।
原文说 wraps 会"保留" __wrapped__。更准确地说:它把 __name__、__qualname__、__doc__、__module__、__annotations__ 和 __dict__ 从 fn 复制 到 wrapper 上,并 新增 一个指向原函数的属性 __wrapped__。不用 wraps 时,__wrapped__ 根本不存在。正因为有 __wrapped__,inspect.signature() 和 help() 才能显示真实的参数。
🏷️ Simulator: @wraps Lab🏷️ Simulator: @wraps Lab🏷️ 模拟器:@wraps 实验室
Switch @wraps off and on. Every value below was printed by real Python for the same add function.
@wraps को बंद और चालू कीजिए। नीचे की हर value उसी add function के लिए असली Python ने print की है।
把 @wraps 关掉再打开。下面每个值都是真实 Python 针对同一个 add 函数打印出来的。
Picture It — How a Decorator Wraps a Function 🖼️तस्वीर में — Decorator Function को कैसे लपेटता है 🖼️图解 — 装饰器如何包裹函数 🖼️
Here is the mental model. The original function is the gift; the decorator is the wrapping paper. Once wrapped, every call goes through the wrapper: it does its "before" work, calls the inner function, does its "after" work and returns the result.
यह रहा मन का चित्र। मूल function gift है; decorator wrapping paper है। लपेटने के बाद हर call wrapper से होकर जाती है: वह अपना "पहले" वाला काम करता है, अंदर का function बुलाता है, "बाद" वाला काम करता है और नतीजा लौटाता है।
这是心智模型:原函数是礼物,装饰器是包装纸。包好以后,每次调用都经过 wrapper:它先做"之前"的工作,再调用里面的函数,然后做"之后"的工作,最后返回结果。
The dashed amber box is the wrapper. It surrounds the original function (green) without editing it — the decorator only changes what the name points to.
Dashed नारंगी box wrapper है। यह मूल function (हरा) को बिना बदले घेरता है — decorator सिर्फ़ यह बदलता है कि नाम किसकी ओर इशारा करे।
琥珀色虚线框就是 wrapper。它包住原函数(绿色)而不修改它 — 装饰器只改变名字所指向的对象。
Definition Time vs Call TimeDefinition Time बनाम Call Time定义时 vs 调用时
Phase 1 changes what the name points to, once. Phase 2 is what users see — every call flows through the wrapper.
Phase 1 एक बार बदलता है कि नाम किसकी ओर इशारा करे। Phase 2 वह है जो users देखते हैं — हर call wrapper से होकर जाती है।
阶段 1 只做一次:改变名字指向的对象。阶段 2 是使用者看到的部分 — 每次调用都流经 wrapper。
The original figure said Phase 1 "runs ONCE when the module loads". More exactly, it runs when the def statement runs. For a function at the top of a module, that is when the module is imported. But a def inside another function runs — and is decorated again — every time that outer function is called.
मूल चित्र में लिखा था कि Phase 1 "module load होने पर एक बार" चलता है। सही बात: यह तब चलता है जब def statement चलता है। Module के top-level function के लिए यह module import होने का समय है। पर किसी दूसरे function के अंदर का def हर बार चलता है — और फिर से decorate होता है — जब भी बाहर वाला function बुलाया जाए।
原图说阶段 1 "在模块加载时运行一次"。更准确地说,它在 def 语句执行时运行。对于模块顶层的函数,这就是模块被导入的时候。但写在另一个函数内部的 def,每次外层函数被调用时都会执行一次 — 也会被重新装饰一次。
👣 Simulator: Decorator Step by Step👣 Simulator: Decorator क़दम-दर-क़दम👣 模拟器:装饰器逐步演示
Walk through a real run, one step at a time. Watch the global name change from ƒ say_hello to ƒ wrapper during definition time, and watch fn inside the wrapper still point to the original. Every step was recorded from Python with sys.settrace. (These examples skip @wraps so the names stay easy to tell apart.)
एक असली run को क़दम-दर-क़दम देखिए। Definition time में global नाम को ƒ say_hello से ƒ wrapper बनते देखिए, और देखिए कि wrapper के अंदर fn अब भी मूल की ओर इशारा करता है। हर क़दम Python से sys.settrace द्वारा रिकॉर्ड किया गया। (इन examples में @wraps नहीं है ताकि नाम आसानी से पहचाने जा सकें।)
一步一步地看一次真实运行。观察在定义时全局名字如何从 ƒ say_hello 变成 ƒ wrapper,并观察 wrapper 里的 fn 仍然指向原函数。每一步都是用 sys.settrace 从 Python 中记录的。(这些例子没用 @wraps,方便区分名字。)
Decorators That Take Arguments 🎚️Arguments लेने वाले Decorators 🎚️带参数的装饰器 🎚️
What if you want @repeat(3) to run a function three times, or @retry(times=5) to try again on failure? Now the decorator itself needs arguments. That needs three levels of nested functions:
अगर आप चाहें कि @repeat(3) किसी function को तीन बार चलाए, या @retry(times=5) fail होने पर दोबारा कोशिश करे? अब decorator को ख़ुद arguments चाहिए। इसके लिए nested functions के तीन स्तर चाहिए:
如果你想让 @repeat(3) 把函数运行三次,或者让 @retry(times=5) 在失败时重试呢?这时装饰器本身需要参数。这就需要 三层 嵌套函数:
times=3).times=3)।times=3)。fn.fn लेता है।fn。wrapper receives the call arguments *args, **kwargs and does the work.wrapper call के arguments *args, **kwargs लेता है और काम करता है।wrapper 接收 调用参数 *args, **kwargs 并执行工作。@repeat(3) above def greet means greet = repeat(3)(greet). Each layer keeps its own arguments through a closure.
def greet के ऊपर @repeat(3) का मतलब है greet = repeat(3)(greet)। हर परत closure से अपने arguments याद रखती है।
def greet 上方的 @repeat(3) 就是 greet = repeat(3)(greet)。每一层都通过闭包保存自己的参数。
Steps 1–2 run once. Steps 3–4 run on every call. That is why times=3 must live in a closure — it has to survive across all later calls.
Steps 1–2 एक बार चलते हैं। Steps 3–4 हर call पर। इसीलिए times=3 को closure में रहना पड़ता है — उसे बाद की सारी calls तक बचे रहना है।
第 1–2 步只运行一次,第 3–4 步每次调用都运行。所以 times=3 必须存放在闭包里 — 它要在之后所有调用中一直存在。
from functools import wraps
def repeat(times): # Layer 1 - takes decorator args
def decorator(fn): # Layer 2 - real decorator
@wraps(fn)
def wrapper(*args, **kwargs): # Layer 3 - runtime
result = None
for _ in range(times):
result = fn(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3) # note: @repeat(3), not @repeat
def greet(name):
print(f"Hi, {name}!")
greet("Mohit")
@repeat(3) is really repeat(3)(greet). First repeat(3) is called and returns the actual decorator; then that decorator is applied to greet. Two calls → two extra levels of nesting. Every decorator that takes arguments follows this pattern. Try the @repeat(2) tab in the simulator in Section 06 to watch all three layers run.
@repeat(3) असल में repeat(3)(greet) है। पहले repeat(3) बुलाया जाता है और असली decorator लौटाता है; फिर वह decorator greet पर लगता है। दो calls → nesting के दो अतिरिक्त स्तर। Arguments लेने वाला हर decorator इसी pattern पर चलता है। तीनों परतों को चलते देखने के लिए Section 06 के simulator में @repeat(2) tab आज़माइए।
@repeat(3) 实际上就是 repeat(3)(greet)。先调用 repeat(3),它返回真正的装饰器;再把这个装饰器作用于 greet。两次调用 → 多两层嵌套。所有带参数的装饰器都遵循这个模式。可以在第 06 节模拟器里点 @repeat(2) 标签,看三层依次运行。
Practical Examples — @timer, @retry, @cache 🧰काम के Examples — @timer, @retry, @cache 🧰实用示例 — @timer、@retry、@cache 🧰
⏱️ @timer — measure how long a call takes⏱️ @timer — call में कितना समय लगा⏱️ @timer — 测量调用耗时
import time
from functools import wraps
def timer(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = fn(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{fn.__name__} took {elapsed*1000:.2f} ms")
return result
return wrapper
@timer
def slow_sum(n):
return sum(range(n))
slow_sum(1_000_000)
The original showed 18.42 ms; our real run printed the value above. Timing depends on your computer and on what else is running, so expect a different number each time.
मूल में 18.42 ms दिखाया गया था; हमारे असली run ने ऊपर वाली value print की। Timing आपके computer पर और उस समय चल रहे दूसरे कामों पर निर्भर है, इसलिए हर बार अलग number की उम्मीद रखें।
原文显示的是 18.42 ms;我们真实运行打印的是上面的值。耗时取决于你的电脑和同时运行的其他程序,所以每次的数字都会不同。
🔁 @retry — try again after an exception (takes arguments)🔁 @retry — exception के बाद दोबारा कोशिश (arguments लेता है)🔁 @retry — 出现异常后重试(带参数)
import time, random
from functools import wraps
def retry(times=3, delay=1.0):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(1, times + 1):
try:
return fn(*args, **kwargs)
except Exception as e:
last_error = e
print(f"[attempt {attempt}] failed: {e}")
time.sleep(delay)
raise last_error
return wrapper
return decorator
@retry(times=3, delay=0.5)
def flaky_api_call():
if random.random() < 0.7: # fails 70% of the time
raise ConnectionError("server busy")
return "OK"
random.seed(11) # fixed seed, so you see the same run as below
print(flaky_api_call())
The original code used random.random() with no seed but showed one fixed output, so your run would rarely match it. We added random.seed(11) so the output above is exactly what you get. Also know the odds: with a 70% failure rate and 3 attempts, all three fail 0.7 × 0.7 × 0.7 = 34.3% of the time — then the decorator re-raises the last ConnectionError. Try it in the lab below.
मूल code में बिना seed के random.random() था पर एक तय output दिखाया गया था, इसलिए आपका run शायद ही उससे मिलता। हमने random.seed(11) जोड़ा ताकि ऊपर वाला output ठीक वही हो जो आपको मिलेगा। संभावना भी जानिए: 70% failure और 3 attempts में तीनों के fail होने की संभावना 0.7 × 0.7 × 0.7 = 34.3% है — तब decorator आख़िरी ConnectionError फिर से raise करता है। नीचे के lab में आज़माइए।
原代码用了没有种子的 random.random(),却给出一个固定输出,所以你的运行结果很少能对上。我们加了 random.seed(11),这样上面的输出就和你运行得到的完全一样。还要知道概率:失败率 70%、尝试 3 次时,三次全失败的概率是 0.7 × 0.7 × 0.7 = 34.3% — 此时装饰器会重新抛出最后一个 ConnectionError。在下面的实验室里试试。
🗄️ @cache — remember expensive results🗄️ @cache — महँगे नतीजे याद रखें🗄️ @cache — 记住代价高的结果
from functools import wraps
def cache(fn):
stored = {}
@wraps(fn)
def wrapper(*args):
if args not in stored:
stored[args] = fn(*args)
return stored[args]
return wrapper
@cache
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(50)) # instant with the cache
The original comment said fib(50) without a cache "would hang". It does not hang — it just takes a very long time. Without a cache, fib(n) makes 2 × fib(n+1) − 1 calls, which is 40,730,022,147 calls for fib(50). We timed fib(30) and scaled it up:
मूल comment में कहा गया कि cache के बिना fib(50) "hang हो जाएगा"। यह hang नहीं होता — बस बहुत लंबा समय लेता है। Cache के बिना fib(n) 2 × fib(n+1) − 1 calls करता है, जो fib(50) के लिए 40,730,022,147 calls हैं। हमने fib(30) का समय नापा और उसे बढ़ाकर देखा:
原注释说不用缓存时 fib(50)"会卡死"。其实不会卡死 — 只是要很长时间。没有缓存时,fib(n) 会调用 2 × fib(n+1) − 1 次,对 fib(50) 来说就是 40,730,022,147 次调用。我们测量了 fib(30) 的耗时并按比例推算:
import time
def fib(n): # NO cache
if n < 2:
return n
return fib(n-1) + fib(n-2)
start = time.perf_counter()
fib(30)
sec = time.perf_counter() - start
print(f"fib(30) without cache: {sec:.2f} s")
# fib(n) makes 2*fib(n+1) - 1 calls when there is no cache
calls = lambda n: 2 * ((((1 + 5**0.5) / 2)**(n + 1)) / 5**0.5) - 1
est = sec * calls(50) / calls(30) / 60
print(f"fib(50) would make {calls(50):,.0f} calls")
print(f"fib(50) without cache: about {est:.0f} minutes")
🏁 Simulator: Cache Race🏁 Simulator: Cache Race🏁 模拟器:缓存赛跑
Pick n and switch the cache on or off. For small n you see the whole call tree grow; green nodes were answered from the cache without running the body. The counts were checked against Python for n = 0 to 25.
n चुनिए और cache चालू या बंद कीजिए। छोटे n पर आप पूरा call tree बढ़ता देखेंगे; हरे nodes का जवाब cache से मिला, body चलाए बिना। गिनतियाँ n = 0 से 25 तक Python से जाँची गईं।
选择 n,打开或关闭缓存。n 较小时可以看到整棵调用树逐渐长出来;绿色节点直接从缓存拿到答案,没有运行函数体。n = 0 到 25 的计数都已与 Python 核对。
The @cache above is a teaching version. In real code use @functools.lru_cache(maxsize=128): it is written in C, safe to use from several threads, supports keyword arguments and can limit its size. Since Python 3.9 there is also @functools.cache, which is the same thing with no size limit.
ऊपर वाला @cache सिखाने वाला version है। असली code में @functools.lru_cache(maxsize=128) लें: यह C में लिखा है, कई threads से इस्तेमाल करना सुरक्षित है, keyword arguments चलाता है और अपना size सीमित कर सकता है। Python 3.9 से @functools.cache भी है, जो बिना size limit के यही काम करता है।
上面的 @cache 是教学版本。实际项目请用 @functools.lru_cache(maxsize=128):它用 C 实现,可以在多线程中安全使用,支持关键字参数,还能限制缓存大小。从 Python 3.9 起还有 @functools.cache,作用相同但没有大小限制。
Class-Based Decorators 🏛️Class-Based Decorators 🏛️基于类的装饰器 🏛️
A decorator does not have to be a function — anything callable works. A class becomes a decorator with __init__ (receives the function) and __call__ (runs when the decorated function is called).
Decorator का function होना ज़रूरी नहीं — कोई भी callable चलेगा। Class __init__ (function लेता है) और __call__ (decorated function बुलाने पर चलता है) से decorator बन जाती है।
装饰器不一定是函数 — 任何可调用对象都行。一个类只要有 __init__(接收函数)和 __call__(被装饰函数被调用时运行),就能当装饰器。
self.self पर attributes की तरह रखिए।self 的属性。*args, **kwargs, do your before/after work, call self.fn(...) and return the result.*args, **kwargs लीजिए, पहले/बाद का काम कीजिए, self.fn(...) बुलाइए और नतीजा लौटाइए।*args, **kwargs,做前后处理,调用 self.fn(...) 并返回结果。CountCalls — a class-based decorator with stateCountCalls — state वाला class-based decoratorCountCalls — 带状态的基于类的装饰器
from functools import wraps
class CountCalls:
def __init__(self, fn):
wraps(fn)(self) # preserve metadata
self.fn = fn
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.fn.__name__} call #{self.count}")
return self.fn(*args, **kwargs)
def reset(self):
self.count = 0
@CountCalls
def say_hi(name):
print(f"Hi, {name}!")
say_hi("Mohit")
say_hi("Aarav")
say_hi("Priya")
print(f"Total: {say_hi.count} calls") # -> 3
say_hi.reset()
print(f"After reset: {say_hi.count}") # -> 0
A class-based decorator that takes argumentsArguments लेने वाला class-based decorator带参数的基于类的装饰器
from functools import wraps
class Repeat:
def __init__(self, times): # Layer 1 - receives args
self.times = times
def __call__(self, fn): # Layer 2 - receives fn
@wraps(fn)
def wrapper(*args, **kwargs): # Layer 3 - runtime
result = None
for _ in range(self.times):
result = fn(*args, **kwargs)
return result
return wrapper
@Repeat(2)
def shout(msg):
print(msg.upper())
shout("hello")
In Repeat, __init__ is Layer 1 (gets 2), __call__ is Layer 2 (gets shout) and the inner wrapper is Layer 3 (gets "hello"). @Repeat(2) still means shout = Repeat(2)(shout).
Repeat में __init__ परत 1 है (2 लेता है), __call__ परत 2 है (shout लेता है) और अंदर का wrapper परत 3 है ("hello" लेता है)। @Repeat(2) का मतलब अब भी shout = Repeat(2)(shout) है।
在 Repeat 中,__init__ 是第 1 层(接收 2),__call__ 是第 2 层(接收 shout),内部的 wrapper 是第 3 层(接收 "hello")。@Repeat(2) 仍然等于 shout = Repeat(2)(shout)。
Function vs Class — When to Use Which ⚖️Function बनाम Class — कब कौन-सा ⚖️函数 vs 类 — 何时用哪个 ⚖️
| Aspectपहलू方面 | Function-BasedFunction-Based基于函数 | Class-BasedClass-Based基于类 |
|---|---|---|
| Code sizeCode का आकार代码量 | Light — a few nested defsहल्का — कुछ nested def轻量 — 几个嵌套的 def | Heavier — needs a class bodyभारी — class body चाहिए较重 — 需要类体 |
| Lasting stateलंबी state持久状态 | Possible with closures or nonlocalClosures या nonlocal से संभव可用闭包或 nonlocal 实现 | Natural — use self.attrस्वाभाविक — self.attr लें自然 — 用 self.attr |
| Extra methods (reset, stats)अतिरिक्त methods (reset, stats)额外方法(reset、stats) | Awkward — attach as attributesअसुविधाजनक — attributes की तरह जोड़ें别扭 — 以属性形式挂上去 | First-class — just add methodsआसान — बस methods जोड़ें一等支持 — 直接加方法 |
| Decorating methods in a classClass के methods decorate करना装饰类中的方法 | Works as is — self arrives in *argsजैसा है वैसा चलता है — self *args में आता है直接可用 — self 会进入 *args | Breaks without extra work — see Section 14अतिरिक्त काम के बिना टूटता है — Section 14 देखें不做额外处理就会出错 — 见第 14 节 |
| Easy for beginnersBeginners के लिए आसान对初学者友好 | Reads top to bottomऊपर से नीचे पढ़ा जाता है从上到下顺序阅读 | Needs OOP knowledgeOOP की समझ चाहिए需要了解面向对象 |
| Best fitसबसे सही最适合 | Small, stateless helpers (log, time)छोटे, बिना state वाले काम (log, time)小型无状态功能(日志、计时) | Stateful ones (counters, rate limiters, registries)State वाले काम (counters, rate limiters, registries)有状态的功能(计数器、限流器、注册表) |
Start with a function decorator. If you find yourself using nonlocal to change state, or attaching methods like .reset() or .stats() to the wrapper, that is your signal to switch to a class-based decorator.
शुरुआत function decorator से कीजिए। अगर आप state बदलने के लिए nonlocal लिखने लगें, या wrapper पर .reset() या .stats() जैसे methods जोड़ने लगें, तो यह class-based decorator पर जाने का संकेत है।
先从函数装饰器开始。如果你发现自己要用 nonlocal 修改状态,或者要往 wrapper 上挂 .reset()、.stats() 之类的方法,这就是改用基于类的装饰器的信号。
Stacking Several Decorators 🥞कई Decorators एक के ऊपर एक 🥞叠加多个装饰器 🥞
You can stack decorators. They are applied bottom-up: the one closest to the function wraps it first, then the next one wraps that, and so on. But when you call the function, the wrappers run top-down.
आप decorators एक के ऊपर एक लगा सकते हैं। वे नीचे से ऊपर लगते हैं: function के सबसे पास वाला उसे पहले लपेटता है, फिर अगला उसे लपेटता है, और आगे भी ऐसे ही। पर function बुलाने पर wrappers ऊपर से नीचे चलते हैं।
装饰器可以叠加。它们 从下往上应用:离函数最近的那个先包住它,接着下一个再包住这一层,依此类推。但调用函数时,各层 wrapper 从上往下执行。
def bold(fn):
def wrapper(*a, **k):
return f"<b>{fn(*a, **k)}</b>"
return wrapper
def italic(fn):
def wrapper(*a, **k):
return f"<i>{fn(*a, **k)}</i>"
return wrapper
@bold # applied SECOND - outermost
@italic # applied FIRST - innermost
def say(msg):
return msg
print(say("hello"))
# Equivalent to: say = bold(italic(say))
Left: how the decorators wrap the function (the one nearest the function goes first, at definition time). Right: how a call flows (the outermost wrapper starts first and finishes last).
बाईं ओर: decorators function को कैसे लपेटते हैं (function के सबसे पास वाला पहले, definition time पर)। दाईं ओर: call कैसे बहती है (सबसे बाहरी wrapper पहले शुरू होता है और आख़िर में ख़त्म)।
左边:装饰器如何包装函数(离函数最近的先应用,在定义时)。右边:一次调用如何流动(最外层 wrapper 最先开始、最后结束)。
The original figure traced say() returning "hi", but the code calls say("hello"). The figure above now follows the real code and its real output, <b><i>hello</i></b>.
मूल चित्र में say() को "hi" लौटाते दिखाया गया था, पर code say("hello") बुलाता है। ऊपर का चित्र अब असली code और उसके असली output <b><i>hello</i></b> के अनुसार है।
原图追踪的是 say() 返回 "hi",但代码调用的是 say("hello")。上面的图现在与真实代码及其真实输出 <b><i>hello</i></b> 一致。
🥞 Simulator: Stack Lab🥞 Simulator: Stack Lab🥞 模拟器:堆叠实验室
Build your own stack of up to three decorators and call say("hello"). Watch the wrapping order, then the call go down through the layers and the return values come back up. Every one of the 85 possible stacks was checked against Python.
तीन तक decorators का अपना stack बनाइए और say("hello") बुलाइए। लपेटने का क्रम देखिए, फिर call को परतों में नीचे जाते और return values को ऊपर लौटते देखिए। सभी 85 संभव stacks Python से जाँचे गए।
自己搭一个最多三层的装饰器栈,然后调用 say("hello")。先看包装顺序,再看调用如何一层层往下走、返回值如何一层层往上回。全部 85 种可能的组合都已与 Python 核对。
The four decorators used in the lab are as simple as bold and italic above:
Lab में इस्तेमाल हुए चारों decorators ऊपर वाले bold और italic जितने ही सरल हैं:
实验室里用到的四个装饰器和上面的 bold、italic 一样简单:
def bold(fn):
def wrapper(*a, **k):
r = fn(*a, **k)
return f"<b>{r}</b>"
return wrapper
def italic(fn):
def wrapper(*a, **k):
r = fn(*a, **k)
return f"<i>{r}</i>"
return wrapper
def upper(fn):
def wrapper(*a, **k):
r = fn(*a, **k)
return r.upper()
return wrapper
def exclaim(fn):
def wrapper(*a, **k):
r = fn(*a, **k)
return r + "!"
return wrapper
Reversing to @italic @bold gives <i><b>hello</b></i>. For security it matters even more: put the login check outside (on top of) the cache. If @cache is on top, it answers from its stored results before the login wrapper ever runs — so a logged-out user can get data that was cached for a logged-in user. See this exact bug in the Bug Doctor (Section 14).
उलटकर @italic @bold करने पर <i><b>hello</b></i> मिलता है। Security में यह और ज़्यादा मायने रखता है: login check को cache के बाहर (ऊपर) रखिए। अगर @cache ऊपर है, तो वह login wrapper के चलने से पहले ही अपने रखे नतीजों से जवाब दे देता है — यानी logged-out user को वह data मिल सकता है जो logged-in user के लिए cache हुआ था। यही bug Bug Doctor (Section 14) में देखिए।
反过来写成 @italic @bold 会得到 <i><b>hello</b></i>。对安全来说顺序更重要:要把登录检查放在缓存的 外面(上面)。如果 @cache 在上面,它会在登录 wrapper 运行 之前 就直接用缓存结果作答 — 于是一个已登出的用户也能拿到为已登录用户缓存的数据。这个 bug 可以在 Bug 医生(第 14 节)中看到。
The original reason was "so it doesn't cache unauthorised responses". That is not the real danger: a rejected call raises PermissionError, and an exception is never stored in the cache. The real danger is the other way round — a cache on the outside serves stored results to users who never passed the check.
मूल कारण दिया गया था "ताकि unauthorised responses cache न हों"। असली ख़तरा यह नहीं है: रोकी गई call PermissionError raise करती है, और exception कभी cache में नहीं रखा जाता। असली ख़तरा उलटा है — बाहर रखा cache उन users को रखे हुए नतीजे दे देता है जिन्होंने check कभी पास ही नहीं किया।
原文给出的理由是"这样就不会缓存未授权的响应"。这并不是真正的危险:被拒绝的调用会抛出 PermissionError,而异常永远不会被存进缓存。真正的危险恰恰相反 — 放在外层的缓存会 把已存的结果交给从未通过检查的用户。
Real-World Example — @login_required 🔐असली Example — @login_required 🔐真实案例 — @login_required 🔐
Now let us build the classic login decorator. In web frameworks, @login_required is the best-known use of decorators — it guards view functions so only logged-in users can call them.
अब classic login decorator बनाते हैं। Web frameworks में @login_required decorators का सबसे जाना-माना इस्तेमाल है — यह view functions की रखवाली करता है ताकि सिर्फ़ logged-in users उन्हें बुला सकें।
现在来写经典的登录装饰器。在 Web 框架中,@login_required 是装饰器最有名的用法 — 它守护视图函数,只有已登录的用户才能调用。
The decorator is a gate. Calls without a logged-in user are rejected before the view function runs, so the view stays pure business logic — no if user is None clutter.
Decorator एक द्वार है। बिना logged-in user वाली calls view function चलने से पहले ही रोक दी जाती हैं, इसलिए view में सिर्फ़ business logic रहता है — if user is None का झंझट नहीं।
装饰器就是一道关卡。没有登录用户的调用会在视图函数运行之前被拒绝,所以视图里只剩纯粹的业务逻辑 — 没有一堆 if user is None。
Version 1 — a simple @login_requiredVersion 1 — सरल @login_required版本 1 — 简单的 @login_required
from functools import wraps
# Fake session - in a real app this comes from Flask/Django
current_session = {"user": None}
def login_required(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
user = current_session.get("user")
if user is None:
raise PermissionError("Login required")
return fn(*args, **kwargs)
return wrapper
@login_required
def view_dashboard():
return f"Welcome, {current_session['user']['name']}!"
# --- Try it without logging in ---
try:
view_dashboard()
except PermissionError as e:
print(f"Blocked: {e}")
# --- Log in and try again ---
current_session["user"] = {"name": "Mohit", "role": "admin"}
print(view_dashboard())
Version 2 — @requires_role("admin") with argumentsVersion 2 — Arguments के साथ @requires_role("admin")版本 2 — 带参数的 @requires_role("admin")
Now we add arguments. We want @requires_role("admin") to allow only users with a matching role. This uses the three-layer pattern from Section 07.
अब arguments जोड़ते हैं। हम चाहते हैं कि @requires_role("admin") सिर्फ़ मेल खाने वाले role वाले users को आने दे। इसमें Section 07 का तीन-परत वाला pattern लगता है।
现在加入参数。我们希望 @requires_role("admin") 只放行角色匹配的用户。这里用的就是第 07 节的三层模式。
from functools import wraps
current_session = {"user": None}
def requires_role(*allowed_roles): # Layer 1 - decorator args
def decorator(fn): # Layer 2 - real decorator
@wraps(fn)
def wrapper(*args, **kwargs): # Layer 3 - runtime
user = current_session.get("user")
if user is None:
raise PermissionError("Login required")
if user["role"] not in allowed_roles:
raise PermissionError(
f"Role '{user['role']}' not in {allowed_roles}"
)
return fn(*args, **kwargs)
return wrapper
return decorator
@requires_role("admin")
def delete_all_users():
return "All users deleted!"
@requires_role("admin", "editor")
def publish_article(title):
return f"Published: {title}"
# --- Editor tries admin-only action ---
current_session["user"] = {"name": "Priya", "role": "editor"}
try:
delete_all_users()
except PermissionError as e:
print(f"Blocked: {e}")
print(publish_article("Python Decorators"))
# --- Admin can do both ---
current_session["user"] = {"name": "Mohit", "role": "admin"}
print(delete_all_users())
Version 3 — class-based @LoginRequired with an audit logVersion 3 — Audit log के साथ class-based @LoginRequired版本 3 — 带审计日志的基于类的 @LoginRequired
A class keeps state between calls, so it can log every attempt and report statistics.
Class calls के बीच state रखती है, इसलिए वह हर कोशिश का log रख सकती है और statistics बता सकती है।
类能在多次调用之间保存状态,所以它可以记录每一次尝试并给出统计。
from functools import wraps
from datetime import datetime
current_session = {"user": None}
class LoginRequired:
def __init__(self, fn):
wraps(fn)(self)
self.fn = fn
self.granted = 0
self.denied = 0
self.log = []
def __call__(self, *args, **kwargs):
user = current_session.get("user")
ts = datetime.now().strftime("%H:%M:%S")
if user is None:
self.denied += 1
self.log.append((ts, "DENIED", self.fn.__name__))
raise PermissionError("Login required")
self.granted += 1
self.log.append((ts, "GRANTED", user["name"]))
return self.fn(*args, **kwargs)
def stats(self):
return {"granted": self.granted, "denied": self.denied}
@LoginRequired
def view_profile():
return f"Profile of {current_session['user']['name']}"
# Simulate a mix of logged-in and logged-out attempts
for user in [None, {"name": "Mohit"}, None, {"name": "Aarav"}]:
current_session["user"] = user
try:
view_profile()
except PermissionError:
pass
print(view_profile.stats())
for entry in view_profile.log:
print(entry)
The timestamps come from datetime.now(), so they show the moment you run the code. The output above is from our real run.
Timestamps datetime.now() से आते हैं, इसलिए वे वही पल दिखाते हैं जब आप code चलाते हैं। ऊपर का output हमारे असली run का है।
时间戳来自 datetime.now(),显示的是你运行代码的那一刻。上面的输出来自我们的真实运行。
🔐 Simulator: Login Gate🔐 Simulator: Login Gate🔐 模拟器:登录关卡
Choose who is logged in and which protected function to call, then send the request through the gate. The results were produced by running the Version 1 and Version 2 decorators in Python; the audit log counts attempts like Version 3.
चुनिए कौन logged-in है और कौन-सा protected function बुलाना है, फिर request को gate से भेजिए। नतीजे Version 1 और Version 2 वाले decorators को Python में चलाकर बने हैं; audit log Version 3 की तरह कोशिशें गिनता है।
选择谁已登录、要调用哪个受保护的函数,然后把请求送过关卡。结果是在 Python 中运行版本 1 和版本 2 的装饰器得到的;审计日志像版本 3 一样统计每次尝试。
USERsends the callcall भेजता है发出调用Login check, role check, audit log, statistics — all from one decorator. view_profile itself has no login code inside it. Business logic and security stay cleanly apart. That is why Django ships @login_required and @permission_required as decorators, and why the Flask-Login extension provides @login_required for Flask.
Login check, role check, audit log, statistics — सब एक decorator से। view_profile के अंदर कोई login code नहीं। Business logic और security साफ़-साफ़ अलग रहते हैं। इसीलिए Django @login_required और @permission_required decorators के रूप में देता है, और Flask के लिए Flask-Login extension @login_required देता है।
登录检查、角色检查、审计日志、统计数据 — 全都来自一个装饰器。view_profile 内部没有任何登录代码。业务逻辑和安全逻辑干净地分开。这就是为什么 Django 以装饰器形式提供 @login_required 和 @permission_required,而 Flask 的 @login_required 由 Flask-Login 扩展提供。
The original said "Flask's @login_required". Flask itself does not include it — it comes from the Flask-Login extension (from flask_login import login_required). Django's versions live in django.contrib.auth.decorators.
मूल में "Flask का @login_required" लिखा था। Flask में यह अपने आप नहीं आता — यह Flask-Login extension से आता है (from flask_login import login_required)। Django वाले django.contrib.auth.decorators में हैं।
原文写的是"Flask 的 @login_required"。Flask 本身并不包含它 — 它来自 Flask-Login 扩展(from flask_login import login_required)。Django 的版本位于 django.contrib.auth.decorators。
Built-in Decorators You Should Know 📚Built-in Decorators जो आपको पता होने चाहिए 📚你应该知道的内置装饰器 📚
| DecoratorDecorator装饰器 | Purposeकाम作用 | Where it livesकहाँ मिलता है所在位置 |
|---|---|---|
@staticmethod | A method that needs neither self nor clsऐसा method जिसे न self चाहिए न cls既不需要 self 也不需要 cls 的方法 | built-in |
@classmethod | A method that receives the class cls instead of selfऐसा method जिसे self की जगह class cls मिलती है接收类 cls 而不是 self 的方法 | built-in |
@property | Read a method like an attribute (obj.name, not obj.name())Method को attribute की तरह पढ़ें (obj.name, obj.name() नहीं)像属性一样读取方法(obj.name 而不是 obj.name()) | built-in |
@functools.wraps | Copy name, docstring and more onto a wrapperWrapper पर नाम, docstring आदि copy करें把名字、文档字符串等复制到 wrapper 上 | functools |
@functools.lru_cache | Remember function results (with a size limit)Function के नतीजे याद रखें (size limit के साथ)记住函数结果(可限制大小) | functools |
@functools.cached_property | Compute once, then store on the instanceएक बार compute करें, फिर instance पर रखें只计算一次,然后保存在实例上 | functools |
@dataclass | Auto-generate __init__, __repr__, __eq____init__, __repr__, __eq__ अपने आप बनाता है自动生成 __init__、__repr__、__eq__ | dataclasses |
@contextmanager | Turn a generator into something you can use with withGenerator को with के साथ चलने लायक बनाता है把生成器变成可以配合 with 使用的对象 | contextlib |
class User:
def __init__(self, first, last):
self._first = first
self._last = last
@property # access as attribute
def full_name(self):
return f"{self._first} {self._last}"
@classmethod # alternative constructor
def from_string(cls, s):
first, last = s.split(" ", 1)
return cls(first, last)
@staticmethod # utility, no self/cls
def is_valid_name(s):
return len(s.split()) >= 2
u = User.from_string("Mohit Kumar")
print(u.full_name) # -> Mohit Kumar (no parens!)
print(User.is_valid_name("Mohit Kumar"))
@dataclass and @contextmanager each deserve their own lesson. They are covered in upcoming tutorials; here you only need to recognise them as decorators.
@dataclass और @contextmanager दोनों के लिए अलग lesson चाहिए। ये आने वाले tutorials में आएँगे; यहाँ बस इतना पहचानिए कि ये decorators हैं।
@dataclass 和 @contextmanager 都值得单独讲一课,会在 后续教程 中介绍;这里你只需认出它们是装饰器。
Common Pitfalls (and Good Habits) 🩹आम ग़लतियाँ (और अच्छी आदतें) 🩹常见陷阱(与好习惯)🩹
help(), docs tools and logs show wrapper. In Flask, two views decorated this way both get the endpoint name wrapper and the app refuses to start. pytest fixtures stop being passed in.help(), docs tools और logs wrapper दिखाते हैं। Flask में ऐसे decorate किए दो views दोनों को endpoint नाम wrapper मिलता है और app शुरू होने से मना कर देता है। pytest fixtures भी नहीं पहुँचते।help()、文档工具和日志都显示 wrapper。在 Flask 中,两个这样装饰的视图都会得到端点名 wrapper,应用会拒绝启动。pytest 的 fixture 也无法注入。fn() but does not return its result, so the decorated function quietly returns None. No error, just wrong values.fn() बुलाता है पर उसका नतीजा return नहीं करता, इसलिए decorated function चुपचाप None लौटाता है। कोई error नहीं, बस ग़लत values।fn() 却没有返回它的结果,于是被装饰的函数悄悄返回 None。没有报错,只是值不对。@deco. A decorator with arguments is written @deco(...). Writing @repeat instead of @repeat(3) gives no error — the function just silently stops doing its job.@deco लिखा जाता है। Arguments वाला decorator @deco(...) लिखा जाता है। @repeat(3) की जगह @repeat लिखने पर कोई error नहीं आता — function बस चुपचाप अपना काम करना बंद कर देता है।@deco,带参数的装饰器写作 @deco(...)。把 @repeat(3) 写成 @repeat 不会报错 — 函数只是悄悄地不再做它该做的事。@login_required) outermost. If a cache sits above them, it serves stored results to callers who never passed the check.@login_required) सबसे बाहर रखिए। अगर cache उनके ऊपर है, तो वह check पास न करने वाले callers को भी रखे हुए नतीजे दे देता है।@login_required)要放在最外层。如果缓存在它们上面,就会把已存的结果交给从未通过检查的调用者。self. A function decorator's *args catches it automatically — no special handling needed. (A class-based decorator is different; see the fix below.)self है। Function decorator का *args उसे अपने आप पकड़ लेता है — कुछ ख़ास करने की ज़रूरत नहीं। (Class-based decorator अलग है; नीचे का सुधार देखें।)self。函数 装饰器的 *args 会自动接住它 — 无需特殊处理。(基于类 的装饰器则不同,见下面的修正。)@wraps, the original function is still reachable as decorated.__wrapped__. Handy for unit tests that want to skip the decorator.@wraps लगाया है, तो मूल function अब भी decorated.__wrapped__ से मिलता है। Decorator को छोड़कर test करने वाले unit tests के लिए काम का।@wraps,原函数仍然可以通过 decorated.__wrapped__ 拿到。对想跳过装饰器的单元测试很方便。(1) The original said a missing @wraps breaks pytest test discovery. Discovery still works — pytest finds tests by their names in the module. What really breaks is fixtures: pytest reads the signature, sees only (*args, **kwargs) and passes nothing, so a test like test_x(num) fails with TypeError: … missing 1 required positional argument: 'num' (we ran it with pytest). (2) It said decorating methods needs "no special handling". That is true for function decorators, but a class-based decorator like CountCalls breaks on a method, because Python does not pass self to it:
(1) मूल में कहा गया कि @wraps न होने से pytest test discovery टूटती है। Discovery तब भी चलती है — pytest module में नामों से tests ढूँढता है। असल में fixtures टूटते हैं: pytest signature पढ़ता है, उसे सिर्फ़ (*args, **kwargs) दिखता है और वह कुछ नहीं भेजता, इसलिए test_x(num) जैसा test TypeError: … missing 1 required positional argument: 'num' से fail होता है (हमने इसे pytest से चलाकर देखा)। (2) कहा गया कि methods decorate करने में "कुछ ख़ास नहीं करना पड़ता"। Function decorators के लिए यह सही है, पर CountCalls जैसा class-based decorator method पर टूट जाता है, क्योंकि Python उसे self नहीं देता:
(1) 原文说缺少 @wraps 会破坏 pytest 的测试发现。测试发现其实照常工作 — pytest 按模块中的名字查找测试。真正出问题的是 fixture:pytest 读取函数签名,只看到 (*args, **kwargs),于是什么都不传,像 test_x(num) 这样的测试就会以 TypeError: … missing 1 required positional argument: 'num' 失败(我们用 pytest 实际运行过)。(2) 原文说装饰方法"无需特殊处理"。这对函数装饰器成立,但像 CountCalls 这样 基于类 的装饰器用在方法上就会出错,因为 Python 不会把 self 传给它:
from functools import wraps
class CountCalls:
def __init__(self, fn):
wraps(fn)(self)
self.fn = fn
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
return self.fn(*args, **kwargs)
class Greeter:
@CountCalls
def hello(self):
return "hello"
try:
Greeter().hello()
except TypeError as e:
print("TypeError:", e)
A function decorator on the same kind of method works, and you can see self arrive as the first item of args:
उसी तरह के method पर function decorator चलता है, और आप self को args के पहले item के रूप में आते देख सकते हैं:
同样的方法换成函数装饰器就能正常工作,还能看到 self 作为 args 的第一个元素传进来:
from functools import wraps
def announce(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
print("args =", args) # self is args[0]
return fn(*args, **kwargs)
return wrapper
class Greeter:
@announce
def hello(self, name):
return f"hello, {name}"
print(Greeter().hello("Mohit"))
Plain functions turn into bound methods when you read them from an instance; ordinary objects like a CountCalls instance do not. The machinery behind this — descriptors and __get__ — is covered in an upcoming tutorial. For now: use function decorators on methods.
साधारण functions instance से पढ़े जाने पर bound methods बन जाते हैं; CountCalls instance जैसे आम objects नहीं बनते। इसके पीछे की मशीनरी — descriptors और __get__ — एक आने वाले tutorial में है। अभी के लिए: methods पर function decorators लगाइए।
普通函数从实例上读取时会变成 绑定方法;而像 CountCalls 实例这样的普通对象不会。其背后的机制 — 描述符 与 __get__ — 会在 后续教程 中讲解。目前只需记住:给方法加装饰器时用函数装饰器。
🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生
Six real decorator mistakes. Buggy code on the left, fixed code on the right, changed lines marked. Every output is from a real run.
Decorators की छह असली ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर output असली run से है।
六个真实的装饰器错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每个输出都来自真实运行。
Golden Rules and Quiz 🏆सुनहरे नियम और Quiz 🏆黄金法则与小测验 🏆
@functools.wraps(fn) on your wrapper. It copies __name__ and __doc__ and adds __wrapped__. Skipping it breaks tracebacks, docs and framework features that rely on the name.@functools.wraps(fn) लगाइए। यह __name__ और __doc__ copy करता है और __wrapped__ जोड़ता है। इसे छोड़ने से tracebacks, docs और नाम पर टिके framework features टूटते हैं।@functools.wraps(fn)。它会复制 __name__ 和 __doc__,并添加 __wrapped__。不用它会破坏 traceback、文档以及依赖函数名的框架功能。*args, **kwargs in the wrapper and pass them on unchanged. Then your decorator works with any function — including methods, where the first argument is self.*args, **kwargs लीजिए और बिना बदले आगे भेजिए। तब आपका decorator हर function के साथ चलेगा — methods के साथ भी, जहाँ पहला argument self होता है।*args, **kwargs 并原样转发。这样你的装饰器就能用于任何函数 — 包括第一个参数是 self 的方法。return the result of fn(*args, **kwargs). Forgetting it makes the decorated function quietly return None — one of the hardest bugs to spot in code review.fn(*args, **kwargs) का नतीजा हमेशा return कीजिए। भूलने पर decorated function चुपचाप None लौटाता है — code review में पकड़ने में सबसे मुश्किल bugs में से एक।return fn(*args, **kwargs) 的结果。忘了它,被装饰的函数会悄悄返回 None — 这是代码审查中最难发现的 bug 之一。@repeat(3) is really repeat(3)(fn).@repeat(3) असल में repeat(3)(fn) है।@repeat(3) 实际上就是 repeat(3)(fn)。@login_required, @requires_role) go on top. Caching decorators go below them, so a cache can never answer a caller who has not passed the check.@login_required, @requires_role) ऊपर जाते हैं। Caching decorators उनके नीचे, ताकि cache कभी उस caller को जवाब न दे जिसने check पास नहीं किया।@login_required、@requires_role)放在上面,缓存类装饰器放在它们下面,这样缓存永远不会回应没通过检查的调用者。.reset(), .stats()) or a clearer OOP structure — and remember a plain class decorator does not work on methods..reset(), .stats()) या साफ़ OOP ढाँचा चाहिए, तब class पर जाइए — और याद रखिए कि साधारण class decorator methods पर नहीं चलता।.reset()、.stats())或更清晰的面向对象结构时再改用类 — 并记住普通的类装饰器不能直接用在方法上。functools.lru_cache, functools.cache, functools.cached_property, dataclasses.dataclass and contextlib.contextmanager already exist. Reach for them before writing your own.functools.lru_cache, functools.cache, functools.cached_property, dataclasses.dataclass और contextlib.contextmanager पहले से मौजूद हैं। अपना लिखने से पहले इन्हें आज़माइए।functools.lru_cache、functools.cache、functools.cached_property、dataclasses.dataclass 和 contextlib.contextmanager 都已经有了。先用它们,再考虑自己写。Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题
A decorator is a callable that takes a function and returns a replacement, and @deco is just f = deco(f). Inside, a wrapper with *args, **kwargs does the extra work, calls the original and returns its result; @wraps keeps the original's identity. Decorators with arguments add one more layer, classes add lasting state, and when you stack them they wrap bottom-up and run top-down — so put the security gate on top.
Decorator एक callable है जो function लेकर उसकी जगह नया लौटाता है, और @deco बस f = deco(f) है। अंदर *args, **kwargs वाला wrapper अतिरिक्त काम करता है, मूल को बुलाता है और उसका नतीजा लौटाता है; @wraps मूल की पहचान बचाए रखता है। Arguments वाले decorators एक परत और जोड़ते हैं, classes लंबी state जोड़ती हैं, और एक के ऊपर एक लगाने पर वे नीचे से ऊपर लपेटते हैं और ऊपर से नीचे चलते हैं — इसलिए security gate ऊपर रखिए।
装饰器是一个接收函数并返回替代品的可调用对象,@deco 就是 f = deco(f)。内部带 *args, **kwargs 的 wrapper 负责额外工作,调用原函数并返回其结果;@wraps 保留原函数的身份信息。带参数的装饰器多一层嵌套,类提供持久状态;叠加时从下往上包装、从上往下执行 — 所以要把安全关卡放在最上面。