Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Freym nima
- 2.2. Chaqiruv steki
- 2.3. Freym atributlari
- 2.4. Generator freymlari
- 2.5. f_locals va locals() (PEP 667, 3.13+)
- 2.6. Traceback va xotira
- 2.7. Rekursiya chegarasi
- 2.8. Freymlar qayerda ishlatiladi
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Freym anatomiyasi va chaqiruv steki
- Misol 2 — f_locals, locals() va rekursiya
- Misol 3 — Traceback va freymlar orqali sizish
- Misol 4 — Amaliy: chaqiruvchi ifodani ko'rsatadigan kor()
- 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
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:
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_backzanjiri,sys._getframe,inspect.stack - Generator freymlari: to'xtaganda stekdan uziladi
-
f_localsvalocals(): 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
def a(): b()
def b(): c()
def c(): ... # shu yerda stek: c ← b ← a ← <module>- Har freymning
f_backi — uni chaqirgan freym - Har ipning o'z steki bor
- Funksiya qaytganda freym stekdan chiqadi; Python
frameobyekti 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 |
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 # 2Proksi 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 NoneHar 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, chuqurrepr) 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() → nusxaTraceback
xato.__traceback__ → tb_frame, tb_lineno, tb_next
traceback.format_exception(xato) traceback.clear_frames(tb)
except ... as xato: → blok oxirida xato o'chiriladiRekursiya
sys.getrecursionlimit() → 1000 RecursionError tail call yo'q → sikl + stek4. Batafsil misollar
Misol 1 — Freym anatomiyasi va chaqiruv steki
"""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:
=== 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: NoneNima 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
"""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:
=== 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: TrueNima 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
"""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:
=== 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.
"""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:
=== 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, 77Nima 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
self.xatolar.append(xato) # ❌ freymlar va lokallar tirik
self.xatolar.append(traceback.TracebackException.from_exception(xato)) # ✅2. Freymni global/atributda saqlash
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()
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
def f():
locals()["natija"] = 5
return natija # ❌ NameError Lug'at ishlating: qiymatlar["natija"] = 5.
5. Chuqur rekursiya uchun chegarani oshirish
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
def ogohlantir(xabar):
warnings.warn(xabar, DeprecationWarning) # ⚠️ ogohlantirish o'rovchi qatorini ko'rsatadi
warnings.warn(xabar, DeprecationWarning, stacklevel=2) # ✅ chaqiruvchini7. except dan tashqarida istisno nomini ishlatish
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
def gen():
chaqiruvchi = sys._getframe(1) # ⚠️ har next() da boshqa bo'lishi mumkin7. 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
asynciovazifalari steki - 15.16-dars:
logging—stacklevel,funcName - 18-qism:
pdb,sys.monitoring, profilerlar
8. Eng yaxshi amaliyotlar
Istisnolarni matn yoki
TracebackExceptionsifatida saqlang.Freym obyektlarini uzoq saqlamang — kerakli ma'lumotni oling va
del.Chaqiruvchi ma'lumoti uchun
sys._getframe,inspect.stackemas.O'rovchi funksiyalarda
stacklevelbering —logging,warnings.locals()ni faqat o'qish uchun ishlating.Chuqur rekursiya — siklga.
Freym orqali lokallarni o'zgartirish — faqat debug vositalarida.
Uzoq ishlaydigan jarayonlarda xotira o'sishini tekshirganda saqlangan istisnolarni birinchi gumon qiling.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
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
fh1—locals()nusxa2— proksi haqiqiy lokalga yozadidict— modul darajasida globallar lug'atiTrue— ishlayotgan generator freyminextchaqiruvchisiga ulanganFalse—exceptoxirida nom o'chiriladi1000t2— modul vatpValueError
Vazifa 2: Xatolarni tuzating
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_portJavoblar
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() emasVazifa 3: Stek surati
stek_surati(chuqurlik=10) funksiyasini yozing:
- Joriy stekni
sys._getframebilan aylanib chiqsin (inspect siz) - Har freym uchun: modul,
co_qualname, qator, lokal o'zgaruvchilar nomlari va ularningreprining qisqartirilgan ko'rinishi (50 belgigacha) - Parol, token kabi nomli lokallarni
***bilan yashirsin - Natijani JSON sifatida qaytarsin (log tizimiga yuborish uchun)
- Freymlarga havola qoldirmasligini
weakrefbilan tekshiring
Vazifa 4: Rekursiya chuqurligi o'lchagichi
@chuqurlik_kuzat dekoratorini yozing (10.8-dars):
- Funksiya rekursiv chaqirilganda joriy va maksimal chuqurlikni hisoblasin (
sys._getframezanjirida o'z kodini sanab) - Maksimal chuqurlik chegaraning 80% iga yetsa ogohlantirish bersin
- Iplar uchun alohida hisoblasin (
threading.local) - Fibonachchi, Akkerman va daraxt aylanishida sinab, natijalarni jadvalda ko'rsating
Vazifa 5: Xavfsiz xatolar to'plovchisi
XatoToplovchi klassini yozing:
with toplovchi.ushla(kontekst="..."):— blok ichidagi istisnoni ushlab, saqlasin- Freymlarni ushlab turmasin, lekin har freymning lokal nomlari va qisqa
reprlarini saqlasin (Vazifa 3 dan foydalaning) - Bir xil joydan (fayl + qator + tur) kelgan xatolarni guruhlasin va sonini hisoblasin
hisobot()— Markdown jadval- 10 000 ta xato bilan xotira o'sishini
tracemallocbilan tekshiring
Vazifa 6: Mini-debugger
sys.settrace yordamida (18-qismga tayyorgarlik):
kuzat(funksiya, nuqtalar={qator: shart})— ko'rsatilgan qatorlarda shart bajarilsa lokallarni chop etsinf_tracevaframe.f_trace_linesni to'g'ri sozlasin- Kuzatuvni faqat berilgan funksiya va uning ichidagi chaqiruvlarga cheklasin
- 3.12+ dagi
sys.monitoringbilan 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;
Throwablestack trace ni ko'chirma (StackTraceElement[]— sinf, metod, qator) sifatida saqlaydi - Lokal o'zgaruvchilar saqlanmaydi — istisnoni saqlash xotirani sizdirmaydi (Python'dagi
TracebackExceptionga 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
(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:
- Traceback buziladi: dum chaqiruvida freym qayta ishlatiladi — xato paytida qaysi chaqiruvlar zanjiri bo'lganini ko'rsatib bo'lmaydi
- Rekursiya — Python'da asosiy vosita emas: sikllar va iteratorlar idiomatik; TCO rekursiv uslubni rag'batlantirardi
- Dinamik nomlar:
return f(x)dafish vaqtida boshqa funksiyaga almashtirilishi mumkin — kompilyator dum chaqiruvini ishonchli aniqlay olmaydi - Implementatsiyalar orasida moslik: TCO ga tayanadigan kod boshqa interpretatorlarda ishlamay qolardi
7. Xulosa
- Python freymlari — obyektlar: introspeksiya va generatorlar uchun kuchli, xotira uchun xavfli
- Java stack trace ni ko'chirma sifatida saqlaydi — Python'da buning o'xshashi
TracebackException - 3.11+ ichki freymlar ixcham va dangasa — Go'ning samarali steklari g'oyasiga yaqin
- 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:
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, Pythonframeobyekti faqat so'ralganda yaratiladi.sys._getframe(n)arzon,inspect.stack()qimmat. Generator freymiyieldda stekdan uziladi (f_back = None) vanextda qayta ulanadi.f_localsvalocals()endi aniq (PEP 667). Funksiyadaframe.f_locals— haqiqiy lokallarga yozadigan proksi (o'chirishValueError),locals()— nusxa; modul darajasida ikkalasi globallar lug'ati. Rekursiya chegarasi 1000 freym, dum chaqiruvlari optimallashtirilmaydi — chuqur rekursiyani aniq stekli siklga aylantiring.Traceback freymlarni, freymlar lokallarni ushlaydi. Saqlangan istisno butun zanjirdagi barcha lokal o'zgaruvchilarni tirik saqlaydi — uzoq ishlaydigan jarayonlarda klassik sizish. Hisobot uchun
format_exceptionyokiTracebackExceptionsaqlang, kerak bo'lsatraceback.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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!