IlmHamroh
Python kursi/Malumot tuzilmalari13/18-dars38 daqiqa
Mundarija (22)

6.13-dars: Hash nima va nega kalit hashlanadi

6-QISM — MA'LUMOT TUZILMALARI · 13-dars


1. Kirish va motivatsiya

Uch darsda takror aytdik: "kalitlar hashlanishi kerak". Endi savol — nega?

python
d = {}
d[[1, 2]] = "x"                 # ❌ TypeError: unhashable type: 'list'
d[(1, 2)] = "x"                 # ✅

Va nozikroq savol:

python
class Nuqta:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __eq__(self, boshqa):
        return (self.x, self.y) == (boshqa.x, boshqa.y)


n = Nuqta(1, 2)
{n}                             # ❌ TypeError: unhashable type: 'Nuqta'

Faqat __eq__ yozdik — va sinf hashlanmaydigan bo'lib qoldi. Nega?

Yana biri:

python
class Yomon:
    def __init__(self, q):
        self.q = q

    def __hash__(self):
        return hash(self.q)

    def __eq__(self, boshqa):
        return self.q == boshqa.q


y = Yomon([1, 2])               # ⚠️ o'zgaruvchan ichki holat
s = {y}
y.q.append(3)
y in s                          # False! ← o'zimizni yo'qotdik

Bu darsda:

  • Hash jadvali ichki mexanizmi — uyachalar, probing, o'sish
  • __hash__ / __eq__ shartnomasi — uch qoida
  • To'qnashuvlar va ularning narxi
  • O'z sinflaringizni to'g'ri hashlanadigan qilish
  • dataclass, NamedTuple, frozen — avtomatik hash
  • Kriptografik hash bilan farq

2. Nazariya — chuqur tushuntirish

2.1. Hash nima

Hash funksiyasi — har qanday obyektni butun songa aylantiradi:

python
hash(42)                # 42
hash("salom")           # -3874927349... (har jarayonda boshqa)
hash((1, 2))            # -3550055125485641917
hash(3.14)             # 322818021289917443
hash(None)              # 5740354900026072187 (versiyaga bog'liq)

Uch talab:

  1. Determinizm (bir jarayon ichida): hash(x) doim bir xil
  2. Teng obyektlar — teng hash: a == b → hash(a) == hash(b)
  3. Tez: O(1) yoki obyekt hajmiga proporsional

Teskarisi shart emas: hash(a) == hash(b) bo'lsa ham a != b bo'lishi mumkin — bu to'qnashuv (collision).

Nega hash kerak:

Kalitni topish uchun uni qayerga qo'yganini hisoblash mumkin bo'lishi kerak:

d["salom"] = 1

1. hash("salom")           → 8934572...
2. 8934572 & (hajm - 1)    → 5
3. jadval[5] = ("salom", 1)

d["salom"] ni o'qish:

1. hash("salom")           → 8934572  (BIR XIL)
2. & (hajm - 1)            → 5
3. jadval[5] ni tekshirish → topildi

Bitta hisoblash — elementlar soniga bog'liq emas. Bu — O(1).

Agar hash bo'lmasa: har elementni birma-bir solishtirish kerak — O(n).

2.2. Nega o'zgaruvchan obyektlar hashlanmaydi

Hash obyekt qiymatidan hisoblanadi. Qiymat o'zgarsa — hash ham o'zgaradi.

Tasavvur qiling, list hashlansa:

python
r = [1, 2]
d = {}
d[r] = "qiymat"                 # hash([1,2]) = 5 → jadval[5]

r.append(3)                     # endi hash([1,2,3]) = 9

d[r]                            # jadval[9] ga qaraydi → TOPILMADI!

Obyekt lug'at ichida, lekin uni topib bo'lmaydi. U "yo'qolib qoladi".

Bundan ham yomoni:

python
for kalit in d:
    print(kalit, d[kalit])      # ❌ KeyError — iteratsiya kalitini topib bo'lmaydi!

Shuning uchun Python o'zgaruvchan obyektlarni hashlashni TAQIQLAYDI:

python
list.__hash__                   # None
dict.__hash__                   # None
set.__hash__                    # None

tuple.__hash__                  # <slot wrapper>
frozenset.__hash__              # <slot wrapper>
str.__hash__                    # <slot wrapper>

__hash__ = None — "bu tur hashlanmaydi" degan aniq signal.

Bu — kelishuv, majburiyat emas:

python
class OzgaruvchanLekinHashlanadigan:
    def __init__(self, q):
        self.q = q

    def __hash__(self):
        return hash(self.q)     # ⚠️ q o'zgarsa hash ham o'zgaradi

    def __eq__(self, b):
        return self.q == b.q

Python bunga ruxsat beradi, lekin natija — yuqoridagi muammo.

Sayoz o'zgarmaslik yetarli emas:

python
t = (1, [2, 3])                 # tuple o'zgarmas
hash(t)                         # ❌ TypeError — ichida list bor

Tuple hash'i elementlar hash'idan hisoblanadi. Element hashlanmasa — tuple ham hashlanmaydi (6.5-dars).

2.3. __hash__ / __eq__ shartnomasi

Uchta qoida:

Qoida 1: a == b → hash(a) == hash(b)

Bu — majburiy. Buzilsa lug'at buziladi:

python
class Buzuq:
    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        return self.q == b.q

    def __hash__(self):
        return id(self)         # ⚠️ teng obyektlar turli hash!


a, b = Buzuq(1), Buzuq(1)
a == b                          # True
hash(a) == hash(b)              # False  ← SHARTNOMA BUZILDI

d = {a: "birinchi"}
d[b]                            # ❌ KeyError — teng, lekin topilmaydi

Qoida 2: __eq__ yozsangiz, __hash__ ham yozing (yoki None qiling)

Python buni avtomatik amalga oshiradi:

python
class Faqat_eq:
    def __eq__(self, b):
        return True

Faqat_eq.__hash__               # None  ← Python o'chirdi
{Faqat_eq()}                    # ❌ TypeError: unhashable

Nega: sukut __hash__ — id() asosida. Agar __eq__ ni qiymat bo'yicha qilsangiz, sukut hash 1-qoidani buzardi. Python xavfsizlik uchun __hash__ ni o'chiradi.

Bu — Pythonning eng foydali "himoya to'siqlaridan" biri.

Qoida 3: Hash obyekt ishlash muddatida o'zgarmasligi kerak

python
class Xavfli:
    def __init__(self, q):
        self.q = q

    def __hash__(self):
        return hash(self.q)

    def __eq__(self, b):
        return self.q == b.q


x = Xavfli(1)
s = {x}
x.q = 2                         # ⚠️ hash o'zgardi
x in s                          # False — o'zi to'plamda, lekin topilmaydi
len(s)                          # 1 — hali ham ichida
list(s)[0] is x                 # True — o'sha obyekt!

Obyekt to'plamda, lekin qidirib topilmaydi. Bu — "yashirin element" muammosi.

Yechim: hash faqat o'zgarmas maydonlardan hisoblanishi kerak:

python
class Xodim:
    def __init__(self, xodim_id: int, ism: str, maosh: int):
        self._id = xodim_id     # o'zgarmas identifikator
        self.ism = ism          # o'zgarishi mumkin
        self.maosh = maosh      # o'zgarishi mumkin

    def __eq__(self, b):
        return isinstance(b, Xodim) and self._id == b._id

    def __hash__(self):
        return hash(self._id)   # ⭐ faqat o'zgarmas maydon

Bu — ma'lumotlar bazasi obyektlari uchun standart naqsh: id bo'yicha tenglik va hash.

2.4. Sukut xatti-harakat

Sukut __hash__ va __eq__:

python
class Oddiy:
    pass

a, b = Oddiy(), Oddiy()

a == b                          # False — id bo'yicha
a == a                          # True
hash(a)                         # id(a) // 16 ga yaqin
{a, b}                          # ✅ ikkita element

Sukut bo'yicha har obyekt o'ziga xos. Bu — mantiqiy: ikki turli obyekt, garchi maydonlari bir xil bo'lsa ham, turli.

python
class Nuqta:
    def __init__(self, x, y):
        self.x, self.y = x, y

Nuqta(1, 2) == Nuqta(1, 2)      # False  ← sukut
{Nuqta(1, 2), Nuqta(1, 2)}      # ikkita element

Qachon sukut yetarli:

  • Obyekt identifikatsiya bilan aniqlanadi (ulanish, fayl, oqim)
  • Ikki turli obyekt hech qachon "bir xil" bo'lmaydi

Qachon o'zgartirish kerak:

  • Obyekt qiymat (value object): nuqta, pul, sana, rang
  • Lug'at kaliti yoki to'plam elementi bo'lishi kerak

2.5. Avtomatik hash — dataclass va NamedTuple

dataclass uchta rejim:

python
from dataclasses import dataclass

@dataclass                          # eq=True, frozen=False (sukut)
class A:
    x: int
# __eq__ ✅, __hash__ = None  → hashlanmaydi

@dataclass(frozen=True)             # ⭐ eng ko'p ishlatiladigan
class B:
    x: int
# __eq__ ✅, __hash__ ✅  → hashlanadi

@dataclass(eq=False)
class C:
    x: int
# __eq__ ❌ (sukut id), __hash__ ✅ (sukut id)

@dataclass(eq=True, unsafe_hash=True)   # ⚠️ ehtiyot bo'ling
class D:
    x: int
# __eq__ ✅, __hash__ ✅, lekin O'ZGARUVCHAN

Jadval:

eq frozen unsafe_hash __hash__
True False False None (sukut)
True True — Yaratiladi
False — — Meros (id)
True False True Yaratiladi (xavfli)

unsafe_hash=True nomi ogohlantiruvchi:

python
@dataclass(unsafe_hash=True)
class Xavfli:
    x: int

o = Xavfli(1)
s = {o}
o.x = 2                             # hash o'zgardi
o in s                              # False  ← yashirin element

Uni faqat maydonlar amalda o'zgarmasligini kafolatlay olsangiz ishlating.

NamedTuple — doim hashlanadi:

python
from typing import NamedTuple

class Nuqta(NamedTuple):
    x: int
    y: int

hash(Nuqta(1, 2))                   # ✅ tuple hash'i
Nuqta(1, 2) == (1, 2)               # ⚠️ True (6.6-dars)

Solishtirish:

__eq__ __hash__ O'zgaruvchan
Oddiy sinf id id
@dataclass Qiymat None
@dataclass(frozen=True) Qiymat Qiymat
NamedTuple Qiymat (tuple) Qiymat
@dataclass(unsafe_hash=True) Qiymat Qiymat

Tavsiya: kalit bo'ladigan qiymat obyektlari uchun @dataclass(frozen=True, slots=True) yoki NamedTuple.

2.6. Hash jadvali ichkarisi

Uyacha topish:

c
i = hash & (jadval_hajmi - 1)       // hajm — 2 ning darajasi

Bitli AND — % dan ~10x tez.

Faqat pastki bitlar ishlatiladi:

python
hash(1) & 7                     # 1
hash(9) & 7                     # 1  ← to'qnashuv (8 uyachali jadvalda)
hash(17) & 7                    # 1

Shuning uchun hash funksiyasi bitlarni yaxshi aralashtirishi kerak.

To'qnashuv hal qilish — ochiq adreslash (open addressing):

CPython zanjir ishlatmaydi (bog'langan ro'yxat), balki keyingi uyachani qidiradi:

c
perturb = hash;
while (band) {
    i = (i * 5 + 1 + perturb) & mask;
    perturb >>= 5;
}

Bu — tasodifiy probing: keyingi uyacha oldingi hash'ning yuqori bitlaridan hisoblanadi. Klasterlanishni kamaytiradi.

Nega zanjir emas:

  • Bog'langan ro'yxat — har element uchun qo'shimcha xotira
  • Kesh do'stligi yomon (xotirada sochilgan)
  • Ochiq adreslash — ketma-ket xotira, protsessor keshi yaxshi ishlaydi

O'sish:

Yuklanish (elementlar / hajm) chegaradan oshsa, jadval kattalashadi va barcha elementlar qayta joylashtiriladi (rehashing).

Tuzilma Yuklanish chegarasi O'sish
dict 2/3 ×2 yoki ×4
set 3/5 ×4 (kichik), ×2 (katta)

Rehashing — O(n). Lekin amortizatsiyalangan holda O(1) (6.2-dars mantiqi).

Hash keshlanadi:

python
s = "juda uzun satr" * 1000
hash(s)                         # birinchi marta — O(n)
hash(s)                         # keyin — O(1), keshdan

str obyekti hash'ni ichida saqlaydi. frozenset ham. tuple esa Python 3.14 dan boshlab saqlaydi — 3.13 gacha har safar qayta hisoblanardi (katta tuple'ni lug'at kaliti qilganda bu sezilardi).

Solishtirish tartibi:

Uyachada element topilganda:

c
if (yozuv->hash == qidirilayotgan_hash)     // 1. hash solishtirish (arzon)
    if (yozuv->key == key)                  // 2. identiklik (arzon)
        return topildi;
    if (PyObject_RichCompare(...))          // 3. __eq__ (qimmat)
        return topildi;

Uch bosqichli optimallashtirish. Shuning uchun:

  • Hash to'qnashuvi kam bo'lsa — __eq__ deyarli chaqirilmaydi
  • Interning (4.11-dars) 2-bosqichda tezlik beradi

2.7. To'qnashuvlar va HashDoS

To'qnashuv — ikki turli obyekt bir xil hash.

python
hash(1) == hash(1.0) == hash(True)      # True — ATAYLAB (teng qiymatlar)

# Tasodifiy to'qnashuvlar:
hash(-1) == hash(-2)                    # True — CPython nozikligi

hash(-1) == -2: CPython da -1 xato belgisi sifatida band, shuning uchun hash(-1) -2 qaytaradi.

To'qnashuvlar narxi:

Yuklanish O'rtacha probing
25% ~1.15
50% ~1.5
66% ~2.0
90% ~5.5
99% ~50

Yuklanish 2/3 dan past bo'lsa — deyarli har doim birinchi urinishda topiladi.

HashDoS hujumi:

Hujumchi barchasi bir xil hash beradigan kalitlar yuboradi:

POST /api  {"aaa": 1, "bbX": 2, "cYc": 3, ...}   # 10000 ta to'qnashuvchi kalit

Server ularni lug'atga qo'yadi → har qo'shish O(n) → jami O(n²) → server osiladi.

Himoya — hash randomizatsiyasi (PEP 456, Python 3.3+):

Har jarayon boshlanganda tasodifiy urug' (seed) tanlanadi. str va bytes hash'i shu urug'ga bog'liq:

bash
$ python -c "print(hash('a'))"
-7028830851905903200
$ python -c "print(hash('a'))"
2543271982834501283

Hujumchi kalitlarni oldindan hisoblay olmaydi.

Sonlar randomizatsiya qilinmaydi:

python
hash(42) == 42                  # doim

Sabab: sonlar uchun hash qiymatning o'zi — bu tez va range kabi ketma-ket kalitlarni yaxshi taqsimlaydi. Son kalitlar bilan HashDoS ham mumkin, lekin ancha qiyin.

SipHash algoritmi:

Python 3.4+ da str/bytes uchun SipHash-1-3 ishlatiladi — kriptografik tarzda kuchli, lekin tez.

2.8. hash() va kriptografik hash — farq

Butunlay turli maqsadlar:

hash() hashlib.sha256()
Maqsad Tez qidirish Xavfsizlik, butunlik
Natija 64-bit son 256-bit bayt
Tezlik ~50 ns ~1 µs
Jarayonlar aro barqaror (str)
Teskari qilib bo'lmaydi Qisman
To'qnashuvga chidamli

hash() ni fayl imzosi, parol yoki ID sifatida ishlatmang:

python
# ❌ Har jarayonda boshqa
kesh_kaliti = hash(foydalanuvchi_email)

# ❌ Parol
saqlangan = hash(parol)

# ✅ Barqaror identifikator
import hashlib
kesh_kaliti = hashlib.sha256(email.encode()).hexdigest()

# ✅ Parol (20-qism)
import bcrypt
saqlangan = bcrypt.hashpw(parol.encode(), bcrypt.gensalt())

hash() faqat bir jarayon ichida, xotirada ishlaydi.


3. Tez ma'lumotnoma

Hash talablari

1. Determinizm (bir jarayonda)
2. a == b  →  hash(a) == hash(b)     ⭐ MAJBURIY
3. Tez

Shartnoma

python
__eq__ yozdingiz  →  __hash__ AVTOMATIK None bo'ladi
                     (Python o'zi o'chiradi — himoya)

__hash__ faqat O'ZGARMAS maydonlardan hisoblansin
hash obyekt umri davomida O'ZGARMASIN

Sinf turlari

python
class A: ...                        eq=id,     hash=id       ✅ hashlanadi
@dataclass                          eq=qiymat, hash=None     ❌
@dataclass(frozen=True)             eq=qiymat, hash=qiymat   ✅ ⭐
@dataclass(eq=False)                eq=id,     hash=id       ✅
@dataclass(unsafe_hash=True)        eq=qiymat, hash=qiymat   ⚠️ o'zgaruvchan
class X(NamedTuple)                 eq=tuple,  hash=tuple    ✅

Hash jadvali

i = hash & (hajm - 1)               hajm — 2 ning darajasi
To'qnashuv → ochiq adreslash (tasodifiy probing)
Yuklanish: dict 2/3, set 3/5 → o'sish + rehashing O(n)

Solishtirish: hash → is → __eq__   (arzondan qimmatga)
Hash keshlanadi: str ✅, frozenset ✅, tuple ✅ (3.14+; oldin ❌)

Tuzoqlar

python
hash(-1) == -2                      CPython nozikligi
hash(1)==hash(1.0)==hash(True)      ataylab
hash((1,[2]))                       ❌ ichida list
str hash — har jarayonda BOSHQA     (PEP 456, HashDoS)
hash(42) == 42                      sonlar randomizatsiya QILINMAYDI

hash() ≠ hashlib                    xotira uchun, imzo uchun emas

4. Batafsil misollar

Misol 1 — Hash asoslari

python
"""hash() nima qiladi va nima uchun kerak."""

import sys

print("=== 1. Turli turlar hash'i ===")

NAMUNALAR = [
    ("42",                  42),
    ("-1",                  -1),
    ("0",                   0),
    ("1",                   1),
    ("1.0",                 1.0),
    ("True",                True),
    ("False",               False),
    ("3.14",                3.14),
    ("'a'",                 "a"),
    ("(1, 2)",              (1, 2)),
    ("frozenset([1, 2])",   frozenset([1, 2])),
    ("None",                None),
    ("b'ab'",               b"ab"),
    ("[1, 2]",              [1, 2]),
    ("{1, 2}",              {1, 2}),
    ("{'a': 1}",            {"a": 1}),
    ("(1, [2])",            (1, [2])),
]

print(f"  {'Obyekt':<22} {'hash()'}")
print("  " + "─" * 50)
for nom, o in NAMUNALAR:
    try:
        h = str(hash(o))
    except TypeError as x:
        h = f"❌ {x}"
    print(f"  {nom:<22} {h}")

print("""
  ⚠️ hash(-1) == -2 — CPython da -1 xato belgisi sifatida band
  ⚠️ hash(1) == hash(1.0) == hash(True) — ATAYLAB (teng qiymatlar)
  ⚠️ (1, [2]) — tuple o'zgarmas, lekin ICHIDA list bor
""")


print("\n=== 2. __hash__ = None ===")

TURLAR = [int, float, str, bytes, tuple, frozenset, type(None),
          list, dict, set, bytearray]

print(f"  {'Tur':<14} {'__hash__':<30} {'Hashlanadi'}")
print("  " + "─" * 58)
for t in TURLAR:
    h = t.__hash__
    holat = "✅" if h is not None else "❌"
    korinish = "None" if h is None else str(h).split()[0].strip("<")
    print(f"  {t.__name__:<14} {korinish:<30} {holat}")

print("""
  ⭐ __hash__ = None — "bu tur hashlanmaydi" degan ANIQ signal.
     Bu tasodif emas, ataylab qo'yilgan.
""")


print("\n=== 3. Nega o'zgaruvchan hashlanmaydi ===")


class SohtaHashlanadiganRoyxat(list):
    """⚠️ FAQAT NAMOYISH UCHUN — hech qachon yozmang!"""

    def __hash__(self):
        return hash(tuple(self))


r = SohtaHashlanadiganRoyxat([1, 2])
d = {r: "qiymat"}

print(f"  r = {list(r)}, d = {{r: 'qiymat'}}")
print(f"    hash(r) = {hash(r)}")
print(f"    d[r]    = {d[r]!r}   ✅\n")

r.append(4)
print(f"  r.append(4):")
print(f"    r       = {list(r)}")
print(f"    hash(r) = {hash(r)}   ← O'ZGARDI")

try:
    print(f"    d[r]    = {d[r]!r}")
except KeyError:
    print(f"    d[r]    → ❌ KeyError — obyekt lug'atda, lekin TOPILMAYDI")

print(f"\n    len(d)  = {len(d)}   ← hali ham ichida!")
print(f"    Iteratsiya bilan:")
for kalit, qiymat in d.items():
    print(f"      {list(kalit)} → {qiymat!r}")
    try:
        d[kalit]
    except KeyError:
        print(f"      ⚠️ d[kalit] → KeyError — o'z kalitini topa olmaydi!")

print(f"""
  ⚠️ NOZIK HOLAT: r.append(3) qilsak, kalit TOPILIB qolardi!
     8 uyali jadvalda hash((1, 2)) & 7 == hash((1, 2, 3)) & 7 == 3 —
     yangi hash tasodifan ESKI uyaga tushadi, Python esa uyada avval
     `is` bilan solishtiradi. Ya'ni xato har doim ham ko'rinmaydi —
     aynan shuning uchun u juda xavfli.""")

print("""
  ⭐ Shuning uchun Python o'zgaruvchan obyektlarni
     hashlashni TAQIQLAYDI. Bu — himoya, cheklov emas.
""")


print("\n=== 4. Hash uyachani belgilaydi ===")


def uyacha(x, hajm=8):
    return hash(x) & (hajm - 1)


MEVALAR = ["olma", "anor", "behi", "uzum", "nok", "shaftoli"]

print(f"  8 uyachali jadvalda:\n")
print(f"  {'Element':<12} {'hash()':>22} {'& 7':>5}")
print("  " + "─" * 44)
for m in MEVALAR:
    print(f"  {m:<12} {hash(m):>22} {uyacha(m):>5}")

jadval = [[] for _ in range(8)]
for m in MEVALAR:
    jadval[uyacha(m)].append(m)

print(f"\n  Jadval:")
for i, u in enumerate(jadval):
    belgi = ", ".join(u) if u else "─"
    ogoh = "  ⚠️ TO'QNASHUV" if len(u) > 1 else ""
    print(f"    [{i}] {belgi}{ogoh}")

print("""
  ⭐ hash & (hajm-1) — faqat PASTKI bitlar ishlatiladi.
     Shuning uchun hash funksiyasi bitlarni yaxshi
     aralashtirishi kerak.

  ⚠️ Bu natija HAR ISHGA TUSHIRISHDA boshqa —
     satrlar uchun hash randomizatsiyasi bor.
""")


print("\n=== 5. Sonlar uchun hash ===")

print(f"  Kichik butun sonlar — hash = qiymatning o'zi:")
for x in [0, 1, 42, 1000, -2, -100]:
    print(f"    hash({x:>6}) = {hash(x):>6}   {'✅ teng' if hash(x) == x else '⚠️ farqli'}")

print(f"\n  Katta sonlar (modul olinadi):")
for x in [2**61 - 1, 2**61, 2**64, 10**20]:
    print(f"    hash({x:>24}) = {hash(x)}")

print(f"\n  Float:")
for x in [1.0, 1.5, 2.0, 0.5, -1.5, float('inf')]:
    print(f"    hash({x:>6}) = {hash(x)}")

print(f"\n  ⭐ hash(n) == hash(float(n)) butun float lar uchun:")
for x in [1, 2, 100]:
    print(f"    hash({x}) == hash({float(x)}): {hash(x) == hash(float(x))}")

print(f"\n  Decimal va Fraction ham mos:")
from decimal import Decimal
from fractions import Fraction
print(f"    hash(1) == hash(Decimal('1')): {hash(1) == hash(Decimal('1'))}")
print(f"    hash(0.5) == hash(Fraction(1,2)): {hash(0.5) == hash(Fraction(1, 2))}")

print("""
  ⭐ Python sonli turlarni MOS hashlaydi:
     1 == 1.0 == Decimal('1') == Fraction(1,1)
     → hash'lari ham teng → bitta lug'at kaliti
""")

Natijaning muhim qismi:

text
=== 1. Turli turlar hash'i ===
  Obyekt                 hash()
  ──────────────────────────────────────────────────
  -1                     -2
  1                      1
  1.0                    1
  True                   1
  [1, 2]                 ❌ unhashable type: 'list'
  (1, [2])               ❌ unhashable type: 'list'

=== 3. Nega o'zgaruvchan hashlanmaydi ===
  r.append(4):
    hash(r) = -4363729961677198915   ← O'ZGARDI
    d[r]    → ❌ KeyError — obyekt lug'atda, lekin TOPILMAYDI

    len(d)  = 1   ← hali ham ichida!
      [1, 2, 4] → 'qiymat'
      ⚠️ d[kalit] → KeyError — o'z kalitini topa olmaydi!

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

Misol 2 — __hash__ / __eq__ shartnomasi

python
"""Uch qoida va ularni buzish oqibatlari."""

from dataclasses import dataclass

print("=== 1. Qoida 1: teng → teng hash ===")


class Buzuq:
    """❌ __eq__ qiymat bo'yicha, __hash__ id bo'yicha."""

    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        return isinstance(b, Buzuq) and self.q == b.q

    def __hash__(self):
        return id(self)                 # ⚠️ SHARTNOMA BUZILDI


class Togri:
    """✅ Ikkalasi ham bir xil maydondan."""

    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        return isinstance(b, Togri) and self.q == b.q

    def __hash__(self):
        return hash(self.q)


for sinf, nom in [(Buzuq, "Buzuq"), (Togri, "To'g'ri")]:
    a, b = sinf(1), sinf(1)
    print(f"\n  {nom}:")
    print(f"    a == b:                 {a == b}")
    print(f"    hash(a) == hash(b):     {hash(a) == hash(b)}")

    d = {a: "birinchi"}
    try:
        natija = d[b]
        print(f"    d[b] (a bilan teng):    {natija!r}  ✅")
    except KeyError:
        print(f"    d[b] (a bilan teng):    ❌ KeyError")

    s = {a, b}
    print(f"    len({{a, b}}):            {len(s)}  "
          f"{'✅' if len(s) == 1 else '⚠️ teng obyektlar ikki marta!'}")


print("\n\n=== 2. Qoida 2: __eq__ → __hash__ = None ===")


class FaqatEq:
    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        return isinstance(b, FaqatEq) and self.q == b.q


print(f"  class FaqatEq:  __eq__ yozildi, __hash__ yozilmadi\n")
print(f"    FaqatEq.__hash__ = {FaqatEq.__hash__}")

try:
    {FaqatEq(1)}
except TypeError as x:
    print(f"    {{FaqatEq(1)}}      → ❌ TypeError: {x}")

print("""
  ⭐ Python __hash__ ni AVTOMATIK None qildi.

     Nega: sukut __hash__ id() asosida ishlaydi.
     Agar __eq__ qiymat bo'yicha bo'lsa, sukut hash
     1-QOIDANI BUZARDI — teng obyektlar turli hash olardi.

     Python xavfsizlik uchun hashlashni O'CHIRADI.
     Bu — eng foydali "himoya to'sig'i".
""")

print("  Qayta yoqish uchun:")


class EqVaHash:
    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        return isinstance(b, EqVaHash) and self.q == b.q

    __hash__ = object.__hash__          # ⚠️ id bo'yicha — shartnoma buziladi!


class TogriEqVaHash:
    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        return isinstance(b, TogriEqVaHash) and self.q == b.q

    def __hash__(self):
        return hash(self.q)             # ✅ bir xil maydondan


print(f"    __hash__ = object.__hash__   ⚠️ shartnoma buziladi")
print(f"    def __hash__: hash(self.q)   ✅ to'g'ri")

a, b = EqVaHash(1), EqVaHash(1)
print(f"\n    EqVaHash:      a==b: {a == b}, len({{a,b}}): {len({a, b})}  ⚠️")
c, e = TogriEqVaHash(1), TogriEqVaHash(1)
print(f"    TogriEqVaHash: a==b: {c == e}, len({{a,b}}): {len({c, e})}  ✅")


print("\n\n=== 3. Qoida 3: hash o'zgarmasin ===")


class Ozgaruvchan:
    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        return isinstance(b, Ozgaruvchan) and self.q == b.q

    def __hash__(self):
        return hash(self.q)


o = Ozgaruvchan(1)
s = {o}

print(f"  o = Ozgaruvchan(1);  s = {{o}}")
print(f"    o in s:      {o in s}   ✅")
print(f"    len(s):      {len(s)}")

o.q = 2
print(f"\n  o.q = 2  (hash o'zgardi):")
print(f"    o in s:      {o in s}   ⚠️ FALSE!")
print(f"    len(s):      {len(s)}   ← hali ham ichida")
print(f"    list(s)[0] is o: {list(s)[0] is o}   ← O'SHA obyekt!")

print(f"\n  ⚠️ 'Yashirin element' — to'plamda bor, lekin topilmaydi.")

print("\n  ✅ Yechim — faqat o'zgarmas maydondan hash:")


class Xodim:
    """ID bo'yicha tenglik — DB obyektlari uchun standart naqsh."""

    def __init__(self, xodim_id: int, ism: str, maosh: int):
        self._id = xodim_id             # o'zgarmas
        self.ism = ism                  # o'zgarishi mumkin
        self.maosh = maosh              # o'zgarishi mumkin

    @property
    def id(self):
        return self._id

    def __eq__(self, b):
        return isinstance(b, Xodim) and self._id == b._id

    def __hash__(self):
        return hash(self._id)

    def __repr__(self):
        return f"Xodim({self._id}, {self.ism!r}, {self.maosh:,})"


x = Xodim(1, "Aziz", 7_000_000)
s = {x}
print(f"\n    x = {x}")
print(f"    x in s: {x in s}")

x.maosh = 8_000_000
x.ism = "Aziz Karimov"
print(f"\n    x.maosh va x.ism o'zgardi:")
print(f"    x = {x}")
print(f"    x in s: {x in s}   ✅ hali ham topiladi")

print(f"\n    Bir xil ID — teng:")
x2 = Xodim(1, "Boshqa ism", 999)
print(f"    x2 = {x2}")
print(f"    x == x2: {x == x2},  x2 in s: {x2 in s}   ✅")


print("\n\n=== 4. dataclass rejimlari ===")


@dataclass
class Sukut:
    x: int


@dataclass(frozen=True)
class Muzlatilgan:
    x: int


@dataclass(eq=False)
class EqSiz:
    x: int


@dataclass(unsafe_hash=True)
class Xavfli:
    x: int


from typing import NamedTuple


class NT(NamedTuple):
    x: int


class Oddiy:
    def __init__(self, x):
        self.x = x


SINFLAR = [
    ("class Oddiy",                     Oddiy),
    ("@dataclass",                      Sukut),
    ("@dataclass(frozen=True)",         Muzlatilgan),
    ("@dataclass(eq=False)",            EqSiz),
    ("@dataclass(unsafe_hash=True)",    Xavfli),
    ("NamedTuple",                      NT),
]

print(f"  {'Sinf':<32} {'a==b':>7} {'hash':>8} {'o`zg.':>7}")
print("  " + "─" * 58)

for nom, sinf in SINFLAR:
    a, b = sinf(1), sinf(1)
    teng = a == b
    try:
        hash(a)
        hashlanadi = "✅"
    except TypeError:
        hashlanadi = "❌"
    try:
        a.x = 2
        ozgaruvchan = "✅"
    except (AttributeError, TypeError):
        ozgaruvchan = "❌"
    print(f"  {nom:<32} {str(teng):>7} {hashlanadi:>7} {ozgaruvchan:>7}")

print("""
  ⭐ @dataclass(frozen=True) — kalit bo'ladigan
     qiymat obyektlari uchun ENG YAXSHI tanlov.

  ⚠️ unsafe_hash=True — nomi ogohlantiradi.
     Faqat maydonlar amalda o'zgarmasligini
     kafolatlay olsangiz ishlating.
""")

print("  unsafe_hash muammosi namoyishda:")
xv = Xavfli(1)
s = {xv}
print(f"    xv = Xavfli(1); s = {{xv}};  xv in s: {xv in s}")
xv.x = 2
print(f"    xv.x = 2;                   xv in s: {xv in s}  ⚠️")


print("\n=== 5. Solishtirish tartibi ===")


class Kuzatuvchi:
    """__eq__ chaqirilishini kuzatadi."""
    eq_chaqiruvlari = 0

    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        Kuzatuvchi.eq_chaqiruvlari += 1
        return isinstance(b, Kuzatuvchi) and self.q == b.q

    def __hash__(self):
        return hash(self.q)


obyektlar = [Kuzatuvchi(i) for i in range(1000)]
s = set(obyektlar)

Kuzatuvchi.eq_chaqiruvlari = 0
for o in obyektlar:
    _ = o in s
print(f"  1000 ta MAVJUD obyektni qidirish:")
print(f"    __eq__ chaqirilishlari: {Kuzatuvchi.eq_chaqiruvlari}")
print(f"    ⭐ 0 — chunki `is` tekshiruvi yetarli bo'ldi")

Kuzatuvchi.eq_chaqiruvlari = 0
for i in range(1000):
    _ = Kuzatuvchi(i) in s              # YANGI obyektlar
print(f"\n  1000 ta YANGI (lekin teng) obyektni qidirish:")
print(f"    __eq__ chaqirilishlari: {Kuzatuvchi.eq_chaqiruvlari}")
print(f"    ⭐ ~1000 — hash mos keldi, `is` mos kelmadi → __eq__")

Kuzatuvchi.eq_chaqiruvlari = 0
for i in range(2000, 3000):
    _ = Kuzatuvchi(i) in s              # YO'Q obyektlar
print(f"\n  1000 ta YO'Q obyektni qidirish:")
print(f"    __eq__ chaqirilishlari: {Kuzatuvchi.eq_chaqiruvlari}")
print(f"    ⭐ ~0 — hash mos kelmadi, __eq__ CHAQIRILMADI")

print("""
  ⭐ Uch bosqichli optimallashtirish:
     1. hash solishtirish   (arzon)
     2. `is` tekshiruvi     (arzon)
     3. __eq__ chaqirish    (qimmat)

     Shuning uchun __eq__ sekin bo'lsa ham,
     amalda kam chaqiriladi.
""")

Natijaning muhim qismi:

text
=== 1. Qoida 1: teng → teng hash ===

  Buzuq:
    a == b:                 True
    hash(a) == hash(b):     False
    d[b] (a bilan teng):    ❌ KeyError
    len({a, b}):            2  ⚠️ teng obyektlar ikki marta!

  To'g'ri:
    a == b:                 True
    hash(a) == hash(b):     True
    d[b] (a bilan teng):    'birinchi'  ✅
    len({a, b}):            1  ✅

=== 4. dataclass rejimlari ===
  Sinf                                a==b     hash   o`zg.
  ──────────────────────────────────────────────────────────
  class Oddiy                        False       ✅       ✅
  @dataclass                          True       ❌       ✅
  @dataclass(frozen=True)             True       ✅       ❌
  @dataclass(eq=False)               False       ✅       ✅
  @dataclass(unsafe_hash=True)        True       ✅       ✅
  NamedTuple                          True       ✅       ❌

=== 5. Solishtirish tartibi ===
  1000 ta MAVJUD obyektni qidirish:
    __eq__ chaqirilishlari: 0
  1000 ta YANGI (lekin teng) obyektni qidirish:
    __eq__ chaqirilishlari: 1000
  1000 ta YO'Q obyektni qidirish:
    __eq__ chaqirilishlari: 0

Nima ko'rsatdi: 2.3, 2.4, 2.5, 2.6-bo'limlar.

Misol 3 — To'qnashuvlar va tezlik

python
"""Hash sifati qanday qilib tezlikka ta'sir qiladi."""

import time
import sys
from collections import Counter

print("=== 1. Yomon hash narxi ===")


class YaxshiHash:
    def __init__(self, q):
        self.q = q

    def __hash__(self):
        return hash(self.q)

    def __eq__(self, b):
        return isinstance(b, YaxshiHash) and self.q == b.q


class OrtachaHash:
    """Faqat 100 xil hash qiymati."""

    def __init__(self, q):
        self.q = q

    def __hash__(self):
        return self.q % 100

    def __eq__(self, b):
        return isinstance(b, OrtachaHash) and self.q == b.q


class YomonHash:
    """Barcha obyekt uchun bir xil hash."""

    def __init__(self, q):
        self.q = q

    def __hash__(self):
        return 42

    def __eq__(self, b):
        return isinstance(b, YomonHash) and self.q == b.q


N = 3_000

print(f"  {N:,} obyekt bilan:\n")
print(f"  {'Hash sifati':<24} {'Qurish':>12} {'Qidirish':>12} {'Nisbat':>9}")
print("  " + "─" * 60)

natijalar = []
for sinf, nom in [(YaxshiHash, "Yaxshi (to'liq)"),
                  (OrtachaHash, "O'rtacha (100 xil)"),
                  (YomonHash, "Yomon (hammasi 42)")]:
    obyektlar = [sinf(i) for i in range(N)]

    boshlandi = time.perf_counter()
    s = set(obyektlar)
    qurish = time.perf_counter() - boshlandi

    qidiruvlar = obyektlar[::10]
    boshlandi = time.perf_counter()
    for o in qidiruvlar:
        _ = o in s
    qidirish = time.perf_counter() - boshlandi

    natijalar.append((nom, qurish, qidirish))

eng_tez = min(q for _, q, _ in natijalar)
for nom, qurish, qidirish in natijalar:
    print(f"  {nom:<24} {qurish * 1000:>9.1f} ms {qidirish * 1000:>9.1f} ms "
          f"{qurish / eng_tez:>8.0f}x")

print("""
  ⭐ Yomon hash to'plamni RO'YXATGA aylantiradi: O(1) → O(n)
     Qurish esa O(n) → O(n²)
""")


print("\n=== 2. Hash taqsimoti ===")


def taqsimot(elementlar, hajm=64):
    uyachalar = Counter(hash(x) & (hajm - 1) for x in elementlar)
    return uyachalar


TOPLAMLAR = [
    ("Ketma-ket sonlar",    list(range(64))),
    ("Tasodifiy sonlar",    [i * 7919 % 100003 for i in range(64)]),
    ("Qisqa satrlar",       [f"k{i}" for i in range(64)]),
    ("Tuple lar",           [(i, i * 2) for i in range(64)]),
    ("64 ning karralari",   [i * 64 for i in range(64)]),
]

print(f"  64 uyachali jadvalda 64 element:\n")
print(f"  {'Ma`lumot turi':<22} {'Band uyacha':>12} {'Max/uyacha':>12} {'Sifat'}")
print("  " + "─" * 60)

for nom, elementlar in TOPLAMLAR:
    t = taqsimot(elementlar)
    band = len(t)
    eng_kop = max(t.values())
    sifat = "✅" if band > 40 else ("⚠️" if band > 20 else "❌")
    print(f"  {nom:<22} {band:>12} {eng_kop:>12} {sifat:>6}")

print(f"\n  '64 ning karralari' taqsimoti:")
t = taqsimot([i * 64 for i in range(64)])
for uyacha in sorted(t)[:8]:
    print(f"    [{uyacha:>2}] {'█' * t[uyacha]} {t[uyacha]}")

print("""
  ⚠️ i * 64 → hash = i * 64 → & 63 = 0 (hammasi!)

     Sonlar uchun hash = qiymatning o'zi.
     Agar kalitlar jadval hajmiga karrali bo'lsa —
     hammasi bitta uyachaga tushadi.

  ⭐ CPython buni "perturbation" bilan yumshatadi:
     probing yuqori bitlarni ham hisobga oladi.
""")


print("\n=== 3. Yuklanish va probing ===")

print("""  Nazariy o'rtacha probing soni (ochiq adreslash):

    Yuklanish    O'rtacha probing
    ─────────────────────────────
       25%            1.2
       50%            1.5
       66%            2.0     ← dict chegarasi
       75%            2.5
       90%            5.5
       95%           10.5
       99%           50.5

  ⭐ CPython yuklanishni 2/3 dan past ushlab turadi (dict),
     3/5 (set) — shuning uchun amalda ~1.5 probing.
""")

print("  Amaliy o'lchov — jadval o'sishi:")
d = {}
oldingi_hajm = sys.getsizeof(d)
osishlar = []
for i in range(200):
    d[i] = i
    hajm = sys.getsizeof(d)
    if hajm != oldingi_hajm:
        osishlar.append((len(d), hajm))
        oldingi_hajm = hajm

print(f"\n    {'Elementlar':>11} {'Hajm':>9} {'O`sish':>9}")
print("    " + "─" * 32)
oldingi = 0
for n, h in osishlar[:10]:
    osish = f"{h / oldingi:.2f}x" if oldingi else "—"
    print(f"    {n:>11} {h:>9,} {osish:>9}")
    oldingi = h


print("\n=== 4. Hash keshlash ===")

import timeit

UZUN_SATR = "x" * 100_000
UZUN_TUPLE = tuple(range(10_000))
UZUN_FROZENSET = frozenset(range(10_000))

# Birinchi hash
h1 = hash(UZUN_SATR)
h2 = hash(UZUN_TUPLE)
h3 = hash(UZUN_FROZENSET)

SINOVLAR = [
    ("str (100k belgi)",        "hash(s)",  {"s": UZUN_SATR}),
    ("tuple (10k element)",     "hash(t)",  {"t": UZUN_TUPLE}),
    ("frozenset (10k)",         "hash(f)",  {"f": UZUN_FROZENSET}),
]

print(f"  Takroriy hash chaqiruvi:\n")
print(f"  {'Tur':<24} {'ns/chaqiruv':>14} {'Keshlanadi'}")
print("  " + "─" * 52)
for nom, kod, muhit in SINOVLAR:
    vaqt = timeit.timeit(kod, globals=muhit, number=100_000)
    ns = vaqt / 100_000 * 1e9
    keshlanadi = "✅" if ns < 100 else "❌"
    print(f"  {nom:<24} {ns:>12.1f} ns {keshlanadi:>10}")

print("""
  ⭐ str va frozenset hash'ni obyekt ichida SAQLAYDI.
     tuple — Python 3.14 dan saqlaydi (3.13 gacha har safar qayta
     hisoblardi; natijaga qarab o'z versiyangizni tekshiring).

  ⚠️ Katta tuple ni lug'at kaliti sifatida ko'p ishlatsangiz,
     frozenset yoki oldindan hisoblangan kalit tejamliroq.
""")


print("\n=== 5. HashDoS namoyishi ===")

print("  Bir xil hash beradigan kalitlar bilan hujum:\n")


class HujumKaliti:
    """Barchasi bir xil hash — HashDoS simulyatsiyasi."""

    def __init__(self, i):
        self.i = i

    def __hash__(self):
        return 0

    def __eq__(self, b):
        return isinstance(b, HujumKaliti) and self.i == b.i


print(f"  {'Kalitlar soni':>15} {'Normal':>12} {'Hujum':>12} {'Sekinlashuv':>13}")
print("  " + "─" * 56)

for n in [100, 500, 1000, 2000]:
    normal = [f"kalit{i}" for i in range(n)]
    hujum = [HujumKaliti(i) for i in range(n)]

    boshlandi = time.perf_counter()
    d1 = {k: 1 for k in normal}
    vaqt_normal = time.perf_counter() - boshlandi

    boshlandi = time.perf_counter()
    d2 = {k: 1 for k in hujum}
    vaqt_hujum = time.perf_counter() - boshlandi

    print(f"  {n:>15,} {vaqt_normal * 1000:>10.2f}ms "
          f"{vaqt_hujum * 1000:>10.2f}ms {vaqt_hujum / vaqt_normal:>12.0f}x")

print("""
  ⭐ Kalitlar soni 2x oshsa, hujum vaqti 4x oshadi — O(n²).

     Haqiqiy hujumda: JSON so'rovda 10 000 to'qnashuvchi kalit
     → server bir so'rovda bir necha soniya band bo'ladi
     → xizmatni rad etish (DoS).

  ⭐ Himoya: hash randomizatsiyasi (PEP 456).
     Hujumchi str hash'ini oldindan hisoblay olmaydi.
""")

print("  Satr hash'i har jarayonda boshqa:")
import subprocess
for i in range(3):
    try:
        h = subprocess.run(
            [sys.executable, "-c", "print(hash('hujum'))"],
            capture_output=True, text=True, timeout=10
        ).stdout.strip()
        print(f"    {i + 1}. hash('hujum') = {h}")
    except Exception:
        print(f"    {i + 1}. (o'lchab bo'lmadi)")

print(f"\n  ⚠️ Sonlar randomizatsiya QILINMAYDI:")
print(f"    hash(42) = {hash(42)} — har doim")

Natijaning muhim qismi:

text
=== 1. Yomon hash narxi ===
  3,000 obyekt bilan:

  Hash sifati                    Qurish     Qidirish    Nisbat
  ────────────────────────────────────────────────────────────
  Yaxshi (to'liq)                  1.2 ms      0.3 ms        1x
  O'rtacha (100 xil)              18.4 ms      4.1 ms       15x
  Yomon (hammasi 42)            2841.2 ms    284.3 ms     2368x

=== 4. Hash keshlash ===
  Tur                         ns/chaqiruv  Keshlanadi
  ────────────────────────────────────────────────────
  str (100k belgi)                   41.2 ns          ✅
  tuple (10k element)              41.0 ns          ✅
  frozenset (10k)                    38.1 ns          ✅

=== 5. HashDoS namoyishi ===
    Kalitlar soni       Normal        Hujum   Sekinlashuv
  ────────────────────────────────────────────────────────
              100         0.01ms       0.42ms           42x
              500         0.04ms       8.91ms          223x
            1,000         0.08ms      35.12ms          439x
            2,000         0.16ms     141.83ms          886x

Nima ko'rsatdi: 2.6, 2.7-bo'limlar.

Misol 4 — Amaliy: to'g'ri hashlanadigan sinflar

python
"""Turli vazifalar uchun to'g'ri hash strategiyasi."""

from dataclasses import dataclass, field
from typing import NamedTuple
from decimal import Decimal
import hashlib
import json

print("=== 1. Qiymat obyekti (value object) ===")


@dataclass(frozen=True, slots=True)
class Pul:
    """Miqdor + valyuta. To'liq o'zgarmas."""
    miqdor: Decimal
    valyuta: str = "UZS"

    def __post_init__(self):
        if not isinstance(self.miqdor, Decimal):
            object.__setattr__(self, "miqdor", Decimal(str(self.miqdor)))
        object.__setattr__(self, "valyuta", self.valyuta.upper())

    def __add__(self, b: "Pul") -> "Pul":
        if self.valyuta != b.valyuta:
            raise ValueError(f"Turli valyuta: {self.valyuta} + {b.valyuta}")
        return Pul(self.miqdor + b.miqdor, self.valyuta)

    def __str__(self):
        return f"{self.miqdor:,.2f} {self.valyuta}"


NARXLAR = {
    Pul(5000): "non",
    Pul(12000): "sut",
    Pul(5000, "uzs"): "non (takror)",       # ⭐ bir xil kalit
    Pul(100, "USD"): "import",
}

print(f"  Lug'at ({len(NARXLAR)} kalit — 4 ta qo'shildi):")
for pul, nom in NARXLAR.items():
    print(f"    {str(pul):>18}  {nom}")

print(f"\n  ⭐ Pul(5000) va Pul(5000, 'uzs') — BIR XIL kalit")
print(f"     hash teng: {hash(Pul(5000)) == hash(Pul(5000, 'uzs'))}")
print(f"     teng:      {Pul(5000) == Pul(5000, 'uzs')}")

s = {Pul(5000), Pul(5000), Pul(12000)}
print(f"\n  To'plam: {len(s)} element (3 ta qo'shildi)")

try:
    p = Pul(5000)
    p.miqdor = Decimal(9999)
except Exception as x:
    print(f"\n  O'zgartirishga urinish: ❌ {type(x).__name__}")


print("\n\n=== 2. Identifikatsiya obyekti (entity) ===")


class Foydalanuvchi:
    """ID bo'yicha tenglik — maydonlar o'zgarishi mumkin."""

    def __init__(self, foydalanuvchi_id: int, ism: str, email: str):
        self._id = foydalanuvchi_id
        self.ism = ism
        self.email = email
        self.oxirgi_kirish = None

    @property
    def id(self) -> int:
        return self._id

    def __eq__(self, b):
        return isinstance(b, Foydalanuvchi) and self._id == b._id

    def __hash__(self):
        return hash(self._id)               # ⭐ faqat o'zgarmas maydon

    def __repr__(self):
        return f"Foydalanuvchi({self._id}, {self.ism!r})"


f1 = Foydalanuvchi(1, "Aziz", "aziz@mail.uz")
f2 = Foydalanuvchi(1, "Aziz Karimov", "yangi@mail.uz")     # bir xil ID
f3 = Foydalanuvchi(2, "Bobur", "bobur@mail.uz")

FAOL = {f1, f3}

print(f"  f1 = {f1}")
print(f"  f2 = {f2}   ← bir xil ID, boshqa maydonlar")
print(f"  f3 = {f3}\n")

print(f"    f1 == f2:     {f1 == f2}   ✅ ID bir xil")
print(f"    f2 in FAOL:   {f2 in FAOL}   ✅")
print(f"    len(FAOL):    {len(FAOL)}")

f1.ism = "Aziz Yangi"
f1.email = "boshqa@mail.uz"
print(f"\n  f1 maydonlari o'zgardi: {f1}")
print(f"    f1 in FAOL:   {f1 in FAOL}   ✅ hali ham topiladi")

print("""
  ⭐ Bu — ORM va DB obyektlari uchun STANDART naqsh:
     • Tenglik va hash — faqat birlamchi kalit (id)
     • Boshqa maydonlar erkin o'zgaradi
""")


print("\n\n=== 3. Kompozit kalit ===")


class KeshKaliti(NamedTuple):
    """Ko'p mezonli kesh kaliti."""
    foydalanuvchi_id: int
    resurs: str
    versiya: int = 1
    parametrlar: frozenset = frozenset()        # ⭐ frozenset, dict emas


KESH: dict[KeshKaliti, str] = {}


def olish(foydalanuvchi_id, resurs, versiya=1, **parametrlar):
    kalit = KeshKaliti(
        foydalanuvchi_id, resurs, versiya,
        frozenset(parametrlar.items())          # ⭐ tartibsiz, hashlanadi
    )
    if kalit in KESH:
        return f"KESHDAN: {KESH[kalit]}"
    natija = f"{resurs}#{foydalanuvchi_id}v{versiya}"
    KESH[kalit] = natija
    return f"HISOBLANDI: {natija}"


SOROVLAR = [
    (1, "profil"),
    (1, "profil"),                                      # keshdan
    (1, "profil", 2),
    (2, "profil"),
    (1, "sozlamalar", 1, {"til": "uz", "tema": "qora"}),
    (1, "sozlamalar", 1, {"tema": "qora", "til": "uz"}), # ⭐ boshqa tartib → keshdan
]

print(f"  {'So`rov':<44} Natija")
print("  " + "─" * 74)
for sorov in SOROVLAR:
    if len(sorov) == 4:
        fid, res, ver, par = sorov
        natija = olish(fid, res, ver, **par)
        korinish = f"olish({fid}, {res!r}, {ver}, **{par})"
    else:
        natija = olish(*sorov)
        korinish = f"olish{sorov}"
    print(f"  {korinish:<44} {natija}")

print(f"\n  Kesh: {len(KESH)} yozuv (6 so'rov)")
print("""
  ⭐ frozenset(kwargs.items()) — nomli argumentlar tartibidan
     mustaqil kanonik kalit (6.9-dars).
""")


print("\n\n=== 4. Hash strategiyasi tanlash ===")

print("""
  ┌──────────────────────────┬─────────────────────────────────┐
  │ Obyekt turi              │ Strategiya                      │
  ├──────────────────────────┼─────────────────────────────────┤
  │ Qiymat (nuqta, pul, rang)│ @dataclass(frozen=True) ⭐       │
  │ Kichik qiymat            │ NamedTuple ⭐                    │
  │ DB yozuvi (entity)       │ hash(self.id) ⭐                 │
  │ Ulanish, fayl, oqim      │ Sukut (id) — o'zgartirmang      │
  │ Konfiguratsiya           │ @dataclass(frozen=True)         │
  │ Kesh kaliti              │ NamedTuple + frozenset ⭐        │
  │ O'zgaruvchan konteyner   │ Hashlanmasin (__hash__ = None)  │
  └──────────────────────────┴─────────────────────────────────┘
""")


print("\n=== 5. hash() vs hashlib ===")

MALUMOT = {"foydalanuvchi": "aziz", "amal": "kirish", "vaqt": 1725782400}

print(f"  Ma'lumot: {MALUMOT}\n")

print(f"  hash() — xotira uchun:")
print(f"    hash(tuple(sorted(MALUMOT.items()))) = "
      f"{hash(tuple(sorted(MALUMOT.items())))}")
print(f"    ⚠️ Har jarayonda BOSHQA (satrlar bor)")

matn = json.dumps(MALUMOT, sort_keys=True)
sha = hashlib.sha256(matn.encode()).hexdigest()
print(f"\n  hashlib — barqaror imzo:")
print(f"    sha256(...) = {sha[:32]}...")
print(f"    ✅ Har doim, har joyda BIR XIL")

import time
N = 100_000
kalit = tuple(sorted(MALUMOT.items()))

boshlandi = time.perf_counter()
for _ in range(N):
    hash(kalit)
vaqt_hash = time.perf_counter() - boshlandi

boshlandi = time.perf_counter()
for _ in range(N):
    hashlib.sha256(matn.encode()).hexdigest()
vaqt_sha = time.perf_counter() - boshlandi

print(f"\n  Tezlik ({N:,} chaqiruv):")
print(f"    hash():             {vaqt_hash * 1000:>8.1f} ms  "
      f"({vaqt_hash / N * 1e9:.0f} ns/chaqiruv)")
print(f"    hashlib.sha256():   {vaqt_sha * 1000:>8.1f} ms  "
      f"({vaqt_sha / N * 1e9:.0f} ns/chaqiruv)")
print(f"    Farq:               {vaqt_sha / vaqt_hash:>8.0f}x")

print("""
  ⭐ hash()   → lug'at/to'plam, bir jarayon, xotirada
     hashlib  → fayl imzosi, ETag, kesh kaliti (diskda),
                versiya identifikatori, butunlik tekshiruvi

  ⚠️ Parol uchun ikkalasi ham EMAS — bcrypt/argon2 (20-qism)
""")


print("\n=== 6. Xatolarni topish vositasi ===")


def hash_shartnomasini_tekshir(sinf, *namunalar):
    """Sinf hash shartnomasiga rioya qiladimi."""
    muammolar = []

    # Hashlanadimi
    try:
        hash(namunalar[0])
    except TypeError:
        return ["❌ Hashlanmaydi (__hash__ = None yoki yo'q)"]

    # Qoida 1: teng → teng hash
    for i, a in enumerate(namunalar):
        for b in namunalar[i + 1:]:
            if a == b and hash(a) != hash(b):
                muammolar.append(
                    f"❌ QOIDA 1: {a!r} == {b!r}, lekin hash farqli"
                )

    # Qoida 3: hash barqarormi (o'zgaruvchan maydonlarni sinash)
    o = namunalar[0]
    eski_hash = hash(o)
    ozgartirildi = False
    for maydon in list(vars(o)) if hasattr(o, "__dict__") else []:
        try:
            eski_qiymat = getattr(o, maydon)
            setattr(o, maydon, "O'ZGARTIRILDI")
            ozgartirildi = True
            if hash(o) != eski_hash:
                muammolar.append(
                    f"⚠️ QOIDA 3: '{maydon}' o'zgarganda hash o'zgardi"
                )
            setattr(o, maydon, eski_qiymat)
        except (AttributeError, TypeError):
            pass

    if not ozgartirildi and not muammolar:
        muammolar.append("✅ O'zgarmas — hash barqaror")

    return muammolar or ["✅ Shartnomaga rioya qilinadi"]


print(f"  Tekshiruv natijalari:\n")

SINOVLAR = [
    ("Pul (frozen dataclass)",
     Pul, [Pul(100), Pul(100), Pul(200)]),
    ("Foydalanuvchi (id hash)",
     Foydalanuvchi, [Foydalanuvchi(1, "a", "a@x"), Foydalanuvchi(1, "b", "b@x")]),
    ("KeshKaliti (NamedTuple)",
     KeshKaliti, [KeshKaliti(1, "a"), KeshKaliti(1, "a")]),
]

for nom, sinf, namunalar in SINOVLAR:
    print(f"  {nom}:")
    for xabar in hash_shartnomasini_tekshir(sinf, *namunalar):
        print(f"    {xabar}")
    print()


class BuzuqSinf:
    def __init__(self, q):
        self.q = q

    def __eq__(self, b):
        return isinstance(b, BuzuqSinf) and self.q == b.q

    def __hash__(self):
        return hash(self.q)         # ⚠️ q o'zgaruvchan


print(f"  BuzuqSinf (o'zgaruvchan maydondan hash):")
for xabar in hash_shartnomasini_tekshir(
        BuzuqSinf, BuzuqSinf(1), BuzuqSinf(1)):
    print(f"    {xabar}")

Natijaning muhim qismi:

text
=== 1. Qiymat obyekti (value object) ===
  Lug'at (3 kalit — 4 ta qo'shildi):
              5,000.00 UZS  non (takror)
             12,000.00 UZS  sut
                100.00 USD  import

  ⭐ Pul(5000) va Pul(5000, 'uzs') — BIR XIL kalit

=== 3. Kompozit kalit ===
  So`rov                                       Natija
  ──────────────────────────────────────────────────────────────────────────
  olish(1, 'profil')                           HISOBLANDI: profil#1v1
  olish(1, 'profil')                           KESHDAN: profil#1v1
  olish(1, 'sozlamalar', 1, **{'til': ...})    HISOBLANDI: sozlamalar#1v1
  olish(1, 'sozlamalar', 1, **{'tema': ...})   KESHDAN: sozlamalar#1v1

=== 6. Xatolarni topish vositasi ===
  BuzuqSinf (o'zgaruvchan maydondan hash):
    ⚠️ QOIDA 3: 'q' o'zgarganda hash o'zgardi

Nima ko'rsatdi: 2.3, 2.5, 2.8-bo'limlar.


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

Noto'g'ri fikr To'g'risi
"hash(a) == hash(b) → a == b" To'qnashuv mumkin
"__eq__ yozsam yetarli" __hash__ avtomatik None bo'ladi
"Sukut __hash__ har doim to'g'ri" __eq__ o'zgartirilsa — shartnoma buziladi
"@dataclass hashlanadi" frozen=True kerak
"O'zgarmas tuple doim hashlanadi" Ichida list bo'lsa yo'q
"hash() xavfsiz identifikator" Har jarayonda boshqa (str uchun)
"hash(-1) == -1" -2 — CPython nozikligi
"To'qnashuv — nazariy muammo" HashDoS — real hujum vektori
"tuple hash keshlanadi" str va frozenset — ha, tuple — yo'q

6. Keng tarqalgan xatolar va yechimlari

1. Faqat __eq__ yozish

python
class A:
    def __eq__(self, b): ...        # ❌ hashlanmaydi

class A:
    def __eq__(self, b): ...
    def __hash__(self):             # ✅
        return hash(self.kalit)

2. __hash__ va __eq__ turli maydonlardan

python
def __eq__(self, b): return self.x == b.x
def __hash__(self): return hash(self.y)     # ❌ SHARTNOMA BUZILDI

def __hash__(self): return hash(self.x)     # ✅ bir xil maydon

3. O'zgaruvchan maydondan hash

python
def __hash__(self):
    return hash(self.maosh)         # ⚠️ o'zgarsa yashirin element

def __hash__(self):
    return hash(self._id)           # ✅ o'zgarmas

4. @dataclass ni kalit sifatida

python
@dataclass                          # ❌ hashlanmaydi
class Nuqta: x: int

@dataclass(frozen=True)             # ✅
class Nuqta: x: int

5. unsafe_hash=True ni o'ylamasdan

python
@dataclass(unsafe_hash=True)        # ⚠️ nomi ogohlantiradi
class A: x: int

@dataclass(frozen=True)             # ✅
class A: x: int

6. hash() ni barqaror ID sifatida

python
kesh_kaliti = hash(email)                       # ❌ jarayonlar aro barqaror emas
kesh_kaliti = hashlib.sha256(email.encode()).hexdigest()    # ✅

7. Tuple ichida o'zgaruvchan element

python
kalit = (1, [2, 3])                 # ❌ unhashable
kalit = (1, (2, 3))                 # ✅
kalit = (1, frozenset([2, 3]))      # ✅

8. __eq__ da isinstance tekshirmaslik

python
def __eq__(self, b):
    return self.x == b.x            # ❌ AttributeError boshqa tur bilan

def __eq__(self, b):
    return isinstance(b, Nuqta) and self.x == b.x       # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 6.7, 6.10-darslar (o'tilgan): set va dict — hash jadvali
  • 6.5, 6.9-darslar (o'tilgan): tuple, frozenset hashlanishi
  • 4.11-dars (o'tilgan): satr interning va hash keshi
  • 8-qism: __eq__, __hash__, dataclass, __slots__
  • 10-qism: functools.lru_cache — hashlanadigan argumentlar
  • 15, 20-qismlar: kriptografiya, hashlib, parollar
  • 23-qism: ORM, birlamchi kalit, obyekt identifikatsiyasi

8. Eng yaxshi amaliyotlar

  1. __eq__ va __hash__ — birga va bir xil maydonlardan. Bu — buzilmaydigan qoida.

  2. Hash faqat o'zgarmas maydonlardan. DB obyektlari uchun hash(self.id).

  3. Qiymat obyektlari — @dataclass(frozen=True, slots=True). Yoki NamedTuple.

  4. unsafe_hash=True dan qoching. Nomi bejiz emas.

  5. __eq__ da isinstance tekshiring. Aks holda boshqa tur bilan AttributeError.

  6. Barqaror identifikator uchun hashlib. hash() — faqat xotirada, bir jarayonda.

  7. O'zgaruvchan konteyner hashlanmasin. Sukut xatti-harakat to'g'ri — buzmang.

  8. Kompozit kalit — NamedTuple yoki frozenset. Tartib muhim bo'lsa birinchisi, muhim bo'lmasa ikkinchisi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
1.  print(hash(-1))
2.  print(hash(1) == hash(1.0) == hash(True))
3.  print(list.__hash__)
4.  print(hash((1, (2, 3))))
5.  hash((1, [2]))
6.  class A:
        def __eq__(self, b): return True
    print(A.__hash__)
7.  from dataclasses import dataclass
    @dataclass
    class B: x: int
    hash(B(1))
8.  @dataclass(frozen=True)
    class C: x: int
    print(hash(C(1)) == hash(C(1)))
9.  print(hash(frozenset([1,2])) == hash(frozenset([2,1])))
10. print(hash((1,2)) == hash((2,1)))
11. class D:
        def __init__(s, x): s.x = x
    print(D(1) == D(1))
12. print(hash(42) == 42)
Javoblar
  1. -2 — CPython nozikligi
  2. True — teng qiymatlar
  3. None — hashlanmaydi
  4. Biror son — ichma-ich tuple hashlanadi
  5. TypeError — ichida list
  6. None — Python o'chirdi
  7. TypeError — @dataclass __hash__ = None
  8. True — frozen=True hash yaratadi
  9. True — tartib muhim emas
  10. False — tuple da tartib muhim
  11. False — sukut __eq__ id bo'yicha
  12. True — kichik butun sonlar

Vazifa 2: Xatolarni tuzating

python
1.  class A:
        def __eq__(self, b): return self.x == b.x
2.  def __eq__(self, b): return self.id == b.id
    def __hash__(self): return hash(self.ism)
3.  @dataclass
    class Nuqta: x: int
    d = {Nuqta(1): "a"}
4.  def __hash__(self): return hash(self.maosh)   # maosh o'zgaradi
5.  kalit = (foydalanuvchi_id, ruxsatlar_royxati)
6.  kesh_id = hash(email)   # DB da saqlanadi
7.  def __eq__(self, b): return self.x == b.x     # b boshqa tur bo'lishi mumkin
8.  @dataclass(unsafe_hash=True)
    class Buyurtma: mahsulotlar: list
Javoblar
python
1.  def __hash__(self): return hash(self.x)   # qo'shing
2.  def __hash__(self): return hash(self.id)  # bir xil maydon
3.  @dataclass(frozen=True)
4.  def __hash__(self): return hash(self._id)
5.  kalit = (foydalanuvchi_id, frozenset(ruxsatlar_royxati))
6.  kesh_id = hashlib.sha256(email.encode()).hexdigest()
7.  return isinstance(b, Nuqta) and self.x == b.x
8.  @dataclass(frozen=True)
    class Buyurtma: mahsulotlar: tuple

Vazifa 3: Hash tekshiruvchi

hash_shartnomasini_tekshir ni kengaytiring:

  1. Uchala qoidani ham avtomatik tekshirsin
  2. dataclass maydonlarini aniqlasin
  3. Ichma-ich obyektlarni tekshirsin
  4. Hash taqsimotini baholasin (100 namuna bilan)
  5. Tavsiyalar bersin (frozen=True qo'shing va h.k.)
  6. pytest testi sifatida ishlasin

Vazifa 4: Hash jadvali

Noldan hash jadvali yozing:

  1. qoy(k, v), ol(k), ochir(k), __len__, __contains__
  2. Ochiq adreslash (chiziqli va tasodifiy probing)
  3. Yuklanish 2/3 dan oshsa o'sish
  4. TOMBSTONE bilan o'chirish
  5. Statistika: to'qnashuvlar, o'rtacha probing, yuklanish
  6. Zanjir usuli bilan solishtiring

Vazifa 5: Qiymat obyektlari kutubxonasi

@dataclass(frozen=True) bilan yozing:

  1. Pul(miqdor, valyuta) — +, -, *, taqqoslash
  2. Oraliq(boshi, oxiri) — kesishish, birlashma, in
  3. Versiya(major, minor, patch) — taqqoslash, >, parsing
  4. Rang(r, g, b) — hex, aralashtirish, masofa
  5. Har biri hashlanadigan va lug'at kaliti bo'la olsin
  6. Shartnoma testlari

Vazifa 6: HashDoS tadqiqoti

  1. Berilgan Python versiyasi uchun to'qnashuvchi satrlar toping (PYTHONHASHSEED=0 bilan)
  2. n = 100…5000 uchun sekinlashuvni o'lchang
  3. Grafik chizing (O(n) vs O(n²))
  4. dict, set, Counter uchun taqqoslang
  5. Himoya usullarini sinang (kalitlar sonini cheklash, frozenset kaliti)

Vazifa 7: O'ylash

Nega Python __eq__ aniqlanganda __hash__ ni avtomatik None qiladi, __eq__ bilan mos hash avtomatik yaratmaydi?

Javob

Chunki Python __eq__ nimaga qarashini bilmaydi.

Avtomatik hash yaratish uchun nima kerak:

python
def __eq__(self, b):
    return self.x == b.x            # faqat x

Python bu kodni tahlil qilib, "hash self.x dan hisoblanishi kerak" degan xulosaga kelishi kerak edi. Bu — statik tahlil, va u umumiy holda imkonsiz:

python
def __eq__(self, b):
    if self.turi == "A":
        return self.x == b.x
    return self.y == b.y and self.z != b.z      # qaysi maydondan hash?

def __eq__(self, b):
    return self.hisobla() == b.hisobla()        # metod natijasi

def __eq__(self, b):
    return abs(self.x - b.x) < 0.001            # ⚠️ tranzitiv EMAS

Oxirgi misol ayniqsa muhim: taxminiy tenglik uchun to'g'ri hash mavjud emas. a ≈ b va b ≈ c bo'lsa ham a ≉ c bo'lishi mumkin — hash bunday munosabatni ifodalay olmaydi.

Nega None qilish to'g'ri:

Uchta variant bor edi:

1. Hech narsa qilmaslik (sukut hash qolsin) —

python
class A:
    def __eq__(self, b): return self.x == b.x
    # __hash__ meros — id() asosida

a, b = A(1), A(1)
a == b                      # True
{a, b}                      # ⚠️ IKKI element — jimgina noto'g'ri
d = {a: 1}; d[b]            # ⚠️ KeyError — jimgina noto'g'ri

Bu — jim buzilish. Kod ishlaydi, testlar o'tadi, keyin ishlab chiqarishda g'alati xatolar chiqadi. Eng yomon variant.

2. Avtomatik hash yaratish — imkonsiz (yuqorida).

3. __hash__ = None —

python
{a, b}                      # ❌ TypeError: unhashable type: 'A'

Darhol, aniq xato. Dasturchi qaror qabul qilishi kerak:

  • Hashlanmasin (o'zgaruvchan konteyner) → hech narsa qilmang
  • Hashlansin → __hash__ yozing, va __eq__ bilan mos qilishga majbursiz

Bu — "aniq yashirinlikdan yaxshi" (Zen) ning namunasi.

Python 2 da qanday edi:

Python 2 da __hash__ avtomatik o'chirilmasdi:

python
# Python 2
class A(object):
    def __eq__(self, b): return self.x == b.x

a, b = A(1), A(1)
a == b                      # True
{a, b}                      # ⚠️ 2 element — jim xato

Bu — real xatolar manbai edi. Python 3 da ataylab o'zgartirildi (bu — orqaga moslikni buzuvchi o'zgarishlardan biri).

dataclass qanday hal qiladi:

dataclass maydonlarni biladi — chunki ular e'lon qilingan:

python
@dataclass(frozen=True)
class A:
    x: int
    y: int
# __eq__: (self.x, self.y) == (b.x, b.y)
# __hash__: hash((self.x, self.y))       ← bir xil maydonlardan

Shuning uchun dataclass avtomatik hash yarata oladi — u qo'lda yozilgan __eq__ ni tahlil qilmaydi, balki ikkalasini ham o'zi yaratadi.

Va frozen=True talab qilinadi, chunki 3-qoida (hash barqarorligi) faqat o'zgarmas obyektda kafolatlanadi.

Xulosa: Python "sehr qilishdan" bosh tortdi va o'rniga noto'g'ri holatni imkonsiz qildi. Bu — API dizaynining yaxshi namunasi: agar to'g'ri javobni bilib bo'lmasa, xato ber, taxmin qilma.

Nimani mustahkamlaydi: 2.2, 2.3, 2.5-bo'limlar.


Xulosa

Bu darsda hashni chuqur o'rgandik.

Eng muhim uch fikr:

  1. Hash — obyektning xotiradagi "manzili". hash(x) & (hajm-1) uyachani beradi, shuning uchun qidirish O(1). Obyekt o'zgarsa hash o'zgaradi va u "yo'qolib qoladi" — shuning uchun o'zgaruvchan obyektlar hashlanmaydi.

  2. __eq__ va __hash__ — bir shartnomaning ikki qismi. a == b → hash(a) == hash(b), ikkalasi bir xil maydonlardan, va hash obyekt umri davomida o'zgarmasin. __eq__ yozsangiz Python __hash__ ni avtomatik None qiladi — bu himoya, cheklov emas.

  3. To'g'ri tanlov: @dataclass(frozen=True) yoki hash(self.id). Qiymat obyektlari uchun birinchisi, ma'lumotlar bazasi obyektlari uchun ikkinchisi. unsafe_hash=True — nomi bejiz emas.

Keyingi darsda ichma-ich tuzilmalar ni ko'ramiz: ro'yxat ichida lug'at, lug'at ichida ro'yxat — JSON kabi murakkab ma'lumotlar bilan xavfsiz ishlash.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
6.13-dars: Hash nima va nega kalit hashlanadi — IlmHamroh