IlmHamroh
Python kursi/Ilgor til imkoniyatlari13/17-dars30 daqiqa
Mundarija (21)

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:

python
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:

  1. Kesh kaliti self ni ham o'z ichiga oladi. Har so'rov uchun yaratilgan NarxXizmati nusxasi keshda abadiy qoladi — u bilan birga butun HTTP so'rov ham
  2. 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_cache ichidan: kalit yasash, LRU tartibi, iplar
  • Nima keshlanmaydi va keshning xavfli joylari
  • Metodlarda kesh: xotira sizishi va teng obyektlar tuzog'i
  • partial chuqur: tekislash, Placeholder 3.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

python
@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:

python
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 chaqirish

Kesh 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

python
class Hisobot:
    @lru_cache(maxsize=None)
    def hisobla(self, oy): ...

Muammolar:

  1. Xotira sizishi. Kalit (self, oy) — kesh har bir self ga kuchli havola saqlaydi. maxsize=None bo'lsa, nusxalar hech qachon o'chmaydi (flake8-bugbear bu naqshni B019 deb belgilaydi)
  2. Kesh klass darajasida umumiy. Barcha nusxalar bitta keshni to'ldiradi, cache_clear() hammasini tozalaydi
  3. Teng nusxalar natijani bo'lishadi. Kalit self ning __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() dagi hits / (hits + misses) ulushini kuzating
  • Eskirish (TTL) — lru_cache da 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

python
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'rsatadi
  • pickle qilinadi (agar func va argumentlar pickle bo'lsa)

Placeholder (Python 3.14): o'rtadagi pozitsion argumentni bo'sh qoldirish:

python
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 allowed

Ilgari 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:

python
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

python
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

python
@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 sifatida

partial

python
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 uchun

reduce

python
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 element

4. Batafsil misollar

Misol 1 — Kesh kaliti, LRU tartibi va nima keshlanmaydi

python
"""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:

text
=== 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

python
"""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:

text
=== 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

python
"""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:

text
=== 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.

python
"""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:

text
=== 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,429

Nima 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

python
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'lumot

2. O'zgaruvchan natijani keshlash

python
@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

python
@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 kalitda

4. Nomuvofiq chaqiruv shakli

python
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

python
@cache
def jami(narxlar): ...
jami([1, 2, 3])                        # ❌ TypeError: unhashable type: 'list'

jami(tuple(narxlar))                   # ✅

6. 3.14 ga o'tishda klassdagi partial

python
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'lsa

7. Boshlang'ich qiymatsiz reduce

python
reduce(operator.or_, toplamlar)        # ❌ bo'sh ro'yxatda TypeError

reduce(operator.or_, toplamlar, frozenset())   # ✅ neytral element

8. Assotsiativ bo'lmagan amalni bo'laklab yig'ish

python
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 juftlik

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 7.11–7.13-darslar (o'tilgan): partial, reduce, @cache bilan 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 — partial nega erta
  • 10.17-dars: deskriptorlar — partial 3.14 da nega self ni bog'laydi
  • 14-qism: concurrent.futures va 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

  1. Faqat sof funksiyalarni keshlang. Vaqt, tasodif, tashqi holat — argument bo'lsin.

  2. Keshlangan funksiya o'zgarmas qiymat qaytarsin — tuple, frozenset, MappingProxyType.

  3. Metodga lru_cache qo'ymang. cached_property, nusxa keshi yoki modul funksiyasi.

  4. maxsize ni o'lchab tanlang va cache_info() ni metrikaga chiqaring.

  5. TTL kerak bo'lsa — vaqt chelagi argumenti; murakkab eskirish uchun maxsus kesh kutubxonasi.

  6. Klassda partial — staticmethod yoki partialmethod bilan, 3.13 va 3.14 da bir xil ishlashi uchun.

  7. reduce ga doim neytral elementni bering, va bo'laklab yig'ishdan oldin amal assotsiativmi — tekshiring.

  8. sum, max, math.prod, "".join bor joyda reduce yozmang.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
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
  1. [2, 3] 1 — ikkinchi kv(2) keshdan
  2. [1, 2, 3, 1] — maxsize=2: 3 kelganda 1 o'chirildi
  3. 3 — uch xil chaqiruv shakli, uch yozuv
  4. [0, 0, 5] — keshdagi ro'yxat o'zgardi
  5. True (1, 2) — tekislandi
  6. (1, 2, 7) — nomli argument qayta yozildi
  7. (1, 2, 0)
  8. 3 — 3.14 da partial self ni bog'ladi: h("x", <K>, 3)
  9. 5 — (10 - 3) - 2
  10. 0 13
  11. TypeError — bo'sh va boshlang'ich qiymatsiz
  12. cba — har qadamda yangi harf oldinga qo'shiladi

Vazifa 2: Xatolarni tuzating

python
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
python
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 / soni

Vazifa 3: ttl_cache

ttl_cache(muddat, *, maxsize=128, soat=time.monotonic) dekoratorini yozing:

  1. Har yozuv o'z yaratilgan vaqtini saqlasin va muddat o'tgach eskirsin (chelak emas — aniq muddat)
  2. maxsize to'lganda LRU tartibida o'chirsin (collections.OrderedDict)
  3. cache_info() — hits, misses, eskirgan, currsize
  4. Bir xil kalit uchun parallel hisobni oldini olish — har kalit uchun qulf
  5. Soxta soat bilan testlar

Vazifa 4: Normallashtiruvchi kesh

imzoli_cache dekoratorini yozing:

  1. inspect.signature(f).bind(*args, **kwargs) + apply_defaults() bilan kalit yasasin — f(1), f(x=1), f(1, 0) bitta yozuv bo'lsin
  2. list/dict/set argumentlarni xeshlanadigan ko'rinishga o'girsin (tuple, frozenset(items))
  3. Qaytarilgan list/dict ni nusxalab bersin
  4. Misol 1 dagi 1-bo'limni shu dekorator bilan qayta ishga tushirib, yozuvlar sonini solishtiring

Vazifa 5: Monoidlar kutubxonasi

Monoid(amal, neytral) klassini yozing:

  1. yig(ketma_ketlik) — ketma-ket reduce
  2. bolaklab_yig(ketma_ketlik, hajm, executor=None) — bo'laklarga bo'lib, executor.map bilan parallel
  3. Tayyor monoidlar: YIGINDI, KOPAYTMA, MAKS, LUGAT_BIRLASHTIRISH, ORTACHA (juftlik orqali), SOZ_SANOQ (Counter)
  4. Assotsiativlikni tasodifiy ma'lumotlar bilan tekshiruvchi tekshir(monoid, namunalar) — ayirma va (a+b)/2 ni rad etsin

Vazifa 6: Konfiguratsiyalanadigan quvur

Misol 4 dagi quvurni kengaytiring:

  1. Qoidalar JSON dan o'qilsin: [{"tur": "foiz", "foiz": 10}, {"tur": "chegara", ...}] — partial bilan funksiyaga aylantirilsin
  2. Har qoida nomi va o'zgarishi hisobotda chiqsin (accumulate)
  3. Qoidalar ro'yxatining xeshi bo'yicha hisobla funksiyasi keshlansin — bir xil sozlama ikkinchi marta yig'ilmasin
  4. 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, EXPIRE bilan 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=..., ETag va If-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-age yoki versiyali URL

4. Qaror qabul qilish tartibi

  1. Kesh kerakmi? Avval o'lchang — sekin joy aynan shu funksiyami
  2. Funksiya sofmi? Yo'q bo'lsa, qaysi tashqi holatga bog'liq — uni kalitga qo'shing
  3. Qanchalik eski ma'lumotga chidash mumkin? Soniyalar → TTL; umuman yo'q → aniq bekor qilish yoki keshlamaslik
  4. Kim bo'lishishi kerak? Bitta jarayon → lru_cache; bir nechta → tashqi kesh; foydalanuvchi brauzeri → HTTP
  5. Bekor qilish yo'lini yozing — keshni qo'shayotgan paytda, keyin emas

5. Xulosa

  1. lru_cache saqlash mexanizmini beradi, eskirish qarorini emas
  2. Bekor qilish strategiyalari — TTL, aniq o'chirish, versiyali kalit, hodisa, mazmun xeshi — har biri aniqlik va soddalik orasidagi kelishuv
  3. Jarayon ichidagi kesh ko'p jarayonli serverda bo'lishilmaydi — bu eng ko'p unutiladigan tuzoq
  4. Kesh qatlamini ma'lumot qancha "eski" bo'la olishi va kim bilan bo'lishilishi belgilaydi
  5. 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:

  1. lru_cache kaliti — chaqiruv shakli, va u faqat sof funksiyalar uchun xavfsiz. f(1), f(x=1) va f(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.

  2. Metodga lru_cache qo'ymang. Kalitda self bo'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 yoki WeakKeyDictionary — vaziyatga qarab.

  3. partial — erta bog'lash, reduce — chap yig'ish. partial tekislanadi, nomli argumentlarni qayta yozishga ruxsat beradi, 3.14 da Placeholder bilan o'rtadagi joyni bo'sh qoldira oladi va klass atributi sifatida self ni bog'laydi. reduce ga 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.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.13-dars: functools: cache, partial, reduce — IlmHamroh