Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. @contextmanager ichidan
- 2.2. yield atrofidagi uch qoida
- 2.3. Istisnoni o'zgartirish
- 2.4. Bir martalik va dekorator sifatida
- 2.5. ExitStack — dinamik with
- 2.6. pop_all — egalikni o'tkazish
- 2.7. Kichik vositalar
- 2.8. Asinxron juftlari
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — O'z @contextmanager imizni yozib, asli bilan solishtirish
- Misol 2 — Menejer dekorator sifatida va kichik vositalar
- Misol 3 — ExitStack chuqur
- Misol 4 — Amaliy: CLI dasturi uchun sinov muhiti
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
10.7-dars: contextlib
10-QISM — ILG'OR TIL IMKONIYATLARI · 7-dars
1. Kirish va motivatsiya
9.3-darsda @contextmanager ni context manager yozishning qisqa yo'li sifatida ko'rgan edik. Mana shunday yozilgan taymer:
from contextlib import contextmanager
import time
@contextmanager
def taymer(nom):
bosh = time.perf_counter()
yield
print(f"{nom}: {time.perf_counter() - bosh:.3f} s")
with taymer("import"):
malumotlarni_yukla()Ishlaydi. Endi malumotlarni_yukla() xato bersa nima bo'ladi? Vaqt chop etilmaydi. Chunki yield dan keyingi qator finally ichida emas: blokdagi istisno generator ichiga aynan yield qatorida "otiladi", va funksiyaning qolgan qismi bajarilmay qoladi.
Yana bir holat:
t = taymer("hisobot")
with t: ...
with t: ... # AttributeError: '_GeneratorContextManager' object has no attribute 'args'Bu xabardan hech narsa tushunib bo'lmaydi. Aslida sabab oddiy: generator faqat bir marta ishlaydi.
contextlib vositalari qulay, lekin qanday ishlashini bilmasangiz, ular xatolarni yashiradi. Bu darsda 9.3-darsdagi "qanday ishlatiladi" dan "ichida nima bo'ladi" ga o'tamiz.
Bu darsda:
-
@contextmanagerichidan: generator qanday context manager'ga aylanadi yieldatrofidagi uch qoida- Context manager'ni dekorator sifatida ishlatish —
ContextDecorator -
ExitStackchuqur:enter_context,callback,push,pop_all - Resursga egalikni boshqa obyektga o'tkazish
suppressvaExceptionGroup,nullcontext,chdir,redirect_stdout,AbstractContextManager
2. Nazariya — chuqur tushuntirish
2.1. @contextmanager ichidan
@contextmanager generator funksiyani oladi va har chaqiruvda yangi _GeneratorContextManager obyektini qaytaradi. Uning mantig'i soddalashtirilgan holda:
class GenMenejer:
def __init__(self, funksiya, args, kwargs):
self.gen = funksiya(*args, **kwargs)
def __enter__(self):
try:
return next(self.gen) # ⭐ yield gacha bajaradi
except StopIteration:
raise RuntimeError("generator didn't yield") from None
def __exit__(self, tur, qiymat, tb):
if tur is None: # blok xatosiz tugadi
try:
next(self.gen) # yield dan keyingi qismni bajaradi
except StopIteration:
return False
raise RuntimeError("generator didn't stop")
try:
self.gen.throw(qiymat) # ⭐ istisnoni yield qatoriga otadi
except StopIteration as e:
return e is not qiymat # generator jim tugadi → yutildi
except BaseException as e:
if e is qiymat:
return False # xuddi o'sha istisno → o'tkazib yuboradi
raise # boshqa istisno → uni ko'taradi
raise RuntimeError("generator didn't stop after throw()")Demak:
| Generator ichida | Natija |
|---|---|
yield gacha bo'lgan kod |
__enter__ |
yield qiymati |
as NOM |
yield dan keyingi kod |
__exit__ — faqat blok xatosiz tugasa |
| Blokdagi istisno | yield qatorida ko'tariladi |
| Generator istisnoni ushlab, jim tugasa | Istisno yutiladi |
| Generator istisnoni qayta ko'tarsa | Istisno o'tadi |
| Generator boshqa istisno ko'tarsa | Asl istisno o'rniga shu chiqadi |
Haqiqiy kodda PEP 479 (10.3-dars) tufayli blokdagi StopIteration generator ichida RuntimeError ga aylanishini hisobga oladigan qo'shimcha tekshiruv ham bor.
2.2. yield atrofidagi uch qoida
1. Tozalash finally da bo'lsin.
@contextmanager
def ulanish(manzil):
u = ulan(manzil)
try:
yield u
finally:
u.yop() # ✅ blokda xato bo'lsa ham2. Aniq bitta yield. Hech yield qilmasa — RuntimeError: generator didn't yield, ikkinchi yield ga yetib borsa — generator didn't stop. Shartli yield yozmang:
@contextmanager
def yomon(kerak):
if kerak:
yield # ❌ kerak=False bo'lsa — RuntimeError3. except — bu yutish. Generator istisnoni ushlab, qayta ko'tarmasa, u yo'qoladi:
@contextmanager
def jurnal_bilan():
try:
yield
except Exception as e:
jurnal.error(e) # ⚠️ raise yo'q — istisno yutildiIstisnoni ko'rish, lekin o'tkazib yuborish kerak bo'lsa — raise yozing.
2.3. Istisnoni o'zgartirish
Generator ichida boshqa istisno ko'tarish mumkin — asl istisno __cause__ yoki __context__ ga tushadi (9.5-dars):
@contextmanager
def domen_xatolari():
try:
yield
except sqlite3.IntegrityError as e:
raise DublikatXato("bunday foydalanuvchi bor") from eBu qatlamlar orasida xato turlarini tarjima qilishning toza usuli.
2.4. Bir martalik va dekorator sifatida
@contextmanager qaytargan obyekt bir martalik: generator bir marta ishlaydi. __enter__ o'z argumentlarini o'chiradi (ular endi kerak emas), shuning uchun ikkinchi with tushunarsiz AttributeError beradi.
Lekin u dekorator sifatida bemalol qayta ishlatiladi:
@taymer("hisobot")
def hisobot_tuz():
...
hisobot_tuz() # har chaqiruvda yangi generator
hisobot_tuz() # ✅Sababi — ContextDecorator bazaviy sinfi. Dekorator funksiyani quyidagicha o'raydi:
def __call__(self, funksiya):
def orovchi(*args, **kwargs):
with self._recreate_cm(): # ⭐ har chaqiruvda yangi menejer
return funksiya(*args, **kwargs)
return orovchi_GeneratorContextManager._recreate_cm() o'sha argumentlar bilan yangi obyekt yaratadi. Oddiy ContextDecorator esa self ni qaytaradi — ya'ni sinf asosidagi menejer bitta nusxa bilan barcha chaqiruvlarda ishlaydi va qayta kiriladigan bo'lishi kerak (10.6-dars, 2.8).
Dekorator shaklida as yo'q — yield qiymati funksiyaga yetib bormaydi.
2.5. ExitStack — dinamik with
9.3-darsda ExitStack ni soni oldindan noma'lum fayllar uchun ishlatgan edik. Uning to'liq interfeysi:
| Metod | Nima qiladi | Chiqishda chaqiriladi |
|---|---|---|
enter_context(cm) |
cm.__enter__() ni chaqiradi, natijani qaytaradi |
cm.__exit__(...) |
callback(f, *args, **kw) |
Hech narsa chaqirmaydi | f(*args, **kw) — istisno ma'lumotisiz |
push(exit_f) |
Hech narsa chaqirmaydi | exit_f(tur, qiymat, tb) — yuta oladi |
pop_all() |
Barcha yozuvlarni yangi ExitStack ga ko'chiradi |
Joriysi endi hech narsa qilmaydi |
close() |
Hozirning o'zida hammasini chaqiradi | — |
Qoidalar:
- Chiqish — teskari tartibda (LIFO)
- Istisno ma'lumoti keyingi (tashqariroq) yozuvga uzatiladi; biror
__exit__/pushfunksiyasi uni yutsa, qolganlar(None, None, None)oladi - Yozuvlardan biri xato bersa, keyingilari baribir chaqiriladi, xatolar
__context__zanjiriga bog'lanadi — xuddi ichma-ichwithdagidek pushvacallbackdekorator sifatida ham ishlaydi:@stack.callback
2.6. pop_all — egalikni o'tkazish
Bir nechta resursni olish kerak, ulardan biri ochilmasa — ochilganlarini yopish kerak. Hammasi ochilsa — ularni boshqa obyektga topshirish kerak, yopmaslik:
def ulan_hammasi(manzillar):
with ExitStack() as stack:
ulanishlar = [stack.enter_context(Ulanish(m)) for m in manzillar]
# shu qatorgacha xato bo'lsa — with ochilganlarini yopadi
yopuvchi = stack.pop_all()
# ⭐ endi stack bo'sh, with chiqishda hech narsa yopmaydi
return ulanishlar, yopuvchiChaqiruvchi keyinroq with yopuvchi: yoki yopuvchi.close() bilan hammasini yopadi. Bu __enter__ ichida bir nechta resurs olishning eng ishonchli usuli (10.6-dars, 6-xato).
2.7. Kichik vositalar
nullcontext(qiymat=None) — hech narsa qilmaydigan menejer, __enter__ qiymat ni qaytaradi:
kontekst = qulf if parallel else nullcontext()
with kontekst:
...suppress(*turlar) — tanlangan istisnolarni yutadi. 3.12 dan ExceptionGroup bilan ishlaydi: mos keladiganlarini guruhdan olib tashlaydi, qolganlarini ko'taradi.
closing(obj) — chiqishda obj.close(); __enter__ obj ni qaytaradi.
chdir(yol) (3.11+) — joriy papkani vaqtincha almashtiradi. Joriy papka butun jarayonga tegishli — iplar bilan xavfsiz emas.
redirect_stdout(f) / redirect_stderr(f) — sys.stdout ni vaqtincha almashtiradi. Qayta kiriladigan, lekin global holatni o'zgartiradi: kutubxona kodida va iplarda ishlatmang.
AbstractContextManager — __enter__ ni self qaytaradigan qilib beradi, __exit__ ni abstrakt qiladi. isinstance(obj, AbstractContextManager) ikkala metodi bor har qanday obyekt uchun True (merosxo'r bo'lmasa ham).
2.8. Asinxron juftlari
asynccontextmanager, AsyncExitStack, aclosing — xuddi shu g'oyalar async with uchun. Ular 14-qismda.
3. Tez ma'lumotnoma
@contextmanager shabloni
@contextmanager
def nom(...):
resurs = ol() # __enter__
try:
yield resurs # as NOM
except Tur: # ixtiyoriy — yutish yoki tarjima
raise BoshqaXato(...) from None
finally:
resurs.qoy() # __exit__ — har doimXatolar
yield yo'q → RuntimeError: generator didn't yield
ikkinchi yield → RuntimeError: generator didn't stop
qayta with → AttributeError: ... no attribute 'args'
finally yo'q + istisno → tozalash bajarilmaydi (xabarsiz!)
except + raise yo'q → istisno yutiladiExitStack
with ExitStack() as s:
f = s.enter_context(open(yol)) # menejer
s.callback(os.remove, vaqtincha) # oddiy funksiya
s.push(exit_funksiya) # (tur, qiymat, tb) → bool
egasi = s.pop_all() # egalikni o'tkazishVositalar
nullcontext(x) → as x, hech narsa qilmaydi
suppress(T, ...) → T larni yutadi; ExceptionGroup ni bo'ladi
closing(obj) → obj.close()
chdir(yol) → os.chdir vaqtincha (3.11+)
redirect_stdout(f) → sys.stdout vaqtincha
ContextDecorator → menejer = dekorator
AbstractContextManager → __enter__ = self4. Batafsil misollar
Misol 1 — O'z @contextmanager imizni yozib, asli bilan solishtirish
"""Soddalashtirilgan GenMenejer va contextlib.contextmanager bir xil holatlarda qanday ishlaydi."""
import contextlib
class GenMenejer:
def __init__(self, funksiya, args, kwargs):
self.gen = funksiya(*args, **kwargs)
def __enter__(self):
try:
return next(self.gen)
except StopIteration:
raise RuntimeError("generator didn't yield") from None
def __exit__(self, tur, qiymat, tb):
if tur is None:
try:
next(self.gen)
except StopIteration:
return False
raise RuntimeError("generator didn't stop")
try:
self.gen.throw(qiymat)
except StopIteration as e:
return e is not qiymat
except RuntimeError as e:
# PEP 479: blokdagi StopIteration generator ichida RuntimeError ga aylanadi
if e is qiymat or (isinstance(qiymat, StopIteration) and e.__cause__ is qiymat):
return False
raise
except BaseException as e:
if e is qiymat:
return False
raise
raise RuntimeError("generator didn't stop after throw()")
def menejer_qil(funksiya):
def yaratuvchi(*args, **kwargs):
return GenMenejer(funksiya, args, kwargs)
return yaratuvchi
iz = []
def oddiy():
iz.append("kirdi")
try:
yield "resurs"
finally:
iz.append("tozaladi")
def finallysiz():
iz.append("kirdi")
yield "resurs"
iz.append("tozaladi")
def yutuvchi():
try:
yield
except KeyError:
iz.append("KeyError yutildi")
def tarjimon():
try:
yield
except KeyError as e:
raise LookupError(f"topilmadi: {e}") from e
def yieldsiz():
return
yield
def ikki_yield():
yield 1
yield 2
HOLATLAR = [
("oddiy, xatosiz", oddiy, None),
("oddiy, ValueError", oddiy, ValueError("v")),
("⚠️ finallysiz, ValueError", finallysiz, ValueError("v")),
("yutuvchi, KeyError", yutuvchi, KeyError("k")),
("yutuvchi, TypeError", yutuvchi, TypeError("t")),
("tarjimon, KeyError", tarjimon, KeyError("id")),
("yieldsiz", yieldsiz, None),
("ikki_yield, xatosiz", ikki_yield, None),
("oddiy, StopIteration", oddiy, StopIteration("s")),
]
def sina(dekorator, funksiya, istisno):
iz.clear()
menejer = dekorator(funksiya)
try:
with menejer() as q:
iz.append(f"blok({q})")
if istisno is not None:
raise istisno
natija = "jim tugadi"
except BaseException as e:
sabab = f" ← {type(e.__cause__).__name__}" if e.__cause__ else ""
natija = f"{type(e).__name__}: {e}{sabab}"
return f"{natija} | {' → '.join(iz) or '-'}"
print("=== Har bir holat: asl contextmanager va bizning GenMenejer ===")
mos = 0
for nom, funksiya, istisno in HOLATLAR:
asli = sina(contextlib.contextmanager, funksiya, istisno)
bizniki = sina(menejer_qil, funksiya, istisno)
mos += asli == bizniki
belgi = "=" if asli == bizniki else "≠"
print(f"\n {nom}")
print(f" asli: {asli}")
print(f" bizniki: {bizniki} {belgi}")
print(f"\n mos kelgan holatlar: {mos}/{len(HOLATLAR)}")
print("\n=== ⚠️ Bir martalik: bir obyekt bilan ikki marta with ===")
cm = contextlib.contextmanager(oddiy)()
with cm:
pass
try:
with cm:
pass
except AttributeError as e:
print(f" AttributeError: {e}")
print(f" turi: {type(cm).__name__}, bazalari: {[b.__name__ for b in type(cm).__mro__[1:-1]]}")Natijaning muhim qismi:
=== Har bir holat: asl contextmanager va bizning GenMenejer ===
oddiy, xatosiz
asli: jim tugadi | kirdi → blok(resurs) → tozaladi
bizniki: jim tugadi | kirdi → blok(resurs) → tozaladi =
oddiy, ValueError
asli: ValueError: v | kirdi → blok(resurs) → tozaladi
bizniki: ValueError: v | kirdi → blok(resurs) → tozaladi =
⚠️ finallysiz, ValueError
asli: ValueError: v | kirdi → blok(resurs)
bizniki: ValueError: v | kirdi → blok(resurs) =
yutuvchi, KeyError
asli: jim tugadi | blok(None) → KeyError yutildi
bizniki: jim tugadi | blok(None) → KeyError yutildi =
yutuvchi, TypeError
asli: TypeError: t | blok(None)
bizniki: TypeError: t | blok(None) =
tarjimon, KeyError
asli: LookupError: topilmadi: 'id' ← KeyError | blok(None)
bizniki: LookupError: topilmadi: 'id' ← KeyError | blok(None) =
yieldsiz
asli: RuntimeError: generator didn't yield | -
bizniki: RuntimeError: generator didn't yield | - =
ikki_yield, xatosiz
asli: RuntimeError: generator didn't stop | blok(1)
bizniki: RuntimeError: generator didn't stop | blok(1) =
oddiy, StopIteration
asli: StopIteration: s | kirdi → blok(resurs) → tozaladi
bizniki: StopIteration: s | kirdi → blok(resurs) → tozaladi =
mos kelgan holatlar: 9/9
=== ⚠️ Bir martalik: bir obyekt bilan ikki marta with ===
AttributeError: '_GeneratorContextManager' object has no attribute 'args'
turi: _GeneratorContextManager, bazalari: ['_GeneratorContextManagerBase', 'AbstractContextManager', 'ABC', 'ContextDecorator']Nima ko'rsatdi: 2.1, 2.2, 2.3, 2.4-bo'limlar.
Misol 2 — Menejer dekorator sifatida va kichik vositalar
"""ContextDecorator, @contextmanager dekorator sifatida, nullcontext, suppress va ExceptionGroup."""
import contextlib
import io
import threading
from collections import Counter
statistika = Counter()
@contextlib.contextmanager
def kuzat(nom):
statistika[f"{nom}:boshlandi"] += 1
try:
yield
except Exception:
statistika[f"{nom}:xato"] += 1
raise
else:
statistika[f"{nom}:muvaffaqiyat"] += 1
print("=== 1. Bitta menejer — with va dekorator shaklida ===")
with kuzat("import"):
pass
@kuzat("hisobot")
def hisobot_tuz(oy):
if oy > 12:
raise ValueError(f"oy noto'g'ri: {oy}")
return f"{oy}-oy hisoboti"
for oy in (3, 7, 13):
try:
print(f" {hisobot_tuz(oy)}")
except ValueError as e:
print(f" ❌ {e}")
print(f" {dict(sorted(statistika.items()))}")
print(" ⭐ dekorator har chaqiruvda yangi generator yaratdi — 3 marta ishladi")
print("\n=== 2. Sinf asosida ContextDecorator — bitta nusxa, holat stekda ===")
class Chuqurlik(contextlib.ContextDecorator):
def __init__(self):
self.stek = []
def __enter__(self):
self.stek.append(len(self.stek) + 1)
return self
def __exit__(self, *istisno):
self.stek.pop()
return False
chuqurlik = Chuqurlik()
@chuqurlik
def faktorial(n):
if n <= 1:
print(f" eng chuqur nuqtada stek: {chuqurlik.stek}")
return 1
return n * faktorial(n - 1)
print(f" 5! = {faktorial(5)}, keyin stek: {chuqurlik.stek}")
print(f" _recreate_cm() is self: {chuqurlik._recreate_cm() is chuqurlik}")
print("\n=== 3. nullcontext — shartli menejer ===")
def yoz(qatorlar, parallel):
qulf = threading.Lock()
kontekst = qulf if parallel else contextlib.nullcontext()
bufer = io.StringIO()
for q in qatorlar:
with kontekst:
bufer.write(q)
return f"{type(kontekst).__name__}: {bufer.getvalue()!r}"
print(f" {yoz(['a', 'b'], parallel=True)}")
print(f" {yoz(['a', 'b'], parallel=False)}")
with contextlib.nullcontext({"sozlama": 1}) as s:
print(f" nullcontext(qiymat) as s → {s}")
print("\n=== 4. suppress: oddiy va ExceptionGroup bilan ===")
kesh = {}
with contextlib.suppress(KeyError):
del kesh["yo'q kalit"]
print(" bu qator bajarilmaydi")
print(" KeyError yutildi, blokning qolgani o'tkazib yuborildi")
def tekshir(yozuv):
xatolar = []
if not yozuv.get("ism"):
xatolar.append(ValueError("ism bo'sh"))
yosh = yozuv.get("yosh")
if not isinstance(yosh, int):
xatolar.append(TypeError("yosh butun son emas"))
elif yosh < 0:
xatolar.append(ValueError("yosh manfiy"))
if xatolar:
raise ExceptionGroup("yozuv yaroqsiz", xatolar)
for yozuv in ({"ism": "", "yosh": -5}, {"ism": "", "yosh": "o'ttiz"}):
try:
with contextlib.suppress(ValueError):
tekshir(yozuv)
print(f" {yozuv}: hamma xato ValueError edi — jim o'tdi")
except ExceptionGroup as eg:
print(f" {yozuv}: qoldi → {eg.exceptions}")
print("\n=== 5. AbstractContextManager ===")
class Oddiy(contextlib.AbstractContextManager):
def __exit__(self, *istisno):
return None
o = Oddiy()
with o as x:
print(f" __enter__ meros bo'yicha self qaytardi: {x is o}")
class Begona:
def __enter__(self):
return self
def __exit__(self, *a):
return None
print(f" merossiz sinf isinstance: {isinstance(Begona(), contextlib.AbstractContextManager)}")
print(f" fayl isinstance: {isinstance(io.StringIO(), contextlib.AbstractContextManager)}")
try:
class Chala(contextlib.AbstractContextManager):
pass
Chala()
except TypeError as e:
print(f" __exit__ siz: TypeError: {e}")Natijaning muhim qismi:
=== 1. Bitta menejer — with va dekorator shaklida ===
3-oy hisoboti
7-oy hisoboti
❌ oy noto'g'ri: 13
{'hisobot:boshlandi': 3, 'hisobot:muvaffaqiyat': 2, 'hisobot:xato': 1, 'import:boshlandi': 1, 'import:muvaffaqiyat': 1}
⭐ dekorator har chaqiruvda yangi generator yaratdi — 3 marta ishladi
=== 2. Sinf asosida ContextDecorator — bitta nusxa, holat stekda ===
eng chuqur nuqtada stek: [1, 2, 3, 4, 5]
5! = 120, keyin stek: []
_recreate_cm() is self: True
=== 3. nullcontext — shartli menejer ===
lock: 'ab'
nullcontext: 'ab'
nullcontext(qiymat) as s → {'sozlama': 1}
=== 4. suppress: oddiy va ExceptionGroup bilan ===
KeyError yutildi, blokning qolgani o'tkazib yuborildi
{'ism': '', 'yosh': -5}: hamma xato ValueError edi — jim o'tdi
{'ism': '', 'yosh': "o'ttiz"}: qoldi → (TypeError('yosh butun son emas'),)
=== 5. AbstractContextManager ===
__enter__ meros bo'yicha self qaytardi: True
merossiz sinf isinstance: True
fayl isinstance: True
__exit__ siz: TypeError: Can't instantiate abstract class Chala without an implementation for abstract method '__exit__'Nima ko'rsatdi: 2.4, 2.7-bo'limlar.
Misol 3 — ExitStack chuqur
"""enter_context, callback, push, istisno uzatish, xatolar zanjiri va pop_all."""
from contextlib import ExitStack
class Resurs:
ochiq = set()
def __init__(self, nom, buziq=False):
self.nom, self.buziq = nom, buziq
def __enter__(self):
if self.buziq:
raise ConnectionError(f"{self.nom} ochilmadi")
Resurs.ochiq.add(self.nom)
print(f" ochildi {self.nom}")
return self
def __exit__(self, tur, qiymat, tb):
Resurs.ochiq.discard(self.nom)
print(f" yopildi {self.nom} (tur={tur.__name__ if tur else None})")
return False
print("=== 1. Uch xil yozuv va LIFO tartib ===")
with ExitStack() as s:
s.enter_context(Resurs("A"))
s.callback(print, " callback: vaqtincha fayl o'chirildi")
@s.push
def kuzatuvchi(tur, qiymat, tb):
print(f" push: istisno ma'lumoti = {tur.__name__ if tur else None}")
return False
s.enter_context(Resurs("B"))
print(" --- blok tugadi ---")
print("\n=== 2. Istisno uzatilishi va yutish ===")
try:
with ExitStack() as s:
s.enter_context(Resurs("tashqi"))
@s.push
def yutuvchi(tur, qiymat, tb):
print(f" yutuvchi oldi: {tur.__name__ if tur else None}")
return tur is not None and issubclass(tur, KeyError)
s.enter_context(Resurs("ichki"))
raise KeyError("kalit")
print(" ⭐ KeyError yutildi: 'ichki' uni ko'rdi, 'tashqi' esa tur=None oldi")
except KeyError:
print(" yutilmadi")
print("\n=== 3. Yozuvlar o'zi xato bersa — hammasi chaqiriladi, zanjir saqlanadi ===")
def yopuvchi(nom, xato=None):
def f(*istisno):
print(f" {nom} chaqirildi")
if xato:
raise xato
return f
try:
with ExitStack() as s:
s.push(yopuvchi("1-yozuv"))
s.push(yopuvchi("2-yozuv", OSError("disk")))
s.push(yopuvchi("3-yozuv", TimeoutError("tarmoq")))
raise ValueError("asl xato")
except Exception as e:
zanjir = []
while e is not None:
zanjir.append(repr(e))
e = e.__context__
print(" zanjir: " + " ← ".join(zanjir))
print("\n=== 4. ⭐ pop_all: hammasi ochilsa — egalikni topshirish ===")
def ulan_hammasi(nomlar, buziq=None):
with ExitStack() as s:
resurslar = [s.enter_context(Resurs(n, buziq=(n == buziq))) for n in nomlar]
egasi = s.pop_all()
return resurslar, egasi
resurslar, egasi = ulan_hammasi(["db", "kesh", "navbat"])
print(f" funksiyadan qaytgach ochiq: {sorted(Resurs.ochiq)}")
print(" ... ilova ishlayapti ...")
egasi.close()
print(f" egasi.close() dan keyin ochiq: {sorted(Resurs.ochiq)}")
print("\n=== 5. pop_all: biri ochilmasa — ochilganlari yopiladi ===")
try:
ulan_hammasi(["db", "kesh", "navbat"], buziq="navbat")
except ConnectionError as e:
print(f" ❌ {e}; ochiq qolgan: {sorted(Resurs.ochiq)}")
print("\n=== 6. Menejer bo'lmagan obyekt ===")
try:
with ExitStack() as s:
s.enter_context(open)
except TypeError as e:
print(f" TypeError: {e}")Natijaning muhim qismi:
=== 1. Uch xil yozuv va LIFO tartib ===
ochildi A
ochildi B
--- blok tugadi ---
yopildi B (tur=None)
push: istisno ma'lumoti = None
callback: vaqtincha fayl o'chirildi
yopildi A (tur=None)
=== 2. Istisno uzatilishi va yutish ===
ochildi tashqi
ochildi ichki
yopildi ichki (tur=KeyError)
yutuvchi oldi: KeyError
yopildi tashqi (tur=None)
⭐ KeyError yutildi: 'ichki' uni ko'rdi, 'tashqi' esa tur=None oldi
=== 3. Yozuvlar o'zi xato bersa — hammasi chaqiriladi, zanjir saqlanadi ===
3-yozuv chaqirildi
2-yozuv chaqirildi
1-yozuv chaqirildi
zanjir: OSError('disk') ← TimeoutError('tarmoq') ← ValueError('asl xato')
=== 4. ⭐ pop_all: hammasi ochilsa — egalikni topshirish ===
ochildi db
ochildi kesh
ochildi navbat
funksiyadan qaytgach ochiq: ['db', 'kesh', 'navbat']
... ilova ishlayapti ...
yopildi navbat (tur=None)
yopildi kesh (tur=None)
yopildi db (tur=None)
egasi.close() dan keyin ochiq: []
=== 5. pop_all: biri ochilmasa — ochilganlari yopiladi ===
ochildi db
ochildi kesh
yopildi kesh (tur=ConnectionError)
yopildi db (tur=ConnectionError)
❌ navbat ochilmadi; ochiq qolgan: []
=== 6. Menejer bo'lmagan obyekt ===
TypeError: 'builtins.builtin_function_or_method' object does not support the context manager protocolNima ko'rsatdi: 2.5, 2.6-bo'limlar.
Misol 4 — Amaliy: CLI dasturi uchun sinov muhiti
Kichik CLI dasturini testlaymiz. U muhit o'zgaruvchisini o'qiydi, joriy papkaga fayl yozadi, ekranga chop etadi va modul sozlamasiga qaraydi. Har test toza muhitda ishlashi va keyin hamma narsa tiklanishi kerak — istalgan kombinatsiyada.
"""ExitStack + @contextmanager + chdir + redirect_stdout: qayta tiklanadigan sinov muhiti."""
import contextlib
import io
import os
import tempfile
from pathlib import Path
from types import SimpleNamespace
SOZLAMA = SimpleNamespace(batafsil=False, format="txt")
def dastur(argv):
"""Sinovdan o'tkaziladigan kichik CLI: hisobot yozadi."""
til = os.environ.get("HISOBOT_TIL", "uz")
if not argv:
raise SystemExit(2)
sarlavha = {"uz": "Hisobot", "en": "Report"}[til]
fayl = Path(f"{argv[0]}.{SOZLAMA.format}")
fayl.write_text(f"{sarlavha}: {', '.join(argv[1:])}\n", encoding="utf-8")
print(f"{sarlavha} yozildi: {fayl}")
if SOZLAMA.batafsil:
print(f" {len(argv) - 1} ta band")
return 0
@contextlib.contextmanager
def muhit(**qiymatlar):
eski = {k: os.environ.get(k) for k in qiymatlar}
os.environ.update(qiymatlar)
try:
yield
finally:
for k, v in eski.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
@contextlib.contextmanager
def sinov_muhiti(*, env=None, sozlama=None, stdout=True):
"""Kerakli qismlarni dinamik yig'adi; har biri o'z tozalashini ExitStack ga qo'shadi."""
with contextlib.ExitStack() as s:
papka = Path(s.enter_context(tempfile.TemporaryDirectory()))
s.enter_context(contextlib.chdir(papka))
if env:
s.enter_context(muhit(**env))
for nom, qiymat in (sozlama or {}).items():
s.callback(setattr, SOZLAMA, nom, getattr(SOZLAMA, nom))
setattr(SOZLAMA, nom, qiymat)
chiqish = io.StringIO() if stdout else None
s.enter_context(contextlib.redirect_stdout(chiqish) if stdout else contextlib.nullcontext())
yield SimpleNamespace(papka=papka, chiqish=chiqish)
def holat():
return (Path.cwd(), os.environ.get("HISOBOT_TIL"), SOZLAMA.batafsil, SOZLAMA.format)
asl = holat()
natijalar = []
print("=== 1. Oddiy test ===")
with sinov_muhiti() as m:
kod = dastur(["yanvar", "savdo", "xarajat"])
fayllar = sorted(p.name for p in m.papka.iterdir())
matn = (m.papka / "yanvar.txt").read_text(encoding="utf-8")
print(f" kod={kod}, fayllar={fayllar}")
print(f" fayl: {matn!r}")
print(f" stdout ushlandi: {m.chiqish.getvalue()!r}")
natijalar.append(holat() == asl)
print("\n=== 2. Muhit va sozlama o'zgartirilgan test ===")
with sinov_muhiti(env={"HISOBOT_TIL": "en"}, sozlama={"batafsil": True, "format": "md"}) as m:
dastur(["fevral", "a", "b", "c"])
ichki_holat = (os.environ["HISOBOT_TIL"], SOZLAMA.batafsil, SOZLAMA.format)
fayllar = sorted(p.name for p in m.papka.iterdir())
print(f" ichkarida: HISOBOT_TIL, batafsil, format = {ichki_holat}")
print(f" fayllar={fayllar}")
print(f" stdout: {m.chiqish.getvalue().splitlines()}")
natijalar.append(holat() == asl)
print("\n=== 3. Dastur xato bilan tugaydi — muhit baribir tiklanadi ===")
try:
with sinov_muhiti(env={"HISOBOT_TIL": "ru"}, sozlama={"format": "csv"}) as m:
dastur(["mart"])
except KeyError as e:
print(f" ❌ KeyError: {e} — 'ru' tili qo'llab-quvvatlanmaydi")
print(f" vaqtincha papka o'chirildimi: {not m.papka.exists()}")
natijalar.append(holat() == asl)
print("\n=== 4. SystemExit ham BaseException — tozalash baribir ===")
try:
with sinov_muhiti(sozlama={"batafsil": True}):
dastur([])
except SystemExit as e:
print(f" SystemExit({e.code})")
natijalar.append(holat() == asl)
print("\n=== 5. stdout ushlanmasin — nullcontext ===")
with sinov_muhiti(stdout=False) as m:
dastur(["aprel", "x"])
print(f" m.chiqish = {m.chiqish}")
natijalar.append(holat() == asl)
print(f"\n har testdan keyin holat tiklandimi: {natijalar}")Natijaning muhim qismi:
=== 1. Oddiy test ===
kod=0, fayllar=['yanvar.txt']
fayl: 'Hisobot: savdo, xarajat\n'
stdout ushlandi: 'Hisobot yozildi: yanvar.txt\n'
=== 2. Muhit va sozlama o'zgartirilgan test ===
ichkarida: HISOBOT_TIL, batafsil, format = ('en', True, 'md')
fayllar=['fevral.md']
stdout: ['Report yozildi: fevral.md', ' 3 ta band']
=== 3. Dastur xato bilan tugaydi — muhit baribir tiklanadi ===
❌ KeyError: 'ru' — 'ru' tili qo'llab-quvvatlanmaydi
vaqtincha papka o'chirildimi: True
=== 4. SystemExit ham BaseException — tozalash baribir ===
SystemExit(2)
=== 5. stdout ushlanmasin — nullcontext ===
Hisobot yozildi: aprel.txt
m.chiqish = None
har testdan keyin holat tiklandimi: [True, True, True, True, True]Nima ko'rsatdi: 2.2, 2.5, 2.7-bo'limlar — bir necha mustaqil tozalash bitta ExitStack da, shartli qismlar bilan.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"yield dan keyingi kod har doim bajariladi" |
Faqat blok xatosiz tugasa; aks holda finally kerak |
"Generator ichidagi except istisnoni faqat ko'radi" |
raise qilinmasa — istisno yutiladi |
"@contextmanager obyektini qayta ishlatsa bo'ladi" |
Bir martalik; dekorator shaklida esa har chaqiruvda yangisi yaratiladi |
"ContextDecorator ham har chaqiruvda yangi nusxa yaratadi" |
Oddiy sinf uchun self ni qaytaradi — bitta nusxa |
"ExitStack.callback istisnoni ko'radi" |
Ko'rmaydi; buning uchun push |
"ExitStack dagi bitta yozuv xato bersa, qolganlari qoladi" |
Hammasi chaqiriladi, xatolar zanjirga bog'lanadi |
"suppress ExceptionGroup ni to'liq yutadi" |
Faqat mos keladigan qismini; qolgani ko'tariladi |
"isinstance(x, AbstractContextManager) meros talab qiladi" |
Ikki metodi bor har qanday obyekt uchun True |
6. Keng tarqalgan xatolar va yechimlari
1. yield atrofida finally yo'q
@contextmanager
def qulf_ol(q):
q.acquire()
yield # ❌ blokda xato — qulf abadiy band
q.release()
@contextmanager
def qulf_ol(q):
q.acquire()
try:
yield
finally:
q.release() # ✅2. Resurs olish try ichida
@contextmanager
def fayl(yol):
try:
f = open(yol) # ❌ ochilmasa, finally da f yo'q → UnboundLocalError asl xatoni yashiradi
yield f
finally:
f.close()
@contextmanager
def fayl(yol):
f = open(yol) # ✅ try dan oldin
try:
yield f
finally:
f.close()3. Kuzatish uchun except yozib, raise ni unutish
@contextmanager
def jurnal():
try:
yield
except Exception as e:
log.error(e) # ❌ xato yo'qoldi
except Exception as e:
log.error(e)
raise # ✅4. Shartli yield
@contextmanager
def kesh(yoqilgan):
if yoqilgan:
yield Kesh() # ❌ yoqilgan=False → generator didn't yield
@contextmanager
def kesh(yoqilgan):
yield Kesh() if yoqilgan else None # ✅ har doim bitta yield5. @contextmanager obyektini saqlab qayta ishlatish
TRANZAKSIYA = tranzaksiya(db) # ❌ modul darajasida bitta obyekt
with TRANZAKSIYA: ...
with TRANZAKSIYA: ... # AttributeError
def ish():
with tranzaksiya(db): ... # ✅ har safar yangi6. ExitStack da callback bilan istisnoga javob berishga urinish
s.callback(rollback) # ❌ har doim chaqiriladi, xatosiz bo'lsa ham
@s.push
def faqat_xatoda(tur, qiymat, tb): # ✅
if tur is not None:
rollback()7. pop_all siz resurslarni funksiyadan qaytarish
def ochish(yollar):
with ExitStack() as s:
return [s.enter_context(open(y)) for y in yollar] # ❌ qaytishda hammasi yopiladi
def ochish(yollar):
with ExitStack() as s:
fayllar = [s.enter_context(open(y)) for y in yollar]
return fayllar, s.pop_all() # ✅8. redirect_stdout ni kutubxona yoki iplarda ishlatish
def hisobla():
with redirect_stdout(io.StringIO()): # ❌ boshqa iplarning print() lari ham yo'qoladi
shovqinli_funksiya() Funksiyaga file= parametri bering yoki logging ishlating; redirect_stdout — skriptlar va testlar uchun.
7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 9.3-dars (o'tilgan):
contextlibvositalaridan foydalanish - 10.3-dars (o'tilgan):
throw(),close(), PEP 479 —@contextmanagerning poydevori - 10.6-dars (o'tilgan):
__enter__/__exit__protokoli, qayta kiriladigan menejerlar - 10.8–10.11-darslar: dekoratorlar —
ContextDecoratorqanday funksiyani o'raydi - 14-qism:
asynccontextmanager,AsyncExitStack,aclosing - 17-qism: pytest fixture'lari
yieldbilan — xuddi shu g'oya,ExitStackuslubidagi tozalash - 20-qism: FastAPI
lifespanvayieldli bog'liqliklar
8. Eng yaxshi amaliyotlar
@contextmanagerdayielddoimtry/finallyichida. Istisnosiz tozalash — istisno bo'lganda tozalanmaslik degani.Resursni
trydan oldin oling. Olinmagan resursni yopishga urinmang.Generator ichida
exceptbo'lsa —raiseyoki ongli yutish. Ikkinchisini izoh bilan belgilang.Holatsiz, oddiy menejer —
@contextmanager; holatli, qayta ishlatiladigan yoki tez ishlashi kerak — sinf.Soni yoki tarkibi ish vaqtida aniqlanadigan resurslar uchun
ExitStack. Ichma-ichwithva shartliiflar o'rniga.Resurslarni topshirish kerak bo'lsa —
pop_all().callback— tozalash,push— istisnoga javob.Global holatni o'zgartiruvchi vositalar (
chdir,redirect_stdout) — skript va testlarda. Kutubxona va ko'p ipli kodda emas.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
from contextlib import contextmanager, ExitStack, suppress, nullcontext
@contextmanager
def g(yut=False):
print("a", end=" ")
try:
yield "q"
except KeyError:
print("k", end=" ")
if not yut:
raise
finally:
print("f", end=" ")
1. with g() as x:
print(x, end=" ")
print()
2. with g(yut=True):
raise KeyError
print("davom")
3. try:
with g():
raise KeyError
except KeyError:
print("tashqi")
4. try:
with g():
raise ValueError
except ValueError:
print("VE")
5. @contextmanager
def h():
yield
yield
try:
with h():
pass
except RuntimeError as e:
print(e)
6. @contextmanager
def h():
return
yield
try:
with h():
pass
except RuntimeError as e:
print(e)
7. with ExitStack() as s:
s.callback(print, 1, end=" ")
s.callback(print, 2, end=" ")
print(0, end=" ")
print()
8. with nullcontext(5) as n:
print(n + 1)
9. with suppress(LookupError):
print("a", end=" ")
[][1]
print("b", end=" ")
print("c")
10. try:
with suppress(ValueError):
raise ExceptionGroup("g", [ValueError(1), KeyError(2)])
except* KeyError as eg:
print(eg.exceptions)
11. @g()
def ish():
print("ish", end=" ")
ish()
ish()
print()
12. m = g()
with m:
pass
try:
with m:
pass
except AttributeError:
print("bir martalik")Javoblar
a q f—yieldqiymatiasga tushdia k f davom—raiseqilinmadi, istisno yutildia k f tashqi—exceptushladi, lekin qayta ko'tardia f VE—except KeyErrormos kelmadi,finallybajarildigenerator didn't stop— ikkinchiyieldga yetib bordigenerator didn't yield0 2 1—callbacklar teskari tartibda6—nullcontextargumentini qaytaradia c—[][1]dagiIndexError(LookupErroravlodi) yutildi,bbajarilmadi(KeyError(2),)—suppressguruhdanValueErrorni olib tashladia ish f a ish f— dekorator har chaqiruvda yangi generator yaratadia f bir martalik— ikkinchiwithAttributeErrorberdi
Vazifa 2: Xatolarni tuzating
1. @contextmanager
def vaqtincha_fayl(yol):
try:
f = open(yol, "w")
yield f
finally:
f.close()
os.remove(yol)
2. @contextmanager
def tranzaksiya(db):
db.begin()
try:
yield db
except Exception:
db.rollback()
else:
db.commit()
3. @contextmanager
def ixtiyoriy_qulf(qulf):
if qulf is not None:
with qulf:
yield
4. def fayllarni_och(yollar):
with ExitStack() as s:
return [s.enter_context(open(y)) for y in yollar]
5. with ExitStack() as s:
s.callback(db.rollback)
ish(db)
db.commit()Javoblar
1. @contextmanager
def vaqtincha_fayl(yol):
f = open(yol, "w") # try dan oldin — ochilmasa, f yo'q
try:
yield f
finally:
f.close()
os.remove(yol)
2. except Exception:
db.rollback()
raise # aks holda xato yutiladi
3. @contextmanager
def ixtiyoriy_qulf(qulf):
with qulf if qulf is not None else nullcontext():
yield # har doim aniq bitta yield
4. def fayllarni_och(yollar):
with ExitStack() as s:
fayllar = [s.enter_context(open(y)) for y in yollar]
return fayllar, s.pop_all() # egalik chaqiruvchiga
5. with ExitStack() as s:
@s.push
def xatoda_rollback(tur, qiymat, tb):
if tur is not None:
db.rollback() # callback commit dan keyin ham rollback qilardi
ish(db)
db.commit()Vazifa 3: @contextmanager ni to'liq yozing
Misol 1 dagi GenMenejer ni kengaytiring:
ContextDecoratordan meros oling va_recreate_cmni yozing — dekorator sifatida ishlasin- Qayta
withda tushunarli xabar:RuntimeError("bu menejer bir martalik; har safar funksiyani qayta chaqiring") functools.update_wrapperbilan yaratuvchi funksiyaga asl nom va hujjatni ko'chiring- Misol 1 dagi 9 holat va dekorator shakli uchun testlar yozing — natijalar asli bilan bir xil bo'lsin
Vazifa 4: ExitStack siz ExitStack
MeningStekim sinfini yozing: enter_context, callback, push, pop_all, close.
- LIFO tartib
- Yutilgan istisno keyingi yozuvlarga
(None, None, None)bo'lib borsin - Yozuv xato bersa, qolganlari baribir chaqirilsin va xatolar
__context__zanjirida bo'lsin - Misol 3 dagi 6 bo'limni
MeningStekimbilan ishga tushirib, natijani solishtiring
Vazifa 5: Resurslar hovuzi
ulanishlar_hovuzi(manzillar, kamida) funksiyasini yozing:
- Har bir manzilga ulanishga urinadi; ulanmaganlarini o'tkazib yuboradi
- Kamida
kamidata ulanish bo'lmasa — ochilganlarini yopib,ExceptionGroupbilan barcha ulanish xatolarini ko'taradi - Yetarli bo'lsa —
pop_allbilanHovuzobyektiga topshiradi, u context manager bo'lsin Hovuz.ol()— navbatdagi ulanishni qaytaradi (with hovuz.ol() as u:), ishlatib bo'lgach hovuzga qaytaradi
Vazifa 6: Pytest uslubidagi fixture'lar
Kichik test yuritgich yozing:
@fixture—yieldli generator funksiyani ro'yxatga oladi- Test funksiyasi parametr nomlari bo'yicha fixture'larni oladi; fixture boshqa fixture'ga bog'liq bo'lishi mumkin
- Hammasi bitta
ExitStackda ochiladi va test tugagach teskari tartibda yopiladi - Fixture o'rnatilishida xato bo'lsa — test "ERROR", testning o'zida — "FAIL" deb belgilansin; ikkala holatda ham ochilgan fixture'lar yopilsin
Vazifa 7: O'ylash
Context manager'ni sinf bilan ham, @contextmanager bilan ham yozish mumkin. Ular faqat sintaksis bilan farq qiladimi? Qachon qaysi birini tanlaysiz — ishlash tezligi, holat, qayta ishlatish, xatolarni tuzatish va o'qilishi nuqtai nazaridan.
Javob
Qisqa javob: yo'q, farq faqat sintaksisda emas. @contextmanager — generator ustidagi adapter: u bir martalik, sinfdan sezilarli sekinroq, istisnoni throw() orqali uzatadi va holatni generator freymida yashiradi. Evaziga "oldin — yield — keyin" ketma-ketligini bitta funksiyada o'qiladigan qilib yozadi. Oddiy, holatsiz, kam chaqiriladigan menejerlar uchun — @contextmanager; holatli, qayta ishlatiladigan, tez-tez chaqiriladigan yoki ommaviy API qismi bo'lgan menejerlar uchun — sinf.
1. O'qilishi
@contextmanager
def vaqtincha_papka():
yol = mkdtemp()
try:
yield Path(yol)
finally:
rmtree(yol)class VaqtinchaPapka:
def __enter__(self):
self.yol = mkdtemp()
return Path(self.yol)
def __exit__(self, tur, qiymat, tb):
rmtree(self.yol)
return FalseGenerator variantida olish va qo'yib yuborish yonma-yon, oradagi lokal o'zgaruvchi (yol) avtomatik saqlanadi. Sinfda esa u self ga ko'chadi va ikki metod orasida bo'linadi. Qisqa menejerlar uchun generator ancha tabiiy o'qiladi.
2. Tezlik
Python 3.14 da bo'sh with blokini o'lchasak (chaqiruv narxi bilan birga, bu kompyuterda):
| Usul | Taxminiy vaqt |
|---|---|
try/finally |
~0.04 µs |
| Sinf | ~0.2 µs |
nullcontext() |
~0.3 µs |
@contextmanager |
~1.4 µs |
ExitStack + callback |
~2.5 µs |
@contextmanager sinfdan taxminan 6–7 marta sekinroq: har with da generator obyekti, adapter obyekti yaratiladi, next() ikki marta chaqiriladi. Bitta fayl ochish yoki tranzaksiya uchun bu farq ahamiyatsiz — resursning o'zi millisekundlar oladi. Lekin sikl ichida million marta chaqiriladigan qulf yoki o'lchov menejeri uchun farq seziladi. Shuning uchun threading.Lock, decimal.localcontext kabi standart menejerlar sinf (yoki C) da yozilgan.
3. Holat va qayta ishlatish
@contextmanagerobyekti bir martalik — generator tugagach qayta ishga tushmaydi- U qayta kiriladigan bo'la olmaydi: har
withuchun alohida generator kerak - Holat generator freymida yashirin — tashqaridan ko'rib bo'lmaydi (masalan, Taymer natijasi uchun
yieldqilingan obyektga yozish kerak bo'ladi)
Sinf esa:
- qayta ishlatiladigan bo'lishi mumkin (
Lock) - qayta kiriladigan bo'lishi mumkin (holat stekda, 10.6-dars)
- holatini atributlar orqali ochiq ko'rsatadi (
t.soniya,tx.jurnal) - boshqa metodlarga ega bo'lishi mumkin (
hovuz.ol(),tx.tasdiqdan_keyin(...))
4. Istisnolar va xatolarni tuzatish
Sinfda __exit__ istisnoni argument sifatida oladi va qaror qabul qiladi — hech narsa ko'tarilmaydi. Generatorda istisno yield qatorida haqiqatan ko'tariladi:
- traceback'da qo'shimcha
contextlibfreymlari paydo bo'ladi exceptyozish tabiiy, lekinraiseni unutish — jim yutishfinallyni unutish — jim tozalanmaslik- xato xabarlari (
generator didn't stop,no attribute 'args') kodga emas, adapterning ichki holatiga ishora qiladi
Sinfda __exit__ qaytish qiymati aniq ko'rinadi, istisnolarni tanlab boshqarish mantig'i issubclass bilan oshkora yoziladi.
5. Qachon qaysi biri
| Vaziyat | Tanlov |
|---|---|
| Bir martalik, holatsiz "oldin/keyin" | @contextmanager |
| Xato turlarini tarjima qilish | @contextmanager (try/except/raise from) |
| Test yordamchilari, skriptlar | @contextmanager |
| Tez-tez chaqiriladigan (siklda) | Sinf |
| Qayta ishlatiladigan yoki qayta kiriladigan | Sinf |
| Blokdan keyin natija o'qiladi yoki qo'shimcha metodlar kerak | Sinf |
| Ommaviy kutubxona API si | Sinf (aniq xatolar, hujjatlashtirish, tur annotatsiyasi) |
| Soni ish vaqtida aniqlanadigan resurslar | ExitStack — ikkalasini birlashtiradi |
6. Aralash yondashuv
Ko'pincha eng yaxshisi — ikkalasi birga: resursning o'zi sinf (Ulanish, Hovuz), undan foydalanish naqshlari esa @contextmanager (tranzaksiya(ulanish), domen_xatolari()). Pytest ham shunday: fixture'lar yield li generatorlar, ular ichida esa sinf asosidagi resurslar ishlatiladi.
7. Xulosa
@contextmanager— generatorni protokolga moslovchi adapter, yangi imkoniyat emas- U o'qilishi oson, lekin bir martalik, sekinroq va xatolarni
throw()orqali uzatadi - Sinf holat, qayta ishlatish, qayta kirish va tezlik kerak bo'lganda ustun
yieldatrofidagitry/finallyvaexceptdagiraise— generator variantining eng ko'p unutiladigan qismlari- Amalda ikkalasi birga ishlatiladi: resurs — sinf, foydalanish naqshi — generator
Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.
Xulosa
Bu darsda contextlib vositalarining ichki ishlashini o'rgandik.
Eng muhim uch fikr:
@contextmanagergeneratorni protokolga moslaydi.yieldgacha —__enter__,yieldqiymati —as, blokdagi istisnoyieldqatoridathrow()bilan ko'tariladi. Shuning uchun tozalash albattafinallyda,exceptbo'lsa —raisebilan bo'lishi kerak; aks holda tozalash jim o'tkazib yuboriladi yoki xato jim yutiladi. Obyekt bir martalik, lekin dekorator sifatida har chaqiruvda yangisi yaratiladi.ExitStack— dasturlash yo'li bilan qurilganwith.enter_contextmenejerlar uchun,callbackoddiy tozalash uchun,pushistisnoga javob uchun. Chiqish teskari tartibda, yozuvlardan biri xato bersa ham qolganlari chaqiriladi va xatolar zanjirga bog'lanadi.pop_all()bir nechta resursni xavfsiz olib, egalikni boshqa obyektga topshirish imkonini beradi.Kichik vositalarning chegarasini biling.
nullcontextshartli menejer uchun,suppressExceptionGroupdan faqat mos qismini olib tashlaydi,AbstractContextManagertekshiruvi meros talab qilmaydi.chdirvaredirect_stdoutbutun jarayonning global holatini o'zgartiradi — ular test va skriptlar uchun, kutubxona va ko'p ipli kod uchun emas.
Keyingi darsda dekoratorlarni boshlaymiz: funksiya — obyekt, uni o'rab, xatti-harakatini o'zgartiradigan funksiya qanday yoziladi va @ sintaksisi aslida nima qiladi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!