IlmHamroh
Python kursi/CPython ichki tuzilishi3/8-dars23 daqiqa
Mundarija (22)

12.3-dars: Frame va chaqiruv steki

12-QISM — CPYTHON ICHKI TUZILISHI · 3-dars


1. Kirish va motivatsiya

12.2-darsda instruksiyalarni o'qidik. Har bir instruksiya qayerdadir bajariladi: lokal o'zgaruvchilar qayerda turadi, stek qayerda, funksiya tugagach qayerga qaytiladi? Javob — freym (frame).

Real vaziyat. Ma'lumotlarni qayta ishlash servisi (Celery ishchisi) har vazifada 200 MB lik jadvalni yuklab, hisob-kitob qiladi. Xatolar hisobotini kun oxirida yuborish uchun jamoa ularni ro'yxatda saqladi:

python
except Exception as xato:
    self.xatolar.append(xato)

Ishchi har 3–4 soatda xotira yetishmay qulab tushdi. Sabab: istisno obyekti __traceback__ orqali xato sodir bo'lgan freymlarni ushlab turadi, freymlar esa o'zining barcha lokal o'zgaruvchilarini — jumladan 200 MB lik jadvalni. 60 ta saqlangan xato — 12 GB. Tuzatish: xato matnini saqlash (traceback.format_exception) yoki freymlarni tozalash (traceback.clear_frames).

Freymlarni tushunish nafaqat sizishlarni topishga, balki debuggerlar, logging'dagi "kim chaqirdi" ma'lumoti, rekursiya chegarasi va generatorlar qanday ishlashini tushunishga ham kalit.

Bu darsda:

  • Freym nima: kod, globallar, lokallar, stek, joriy instruksiya
  • Chaqiruv steki: f_back zanjiri, sys._getframe, inspect.stack
  • Generator freymlari: to'xtaganda stekdan uziladi
  • f_locals va locals(): PEP 667 (3.13+) semantikasi
  • Traceback obyektlari va freymlar orqali xotira sizishi
  • Rekursiya chegarasi va rekursiyani siklga aylantirish
  • Amaliy: chaqiruvchi ifodani ko'rsatadigan debug yordamchi

2. Nazariya — chuqur tushuntirish

2.1. Freym nima

Har bir funksiya chaqiruvi uchun interpretator freym yaratadi — shu chaqiruvning bajarilish holati:

 ┌─────────────── freym: hisobla(3) ───────────────┐
 │ kod obyekti      → hisobla.__code__  (12.1)     │
 │ globallar        → modul __dict__               │
 │ builtins         → builtins.__dict__            │
 │ lokal va hujayralar massivi: [soni, narx, jami] │  ← LOAD_FAST indeks bo'yicha
 │ qiymatlar steki:  [ ... ]  (co_stacksize)       │  ← 12.2 dagi stek
 │ joriy instruksiya → f_lasti                     │
 │ oldingi freym    → chaqiruvchi (f_back)         │
 └─────────────────────────────────────────────────┘

3.11+ ichki tuzilishi: freymlar ipning maxsus "ma'lumotlar stekida" ixcham C tuzilmalari sifatida yashaydi. Python'dagi frame obyekti faqat kerak bo'lganda yaratiladi — sys._getframe(), traceback, debugger, inspect. Shuning uchun oddiy funksiya chaqiruvi 3.11 dan boshlab ancha arzonlashdi.

2.2. Chaqiruv steki

python
def a(): b()
def b(): c()
def c(): ...        # shu yerda stek:  c ← b ← a ← <module>
  • Har freymning f_back i — uni chaqirgan freym
  • Har ipning o'z steki bor
  • Funksiya qaytganda freym stekdan chiqadi; Python frame obyekti yaratilgan bo'lsa, u havola bor ekan yashaydi (lokallari bilan!)
Vosita Nima beradi Narxi
sys._getframe(n) n qavat yuqoridagi freym Juda arzon
inspect.currentframe() Joriy freym (None bo'lishi mumkin — boshqa implementatsiyalar) Arzon
inspect.stack() Butun stek + manba qatorlari Qimmat — fayllarni o'qiydi
traceback.extract_stack() FrameSummary ro'yxati O'rtacha
sys._getframemodulename(n) Chaqiruvchi modul nomi Arzon

2.3. Freym atributlari

Atribut Ma'nosi
f_code Kod obyekti
f_locals Lokal nomlar (3.13+ da funksiyalar uchun — proksi, 2.5)
f_globals, f_builtins Global va builtins lug'atlari
f_back Chaqiruvchi freym yoki None
f_lineno Joriy qator
f_lasti Joriy instruksiya offseti (dis bilan bog'lash mumkin)
f_trace Qator bo'yicha kuzatuv funksiyasi (debuggerlar)
f_generator (3.14) Freymga egalik qiluvchi generator yoki korutina
clear() Lokal havolalarni tozalash (bajarilayotgan freymda — RuntimeError)

2.4. Generator freymlari

Generator (10.3-dars) — to'xtatib qo'yilgan freym:

Holat gi_frame.f_back
To'xtagan (GEN_SUSPENDED) None — stekka ulanmagan
Ishlayapti (next() ichida) next ni chaqirgan freym
Tugagan (GEN_CLOSED) gi_frame — None

Freym yield da stekdan uziladi va next() da boshqa joyga qayta ulanishi mumkin — shuning uchun generator kim chaqirganini "eslab qolmaydi". Korutinalar (14-qism) ham shu mexanizmda.

2.5. f_locals va locals() (PEP 667, 3.13+)

Funksiya ichida Modul/klass darajasida
frame.f_locals FrameLocalsProxy — haqiqiy lokallarga yozadi Oddiy dict (globallar)
locals() Har chaqiruvda yangi nusxa (dict) O'sha dict
python
def f():
    x = 1
    sys._getframe().f_locals["x"] = 2    # x endi 2 — proksi orqali yozildi
    locals()["x"] = 3                    # hech narsa — nusxaga yozildi
    return x                             # 2

Proksi xulqi:

  • Mavjud lokal yoki closure o'zgaruvchisini o'zgartirish — darhol ta'sir qiladi
  • Yangi kalit qo'shish — lekin oddiy kod uni ko'rmaydi (kompilyator bu nomni global deb biladi, 12.1-dars)
  • Lokalni o'chirish — ValueError

3.12 va undan oldin xulq boshqacha va nomuvofiq edi. Debugger yozmasangiz, lokallarni freym orqali o'zgartirmang.

2.6. Traceback va xotira

Istisno ko'tarilganda interpretator traceback zanjirini quradi — tashqi freymdan xato joyiga:

xato.__traceback__           tb (<module>, qator 30)
    .tb_next                 tb (ishlov_ber, qator 12)
        .tb_next             tb (hisobla, qator 5)   ← xato shu yerda
            .tb_next         None

Har tb da: tb_frame (freym), tb_lineno, tb_lasti.

Oqibat: istisno obyekti → traceback → freymlar → barcha lokal o'zgaruvchilar. Istisnoni saqlash — ular hammasini tirik saqlash.

Python o'zi bitta sikldan himoya qiladi: except E as xato: bloki tugaganda xato nomi avtomatik o'chiriladi (aks holda freym → xato → traceback → freym sikli paydo bo'ladi).

Yechim Qachon
traceback.format_exception(xato) — matnni saqlash Hisobot, log
traceback.TracebackException.from_exception(xato) Tuzilgan ma'lumot kerak, freymsiz
traceback.clear_frames(tb) Istisno obyekti kerak, lokallar emas
xato.with_traceback(None) Tracebacksiz nusxa

2.7. Rekursiya chegarasi

  • sys.getrecursionlimit() — sukut bo'yicha 1000 Python freymi
  • Oshsa — RecursionError (sizning kodingiz uchun oddiy istisno)
  • 3.12+ da Python → Python chaqiruvlari C stekini ishlatmaydi; C darajasidagi rekursiya alohida himoyalangan
  • sys.setrecursionlimit(n) — juda katta qiymat va C rekursiyasi (masalan, chuqur repr) jarayonni yiqitishi mumkin
  • CPython dum rekursiyasini optimallashtirmaydi (tail call) — chuqur rekursiyani siklga va aniq stekka aylantiring

2.8. Freymlar qayerda ishlatiladi

Vosita Freymdan nima oladi
logging funcName, lineno, stacklevel bilan chaqiruvchini
warnings.warn(stacklevel=2) Ogohlantirishni chaqiruvchi qatoriga bog'lash
pdb, IDE debuggerlari f_locals, f_lineno, f_trace, sys.monitoring (3.12+)
Profilerlar Chaqiruv zanjiri (18-qism)
pytest Xato paytidagi lokallarni ko'rsatish
dataclasses, typing, enum sys._getframemodulename — chaqiruvchi modul

3. Tez ma'lumotnoma

Freymga yetish

sys._getframe(0|1|n)     inspect.currentframe()
inspect.stack(context=0) traceback.extract_stack(limit=n)
sys._getframemodulename(1)

Atributlar

f_code f_locals f_globals f_builtins f_back f_lineno f_lasti f_trace f_generator clear()

Lokallar (3.13+)

frame.f_locals  → funksiyada proksi: yozish haqiqiy, o'chirish ValueError
locals()        → nusxa

Traceback

xato.__traceback__ → tb_frame, tb_lineno, tb_next
traceback.format_exception(xato)   traceback.clear_frames(tb)
except ... as xato: → blok oxirida xato o'chiriladi

Rekursiya

sys.getrecursionlimit() → 1000     RecursionError     tail call yo'q → sikl + stek

4. Batafsil misollar

Misol 1 — Freym anatomiyasi va chaqiruv steki

python
"""Joriy freym atributlari; f_back zanjiri; inspect.stack; qaytgandan keyin yashaydigan freym; generator freymi holatlari."""

import dis
import inspect
import sys


def stek_zanjiri():
    natija = []
    freym = sys._getframe(1)
    while freym is not None:
        natija.append(f"{freym.f_code.co_qualname}:+{freym.f_lineno - freym.f_code.co_firstlineno}")
        freym = freym.f_back
    return natija


class Buyurtma:
    def __init__(self, narx, soni):
        self.narx, self.soni = narx, soni

    def jami(self):
        chegirma = 0.1
        return self._hisobla(chegirma)

    def _hisobla(self, chegirma):
        oraliq = self.narx * self.soni
        global SAQLANGAN
        SAQLANGAN = sys._getframe()
        print("=== 1. Joriy freym ===")
        f = SAQLANGAN
        print(f"  f_code.co_qualname: {f.f_code.co_qualname}")
        print(f"  f_locals kalitlari: {sorted(f.f_locals)}")
        print(f"  f_globals is modul globallari: {f.f_globals is globals()}, f_builtins da 'len': {'len' in f.f_builtins}")
        instr = next(i for i in dis.get_instructions(f.f_code) if i.offset == f.f_lasti)
        print(f"  f_lineno: +{f.f_lineno - f.f_code.co_firstlineno}, f_lasti: {f.f_lasti} → {instr.opname}")
        print(f"  f_back: {f.f_back.f_code.co_qualname}")

        print("\n=== 2. Chaqiruv steki ===")
        print(f"  f_back zanjiri: {stek_zanjiri()}")
        print(f"  inspect.stack(context=0): {[(k.function, k.lineno - k.frame.f_code.co_firstlineno) for k in inspect.stack(context=0)]}")
        print(f"  chaqiruvchi modul: {sys._getframemodulename(1)}")
        return oraliq * (1 - chegirma)


def asosiy():
    return Buyurtma(15_000, 4).jami()


natija = asosiy()

print("\n=== 3. Qaytgandan keyin freym ===")
print(f"  natija: {natija}")
tirik = SAQLANGAN.f_locals
print(f"  saqlangan freym lokallari hali tirik: {sorted(tirik)}, chegirma={tirik['chegirma']}, oraliq={tirik['oraliq']}, self turi {type(tirik['self']).__name__}")
SAQLANGAN.clear()
print(f"  clear() dan keyin: {dict(SAQLANGAN.f_locals)}")

print("\n=== 4. Generator freymi ===")


def sanagich(n):
    for i in range(n):
        yield i, sys._getframe().f_back.f_code.co_name


def iste_molchi_a(g):
    return next(g)


def iste_molchi_b(g):
    return next(g)


g = sanagich(3)
print(f"  yaratildi: {inspect.getgeneratorstate(g)}, gi_frame.f_back: {g.gi_frame.f_back}")
print(f"  a orqali: {iste_molchi_a(g)}, b orqali: {iste_molchi_b(g)}  ← freym har safar boshqa joyga ulandi")
print(f"  to'xtagan: {inspect.getgeneratorstate(g)}, gi_frame.f_back: {g.gi_frame.f_back}, f_generator is g: {g.gi_frame.f_generator is g}")
g.close()
print(f"  yopilgan: {inspect.getgeneratorstate(g)}, gi_frame: {g.gi_frame}")

Natijaning muhim qismi:

text
=== 1. Joriy freym ===
  f_code.co_qualname: Buyurtma._hisobla
  f_locals kalitlari: ['chegirma', 'f', 'oraliq', 'self']
  f_globals is modul globallari: True, f_builtins da 'len': True
  f_lineno: +10, f_lasti: 568 → CALL
  f_back: Buyurtma.jami

=== 2. Chaqiruv steki ===
  f_back zanjiri: ['Buyurtma._hisobla:+14', 'Buyurtma.jami:+2', 'asosiy:+1', '<module>:+48']
  inspect.stack(context=0): [('_hisobla', 15), ('jami', 2), ('asosiy', 1), ('<module>', 48)]
  chaqiruvchi modul: __main__

=== 3. Qaytgandan keyin freym ===
  natija: 54000.0
  saqlangan freym lokallari hali tirik: ['chegirma', 'f', 'instr', 'oraliq', 'self'], chegirma=0.1, oraliq=60000, self turi Buyurtma
  clear() dan keyin: {}

=== 4. Generator freymi ===
  yaratildi: GEN_CREATED, gi_frame.f_back: None
  a orqali: (0, 'iste_molchi_a'), b orqali: (1, 'iste_molchi_b')  ← freym har safar boshqa joyga ulandi
  to'xtagan: GEN_SUSPENDED, gi_frame.f_back: None, f_generator is g: True
  yopilgan: GEN_CLOSED, gi_frame: None

Nima ko'rsatdi: 2.1–2.4-bo'limlar. Funksiya qaytgandan keyin ham saqlangan freym self, chegirma, oraliq ni ushlab turdi — clear() ularni bo'shatdi.

Misol 2 — f_locals, locals() va rekursiya

python
"""PEP 667: proksi yozadi, locals() nusxa; closure o'zgaruvchisi; chaqiruvchi lokalini o'zgartirish; rekursiya chegarasi; rekursiyadan siklga."""

import sys


print("=== 1. f_locals proksi va locals() nusxa ===")


def tajriba():
    x = 1
    proksi = sys._getframe().f_locals
    nusxa = locals()
    proksi["x"] = 2
    holat_proksi = x
    nusxa["x"] = 3
    holat_nusxa = x
    proksi["yangi_nom"] = "salom"
    try:
        del proksi["x"]
        ochirish = "o'chirildi"
    except ValueError as xato:
        ochirish = f"ValueError: {xato}"
    return type(proksi).__name__, type(nusxa).__name__, holat_proksi, holat_nusxa, proksi["yangi_nom"], ochirish


for nom, qiymat in zip(["proksi turi", "locals() turi", "proksi orqali yozgach x", "nusxaga yozgach x",
                        "yangi kalit proksida", "lokalni o'chirish"], tajriba()):
    print(f"  {nom:26} {qiymat}")
print(f"  modul darajasida f_locals is globals(): {sys._getframe().f_locals is globals()}")


def tashqi():
    hisob = 10

    def ichki():
        return hisob

    sys._getframe().f_locals["hisob"] = 99
    return ichki()


print(f"  closure o'zgaruvchisi proksi orqali: ichki() → {tashqi()}")

print("\n=== 2. Chaqiruvchining lokalini o'zgartirish (debugger kabi) ===")


def sozla(nom, qiymat):
    sys._getframe(1).f_locals[nom] = qiymat


def xizmat():
    rejim = "prod"
    sozla("rejim", "debug")
    return rejim


print(f"  xizmat() → {xizmat()!r}  ⚠️ ishlaydi, lekin kodni o'qib bo'lmas qiladi")

print("\n=== 3. Rekursiya chegarasi ===")
chuqurlik = 0


def chuqur(n):
    global chuqurlik
    chuqurlik = n
    return chuqur(n + 1)


print(f"  getrecursionlimit: {sys.getrecursionlimit()}")
for chegara in (100, 1000):
    sys.setrecursionlimit(chegara)
    try:
        chuqur(1)
    except RecursionError as xato:
        print(f"  chegara {chegara}: {chuqurlik} qavatda RecursionError: {xato}")
sys.setrecursionlimit(1000)

print("\n=== 4. Rekursiyadan siklga ===")


def daraxt(chuqurlik_):
    ildiz = {"nom": "0", "bolalar": []}
    tugun = ildiz
    for i in range(1, chuqurlik_):
        yangi = {"nom": str(i), "bolalar": []}
        tugun["bolalar"].append(yangi)
        tugun = yangi
    return ildiz


def tugunlar_rekursiv(t):
    return 1 + sum(tugunlar_rekursiv(b) for b in t["bolalar"])


def tugunlar_sikl(t):
    soni, stek = 0, [t]
    while stek:
        joriy = stek.pop()
        soni += 1
        stek.extend(joriy["bolalar"])
    return soni


chiziq = daraxt(5000)
try:
    tugunlar_rekursiv(chiziq)
except RecursionError:
    print("  rekursiv: 5000 qavatli daraxtda RecursionError")
print(f"  sikl + aniq stek: {tugunlar_sikl(chiziq)} tugun")
kichik = daraxt(50)
print(f"  kichik daraxtda ikkalasi teng: {tugunlar_rekursiv(kichik) == tugunlar_sikl(kichik) == 50}")

Natijaning muhim qismi:

text
=== 1. f_locals proksi va locals() nusxa ===
  proksi turi                FrameLocalsProxy
  locals() turi              dict
  proksi orqali yozgach x    2
  nusxaga yozgach x          2
  yangi kalit proksida       salom
  lokalni o'chirish          ValueError: cannot remove local variables from FrameLocalsProxy
  modul darajasida f_locals is globals(): True
  closure o'zgaruvchisi proksi orqali: ichki() → 99

=== 2. Chaqiruvchining lokalini o'zgartirish (debugger kabi) ===
  xizmat() → 'debug'  ⚠️ ishlaydi, lekin kodni o'qib bo'lmas qiladi

=== 3. Rekursiya chegarasi ===
  getrecursionlimit: 1000
  chegara 100: 99 qavatda RecursionError: maximum recursion depth exceeded
  chegara 1000: 999 qavatda RecursionError: maximum recursion depth exceeded

=== 4. Rekursiyadan siklga ===
  rekursiv: 5000 qavatli daraxtda RecursionError
  sikl + aniq stek: 5000 tugun
  kichik daraxtda ikkalasi teng: True

Nima ko'rsatdi: 2.5, 2.7-bo'limlar. Rekursiv variantdagi generator ifodasi har qavatda ikkinchi freym qo'shadi — shuning uchun u chegaraga oddiy rekursiyadan ham tezroq yetadi.

Misol 3 — Traceback va freymlar orqali sizish

python
"""Traceback zanjiri; except-as nomining o'chirilishi; saqlangan istisno katta obyektni ushlashi; format_exception, TracebackException, clear_frames."""

import gc
import traceback
import tracemalloc
import weakref


def hisobla(jadval):
    return jadval["ustun"] / 0


class Malumot:
    def __init__(self, n):
        self.baytlar = bytearray(n)


def ishlov_ber(n):
    jadval = {"ustun": 1, "malumot": Malumot(n)}
    return hisobla(jadval)


print("=== 1. Traceback zanjiri ===")
try:
    ishlov_ber(10)
except ZeroDivisionError as xato:
    tb = xato.__traceback__
    zanjir = []
    while tb is not None:
        zanjir.append(f"{tb.tb_frame.f_code.co_name}:+{tb.tb_lineno - tb.tb_frame.f_code.co_firstlineno}")
        tb = tb.tb_next
    print(f"  tashqidan ichkariga: {zanjir}")
    oxirgi = traceback.extract_tb(xato.__traceback__)[-1]
    print(f"  extract_tb oxirgisi: {oxirgi.name}, qator matni: {oxirgi.line!r}")
    ichki_freym = xato.__traceback__.tb_next.tb_next.tb_frame
    print(f"  xato freymining lokallari: {sorted(ichki_freym.f_locals)}")
print(f"  except blokidan keyin 'xato' nomi bormi: {'xato' in globals()}")

print("\n=== 2. Saqlangan istisnolar katta ma'lumotni ushlaydi ===")


class Ishchi:
    def __init__(self):
        self.xatolar = []

    def vazifa(self, n):
        try:
            ishlov_ber(n)
        except ZeroDivisionError as xato:
            self.xatolar.append(xato)


def olchov(ishchi, saqlash_usuli):
    gc.collect()
    tracemalloc.start()
    for _ in range(20):
        ishchi.vazifa(1_000_000)
        if saqlash_usuli is not None:
            ishchi.xatolar[-1] = saqlash_usuli(ishchi.xatolar[-1])
    joriy, _ = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    return joriy / 1e6


def matn(xato):
    return "".join(traceback.format_exception(xato))


def tuzilgan(xato):
    return traceback.TracebackException.from_exception(xato)


def freymsiz(xato):
    traceback.clear_frames(xato.__traceback__)
    return xato


for tavsif, usul in [("istisno obyektini o'zi", None), ("format_exception matni", matn),
                     ("TracebackException", tuzilgan), ("clear_frames bilan", freymsiz)]:
    print(f"  {tavsif:24} 20 ta 1 MB li vazifadan keyin: {olchov(Ishchi(), usul):6.2f} MB")

print("\n=== 3. Qaysi obyekt ushlab turibdi ===")
ishchi = Ishchi()
ishchi.vazifa(100)
jadval_ref = weakref.ref(ishchi.xatolar[0].__traceback__.tb_next.tb_frame.f_locals["jadval"]["malumot"])
print(f"  jadval['malumot'] tirik: {jadval_ref() is not None}")
tuzilgan_xato = traceback.TracebackException.from_exception(ishchi.xatolar[0])
ishchi.xatolar.clear()
gc.collect()
print(f"  xatolar tozalangach tirik: {jadval_ref() is not None}")
print(f"  TracebackException hali ham hisobot bera oladi: {list(tuzilgan_xato.format())[-1].strip()!r}")

Natijaning muhim qismi:

text
=== 1. Traceback zanjiri ===
  tashqidan ichkariga: ['<module>:+24', 'ishlov_ber:+2', 'hisobla:+1']
  extract_tb oxirgisi: hisobla, qator matni: 'return jadval["ustun"] / 0'
  xato freymining lokallari: ['jadval']
  except blokidan keyin 'xato' nomi bormi: False

=== 2. Saqlangan istisnolar katta ma'lumotni ushlaydi ===
  istisno obyektini o'zi   20 ta 1 MB li vazifadan keyin:  20.03 MB
  format_exception matni   20 ta 1 MB li vazifadan keyin:   0.02 MB
  TracebackException       20 ta 1 MB li vazifadan keyin:   0.02 MB
  clear_frames bilan       20 ta 1 MB li vazifadan keyin:   0.02 MB

=== 3. Qaysi obyekt ushlab turibdi ===
  jadval['malumot'] tirik: True
  xatolar tozalangach tirik: False
  TracebackException hali ham hisobot bera oladi: 'ZeroDivisionError: division by zero'

Nima ko'rsatdi: 2.6-bo'lim — kirishdagi Celery vaziyatining kichik nusxasi.

Misol 4 — Amaliy: chaqiruvchi ifodani ko'rsatadigan kor()

Debug paytida print(f"{x=}") yozish o'rniga ishlatiladigan kor(x, narx * soni) yordamchisini yozamiz. U chaqiruvchi freymdan funksiya nomi, qator va argumentlarning manba matnini oladi: f_lasti → CALL instruksiyasi → positions → manba satri → ast bilan argumentlar. Qo'shimcha: logging ning stacklevel parametri o'rab olingan funksiyalar uchun to'g'ri qatorni ko'rsatishi.

python
"""kor(*ifodalar): freym → CALL positions → manba → ast argumentlar; ko'p qatorli chaqiruv; qiymatni qaytaradi; logging stacklevel."""

import ast
import dis
import linecache
import logging
import sys


def kor(*qiymatlar):
    freym = sys._getframe(1)
    kod = freym.f_code
    instr = next(i for i in dis.get_instructions(kod) if i.offset == freym.f_lasti)
    p = instr.positions
    qatorlar = [linecache.getline(kod.co_filename, n) for n in range(p.lineno, p.end_lineno + 1)]
    if len(qatorlar) == 1:
        manba = qatorlar[0][p.col_offset:p.end_col_offset]
    else:
        manba = qatorlar[0][p.col_offset:] + "".join(qatorlar[1:-1]) + qatorlar[-1][:p.end_col_offset]
    chaqiruv = ast.parse(manba.strip(), mode="eval").body
    matnlar = [ast.unparse(arg) for arg in chaqiruv.args]
    joy = f"{kod.co_qualname}:+{p.lineno - kod.co_firstlineno}"
    juftlar = ", ".join(f"{m} = {q!r}" for m, q in zip(matnlar, qiymatlar))
    print(f"  kor| {joy} | {juftlar}")
    return qiymatlar[0] if len(qiymatlar) == 1 else qiymatlar


def buyurtma(narx, soni, chegirma=0.0):
    kor(narx, soni)
    jami = kor(narx * soni * (1 - chegirma))
    kor(
        jami > 100_000,
        round(jami / soni, 2),
    )
    return jami


class Savat:
    def __init__(self):
        self.mahsulotlar = {"olma": 3, "nok": 2}

    def soni(self):
        return kor(sum(self.mahsulotlar.values()))


print("=== 1. kor() ===")
natija = buyurtma(45_000, 3, chegirma=0.1)
Savat().soni()
print(f"  qaytgan qiymat ishlatildi: natija = {natija}")

print("\n=== 2. logging va stacklevel ===")


class QatorFormat(logging.Formatter):
    def format(self, yozuv):
        return f"  log| {yozuv.funcName}:{yozuv.lineno} | {yozuv.getMessage()}"


handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(QatorFormat())
log = logging.getLogger("demo")
log.addHandler(handler)
log.setLevel(logging.INFO)
log.propagate = False


def audit_oddiy(xabar):
    log.info(xabar)


def audit_togri(xabar):
    log.info(xabar, stacklevel=2)


def tolov_qabul(summa):
    audit_oddiy(f"to'lov: {summa} (stacklevel yo'q)")
    audit_togri(f"to'lov: {summa} (stacklevel=2)")


tolov_qabul(250_000)
qator_oddiy = audit_oddiy.__code__.co_firstlineno + 1
qator_tolov = tolov_qabul.__code__.co_firstlineno + 1
print(f"  audit_oddiy ichidagi log.info qatori: {qator_oddiy}; tolov_qabul dagi chaqiruvlar: {qator_tolov}, {qator_tolov + 1}")

Natijaning muhim qismi:

text
=== 1. kor() ===
  kor| buyurtma:+1 | narx = 45000, soni = 3
  kor| buyurtma:+2 | narx * soni * (1 - chegirma) = 121500.0
  kor| buyurtma:+3 | jami > 100000 = True, round(jami / soni, 2) = 40500.0
  kor| Savat.soni:+1 | sum(self.mahsulotlar.values()) = 5
  qaytgan qiymat ishlatildi: natija = 121500.0

=== 2. logging va stacklevel ===
  log| audit_oddiy:68 | to'lov: 250000 (stacklevel yo'q)
  log| tolov_qabul:77 | to'lov: 250000 (stacklevel=2)
  audit_oddiy ichidagi log.info qatori: 68; tolov_qabul dagi chaqiruvlar: 76, 77

Nima ko'rsatdi: 2.2, 2.3, 2.8-bo'limlar — f_lasti va positions orqali chaqiruvning aniq manbasi topildi, ko'p qatorli chaqiruv ham. stacklevel bo'lmasa log o'rovchi audit_oddiy ichidagi qatorni ko'rsatadi — foydasiz; stacklevel=2 esa haqiqiy chaqiruvchi tolov_qabul ni.


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

Noto'g'ri fikr To'g'risi
"Funksiya qaytgach lokallari darhol yo'qoladi" Freym obyektiga havola (traceback, saqlangan freym) bo'lsa — yashaydi
"Har chaqiruvda frame obyekti yaratiladi" 3.11+ da faqat so'ralganda
"locals() ga yozish o'zgaruvchini o'zgartiradi" Funksiyada — nusxa, ta'sir qilmaydi
"inspect.stack() arzon" Manba fayllarini o'qiydi — issiq yo'lda sys._getframe
"Istisnoni saqlash — kichik obyektni saqlash" Traceback orqali butun freymlar va lokallar
"except ... as e dan keyin e mavjud" Blok oxirida o'chiriladi
"setrecursionlimit(10**6) — rekursiya muammosini hal qiladi" Jarayon yiqilishi mumkin; siklga aylantiring
"Generator o'zini chaqirgan joyni eslaydi" To'xtaganda f_back = None, har next da qayta ulanadi

6. Keng tarqalgan xatolar va yechimlari

1. Istisnolarni keyinroq hisobot uchun saqlash

python
self.xatolar.append(xato)                               # ❌ freymlar va lokallar tirik
self.xatolar.append(traceback.TracebackException.from_exception(xato))   # ✅

2. Freymni global/atributda saqlash

python
self._chaqiruvchi = sys._getframe(1)                   # ❌ butun zanjir va lokallar tirik
self._chaqiruvchi = sys._getframe(1).f_code.co_qualname   # ✅ faqat kerakli ma'lumot

Freym kerak bo'lsa — funksiya oxirida del freym.

3. Issiq yo'lda inspect.stack()

python
def log(xabar):
    chaqiruvchi = inspect.stack()[1].function         # ❌ har chaqiruvda fayllarni o'qiydi
    chaqiruvchi = sys._getframe(1).f_code.co_name     # ✅

4. locals() orqali o'zgaruvchi yaratish

python
def f():
    locals()["natija"] = 5
    return natija                                      # ❌ NameError

Lug'at ishlating: qiymatlar["natija"] = 5.

5. Chuqur rekursiya uchun chegarani oshirish

python
sys.setrecursionlimit(1_000_000)                       # ❌ C darajasida stek to'lishi

Aniq stek bilan sikl (Misol 2) yoki functools.cache bilan pastdan-yuqoriga dinamik dasturlash.

6. O'rovchi funksiyada stacklevel ni unutish

python
def ogohlantir(xabar):
    warnings.warn(xabar, DeprecationWarning)           # ⚠️ ogohlantirish o'rovchi qatorini ko'rsatadi
    warnings.warn(xabar, DeprecationWarning, stacklevel=2)   # ✅ chaqiruvchini

7. except dan tashqarida istisno nomini ishlatish

python
try:
    ...
except ValueError as xato:
    pass
print(xato)                                            # ❌ NameError

Kerak bo'lsa boshqa nomga saqlang: oxirgi_xato = xato (va 1-xatoni esda tuting).

8. Generator freymidan chaqiruvchini topishga urinish

python
def gen():
    chaqiruvchi = sys._getframe(1)                     # ⚠️ har next() da boshqa bo'lishi mumkin

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 7-qism (o'tilgan): rekursiya va ko'rinish sohalari
  • 9-qism (o'tilgan): istisnolar va traceback
  • 10.3-dars (o'tilgan): generatorlar — to'xtatilgan freym
  • 11.4-dars (o'tilgan): havolalar va sizish manbalari
  • 12.2-dars (o'tilgan): stek va instruksiyalar
  • 12.8-dars: 3.11+ freymlarning arzonlashishi
  • 14-qism: korutinalar va asyncio vazifalari steki
  • 15.16-dars: logging — stacklevel, funcName
  • 18-qism: pdb, sys.monitoring, profilerlar

8. Eng yaxshi amaliyotlar

  1. Istisnolarni matn yoki TracebackException sifatida saqlang.

  2. Freym obyektlarini uzoq saqlamang — kerakli ma'lumotni oling va del.

  3. Chaqiruvchi ma'lumoti uchun sys._getframe, inspect.stack emas.

  4. O'rovchi funksiyalarda stacklevel bering — logging, warnings.

  5. locals() ni faqat o'qish uchun ishlating.

  6. Chuqur rekursiya — siklga.

  7. Freym orqali lokallarni o'zgartirish — faqat debug vositalarida.

  8. Uzoq ishlaydigan jarayonlarda xotira o'sishini tekshirganda saqlangan istisnolarni birinchi gumon qiling.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import sys
import inspect
import traceback
1.  def f():
        return sys._getframe().f_code.co_name
    print(f())
2.  def g():
        return sys._getframe(1).f_code.co_name
    def h():
        return g()
    print(h())
3.  def k():
        x = 1
        locals()["x"] = 2
        return x
    print(k())
4.  def m():
        x = 1
        sys._getframe().f_locals["x"] = 2
        return x
    print(m())
5.  print(type(sys._getframe().f_locals).__name__)
6.  def gen():
        yield sys._getframe().f_back is not None
    print(next(gen()))
7.  try:
        1 / 0
    except ZeroDivisionError as e:
        pass
    print("e" in dir())
8.  print(sys.getrecursionlimit())
9.  def t():
        raise ValueError("x")
    try:
        t()
    except ValueError as xato:
        tb = xato.__traceback__
    print(tb.tb_next.tb_frame.f_code.co_name)
10. print(len(traceback.extract_tb(tb)))
11. def p():
        return inspect.stack()[0].function
    print(p())
12. def q():
        fr = sys._getframe()
        try:
            del fr.f_locals["fr"]
        except ValueError:
            return "ValueError"
    print(q())
Javoblar
  1. f
  2. h
  3. 1 — locals() nusxa
  4. 2 — proksi haqiqiy lokalga yozadi
  5. dict — modul darajasida globallar lug'ati
  6. True — ishlayotgan generator freymi next chaqiruvchisiga ulangan
  7. False — except oxirida nom o'chiriladi
  8. 1000
  9. t
  10. 2 — modul va t
  11. p
  12. ValueError

Vazifa 2: Xatolarni tuzating

python
1.  class Hisobot:
        def __init__(self):
            self.xatolar = []
        def ishlat(self, vazifa):
            try:
                vazifa()
            except Exception as e:
                self.xatolar.append(e)

2.  def joriy_funksiya():
        return inspect.stack()[1][3]      # har so'rovda chaqiriladi

3.  def eskirgan(funksiya):
        def orov(*a, **k):
            warnings.warn(f"{funksiya.__name__} eskirgan", DeprecationWarning)
            return funksiya(*a, **k)
        return orov

4.  def tugunlar(t):
        return 1 + sum(tugunlar(b) for b in t.bolalar)   # 100 000 qavatli daraxt

5.  def sozlamalar():
        for k, v in os.environ.items():
            if k.startswith("APP_"):
                locals()[k.lower()] = v
        return app_port
Javoblar
python
1.  class Hisobot:
        def __init__(self):
            self.xatolar = []
        def ishlat(self, vazifa):
            try:
                vazifa()
            except Exception as e:
                self.xatolar.append(traceback.TracebackException.from_exception(e))   # freymlarsiz

2.  def joriy_funksiya():
        return sys._getframe(1).f_code.co_name          # fayl o'qimaydi

3.  def eskirgan(funksiya):
        @functools.wraps(funksiya)
        def orov(*a, **k):
            warnings.warn(f"{funksiya.__name__} eskirgan", DeprecationWarning, stacklevel=2)
            return funksiya(*a, **k)
        return orov

4.  def tugunlar(t):
        soni, stek = 0, [t]
        while stek:
            joriy = stek.pop()
            soni += 1
            stek.extend(joriy.bolalar)
        return soni

5.  def sozlamalar():
        return {k.lower(): v for k, v in os.environ.items() if k.startswith("APP_")}   # lug'at, locals() emas

Vazifa 3: Stek surati

stek_surati(chuqurlik=10) funksiyasini yozing:

  1. Joriy stekni sys._getframe bilan aylanib chiqsin (inspect siz)
  2. Har freym uchun: modul, co_qualname, qator, lokal o'zgaruvchilar nomlari va ularning repr ining qisqartirilgan ko'rinishi (50 belgigacha)
  3. Parol, token kabi nomli lokallarni *** bilan yashirsin
  4. Natijani JSON sifatida qaytarsin (log tizimiga yuborish uchun)
  5. Freymlarga havola qoldirmasligini weakref bilan tekshiring

Vazifa 4: Rekursiya chuqurligi o'lchagichi

@chuqurlik_kuzat dekoratorini yozing (10.8-dars):

  1. Funksiya rekursiv chaqirilganda joriy va maksimal chuqurlikni hisoblasin (sys._getframe zanjirida o'z kodini sanab)
  2. Maksimal chuqurlik chegaraning 80% iga yetsa ogohlantirish bersin
  3. Iplar uchun alohida hisoblasin (threading.local)
  4. Fibonachchi, Akkerman va daraxt aylanishida sinab, natijalarni jadvalda ko'rsating

Vazifa 5: Xavfsiz xatolar to'plovchisi

XatoToplovchi klassini yozing:

  1. with toplovchi.ushla(kontekst="..."): — blok ichidagi istisnoni ushlab, saqlasin
  2. Freymlarni ushlab turmasin, lekin har freymning lokal nomlari va qisqa repr larini saqlasin (Vazifa 3 dan foydalaning)
  3. Bir xil joydan (fayl + qator + tur) kelgan xatolarni guruhlasin va sonini hisoblasin
  4. hisobot() — Markdown jadval
  5. 10 000 ta xato bilan xotira o'sishini tracemalloc bilan tekshiring

Vazifa 6: Mini-debugger

sys.settrace yordamida (18-qismga tayyorgarlik):

  1. kuzat(funksiya, nuqtalar={qator: shart}) — ko'rsatilgan qatorlarda shart bajarilsa lokallarni chop etsin
  2. f_trace va frame.f_trace_lines ni to'g'ri sozlasin
  3. Kuzatuvni faqat berilgan funksiya va uning ichidagi chaqiruvlarga cheklasin
  4. 3.12+ dagi sys.monitoring bilan xuddi shu vositani yozib, tezlikni solishtiring

Vazifa 7: O'ylash

C va Rust'da chaqiruv freymi mashina stekida, ko'pincha registrlarda yashaydi va funksiya tugagach butunlay yo'qoladi; Java JVM freymlari ham stekda, lekin istisnolar stack trace ni matn ko'rinishida saqlaydi; Go esa goroutine'lar uchun o'sib-kichrayadigan steklar ishlatadi; Scheme va ba'zi funksional tillar dum chaqiruvlarini optimallashtiradi. Python freymlarining obyekt sifatida mavjudligi va traceback ularni ushlab turishi qanday afzallik va narxlarga ega, va nega Guido van Rossum dum chaqiruvlarini optimallashtirishni rad etgan?

Javob

Qisqa javob: Python freymni birinchi darajali obyekt sifatida ko'rsatadi — bu kuchli introspeksiya (debuggerlar, pytest'ning lokallarni ko'rsatishi, logging chaqiruvchisi, generatorlar) beradi, lekin narxi — istisnolar orqali xotira sizishi va (3.11 gacha) sekinroq chaqiruvlar. Dum chaqiruvlarini optimallashtirish esa aynan shu introspeksiyani buzadi: traceback'dan freymlar yo'qolardi.

1. C va Rust

  • Freym — mashina stekida bir necha bayt, qaytishda "yo'qoladi"
  • Optimallashtiruvchi o'zgaruvchilarni registrlarga qo'yadi yoki butunlay olib tashlaydi — debug axborotisiz stekni tiklash qiyin
  • Istisno (C++) yoki panik (Rust) paytida stack trace alohida mexanizm bilan yig'iladi (backtrace), lokal qiymatlar yo'q

2. Java

  • JVM freymlari stekda; Throwable stack trace ni ko'chirma (StackTraceElement[] — sinf, metod, qator) sifatida saqlaydi
  • Lokal o'zgaruvchilar saqlanmaydi — istisnoni saqlash xotirani sizdirmaydi (Python'dagi TracebackException ga o'xshash)
  • Narxi: xato paytidagi lokallarni ko'rish uchun debugger ulangan bo'lishi kerak

3. Go

  • Goroutine steki kichik (bir necha KB) boshlanib, kerak bo'lganda ko'chiriladi va o'sadi — millionlab goroutine
  • Python 3.11+ ichki freymlarni ixcham "ma'lumotlar stekida" saqlashi — shu g'oyaga yaqin: obyektni faqat so'ralganda yaratish

4. Scheme: dum chaqiruvlari

scheme
(define (sanoq n) (if (= n 0) 'tayyor (sanoq (- n 1))))   ; cheksiz chuqurlikda ham doimiy stek
  • Til standarti TCO ni talab qiladi — sikllar rekursiya bilan yoziladi

5. Python yondashuvi

Afzallik Narx
pytest, pdb, IDE: xato paytidagi lokallar Saqlangan istisno → lokallar tirik (sizish)
logging/warnings chaqiruvchini topadi sys._getframe implementatsiyaga bog'liq
Generator va korutinalar — to'xtatiladigan freym Freym obyekti xotirasi
To'liq, o'qiladigan traceback Rekursiya chegarasi, TCO yo'q

3.11 dan freym obyektlarini dangasa yaratish chaqiruv narxini kamaytirdi — afzalliklar saqlanib, narxning bir qismi olib tashlandi.

6. Nega TCO rad etilgan

Guido van Rossum 2009 yilgi maqolalarida quyidagi sabablarni keltirgan:

  1. Traceback buziladi: dum chaqiruvida freym qayta ishlatiladi — xato paytida qaysi chaqiruvlar zanjiri bo'lganini ko'rsatib bo'lmaydi
  2. Rekursiya — Python'da asosiy vosita emas: sikllar va iteratorlar idiomatik; TCO rekursiv uslubni rag'batlantirardi
  3. Dinamik nomlar: return f(x) da f ish vaqtida boshqa funksiyaga almashtirilishi mumkin — kompilyator dum chaqiruvini ishonchli aniqlay olmaydi
  4. Implementatsiyalar orasida moslik: TCO ga tayanadigan kod boshqa interpretatorlarda ishlamay qolardi

7. Xulosa

  1. Python freymlari — obyektlar: introspeksiya va generatorlar uchun kuchli, xotira uchun xavfli
  2. Java stack trace ni ko'chirma sifatida saqlaydi — Python'da buning o'xshashi TracebackException
  3. 3.11+ ichki freymlar ixcham va dangasa — Go'ning samarali steklari g'oyasiga yaqin
  4. TCO traceback va dinamik semantikani buzgani uchun yo'q — chuqur rekursiyani siklga aylantiring

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda freymlar va chaqiruv stekini o'rgandik.

Eng muhim uch fikr:

  1. Freym — bitta chaqiruvning bajarilish holati. U kod obyekti, globallar, lokallar massivi, qiymatlar steki, joriy instruksiya (f_lasti) va chaqiruvchiga havola (f_back) dan iborat. 3.11+ da freymlar ixcham ichki tuzilmalar, Python frame obyekti faqat so'ralganda yaratiladi. sys._getframe(n) arzon, inspect.stack() qimmat. Generator freymi yield da stekdan uziladi (f_back = None) va next da qayta ulanadi.

  2. f_locals va locals() endi aniq (PEP 667). Funksiyada frame.f_locals — haqiqiy lokallarga yozadigan proksi (o'chirish ValueError), locals() — nusxa; modul darajasida ikkalasi globallar lug'ati. Rekursiya chegarasi 1000 freym, dum chaqiruvlari optimallashtirilmaydi — chuqur rekursiyani aniq stekli siklga aylantiring.

  3. Traceback freymlarni, freymlar lokallarni ushlaydi. Saqlangan istisno butun zanjirdagi barcha lokal o'zgaruvchilarni tirik saqlaydi — uzoq ishlaydigan jarayonlarda klassik sizish. Hisobot uchun format_exception yoki TracebackException saqlang, kerak bo'lsa traceback.clear_frames. except ... as xato: nomi blok oxirida o'chiriladi — sikl oldini olish uchun.

Keyingi darsda obyektlarning C darajasidagi ko'rinishini o'rganamiz: PyObject va PyVarObject tuzilmalari, tur obyekti (PyTypeObject) va uning slotlari, ctypes bilan obyekt baytlarini o'qish va C kengaytma nima ekanligi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
12.3-dars: Frame va chaqiruv steki — IlmHamroh