Mundarija (20)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Havolalar soni qachon o'zgaradi
- 2.2. Nol bo'lganda nima bo'ladi
- 2.3. sys.getrefcount va uning nozikliklari
- 2.4. Yashirin havolalar
- 2.5. __del__ qoidalari
- 2.6. Kim ushlab turibdi?
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Havolalar sonini kuzatish
- Misol 2 — __del__ qachon va qanday chaqiriladi
- Misol 3 — Yashirin havolalar va "kim ushlab turibdi"
- Misol 4 — Amaliy: obyektlar hayot davri kuzatuvchisi
- 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
11.4-dars: Reference counting
11-QISM — OBYEKT MODELI VA XOTIRA · 4-dars
1. Kirish va motivatsiya
11.1-darsda har obyekt sarlavhasida ob_refcnt — havolalar soni borligini ko'rdik. Endi shu son qanday o'zgarishini va nol bo'lganda nima bo'lishini o'rganamiz.
Veb-xizmatdan real hodisa. Har so'rov katta hisobot obyektini yaratadi va ish tugagach "tashlab yuboriladi". Lekin xotira sekin-asta o'sib, bir kunda 4 GB ga yetadi. Profiler hech qanday "sizish" topmaydi — barcha obyektlar qonuniy havolalarga ega. Sabab xatolar jurnalida edi:
oxirgi_xatolar = []
def sorovni_ishla(sorov):
hisobot = Hisobot.yarat(sorov) # 50 MB
try:
hisobot.hisobla()
except HisobXatosi as e:
oxirgi_xatolar.append(e) # "keyin ko'rib chiqamiz"
raiseIstisno obyekti __traceback__ orqali freymni, freym esa barcha lokal o'zgaruvchilarni — jumladan 50 MB lik hisobot ni — ushlab turadi. Ro'yxatdagi har bir xato bitta to'liq hisobotni tirik saqlaydi.
CPython obyektni havolalar soni nolga tushgan zahoti o'chiradi — deterministik va darhol. Bu kuchli kafolat: fayl del dan keyin yopiladi, xotira kutilmagan pauzalarsiz bo'shaydi. Lekin buning teskari tomoni ham bor: bitta yashirin havola obyektni cheksiz tirik ushlaydi. Qaysi amallar havola yaratishini bilish — xotira muammolarini topishning asosi.
Bu darsda:
- Havolalar soni qachon oshadi va kamayadi
- Nol bo'lganda: darhol o'chirish, zanjir bo'ylab tozalash,
__del__ sys.getrefcountva uning Python 3.14 dagi nozikliklari- Obyektni kutilganidan uzoq ushlab turuvchi yashirin havolalar
__del__ning qoidalari: bir marta, xatolar yutiladi, tirilish- Kim ushlab turibdi? —
gc.get_referrersbilan tekshirish
Sikllar (bir-biriga ishora qiluvchi obyektlar) va axlat yig'uvchi — 11.5-darsda.
2. Nazariya — chuqur tushuntirish
2.1. Havolalar soni qachon o'zgaradi
C darajasida har yangi havola Py_INCREF, har yo'qolgan havola Py_DECREF ni chaqiradi.
| Amal | Havola |
|---|---|
Nomga bog'lash: x = obj |
+1 (eski qiymatga −1) |
Konteynerga qo'yish: royxat.append(obj), d[k] = obj, (obj,) |
+1 |
Atribut: o.a = obj |
+1 |
| Funksiya argumenti — chaqiruv davomida | +1 (freym ichida) |
Closure hujayrasi, bog'langan metod (__self__), generator freymi |
+1 |
Istisnoning __traceback__ → freym → lokal o'zgaruvchilar |
Zanjir orqali |
del x, nomni qayta bog'lash, konteynerdan olib tashlash |
−1 |
| Funksiyadan chiqish — lokal o'zgaruvchilar | Har biriga −1 |
O'lmas obyektlar (None, kichik sonlar...) |
O'zgarmaydi (11.1-dars) |
2.2. Nol bo'lganda nima bo'ladi
Havolalar soni nolga tushgan paytda (keyingi qatorni kutmasdan):
- Obyektda
__del__bo'lsa — chaqiriladi - Obyekt o'z havolalarini bo'shatadi — ichidagi har bir obyektga
Py_DECREF - Ular ham nolga tushsa — zanjir bo'ylab xuddi shu jarayon
- Xotira ajratuvchiga qaytariladi (11.1-dars, 2.6)
Oqibatlari:
- Deterministik:
del katta_obyektdan keyin xotira darhol bo'shaydi (operatsion tizimga qaytishi shart emas) - Tartib: tashqi obyekt avval, keyin ichkilari
- Narxi zanjirda: 10 million elementli tuzilmani
delqilish — shu joyda sezilarli pauza - Sikllarda ishlamaydi:
a.b = b; b.a = a— hech biri nolga tushmaydi (11.5-dars)
Bu CPython xususiyati. PyPy va boshqa implementatsiyalar izlovchi (tracing) axlat yig'uvchi ishlatadi — ular obyektni keyinroq, noma'lum paytda o'chiradi. Resursni yopish uchun havolalar hisobiga tayanmang — with ishlating (10.6-dars).
2.3. sys.getrefcount va uning nozikliklari
sys.getrefcount(x) havolalar sonini qaytaradi, lekin:
| Holat | Natija |
|---|---|
| Global nom orqali | Haqiqiydan +1 (argument sifatida vaqtinchalik havola) |
| Python 3.14 da lokal nom orqali | Ko'pincha aniq — interpretator lokal o'zgaruvchini "qarz" sifatida uzatadi (+1 yo'q) |
| O'lmas obyekt | Maxsus katta son |
| Iplar ishlayotganda | Boshqa iplar o'zgartirishi mumkin |
Qoida: getrefcount ni mutlaq son sifatida emas, farq sifatida ishlating — "bu amaldan keyin nechta havola qo'shildi". Aniq qiymat uchun o'rganish maqsadida ctypes bilan ob_refcnt ni o'qish mumkin (11.1-dars).
2.4. Yashirin havolalar
Obyektni kutilganidan uzoq ushlab turadigan joylar:
| Manba | Qanday | Yechim |
|---|---|---|
| Saqlangan istisno | e.__traceback__ → freymlar → barcha lokal o'zgaruvchilar |
Faqat kerakli ma'lumotni saqlang: str(e), traceback.format_exception(e); yoki e.__traceback__ = None |
| To'xtatilgan generator | Generator freymi lokal o'zgaruvchilarni ushlaydi | gen.close(), oxirigacha iteratsiya |
| Closure | Hujayra butun obyektni ushlaydi (10.12-dars) | Faqat kerakli qiymatni oling |
| Bog'langan metod callback'da | obj.metod → __self__ |
weakref.WeakMethod |
| Kesh | lru_cache argument va natijalarni ushlaydi (10.13-dars) |
maxsize, cache_clear() |
| Global ro'yxatlar/registrlar | Plaginlar, kuzatuvchilar, "tarix" | WeakSet, WeakValueDictionary (11.10-dars) |
| Klass atributlari | Klass modul yashagan paytgacha tirik | Nusxa atributi |
| REPL | _ oxirgi natijani saqlaydi |
— |
| Freym obyektlari | sys._getframe(), inspect.currentframe() ni saqlash |
del frame yoki try/finally |
2.5. __del__ qoidalari
class Fayl:
def __del__(self):
print("yopildi")| Qoida | Tafsilot |
|---|---|
| Qachon | Havolalar soni nolga tushganda (yoki GC sikl topganda — 11.5) |
| Necha marta | Bir marta — obyekt "tirilsa" ham qayta chaqirilmaydi (PEP 442) |
| Xato bo'lsa | Tashqariga chiqmaydi — Exception ignored in: ... deb stderr ga yoziladi (sys.unraisablehook) |
| Tirilish | __del__ ichida self ni biror joyga saqlash obyektni tirik qoldiradi |
| Interpretator yopilganda | Chaqirilishi kafolatlanmagan; modul global o'zgaruvchilari allaqachon None bo'lishi mumkin |
| Tartib | Tashqi obyekt avval; sikldagi obyektlar orasida tartib aniqlanmagan |
Resurslar uchun __del__ o'rniga:
withva kontekst menejerlar — aniq, istisnolar bilan ishlaydi (10.6-dars)weakref.finalize(obj, funksiya, *args)—selfga havola saqlamaydi, interpretator yopilganda ham ishonchliroq chaqiriladi (11.10-dars)
2.6. Kim ushlab turibdi?
import gc
gc.get_referrers(obj) # obj ga ishora qiluvchi konteynerlar (GC kuzatadigan)
gc.get_referents(obj) # obj ishora qiladigan obyektlar- Natijada joriy freym va tekshiruv uchun yaratilgan ro'yxatlar ham chiqadi — ularni filtrlang
- Faqat GC kuzatadigan obyektlar (konteynerlar) ko'rinadi
- Tuzatish vositasi — ishlab chiqarish kodida ishlatmang (sekin)
Chuqurroq tahlil uchun uchinchi tomon objgraph kutubxonasi havolalar grafini rasm qilib chizadi; tracemalloc esa xotira qayerda ajratilganini ko'rsatadi (29-qism).
3. Tez ma'lumotnoma
O'zgarish
+1: nomga bog'lash, konteynerga qo'yish, atribut, argument, closure, bog'langan metod
−1: del, qayta bog'lash, konteynerdan olish, funksiyadan chiqish
0 → darhol: __del__ → ichkilarga DECREF → zanjir → xotirasys.getrefcount
global nom: haqiqiy + 1
3.14 lokal nom: ko'pincha haqiqiy (qarz)
o'lmas: maxsus son
→ farq sifatida ishlatingYashirin havolalar
saqlangan istisno → traceback → freym → lokallar
to'xtatilgan generator, closure, bog'langan metod, lru_cache, global ro'yxat, klass atributi__del__
bir marta (PEP 442) xato → "Exception ignored" yopilishda kafolat yo'q
resurs uchun: with / weakref.finalizeTekshirish
gc.get_referrers(obj) gc.get_referents(obj) weakref.ref(obj)() is None4. Batafsil misollar
Misol 1 — Havolalar sonini kuzatish
"""ob_refcnt ni aniq o'qish; har amal qancha havola qo'shadi; getrefcount ning global va lokal farqi."""
import ctypes
import sys
def refcnt(obj):
return ctypes.c_ssize_t.from_address(id(obj)).value
class Tugun:
def metod(self):
return self
print("=== 1. Qaysi amal havola qo'shadi ===")
obyekt = Tugun()
qadamlar = [("yaratildi", None)]
boshlangich = refcnt(obyekt)
saqlagichlar = []
def qadam(tavsif):
print(f" {tavsif:34} ob_refcnt = {refcnt(obyekt)} (+{refcnt(obyekt) - boshlangich})")
qadam("obyekt = Tugun()")
b = obyekt
qadam("b = obyekt")
royxat = [obyekt, obyekt]
qadam("royxat = [obyekt, obyekt]")
lugat = {"kalit": obyekt}
qadam("lugat['kalit'] = obyekt")
kortej = (obyekt,)
qadam("kortej = (obyekt,)")
boshqa = Tugun()
boshqa.havola = obyekt
qadam("boshqa.havola = obyekt")
bogl = obyekt.metod
qadam("bogl = obyekt.metod (__self__)")
closure = (lambda: obyekt)
qadam("lambda: obyekt (global — hujayra yo'q)")
def yasa(x):
return lambda: x
closure2 = yasa(obyekt)
qadam("yasa(obyekt) — closure hujayrasi")
del b, royxat, lugat, kortej, boshqa, bogl, closure2
qadam("hammasi del qilindi")
print("\n=== 2. sys.getrefcount: global va lokal ===")
print(f" global: ob_refcnt={refcnt(obyekt)}, sys.getrefcount={sys.getrefcount(obyekt)}")
def lokal_sinov():
lokal = Tugun()
return refcnt(lokal), sys.getrefcount(lokal)
print(f" lokal 3.14-bob: ob_refcnt={lokal_sinov()[0]}, sys.getrefcount={lokal_sinov()[1]}")
print(" ⚠️ getrefcount ni farq sifatida ishlating")
print("\n=== 3. Funksiya chaqiruvi davomida ===")
def ichkarida(x):
return refcnt(x)
oldin = refcnt(obyekt)
print(f" chaqiruvdan oldin {oldin}, chaqiruv ichida {ichkarida(obyekt)}, keyin {refcnt(obyekt)}")
print(" (tashqarida: global nom + refcnt() argumenti; ichkarida: global nom + x parametri — ikkalasida bittadan qo'shimcha)")
print("\n=== 4. Generator freymi ushlab turadi ===")
def generator(x):
yield x
yield x
oldin = refcnt(obyekt)
g = generator(obyekt)
next(g)
print(f" to'xtatilgan generator: +{refcnt(obyekt) - oldin}")
g.close()
print(f" g.close() dan keyin: +{refcnt(obyekt) - oldin}")Natijaning muhim qismi:
=== 1. Qaysi amal havola qo'shadi ===
obyekt = Tugun() ob_refcnt = 2 (+0)
b = obyekt ob_refcnt = 3 (+1)
royxat = [obyekt, obyekt] ob_refcnt = 5 (+3)
lugat['kalit'] = obyekt ob_refcnt = 6 (+4)
kortej = (obyekt,) ob_refcnt = 7 (+5)
boshqa.havola = obyekt ob_refcnt = 8 (+6)
bogl = obyekt.metod (__self__) ob_refcnt = 9 (+7)
lambda: obyekt (global — hujayra yo'q) ob_refcnt = 9 (+7)
yasa(obyekt) — closure hujayrasi ob_refcnt = 10 (+8)
hammasi del qilindi ob_refcnt = 2 (+0)
=== 2. sys.getrefcount: global va lokal ===
global: ob_refcnt=2, sys.getrefcount=2
lokal 3.14-bob: ob_refcnt=1, sys.getrefcount=1
⚠️ getrefcount ni farq sifatida ishlating
=== 3. Funksiya chaqiruvi davomida ===
chaqiruvdan oldin 2, chaqiruv ichida 2, keyin 2
(tashqarida: global nom + refcnt() argumenti; ichkarida: global nom + x parametri — ikkalasida bittadan qo'shimcha)
=== 4. Generator freymi ushlab turadi ===
to'xtatilgan generator: +1
g.close() dan keyin: +0Nima ko'rsatdi: 2.1, 2.3-bo'limlar. lambda: obyekt havola qo'shmadi — obyekt global nom, lambda uni chaqiruv paytida lug'atdan o'qiydi; yasa(obyekt) esa hujayra orqali havola saqladi.
Misol 2 — __del__ qachon va qanday chaqiriladi
"""del, qayta bog'lash, funksiyadan chiqish, konteynerdan olish, zanjir; __del__ dagi xato, tirilish."""
import sys
class R:
def __init__(self, nom):
self.nom = nom
def __del__(self):
print(f" 🗑 {self.nom}")
print("=== 1. Darhol o'chirish ===")
r = R("del bilan")
print(" del dan oldin")
del r
print(" del dan keyin")
r = R("birinchi")
r = R("ikkinchi")
print(" qayta bog'lashdan keyin")
del r
def funksiya():
lokal = R("lokal o'zgaruvchi")
print(" funksiya tugayapti")
funksiya()
print(" funksiyadan keyin")
royxat = [R("ro'yxat elementi")]
royxat.pop()
print(" pop dan keyin")
print("\n=== 2. Zanjir: tashqaridan ichkariga ===")
ota = R("ota")
ota.bola = R("bola")
ota.bola.nevara = R("nevara")
del ota
print("\n=== 3. Ikki havola — ikkinchisi yo'qolguncha tirik ===")
a = R("ikki havolali")
b = a
del a
print(" a o'chirildi, b hali ushlab turibdi")
del b
print("\n=== 4. __del__ dagi xato tashqariga chiqmaydi ===")
def hook(xabar):
print(f" unraisablehook: {type(xabar.exc_value).__name__}: {xabar.exc_value}")
eski_hook = sys.unraisablehook
sys.unraisablehook = hook
class XatoliDel:
def __del__(self):
raise RuntimeError("fayl yopilmadi")
x = XatoliDel()
del x
print(" dastur davom etdi")
sys.unraisablehook = eski_hook
print("\n=== 5. Tirilish: __del__ faqat bir marta ===")
qabriston = []
class Tiriluvchi:
def __del__(self):
print(" __del__ chaqirildi — o'zini saqlayapti")
qabriston.append(self)
t = Tiriluvchi()
del t
print(f" tirildi: {len(qabriston)} ta obyekt ro'yxatda")
qabriston.clear()
print(" ro'yxat tozalandi — __del__ ikkinchi marta chaqirilmadi")Natijaning muhim qismi:
=== 1. Darhol o'chirish ===
del dan oldin
🗑 del bilan
del dan keyin
🗑 birinchi
qayta bog'lashdan keyin
🗑 ikkinchi
funksiya tugayapti
🗑 lokal o'zgaruvchi
funksiyadan keyin
🗑 ro'yxat elementi
pop dan keyin
=== 2. Zanjir: tashqaridan ichkariga ===
🗑 ota
🗑 bola
🗑 nevara
=== 3. Ikki havola — ikkinchisi yo'qolguncha tirik ===
a o'chirildi, b hali ushlab turibdi
🗑 ikki havolali
=== 4. __del__ dagi xato tashqariga chiqmaydi ===
unraisablehook: RuntimeError: fayl yopilmadi
dastur davom etdi
=== 5. Tirilish: __del__ faqat bir marta ===
__del__ chaqirildi — o'zini saqlayapti
tirildi: 1 ta obyekt ro'yxatda
ro'yxat tozalandi — __del__ ikkinchi marta chaqirilmadiNima ko'rsatdi: 2.2, 2.5-bo'limlar.
Misol 3 — Yashirin havolalar va "kim ushlab turibdi"
"""Saqlangan istisno freymni ushlaydi; generator, closure, callback, kesh; gc.get_referrers bilan topish va tuzatish."""
import gc
import traceback
import weakref
from functools import lru_cache
class Hisobot:
def __init__(self, nom):
self.nom = nom
self.malumot = bytearray(1_000_000)
def __repr__(self):
return f"Hisobot({self.nom!r})"
def tirikmi(havola):
return "tirik" if havola() is not None else "o'lgan"
print("=== 1. ⚠️ Saqlangan istisno lokal o'zgaruvchilarni ushlaydi ===")
xatolar = []
xato_matnlari = []
def ishla(nom, saqlash_usuli):
hisobot = Hisobot(nom)
havola = weakref.ref(hisobot)
try:
raise ValueError(f"{nom}: hisoblashda xato")
except ValueError as e:
if saqlash_usuli == "obyekt":
xatolar.append(e)
else:
xato_matnlari.append("".join(traceback.format_exception(e)).strip().splitlines()[-1])
return havola
h1 = ishla("A", "obyekt")
h2 = ishla("B", "matn")
print(f" istisno obyekti saqlandi: Hisobot A — {tirikmi(h1)}")
print(f" faqat matn saqlandi: Hisobot B — {tirikmi(h2)}, matn: {xato_matnlari[0]!r}")
def kim_ushlab_turibdi(obj, chuqurlik=4):
zanjir = []
joriy = obj
for _ in range(chuqurlik):
egalar = [r for r in gc.get_referrers(joriy)
if r is not zanjir and type(r).__name__ not in ("list_iterator",)
and r is not globals() and not (isinstance(r, list) and r is gc.garbage)]
egalar = [r for r in egalar if type(r).__name__ in ("frame", "dict", "traceback", "ValueError", "list")]
if not egalar:
break
joriy = egalar[0]
zanjir.append(type(joriy).__name__)
if joriy is xatolar:
break
return " ← ".join(zanjir)
print(f" Hisobot A ni kim ushlaydi: Hisobot ← {kim_ushlab_turibdi(h1())}")
xatolar[0].__traceback__ = None
gc.collect()
print(f" e.__traceback__ = None dan keyin: Hisobot A — {tirikmi(h1)}")
print("\n=== 2. To'xtatilgan generator ===")
def hisobot_oqimi():
hisobot = Hisobot("generator")
yield hisobot.nom
yield "oxiri"
g = hisobot_oqimi()
print(f" next(g) = {next(g)!r}")
gh = weakref.ref(g.gi_frame.f_locals["hisobot"])
print(f" to'xtatilgan: {tirikmi(gh)}")
g.close()
print(f" g.close(): {tirikmi(gh)}")
print("\n=== 3. Callback, kesh va closure ===")
class Tugma:
def __init__(self):
self.ishlovchilar = []
class Oyna:
def __init__(self, tugma):
self.hisobot = Hisobot("oyna")
tugma.ishlovchilar.append(self.yangila)
def yangila(self):
return self.hisobot.nom
tugma = Tugma()
oyna_havola = weakref.ref(Oyna(tugma))
print(f" bog'langan metod callback'da: Oyna — {tirikmi(oyna_havola)}")
tugma.ishlovchilar.clear()
print(f" callback olib tashlangach: Oyna — {tirikmi(oyna_havola)}")
@lru_cache(maxsize=None)
def tahlil(hisobot):
return len(hisobot.malumot)
kesh_hisobot = Hisobot("kesh")
kh = weakref.ref(kesh_hisobot)
tahlil(kesh_hisobot)
del kesh_hisobot
print(f" lru_cache argumenti: {tirikmi(kh)}")
tahlil.cache_clear()
print(f" cache_clear(): {tirikmi(kh)}")
def yasa_hisoblagich(hisobot):
return lambda: len(hisobot.malumot)
c_hisobot = Hisobot("closure")
ch = weakref.ref(c_hisobot)
hisoblagich = yasa_hisoblagich(c_hisobot)
del c_hisobot
print(f" closure hujayrasi: {tirikmi(ch)}")
del hisoblagich
print(f" closure o'chirilgach: {tirikmi(ch)}")Natijaning muhim qismi:
=== 1. ⚠️ Saqlangan istisno lokal o'zgaruvchilarni ushlaydi ===
istisno obyekti saqlandi: Hisobot A — tirik
faqat matn saqlandi: Hisobot B — o'lgan, matn: 'ValueError: B: hisoblashda xato'
Hisobot A ni kim ushlaydi: Hisobot ← frame ← traceback ← ValueError ← list
e.__traceback__ = None dan keyin: Hisobot A — o'lgan
=== 2. To'xtatilgan generator ===
next(g) = 'generator'
to'xtatilgan: tirik
g.close(): o'lgan
=== 3. Callback, kesh va closure ===
bog'langan metod callback'da: Oyna — tirik
callback olib tashlangach: Oyna — o'lgan
lru_cache argumenti: tirik
cache_clear(): o'lgan
closure hujayrasi: tirik
closure o'chirilgach: o'lganNima ko'rsatdi: 2.4, 2.6-bo'limlar. Istisno obyekti traceback orqali butun 1 MB lik hisobotni ushlab turdi, faqat xato matnini saqlash esa hech narsani ushlamadi.
Misol 4 — Amaliy: obyektlar hayot davri kuzatuvchisi
Veb-xizmatdagi so'rov obyektlari hayot davrini kuzatuvchi vosita: har Sorov yaratilganda weakref.finalize ro'yxatga oladi, o'lganda qayd qiladi. Simulyatsiyada ba'zi so'rovlar turli yo'llar bilan "sizadi" — vosita ularni topib, sababini ko'rsatadi.
"""weakref.finalize bilan hayot davri jurnali; __del__ vs finalize; sizish sabablarini gc.get_referrers bilan tahlil."""
import gc
import weakref
from functools import lru_cache
class Kuzatuvchi:
def __init__(self):
self.tiriklar = weakref.WeakValueDictionary()
self.yaratildi = 0
self.oldi = 0
def royxatga_ol(self, obj, nom):
self.yaratildi += 1
self.tiriklar[nom] = obj
weakref.finalize(obj, self._oldi)
def _oldi(self):
self.oldi += 1
def hisobot(self):
return {"yaratildi": self.yaratildi, "oldi": self.oldi, "tirik": sorted(self.tiriklar.keys())}
kuzat = Kuzatuvchi()
class Sorov:
def __init__(self, raqam):
self.raqam = raqam
self.tana = bytearray(100_000)
kuzat.royxatga_ol(self, f"sorov-{raqam}")
xatolar = []
tinglovchilar = []
@lru_cache(maxsize=None)
def narx_hisobla(sorov):
return sorov.raqam * 1000
def tinglovchi_qosh(sorov):
tinglovchilar.append(lambda: sorov.raqam)
def ishla(raqam):
sorov = Sorov(raqam)
if raqam % 10 == 3:
try:
raise TimeoutError(f"so'rov {raqam}")
except TimeoutError as e:
xatolar.append(e)
elif raqam % 10 == 7:
tinglovchi_qosh(sorov)
elif raqam % 10 == 9:
narx_hisobla(sorov)
return sorov.raqam
for i in range(30):
ishla(i)
print("=== 1. 30 ta so'rovdan keyin ===")
h = kuzat.hisobot()
print(f" yaratildi: {h['yaratildi']}, o'ldi: {h['oldi']}, tirik qoldi: {len(h['tirik'])}")
print(f" tiriklar: {h['tirik']}")
def sabab(obj):
for ega in gc.get_referrers(obj):
tur = type(ega).__name__
if tur == "frame":
for ega2 in gc.get_referrers(ega):
if type(ega2).__name__ == "traceback":
return "saqlangan istisno traceback'i"
if tur == "cell":
return "closure hujayrasi (tinglovchi lambda)"
if tur == "tuple":
for ega2 in gc.get_referrers(ega):
if type(ega2).__name__ == "dict" and ega2 is not globals():
return "lru_cache kaliti"
return "noma'lum"
print("\n=== 2. Sizish sabablari ===")
for nom in sorted(kuzat.tiriklar.keys(), key=lambda n: int(n.split("-")[1])):
obj = kuzat.tiriklar.get(nom)
if obj is not None:
print(f" {nom:10} ← {sabab(obj)}")
del obj
print("\n=== 3. Tuzatishlar ===")
for e in xatolar:
e.__traceback__ = None
print(f" traceback'lar tozalandi → tirik: {len(kuzat.tiriklar)}")
tinglovchilar.clear()
print(f" tinglovchilar tozalandi → tirik: {len(kuzat.tiriklar)}")
narx_hisobla.cache_clear()
gc.collect()
print(f" kesh tozalandi → tirik: {len(kuzat.tiriklar)}, o'ldi: {kuzat.oldi}/{kuzat.yaratildi}")
print("\n=== 4. __del__ va weakref.finalize ===")
class DelBilan:
def __del__(self):
print(" __del__: self hali mavjud — lekin tirilish mumkin, interpretator yopilishida kafolat yo'q")
def tozalovchi(nom):
print(f" finalize: {nom} — self ga havola yo'q, faqat kerakli argumentlar")
d = DelBilan()
del d
f_obj = Sorov(999)
fin = weakref.finalize(f_obj, tozalovchi, "sorov-999")
print(f" finalize tirikmi: {fin.alive}, interpretator yopilishida ham chaqiriladi (atexit): {fin.atexit}")
del f_obj
print(f" obyekt o'lgach finalize tirikmi: {fin.alive} (bir marta ishladi)")Natijaning muhim qismi:
=== 1. 30 ta so'rovdan keyin ===
yaratildi: 30, o'ldi: 21, tirik qoldi: 9
tiriklar: ['sorov-13', 'sorov-17', 'sorov-19', 'sorov-23', 'sorov-27', 'sorov-29', 'sorov-3', 'sorov-7', 'sorov-9']
=== 2. Sizish sabablari ===
sorov-3 ← saqlangan istisno traceback'i
sorov-7 ← closure hujayrasi (tinglovchi lambda)
sorov-9 ← lru_cache kaliti
sorov-13 ← saqlangan istisno traceback'i
sorov-17 ← closure hujayrasi (tinglovchi lambda)
sorov-19 ← lru_cache kaliti
sorov-23 ← saqlangan istisno traceback'i
sorov-27 ← closure hujayrasi (tinglovchi lambda)
sorov-29 ← lru_cache kaliti
=== 3. Tuzatishlar ===
traceback'lar tozalandi → tirik: 6
tinglovchilar tozalandi → tirik: 3
kesh tozalandi → tirik: 0, o'ldi: 30/30
=== 4. __del__ va weakref.finalize ===
__del__: self hali mavjud — lekin tirilish mumkin, interpretator yopilishida kafolat yo'q
finalize tirikmi: True, interpretator yopilishida ham chaqiriladi (atexit): True
finalize: sorov-999 — self ga havola yo'q, faqat kerakli argumentlar
obyekt o'lgach finalize tirikmi: False (bir marta ishladi)Nima ko'rsatdi: 2.4, 2.5, 2.6-bo'limlar — weakref.finalize obyektni tirik ushlamasdan uning o'limini qayd etadi, gc.get_referrers esa havola zanjiri bo'ylab sabab turini ko'rsatadi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Python obyektlarni vaqti-vaqti bilan axlat yig'uvchi o'chiradi" | CPython'da ko'p obyekt havolalar soni nolga tushganda darhol o'chadi; GC faqat sikllar uchun |
"del x obyektni o'chiradi" |
Bitta havolani olib tashlaydi |
"sys.getrefcount — aniq havolalar soni" |
Globalda +1, 3.14 lokalda ko'pincha aniq, o'lmaslarda maxsus son |
| "Xato ro'yxatiga istisnoni saqlash arzon" | Traceback orqali butun freymlar va lokal o'zgaruvchilar tirik |
"__del__ — destruktor, har doim chaqiriladi" |
Interpretator yopilishida kafolat yo'q; bir marta; xatolari yutiladi |
"__del__ ichida xato ko'tarsam, chaqiruvchi ko'radi" |
Exception ignored in — faqat stderr |
| "Havolalar hisobi — barcha Python implementatsiyalarida" | CPython xususiyati; PyPy boshqacha |
"Obyekt tirikmi — sys.getrefcount bilan tekshiraman" |
weakref.ref(obj)() is None — ishonchli usul |
6. Keng tarqalgan xatolar va yechimlari
1. Istisno obyektlarini ro'yxatda saqlash
xatolar.append(e) # ❌ traceback → freym → lokallar
xatolar.append({"tur": type(e).__name__, "xabar": str(e),
"iz": traceback.format_exception(e)}) # ✅ faqat matn2. Resursni __del__ da yopish
class Ulanish:
def __del__(self):
self.soket.close() # ❌ qachon chaqirilishi noma'lum (PyPy, sikl, yopilish)
def __enter__(self): return self
def __exit__(self, *e): self.soket.close() # ✅ with bilan aniq3. __del__ ichida global nomlarga tayanish
def __del__(self):
jurnal.info("yopildi") # ❌ yopilishda jurnal allaqachon None bo'lishi mumkin weakref.finalize(self, funksiya, kerakli_argumentlar) — argumentlar oldindan olinadi.
4. Generator oxirigacha yetmasdan tashlab yuborish
satr = next(fayl_oqimi(yol)) # ❌ generator freymi faylni ochiq ushlaydi with contextlib.closing(fayl_oqimi(yol)) as oqim: yoki gen.close().
5. Obyekt o'lganmi — getrefcount bilan tekshirish
if sys.getrefcount(obj) == 2: ... # ❌ versiyaga va chaqiruv joyiga bog'liq
havola = weakref.ref(obj)
if havola() is None: ... # ✅6. Katta tuzilmani issiq yo'lda del qilish
def sorovni_ishla():
kesh = qur_katta_indeks() # 10 mln obyekt
...
return javob # ⚠️ chiqishda butun indeks o'chiriladi — javob kechikadiKatta tuzilmani qayta ishlating yoki tozalashni javobdan keyinga suring.
7. Freym obyektini saqlash
self.chaqiruvchi = inspect.currentframe().f_back # ❌ butun chaqiruv zanjiri tirik
self.chaqiruvchi_nomi = inspect.currentframe().f_back.f_code.co_name # ✅8. Callback ro'yxatiga bog'langan metod
hodisalar.append(self.yangila) # ❌ self abadiy tirik
hodisalar.append(weakref.WeakMethod(self.yangila)) # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 9.5-dars (o'tilgan):
__traceback__,except ... as eblok oxirida o'chirilishi - 10.3, 10.6-darslar (o'tilgan): generator
close(), kontekst menejerlar - 10.12–10.13-darslar (o'tilgan): closure va
lru_cachehavolalari - 11.5-dars: sikllar va axlat yig'uvchi — havolalar hisobi yetmaydigan joy
- 11.10-dars:
weakref— havola qo'shmasdan kuzatish - 12-qism:
Py_INCREF/Py_DECREFbytecode va C darajasida - 14-qism: GIL va havolalar hisobi; erkin ipli yig'mada "biased reference counting"
- 29-qism:
tracemallocva xotira sizishini ishlab chiqarishda topish - 30-qism: C kengaytmalarda havolalarni qo'lda boshqarish
8. Eng yaxshi amaliyotlar
Resurslarni
withbilan yoping — havolalar hisobiga tayanmang.Istisnolardan faqat kerakli ma'lumotni saqlang — obyektni emas.
Generatorlarni oxirigacha iteratsiya qiling yoki
close()qiling.Uzoq yashaydigan ro'yxatlarga — zaif havolalar (
WeakSet,WeakMethod,WeakValueDictionary).__del__o'rnigaweakref.finalize.Obyekt tirikligini
weakref.refbilan tekshiring,getrefcountbilan emas.Sizishni taxmin qilmang —
gc.get_referrers,tracemallocbilan isbotlang.Katta tuzilmalar o'chirilishi vaqtini hisobga oling — kechikishga sezgir kodda.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
import sys
class R:
def __init__(self, n):
self.n = n
def __del__(self):
print("del", self.n, end=" ")
1. r = R(1)
del r
print("keyin")
2. r = R(1)
r = R(2)
print("|", end=" ")
del r
print()
3. def f():
t = R("f")
print("ichkarida", end=" ")
f()
print("tashqarida")
4. a = R("a")
b = a
del a
print("hali", end=" ")
del b
print()
5. l = [R("x")]
l.clear()
print("tozalandi")
6. o = object()
d = {1: o, 2: o}
print(sys.getrefcount(o) - 1)
7. def g():
x = []
return sys.getrefcount(x)
print(g())
8. saqla = []
class T:
def __del__(self):
saqla.append(self)
print("del", end=" ")
t = T()
del t
saqla.clear()
print(len(saqla))
9. def f():
obj = R("freym")
raise ValueError
try:
f()
except ValueError as e:
saqlangan = e
print("except dan keyin", end=" ")
del saqlangan
print()
10. import weakref
r = R("w")
w = weakref.ref(r)
del r
print(w())
11. a = R("ota")
a.bola = R("bola")
del a
print()
12. def gen():
obj = R("gen")
yield 1
yield 2
g = gen()
next(g)
print("to'xtatildi", end=" ")
del g
print()Javoblar
del 1 keyin—delbilan darholdel 1 | del 2— qayta bog'lash eski obyektni o'chirdiichkarida del f tashqarida— funksiyadan chiqishdahali del a— ikkinchi havola o'chguncha tirikdel x tozalandi3—o,d[1],d[2](global nom orqali +1 ayrildi)1— Python 3.14 da lokal o'zgaruvchi qarz sifatida uzatildidel 0— tirilgan obyektning__del__i ikkinchi marta chaqirilmadiexcept dan keyin del freym— saqlangan istisno freymni ushlab turdidel w Nonedel ota del bola— tashqaridan ichkarigato'xtatildi del gen— generator o'chganda freymi ham
Vazifa 2: Xatolarni tuzating
1. class Jurnal:
def __init__(self, yol):
self.fayl = open(yol, "a")
def __del__(self):
self.fayl.close()
2. so_nggi_xatolar = []
def ishla(malumot):
katta = yukla(malumot)
try:
return tahlil(katta)
except Exception as e:
so_nggi_xatolar.append(e)
raise
3. def birinchi_qator(yol):
def oqi():
with open(yol) as f:
yield from f
return next(oqi())
4. class Hodisa:
obunachilar = []
def obuna(self, f):
self.obunachilar.append(f)
class Panel:
def __init__(self, hodisa):
hodisa.obuna(self.yangila)
5. def tirikmi(obj):
return sys.getrefcount(obj) > 2Javoblar
1. class Jurnal:
def __init__(self, yol):
self.fayl = open(yol, "a")
def __enter__(self):
return self
def __exit__(self, *e):
self.fayl.close() # with bilan aniq yopish
# yoki: self._fin = weakref.finalize(self, self.fayl.close) — self ni ushlamaydigan zaxira
2. so_nggi_xatolar = []
def ishla(malumot):
katta = yukla(malumot)
try:
return tahlil(katta)
except Exception as e:
so_nggi_xatolar.append("".join(traceback.format_exception(e))) # faqat matn
raise
3. def birinchi_qator(yol):
with open(yol) as f:
return next(f) # generator va ochiq fayl freymda qolmaydi
4. class Hodisa:
def __init__(self):
self.obunachilar = [] # klass emas, nusxa atributi
def obuna(self, f):
self.obunachilar.append(weakref.WeakMethod(f))
# chaqirishda: for w in list(self.obunachilar): f = w(); f() if f else self.obunachilar.remove(w)
5. # Tiriklikni tekshirish uchun obyektning o'zini emas, zaif havolani saqlash kerak:
havola = weakref.ref(obj)
def tirikmi():
return havola() is not NoneVazifa 3: Havola hisoblagich jadvali
Misol 1 ni kengaytiring:
- 20 xil amal uchun (
set.add,dictkaliti,functools.partial,propertyqaytarishi,__slots__atributi,enumerate,zip,mapiteratori,threading.local...) havolalar sonini o'lchang - Har amal uchun "necha havola va nega" izohini yozing
- Python 3.14 va imkoni bo'lsa boshqa versiyada natijalarni solishtiring — qaysi qatorlar farq qiladi va nima uchun
Vazifa 4: Sizish detektori dekoratori
@sizishni_kuzat(chegara=100) klass dekoratori yozing:
- Klassning barcha nusxalarini
WeakSetda kuzatsin - Tirik nusxalar soni
chegaradan oshsa, ogohlantirish chiqarsin va eng ko'p uchraydigan "egalar" turlarini (gc.get_referrersbo'yicha) ko'rsatsin klass.hisobot()— yaratilgan, o'lgan, tirik soni va o'rtacha yashash davomiyligi- Misol 4 dagi simulyatsiyaga qo'llang
Vazifa 5: Xavfsiz istisno saqlovchi
XatoArxivi klassini yozing:
saqla(e)— istisnoning turi, xabari,__notes__, sabab zanjiri va formatlangan traceback'ini saqlasin, lekin freymlarni emas- Har freymdan faqat fayl, qator, funksiya nomi va (ixtiyoriy) lokal o'zgaruvchilarning
repridan birinchi 100 belgini olsin oxirgi(n)vatur_boyicha()statistikasi- Test: saqlangandan keyin asl lokal obyektlar
weakrefbo'yicha o'lganini tekshiring
Vazifa 6: O'chirish narxini o'lchash
- 10⁴, 10⁵, 10⁶, 10⁷ elementli (a)
list[int], (b)list[list], (c)dict[str, obyekt], (d) bog'langan ro'yxat (har tugunnextatributi bilan) tuzilmalarni yarating - Har biri uchun
delvaqtinitime.perf_counterbilan o'lchang - Nega bog'langan ro'yxatda (d) chuqur zanjir bo'lsa ham interpretator stek toshishidan yiqilmasligini tushuntiring (CPython'ning "trashcan" mexanizmi haqida o'qing)
- Natijalarni grafik yoki jadval qilib chiqaring
Vazifa 7: O'ylash
CPython xotirani havolalar hisobi bilan boshqaradi. Java, Go va .NET izlovchi (tracing) axlat yig'uvchilarga, Swift esa kompilyator qo'shadigan havolalar hisobiga (ARC), Rust esa egalik tizimiga va kerak bo'lganda Rc/Arc ga tayanadi. Havolalar hisobining afzallik va kamchiliklari nimada, va nega CPython uni saqlab kelmoqda?
Javob
Qisqa javob: havolalar hisobi deterministik va bosqichma-bosqich: obyekt oxirgi havola yo'qolgan zahoti o'ladi, katta "dunyoni to'xtatuvchi" pauzalar yo'q, resurslar darhol bo'shaydi. Narxi — har tayinlashda schyotchikni yangilash (ayniqsa ko'p ipli muhitda), sikllarni topa olmaslik va o'chirish narxining chaqiruv joyiga "tushishi". CPython uni saqlaydi, chunki butun C API, kengaytmalar ekotizimi va Python kodining deterministik tozalashga bo'lgan kutishlari shu modelga qurilgan — o'rniga esa bosqichma-bosqich yaxshilanishlar (o'lmas obyektlar, biased reference counting) qo'shilmoqda.
1. Havolalar hisobining afzalliklari
| Afzallik | Oqibat |
|---|---|
| Darhol tozalash | del yoki funksiyadan chiqishda xotira qaytadi |
| Bashorat qilinadigan pauzalar | Katta GC pauzalari yo'q (sikl GC bundan mustasno, u kichik) |
| Kesh lokalligi | Bo'shagan xotira darhol qayta ishlatiladi — hali "issiq" |
| Sodda C API | Kengaytma yozuvchi Py_INCREF/Py_DECREF bilan aniq egalikni ifodalaydi |
| Kichik xotira sarfi | Izlovchi GC odatda "bo'sh joy zaxirasi" talab qiladi |
2. Kamchiliklari
| Kamchilik | Oqibat |
|---|---|
| Har tayinlashda yozuv | Hatto o'qish uchun ham schyotchik yangilanadi — CPU keshi va tezlik |
| Ko'p iplilik | Schyotchikni atomar yangilash qimmat → GIL tarixan shu sabab (14-qism) |
| Sikllar | Alohida sikl yig'uvchi kerak (11.5-dars) |
| O'chirish zanjiri | Katta tuzilma o'chirilganda narx aynan o'sha joyda |
| Qo'lda xatolar | C kengaytmalardagi INCREF/DECREF xatolari — sizish yoki yiqilish |
3. Boshqa tillar
Java, .NET, Go — izlovchi GC: vaqti-vaqti bilan "tirik" obyektlarni ildizlardan boshlab belgilaydi, qolganini tozalaydi. Tayinlash arzon (schyotchik yo'q), sikllar muammo emas, zamonaviy yig'uvchilar (G1, ZGC, Go'ning konkurent yig'uvchisi) pauzalarni juda kichraytirgan. Lekin tozalash vaqti noma'lum — resurslar uchun try-with-resources / using / defer kerak.
Swift — ARC: kompilyator retain/release chaqiruvlarini avtomatik qo'yadi va keraksizlarini optimallashtiradi. Sikllar uchun dasturchi weak va unowned havolalarni aniq belgilaydi — sikl yig'uvchi yo'q.
Rust — egalik: ko'p hollarda schyotchik umuman kerak emas, obyekt egasi doiradan chiqqanda o'chadi. Umumiy egalik uchun Rc (bir ip) va Arc (atomar, ko'p ip); sikllar uchun Weak. Dasturchi narxni aniq tanlaydi.
4. CPython nega havolalar hisobini saqlaydi
- C API va ekotizim: NumPy, pandas va minglab kengaytmalar
Py_INCREF/Py_DECREFga tayanadi — boshqa modelga o'tish ekotizimni buzadi - Deterministik tozalash kutilmasi: ko'p Python kodi (bilib yoki bilmay) faylning
deldan keyin yopilishiga tayanadi - Bosqichma-bosqich yaxshilanishlar:
- O'lmas obyektlar (PEP 683, 3.12) —
None, kichik sonlar kabi ko'p ishlatiladigan obyektlarda schyotchik yozuvlari yo'q - Qarz olingan havolalar — 3.14 da lokal o'zgaruvchilarni ba'zi amallarda schyotchikni oshirmasdan uzatish
- Biased reference counting (PEP 703, erkin ipli yig'ma) — obyektni yaratgan ip o'z schyotchigini arzon, atomarsiz yangilaydi, boshqa iplar esa alohida atomar schyotchikdan foydalanadi
- Kechiktirilgan havolalar hisobi ba'zi tur obyektlari va funksiyalar uchun (erkin ipli yig'ma)
- O'lmas obyektlar (PEP 683, 3.12) —
PyPy esa izlovchi GC ishlatadi va C API ni emulyatsiya qatlami orqali qo'llab-quvvatlaydi — bu mumkin, lekin kengaytmalar sekinroq ishlaydi.
5. Amaliy xulosa Python dasturchisi uchun
- Deterministik tozalashga tayanmang —
withishlating; kod PyPy'da ham to'g'ri ishlasin - Havolalar hisobi qanday ishlashini bilish esa xotira sizishini topishda kerak: yashirin havola = tirik obyekt
- Sikllardan qoching yoki
weakrefbilan uzing — shunda obyektlar darhol o'ladi, GC ga kutilmaydi - Ko'p ipli kod uchun erkin ipli yig'maning narxlarini (14-qism) biling
6. Xulosa
- Havolalar hisobi obyektni darhol va bashorat qilinadigan tarzda o'chiradi, lekin har tayinlashda narx to'laydi va sikllarni topa olmaydi
- Izlovchi GC tayinlashni arzon qiladi va sikllarni hal qiladi, lekin tozalash vaqti noma'lum
- Swift va Rust narxni kompilyator va dasturchi nazoratiga o'tkazadi
- CPython C API va ekotizim tufayli havolalar hisobini saqlaydi va uni o'lmas obyektlar, qarz havolalar va biased reference counting bilan yaxshilamoqda
- Dasturchi uchun: resurslar —
with, yashirin havolalar — sizish, sikllar —weakref
Nimani mustahkamlaydi: 2.1–2.6-bo'limlar.
Xulosa
Bu darsda CPython xotirasining asosiy mexanizmi — havolalar hisobini o'rgandik.
Eng muhim uch fikr:
Havolalar soni nolga tushgan zahoti obyekt o'ladi. Nomga bog'lash, konteynerga qo'yish, atribut, argument, closure va bog'langan metod havola qo'shadi;
del, qayta bog'lash va funksiyadan chiqish ayiradi. Nol bo'lganda__del__chaqiriladi va obyekt ichidagi havolalar zanjir bo'ylab bo'shatiladi — deterministik, lekin bu CPython xususiyati: resurslar uchunwithishlating.Yashirin havola — tirik obyekt. Saqlangan istisno traceback orqali freymlar va barcha lokal o'zgaruvchilarni, to'xtatilgan generator o'z freymini, closure hujayrasi, callback'dagi bog'langan metod va
lru_cacheargumentlarni ushlab turadi. Istisnolardan faqat matnni saqlang, generatorlarni yoping, uzoq yashaydigan ro'yxatlarda zaif havolalardan foydalaning va sababnigc.get_referrersbilan isbotlang.getrefcountva__del__— ehtiyot bilan.sys.getrefcountglobal nom orqali +1 qo'shadi, Python 3.14 da lokal o'zgaruvchi uchun ko'pincha aniq son beradi, o'lmas obyektlar uchun maxsus qiymat qaytaradi — uni farq sifatida ishlating, tiriklikni esaweakref.refbilan tekshiring.__del__bir marta chaqiriladi, xatolari yutiladi va interpretator yopilishida kafolatlanmagan —weakref.finalizeishonchliroq.
Keyingi darsda havolalar hisobi hal qila olmaydigan muammoni — sikllarni — va CPython'ning avlodli axlat yig'uvchisini o'rganamiz: gc moduli, avlodlar va chegaralar, gc.disable() qachon mantiqiy va sikllarni weakref bilan qanday uzish mumkin.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!