Decorators & Context Managers
ডেকোরেটর ও কনটেক্সট ম্যানেজার — Python-এর সবচেয়ে elegant প্যাটার্ন
1. Functions as Values, Functions as Decorators
In Python, functions are first-class — you can pass them around, return them, and store them in variables.
A decorator is a function that takes another function and returns a new, modified one.
The @ syntax is just syntactic sugar. Decorators let you add features — logging, timing, caching,
access control — without touching the original code.
@ সিনট্যাক্স শুধু একটি syntactic sugar। Decorator দিয়ে আপনি মূল কোড না ছুঁয়ে logging, timing, caching, access control যোগ করতে পারেন।
2. A Simple Decorator
def announce(fn):
def wrapper(*args, **kwargs):
print(f"Calling {fn.__name__}...")
result = fn(*args, **kwargs)
print(f"{fn.__name__} returned {result!r}")
return result
return wrapper
@announce
def add(a, b):
return a + b
print(add(3, 4))
# @announce is equivalent to: add = announce(add)
3. Preserving Metadata with functools.wraps
Without help, the wrapper loses the original function's name and docstring. functools.wraps
copies them back.
from functools import wraps
import time
def timed(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:.6f}s")
return result
return wrapper
@timed
def slow_sum(n):
"""Sum 1..n the slow way."""
return sum(range(n + 1))
print(slow_sum(100000))
print(slow_sum.__name__, "|", slow_sum.__doc__)
4. Decorators with Arguments
from functools import wraps
def repeat(times):
def outer(fn):
@wraps(fn)
def inner(*args, **kwargs):
for _ in range(times):
result = fn(*args, **kwargs)
return result
return inner
return outer
@repeat(3)
def say_hi(name):
print(f"Hi, {name}!")
say_hi("Ayesha")
5. Context Managers & the with Statement
A context manager defines setup and teardown logic around a block of code. You use one every
time you write with open(...). Writing your own is easy — implement __enter__ and
__exit__, or use contextlib.contextmanager.
class Timer:
def __enter__(self):
import time
self.t = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, tb):
import time
elapsed = time.perf_counter() - self.t
print(f"elapsed: {elapsed:.4f}s")
with Timer():
sum(range(1_000_000))
6. contextlib.contextmanager — The Easy Way
from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"<{name}>")
try:
yield
finally:
print(f"</{name}>")
with tag("section"):
print(" This text is inside a tag.")
7. Vocabulary (শব্দভাণ্ডার)
| Term | Meaning | বাংলায় |
|---|---|---|
| First-class function | Functions can be assigned, passed, returned. | ফাংশন variable-এ রাখা, pass করা, return করা যায়। |
| Decorator | Function that modifies another function. | একটি ফাংশনকে পরিবর্তন করে অন্য ফাংশন দেয়। |
| Wrapper | The replacement function returned by a decorator. | Decorator-এর return করা নতুন ফাংশন। |
| Context manager | Object with setup/teardown via with. | with-এর মাধ্যমে setup/teardown করা object। |
__enter__ / __exit__ | Dunder methods implementing the protocol. | Protocol-এর দুটি dunder method। |
8. Practice Problems
-
Write a
@logdecorator that prints the arguments and return value.Argument ও return value প্রিন্ট করে এমন@logdecorator লিখুন।✨ Show Answer (উত্তর দেখুন)
ans1.pyfrom functools import wraps def log(fn): @wraps(fn) def w(*a, **kw): print(f"{fn.__name__}({a}, {kw})") r = fn(*a, **kw) print(f" → {r}") return r return w @log def mul(x, y): return x * y mul(3, 7) -
Write a context manager
suppress_errorsthat swallows any exception in its block.একটি context manager লিখুন যা ব্লকের যেকোনো exception silently চেপে রাখে।✨ Show Answer (উত্তর দেখুন)
ans2.pyfrom contextlib import contextmanager @contextmanager def suppress_errors(): try: yield except Exception as e: print("ignored:", e) with suppress_errors(): 1 / 0 print("moved on") -
Write
@retry(times=3)that retries a failing function up to N times.@retry(times=3)লিখুন যা ব্যর্থ হলে ফাংশনকে N বার পর্যন্ত retry করবে।✨ Show Answer (উত্তর দেখুন)
ans3.pyfrom functools import wraps import random def retry(times): def outer(fn): @wraps(fn) def inner(*a, **kw): for i in range(times): try: return fn(*a, **kw) except Exception as e: print(f"attempt {i+1} failed: {e}") raise RuntimeError("all attempts failed") return inner return outer @retry(3) def flaky(): if random.random() < 0.7: raise ValueError("unlucky") return "success" try: print(flaky()) except RuntimeError as e: print(e) -
Explain what
functools.wrapsdoes — and why it matters.functools.wrapsকী কাজ করে এবং কেন জরুরি — ব্যাখ্যা করুন।✨ Show Answer (উত্তর দেখুন)
Answer: Without
wraps, the wrapped function loses its original__name__,__doc__, module, and signature — everything introspection tools and debuggers rely on.@wraps(fn)copies that metadata onto the wrapper, so the decorated function looks identical to the original from the outside while still running the added behaviour.wrapsছাড়া ফাংশন-এর__name__,__doc__, signature সব হারিয়ে যায়।@wraps(fn)এগুলো wrapper-এ কপি করে — ফলে বাইরে থেকে decorated ফাংশন মূলটির মতোই দেখায়, কিন্তু ভেতরে নতুন behaviour চলে। -
Write a context manager that changes the working directory temporarily.Temporary-ভাবে directory পরিবর্তন করে এমন context manager লিখুন।
✨ Show Answer (উত্তর দেখুন)
ans5.pyimport os from contextlib import contextmanager @contextmanager def chdir(path): old = os.getcwd() os.chdir(path) try: yield finally: os.chdir(old) with chdir("/tmp"): print("inside:", os.getcwd()) print("outside:", os.getcwd())
Summary — Module 26
Decorators wrap functions to add behaviour without touching the original. Use functools.wraps to
preserve metadata. For parameterized decorators, nest one more level. Context managers bracket a block with
setup and teardown — write them as classes with __enter__/__exit__, or more easily as
generators with @contextmanager. Both are building blocks behind logging, retries, locks, and
transaction-style code.
functools.wraps দিয়ে metadata রক্ষা করুন। Parameter-সহ decorator-এ আরেকটি স্তর nest করুন। Context manager — setup/teardown bracket; class-এ বা @contextmanager generator-এ লিখতে পারেন।