Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. cache va lru_cache ichidan
- 2.2. Kalit qanday yasaladi
- 2.3. Nima keshlanmaydi va nima buziladi
- 2.4. Metodlarda kesh
- 2.5. Keshni boshqarish
- 2.6. partial chuqur
- 2.7. reduce — chap yig'ish
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Kesh kaliti, LRU tartibi va nima keshlanmaydi
- Misol 2 — Metodlarda kesh
- Misol 3 — partial chuqur
- Misol 4 — Amaliy: narx hisoblash quvuri
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
10.13-dars: functools: cache, partial, reduce
10-QISM — ILG'OR TIL IMKONIYATLARI · 13-dars
1. Kirish va motivatsiya
7.13-darsda @cache bilan rekursiv Fibonachchini tezlashtirgan, cache_info() ni ko'rgan edik. 7.11 va 7.12-darslarda partial va reduce bilan tanishgan edik. Endi ishlab chiqarishdagi xizmatdan parcha:
class NarxXizmati:
def __init__(self, sorov):
self.sorov = sorov # HTTP so'rov: sarlavhalar, tana, fayllar
@lru_cache(maxsize=None)
def chegirmalar(self, mijoz_id):
return list(bazadan_ol(mijoz_id))Bir hafta ishlagach server xotirasi tugadi. Ikki sababi bor:
- Kesh kaliti
selfni ham o'z ichiga oladi. Har so'rov uchun yaratilganNarxXizmatinusxasi keshda abadiy qoladi — u bilan birga butun HTTP so'rov ham - Qaytarilgan ro'yxat umumiy. Bir joyda
chegirmalar(5).append(...)qilinsa, keyingi barcha mijozlar "buzilgan" keshni oladi
Bundan tashqari, Python 3.13 dan 3.14 ga o'tgach, klass atributi sifatida saqlangan partial obyektlari to'satdan self ni birinchi argument sifatida ola boshladi.
functools vositalari bir qatorda ishlatiladi, lekin har birining o'z semantikasi bor: kesh kaliti qanday yasaladi, nima keshlanmaydi, partial qachon bog'lanadi, reduce qachon to'g'ri va qachon noto'g'ri natija beradi.
Bu darsda:
-
lru_cacheichidan: kalit yasash, LRU tartibi, iplar - Nima keshlanmaydi va keshning xavfli joylari
- Metodlarda kesh: xotira sizishi va teng obyektlar tuzog'i
partialchuqur: tekislash,Placeholder3.14-bob, deskriptor sifatida (3.14)reduce— chap yig'ish, neytral element, assotsiativlik va bo'laklab hisoblash
2. Nazariya — chuqur tushuntirish
2.1. cache va lru_cache ichidan
@lru_cache(maxsize=128, typed=False)
def f(x): ...maxsize=N |
maxsize=None (@cache) |
|
|---|---|---|
| Tuzilma | Lug'at + ikki tomonlama bog'langan ro'yxat | Faqat lug'at |
| To'lganda | Eng uzoq ishlatilmagan yozuv o'chiriladi | Hech qachon — cheksiz o'sadi |
| Tezlik | Biroz sekinroq (ro'yxatni yangilash) | Tezroq |
@cache — aynan @lru_cache(maxsize=None).
O'rovchi API:
f.cache_info() # CacheInfo(hits, misses, maxsize, currsize)
f.cache_clear() # hammasini o'chirish
f.cache_parameters() # {'maxsize': 128, 'typed': False}
f.__wrapped__ # asl funksiya — keshsiz chaqirishKesh tuzilmasi iplar uchun xavfsiz (buzilmaydi), lekin funksiyaning o'zi qulf ostida chaqirilmaydi: ikki ip bir vaqtda bir xil kalit bilan kelsa, ikkalasi ham hisoblaydi.
2.2. Kalit qanday yasaladi
Kalit — chaqiruv shakli, funksiya imzosi emas:
kalit = pozitsion argumentlar + (belgi) + nomli argumentlar (berilgan tartibda)| Chaqiruvlar | Bitta yozuvmi |
|---|---|
f(1) va f(x=1) |
pozitsion va nomli — boshqa kalit |
f(1) va f(1, 0) (y=0 sukut) |
sukut qiymatlar kalitga qo'shilmaydi |
f(x=1, y=0) va f(y=0, x=1) |
nomli argumentlar tartibi ham farq qiladi |
f(1, 2) va f(1.0, 2.0) |
1 == 1.0 va xeshlari teng |
f(1) va f(1.0) |
bitta int/str argument uchun tez yo'l — kalit argumentning o'zi, 1.0 esa kortejga o'raladi |
typed=True bilan f(1, 2) va f(1.0, 2.0) |
turlar ham kalitga qo'shiladi |
Amaliy xulosa: keshlangan funksiyani bir xil shaklda chaqiring yoki kichik "normallashtiruvchi" o'rovchi yozing.
Argumentlar xeshlanadigan bo'lishi shart: list, dict, set — TypeError: unhashable type.
2.3. Nima keshlanmaydi va nima buziladi
| Holat | Nima bo'ladi |
|---|---|
| Funksiya istisno ko'taradi | Keshlanmaydi — keyingi chaqiruv yana hisoblaydi va yana ko'taradi |
O'zgaruvchan qiymat qaytaradi (list, dict) |
Hamma chaqiruvchi bitta obyektni oladi — biri o'zgartirsa, hammasiga ta'sir qiladi |
| Yon ta'sirli funksiya (fayl yozish, xabar yuborish) | Yon ta'sir faqat birinchi chaqiruvda |
| Natija vaqtga, tasodifga, tashqi holatga bog'liq | Eski natija abadiy qaytadi |
| Ikki ip bir vaqtda bir xil kalit bilan | Ikkalasi ham hisoblaydi (qulf yo'q) |
| Ko'p jarayonli server (gunicorn, uvicorn workerlari) | Har jarayonning o'z keshi — ular bo'lishilmaydi |
Keshlashga yaroqli funksiya — sof: bir xil argumentlar uchun doim bir xil natija, yon ta'sirsiz. Natija o'zgaruvchan bo'lsa — tuple, frozenset yoki MappingProxyType qaytaring.
2.4. Metodlarda kesh
class Hisobot:
@lru_cache(maxsize=None)
def hisobla(self, oy): ...Muammolar:
- Xotira sizishi. Kalit
(self, oy)— kesh har birselfga kuchli havola saqlaydi.maxsize=Nonebo'lsa, nusxalar hech qachon o'chmaydi (flake8-bugbearbu naqshniB019deb belgilaydi) - Kesh klass darajasida umumiy. Barcha nusxalar bitta keshni to'ldiradi,
cache_clear()hammasini tozalaydi - Teng nusxalar natijani bo'lishadi. Kalit
selfning__eq__va__hash__iga tayanadi. Ikki nusxa teng deb hisoblansa-yu, hisob uchun muhim maydoni farq qilsa — boshqa nusxaning natijasi qaytadi
Yechimlar:
| Yechim | Qachon |
|---|---|
functools.cached_property (8.15-dars) |
Argumentsiz, nusxaga xos qiymat; __dict__ kerak |
__init__ da nusxaga xos kesh: self.hisobla = lru_cache()(self._hisobla) |
Argumentli, nusxa bilan birga o'lishi kerak (aylanma havola — gc tozalaydi) |
Modul darajasidagi keshlangan funksiya, kalit — xeshlanadigan ma'lumot (id, tuple) |
Natija nusxaga emas, ma'lumotga bog'liq |
weakref.WeakKeyDictionary asosidagi o'z keshingiz |
Nusxa kalit, lekin uni tirik ushlamaslik kerak |
2.5. Keshni boshqarish
maxsize— ishchi to'plam hajmiga yaqin tanlang;cache_info()dagihits / (hits + misses)ulushini kuzating- Eskirish (TTL) —
lru_cacheda yo'q. Oddiy usul: vaqt "chelagini" argument sifatida qo'shish:kurs(valyuta, int(time.time() // 300))— har 5 daqiqada yangi kalit - Testlarda — har testdan oldin
cache_clear(), yoki__wrapped__bilan keshsiz chaqirish
2.6. partial chuqur
p = partial(f, 1, b=2)
p.func, p.args, p.keywords # f, (1,), {'b': 2}
p(3, c=4) # f(1, 3, b=2, c=4)Qoidalar:
- Pozitsion argumentlar qo'shiladi: saqlanganlar oldinda, chaqiruvdagilar keyin
- Nomli argumentlarni chaqiruvda qayta yozish mumkin:
partial(f, c=5)(1, 2, c=7)→c=7 partial(partial(f, 1), 2)— tekislanadi:func—f,args—(1, 2)inspect.signature(p)qolgan parametrlarni ko'rsatadipickleqilinadi (agarfuncva argumentlar pickle bo'lsa)
Placeholder (Python 3.14): o'rtadagi pozitsion argumentni bo'sh qoldirish:
from functools import partial, Placeholder as _
ikkiga_bol = partial(operator.truediv, _, 2) # x / 2
ikkiga_bol(10) # 5.0
partial(f, 1, _) # ❌ TypeError: trailing Placeholders are not allowedIlgari buning uchun lambda x: operator.truediv(x, 2) yozish kerak edi.
Deskriptor sifatida (Python 3.14): partial endi metod deskriptori — klass atributi bo'lsa, oddiy funksiya kabi self ni bog'laydi:
class Hujjat:
saqla = partial(yoz, "json")
Hujjat().saqla("a.json") # 3.14: yoz("json", <Hujjat>, "a.json")3.14 gacha klass atributidagi partial self ni bog'lamas edi — shuning uchun eski kod yangi versiyada boshqacha ishlashi mumkin. Eski xatti-harakat kerak bo'lsa — staticmethod(partial(...)). Metod uchun maxsus vosita — partialmethod: u self ni birinchi argument qilib qo'yadi, saqlangan argumentlar undan keyin keladi.
partial va lambda:
partial(f, x) |
lambda: f(x) |
|
|---|---|---|
| Qiymat qachon olinadi | Yaratilganda (erta) | Chaqirilganda (kechikkan, 10.12-dars) |
| Ichini ko'rish | .func, .args, .keywords |
__closure__ orqali |
pickle |
||
| Imzo | Qolgan parametrlar | Lambda'ning o'z imzosi |
| Moslashuvchanlik | Faqat argument qotirish | Istalgan ifoda |
2.7. reduce — chap yig'ish
reduce(f, [a, b, c, d], boshlangich) = f(f(f(f(boshlangich, a), b), c), d)Bo'sh ketma-ketlik: boshlang'ich qiymatsiz — TypeError: reduce() of empty iterable with no initial value. Python 3.14 dan initial= nomli argument sifatida ham berilishi mumkin.
Neytral element. To'g'ri boshlang'ich qiymat — amalning neytral elementi: natijani o'zgartirmaydigan qiymat.
| Amal | Neytral element |
|---|---|
+ |
0 |
* |
1 |
max |
float("-inf") |
min |
float("inf") |
lug'at birlashtirish | |
{} |
| funksiya kompozitsiyasi | lambda x: x |
math.gcd |
0 |
Assotsiativlik. Agar f(f(a, b), c) == f(a, f(b, c)) bo'lsa, amal assotsiativ — ketma-ketlikni istalgan joydan bo'laklarga bo'lib, bo'laklarni alohida (masalan, parallel) yig'ib, keyin natijalarni yig'ish mumkin. Assotsiativ amal + neytral element — monoid; MapReduce, Spark va concurrent.futures bilan parallel hisoblash shu xossaga tayanadi.
| Amal | Assotsiativmi | Bo'laklab yig'ish to'g'rimi |
|---|---|---|
+, *, max, min, gcd |
||
Lug'at birlashtirish | |
(tartib saqlansa) | |
| Satr/ro'yxat qo'shish | (tartib saqlansa) | |
-, /, ** |
||
O'rtacha qiymat (a + b) / 2 |
— (yig'indi, soni) juftligini yig'ing |
O'ng yig'ish kerak bo'lsa: reduce(lambda acc, x: f(x, acc), reversed(seq), boshlangich).
Oraliq natijalar kerak bo'lsa: itertools.accumulate(seq, f, initial=...).
Qachon reduce: kompozitsiya, birlashtirish (lug'atlar, to'plamlar, intervallar), gcd/lcm, holat mashinasi qadamlari. sum, max, math.prod, "".join, any/all bor joyda — ular.
3. Tez ma'lumotnoma
Kesh
@cache # = lru_cache(maxsize=None)
@lru_cache(maxsize=256, typed=False)
f.cache_info() / f.cache_clear() / f.cache_parameters() / f.__wrapped__Kalit = chaqiruv shakli: f(1) ≠ f(x=1) ≠ f(1, 0)
Keshlanmaydi: istisnolar
Umumiy: qaytarilgan o'zgaruvchan obyekt
Metodda: self kalitda → nusxalar tirik, teng nusxalar natijani bo'lishadi
Iplar: tuzilma xavfsiz, hisob takrorlanishi mumkin
Jarayonlar: har birining o'z keshi
TTL: vaqt chelagi argument sifatidapartial
p = partial(f, 1, b=2) # p.func, p.args, p.keywords
p(3, b=9) # nomli qayta yoziladi
partial(f, Placeholder, 2) # 3.14: o'rtadagi joy
staticmethod(partial(...)) # 3.14: klassda self bog'lanmasin
partialmethod(f, ...) # metod uchunreduce
reduce(f, seq) # bo'sh bo'lsa TypeError
reduce(f, seq, neytral) # yoki initial=neytral (3.14)
accumulate(seq, f, initial=...) # oraliq natijalar
# bo'laklab/parallel — faqat assotsiativ amal + neytral element4. Batafsil misollar
Misol 1 — Kesh kaliti, LRU tartibi va nima keshlanmaydi
"""Kalit shakli, typed, LRU o'chirish tartibi, istisnolar, o'zgaruvchan natija va iplar."""
import threading
from functools import cache, lru_cache
print("=== 1. Kalit — chaqiruv shakli ===")
hisoblangan = []
@lru_cache(maxsize=None)
def narx(mahsulot, soni=1):
hisoblangan.append((mahsulot, soni))
return 12_000 * soni
chaqiruvlar = [
("narx('olma')", lambda: narx("olma")),
("narx(mahsulot='olma')", lambda: narx(mahsulot="olma")),
("narx('olma', 1)", lambda: narx("olma", 1)),
("narx('olma', soni=1)", lambda: narx("olma", soni=1)),
("narx(soni=1, mahsulot='olma')", lambda: narx(soni=1, mahsulot="olma")),
("narx('olma') # takror", lambda: narx("olma")),
]
for tavsif, chaqir in chaqiruvlar:
oldin = narx.cache_info().misses
chaqir()
print(f" {tavsif:32} {'hisoblandi' if narx.cache_info().misses > oldin else 'keshdan'}")
print(f" {narx.cache_info()} — bir xil natija uchun 5 ta yozuv")
print("\n=== 2. Teng qiymatlar, typed va tez yo'l ===")
@lru_cache(maxsize=None)
def ikki(x, y):
return x + y
@lru_cache(maxsize=None)
def bitta(x):
return x
@lru_cache(maxsize=None, typed=True)
def ikki_typed(x, y):
return x + y
for f, argumentlar in [(ikki, [(1, 2), (1.0, 2.0), (True, 2)]),
(ikki_typed, [(1, 2), (1.0, 2.0), (True, 2)]),
(bitta, [(1,), (1.0,), (1,)])]:
for a in argumentlar:
f(*a)
ci = f.cache_info()
print(f" {f.__name__:10} {str(argumentlar):28} hits={ci.hits}, yozuvlar={ci.currsize}")
print("\n=== 3. LRU o'chirish tartibi (maxsize=2) ===")
iz = []
@lru_cache(maxsize=2)
def kv(x):
iz.append(x)
return x * x
for x in [1, 2, 1, 3, 2, 1]:
oldin = len(iz)
kv(x)
holat = "hisoblandi" if len(iz) > oldin else "keshdan"
print(f" kv({x}) {holat}")
print(f" {kv.cache_info()}, {kv.cache_parameters()}")
print("\n=== 4. ⚠️ Istisnolar keshlanmaydi ===")
urinishlar = {"n": 0}
@cache
def kurs_ol(valyuta):
urinishlar["n"] += 1
raise ConnectionError(f"{valyuta}: server javob bermadi")
for _ in range(3):
try:
kurs_ol("USD")
except ConnectionError:
pass
print(f" 3 chaqiruv → funksiya {urinishlar['n']} marta ishladi, {kurs_ol.cache_info()}")
print("\n=== 5. ⚠️ O'zgaruvchan natija umumiy ===")
@cache
def ruxsatlar(rol):
return ["o'qish"] if rol == "mehmon" else ["o'qish", "yozish"]
@cache
def ruxsatlar_xavfsiz(rol):
return ("o'qish",) if rol == "mehmon" else ("o'qish", "yozish")
r = ruxsatlar("mehmon")
r.append("o'chirish") # bitta foydalanuvchi uchun "vaqtincha"
print(f" keyingi mehmon oladi: {ruxsatlar('mehmon')}")
try:
ruxsatlar_xavfsiz("mehmon").append("o'chirish")
except AttributeError as e:
print(f" tuple qaytarilsa: AttributeError — {e}")
print("\n=== 6. ⚠️ Iplar: bir xil kalit ikki marta hisoblanadi ===")
toliq = threading.Barrier(2)
hisoblaganlar = []
@cache
def ogir_hisobot(oy):
hisoblaganlar.append(threading.current_thread().name)
toliq.wait() # ikkala ip ham hisob ichida uchrashadi
return f"{oy}-oy"
iplar = [threading.Thread(target=ogir_hisobot, args=(9,), name=f"ip-{i}") for i in range(2)]
for t in iplar:
t.start()
for t in iplar:
t.join()
print(f" hisoblagan iplar soni: {len(hisoblaganlar)}, {ogir_hisobot.cache_info()}")Natijaning muhim qismi:
=== 1. Kalit — chaqiruv shakli ===
narx('olma') hisoblandi
narx(mahsulot='olma') hisoblandi
narx('olma', 1) hisoblandi
narx('olma', soni=1) hisoblandi
narx(soni=1, mahsulot='olma') hisoblandi
narx('olma') # takror keshdan
CacheInfo(hits=1, misses=5, maxsize=None, currsize=5) — bir xil natija uchun 5 ta yozuv
=== 2. Teng qiymatlar, typed va tez yo'l ===
ikki [(1, 2), (1.0, 2.0), (True, 2)] hits=2, yozuvlar=1
ikki_typed [(1, 2), (1.0, 2.0), (True, 2)] hits=0, yozuvlar=3
bitta [(1,), (1.0,), (1,)] hits=1, yozuvlar=2
=== 3. LRU o'chirish tartibi (maxsize=2) ===
kv(1) hisoblandi
kv(2) hisoblandi
kv(1) keshdan
kv(3) hisoblandi
kv(2) hisoblandi
kv(1) hisoblandi
CacheInfo(hits=1, misses=5, maxsize=2, currsize=2), {'maxsize': 2, 'typed': False}
=== 4. ⚠️ Istisnolar keshlanmaydi ===
3 chaqiruv → funksiya 3 marta ishladi, CacheInfo(hits=0, misses=3, maxsize=None, currsize=0)
=== 5. ⚠️ O'zgaruvchan natija umumiy ===
keyingi mehmon oladi: ["o'qish", "o'chirish"]
tuple qaytarilsa: AttributeError — 'tuple' object has no attribute 'append'
=== 6. ⚠️ Iplar: bir xil kalit ikki marta hisoblanadi ===
hisoblagan iplar soni: 2, CacheInfo(hits=0, misses=2, maxsize=None, currsize=1)Nima ko'rsatdi: 2.1, 2.2, 2.3-bo'limlar. 2-bo'limda bitta(1) va bitta(1.0) — teng qiymatlar bo'lsa ham alohida yozuv: bitta int argument uchun kalit kortejsiz yasaladi.
Misol 2 — Metodlarda kesh
"""lru_cache metodda: nusxalar tirik qoladi, teng nusxalar natijani bo'lishadi; to'rt yechim."""
import gc
import weakref
from dataclasses import dataclass, field
from functools import cache, cached_property, lru_cache
class KattaSorov:
def __init__(self):
self.tana = bytearray(1_000_000)
print("=== 1. ⚠️ Xotira sizishi ===")
class NarxXizmati:
def __init__(self, sorov):
self.sorov = sorov
@lru_cache(maxsize=None)
def chegirma(self, mijoz_id):
return mijoz_id % 7
havolalar = []
for i in range(5):
xizmat = NarxXizmati(KattaSorov())
xizmat.chegirma(i)
havolalar.append(weakref.ref(xizmat))
del xizmat
gc.collect()
print(f" 5 so'rov tugadi → tirik xizmatlar: {sum(h() is not None for h in havolalar)}, "
f"{NarxXizmati.chegirma.cache_info()}")
NarxXizmati.chegirma.cache_clear()
gc.collect()
print(f" cache_clear() dan keyin tirik: {sum(h() is not None for h in havolalar)} ⚠️ hamma nusxalar uchun bitta kesh")
print("\n=== 2. ⚠️ Teng nusxalar natijani bo'lishadi ===")
@dataclass(frozen=True)
class Hisobot:
oy: int
til: str = field(compare=False) # taqqoslashda hisobga olinmaydi
@lru_cache(maxsize=None)
def sarlavha(self):
return {"uz": f"{self.oy}-oy hisoboti", "en": f"Report for month {self.oy}"}[self.til]
uz, en = Hisobot(9, "uz"), Hisobot(9, "en")
print(f" uz == en: {uz == en}, hash teng: {hash(uz) == hash(en)}")
print(f" uz.sarlavha(): {uz.sarlavha()!r}")
print(f" en.sarlavha(): {en.sarlavha()!r} ⚠️ inglizcha kutilgan edi")
print("\n=== 3. Yechimlar ===")
class A_CachedProperty:
def __init__(self, sorov, mijoz_id):
self.sorov, self.mijoz_id = sorov, mijoz_id
@cached_property
def chegirma(self):
return self.mijoz_id % 7
class B_NusxaKeshi:
def __init__(self, sorov):
self.sorov = sorov
self.chegirma = lru_cache(maxsize=64)(self._chegirma)
def _chegirma(self, mijoz_id):
return mijoz_id % 7
@cache
def _chegirma_hisobla(mijoz_id):
return mijoz_id % 7
class C_ModulFunksiyasi:
def __init__(self, sorov):
self.sorov = sorov
def chegirma(self, mijoz_id):
return _chegirma_hisobla(mijoz_id)
class D_ZaifKesh:
_kesh = weakref.WeakKeyDictionary()
def __init__(self, sorov):
self.sorov = sorov
def chegirma(self, mijoz_id):
nusxa_keshi = D_ZaifKesh._kesh.setdefault(self, {})
if mijoz_id not in nusxa_keshi:
nusxa_keshi[mijoz_id] = mijoz_id % 7
return nusxa_keshi[mijoz_id]
for nom, yarat, chaqir in [
("cached_property", lambda i: A_CachedProperty(KattaSorov(), i), lambda x, i: x.chegirma),
("nusxa keshi", lambda i: B_NusxaKeshi(KattaSorov()), lambda x, i: x.chegirma(i)),
("modul funksiyasi", lambda i: C_ModulFunksiyasi(KattaSorov()), lambda x, i: x.chegirma(i)),
("WeakKeyDictionary", lambda i: D_ZaifKesh(KattaSorov()), lambda x, i: x.chegirma(i)),
]:
havolalar = []
for i in range(5):
x = yarat(i)
chaqir(x, i)
chaqir(x, i)
havolalar.append(weakref.ref(x))
del x
darhol = sum(h() is not None for h in havolalar)
gc.collect()
keyin = sum(h() is not None for h in havolalar)
print(f" {nom:18} tirik: del dan keyin darhol {darhol}, gc.collect() dan keyin {keyin}")
print(" (nusxa keshi: self → lru_cache o'rovchisi → bog'langan metod → self — sikl, faqat gc tozalaydi)")Natijaning muhim qismi:
=== 1. ⚠️ Xotira sizishi ===
5 so'rov tugadi → tirik xizmatlar: 5, CacheInfo(hits=0, misses=5, maxsize=None, currsize=5)
cache_clear() dan keyin tirik: 0 ⚠️ hamma nusxalar uchun bitta kesh
=== 2. ⚠️ Teng nusxalar natijani bo'lishadi ===
uz == en: True, hash teng: True
uz.sarlavha(): '9-oy hisoboti'
en.sarlavha(): '9-oy hisoboti' ⚠️ inglizcha kutilgan edi
=== 3. Yechimlar ===
cached_property tirik: del dan keyin darhol 0, gc.collect() dan keyin 0
nusxa keshi tirik: del dan keyin darhol 5, gc.collect() dan keyin 0
modul funksiyasi tirik: del dan keyin darhol 0, gc.collect() dan keyin 0
WeakKeyDictionary tirik: del dan keyin darhol 0, gc.collect() dan keyin 0
(nusxa keshi: self → lru_cache o'rovchisi → bog'langan metod → self — sikl, faqat gc tozalaydi)Nima ko'rsatdi: 2.4-bo'lim.
Misol 3 — partial chuqur
"""Tekislash, qayta yozish, Placeholder, imzo, pickle, 3.14 deskriptor xatti-harakati, partial va lambda."""
import inspect
import operator
import pickle
from functools import Placeholder, partial, partialmethod
def xabar(daraja, modul, matn, *, vaqt="12:00", belgi="•"):
return f"{belgi} [{vaqt}] {daraja} {modul}: {matn}"
print("=== 1. Tekislash va argumentlar tartibi ===")
xato = partial(xabar, "XATO")
xato_db = partial(xato, "db", belgi="✗")
print(f" func: {xato_db.func.__name__}, args: {xato_db.args}, keywords: {xato_db.keywords}")
print(f" {xato_db('ulanish uzildi')}")
print(f" nomli qayta yozildi: {xato_db('ulanish uzildi', belgi='!', vaqt='09:30')}")
try:
xato_db("db2", "matn")
except TypeError as e:
print(f" pozitsionni qayta yozib bo'lmaydi: TypeError: {e}")
print("\n=== 2. Imzo ===")
print(f" xabar: {inspect.signature(xabar)}")
print(f" xato_db: {inspect.signature(xato_db)}")
print("\n=== 3. Placeholder 3.14-bob ===")
ikkiga_bol = partial(operator.truediv, Placeholder, 2)
kvadrat = partial(pow, Placeholder, 2)
auth_xabar = partial(xabar, Placeholder, "auth")
print(f" ikkiga_bol(9) = {ikkiga_bol(9)}, kvadrat(12) = {kvadrat(12)}")
print(f" {auth_xabar('OGOHLANTIRISH', 'parol noto' + chr(39) + 'g' + chr(39) + 'ri')}")
print(f" imzo: {inspect.signature(auth_xabar)}")
try:
partial(xabar, "INFO", Placeholder)
except TypeError as e:
print(f" oxirida Placeholder: TypeError: {e}")
print("\n=== 4. pickle: partial va lambda ===")
for nom, obyekt in [("partial(operator.mul, 3)", partial(operator.mul, 3)),
("lambda x: 3 * x", lambda x: 3 * x)]:
try:
print(f" {nom}: ✅ {pickle.loads(pickle.dumps(obyekt))(5)}")
except (pickle.PicklingError, AttributeError) as e:
print(f" {nom}: ❌ {type(e).__name__}")
print("\n=== 5. ⚠️ 3.14: partial klass atributi sifatida self ni bog'laydi ===")
def yoz(format_, obyekt, yol):
return f"{type(obyekt).__name__} → {yol} ({format_})"
class Hujjat:
saqla = partial(yoz, "json")
saqla_statik = staticmethod(partial(yoz, "json"))
def _eksport(self, format_, yol): # partialmethod uchun: self birinchi
return yoz(format_, self, yol)
saqla_metod = partialmethod(_eksport, "yaml")
saqla_xato = partialmethod(yoz, "yaml") # ⚠️ yoz(self, "yaml", yol) bo'lib chaqiriladi
def __repr__(self):
return "Hujjat()"
h = Hujjat()
print(f" partial __get__ bormi: {hasattr(Hujjat.__dict__['saqla'], '__get__')}")
print(f" h.saqla('a.json') → {h.saqla('a.json')}")
try:
print(f" h.saqla_statik('a.json') → {h.saqla_statik('a.json')}")
except TypeError as e:
print(f" h.saqla_statik('a.json') → TypeError: {e}")
print(f" h.saqla_statik(h, 'a.json') → {h.saqla_statik(h, 'a.json')}")
print(f" h.saqla_metod('b.yaml') → {h.saqla_metod('b.yaml')}")
print(f" h.saqla_xato('b.yaml') → {h.saqla_xato('b.yaml')} ⚠️ format_=Hujjat(), obyekt='yaml'")
print("\n=== 6. partial erta, lambda kechikkan ===")
tugmalar_lambda, tugmalar_partial = [], []
for raqam in range(1, 4):
tugmalar_lambda.append(lambda: xabar("INFO", "ui", f"tugma {raqam}"))
tugmalar_partial.append(partial(xabar, "INFO", "ui", f"tugma {raqam}"))
print(f" lambda: {[t().split(': ')[1] for t in tugmalar_lambda]}")
print(f" partial: {[t().split(': ')[1] for t in tugmalar_partial]}")Natijaning muhim qismi:
=== 1. Tekislash va argumentlar tartibi ===
func: xabar, args: ('XATO', 'db'), keywords: {'belgi': '✗'}
✗ [12:00] XATO db: ulanish uzildi
nomli qayta yozildi: ! [09:30] XATO db: ulanish uzildi
pozitsionni qayta yozib bo'lmaydi: TypeError: xabar() takes 3 positional arguments but 4 positional arguments (and 1 keyword-only argument) were given
=== 2. Imzo ===
xabar: (daraja, modul, matn, *, vaqt='12:00', belgi='•')
xato_db: (matn, *, vaqt='12:00', belgi='✗')
=== 3. Placeholder 3.14-bob ===
ikkiga_bol(9) = 4.5, kvadrat(12) = 144
• [12:00] OGOHLANTIRISH auth: parol noto'g'ri
imzo: (daraja, /, matn, *, vaqt='12:00', belgi='•')
oxirida Placeholder: TypeError: trailing Placeholders are not allowed
=== 4. pickle: partial va lambda ===
partial(operator.mul, 3): ✅ 15
lambda x: 3 * x: ❌ PicklingError
=== 5. ⚠️ 3.14: partial klass atributi sifatida self ni bog'laydi ===
partial __get__ bormi: True
h.saqla('a.json') → Hujjat → a.json (json)
h.saqla_statik('a.json') → TypeError: yoz() missing 1 required positional argument: 'yol'
h.saqla_statik(h, 'a.json') → Hujjat → a.json (json)
h.saqla_metod('b.yaml') → Hujjat → b.yaml (yaml)
h.saqla_xato('b.yaml') → str → b.yaml (Hujjat()) ⚠️ format_=Hujjat(), obyekt='yaml'
=== 6. partial erta, lambda kechikkan ===
lambda: ['tugma 3', 'tugma 3', 'tugma 3']
partial: ['tugma 1', 'tugma 2', 'tugma 3']Nima ko'rsatdi: 2.6-bo'lim. 5-bo'limda staticmethod(partial(...)) bilan self bog'lanmadi — shuning uchun h ni qo'lda uzatish kerak bo'ldi. partialmethod esa self ni doim birinchi qo'yadi: self birinchi parametr bo'lmagan funksiyaga qo'llansa, argumentlar jim aralashib ketadi (saqla_xato).
Misol 4 — Amaliy: narx hisoblash quvuri
Onlayn do'kon savatchasi uchun narx hisoblash: chegirma qoidalari partial bilan sozlanadi, reduce bilan bitta funksiyaga yig'iladi, valyuta kursi eskirish muddati bilan keshlanadi, buyurtmalar statistikasi esa bo'laklab yig'iladi — faqat assotsiativ amallar bilan.
"""partial + reduce kompozitsiya; TTL chelakli lru_cache; bo'laklab reduce va assotsiativlik."""
import operator
from functools import Placeholder, lru_cache, partial, reduce
from itertools import accumulate, batched
def foiz_chegirma(foiz, summa):
return summa * (100 - foiz) // 100
def chegara_chegirma(chegara, ayirma, summa):
return summa - ayirma if summa >= chegara else summa
def eng_kam(minimal, summa):
return max(minimal, summa)
def kompozitsiya(*funksiyalar):
"""chapdan o'ngga: kompozitsiya(f, g)(x) == g(f(x)); neytral element — lambda x: x."""
return reduce(lambda f, g: lambda x: g(f(x)), funksiyalar, lambda x: x)
print("=== 1. Qoidalar partial bilan, quvur reduce bilan ===")
qoidalar = [
partial(foiz_chegirma, 10), # 10% aksiya
partial(chegara_chegirma, 500_000, 50_000), # 500 mingdan oshsa −50 ming
partial(eng_kam, 100_000), # kamida 100 ming
]
hisobla = kompozitsiya(*qoidalar)
for savatcha in (120_000, 600_000, 580_000):
qadamlar = list(accumulate(qoidalar, lambda s, q: q(s), initial=savatcha))
print(f" {savatcha:>9,} → {hisobla(savatcha):>9,} qadamlar: {[f'{q:,}' for q in qadamlar]}")
print(f" bo'sh quvur (neytral element): {kompozitsiya()(777)}")
print("\n=== 2. Valyuta kursi: eskirish muddati bilan kesh ===")
soat = {"t": 0}
tarmoq_sorovlari = []
@lru_cache(maxsize=32)
def _kurs(valyuta, chelak):
tarmoq_sorovlari.append((valyuta, chelak))
return {"USD": 12_600, "EUR": 13_700}[valyuta] + chelak * 10
def kurs(valyuta, *, muddat=300):
return _kurs(valyuta, soat["t"] // muddat)
for t, valyuta in [(0, "USD"), (120, "USD"), (299, "EUR"), (300, "USD"), (450, "USD"), (610, "EUR")]:
soat["t"] = t
print(f" t={t:>3}s {valyuta}: {kurs(valyuta):,}")
print(f" tarmoq so'rovlari: {tarmoq_sorovlari}")
print(f" {_kurs.cache_info()}")
print("\n=== 3. Placeholder bilan konvertatsiya ===")
soat["t"] = 0
som_to_usd = partial(operator.floordiv, Placeholder, kurs("USD"))
print(f" {hisobla(600_000):,} so'm ≈ {som_to_usd(hisobla(600_000))} USD")
print("\n=== 4. ⭐ Bo'laklab yig'ish: faqat assotsiativ amal to'g'ri ===")
buyurtmalar = [120_000, 45_000, 300_000, 80_000, 15_000, 240_000, 99_000]
def bolaklab(amal, malumot, neytral, hajm=3):
bolak_natijalari = [reduce(amal, bolak, neytral) for bolak in batched(malumot, hajm)]
return reduce(amal, bolak_natijalari, neytral)
def ortacha_yomon(a, b):
return (a + b) / 2
def juftlik(a, b):
return (a[0] + b[0], a[1] + b[1])
for nom, amal, neytral, tayyorla in [
("yig'indi (+)", operator.add, 0, lambda x: x),
("eng katta (max)", max, float("-inf"), lambda x: x),
("ayirma (-)", operator.sub, 0, lambda x: x),
("o'rtacha (a+b)/2", ortacha_yomon, 0, lambda x: x),
("(yig'indi, soni)", juftlik, (0, 0), lambda x: (x, 1)),
]:
malumot = [tayyorla(x) for x in buyurtmalar]
ketma_ket = reduce(amal, malumot, neytral)
bolakli = bolaklab(amal, malumot, neytral)
print(f" {nom:18} ketma-ket: {str(ketma_ket):18} bo'laklab: {str(bolakli):18} {'✅' if ketma_ket == bolakli else '❌'}")
jami, soni = reduce(juftlik, [(x, 1) for x in buyurtmalar], (0, 0))
print(f" to'g'ri o'rtacha — juftlikdan: {jami / soni:,.0f}")Natijaning muhim qismi:
=== 1. Qoidalar partial bilan, quvur reduce bilan ===
120,000 → 108,000 qadamlar: ['120,000', '108,000', '108,000', '108,000']
600,000 → 490,000 qadamlar: ['600,000', '540,000', '490,000', '490,000']
580,000 → 472,000 qadamlar: ['580,000', '522,000', '472,000', '472,000']
bo'sh quvur (neytral element): 777
=== 2. Valyuta kursi: eskirish muddati bilan kesh ===
t= 0s USD: 12,600
t=120s USD: 12,600
t=299s EUR: 13,700
t=300s USD: 12,610
t=450s USD: 12,610
t=610s EUR: 13,720
tarmoq so'rovlari: [('USD', 0), ('EUR', 0), ('USD', 1), ('EUR', 2)]
CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
=== 3. Placeholder bilan konvertatsiya ===
490,000 so'm ≈ 38 USD
=== 4. ⭐ Bo'laklab yig'ish: faqat assotsiativ amal to'g'ri ===
yig'indi (+) ketma-ket: 899000 bo'laklab: 899000 ✅
eng katta (max) ketma-ket: 300000 bo'laklab: 300000 ✅
ayirma (-) ketma-ket: -899000 bo'laklab: 899000 ❌
o'rtacha (a+b)/2 ketma-ket: 127390.625 bo'laklab: 80218.75 ❌
(yig'indi, soni) ketma-ket: (899000, 7) bo'laklab: (899000, 7) ✅
to'g'ri o'rtacha — juftlikdan: 128,429Nima ko'rsatdi: 2.5, 2.6, 2.7-bo'limlar — kompozitsiyaning neytral elementi identik funksiya, TTL chelagi kalitning bir qismi, bo'laklab yig'ish esa faqat assotsiativ amallarda ketma-ket natija bilan mos keladi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"f(1) va f(x=1) bir xil kesh yozuvi" |
Kalit — chaqiruv shakli, alohida yozuvlar |
| "Kesh sukut qiymatlarni hisobga oladi" | f(1) va f(1, 0) — alohida |
| "Xato ham keshlanadi" | Istisnolar keshlanmaydi |
| "Keshdan olingan ro'yxatni o'zgartirsam, faqat menga ta'sir qiladi" | Hamma chaqiruvchi bitta obyektni oladi |
"lru_cache iplar uchun xavfsiz — hisob bir marta bo'ladi" |
Tuzilma xavfsiz, lekin hisob takrorlanishi mumkin |
"Metoddagi lru_cache har nusxaning o'z keshi" |
Klass darajasida bitta kesh, nusxalar kalitda — tirik qoladi |
"partial klass atributi sifatida self ni olmaydi" |
3.14 dan oladi — staticmethod bilan o'rang |
"reduce ni istalgan amal bilan bo'laklab parallel qilish mumkin" |
Faqat assotsiativ amal va neytral element bilan |
6. Keng tarqalgan xatolar va yechimlari
1. Metodga @lru_cache
class Xizmat:
@lru_cache(maxsize=None) # ❌ har nusxa abadiy tirik
def hisobla(self, x): ...
class Xizmat:
@cached_property # ✅ argumentsiz bo'lsa
def sozlama(self): ...
# yoki modul darajasidagi @cache funksiya, kalit — ma'lumot2. O'zgaruvchan natijani keshlash
@cache
def standart_sozlama():
return {"til": "uz"} # ❌ bir joyda o'zgartirilsa — hamma joyda
@cache
def standart_sozlama():
return MappingProxyType({"til": "uz"}) # ✅ yoki har chaqiruvda nusxa: dict(...)3. Vaqtga bog'liq funksiyani keshlash
@cache
def bugungi_kurs(valyuta): ... # ❌ ertaga ham kechagi kurs
@lru_cache(maxsize=64)
def _kurs(valyuta, sana): ...
def bugungi_kurs(valyuta):
return _kurs(valyuta, date.today()) # ✅ sana kalitda4. Nomuvofiq chaqiruv shakli
narx("olma") # ❌ uch xil shakl — uch yozuv, uch marta hisob
narx(mahsulot="olma")
narx("olma", 1)Bir xil shaklda chaqiring yoki ommaviy funksiya argumentlarni normallashtirib, keshlangan ichki funksiyani chaqirsin.
5. Xeshlanmaydigan argument
@cache
def jami(narxlar): ...
jami([1, 2, 3]) # ❌ TypeError: unhashable type: 'list'
jami(tuple(narxlar)) # ✅6. 3.14 ga o'tishda klassdagi partial
class Eksport:
csv = partial(eksport, "csv") # ❌ 3.14: eksport("csv", self, ...)
class Eksport:
csv = staticmethod(partial(eksport, "csv")) # ✅ eski xatti-harakat
# yoki partialmethod — agar self kerak bo'lsa7. Boshlang'ich qiymatsiz reduce
reduce(operator.or_, toplamlar) # ❌ bo'sh ro'yxatda TypeError
reduce(operator.or_, toplamlar, frozenset()) # ✅ neytral element8. Assotsiativ bo'lmagan amalni bo'laklab yig'ish
ortacha = reduce(lambda a, b: (a + b) / 2, parallel_natijalar) # ❌ noto'g'ri
jami, soni = reduce(juftlik, [(x, 1) for x in qiymatlar], (0, 0))
ortacha = jami / soni # ✅ assotsiativ juftlik7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 7.11–7.13-darslar (o'tilgan):
partial,reduce,@cachebilan tanishuv - 8.15-dars (o'tilgan):
cached_property - 10.11-dars (o'tilgan):
__wrapped__— keshni chetlab o'tish - 10.12-dars (o'tilgan): kechikkan bog'lanish —
partialnega erta - 10.17-dars: deskriptorlar —
partial3.14 da negaselfni bog'laydi - 14-qism:
concurrent.futuresva bo'laklab parallel yig'ish; iplar va kesh - 23-qism: Redis kabi tashqi keshlar — jarayonlar orasida bo'lishiladigan kesh
- Ma'lumotlar muhandisligi: MapReduce, Spark
reduceByKey— assotsiativlik talabi
8. Eng yaxshi amaliyotlar
Faqat sof funksiyalarni keshlang. Vaqt, tasodif, tashqi holat — argument bo'lsin.
Keshlangan funksiya o'zgarmas qiymat qaytarsin —
tuple,frozenset,MappingProxyType.Metodga
lru_cacheqo'ymang.cached_property, nusxa keshi yoki modul funksiyasi.maxsizeni o'lchab tanlang vacache_info()ni metrikaga chiqaring.TTL kerak bo'lsa — vaqt chelagi argumenti; murakkab eskirish uchun maxsus kesh kutubxonasi.
Klassda
partial—staticmethodyokipartialmethodbilan, 3.13 va 3.14 da bir xil ishlashi uchun.reducega doim neytral elementni bering, va bo'laklab yig'ishdan oldin amal assotsiativmi — tekshiring.sum,max,math.prod,"".joinbor joydareduceyozmang.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
from functools import cache, lru_cache, partial, reduce, Placeholder
import operator
sanoq = []
@lru_cache(maxsize=2)
def kv(x):
sanoq.append(x)
return x * x
def h(a, b, c=0):
return (a, b, c)
1. kv(2)
kv(2)
kv(3)
print(sanoq, kv.cache_info().hits)
2. sanoq.clear()
kv.cache_clear()
for x in [1, 2, 3, 1]:
kv(x)
print(sanoq)
3. @cache
def f(x, y=1):
return x + y
f(1)
f(1, 1)
f(x=1)
print(f.cache_info().currsize)
4. @cache
def r(n):
return [0] * n
r(2).append(5)
print(r(2))
5. p = partial(partial(h, 1), 2)
print(p.func is h, p.args)
6. print(partial(h, c=5)(1, 2, c=7))
7. print(partial(h, Placeholder, 2)(1))
8. class K:
m = partial(h, "x")
print(K().m(3)[2])
9. print(reduce(operator.sub, [10, 3, 2]))
10. print(reduce(operator.add, [], 0), reduce(operator.add, [1, 2], initial=10))
11. try:
reduce(max, [])
except TypeError:
print("TypeError")
12. print(reduce(lambda a, b: b + a, "abc"))Javoblar
[2, 3] 1— ikkinchikv(2)keshdan[1, 2, 3, 1]—maxsize=2:3kelganda1o'chirildi3— uch xil chaqiruv shakli, uch yozuv[0, 0, 5]— keshdagi ro'yxat o'zgardiTrue (1, 2)— tekislandi(1, 2, 7)— nomli argument qayta yozildi(1, 2, 0)3— 3.14 dapartialselfni bog'ladi:h("x", <K>, 3)5—(10 - 3) - 20 13TypeError— bo'sh va boshlang'ich qiymatsizcba— har qadamda yangi harf oldinga qo'shiladi
Vazifa 2: Xatolarni tuzating
1. class Katalog:
def __init__(self, db):
self.db = db
@lru_cache(maxsize=None)
def mahsulot(self, id):
return self.db.ol(id)
2. @cache
def foydalanuvchi_sozlamalari(user_id):
return dict(bazadan(user_id))
s = foydalanuvchi_sozlamalari(7)
s["til"] = "en" # faqat shu so'rov uchun
3. @cache
def valyuta_kursi(kod):
return api.ol(f"/kurs/{kod}") # kurs har soatda yangilanadi
4. class Eksport:
pdf = partial(eksport, "pdf")
Eksport().pdf("hisobot") # 3.14 ga o'tgach buzildi
5. from concurrent.futures import ProcessPoolExecutor
def ortacha(bolak):
return reduce(lambda a, b: (a + b) / 2, bolak)
with ProcessPoolExecutor() as ex:
natija = reduce(lambda a, b: (a + b) / 2, ex.map(ortacha, bolaklar))Javoblar
1. @lru_cache(maxsize=1024)
def _mahsulot(db_nomi, id): # kalitda xeshlanadigan ma'lumot, nusxa emas
return ULANISHLAR[db_nomi].ol(id)
class Katalog:
def __init__(self, db_nomi):
self.db_nomi = db_nomi
def mahsulot(self, id):
return _mahsulot(self.db_nomi, id)
2. @cache
def _sozlamalar(user_id):
return MappingProxyType(dict(bazadan(user_id))) # o'zgarmas ko'rinish
s = dict(_sozlamalar(7)) # o'zgartirish kerak bo'lsa — nusxa
s["til"] = "en"
3. @lru_cache(maxsize=64)
def _kurs(kod, soat):
return api.ol(f"/kurs/{kod}")
def valyuta_kursi(kod):
return _kurs(kod, int(time.time() // 3600))
4. class Eksport:
pdf = staticmethod(partial(eksport, "pdf"))
5. def juftlik(bolak):
return (sum(bolak), len(bolak)) # har bo'lakdan (yig'indi, soni)
with ProcessPoolExecutor() as ex:
jami, soni = reduce(lambda a, b: (a[0] + b[0], a[1] + b[1]),
ex.map(juftlik, bolaklar), (0, 0))
natija = jami / soniVazifa 3: ttl_cache
ttl_cache(muddat, *, maxsize=128, soat=time.monotonic) dekoratorini yozing:
- Har yozuv o'z yaratilgan vaqtini saqlasin va
muddato'tgach eskirsin (chelak emas — aniq muddat) maxsizeto'lganda LRU tartibida o'chirsin (collections.OrderedDict)cache_info()—hits,misses,eskirgan,currsize- Bir xil kalit uchun parallel hisobni oldini olish — har kalit uchun qulf
- Soxta soat bilan testlar
Vazifa 4: Normallashtiruvchi kesh
imzoli_cache dekoratorini yozing:
inspect.signature(f).bind(*args, **kwargs)+apply_defaults()bilan kalit yasasin —f(1),f(x=1),f(1, 0)bitta yozuv bo'lsinlist/dict/setargumentlarni xeshlanadigan ko'rinishga o'girsin (tuple,frozenset(items))- Qaytarilgan
list/dictni nusxalab bersin - Misol 1 dagi 1-bo'limni shu dekorator bilan qayta ishga tushirib, yozuvlar sonini solishtiring
Vazifa 5: Monoidlar kutubxonasi
Monoid(amal, neytral) klassini yozing:
yig(ketma_ketlik)— ketma-ketreducebolaklab_yig(ketma_ketlik, hajm, executor=None)— bo'laklarga bo'lib,executor.mapbilan parallel- Tayyor monoidlar:
YIGINDI,KOPAYTMA,MAKS,LUGAT_BIRLASHTIRISH,ORTACHA(juftlik orqali),SOZ_SANOQ(Counter) - Assotsiativlikni tasodifiy ma'lumotlar bilan tekshiruvchi
tekshir(monoid, namunalar)— ayirma va(a+b)/2ni rad etsin
Vazifa 6: Konfiguratsiyalanadigan quvur
Misol 4 dagi quvurni kengaytiring:
- Qoidalar JSON dan o'qilsin:
[{"tur": "foiz", "foiz": 10}, {"tur": "chegara", ...}]—partialbilan funksiyaga aylantirilsin - Har qoida nomi va o'zgarishi hisobotda chiqsin (
accumulate) - Qoidalar ro'yxatining xeshi bo'yicha
hisoblafunksiyasi keshlansin — bir xil sozlama ikkinchi marta yig'ilmasin - Qoidalar tartibi natijaga ta'sir qilishini (kompozitsiya assotsiativ, lekin kommutativ emas) misol bilan ko'rsating
Vazifa 7: O'ylash
"Dasturlashda faqat ikkita qiyin narsa bor: keshni bekor qilish va nom berish" (Phil Karlton). functools.lru_cache bu muammoning qaysi qismini hal qiladi va qaysisini dasturchiga qoldiradi? Jarayon ichidagi kesh, cached_property, Redis kabi tashqi kesh va HTTP kesh — qachon qaysi biri, va ularning har birida "bekor qilish" qanday qilinadi?
Javob
Qisqa javob: lru_cache saqlash va o'chirish mexanizmini beradi — kalit yasash, LRU tartibi, statistika. Lekin qachon natija eskirgani haqidagi qarorni umuman bilmaydi: u faqat hajm to'lganda yoki cache_clear() chaqirilganda yozuvni o'chiradi. Keshni bekor qilishning qiyinligi aynan shu qarorda — ma'lumot manbai o'zgarganini kesh qayerdan bilishi kerak. Har kesh qatlami bu savolga o'z doirasida javob beradi: jarayon, obyekt, butun tizim yoki tarmoq.
1. lru_cache nimani hal qiladi
- Kalit yasash (chaqiruv shakli bo'yicha — 2.2)
- Hajm chegarasi va LRU o'chirish
- Iplar uchun xavfsiz tuzilma
- Statistika (
cache_info) - Vaqt bo'yicha eskirish
- Ma'lumot manbai o'zgarganda bekor qilish
- Jarayonlar va serverlar orasida bo'lishish
- Bir xil kalitni parallel hisoblashdan himoya
2. Bekor qilish strategiyalari
| Strategiya | Qanday | Afzallik | Kamchilik |
|---|---|---|---|
| Vaqt (TTL) | Yozuv N soniyadan keyin eskiradi | Oddiy, avtomatik | N soniya davomida eski ma'lumot |
| Aniq bekor qilish | Ma'lumot yozilganda cache_clear() / delete(kalit) |
Darhol yangi | Barcha yozuv joylarini topish kerak — biri unutiladi |
| Versiyali kalit | Kalitga versiya qo'shiladi: mahsulot:42:v17 |
Eski yozuvlarni o'chirish shart emas | Versiyani saqlash va oshirish kerak |
| Hodisaga asoslangan | Ma'lumot o'zgarganda xabar (pub/sub) keshlarni tozalaydi | Taqsimlangan tizimda ishlaydi | Murakkab, xabar yo'qolishi mumkin |
| Mazmun xeshi | Kalit — ma'lumotning xeshi (ETag) |
Mutlaqo aniq | Xeshni hisoblash narxi |
3. Qatlamlar
a) Jarayon ichidagi kesh — lru_cache, cache
- Qachon: sof hisoblashlar (parsing, matematik funksiyalar), kamdan-kam o'zgaradigan konfiguratsiya
- Bekor qilish:
cache_clear(), vaqt chelagi argumenti, versiya argumenti - Tuzoq: ko'p jarayonli serverda (gunicorn bilan 8 worker) har worker'ning o'z keshi. Bittasida
cache_clear()qilish qolgan yettitasiga ta'sir qilmaydi — foydalanuvchi so'rov qaysi worker'ga tushishiga qarab turli natija ko'radi
b) Obyekt darajasidagi kesh — cached_property
- Qachon: obyekt yashash davomida o'zgarmaydigan hosila qiymat
- Bekor qilish:
del obj.xususiyat— keyingi murojaatda qayta hisoblanadi; obyekt o'lganda kesh ham yo'qoladi - Tuzoq: obyektning manba atributlari o'zgarsa, kesh eskiradi — obyektni o'zgarmas qilish (
frozen) eng ishonchli
c) Tashqi kesh — Redis, Memcached
- Qachon: bir nechta jarayon yoki server bir xil natijani ko'rishi kerak; qimmat ma'lumotlar bazasi so'rovlari; sessiyalar
- Bekor qilish: kalit bo'yicha
DEL,EXPIREbilan TTL, versiyali kalitlar - Tuzoq: tarmoq kechikishi va serializatsiya narxi; "thundering herd" — mashhur kalit eskirganda yuzlab so'rov bir vaqtda bazaga boradi (qulf yoki "stale-while-revalidate" kerak)
d) HTTP kesh — brauzer, CDN, proksi
- Qachon: statik fayllar, ommaviy API javoblari
- Bekor qilish:
Cache-Control: max-age=...,ETagvaIf-None-Match, fayl nomida xesh (app.3f9c2.js) — yangi versiya yangi URL - Tuzoq: CDN va brauzer keshini serverdan "tozalab" bo'lmaydi — shuning uchun o'zgaruvchan resurslar uchun qisqa
max-ageyoki versiyali URL
4. Qaror qabul qilish tartibi
- Kesh kerakmi? Avval o'lchang — sekin joy aynan shu funksiyami
- Funksiya sofmi? Yo'q bo'lsa, qaysi tashqi holatga bog'liq — uni kalitga qo'shing
- Qanchalik eski ma'lumotga chidash mumkin? Soniyalar → TTL; umuman yo'q → aniq bekor qilish yoki keshlamaslik
- Kim bo'lishishi kerak? Bitta jarayon →
lru_cache; bir nechta → tashqi kesh; foydalanuvchi brauzeri → HTTP - Bekor qilish yo'lini yozing — keshni qo'shayotgan paytda, keyin emas
5. Xulosa
lru_cachesaqlash mexanizmini beradi, eskirish qarorini emas- Bekor qilish strategiyalari — TTL, aniq o'chirish, versiyali kalit, hodisa, mazmun xeshi — har biri aniqlik va soddalik orasidagi kelishuv
- Jarayon ichidagi kesh ko'p jarayonli serverda bo'lishilmaydi — bu eng ko'p unutiladigan tuzoq
- Kesh qatlamini ma'lumot qancha "eski" bo'la olishi va kim bilan bo'lishilishi belgilaydi
- Keshni qo'shish bilan birga uni bekor qilish yo'lini ham loyihalash kerak — Karlton hazili aynan shu haqida
Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.
Xulosa
Bu darsda functools modulining uch vositasini chuqur o'rgandik.
Eng muhim uch fikr:
lru_cachekaliti — chaqiruv shakli, va u faqat sof funksiyalar uchun xavfsiz.f(1),f(x=1)vaf(1, 0)— uch alohida yozuv. Istisnolar keshlanmaydi, qaytarilgan o'zgaruvchan obyekt esa barcha chaqiruvchilar uchun umumiy. Kesh tuzilmasi iplar uchun xavfsiz, lekin bir xil kalit parallel ikki marta hisoblanishi mumkin, jarayonlar esa keshni bo'lishmaydi.Metodga
lru_cacheqo'ymang. Kalitdaselfbo'lgani uchun har nusxa keshda abadiy qoladi, kesh klass darajasida umumiy, teng deb hisoblangan nusxalar esa bir-birining natijasini oladi.cached_property, nusxa keshi, modul darajasidagi funksiya yokiWeakKeyDictionary— vaziyatga qarab.partial— erta bog'lash,reduce— chap yig'ish.partialtekislanadi, nomli argumentlarni qayta yozishga ruxsat beradi, 3.14 daPlaceholderbilan o'rtadagi joyni bo'sh qoldira oladi va klass atributi sifatidaselfni bog'laydi.reducega doim neytral elementni bering; ketma-ketlikni bo'laklab yoki parallel yig'ish faqat assotsiativ amallarda to'g'ri — o'rtacha kabi qiymatlarni assotsiativ juftlik orqali hisoblang.
Keyingi darsda operator ortiqcha yuklash amaliyotini o'rganamiz: __add__ va __radd__ qachon chaqiriladi, NotImplemented nima uchun kerak, joyida amallar (__iadd__) va real tur — pul, vektor, matritsa — uchun to'liq operatorlar to'plamini loyihalash.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!