IlmHamroh
Python kursi/Ilgor til imkoniyatlari12/17-dars29 daqiqa
Mundarija (21)

10.12-dars: Closure chuqur

10-QISM — ILG'OR TIL IMKONIYATLARI · 12-dars


1. Kirish va motivatsiya

7.8-darsda closure'ni "ichki funksiya tashqi o'zgaruvchini eslab qoladi" deb ta'rifladik, __closure__ va kechikkan bog'lanishni ko'rdik. 7.9-darsda nonlocal ni o'rgandik. Oxirgi to'rt darsda esa closure'larsiz birorta dekorator yoza olmadik.

Endi real ilovadan bir parcha — GUI oynasi tugmaga obuna bo'ladi:

python
class Oyna:
    def __init__(self, tugma):
        self.malumot = katta_jadval_yukla()          # 200 MB
        tugma.bosilganda(lambda: self.yangila())

for _ in range(50):
    oyna = Oyna(asosiy_tugma)
    oyna.yopish()
    del oyna

50 ta oyna ochilib yopildi, del qilindi — lekin xotira 10 GB ga yetdi. Hech bir oyna o'chmadi.

Sababi — lambda: self.yangila(). Bu closure self ni hujayra (cell) orqali ushlab turadi, closure esa tugmaning ro'yxatida yashaydi. Tugma tirik ekan, har bir oyna va uning 200 MB ma'lumoti ham tirik.

Closure — "eslab qoluvchi funksiya" emas, hujayra obyektlari orqali boshqa obyektlarni tirik ushlab turadigan funksiya. Uning ichki tuzilishini bilish kechikkan bog'lanish, xotira sizishi, aylanma havolalar va NameError kabi kutilmagan xatolarni tushuntiradi.

Bu darsda:

  • Hujayra (cell) — o'zgaruvchi emas, quti: bir nechta funksiya bitta qutini bo'lishadi
  • Bytecode: MAKE_CELL, LOAD_DEREF, STORE_DEREF, COPY_FREE_VARS
  • Yashirin closure'lar: super() va __class__
  • Kechikkan bog'lanish chuqur: comprehension, generator ifodasi, except ... as e, del
  • Hayot davri: closure nimani tirik ushlaydi, aylanma havolalar, callback'lar va weakref
  • Closure, klass va partial — qachon qaysi biri

2. Nazariya — chuqur tushuntirish

2.1. Hujayra — quti

python
def hisoblagich():
    n = 0
    def ol():
        return n
    def qosh():
        nonlocal n
        n += 1
    return ol, qosh

Kompilyator n ni ichki funksiyalar ishlatishini ko'radi va uni oddiy lokal o'zgaruvchi emas, hujayra o'zgaruvchisi qiladi:

Joy Ko'rinishi
hisoblagich.__code__.co_cellvars ('n',) — "men hujayra yarataman"
ol.__code__.co_freevars ('n',) — "men tashqi hujayrani ishlataman"
ol.__closure__ (<cell>,) — hujayra obyektlarining korteji
ol.__closure__[0].cell_contents Hujayraning hozirgi qiymati

Asosiy fakt: ol va qosh ning __closure__[0] si — bitta hujayra obyekti.

hisoblagich chaqiruvi
     │
     ▼
  ┌──────┐
  │ cell │──► 0 → 1 → 2 ...
  └──────┘
   ▲    ▲
   │    │
  ol   qosh

Hujayra — qiymat emas, qiymatga havola saqlaydigan quti. qosh qutidagi havolani almashtiradi, ol qutidan o'qiydi. Shuning uchun:

  • closure o'zgaruvchini eslaydi, qiymatni emas → kechikkan bog'lanish
  • bir chaqiruvda yaratilgan barcha ichki funksiyalar holatni bo'lishadi
  • tashqi funksiya qaytgandan keyin ham quti yashaydi — uni ichki funksiyalar ushlab turadi
  • har hisoblagich() chaqiruvi yangi quti yaratadi

cell_contents Python 3.7 dan yoziladigan, types.CellType(qiymat) bilan qo'lda hujayra yaratish mumkin — bu asosan disassembler, serializer va tuzatish vositalari uchun.

2.2. Bytecode darajasida

python
import dis
dis.dis(hisoblagich)
Operatsiya Qayerda Nima qiladi
MAKE_CELL n Tashqi funksiya boshida n uchun bo'sh hujayra yaratadi
STORE_DEREF n Tashqi yoki nonlocal li ichki Hujayraga yozadi
LOAD_DEREF n Ichki funksiya Hujayradan o'qiydi
COPY_FREE_VARS Ichki funksiya boshida __closure__ dagi hujayralarni freymga ko'chiradi
MAKE_FUNCTION + SET_FUNCTION_ATTRIBUTE Tashqi funksiya Funksiya obyektini hujayralar korteji bilan yaratadi

Oddiy lokal LOAD_FAST — freymdagi uyadan to'g'ridan-to'g'ri o'qish. LOAD_DEREF — uyadagi hujayra, keyin hujayradagi qiymat: bitta qo'shimcha qadam. Farq kichik, lekin issiq siklda sezilishi mumkin.

2.3. Yashirin closure: super() va __class__

python
class Hisob:
    def yarat(self):
        return super().__init__()

Hisob.yarat.__code__.co_freevars      # ('__class__',)

Argumentsiz super() qanday qilib klassni biladi? Kompilyator metod ichida super yoki __class__ nomini ko'rsa, klass tanasi uchun yashirin __class__ hujayrasi yaratadi, klass yaratilgach unga klassni yozadi. super() shu hujayradan o'qiydi.

Oqibatlari:

  • metodni boshqa klassga ko'chirsangiz (B.metod = A.metod), super() baribir A ni ishlatadi — B nusxasi A turida bo'lmasa, TypeError
  • 10.10-darsdagi dataclass(slots=True) yangi klass yaratganda __class__ hujayralarini yangi klassga qayta yozadi — aks holda super() eski klassga ishora qilardi

2.4. Kechikkan bog'lanish chuqur

Closure hujayrani o'qiydi chaqirilgan paytda. Qiymat qachon o'zgarganiga qarab natija farq qiladi:

Holat Natija Sabab
[lambda: i for i in range(3)] [2, 2, 2] Comprehension bitta i hujayrasi; lambdalar sikl tugagach chaqiriladi
(lambda: i for i in range(3)) darhol chaqirilsa [0, 1, 2] Generator dangasa: har lambda i o'zgarishidan oldin chaqiriladi
for i in ...: funksiyalar.append(lambda: i) Hammasi oxirgi Bitta hujayra
lambda i=i: i [0, 1, 2] Sukut qiymat def paytida hisoblanadi — hujayra emas

Uch yechim va ularning narxi:

python
[lambda i=i: i for i in range(3)]               # 1. sukut argument
[functools.partial(ishla, i) for i in range(3)] # 2. partial
def yasa(i): return lambda: i                   # 3. fabrika — har chaqiruv yangi hujayra
[yasa(i) for i in range(3)]
Yechim Kamchiligi
Sukut argument Imzoda ko'rinadi, chaqiruvchi tasodifan f(5) bilan qayta yozishi mumkin
partial Faqat pozitsion/nomli argumentlar uchun; repr o'qilishi qiyinroq
Fabrika Qo'shimcha funksiya, lekin eng toza — 10.9-darsdagi dekorator fabrikasi aynan shu

Kechikkan bog'lanish foydali ham bo'lishi mumkin: sozlama qayta yuklanganda closure yangi qiymatni avtomatik ko'radi.

2.5. Bo'shatilgan hujayra

Hujayra bo'sh bo'lishi mumkin:

python
def f():
    try:
        1 / 0
    except ZeroDivisionError as e:
        g = lambda: e
    return g

f()()    # NameError: cannot access free variable 'e' where it is not associated with a value in enclosing scope

except ... as e bloki tugaganda Python e ni o'chiradi (del e) — aylanma havolaning oldini olish uchun (traceback → freym → e). Hujayra bo'shaydi. Xuddi shunday del x ham tashqi funksiyada hujayrani bo'shatadi.

Kerakli qiymatni blok ichida oddiy nomga ko'chiring: xato = e.

2.6. Closure nimani tirik ushlaydi

Closure hujayralar orqali butun obyektlarni ushlaydi — ulardan faqat bir qismi kerak bo'lsa ham:

python
def hisobot_yasa(jadval):                  # jadval — 1 mln qator
    soni = len(jadval)
    def sarlavha():
        return f"{len(jadval)} qator"      # ❌ jadvalni ushlaydi
    return sarlavha

def hisobot_yasa(jadval):
    soni = len(jadval)
    def sarlavha():
        return f"{soni} qator"             # ✅ faqat butun son
    return sarlavha

self ni ushlaydigan callback (kirishdagi misol):

python
tugma.bosilganda(lambda: self.yangila())   # tugma → lambda → cell → self

Tugma tirik ekan, self ham tirik. Yechimlar:

  1. Obunani bekor qilish — yopilganda ro'yxatdan olib tashlash
  2. weakref.WeakMethod(self.yangila) — metodga zaif havola; obyekt o'lsa, havola None qaytaradi
  3. weakref.ref(self) ni closure'da ushlash va chaqiruvda tekshirish

Aylanma havola: o'zini chaqiradigan ichki funksiya.

python
def yasa(malumot):
    def yur(n):
        return yur(n - 1) if n else malumot    # yur → cell → yur
    return yur

yur o'z hujayrasiga, hujayra esa yur ga ishora qiladi. Havola hisoblagichi bunday siklni tozalay olmaydi — faqat davriy axlat yig'uvchi (gc), noaniq vaqtda. gc.disable() qilingan ilovada bu sizish.

2.7. Closure, klass, partial

Mezon Closure Klass (__call__) functools.partial
Holat Hujayralarda, yashirin Atributlarda, ochiq Faqat qotirilgan argumentlar
Bir nechta amal Bir nechta ichki funksiya (qaytarish noqulay) Metodlar Yo'q
Chaqiruv tezligi Tez (oddiy funksiya) Sekinroq (__call__ qidirish) Tez, agar C funksiyani o'rasa
pickle lokal funksiya (o'ralgan funksiya pickle bo'lsa)
Tekshirish/tuzatish __closure__ orqali vars(obj) .func, .args, .keywords
Meros, isinstance Yo'q Ha Yo'q

Qoida:

  • Bitta amal + kichik holat — closure (dekoratorlar, callback'lar, fabrikalar)
  • Argumentlarni qotirish, holatsiz — partial
  • Bir nechta amal, ochiq holat, pickle, meros — klass

3. Tez ma'lumotnoma

Ichki tuzilish

python
tashqi.__code__.co_cellvars       # tashqi yaratadigan hujayralar nomlari
ichki.__code__.co_freevars        # ichki ishlatadigan tashqi hujayralar nomlari
ichki.__closure__                 # hujayra obyektlari korteji (yoki None)
ichki.__closure__[0].cell_contents  # hozirgi qiymat (yoziladi)
types.CellType(qiymat)            # qo'lda hujayra

Bytecode

MAKE_CELL  STORE_DEREF  LOAD_DEREF  COPY_FREE_VARS

Kechikkan bog'lanish

[lambda: i for i in r]            → hammasi oxirgi
lambda i=i: i                     → sukut — def paytida
functools.partial(f, i)           → qotirilgan argument
def yasa(i): return lambda: i     → har chaqiruv yangi hujayra

Hayot davri

closure → cell → obyekt           (butun obyekt tirik)
except E as e:  → blok oxirida del e → bo'sh hujayra → NameError
o'zini chaqiruvchi ichki funksiya → sikl → faqat gc
callback'da self                  → WeakMethod / weakref.ref / obunani bekor qilish

4. Batafsil misollar

Misol 1 — Hujayra anatomiyasi

python
"""Bitta hujayrani bo'lishadigan funksiyalar, co_cellvars/co_freevars, bytecode, cell_contents, __class__."""

import dis
import types


def hisoblagich(boshlangich=0):
    n = boshlangich

    def ol():
        return n

    def qosh(qadam=1):
        nonlocal n
        n += qadam
        return n

    def nolla():
        nonlocal n
        n = 0

    return ol, qosh, nolla


print("=== 1. Bir chaqiruv — bitta quti ===")
ol, qosh, nolla = hisoblagich()
print(f"  hisoblagich.co_cellvars: {hisoblagich.__code__.co_cellvars}")
print(f"  ol.co_freevars: {ol.__code__.co_freevars}, qosh.co_freevars: {qosh.__code__.co_freevars}")
print(f"  bitta hujayra: {ol.__closure__[0] is qosh.__closure__[0] is nolla.__closure__[0]}")
qosh(); qosh(5)
print(f"  qosh ikki marta → ol() = {ol()}, cell_contents = {ol.__closure__[0].cell_contents}")
nolla()
print(f"  nolla() → ol() = {ol()}")

print("\n=== 2. Har chaqiruv — yangi quti ===")
a_ol, a_qosh, _ = hisoblagich(100)
b_ol, b_qosh, _ = hisoblagich(100)
a_qosh(); a_qosh(); b_qosh()
print(f"  a: {a_ol()}, b: {b_ol()}, hujayralar bir xilmi: {a_ol.__closure__[0] is b_ol.__closure__[0]}")

print("\n=== 3. Closure o'zgaruvchini eslaydi, qiymatni emas ===")


def yarat():
    xabar = "birinchi"
    f = lambda: xabar
    oldin = f()
    xabar = "ikkinchi"
    return oldin, f


oldin, f = yarat()
print(f"  lambda yaratilgan paytda o'qilsa: {oldin!r}, keyin chaqirilsa: {f()!r}")

print("\n=== 4. Bytecode ===")


def opkodlar(funksiya):
    return sorted({i.opname for i in dis.get_instructions(funksiya)} &
                  {"MAKE_CELL", "STORE_DEREF", "LOAD_DEREF", "COPY_FREE_VARS", "LOAD_FAST",
                   "STORE_FAST", "LOAD_FAST_BORROW"})


for nom, funksiya in [("hisoblagich", hisoblagich), ("ol", ol), ("qosh", qosh)]:
    print(f"  {nom:12} {opkodlar(funksiya)}")


def oddiy(n):
    return n + 1


print(f"  {'oddiy':12} {opkodlar(oddiy)}  closure: {oddiy.__closure__}")

print("\n=== 5. cell_contents yoziladi; CellType ===")
ol, qosh, _ = hisoblagich()
ol.__closure__[0].cell_contents = 41
print(f"  hujayraga 41 yozildi → qosh() = {qosh()}")
hujayra = types.CellType("qo'lda")
yangi = types.FunctionType(ol.__code__, globals(), "ol_nusxa", None, (hujayra,))
print(f"  boshqa hujayra bilan yangi funksiya: {yangi()!r}, asl ol(): {ol()}")

print("\n=== 6. Yashirin closure: super() va __class__ ===")


class Hisob:
    def __init__(self, balans):
        self.balans = balans

    def tavsif(self):
        return f"{type(self).__name__}: {self.balans}"


class Omonat(Hisob):
    def tavsif(self):
        return super().tavsif() + " (omonat)"

    def oddiy(self):
        return self.balans


print(f"  Omonat.tavsif.co_freevars: {Omonat.tavsif.__code__.co_freevars}")
print(f"  Omonat.oddiy.co_freevars:  {Omonat.oddiy.__code__.co_freevars}")
print(f"  __class__ hujayrasida: {Omonat.tavsif.__closure__[0].cell_contents.__name__}")


class Boshqa(Hisob):
    tavsif = Omonat.tavsif


try:
    Boshqa(5).tavsif()
except TypeError as e:
    print(f"  ⚠️ metodni boshqa klassga ko'chirish: TypeError: {e}")

Natijaning muhim qismi:

text
=== 1. Bir chaqiruv — bitta quti ===
  hisoblagich.co_cellvars: ('n',)
  ol.co_freevars: ('n',), qosh.co_freevars: ('n',)
  bitta hujayra: True
  qosh ikki marta → ol() = 6, cell_contents = 6
  nolla() → ol() = 0

=== 2. Har chaqiruv — yangi quti ===
  a: 102, b: 101, hujayralar bir xilmi: False

=== 3. Closure o'zgaruvchini eslaydi, qiymatni emas ===
  lambda yaratilgan paytda o'qilsa: 'birinchi', keyin chaqirilsa: 'ikkinchi'

=== 4. Bytecode ===
  hisoblagich  ['LOAD_FAST_BORROW', 'MAKE_CELL', 'STORE_DEREF', 'STORE_FAST']
  ol           ['COPY_FREE_VARS', 'LOAD_DEREF']
  qosh         ['COPY_FREE_VARS', 'LOAD_DEREF', 'LOAD_FAST_BORROW', 'STORE_DEREF']
  oddiy        ['LOAD_FAST_BORROW']  closure: None

=== 5. cell_contents yoziladi; CellType ===
  hujayraga 41 yozildi → qosh() = 42
  boshqa hujayra bilan yangi funksiya: "qo'lda", asl ol(): 42

=== 6. Yashirin closure: super() va __class__ ===
  Omonat.tavsif.co_freevars: ('__class__',)
  Omonat.oddiy.co_freevars:  ()
  __class__ hujayrasida: Omonat
  ⚠️ metodni boshqa klassga ko'chirish: TypeError: super(type, obj): obj (instance of Boshqa) is not an instance or subtype of type (Omonat).

Nima ko'rsatdi: 2.1, 2.2, 2.3-bo'limlar.

Misol 2 — Kechikkan bog'lanish va bo'sh hujayralar

python
"""Sikl, comprehension, generator ifodasi; uch yechim; except as e va del; foydali kechikkan bog'lanish."""

import functools


print("=== 1. Uch xil yaratish — uch xil natija ===")
royxat = [lambda: i for i in range(3)]
print(f"  list comprehension:          {[f() for f in royxat]}")
print(f"  generator, darhol chaqirilsa: {[f() for f in (lambda: i for i in range(3))]}")
generator = (lambda: i for i in range(3))
saqlangan = list(generator)
print(f"  generator, avval list():      {[f() for f in saqlangan]}")
funksiyalar = []
for j in range(3):
    funksiyalar.append(lambda: j)
j = 99
print(f"  for sikl, keyin j = 99:       {[f() for f in funksiyalar]}   ⚠️ modul darajasida — global o'zgaruvchi")

print("\n=== 2. Tugmalar: har tugma o'z raqamini chiqarishi kerak ===")


class Tugma:
    def __init__(self, yorliq, bosilganda):
        self.yorliq, self.bosilganda = yorliq, bosilganda


def ishla(raqam, belgi="#"):
    return f"{belgi}{raqam}"


def yasa(raqam):
    return lambda: ishla(raqam)


variantlar = {
    "lambda: ishla(r)": [Tugma(r, lambda: ishla(r)) for r in range(1, 4)],
    "lambda r=r: ...": [Tugma(r, lambda r=r: ishla(r)) for r in range(1, 4)],
    "partial(ishla, r)": [Tugma(r, functools.partial(ishla, r)) for r in range(1, 4)],
    "yasa(r)": [Tugma(r, yasa(r)) for r in range(1, 4)],
}
for nom, tugmalar in variantlar.items():
    print(f"  {nom:18} → {[t.bosilganda() for t in tugmalar]}")

print("\n=== 3. ⚠️ Sukut argument yechimining narxi ===")
f = variantlar["lambda r=r: ..."][0].bosilganda
print(f"  imzo ko'rinadi: {f.__defaults__}, tasodifan f(7) → {f(7)!r}")
p = variantlar["partial(ishla, r)"][0].bosilganda
print(f"  partial ichini ko'rish mumkin: func={p.func.__name__}, args={p.args}; p(belgi='№') → {p(belgi='№')!r}")

print("\n=== 4. ⚠️ Bo'sh hujayra: except ... as e va del ===")


def xato_ushla():
    try:
        {}["kalit"]
    except KeyError as e:
        kechikkan = lambda: f"oxirgi xato: {e!r}"
        xato = e
        togri = lambda: f"oxirgi xato: {xato!r}"
    return kechikkan, togri


kechikkan, togri = xato_ushla()
for nom, f in [("e ni ushlagan", kechikkan), ("xato = e", togri)]:
    try:
        print(f"  {nom}: {f()}")
    except NameError as ex:
        print(f"  {nom}: NameError: {ex}")
print(f"  e hujayrasi bo'shmi: {kechikkan.__closure__[0].__repr__().endswith('empty>')}")


def ochirilgan():
    vaqtinchalik = [1, 2, 3]
    f = lambda: sum(vaqtinchalik)
    del vaqtinchalik
    return f


try:
    ochirilgan()()
except NameError as ex:
    print(f"  del dan keyin: NameError: {ex}")

print("\n=== 5. Kechikkan bog'lanish foydali bo'lganda ===")


def sozlamali_ilova():
    sozlama = {"til": "uz", "valyuta": "UZS"}

    def narx(summa):
        return f"{summa:,} {sozlama['valyuta']}"

    def qayta_yukla(yangi):
        nonlocal sozlama
        sozlama = yangi

    return narx, qayta_yukla


narx, qayta_yukla = sozlamali_ilova()
print(f"  {narx(150_000)}")
qayta_yukla({"til": "en", "valyuta": "USD"})
print(f"  qayta yuklangandan keyin o'sha narx funksiyasi: {narx(12)}")

Natijaning muhim qismi:

text
=== 1. Uch xil yaratish — uch xil natija ===
  list comprehension:          [2, 2, 2]
  generator, darhol chaqirilsa: [0, 1, 2]
  generator, avval list():      [2, 2, 2]
  for sikl, keyin j = 99:       [99, 99, 99]   ⚠️ modul darajasida — global o'zgaruvchi

=== 2. Tugmalar: har tugma o'z raqamini chiqarishi kerak ===
  lambda: ishla(r)   → ['#3', '#3', '#3']
  lambda r=r: ...    → ['#1', '#2', '#3']
  partial(ishla, r)  → ['#1', '#2', '#3']
  yasa(r)            → ['#1', '#2', '#3']

=== 3. ⚠️ Sukut argument yechimining narxi ===
  imzo ko'rinadi: (1,), tasodifan f(7) → '#7'
  partial ichini ko'rish mumkin: func=ishla, args=(1,); p(belgi='№') → '№1'

=== 4. ⚠️ Bo'sh hujayra: except ... as e va del ===
  e ni ushlagan: NameError: cannot access free variable 'e' where it is not associated with a value in enclosing scope
  xato = e: oxirgi xato: KeyError('kalit')
  e hujayrasi bo'shmi: True
  del dan keyin: NameError: cannot access free variable 'vaqtinchalik' where it is not associated with a value in enclosing scope

=== 5. Kechikkan bog'lanish foydali bo'lganda ===
  150,000 UZS
  qayta yuklangandan keyin o'sha narx funksiyasi: 12 USD

Nima ko'rsatdi: 2.4, 2.5-bo'limlar. 1-bo'limdagi for sikl modul darajasida bo'lgani uchun j — hujayra emas, global o'zgaruvchi; natija baribir kechikkan, chunki lambda globalni ham chaqiruv paytida o'qiydi.

Misol 3 — Closure va obyektlarning hayot davri

python
"""Closure butun obyektni ushlaydi; self li callback; WeakMethod; o'zini chaqiruvchi ichki funksiya va gc."""

import gc
import weakref


class KattaJadval:
    def __init__(self, qatorlar):
        self.qatorlar = list(range(qatorlar))

    def __len__(self):
        return len(self.qatorlar)


print("=== 1. Closure butun obyektni tirik ushlaydi ===")


def sarlavha_yomon(jadval):
    return lambda: f"{len(jadval)} qator"


def sarlavha_yaxshi(jadval):
    soni = len(jadval)
    return lambda: f"{soni} qator"


for nom, yasovchi in [("jadvalni ushlaydi", sarlavha_yomon), ("faqat sonni", sarlavha_yaxshi)]:
    jadval = KattaJadval(100_000)
    havola = weakref.ref(jadval)
    sarlavha = yasovchi(jadval)
    del jadval
    gc.collect()
    ushlangan = [type(c.cell_contents).__name__ for c in sarlavha.__closure__]
    print(f"  {nom:18} {sarlavha()!r:15} jadval tirikmi: {havola() is not None}, hujayralarda: {ushlangan}")

print("\n=== 2. ⚠️ self ni ushlaydigan callback ===")


class Tugma:
    def __init__(self):
        self.ishlovchilar = []

    def bosilganda(self, f):
        self.ishlovchilar.append(f)

    def bos(self):
        natijalar = []
        for f in list(self.ishlovchilar):
            if isinstance(f, weakref.WeakMethod):
                metod = f()
                if metod is None:
                    self.ishlovchilar.remove(f)
                    continue
                f = metod
            natijalar.append(f())
        return natijalar


class Oyna:
    soni = 0

    def __init__(self, tugma, usul):
        Oyna.soni += 1
        self.raqam = Oyna.soni
        self.jadval = KattaJadval(10_000)
        if usul == "lambda":
            tugma.bosilganda(lambda: self.yangila())
        elif usul == "WeakMethod":
            tugma.bosilganda(weakref.WeakMethod(self.yangila))

    def yangila(self):
        return f"oyna {self.raqam} yangilandi"


for usul in ("lambda", "WeakMethod"):
    tugma = Tugma()
    havolalar = []
    for _ in range(3):
        oyna = Oyna(tugma, usul)
        havolalar.append(weakref.ref(oyna))
        del oyna
    gc.collect()
    tirik = sum(h() is not None for h in havolalar)
    print(f"  {usul:10}: 3 oyna yaratilib o'chirildi → tirik: {tirik}, tugma bosildi: {tugma.bos()}, "
          f"ro'yxatda qoldi: {len(tugma.ishlovchilar)}")

print("\n=== 3. ⚠️ O'zini chaqiruvchi ichki funksiya — aylanma havola ===")


def yurgizuvchi(jadval):
    def yur(n):
        return yur(n - 1) if n else len(jadval)
    return yur


gc.disable()
try:
    jadval = KattaJadval(1_000)
    havola = weakref.ref(jadval)
    yur = yurgizuvchi(jadval)
    print(f"  yur(5) = {yur(5)}, yur hujayralari: {yur.__code__.co_freevars}")
    print(f"  yur o'z hujayrasida o'zi: {yur.__closure__[1].cell_contents is yur}")
    del jadval, yur
    print(f"  del dan keyin (gc o'chiq) jadval tirikmi: {havola() is not None}")
    yigildi = gc.collect()
    print(f"  gc.collect() → {yigildi} obyekt yig'ildi, jadval tirikmi: {havola() is not None}")
finally:
    gc.enable()

print("\n=== 4. Obunani bekor qiluvchi closure ===")


class Hodisalar:
    def __init__(self):
        self.obunachilar = {}

    def obuna(self, nom, f):
        self.obunachilar.setdefault(nom, []).append(f)
        bekor_qilindi = False

        def bekor_qil():
            nonlocal bekor_qilindi
            if bekor_qilindi:
                return False
            self.obunachilar[nom].remove(f)
            bekor_qilindi = True
            return True

        return bekor_qil

    def chiqar(self, nom, *args):
        return [f(*args) for f in self.obunachilar.get(nom, [])]


TOLOV = "to'lov"
h = Hodisalar()
bekor1 = h.obuna(TOLOV, lambda s: f"SMS: {s:,}")
bekor2 = h.obuna(TOLOV, lambda s: f"email: {s:,}")
print(f"  {h.chiqar(TOLOV, 50_000)}")
print(f"  bekor1() → {bekor1()}, yana bekor1() → {bekor1()}")
print(f"  {h.chiqar(TOLOV, 70_000)}")

Natijaning muhim qismi:

text
=== 1. Closure butun obyektni tirik ushlaydi ===
  jadvalni ushlaydi  '100000 qator'  jadval tirikmi: True, hujayralarda: ['KattaJadval']
  faqat sonni        '100000 qator'  jadval tirikmi: False, hujayralarda: ['int']

=== 2. ⚠️ self ni ushlaydigan callback ===
  lambda    : 3 oyna yaratilib o'chirildi → tirik: 3, tugma bosildi: ['oyna 1 yangilandi', 'oyna 2 yangilandi', 'oyna 3 yangilandi'], ro'yxatda qoldi: 3
  WeakMethod: 3 oyna yaratilib o'chirildi → tirik: 0, tugma bosildi: [], ro'yxatda qoldi: 0

=== 3. ⚠️ O'zini chaqiruvchi ichki funksiya — aylanma havola ===
  yur(5) = 1000, yur hujayralari: ('jadval', 'yur')
  yur o'z hujayrasida o'zi: True
  del dan keyin (gc o'chiq) jadval tirikmi: True
  gc.collect() → 6 obyekt yig'ildi, jadval tirikmi: False

=== 4. Obunani bekor qiluvchi closure ===
  ['SMS: 50,000', 'email: 50,000']
  bekor1() → True, yana bekor1() → False
  ['email: 70,000']

Nima ko'rsatdi: 2.6-bo'lim. Callback ro'yxatiga lambda: self... qo'yish obyektni ro'yxat yashagan paytgacha ushlaydi; WeakMethod esa obyekt o'lganda o'zini ro'yxatdan chiqarib yuborish imkonini beradi.

Misol 4 — Amaliy: closure asosidagi yordamchilar va ularning klass muqobili

Real ilovada tez-tez uchraydigan uch yordamchi — debounce (tez-tez chaqiruvlardan faqat oxirgisini bajarish), throttle (davr ichida bir martadan ko'p emas) va memo — avval closure bilan, keyin klass bilan yoziladi va solishtiriladi. Vaqt deterministik bo'lishi uchun soxta soat ishlatiladi.

python
"""debounce, throttle, memo: closure va klass variantlari; holatni ko'rish, pickle, bir nechta amal."""

import pickle


class Soat:
    def __init__(self):
        self.t = 0.0

    def __call__(self):
        return self.t


soat = Soat()


def debounce(kutish, *, soat=soat):
    """Oxirgi chaqiruvdan keyin `kutish` soniya jimlik bo'lsa, `tekshir()` funksiyani bajaradi."""
    def dekorator(funksiya):
        kutilayotgan = None                # (vaqt, args)

        def chaqir(*args):
            nonlocal kutilayotgan
            kutilayotgan = (soat(), args)

        def tekshir():
            nonlocal kutilayotgan
            if kutilayotgan and soat() - kutilayotgan[0] >= kutish:
                _, args = kutilayotgan
                kutilayotgan = None
                return funksiya(*args)
            return None

        chaqir.tekshir = tekshir
        return chaqir
    return dekorator


def throttle(davr, *, soat=soat):
    def dekorator(funksiya):
        oxirgi = float("-inf")

        def orovchi(*args):
            nonlocal oxirgi
            if soat() - oxirgi >= davr:
                oxirgi = soat()
                return funksiya(*args)
            return "o'tkazib yuborildi"

        return orovchi
    return dekorator


def memo(funksiya):
    kesh = {}

    def orovchi(*args):
        if args not in kesh:
            kesh[args] = funksiya(*args)
        return kesh[args]

    orovchi.tozala = kesh.clear
    orovchi.hajm = lambda: len(kesh)
    return orovchi


class Memo:
    def __init__(self, funksiya):
        self.funksiya = funksiya
        self.kesh = {}

    def __call__(self, *args):
        if args not in self.kesh:
            self.kesh[args] = self.funksiya(*args)
        return self.kesh[args]


print("=== 1. debounce: qidiruv maydoniga yozish ===")
sorovlar = []


@debounce(0.3)
def qidir(matn):
    sorovlar.append(matn)
    return f"'{matn}' bo'yicha qidirildi"


for vaqt, harf in [(0.0, "p"), (0.1, "py"), (0.2, "pyt"), (0.25, "pyth"), (0.4, "pytho"), (0.5, "python")]:
    soat.t = vaqt
    qidir(harf)
    natija = qidir.tekshir()
    if natija:
        print(f"  t={vaqt}: {natija}")
soat.t = 0.85
print(f"  t=0.85: {qidir.tekshir()}")
print(f"  serverga so'rovlar: {sorovlar}  (6 ta harf, 1 ta so'rov)")

print("\n=== 2. throttle: skroll hodisalari ===")


@throttle(1.0)
def yukla(sahifa):
    return f"{sahifa}-sahifa yuklandi"


for vaqt, sahifa in [(10.0, 2), (10.3, 3), (10.9, 4), (11.0, 5), (11.5, 6), (12.2, 7)]:
    soat.t = vaqt
    print(f"  t={vaqt}: {yukla(sahifa)}")

print("\n=== 3. Holatni ko'rish: closure vs klass ===")


def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)


m1, m2 = memo(fib), Memo(fib)
for n in (10, 20, 10):
    m1(n), m2(n)
print(f"  closure: hujayralar {m1.__code__.co_freevars}, kesh hajmi (yordamchi orqali): {m1.hajm()}")
print(f"  klass:   vars → {sorted(vars(m2))}, kesh kalitlari: {sorted(m2.kesh)}")

print("\n=== 4. pickle ===")
for nom, obyekt in [("closure memo", m1), ("klass Memo", m2)]:
    try:
        tiklangan = pickle.loads(pickle.dumps(obyekt))
        print(f"  {nom}: ✅ tiklandi, kesh: {sorted(tiklangan.kesh)}")
    except (pickle.PicklingError, AttributeError) as e:
        print(f"  {nom}: ❌ {type(e).__name__}: {str(e).split(' <')[0]}")

print("\n=== 5. Bitta chaqiruvning ichki holati — yashirinlik afzallik ham ===")
m2.kesh[(10,)] = -1
print(f"  klass: tashqaridan buzish oson → m2(10) = {m2(10)}")
try:
    m1.kesh
except AttributeError:
    print(f"  closure: m1.kesh yo'q, faqat tozala/hajm orqali → m1(10) = {m1(10)}")

Natijaning muhim qismi:

text
=== 1. debounce: qidiruv maydoniga yozish ===
  t=0.85: 'python' bo'yicha qidirildi
  serverga so'rovlar: ['python']  (6 ta harf, 1 ta so'rov)

=== 2. throttle: skroll hodisalari ===
  t=10.0: 2-sahifa yuklandi
  t=10.3: o'tkazib yuborildi
  t=10.9: o'tkazib yuborildi
  t=11.0: 5-sahifa yuklandi
  t=11.5: o'tkazib yuborildi
  t=12.2: 7-sahifa yuklandi

=== 3. Holatni ko'rish: closure vs klass ===
  closure: hujayralar ('funksiya', 'kesh'), kesh hajmi (yordamchi orqali): 2
  klass:   vars → ['funksiya', 'kesh'], kesh kalitlari: [(10,), (20,)]

=== 4. pickle ===
  closure memo: ❌ PicklingError: Can't pickle local object
  klass Memo: ✅ tiklandi, kesh: [(10,), (20,)]

=== 5. Bitta chaqiruvning ichki holati — yashirinlik afzallik ham ===
  klass: tashqaridan buzish oson → m2(10) = -1
  closure: m1.kesh yo'q, faqat tozala/hajm orqali → m1(10) = 55

Nima ko'rsatdi: 2.7-bo'lim — closure holatni yashiradi va bitta amal uchun ixcham, klass holatni ochadi, pickle qilinadi va bir nechta amalni tabiiy ifodalaydi.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Closure o'zgaruvchi qiymatining nusxasini saqlaydi" Hujayraga havola saqlaydi — qiymat chaqiruv paytida o'qiladi
"Har ichki funksiyaning o'z nusxasi bor" Bir chaqiruvdagi barcha ichki funksiyalar bitta hujayrani bo'lishadi
"Tashqi funksiya qaytgach, uning o'zgaruvchilari yo'qoladi" Hujayralar ichki funksiyalar yashagan paytgacha yashaydi
"Generator ifodasida ham lambda'lar oxirgi qiymatni oladi" Darhol chaqirilsa — yo'q, generator dangasa
"except ... as e dagi e blokdan keyin ham bor" Blok oxirida o'chiriladi — closure'da NameError
"Closure faqat kerakli qismni ushlaydi" Butun obyektni ushlaydi
"super() hech qanday closure ishlatmaydi" Yashirin __class__ hujayrasidan o'qiydi
"Closure har doim klassdan yaxshiroq" Bitta amal uchun — ha; ochiq holat, pickle, bir nechta amal uchun — klass

6. Keng tarqalgan xatolar va yechimlari

1. Siklda closure yaratish

python
for b in tugmalar:
    b.bosilganda(lambda: ochir(b.id))       # ❌ hammasi oxirgi tugmani o'chiradi

for b in tugmalar:
    b.bosilganda(functools.partial(ochir, b.id))   # ✅

2. nonlocal ni unutish

python
def hisoblagich():
    n = 0
    def qosh():
        n += 1                              # ❌ UnboundLocalError — n lokal bo'lib qoldi
    return qosh

    def qosh():
        nonlocal n                          # ✅
        n += 1

3. Closure'da katta obyektni keraksiz ushlash

python
def hisobot(df):
    return lambda: f"{len(df)} qator"       # ❌ butun DataFrame xotirada qoladi

def hisobot(df):
    n = len(df)
    return lambda: f"{n} qator"             # ✅

4. Callback'da self

python
tugma.bosilganda(lambda: self.yangila())             # ❌ oyna hech qachon o'chmaydi

tugma.bosilganda(weakref.WeakMethod(self.yangila))   # ✅ yoki yopilganda obunani bekor qiling

5. except blokidagi e ni closure'da ishlatish

python
except ValueError as e:
    qayta_urinish = lambda: jurnal(e)       # ❌ NameError

except ValueError as e:
    xato = e
    qayta_urinish = lambda: jurnal(xato)    # ✅

6. Sukut argument yechimi imzoni "ifloslaydi"

python
ishlovchilar = [lambda x, i=i: x * i for i in range(3)]
ishlovchilar[0](5, 10)                      # ❌ tasodifan i qayta yozildi — xato yo'q

ishlovchilar = [functools.partial(operator.mul, i) for i in range(3)]   # ✅

7. Closure asosidagi obyektni pickle/multiprocessing ga berish

python
pool.map(yasa_ishlovchi(sozlama), malumot)  # ❌ PicklingError: Can't pickle local object

pool.map(functools.partial(ishla, sozlama), malumot)   # ✅ modul darajasidagi funksiya + partial

8. Bir nechta amal uchun closure'larni lug'atga yig'ish

python
def hisob(balans):
    def yech(s): ...
    def qosh(s): ...
    def qoldiq(): ...
    return {"yech": yech, "qosh": qosh, "qoldiq": qoldiq}   # ❌ klassni qo'lda yasash

class Hisob: ...                            # ✅ metodlar, repr, isinstance, pickle

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 7.8–7.9-darslar (o'tilgan): LEGB, __closure__, nonlocal
  • 8.7-dars (o'tilgan): super() — endi __class__ hujayrasi orqali tushunamiz
  • 10.8–10.11-darslar (o'tilgan): dekoratorlarning barcha qavatlari — closure
  • 10.13-dars: functools.partial va cache — closure muqobillari
  • 10.17-dars: deskriptorlar va WeakMethod ga o'xshash bog'langan metodlar
  • 14-qism: asyncio callback'lari va closure'dagi holat; iplar orasida hujayra bo'lishish
  • 11-qism: havolalar, axlat yig'uvchi va weakref — closure hayot davrining asosi
  • Tkinter/Qt: signal va callback'lar — 2.6-bo'limdagi tuzoq

8. Eng yaxshi amaliyotlar

  1. Closure'da faqat kerakli qiymatni ushlang — butun obyektni emas.

  2. Siklda callback yaratishda partial yoki fabrika funksiya — sukut argument emas.

  3. Uzoq yashaydigan ro'yxatga self ni ushlagan closure qo'ymang. WeakMethod yoki bekor qilish funksiyasini qaytaring.

  4. except ... as e dagi xatoni saqlash kerak bo'lsa — boshqa nomga ko'chiring.

  5. O'zini chaqiruvchi ichki funksiyadan ehtiyot bo'ling — gc o'chiq muhitda sizadi.

  6. Bitta amal + kichik holat — closure; bir nechta amal yoki ochiq holat — klass.

  7. pickle va multiprocessing kerak bo'lsa — closure emas, modul darajasidagi funksiya + partial yoki klass.

  8. Tuzatishda __closure__ va co_freevars ga qarang — closure nimani ushlab turganini aniq ko'rsatadi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
def yasash():
    n = 0
    def ol():
        return n
    def qosh():
        nonlocal n
        n += 1
        return n
    return ol, qosh
1.  ol, qosh = yasash()
    qosh()
    qosh()
    print(ol())
2.  a = yasash()
    b = yasash()
    a[1]()
    print(a[0](), b[0]())
3.  print([f() for f in [lambda: i for i in range(3)]])
4.  print([f() for f in (lambda: i for i in range(3))])
5.  print([f() for f in [lambda i=i: i for i in range(3)]])
6.  def f():
        x = 1
        g = lambda: x
        x = 2
        return g
    print(f()())
7.  ol, qosh = yasash()
    print(ol.__closure__[0] is qosh.__closure__[0])
8.  ol, qosh = yasash()
    ol.__closure__[0].cell_contents = 10
    print(qosh())
9.  print(yasash.__code__.co_cellvars, yasash()[0].__code__.co_freevars)
10. def f():
        try:
            1 / 0
        except ZeroDivisionError as e:
            g = lambda: type(e).__name__
        return g
    try:
        f()()
    except NameError:
        print("NameError")
11. class K:
        def m(self):
            return super().__str__()
    print(K.m.__code__.co_freevars)
12. def tashqi():
        x = "tashqi"
        def orta():
            x = "orta"
            def ichki():
                nonlocal x
                x = "ichki"
            ichki()
            return x
        return orta(), x
    print(tashqi())
Javoblar
  1. 2 — ol va qosh bitta hujayrani bo'lishadi
  2. 1 0 — har yasash() chaqiruvi yangi hujayra
  3. [2, 2, 2] — bitta i, sikl tugagach o'qildi
  4. [0, 1, 2] — generator dangasa, har lambda darhol chaqirildi
  5. [0, 1, 2] — sukut qiymat def paytida
  6. 2 — lambda o'zgaruvchini eslaydi
  7. True
  8. 11 — hujayraga yozilgan qiymat qosh ga ham ko'rinadi
  9. ('n',) ('n',)
  10. NameError — e blok oxirida o'chirildi
  11. ('__class__',) — super() uchun yashirin hujayra
  12. ('ichki', 'tashqi') — nonlocal eng yaqin tashqi funksiyadagi x ni (orta niki) oldi

Vazifa 2: Xatolarni tuzating

python
1.  def vazifalar_yarat(nomlar):
        vazifalar = []
        for nom in nomlar:
            vazifalar.append(lambda: print(f"{nom} bajarildi"))
        return vazifalar

2.  def sanagich():
        soni = 0
        def keyingi():
            soni += 1
            return soni
        return keyingi

3.  class Grafik:
        def __init__(self, manba):
            self.nuqtalar = manba.yukla()      # juda katta
            manba.yangilanganda(lambda: self.chiz())

4.  def ishlov(fayllar):
        xatolar = []
        for f in fayllar:
            try:
                oqi(f)
            except OSError as e:
                xatolar.append(lambda: f"{f}: {e}")
        return xatolar

5.  def daraja_filtri(jurnal):
        return lambda yozuv: yozuv.daraja >= jurnal.sozlama.daraja and len(jurnal.yozuvlar)
Javoblar
python
1.  def vazifalar_yarat(nomlar):
        return [functools.partial(print, f"{nom} bajarildi") for nom in nomlar]

2.  def sanagich():
        soni = 0
        def keyingi():
            nonlocal soni               # aks holda UnboundLocalError
            soni += 1
            return soni
        return keyingi

3.  class Grafik:
        def __init__(self, manba):
            self.nuqtalar = manba.yukla()
            manba.yangilanganda(weakref.WeakMethod(self.chiz))   # Grafik o'lsa, manba uni ushlab turmaydi
            # yoki: self._bekor_qil = manba.yangilanganda(...) va yopishda self._bekor_qil()

4.  def ishlov(fayllar):
        xatolar = []
        for f in fayllar:
            try:
                oqi(f)
            except OSError as e:
                xabar = f"{f}: {e}"         # f ham kechikkan, e esa o'chiriladi — hozirning o'zida satr
                xatolar.append(lambda xabar=xabar: xabar)
        return xatolar
    # yoki oddiyroq: xatolar.append(f"{f}: {e}") — closure umuman kerak emas

5.  def daraja_filtri(jurnal):
        sozlama = jurnal.sozlama        # butun jurnal (yozuvlari bilan) emas, faqat sozlama
        return lambda yozuv: yozuv.daraja >= sozlama.daraja
        # len(jurnal.yozuvlar) filtr mantiqiga aloqasi yo'q edi va jurnalni tirik ushlardi

Vazifa 3: Closure inspektori

closure_tahlil(f) funksiyasini yozing:

  1. co_freevars va __closure__ dan {nom: qiymat} lug'atini qaytarsin, bo'sh hujayralarni <bo'sh> deb belgilasin
  2. Har qiymatning taxminiy xotira hajmini tracemalloc yoki rekursiv sys.getsizeof bilan baholasin (ehtiyot bo'ling: getsizeof ichki obyektlarni hisoblamaydi)
  3. __wrapped__ zanjiri bo'ylab barcha qatlamlarning closure'larini ko'rsatsin
  4. 10.8–10.11-darslardagi dekoratorlarga qo'llab ko'ring

Vazifa 4: WeakCallback

Istalgan chaqiriladigan obyekt uchun zaif callback yozing:

  1. Bog'langan metod bo'lsa — WeakMethod, oddiy funksiya bo'lsa — kuchli havola (funksiyalar odatda modul darajasida yashaydi)
  2. Closure bo'lsa — hujayralarida self ga o'xshash obyektlar bor-yo'qligini tekshirib, ogohlantirish chiqarsin
  3. Obyekt o'lganda o_lganda= callback'i chaqirilsin (weakref.finalize)
  4. Misol 3 dagi Tugma bilan testlar yozing

Vazifa 5: Closure'siz dekorator

10.9-darsdagi qayta_urin(urinishlar, istisnolar) dekoratorini closure ishlatmasdan yozing:

  1. Sinf asosida (__init__, __call__, __get__)
  2. functools.partial asosida — ichki funksiyalarsiz
  3. Uch variantni (closure, klass, partial) tezlik (timeit), pickle va inspect.signature bo'yicha solishtiring
  4. Xulosangizni jadvalda yozing

Vazifa 6: Hujayralarni qayta bog'lash

qayta_bogla(f, **yangi) funksiyasini yozing:

  1. f ning kodi va boshqa atributlarini saqlagan holda, ko'rsatilgan erkin o'zgaruvchilari uchun yangi hujayralar bilan yangi funksiya yaratsin (types.FunctionType, types.CellType)
  2. Asl funksiyaning hujayralariga tegmasin
  3. Noma'lum nom — ValueError
  4. Testlarda qanday foydali bo'lishi va nega ishlab chiqarish kodida ishlatmaslik kerakligini izohlang

Vazifa 7: O'ylash

"Obyektlar — kambag'alning closure'lari" va "closure'lar — kambag'alning obyektlari" degan mashhur koan bor. Bu nimani anglatadi? Python'da closure va klass qanchalik teng kuchli, va nega Python dasturchilari holatli narsalar uchun ko'pincha klassni, JavaScript dasturchilari esa tarixan closure'ni tanlagan?

Javob

Qisqa javob: closure va obyekt bir xil g'oyaning ikki ko'rinishi — holat + shu holat ustidagi xatti-harakat. Closure'da holat hujayralarda, xatti-harakat — funksiya; obyektda holat atributlarda, xatti-harakat — metodlar. Nazariy jihatdan biri bilan ikkinchisini yasash mumkin. Amalda esa tanlovni til imkoniyatlari belgilaydi: Python klasslarni juda qulay qilgan va closure'larni yozishga cheklov qo'ygan (nonlocal, lambda — bitta ifoda), JavaScript esa uzoq vaqt klass sintaksisiga ega bo'lmagan va funksiyalarni asosiy qurilish bloki qilgan.

1. Koan

Anton van Straaten ga nisbat beriladigan qissada shogird ustoziga "obyektlar — kambag'alning closure'lari" deydi, keyinroq esa "closure'lar — kambag'alning obyektlari" degan xulosaga keladi. Ma'nosi: ikkalasini ham asosiy tushuncha deb olib, ikkinchisini undan qurish mumkin — va ikkala yo'nalishda ham nimadir yo'qotiladi.

2. Closure'dan obyekt

python
def hisob(balans):
    def yech(summa):
        nonlocal balans
        if summa > balans:
            raise ValueError("mablag' yetarli emas")
        balans -= summa
    def qoldiq():
        return balans
    return {"yech": yech, "qoldiq": qoldiq}

h = hisob(1000)
h["yech"](300)
h["qoldiq"]()         # 700

Bu — to'liq inkapsulyatsiyalangan obyekt: balans ga tashqaridan hech qanday yo'l bilan (hujayra ichiga kirmasdan) tegib bo'lmaydi. Lekin yo'qotilgan narsalar: meros, isinstance, repr, pickle, maxsus metodlar (h + boshqa), atribut sintaksisi.

3. Obyektdan closure

python
class Kopaytiruvchi:
    def __init__(self, n):
        self.n = n
    def __call__(self, x):
        return x * self.n

uch_barobar = Kopaytiruvchi(3)
uch_barobar(5)          # 15

Bu — closure'ning xatti-harakati: chaqiriladi va "eslab qolgan" holatni ishlatadi. Yo'qotilgan narsalar: qisqalik, tezlik (Python 3.14 da bu kompyuterda closure chaqiruvi ~70 ns, __call__ li nusxa ~140 ns atrofida o'lchandi — taxminan ikki barobar), va holatning yashirinligi (uch_barobar.n = 0 bilan buzish mumkin).

4. Python klassni nega afzal ko'radi

  • Sintaksis: class va metodlar — birinchi darajali, qulay; lambda esa faqat bitta ifoda, ko'p qatorli anonim funksiya yo'q
  • nonlocal talabi: closure holatini o'zgartirish uchun har safar e'lon kerak — bu ataylab qiyinlashtirish
  • Ma'lumotlar modeli: __repr__, __eq__, __len__ va boshqa protokollar (10.15-dars) faqat klasslar orqali
  • Introspeksiya va vositalar: vars(), dataclass, pickle, tur annotatsiyalari, IDE yordami
  • Madaniyat: "explicit is better than implicit" — ochiq atributlar va _ konvensiyasi, qat'iy yashirinlik o'rniga

Shu bilan birga Python dekoratorlar, callback'lar va fabrikalar uchun closure'ni tabiiy vosita sifatida ishlatadi — ular bitta amal uchun.

5. JavaScript closure'ni nega afzal ko'rgan

  • ES2015 gacha class sintaksisi yo'q edi; prototiplar bilan ishlash noqulay edi
  • Modul naqshi (IIFE — darhol chaqiriladigan funksiya) va closure — xususiy holatning yagona ishonchli usuli edi; #xususiy maydonlar faqat ES2022 da keldi
  • Brauzer hodisalari va asinxron callback'lar — funksiyani uzatish kundalik ish
  • Ko'p qatorli anonim funksiyalar (function () { ... }, () => { ... }) — qulay

Zamonaviy JavaScript'da class keng tarqalgan, React esa aksincha — klass komponentlaridan closure asosidagi hook'larga (useState) o'tdi. Ya'ni tanlov tilning imkoniyatlari va ekotizimi bilan birga o'zgaradi.

6. Qachon qaysi biri (Python)

Vaziyat Tanlov
Dekorator, callback, fabrika — bitta amal Closure
Argumentlarni qotirish functools.partial
Bir nechta amal, ochiq holat, repr, taqqoslash Klass
pickle, multiprocessing, konfiguratsiya sifatida saqlash Klass yoki modul funksiyasi + partial
Holatni tashqaridan buzib bo'lmasligi juda muhim Closure (Python'da yagona "haqiqiy" yashirinlik)
Issiq siklda millionlab chaqiruv Closure yoki partial + C funksiya

7. Xulosa

  1. Closure va obyekt — holat va xatti-harakatning ikki ifodasi; biri bilan ikkinchisini yasash mumkin
  2. Closure'dan yasalgan obyekt meros, protokollar va introspeksiyani yo'qotadi; obyektdan yasalgan closure qisqalik va tezlikni
  3. Python sintaksisi va ma'lumotlar modeli klasslarni holatli narsalar uchun tabiiy qiladi, closure'larni esa bitta amal uchun
  4. JavaScript tarixan klass va xususiy maydonlarsiz yashagani uchun closure'ga tayangan — va bu tanlov til rivojlanishi bilan o'zgarib bormoqda
  5. Tanlov mezoni: amallar soni, holat ochiqligi, pickle/protokollar kerakmi va tezlik

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda closure'larning ichki tuzilishi va hayot davrini o'rgandik.

Eng muhim uch fikr:

  1. Closure hujayra orqali ishlaydi — hujayra esa qiymat emas, quti. Tashqi funksiya MAKE_CELL bilan quti yaratadi, ichki funksiyalar uni __closure__ da ushlaydi va LOAD_DEREF bilan chaqiruv paytida o'qiydi. Bir chaqiruvda yaratilgan barcha ichki funksiyalar bitta qutini bo'lishadi, har yangi chaqiruv yangi quti beradi. super() ham yashirin __class__ hujayrasidan foydalanadi.

  2. Kechikkan bog'lanish va bo'sh hujayralar — shu tuzilishning oqibati. Siklda yaratilgan lambda'lar oxirgi qiymatni ko'radi, lekin darhol chaqirilgan generator ifodasida — yo'q. Yechim — partial yoki fabrika funksiya; sukut argument esa imzoni ochib qo'yadi. except ... as e blok oxirida e ni o'chiradi va uni ushlagan closure NameError beradi.

  3. Closure butun obyektlarni tirik ushlaydi. Faqat kerakli qiymatni hujayraga oling. Uzoq yashaydigan callback ro'yxatiga lambda: self... qo'yish obyektni o'chirmaydi — WeakMethod yoki bekor qilish funksiyasini ishlating. O'zini chaqiruvchi ichki funksiya aylanma havola yaratadi va faqat gc bilan tozalanadi. Bitta amal uchun closure, bir nechta amal va ochiq holat uchun klass tanlang.

Keyingi darsda functools modulining uch muhim vositasini chuqur o'rganamiz: cache va lru_cache qanday ishlaydi va qachon xotirani sizdiradi, partial closure o'rnini qanday bosadi va reduce qachon o'qilishi mumkin bo'lgan tanlov.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.12-dars: Closure chuqur — IlmHamroh