IlmHamroh
Python kursi/Ilgor til imkoniyatlari14/17-dars35 daqiqa
Mundarija (19)

10.14-dars: Operator ortiqcha yuklash amaliyoti

10-QISM — ILG'OR TIL IMKONIYATLARI · 14-dars


1. Kirish va motivatsiya

8.12 va 8.13-darslarda operatorlar qanday hal qilinishini o'rgandik: __add__ va __radd__, NotImplemented, merosxo'r ustuvorligi, __iadd__, sum() bilan ishlash. Endi ular bilan real tur loyihalash vaqti.

Jamoa ma'lumotlar bazasi uchun kichik so'rov quruvchi yozdi, pandas va SQLAlchemy uslubida:

python
kattalar = Foydalanuvchi.yosh >= 18 & Foydalanuvchi.faol == True

Natija:

TypeError: Taqqoslash'ni bool ga aylantirib bo'lmaydi — and/or/not o'rniga & | ~ ishlating va & | atrofiga qavs qo'ying

Kod "to'g'ri" ko'rinadi, lekin Python uni boshqacha o'qiydi. & ning ustuvorligi >= va == dan yuqori, shuning uchun bu:

python
Foydalanuvchi.yosh >= (18 & Foydalanuvchi.faol) == True

— ya'ni zanjirli taqqoslash a >= b == c, u esa (a >= b) and (b == c) ga ochiladi. and esa bool() chaqiradi.

Boshqa jamoa pul turini yozdi. Pul("0.10") + Pul("0.20") + Pul("0.30") bir tartibda 0.60, boshqa tartibda 0.6000000000000001 berdi — ichida float ishlatilgan edi. Uchinchi jamoaning Vektor turi += dan keyin boshqa o'zgaruvchidagi vektorni ham "sehrli" o'zgartirib yubordi.

Operatorni ortiqcha yuklash — sintaksis emas, va'da. + qo'shgan tur foydalanuvchiga "bu qo'shishga o'xshaydi" deb aytadi: o'rin almashtirish, guruhlash, neytral element, == va hash mosligi. Va'da buzilsa, xatolar operator yozilgan joyda emas, uzoqda — sum(), sorted(), lug'at kalitlari yoki parallel hisobda chiqadi.

Bu darsda:

  • Joyida amallar: taxalluslar (alias), NotImplemented dan qaytish, kortej tuzog'i
  • NotImplemented va TypeError farqi amalda
  • Pul turi to'liq: Decimal, valyuta, ruxsat etilgan kombinatsiyalar, taqsimlash, format
  • Algebraik qonunlar va ularni avtomatik tekshirish
  • Operatorlar DSL sifatida: ifoda daraxti, and/or o'rniga &/|, ustuvorlik va __bool__ tuzoqlari

2. Nazariya — chuqur tushuntirish

2.1. Joyida amallar amalda

a += b taxminan:

python
natija = a.__iadd__(b) if hasattr(type(a), "__iadd__") else NotImplemented
if natija is NotImplemented:
    natija = a + b                  # __add__ / __radd__
a = natija                          # ⭐ qayta bog'lash — HAR DOIM
Tur __iadd__ b = a; a += x dan keyin
O'zgarmas (int, str, tuple, o'zgarmas Pul) Yo'q → yangi obyekt a is b → False, b o'zgarmadi
O'zgaruvchan (list, set, Counter) Joyida o'zgartirib, self qaytaradi a is b → True, b ham o'zgardi

Loyihalash qoidasi:

  • Qiymat turlari (pul, vektor, sana, versiya) — o'zgarmas, __iadd__ yozmang
  • Konteyner/akkumulyator turlari (to'plam, hisob-kitob daftari) — __iadd__ joyida o'zgartirsin va self qaytarsin
  • __iadd__ dan NotImplemented qaytsa, Python __add__ ga o'tadi — natija boshqa turdagi yangi obyekt bo'lishi mumkin

Kortej tuzog'i:

python
t = ([1, 2],)
t[0] += [3]         # TypeError: 'tuple' object does not support item assignment
t                   # ([1, 2, 3],)  — lekin ro'yxat baribir o'zgardi!

Chunki t[0] += [3] — bu t[0] = t[0].__iadd__([3]). __iadd__ ro'yxatni joyida o'zgartiradi va muvaffaqiyatli qaytadi, keyin kortejga qayta yozish xato beradi.

2.2. NotImplemented yoki TypeError

Operator metodida Nima bo'ladi
return NotImplemented Python ikkinchi operandning teskari metodini sinaydi; u ham rad etsa — standart TypeError
raise TypeError(...) Hammasi to'xtaydi — ikkinchi operand imkoniyat olmaydi

"Men bu turni bilmayman" → NotImplemented. Boshqa tur (masalan, keyinroq yozilgan Soliq klassi) sizning turingiz bilan qanday ishlashni bilishi mumkin.

"Men bu turni bilaman, lekin bu amal ma'nosiz" → TypeError aniq xabar bilan: Pul("5 USD") + Pul("5 UZS") — ikkala tur ham Pul, boshqa hech kim yordam bera olmaydi, xabar esa foydali bo'lishi kerak.

2.3. Pul turini loyihalash

1. Ichki tasvir. float — yo'q: 0.1 + 0.2 != 0.3. Variantlar:

Tasvir Afzallik Kamchilik
Decimal O'nlik aniqlik, yumaloqlash rejimlari Sekinroq
int — eng kichik birlikda (tiyin, sent) Tez, aniq Valyutaga qarab birlik har xil (JPY da 0, BHD da 3 xona)

2. Qaysi amallar ma'noli:

Amal Natija Izoh
Pul + Pul (bir valyuta) Pul
Pul + Pul (boshqa valyuta) TypeError Kursni aniq ko'rsatish kerak
Pul + 5 Birliksiz son — xato manbai. Faqat 0 (sum uchun)
Pul * 3, 3 * Pul, Pul * Decimal Pul
Pul * float Aniqlik yo'qoladi
Pul * Pul "so'm²" — ma'nosiz
Pul / 3 Qoldiq yo'qoladi — ajrat(3) yaxshiroq
Pul / Pul Decimal Nisbat — birliksiz
-Pul, abs(Pul), bool(Pul)
Pul < Pul (bir valyuta) bool Boshqa valyutada — TypeError

3. Taqsimlash. 100 so'm / 3 → 33.33 × 3 = 99.99 — 1 tiyin yo'qoladi. To'g'ri usul — ajratish: qoldiqni birma-bir taqsimlash ([33.34, 33.33, 33.33]), yig'indi har doim asl summaga teng.

4. Tenglik va xesh. Pul("1.0") == Pul("1.00") bo'lsa, hash ham teng bo'lishi shart. Ichki qiymatni normallashtiring (quantize) — keyin __eq__ va __hash__ bir xil maydonlardan hisoblanadi.

5. Format. __format__(spec) — f"{narx:,}", f"{narx:kod}" kabi o'z format tilingizni berish mumkin.

2.4. Algebraik qonunlar

Operator foydalanuvchiga quyidagi xossalarni va'da qiladi:

Qonun Formula Qayerda kerak
O'rin almashtirish a + b == b + a sum() tartibi, sorted + sum
Guruhlash (assotsiativlik) (a + b) + c == a + (b + c) Parallel yig'ish (10.13-dars), reduce
Neytral element a + 0 == a sum(..., start), bo'sh ro'yxat
Teskari element a + (-a) == 0, a - b == a + (-b) Balans hisobi
Taqsimlash k * (a + b) == k*a + k*b Soliq, chegirma
Tenglik–xesh a == b ⇒ hash(a) == hash(b) Lug'at, to'plam
Tartib to'liqligi a < b, a == b, a > b — aniq bittasi sorted, bisect

Qonunlarni avtomatik tekshirish — "xossaga asoslangan test": tasodifiy qiymatlar yaratiladi va formulalar minglab marta tekshiriladi. Uchinchi tomon hypothesis kutubxonasi buni professional darajada qiladi; oddiy variantini random bilan yozish mumkin (Misol 3).

Hamma turlar hamma qonunni bajarmaydi — va bu normal, lekin hujjatlashtirilishi kerak:

  • float: guruhlash va taqsimlash yaqinlashuv bilan
  • str, list +: o'rin almashtirish yo'q ("a" + "b" != "b" + "a")
  • Matritsa @: o'rin almashtirish yo'q

2.5. Operatorlar DSL sifatida

Operator metodlari istalgan obyekt qaytarishi mumkin. Shu tufayli kutubxonalar kichik "tillar" quradi:

Kutubxona Ifoda Qaytaradi
pathlib Path("a") / "b" Yangi yo'l
set a | b, a & b To'plam
pandas, NumPy df["yosh"] >= 18 Mantiqiy massiv, bool emas
SQLAlchemy User.yosh >= 18 SQL ifoda obyekti
enum.Flag Ruxsat.OQISH | Ruxsat.YOZISH Bayroqlar birikmasi

Cheklovlar va tuzoqlar:

1. and, or, not, is, in (natija turi) ni yuklab bo'lmaydi. and/or operandni bool() ga aylantiradi va qisqa tutashuv qiladi. Shuning uchun DSL lar &, |, ~ ishlatadi.

2. Ustuvorlik o'zgarmaydi. &, |, ^ taqqoslashlardan yuqori:

python
yosh >= 18 & faol == True       # yosh >= (18 & faol) == True  ❌
(yosh >= 18) & (faol == True)   # ✅

3. Zanjirli taqqoslash bool() chaqiradi. a < b < c → (a < b) and (b < c).

4. __eq__ ifoda qaytarsa:

  • x in royxat, royxat.index(x), royxat.remove(x) — ichida == va bool() → xato yoki noto'g'ri natija
  • assert ifoda1 == ifoda2 testlarda — ma'nosiz
  • __hash__ avtomatik None bo'ladi (8.12-dars) — lug'at kaliti bo'lolmaydi; kerak bo'lsa aniq __hash__ = object.__hash__

5. __bool__ ni taqiqlang. Ifoda obyekti bool() da TypeError bersin — NumPy va pandas shunday qiladi ("The truth value of an array is ambiguous"). Aks holda if ifoda: jim True bo'lib, xato yashirinadi.


3. Tez ma'lumotnoma

Joyida amallar

a += b  →  a = a.__iadd__(b)  (NotImplemented bo'lsa → a = a + b)
o'zgarmas tur: __iadd__ yozmang       o'zgaruvchan: joyida + return self
t[0] += [x]  →  ro'yxat o'zgaradi VA TypeError

Rad etish

python
return NotImplemented          # turni bilmayman — boshqasi urinib ko'rsin
raise TypeError("...")         # bilaman, lekin ma'nosiz — aniq xabar

Pul turi

Decimal yoki int (tiyin) — float emas
Pul + Pul (bir valyuta) ✅     Pul + son ❌ (0 bundan mustasno)
Pul * int/Decimal ✅           Pul * float ❌   Pul * Pul ❌
Pul / Pul → Decimal            Pul / n → ajrat(n)
__eq__ va __hash__ — normallashtirilgan qiymatdan

Qonunlar

a+b == b+a    (a+b)+c == a+(b+c)    a+0 == a    a-b == a+(-b)
k*(a+b) == k*a+k*b    a == b ⇒ hash(a) == hash(b)

DSL

and/or/not — yuklanmaydi → & | ~
& | ^ ustuvorligi taqqoslashdan yuqori → qavs
a < b < c → bool() chaqiriladi
__bool__ → TypeError       __hash__ = object.__hash__ (kerak bo'lsa)

4. Batafsil misollar

Misol 1 — Joyida amallar, taxalluslar va rad etish

python
"""+= o'zgarmas va o'zgaruvchan turlarda; __iadd__ dan NotImplemented; kortej tuzog'i; NotImplemented vs TypeError."""


class Vektor:
    """O'zgarmas qiymat turi."""
    __slots__ = ("x", "y")

    def __init__(self, x, y):
        object.__setattr__(self, "x", x)
        object.__setattr__(self, "y", y)

    def __setattr__(self, nom, qiymat):
        raise AttributeError("Vektor o'zgarmas")

    def __add__(self, boshqa):
        if not isinstance(boshqa, Vektor):
            return NotImplemented
        return Vektor(self.x + boshqa.x, self.y + boshqa.y)

    def __repr__(self):
        return f"Vektor({self.x}, {self.y})"


class Savatcha:
    """O'zgaruvchan akkumulyator."""

    def __init__(self, mahsulotlar=()):
        self.mahsulotlar = list(mahsulotlar)

    def __add__(self, boshqa):
        if not isinstance(boshqa, Savatcha):
            return NotImplemented
        return Savatcha(self.mahsulotlar + boshqa.mahsulotlar)

    def __iadd__(self, boshqa):
        if isinstance(boshqa, Savatcha):
            self.mahsulotlar.extend(boshqa.mahsulotlar)
            return self
        if isinstance(boshqa, str):
            self.mahsulotlar.append(boshqa)
            return self
        return NotImplemented

    def __repr__(self):
        return f"Savatcha({self.mahsulotlar})"


print("=== 1. Taxalluslar: o'zgarmas va o'zgaruvchan ===")
a = Vektor(1, 2)
b = a
a += Vektor(10, 10)
print(f"  Vektor:   a is b: {a is b}, a={a}, b={b}")

s = Savatcha(["non"])
nusxa = s
s += "sut"
s += Savatcha(["tuxum"])
print(f"  Savatcha: s is nusxa: {s is nusxa}, nusxa={nusxa}   ⚠️ taxallus ham o'zgardi")
yangi = s + Savatcha(["choy"])
print(f"  s + ... o'sha obyektmi: {yangi is s}, s o'zgarmadi: {s}")

print("\n=== 2. __iadd__ NotImplemented qaytarsa — __add__ ga o'tiladi ===")


class Jurnal(list):
    def __iadd__(self, boshqa):
        if not isinstance(boshqa, str):
            print(f"    Jurnal.__iadd__({boshqa!r}) → NotImplemented")
            return NotImplemented
        self.append(boshqa)
        return self


j = Jurnal(["boshlandi"])
eski = j
j += "xato"
print(f"  j += 'xato' → {j}, turi {type(j).__name__}, o'sha obyekt: {j is eski}")
j += ["a", "b"]
print(f"  j += [...] → {j}, turi {type(j).__name__}, o'sha obyekt: {j is eski}   ⚠️ list.__add__ yangi list berdi")

print("\n=== 3. ⚠️ Kortej ichidagi ro'yxat ===")
t = (["a"], "b")
try:
    t[0] += ["c"]
except TypeError as e:
    print(f"  TypeError: {e}")
print(f"  t: {t}   ⚠️ xato bo'ldi, lekin ro'yxat o'zgardi")
t2 = (["a"], "b")
t2[0].extend(["c"])
print(f"  ✅ extend bilan — xatosiz: {t2}")

print("\n=== 4. NotImplemented yoki TypeError ===")


class Metr:
    def __init__(self, qiymat):
        self.qiymat = qiymat

    def __add__(self, boshqa):
        if isinstance(boshqa, Metr):
            return Metr(self.qiymat + boshqa.qiymat)
        return NotImplemented

    def __repr__(self):
        return f"{self.qiymat} m"


class QattiqMetr(Metr):
    def __add__(self, boshqa):
        if isinstance(boshqa, Metr):
            return QattiqMetr(self.qiymat + boshqa.qiymat)
        raise TypeError(f"Metr ga {type(boshqa).__name__} qo'shib bo'lmaydi")


class Santimetr:
    def __init__(self, qiymat):
        self.qiymat = qiymat

    def __radd__(self, boshqa):
        if isinstance(boshqa, Metr):
            return type(boshqa)(boshqa.qiymat + self.qiymat / 100)
        return NotImplemented


for chap in (Metr(2), QattiqMetr(2)):
    try:
        print(f"  {type(chap).__name__}(2) + Santimetr(50) = {chap + Santimetr(50)}")
    except TypeError as e:
        print(f"  {type(chap).__name__}(2) + Santimetr(50) → TypeError: {e}   ⚠️ Santimetr.__radd__ imkoniyat olmadi")
try:
    Metr(2) + "uch"
except TypeError as e:
    print(f"  Metr(2) + 'uch' → standart TypeError: {e}")

Natijaning muhim qismi:

text
=== 1. Taxalluslar: o'zgarmas va o'zgaruvchan ===
  Vektor:   a is b: False, a=Vektor(11, 12), b=Vektor(1, 2)
  Savatcha: s is nusxa: True, nusxa=Savatcha(['non', 'sut', 'tuxum'])   ⚠️ taxallus ham o'zgardi
  s + ... o'sha obyektmi: False, s o'zgarmadi: Savatcha(['non', 'sut', 'tuxum'])

=== 2. __iadd__ NotImplemented qaytarsa — __add__ ga o'tiladi ===
  j += 'xato' → ['boshlandi', 'xato'], turi Jurnal, o'sha obyekt: True
    Jurnal.__iadd__(['a', 'b']) → NotImplemented
  j += [...] → ['boshlandi', 'xato', 'a', 'b'], turi list, o'sha obyekt: False   ⚠️ list.__add__ yangi list berdi

=== 3. ⚠️ Kortej ichidagi ro'yxat ===
  TypeError: 'tuple' object does not support item assignment
  t: (['a', 'c'], 'b')   ⚠️ xato bo'ldi, lekin ro'yxat o'zgardi
  ✅ extend bilan — xatosiz: (['a', 'c'], 'b')

=== 4. NotImplemented yoki TypeError ===
  Metr(2) + Santimetr(50) = 2.5 m
  QattiqMetr(2) + Santimetr(50) → TypeError: Metr ga Santimetr qo'shib bo'lmaydi   ⚠️ Santimetr.__radd__ imkoniyat olmadi
  Metr(2) + 'uch' → standart TypeError: unsupported operand type(s) for +: 'Metr' and 'str'

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

Misol 2 — Pul turi to'liq

python
"""Decimal asosidagi Pul: valyuta, ruxsat etilgan amallar jadvali, ajratish, tenglik/xesh, format, sum."""

from decimal import ROUND_HALF_EVEN, Decimal
from functools import total_ordering

XONALAR = {"UZS": 2, "USD": 2, "JPY": 0}


@total_ordering
class Pul:
    __slots__ = ("_summa", "valyuta")

    def __init__(self, summa, valyuta="UZS"):
        if isinstance(summa, float):
            raise TypeError("Pul float qabul qilmaydi — satr yoki Decimal bering")
        if valyuta not in XONALAR:
            raise ValueError(f"noma'lum valyuta: {valyuta}")
        birlik = Decimal(1).scaleb(-XONALAR[valyuta])
        object.__setattr__(self, "_summa", Decimal(summa).quantize(birlik, ROUND_HALF_EVEN))
        object.__setattr__(self, "valyuta", valyuta)

    def __setattr__(self, nom, qiymat):
        raise AttributeError("Pul o'zgarmas")

    @property
    def summa(self):
        return self._summa

    def _bir_xil(self, boshqa, amal):
        if boshqa.valyuta != self.valyuta:
            raise TypeError(f"{self.valyuta} va {boshqa.valyuta} ni {amal} uchun avval konvertatsiya qiling")

    def __add__(self, boshqa):
        if isinstance(boshqa, Pul):
            self._bir_xil(boshqa, "qo'shish")
            return Pul(self._summa + boshqa._summa, self.valyuta)
        if boshqa == 0 and type(boshqa) is int:              # sum() uchun
            return self
        return NotImplemented

    __radd__ = __add__

    def __sub__(self, boshqa):
        if isinstance(boshqa, Pul):
            self._bir_xil(boshqa, "ayirish")
            return Pul(self._summa - boshqa._summa, self.valyuta)
        return NotImplemented

    def __neg__(self):
        return Pul(-self._summa, self.valyuta)

    def __abs__(self):
        return Pul(abs(self._summa), self.valyuta)

    def __bool__(self):
        return bool(self._summa)

    def __mul__(self, k):
        if type(k) in (int, Decimal):
            return Pul(self._summa * k, self.valyuta)
        return NotImplemented

    __rmul__ = __mul__

    def __truediv__(self, boshqa):
        if isinstance(boshqa, Pul):
            self._bir_xil(boshqa, "nisbat")
            return self._summa / boshqa._summa
        if type(boshqa) is int:
            raise TypeError(f"Pul / int qoldiqni yo'qotadi — ajrat({boshqa}) ishlating")
        return NotImplemented

    def ajrat(self, *ulushlar):
        """Ulushlarga (masalan 1, 1, 1 yoki 70, 30) qoldiqsiz taqsimlaydi."""
        if len(ulushlar) == 1 and type(ulushlar[0]) is int:
            ulushlar = (1,) * ulushlar[0]
        birlik = Decimal(1).scaleb(-XONALAR[self.valyuta])
        jami_birlik = int(self._summa / birlik)
        jami_ulush = sum(ulushlar)
        qismlar = [jami_birlik * u // jami_ulush for u in ulushlar]
        for i in range(jami_birlik - sum(qismlar)):
            qismlar[i] += 1
        return [Pul(q * birlik, self.valyuta) for q in qismlar]

    def __eq__(self, boshqa):
        if not isinstance(boshqa, Pul):
            return NotImplemented
        return (self._summa, self.valyuta) == (boshqa._summa, boshqa.valyuta)

    def __lt__(self, boshqa):
        if not isinstance(boshqa, Pul):
            return NotImplemented
        self._bir_xil(boshqa, "taqqoslash")
        return self._summa < boshqa._summa

    def __hash__(self):
        return hash((self._summa, self.valyuta))

    def __format__(self, spec):
        if spec == "kod":
            return f"{self._summa} {self.valyuta}"
        son = format(self._summa, spec or ",")
        return f"{son.replace(',', ' ')} {self.valyuta}"

    def __repr__(self):
        return f"Pul('{self._summa}', '{self.valyuta}')"


print("=== 1. Aniqlik: float va Pul ===")
f = [0.10, 0.20, 0.30]
print(f"  float: (0.1+0.2)+0.3 = {(f[0] + f[1]) + f[2]!r}, 0.1+(0.2+0.3) = {f[0] + (f[1] + f[2])!r}")
p = [Pul("0.10"), Pul("0.20"), Pul("0.30")]
print(f"  Pul:   (a+b)+c = {(p[0] + p[1]) + p[2]!r}, a+(b+c) = {p[0] + (p[1] + p[2])!r}")
try:
    Pul(0.1)
except TypeError as e:
    print(f"  Pul(0.1) → TypeError: {e}")

print("\n=== 2. Ruxsat etilgan amallar jadvali ===")
m, d = Pul("1500.50"), Pul("20", "USD")
amallar = [
    ("Pul + Pul", lambda: m + Pul("499.50")),
    ("Pul + Pul(USD)", lambda: m + d),
    ("Pul + 100", lambda: m + 100),
    ("sum([Pul, Pul])", lambda: sum([m, m])),
    ("Pul * 3", lambda: m * 3),
    ("3 * Pul", lambda: 3 * m),
    ("Pul * Decimal('0.12')", lambda: m * Decimal("0.12")),
    ("Pul * 1.5", lambda: m * 1.5),
    ("Pul * Pul", lambda: m * m),
    ("Pul / Pul", lambda: m / Pul("500")),
    ("Pul / 3", lambda: m / 3),
    ("-Pul, abs", lambda: (-m, abs(-m))),
    ("Pul < Pul(USD)", lambda: m < d),
]
for tavsif, amal in amallar:
    try:
        print(f"  {tavsif:24} ✅ {amal()!r}")
    except TypeError as e:
        print(f"  {tavsif:24} ❌ {e}")

print("\n=== 3. Ajratish: qoldiq yo'qolmaydi ===")
for summa, ulushlar in [(Pul("100"), (3,)), (Pul("0.05"), (1, 1)), (Pul("1000", "JPY"), (70, 20, 10)),
                        (Pul("250000.01"), (1, 1, 1, 1))]:
    qismlar = summa.ajrat(*ulushlar)
    print(f"  {summa:kod} ÷ {ulushlar} → {[format(q, 'kod') for q in qismlar]}, yig'indi to'g'ri: {sum(qismlar) == summa}")

print("\n=== 4. Tenglik, xesh va format ===")
a, b = Pul("1.0"), Pul("1.00")
print(f"  Pul('1.0') == Pul('1.00'): {a == b}, hash teng: {hash(a) == hash(b)}, to'plamda: {len({a, b})}")
print(f"  Pul('5', 'UZS') == Pul('5', 'USD'): {Pul('5') == Pul('5', 'USD')}")
narx = Pul("1234567.891")
print(f"  f'{{narx}}' → {narx}   f'{{narx:kod}}' → {narx:kod}   f'{{narx:,.0f}}' → {narx:,.0f}")
print(f"  saralash: {[format(x, 'kod') for x in sorted([Pul('30'), Pul('5.5'), Pul('100')])]}, bool(Pul(0)): {bool(Pul(0))}")

Natijaning muhim qismi:

text
=== 1. Aniqlik: float va Pul ===
  float: (0.1+0.2)+0.3 = 0.6000000000000001, 0.1+(0.2+0.3) = 0.6
  Pul:   (a+b)+c = Pul('0.60', 'UZS'), a+(b+c) = Pul('0.60', 'UZS')
  Pul(0.1) → TypeError: Pul float qabul qilmaydi — satr yoki Decimal bering

=== 2. Ruxsat etilgan amallar jadvali ===
  Pul + Pul                ✅ Pul('2000.00', 'UZS')
  Pul + Pul(USD)           ❌ UZS va USD ni qo'shish uchun avval konvertatsiya qiling
  Pul + 100                ❌ unsupported operand type(s) for +: 'Pul' and 'int'
  sum([Pul, Pul])          ✅ Pul('3001.00', 'UZS')
  Pul * 3                  ✅ Pul('4501.50', 'UZS')
  3 * Pul                  ✅ Pul('4501.50', 'UZS')
  Pul * Decimal('0.12')    ✅ Pul('180.06', 'UZS')
  Pul * 1.5                ❌ unsupported operand type(s) for *: 'Pul' and 'float'
  Pul * Pul                ❌ unsupported operand type(s) for *: 'Pul' and 'Pul'
  Pul / Pul                ✅ Decimal('3.001')
  Pul / 3                  ❌ Pul / int qoldiqni yo'qotadi — ajrat(3) ishlating
  -Pul, abs                ✅ (Pul('-1500.50', 'UZS'), Pul('1500.50', 'UZS'))
  Pul < Pul(USD)           ❌ UZS va USD ni taqqoslash uchun avval konvertatsiya qiling

=== 3. Ajratish: qoldiq yo'qolmaydi ===
  100.00 UZS ÷ (3,) → ['33.34 UZS', '33.33 UZS', '33.33 UZS'], yig'indi to'g'ri: True
  0.05 UZS ÷ (1, 1) → ['0.03 UZS', '0.02 UZS'], yig'indi to'g'ri: True
  1000 JPY ÷ (70, 20, 10) → ['700 JPY', '200 JPY', '100 JPY'], yig'indi to'g'ri: True
  250000.01 UZS ÷ (1, 1, 1, 1) → ['62500.01 UZS', '62500.00 UZS', '62500.00 UZS', '62500.00 UZS'], yig'indi to'g'ri: True

=== 4. Tenglik, xesh va format ===
  Pul('1.0') == Pul('1.00'): True, hash teng: True, to'plamda: 1
  Pul('5', 'UZS') == Pul('5', 'USD'): False
  f'{narx}' → 1 234 567.89 UZS   f'{narx:kod}' → 1234567.89 UZS   f'{narx:,.0f}' → 1 234 568 UZS
  saralash: ['5.50 UZS', '30.00 UZS', '100.00 UZS'], bool(Pul(0)): False

Nima ko'rsatdi: 2.2, 2.3-bo'limlar.

Misol 3 — Qonunlarni avtomatik tekshirish

python
"""Tasodifiy qiymatlar bilan algebraik qonunlar: float, Fraction, Decimal, str va o'zimizning turlar."""

import math
import random
from decimal import Decimal, getcontext
from fractions import Fraction


def qonunlar(qosh, neytral, teskari=None, kopaytir=None):
    q = {
        "o'rin almashtirish": lambda a, b, c: qosh(a, b) == qosh(b, a),
        "guruhlash": lambda a, b, c: qosh(qosh(a, b), c) == qosh(a, qosh(b, c)),
        "neytral element": lambda a, b, c: qosh(a, neytral) == a,
    }
    if teskari:
        q["teskari element"] = lambda a, b, c: qosh(a, teskari(a)) == neytral
    if kopaytir:
        q["taqsimlash"] = lambda a, b, c: kopaytir(3, qosh(a, b)) == qosh(kopaytir(3, a), kopaytir(3, b))
    q["tenglik ⇒ xesh"] = lambda a, b, c: (a != b) or hash(a) == hash(b)
    return q


def tekshir(nom, yaratuvchi, qonun_royxati, sinovlar=2000, urug=42):
    rng = random.Random(urug)
    namunalar = [(yaratuvchi(rng), yaratuvchi(rng), yaratuvchi(rng)) for _ in range(sinovlar)]
    natijalar = []
    for qonun, shart in qonun_royxati.items():
        buzilgan = [n for n in namunalar if not shart(*n)]
        belgi = "✅" if not buzilgan else f"❌ {len(buzilgan)}/{sinovlar}"
        natijalar.append(f"{qonun}: {belgi}")
    print(f"  {nom}")
    for n in natijalar:
        print(f"    {n}")
    return namunalar


print("=== 1. Sonli turlar ===")
getcontext().prec = 28
namuna = tekshir("float", lambda r: round(r.uniform(-100, 100), 2),
                 qonunlar(lambda a, b: a + b, 0.0, lambda a: -a, lambda k, a: k * a))
tekshir("Fraction", lambda r: Fraction(r.randint(-10_000, 10_000), 100),
        qonunlar(lambda a, b: a + b, Fraction(0), lambda a: -a, lambda k, a: k * a))
tekshir("Decimal (2 xona)", lambda r: Decimal(r.randint(-10_000, 10_000)).scaleb(-2),
        qonunlar(lambda a, b: a + b, Decimal(0), lambda a: -a, lambda k, a: k * a))

a, b, c = next((a, b, c) for a, b, c in namuna if (a + b) + c != a + (b + c))
print(f"  float guruhlash buzilishi misoli: ({a} + {b}) + {c} = {(a + b) + c!r}, {a} + ({b} + {c}) = {a + (b + c)!r}")
print(f"  ✅ float uchun to'g'ri taqqoslash: math.isclose → {math.isclose((a + b) + c, a + (b + c))}")

print("\n=== 2. str: + qo'shish emas, ulash ===")
tekshir("str +", lambda r: r.choice("abc") * r.randint(0, 2), qonunlar(lambda a, b: a + b, ""))

print("\n=== 3. O'zimizning turlar: xatolarni qonunlar topadi ===")


class YomonNuqta:
    """eq koordinatalardan, hash esa id dan — va qo'shish yumaloqlaydi."""

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

    def __add__(self, boshqa):
        return YomonNuqta(round((self.x + boshqa.x) * 10) / 10)

    def __neg__(self):
        return YomonNuqta(-self.x)

    def __eq__(self, boshqa):
        return isinstance(boshqa, YomonNuqta) and self.x == boshqa.x

    __hash__ = object.__hash__


class Nuqta:
    __slots__ = ("x",)

    def __init__(self, x):
        self.x = Fraction(x)

    def __add__(self, boshqa):
        return Nuqta(self.x + boshqa.x)

    def __neg__(self):
        return Nuqta(-self.x)

    def __rmul__(self, k):
        return Nuqta(k * self.x)

    def __eq__(self, boshqa):
        return isinstance(boshqa, Nuqta) and self.x == boshqa.x

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


tekshir("YomonNuqta", lambda r: YomonNuqta(r.randint(-100, 100) / 7),
        qonunlar(lambda a, b: a + b, YomonNuqta(0), lambda a: -a))
tekshir("Nuqta (Fraction)", lambda r: Nuqta(Fraction(r.randint(-100, 100), 7)),
        qonunlar(lambda a, b: a + b, Nuqta(0), lambda a: -a, lambda k, a: k * a))

Natijaning muhim qismi:

text
=== 1. Sonli turlar ===
  float
    o'rin almashtirish: ✅
    guruhlash: ❌ 645/2000
    neytral element: ✅
    teskari element: ✅
    taqsimlash: ❌ 853/2000
    tenglik ⇒ xesh: ✅
  Fraction
    o'rin almashtirish: ✅
    guruhlash: ✅
    neytral element: ✅
    teskari element: ✅
    taqsimlash: ✅
    tenglik ⇒ xesh: ✅
  Decimal (2 xona)
    o'rin almashtirish: ✅
    guruhlash: ✅
    neytral element: ✅
    teskari element: ✅
    taqsimlash: ✅
    tenglik ⇒ xesh: ✅
  float guruhlash buzilishi misoli: (27.89 + -95.0) + -44.99 = -112.1, 27.89 + (-95.0 + -44.99) = -112.10000000000001
  ✅ float uchun to'g'ri taqqoslash: math.isclose → True

=== 2. str: + qo'shish emas, ulash ===
  str +
    o'rin almashtirish: ❌ 591/2000
    guruhlash: ✅
    neytral element: ✅
    tenglik ⇒ xesh: ✅

=== 3. O'zimizning turlar: xatolarni qonunlar topadi ===
  YomonNuqta
    o'rin almashtirish: ✅
    guruhlash: ❌ 631/2000
    neytral element: ❌ 1697/2000
    teskari element: ✅
    tenglik ⇒ xesh: ❌ 12/2000
  Nuqta (Fraction)
    o'rin almashtirish: ✅
    guruhlash: ✅
    neytral element: ✅
    teskari element: ✅
    taqsimlash: ✅
    tenglik ⇒ xesh: ✅

Nima ko'rsatdi: 2.4-bo'lim — float guruhlash va taqsimlashni yaqinlashuv bilan bajaradi, str o'rin almashtirmaydi, YomonNuqta dagi yumaloqlash va id asosidagi xesh esa bir necha qator test yozmasdan aniqlandi.

Misol 4 — Amaliy: so'rov quruvchi DSL

Kichik so'rov quruvchi: Ustun ustidagi operatorlar Ifoda daraxtini quradi, u SQL ga (parametrlar bilan) va Python filtrga kompilyatsiya qilinadi. Ustuvorlik, and/or, in va __bool__ tuzoqlari bilan.

python
"""Operatorlar ifoda daraxtini quradi; & | ~; __bool__ taqiqlangan; ustuvorlik va in tuzoqlari; SQL va Python ga kompilyatsiya."""

import operator


class Ifoda:
    __hash__ = object.__hash__                     # __eq__ ni yuklaganimiz uchun aniq

    def __and__(self, boshqa):
        return Mantiq("AND", self, _ifoda(boshqa))

    def __or__(self, boshqa):
        return Mantiq("OR", self, _ifoda(boshqa))

    def __rand__(self, boshqa):
        return Mantiq("AND", _ifoda(boshqa), self)

    def __ror__(self, boshqa):
        return Mantiq("OR", _ifoda(boshqa), self)

    def __invert__(self):
        return Inkor(self)

    def __bool__(self):
        raise TypeError(
            f"{type(self).__name__}'ni bool ga aylantirib bo'lmaydi — "
            "and/or/not o'rniga & | ~ ishlating va & | atrofiga qavs qo'ying"
        )

    def _taqqos(belgi, amal):
        def metod(self, boshqa):
            return Taqqoslash(belgi, amal, self, _ifoda(boshqa))
        return metod

    __eq__ = _taqqos("=", operator.eq)
    __ne__ = _taqqos("<>", operator.ne)
    __lt__ = _taqqos("<", operator.lt)
    __le__ = _taqqos("<=", operator.le)
    __gt__ = _taqqos(">", operator.gt)
    __ge__ = _taqqos(">=", operator.ge)
    del _taqqos

    def ichida(self, qiymatlar):
        return Ichida(self, tuple(qiymatlar))


class Ustun(Ifoda):
    def __init__(self, nom):
        self.nom = nom

    def sql(self, p):
        return self.nom

    def py(self, qator):
        return qator[self.nom]

    def __repr__(self):
        return self.nom


class Qiymat(Ifoda):
    def __init__(self, q):
        self.q = q

    def sql(self, p):
        p.append(self.q)
        return "?"

    def py(self, qator):
        return self.q

    def __repr__(self):
        return repr(self.q)


def _ifoda(x):
    return x if isinstance(x, Ifoda) else Qiymat(x)


class Taqqoslash(Ifoda):
    def __init__(self, belgi, amal, chap, ong):
        self.belgi, self.amal, self.chap, self.ong = belgi, amal, chap, ong

    def sql(self, p):
        return f"{self.chap.sql(p)} {self.belgi} {self.ong.sql(p)}"

    def py(self, qator):
        return self.amal(self.chap.py(qator), self.ong.py(qator))

    def __repr__(self):
        return f"({self.chap!r} {self.belgi} {self.ong!r})"


class Mantiq(Ifoda):
    def __init__(self, belgi, chap, ong):
        self.belgi, self.chap, self.ong = belgi, chap, ong

    def sql(self, p):
        return f"({self.chap.sql(p)} {self.belgi} {self.ong.sql(p)})"

    def py(self, qator):
        c = self.chap.py(qator)
        return (c and self.ong.py(qator)) if self.belgi == "AND" else (c or self.ong.py(qator))

    def __repr__(self):
        return f"({self.chap!r} {self.belgi} {self.ong!r})"


class Inkor(Ifoda):
    def __init__(self, ichki):
        self.ichki = ichki

    def sql(self, p):
        return f"NOT {self.ichki.sql(p)}"

    def py(self, qator):
        return not self.ichki.py(qator)


class Ichida(Ifoda):
    def __init__(self, ustun, qiymatlar):
        self.ustun, self.qiymatlar = ustun, qiymatlar

    def sql(self, p):
        p.extend(self.qiymatlar)
        return f"{self.ustun.sql(p)} IN ({', '.join('?' * len(self.qiymatlar))})"

    def py(self, qator):
        return self.ustun.py(qator) in self.qiymatlar


def kompilyatsiya(ifoda):
    parametrlar = []
    return f"SELECT * FROM foydalanuvchilar WHERE {ifoda.sql(parametrlar)}", parametrlar


yosh, faol, shahar, ism = Ustun("yosh"), Ustun("faol"), Ustun("shahar"), Ustun("ism")
QATORLAR = [
    {"ism": "Aziz", "yosh": 34, "faol": True, "shahar": "Toshkent"},
    {"ism": "Dilnoza", "yosh": 17, "faol": True, "shahar": "Samarqand"},
    {"ism": "Bobur", "yosh": 45, "faol": False, "shahar": "Buxoro"},
    {"ism": "Malika", "yosh": 22, "faol": True, "shahar": "Buxoro"},
]

print("=== 1. To'g'ri yozilgan so'rovlar ===")
sorovlar = [
    (yosh >= 18) & (faol == True),
    ((shahar == "Buxoro") | (shahar == "Samarqand")) & ~(yosh < 18),
    shahar.ichida(["Toshkent", "Buxoro"]) & (18 <= yosh),
]
for s in sorovlar:
    sql, parametrlar = kompilyatsiya(s)
    mos = [q["ism"] for q in QATORLAR if s.py(q)]
    print(f"  {sql}")
    print(f"    parametrlar={parametrlar} → Python filtr: {mos}")
print(f"  18 <= yosh → Python aks ettirdi: {18 <= yosh!r}")

print("\n=== 2. ⚠️ Ustuvorlik: qavssiz & ===")
try:
    yosh >= 18 & faol == True
except TypeError as e:
    print(f"  yosh >= 18 & faol == True → TypeError: {e}")
print(f"  sababi: 18 & faol birinchi hisoblanadi → {18 & faol!r}, keyin zanjirli taqqoslash bool() chaqiradi")

print("\n=== 3. ⚠️ and / or / not ===")
for tavsif, amal in [("(yosh >= 18) and (faol == True)", lambda: (yosh >= 18) and (faol == True)),
                     ("not (yosh < 18)", lambda: not (yosh < 18)),
                     ("if yosh > 18: ...", lambda: 1 if yosh > 18 else 0),
                     ("18 < yosh < 65", lambda: 18 < yosh < 65)]:
    try:
        amal()
        print(f"  {tavsif:32} jim o'tdi ⚠️")
    except TypeError as e:
        print(f"  {tavsif:32} TypeError ✅ (xato yashirinmadi)")

print("\n=== 4. ⚠️ __eq__ ifoda qaytarsa: in, index, lug'at ===")
ustunlar = [yosh, faol]
try:
    print(f"  shahar in ustunlar: {shahar in ustunlar}")
except TypeError as e:
    print(f"  shahar in ustunlar → TypeError (ichida == va bool)")
print(f"  yosh in ustunlar: {yosh in ustunlar}   (avval `is` tekshiriladi — == gacha yetmadi)")
print(f"  lug'at kaliti sifatida: { {yosh: 'butun son', faol: 'mantiqiy'}[yosh] }   (__hash__ = object.__hash__)")
print(f"  ✅ identifikator bo'yicha qidirish: {any(u is shahar for u in ustunlar)}")

Natijaning muhim qismi:

text
=== 1. To'g'ri yozilgan so'rovlar ===
  SELECT * FROM foydalanuvchilar WHERE (yosh >= ? AND faol = ?)
    parametrlar=[18, True] → Python filtr: ['Aziz', 'Malika']
  SELECT * FROM foydalanuvchilar WHERE ((shahar = ? OR shahar = ?) AND NOT yosh < ?)
    parametrlar=['Buxoro', 'Samarqand', 18] → Python filtr: ['Bobur', 'Malika']
  SELECT * FROM foydalanuvchilar WHERE (shahar IN (?, ?) AND yosh >= ?)
    parametrlar=['Toshkent', 'Buxoro', 18] → Python filtr: ['Aziz', 'Bobur', 'Malika']
  18 <= yosh → Python aks ettirdi: (yosh >= 18)

=== 2. ⚠️ Ustuvorlik: qavssiz & ===
  yosh >= 18 & faol == True → TypeError: Taqqoslash'ni bool ga aylantirib bo'lmaydi — and/or/not o'rniga & | ~ ishlating va & | atrofiga qavs qo'ying
  sababi: 18 & faol birinchi hisoblanadi → (18 AND faol), keyin zanjirli taqqoslash bool() chaqiradi

=== 3. ⚠️ and / or / not ===
  (yosh >= 18) and (faol == True)  TypeError ✅ (xato yashirinmadi)
  not (yosh < 18)                  TypeError ✅ (xato yashirinmadi)
  if yosh > 18: ...                TypeError ✅ (xato yashirinmadi)
  18 < yosh < 65                   TypeError ✅ (xato yashirinmadi)

=== 4. ⚠️ __eq__ ifoda qaytarsa: in, index, lug'at ===
  shahar in ustunlar → TypeError (ichida == va bool)
  yosh in ustunlar: True   (avval `is` tekshiriladi — == gacha yetmadi)
  lug'at kaliti sifatida: butun son   (__hash__ = object.__hash__)
  ✅ identifikator bo'yicha qidirish: False

Nima ko'rsatdi: 2.5-bo'lim — & va | taqqoslashdan yuqori ustuvorlikka ega, and/or/not/if va zanjirli taqqoslash bool() ni chaqiradi, shuning uchun __bool__ dagi TypeError jim xatolarni oldini oladi; in esa avval is bilan solishtirgani uchun ba'zan ishlaydi, ba'zan yo'q.


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

Noto'g'ri fikr To'g'risi
"a += b har doim a ni joyida o'zgartiradi" Faqat o'zgaruvchan turda; o'zgarmasda yangi obyekt va qayta bog'lash
"t[0] += [x] xato bersa, hech narsa o'zgarmaydi" Ro'yxat o'zgaradi, keyin kortejga yozish xato beradi
"NotImplemented va raise TypeError bir xil" NotImplemented ikkinchi operandga imkon beradi
"Pul uchun float yetarli, yumaloqlab chiqaramiz" Guruhlash buziladi, tiyinlar yo'qoladi
"Pul / 3 — to'g'ri taqsimlash" Qoldiq yo'qoladi — ajratish kerak
"Operator istalgan narsa qaytara oladi — muammo yo'q" bool, in, hash, zanjirli taqqoslash kutilmagan joyda ishlaydi
"a >= 18 & b == 1 — ikki shart" a >= (18 & b) == 1 — zanjirli taqqoslash
"__bool__ yozmasam, if ifoda: xato beradi" Jim True — xato yashirinadi

6. Keng tarqalgan xatolar va yechimlari

1. O'zgarmas qiymat turiga __iadd__

python
class Vektor:
    def __iadd__(self, b):
        self.x += b.x                  # ❌ taxalluslar o'zgaradi, lug'at kaliti xeshi buziladi
        return self

__iadd__ yozmang — a += b avtomatik a = a + b bo'ladi.

2. Noma'lum turga TypeError

python
def __add__(self, b):
    if not isinstance(b, Metr):
        raise TypeError("...")         # ❌ Santimetr.__radd__ ishlamaydi

def __add__(self, b):
    if not isinstance(b, Metr):
        return NotImplemented          # ✅

3. Pulda float

python
class Pul:
    def __init__(self, summa: float): ...   # ❌

class Pul:
    def __init__(self, summa: str | Decimal | int): ...   # ✅ float — TypeError

4. Pulni bo'lish

python
ulush = jami / 3                      # ❌ 33.33 × 3 = 99.99

ulushlar = jami.ajrat(3)              # ✅ [33.34, 33.33, 33.33]

5. __eq__ normallashtirilgan, __hash__ esa yo'q

python
def __eq__(self, b): return self.summa == b.summa        # Decimal("1.0") == Decimal("1.00")
def __hash__(self): return hash(str(self.summa))         # ❌ "1.0" va "1.00" — turli xesh

def __hash__(self): return hash(self.summa)              # ✅ eq bilan bir xil maydon

6. DSL da qavssiz & / |

python
df[df.yosh >= 18 & df.faol]           # ❌ ustuvorlik

df[(df.yosh >= 18) & df.faol]         # ✅

7. DSL ifodasida and / or

python
filtr = (yosh > 18) and (faol == True)   # ❌ faqat o'ng tomon qoladi (yoki TypeError)

filtr = (yosh > 18) & (faol == True)     # ✅

8. Ifoda obyektida __bool__ ni ochiq qoldirish

python
if Foydalanuvchi.yosh > 18:           # ❌ har doim True — obyekt "rost"
    ...

def __bool__(self):
    raise TypeError("ifodani bool ga aylantirib bo'lmaydi")   # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 8.12–8.14-darslar (o'tilgan): operator hal qilish tartibi, taqqoslash, arifmetika, konteyner protokoli
  • 10.13-dars (o'tilgan): assotsiativlik va neytral element — reduce va parallel yig'ish
  • 10.15-dars: Python ma'lumotlar modeli — barcha dunder metodlar tizimi
  • 17-qism: xossaga asoslangan testlar (hypothesis)
  • 24-qism: NumPy va pandas — elementlab operatorlar, &/| va "ambiguous truth value"
  • 23-qism: SQLAlchemy ifodalari — ushbu DSL ning to'liq versiyasi

8. Eng yaxshi amaliyotlar

  1. Operator faqat tabiiy ma'noga ega bo'lsa. + — qo'shish yoki birlashtirish, / — yo'l yoki bo'lish. Ajablantiradigan operator — yomon API.

  2. Qiymat turlari o'zgarmas. __iadd__ faqat konteyner va akkumulyatorlar uchun.

  3. Bilmagan turga — NotImplemented, ma'nosiz amalga — aniq xabarli TypeError.

  4. Pul — Decimal yoki butun birlik, bo'lish o'rniga ajratish, valyutalar aralashmasin.

  5. __eq__ va __hash__ bir xil, normallashtirilgan maydonlardan.

  6. Qonunlarni avtomatik test qiling — o'rin almashtirish, guruhlash, neytral, xesh mosligi.

  7. DSL da __bool__ ni taqiqlang, __hash__ ni aniq belgilang, hujjatda qavslarni talab qiling.

  8. Qaysi qonunlar bajarilmasligini hujjatlashtiring — masalan, "+ o'rin almashtirmaydi" yoki "float bilan aralashtirilmaydi".


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
class V:
    def __init__(self, x):
        self.x = x
    def __add__(self, o):
        if isinstance(o, V):
            return V(self.x + o.x)
        if isinstance(o, int):
            return V(self.x + o)
        return NotImplemented
    __radd__ = __add__
    def __sub__(self, o):
        return V(self.x - o.x) if isinstance(o, V) else NotImplemented
    def __rsub__(self, o):
        return V(o - self.x) if isinstance(o, int) else NotImplemented
    def __repr__(self):
        return f"V({self.x})"

class R(list):
    def __iadd__(self, o):
        print("iadd", end=" ")
        return NotImplemented
1.  print(V(1) + 2, 2 + V(1))
2.  print(10 - V(3))
3.  print(sum([V(1), V(2)]))
4.  try:
        V(1) - 5
    except TypeError:
        print("TypeError")
5.  a = V(1)
    b = a
    a += V(1)
    print(a is b, b)
6.  a = [1]
    b = a
    a += [2]
    print(a is b, b)
7.  t = ([1],)
    try:
        t[0] += [2]
    except TypeError:
        print("TypeError", t)
8.  r = R([1])
    r += [2]
    print(type(r).__name__, r)
9.  print(0.1 + (0.2 + 0.3) == (0.1 + 0.2) + 0.3)
10. from fractions import Fraction as F
    print(F(1, 10) + (F(2, 10) + F(3, 10)) == (F(1, 10) + F(2, 10)) + F(3, 10))
11. print(1 == 1.0, hash(1) == hash(1.0))
12. print(True + True, 3 | 5, ~0)
Javoblar
  1. V(3) V(3) — ikkinchisi __radd__
  2. V(7) — int.__sub__ rad etdi, V.__rsub__(10) → 10 - 3
  3. V(3) — 0 + V(1) → __radd__ butun sonni qabul qiladi
  4. TypeError — __sub__ NotImplemented, int da __rsub__ V uchun yo'q
  5. False V(1) — __iadd__ yo'q, yangi obyekt
  6. True [1, 2] — ro'yxat joyida o'zgardi
  7. TypeError ([1, 2],) — ro'yxat o'zgardi, keyin kortejga yozish xato berdi
  8. iadd list [1, 2] — NotImplemented → list.__add__ yangi list qaytardi
  9. False — float guruhlashni aniq bajarmaydi
  10. True
  11. True True — teng sonlar teng xeshga ega
  12. 2 7 -1

Vazifa 2: Xatolarni tuzating

python
1.  class Harorat:
        def __init__(self, c):
            self.c = c
        def __add__(self, o):
            if not isinstance(o, Harorat):
                raise TypeError("faqat Harorat")
            return Harorat(self.c + o.c)
        def __iadd__(self, o):
            self.c += o.c
            return self
        def __eq__(self, o):
            return self.c == o.c

2.  class Pul:
        def __init__(self, summa):
            self.summa = round(float(summa), 2)
        def bol(self, n):
            return [Pul(self.summa / n)] * n

3.  filtr = df.narx > 1000 | df.chegirma == True

4.  class Belgi:
        def __init__(self, nom):
            self.nom = nom
        def __eq__(self, o):
            return Taqqoslash(self, o)
    teglar = {Belgi("a"): 1}

5.  def jami(tolovlar):
        natija = tolovlar[0]
        for t in tolovlar[1:]:
            natija += t
        return natija
    # tolovlar — o'zgaruvchan Savatcha obyektlari ro'yxati
Javoblar
python
1.  class Harorat:
        __slots__ = ("c",)
        def __init__(self, c):
            object.__setattr__(self, "c", c)
        def __add__(self, o):
            if not isinstance(o, Harorat):
                return NotImplemented     # boshqa tur imkoniyat olsin
            return Harorat(self.c + o.c)
        # __iadd__ olib tashlandi — qiymat turi o'zgarmas
        def __eq__(self, o):
            return self.c == o.c if isinstance(o, Harorat) else NotImplemented
        def __hash__(self):               # __eq__ bor — xesh ham kerak
            return hash(self.c)

2.  class Pul:
        def __init__(self, summa):
            self.summa = Decimal(str(summa)).quantize(Decimal("0.01"))
        def ajrat(self, n):
            tiyin = int(self.summa * 100)
            qismlar = [tiyin // n] * n
            for i in range(tiyin % n):
                qismlar[i] += 1
            return [Pul(Decimal(q) / 100) for q in qismlar]

3.  filtr = (df.narx > 1000) | (df.chegirma == True)

4.  class Belgi:
        def __init__(self, nom):
            self.nom = nom
        def __eq__(self, o):
            return Taqqoslash(self, o)
        __hash__ = object.__hash__        # __eq__ yuklangani uchun avtomatik None bo'lgan edi
        def __bool__(self):
            raise TypeError("Belgi ifodasini bool ga aylantirib bo'lmaydi")

5.  def jami(tolovlar):
        natija = Savatcha()               # yangi obyekt — birinchi elementni o'zgartirmaymiz
        for t in tolovlar:
            natija += t
        return natija

Vazifa 3: Vaqt oralig'i turi

Oraliq(boshlanish, tugash) — sanalar oralig'i turini yozing:

  1. a & b — kesishma (yo'q bo'lsa None emas, bo'sh oraliq obyekti)
  2. a | b — birlashma: kesishsa yoki yondosh bo'lsa bitta oraliq, aks holda OraliqlarToplami
  3. a - b — ayirma (0, 1 yoki 2 ta oraliq)
  4. sana in oraliq, len(oraliq) — kunlar soni, bool(oraliq)
  5. Qonunlarni Misol 3 uslubida tekshiring: & va | o'rin almashtirish va guruhlash, a & a == a

Vazifa 4: Pulga konvertatsiya

Misol 2 dagi Pul ni kengaytiring:

  1. Kurs(dan, ga, qiymat) turi: Pul("100", "USD") * Kurs("USD", "UZS", "12600") → Pul UZS da; noto'g'ri yo'nalish — TypeError
  2. Kurs @ Kurs — kurslar kompozitsiyasi (USD→UZS @ EUR→USD → EUR→UZS)
  3. ajrat ga salbiy summalarni qo'shing
  4. __round__(n) va __format__ da "q" (qisqa: 1.2 mln so'm) spec
  5. Qonun testlari: taqsimlash (k*(a+b) == k*a + k*b), ajratishdan keyin yig'indi saqlanishi

Vazifa 5: DSL ni kengaytiring

Misol 4 dagi so'rov quruvchiga qo'shing:

  1. ustun.oraliqda(a, b) → BETWEEN, ustun.like("A%")
  2. Arifmetik ifodalar: (narx * soni) > 100_000 — Arifmetik tugun, SQL va Python ga kompilyatsiya
  3. Ifoda.soddalashtir() — ~~x → x, x & True → x, bir xil shartlarni birlashtirish
  4. __repr__ va daraxtni chiroyli chop etish
  5. SQL inyeksiyasiga qarshi test: qiymatlarda '; DROP TABLE bo'lsa ham parametr sifatida qolishini tekshiring

Vazifa 6: Qonun tekshirgich kutubxonasi

Misol 3 ni kichik kutubxonaga aylantiring:

  1. @qonun dekoratori bilan qonunlarni ro'yxatga olish
  2. Buzilgan holatda eng kichik qarshi misolni topish (qiymatlarni kichraytirib qayta tekshirish — "shrinking")
  3. float kabi turlar uchun taqqoslash=math.isclose parametri
  4. Pul, Oraliq, Vektor va set uchun qonunlar to'plamlari
  5. Hisobot: har qonun uchun "", " qarshi misol" yoki " hujjatlashtirilgan istisno"

Vazifa 7: O'ylash

Java foydalanuvchi operatorlarini ortiqcha yuklashga umuman ruxsat bermaydi, C++ deyarli hamma operatorni (hatto , va && ni) yuklashga ruxsat beradi, Python esa oraliq yo'lni tanlagan: ko'p operatorlar yuklanadi, lekin and, or, not, is va tayinlash yo'q. Bu tanlovlarning har biri nimani himoya qiladi va nimani qurbon qiladi?

Javob

Qisqa javob: operator ortiqcha yuklash — ifodalilik va bashorat qilinuvchanlik orasidagi kelishuv. Java bashorat qilinuvchanlikni tanladi: a + b har doim o'qiladigan, lekin BigDecimal va vektorlar bilan kod og'ir. C++ ifodalilikni tanladi: matematik kutubxonalar tabiiy yoziladi, lekin && yoki , ni yuklash qisqa tutashuv va tartib kafolatlarini buzadi. Python operator ma'nosi yuklanadigan, lekin boshqaruv oqimi (and, or, not), identifikatsiya (is) va nomlash (tayinlash) yuklanmaydigan chegarani tanladi.

1. Java — yuklash yo'q

Java'da + faqat sonlar va String uchun ishlaydi. Java dizaynerlari C++ dagi operatorlarni suiiste'mol qilish tajribasini sabab qilib ko'rsatishgan.

  • Har a + b — ma'lum tur uchun ma'lum amal; kod o'quvchi hech narsa taxmin qilmaydi
  • BigDecimal bilan pul: narx.multiply(soni).add(soliq).setScale(2, RoundingMode.HALF_EVEN)
  • Vektor, matritsa, kompleks son — ilmiy hisoblash uchun noqulay; shu sabab Kotlin (JVM tili) operator fun konventsiyasi bilan belgilangan operatorlarni yuklashga ruxsat berdi

2. C++ — deyarli hamma narsa

  • Matematik kutubxonalar (Eigen), iteratorlar (*it, ++it), aqlli ko'rsatkichlar (->), oqimlar (<<) tabiiy yoziladi
  • operator&& va operator|| yuklansa, qisqa tutashuv yo'qoladi — ikkala operand ham hisoblanadi
  • operator, yuklansa, baholanish tartibi haqidagi taxminlar buziladi
  • << ning "chapga siljitish" va "oqimga yozish" ma'nolari aralashadi — ustuvorlik esa siljitishniki qoladi: cout << a & b kutilganidek ishlamaydi

C++ dagi ko'p ko'rsatmalar (masalan, "&&, || va , ni yuklamang") aynan shu muammolardan kelib chiqqan.

3. Python — ma'no yuklanadi, oqim yo'q

Yuklanadiganlar: arifmetika, bitlar, taqqoslash, indekslash, chaqirish, atributga murojaat, konteyner protokoli, kontekst menejeri va boshqalar.

Yuklanmaydiganlar va sababi:

Yuklanmaydi Himoya qiladi
and, or Qisqa tutashuv: x and x.y — x yolg'on bo'lsa x.y hisoblanmaydi
not Mantiqiy inkorning yagona ma'nosi (__bool__ orqali)
is, is not Identifikatsiya — hech qachon "yolg'on gapirmaydi"
= (tayinlash) Nom bog'lash — obyekt emas, nom maydoni ishi
Ustuvorlik va assotsiativlik Kodni tur bilmasdan tahlil qilish mumkin

PEP 335 (and/or ni yuklash taklifi) rad etilgan. Buning o'rniga NumPy va pandas &, |, ~ ni ishlatadi — va ularning ustuvorligi tuzog'i (Misol 4) shu qarorning narxi.

4. Boshqa tillar

  • Swift, Scala, Haskell — butunlay yangi operatorlar yaratishga ruxsat beradi (<|>, >>=, |>) va ularning ustuvorligini belgilash mumkin. Ifodalilik maksimal, lekin notanish kodni o'qish uchun operatorlar lug'atini bilish kerak
  • Kotlin, C#, Rust — faqat oldindan belgilangan operatorlar ro'yxati, aniq nomli metodlar/traitlar orqali (operator fun plus, impl Add). Rust'da && va || yuklanmaydi — Python bilan bir xil sabab

5. Tanlovning amaliy oqibatlari

  • Python'da ilmiy hisoblash ekotizimi (NumPy, pandas, PyTorch) paydo bo'lishiga operatorlar katta hissa qo'shgan: A @ B, df[df.x > 0], tensor * 2. PEP 465 aynan NumPy jamoasi talabi bilan @ operatorini qo'shgan
  • Lekin qolgan cheklovlar — and o'rniga &, qavslar, __bool__ taqiqlari — har bir DSL foydalanuvchisi o'rganishi kerak bo'lgan "shevalar" yaratdi

6. Loyihalovchi uchun qoidalar (qaysi tilda bo'lmasin)

  1. Operator tabiiy ma'noga ega bo'lsin — "ajablantirmaslik tamoyili"
  2. Operatorning algebraik va'dalarini bajaring yoki hujjatlashtiring
  3. Boshqaruv oqimi va identifikatsiyaga ta'sir qiladigan narsalarni yuklamang
  4. Ustuvorlik o'zgarmasligini unutmang — foydalanuvchini qavsga majburlang yoki xatoni tez va aniq ko'rsating
  5. Nomli metod aniqroq bo'lsa, operatorni tanlamang (pul.ajrat(3) — pul / 3 dan yaxshi)

7. Xulosa

  1. Java bashorat qilinuvchanlikni, C++ ifodalilikni maksimal qilgan; ikkalasi ham o'z narxini to'laydi
  2. Python ma'no operatorlarini yuklashga ruxsat beradi, lekin qisqa tutashuv, identifikatsiya va tayinlashni himoya qiladi
  3. Bu chegara NumPy/pandas kabi kutubxonalarni imkonli qildi, lekin &/| ustuvorligi tuzog'ini qoldirdi
  4. Yangi operatorlar yaratishga ruxsat beruvchi tillar ifodalilikni o'qilishi hisobiga oshiradi
  5. Qaysi tilda bo'lmasin, operator — va'da: ma'no, qonunlar va kutilgan xatti-harakat

Nimani mustahkamlaydi: 2.1–2.5-bo'limlar.


Xulosa

Bu darsda operator ortiqcha yuklashni real turlarni loyihalash nuqtai nazaridan o'rgandik.

Eng muhim uch fikr:

  1. Joyida amallar va rad etish — semantika, uslub emas. a += b o'zgaruvchan turda taxalluslarni ham o'zgartiradi, o'zgarmasda yangi obyekt beradi; __iadd__ dan NotImplemented qaytsa __add__ ishlaydi va natija boshqa turda bo'lishi mumkin; t[0] += [x] ro'yxatni o'zgartirib, keyin xato beradi. Qiymat turlarini o'zgarmas qiling. Bilmagan turga NotImplemented, ma'nosiz amalga esa aniq xabarli TypeError qaytaring.

  2. Operator — algebraik va'da. Pul turi Decimal yoki butun birlikda saqlanadi, valyutalar aralashmaydi, bo'lish o'rniga qoldiqsiz ajratish ishlatiladi, __eq__ va __hash__ bir xil normallashtirilgan maydonlardan hisoblanadi. O'rin almashtirish, guruhlash, neytral element va xesh mosligini tasodifiy qiymatlar bilan avtomatik tekshirish yashirin xatolarni bir necha qatorda topadi.

  3. Operatorlar DSL qura oladi, lekin Python grammatikasi o'zgarmaydi. and, or, not yuklanmaydi — &, |, ~ ishlating; ularning ustuvorligi taqqoslashdan yuqori — qavs talab qiling. Ifoda obyektida __bool__ ni taqiqlang, aks holda if, and va zanjirli taqqoslash jim noto'g'ri ishlaydi; __eq__ yuklangani uchun __hash__ ni aniq belgilang va in ning avval is bilan solishtirishini hisobga oling.

Keyingi darsda Python ma'lumotlar modelini yaxlit ko'rib chiqamiz: obyekt, tur va qiymat; dunder metodlar qanday guruhlarga bo'linadi, interpretator ularni qachon va qaysi tartibda chaqiradi, va maxsus metodlar nega nusxada emas, turda qidiriladi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.14-dars: Operator ortiqcha yuklash amaliyoti — IlmHamroh