IlmHamroh
Python kursi/Obyekt modeli va xotira3/10-dars25 daqiqa
Mundarija (20)

11.3-dars: Havola semantikasi

11-QISM — OBYEKT MODELI VA XOTIRA · 3-dars


1. Kirish va motivatsiya

7.2-darsda Python argumentlarni "obyektga havolani qiymat bo'yicha" (call by sharing) uzatishini ko'rdik: x.append(1) tashqarida ko'rinadi, x = [1] esa yo'q. 3.1-darsda o'zgaruvchini "quti emas, yorliq" deb ta'rifladik, 6.18-darsda copy va deepcopy ni o'rgandik.

Lekin havola semantikasi faqat funksiya argumentlarida emas — u butun tilda: ro'yxat elementlarida, lug'at qiymatlarida, klass atributlarida, dataclass maydonlarida, += da. Mana real xato — jadvalli o'yinning boshlanishi:

python
maydon = [[" "] * 3] * 3
maydon[0][0] = "X"
print(maydon)
# [['X', ' ', ' '], ['X', ' ', ' '], ['X', ' ', ' ']]

Birinchi yurishdan keyin butun ustun to'ldi. Yana biri — "o'zgarmas" konfiguratsiya:

python
@dataclass(frozen=True)
class Sozlama:
    ruxsatlar: list

s = Sozlama(["o'qish"])
s.ruxsatlar += ["o'chirish"]      # FrozenInstanceError: cannot assign to field 'ruxsatlar'
s.ruxsatlar                       # ["o'qish", "o'chirish"] — xato berdi, lekin o'zgardi!

Python'da hamma narsa havola orqali ushlanadi: nomlar, konteyner elementlari, atributlar. Qaysi amal obyektni o'zgartiradi va qaysi biri nomni qayta bog'laydi — shu farqni har amal uchun aniq bilish taxalluslar (alias) xatolarini, "o'zgarmas" obyektlardagi teshiklarni va iteratsiya paytidagi buzilishlarni oldini oladi.

Bu darsda:

  • O'zgartirish va qayta bog'lash — har amal uchun aniq jadval
  • Taxallus tuzoqlari: [[x]*n]*m, dict.fromkeys, umumiy klass atributlari
  • "O'zgarmas" obyektlarning ichi: kortej, frozen dataclass, property
  • Inkapsulyatsiya sizishi va himoya nusxalari
  • Iteratsiya paytida konteynerni o'zgartirish
  • Havolalar grafi: umumiy qismlar va sikllar; deepcopy va JSON farqi

2. Nazariya — chuqur tushuntirish

2.1. O'zgartirish va qayta bog'lash

Har amal ikki xil ishdan birini qiladi:

  • O'zgartirish (mutation) — obyektning o'zi o'zgaradi; unga ishora qiluvchi barcha nomlar yangi holatni ko'radi
  • Qayta bog'lash (rebinding) — nom (yoki konteyner uyasi, yoki atribut) boshqa obyektga ishora qila boshlaydi; eski obyekt va unga boshqa havolalar o'zgarmaydi
Amal Nima bo'ladi id(x) o'zgaradimi
x = y x nomi qayta bog'lanadi Ha (yangi obyekt)
x.append(v), x.extend(...), x.sort() x obyekti o'zgaradi Yo'q
x[i] = v, x[k] = v x o'zgaradi (uyasi qayta bog'lanadi) Yo'q
x[:] = [...] x joyida almashtiriladi Yo'q
x.attr = v x o'zgaradi (atribut qayta bog'lanadi) Yo'q
x += y (list, set, dict |=) __iadd__ — joyida, keyin x o'shaga qayta bog'lanadi Yo'q
x += y (int, str, tuple) Yangi obyekt, x qayta bog'lanadi Ha
x = x + y Har doim yangi obyekt Ha
sorted(x), x + [v], x.copy() Yangi obyekt, x o'zgarmaydi —
del x Nom o'chadi —
del x[i] x o'zgaradi Yo'q

Qoida: nuqta, qavs yoki mutatsiya metodi — obyektni o'zgartiradi; yalang'och nomga = — faqat nomni qayta bog'laydi.

2.2. Taxallus tuzoqlari

Konteyner elementlari ham havolalar. Bitta obyektni ko'p joyga qo'yish oson:

Kod Muammo To'g'risi
[[0] * 3] * 3 Uch qator — bitta ro'yxat [[0] * 3 for _ in range(3)]
[[]] * n n ta havola bitta bo'sh ro'yxatga [[] for _ in range(n)]
dict.fromkeys(kalitlar, []) Hamma kalitda bitta ro'yxat {k: [] for k in kalitlar} yoki defaultdict(list)
class K: elementlar = [] Barcha nusxalar bitta ro'yxatni bo'lishadi __init__ da self.elementlar = []
def f(x=[]) Barcha chaqiruvlar bitta ro'yxat 11.6-dars

[0] * 3 esa xavfsiz — 0 o'zgarmas, x[i] = 1 uyani qayta bog'laydi, sonni o'zgartirmaydi. Muammo faqat o'zgaruvchan element ko'paytirilganda.

2.3. "O'zgarmas" obyektlarning ichi

O'zgarmaslik sayoz: obyekt o'z havolalarini almashtirmaydi, lekin ular ishora qilgan obyektlar o'zgarishi mumkin.

Obyekt Himoyalangan Himoyalanmagan
tuple Elementlar ro'yxati Ichidagi list, dict
frozenset A'zolar — (a'zolar xeshlanadigan bo'lishi kerak)
@dataclass(frozen=True) Maydonlarni qayta bog'lash Maydondagi list ning ichi
property (setter'siz) Atributni qayta bog'lash Qaytarilgan obyektning ichi
MappingProxyType Lug'atga yozish Qiymatlarning ichi

+= tuzog'i: obyekt.maydon += [...] — ikki qadam:

  1. obyekt.maydon.__iadd__([...]) — ro'yxat joyida o'zgaradi muvaffaqiyatli
  2. obyekt.maydon = natija — qayta bog'lash FrozenInstanceError / AttributeError

Natija: xato ko'tariladi, lekin o'zgarish allaqachon bo'lgan. 10.14-darsdagi t[0] += [x] bilan bir xil mexanizm.

Chuqur o'zgarmaslik uchun ichki qiymatlarni ham o'zgarmas turlarga o'giring: tuple, frozenset, MappingProxyType (sayoz), yoki __post_init__ da konvertatsiya.

2.4. Inkapsulyatsiya sizishi va himoya nusxalari

python
class Guruh:
    def __init__(self, talabalar):
        self._talabalar = talabalar          # ⚠️ chaqiruvchining ro'yxati

    @property
    def talabalar(self):
        return self._talabalar               # ⚠️ ichki ro'yxat tashqariga

Ikki teshik:

  1. Kirishda: chaqiruvchi keyin o'z ro'yxatini o'zgartirsa, Guruh ning ichi ham o'zgaradi
  2. Chiqishda: guruh.talabalar.append(...) — klass tekshiruvlarini chetlab o'tadi

Yechimlar:

Usul Narxi Qachon
Kirishda nusxa: list(talabalar) O(n) har yaratishda Klass holatni o'zi boshqaradi
Chiqishda nusxa: return list(self._talabalar) O(n) har murojaatda Kam murojaat
O'zgarmas ko'rinish: tuple(...), MappingProxyType Kirishda bir marta O(n) Ko'p o'qish, kam yozish
O'zgarmas ma'lumot turlari: frozen dataclass + tuple maydonlar Loyiha darajasida Qiymat obyektlari

2.5. Iteratsiya paytida o'zgartirish

Konteyner Iteratsiya paytida o'lcham o'zgarsa
list Xato yo'q — lekin elementlar o'tkazib yuboriladi yoki takrorlanadi
dict RuntimeError: dictionary changed size during iteration
set RuntimeError: Set changed size during iteration

dict ning mavjud kalitlari qiymatini o'zgartirish esa ruxsat etilgan.

Naqshlar:

  • Nusxa ustida iteratsiya: for x in list(royxat):, for k in list(lugat):
  • Yangi konteyner yig'ish: royxat[:] = [x for x in royxat if shart(x)]
  • O'zgarishlarni to'plab, keyin qo'llash

2.6. Havolalar grafi

Ichma-ich tuzilmalar — graf: bir obyekt bir nechta joydan ishora qilinishi (umumiy qism) yoki o'ziga qaytishi (sikl) mumkin.

Vosita Umumiy qism Sikl
copy.copy Tashqi nusxa, ichkilar umumiy Ichki havolalar asl
copy.deepcopy Saqlanadi (memo lug'ati) Saqlanadi
json.loads(json.dumps(...)) Har joyda alohida nusxa ValueError: Circular reference detected
pickle Saqlanadi Saqlanadi

"Chuqur nusxa" usulini tanlashda so'rang: nusxada umumiy qismlar umumiy qolishi kerakmi?


3. Tez ma'lumotnoma

O'zgartirish yoki qayta bog'lash

x = ...                  qayta bog'lash (faqat nom)
x.metod(), x[i] = , x.a = , x[:] = , del x[i]   o'zgartirish (obyekt)
x += y   list/set/dict → o'zgartirish     int/str/tuple → yangi obyekt
x = x + y                har doim yangi obyekt

Taxalluslar

[[0]*3]*3        ❌    [[0]*3 for _ in range(3)]   ✅
dict.fromkeys(k, [])  ❌    {x: [] for x in k} / defaultdict(list)  ✅
class K: lst = []     ❌    self.lst = [] (__init__ da)  ✅

"O'zgarmas" ichi

tuple, frozen dataclass, property, MappingProxyType — sayoz himoya
obj.maydon += [...]  → ichki ro'yxat o'zgaradi VA keyin xato

Iteratsiya

list — jim o'tkazib yuboradi   dict/set — RuntimeError
for x in list(konteyner):     royxat[:] = [x for x in royxat if ...]

Graf nusxalash

deepcopy / pickle — umumiy qism va sikl saqlanadi
json — umumiy qism yo'qoladi, sikl → ValueError

4. Batafsil misollar

Misol 1 — Taxallus tuzoqlari va umumiy qismlarni aniqlash

python
"""[[x]*n]*m, [[]]*n, dict.fromkeys, klass atributi; umumiy ichki obyektlarni topuvchi funksiya."""


def umumiylar(tuzilma, yol="root", korildi=None, natija=None):
    """Bir nechta joydan ishora qilingan o'zgaruvchan obyektlarni topadi."""
    if korildi is None:
        korildi, natija = {}, {}
    if isinstance(tuzilma, (list, dict, set)):
        if id(tuzilma) in korildi:
            natija.setdefault(id(tuzilma), [korildi[id(tuzilma)]]).append(yol)
            return natija
        korildi[id(tuzilma)] = yol
        elementlar = tuzilma.items() if isinstance(tuzilma, dict) else enumerate(tuzilma)
        for kalit, qiymat in elementlar:
            umumiylar(qiymat, f"{yol}[{kalit!r}]", korildi, natija)
    return natija


print("=== 1. [[' '] * 3] * 3 ===")
yomon = [[" "] * 3] * 3
yomon[0][0] = "X"
print(f"  yurishdan keyin: {yomon}")
print(f"  umumiy qismlar: {list(umumiylar(yomon).values())}")
yaxshi = [[" "] * 3 for _ in range(3)]
yaxshi[0][0] = "X"
print(f"  ✅ comprehension: {yaxshi}, umumiy: {list(umumiylar(yaxshi).values())}")

print("\n=== 2. [0] * 3 xavfsiz — element o'zgarmas ===")
sonlar = [0] * 3
sonlar[0] += 5
print(f"  {sonlar} — uya qayta bog'landi, 0 obyekti o'zgarmadi")

print("\n=== 3. dict.fromkeys va [[]] * n ===")
guruhlar = dict.fromkeys(["A", "B", "C"], [])
guruhlar["A"].append("Aziz")
print(f"  dict.fromkeys: {guruhlar}")
navbatlar = [[]] * 3
navbatlar[1].append("vazifa")
print(f"  [[]] * 3:      {navbatlar}")
from collections import defaultdict
guruhlar2 = defaultdict(list)
guruhlar2["A"].append("Aziz")
print(f"  ✅ defaultdict: {dict(guruhlar2)}, ✅ comprehension: {[[] for _ in range(3)]}")

print("\n=== 4. Klass atributi — hamma nusxalar uchun bitta ===")


class Savat:
    mahsulotlar = []

    def qosh(self, m):
        self.mahsulotlar.append(m)


a, b = Savat(), Savat()
a.qosh("non")
print(f"  a.qosh('non') → b.mahsulotlar: {b.mahsulotlar}, bitta obyekt: {a.mahsulotlar is b.mahsulotlar is Savat.mahsulotlar}")


class SavatTogri:
    def __init__(self):
        self.mahsulotlar = []


print(f"  ✅ __init__ da: {SavatTogri().mahsulotlar is SavatTogri().mahsulotlar}")

print("\n=== 5. Murakkab konfiguratsiyada umumiy qismlar ===")
standart_portlar = [80, 443]
sozlama = {
    "web": {"portlar": standart_portlar, "host": "0.0.0.0"},
    "api": {"portlar": standart_portlar, "host": "127.0.0.1"},
    "zaxira": [standart_portlar, {"portlar": standart_portlar}],
}
for yollar in umumiylar(sozlama).values():
    print(f"  bitta obyekt {len(yollar)} joyda: {yollar}")
sozlama["web"]["portlar"].append(8080)
print(f"  web ga 8080 qo'shildi → api portlari: {sozlama['api']['portlar']}")

Natijaning muhim qismi:

text
=== 1. [[' '] * 3] * 3 ===
  yurishdan keyin: [['X', ' ', ' '], ['X', ' ', ' '], ['X', ' ', ' ']]
  umumiy qismlar: [['root[0]', 'root[1]', 'root[2]']]
  ✅ comprehension: [['X', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']], umumiy: []

=== 2. [0] * 3 xavfsiz — element o'zgarmas ===
  [5, 0, 0] — uya qayta bog'landi, 0 obyekti o'zgarmadi

=== 3. dict.fromkeys va [[]] * n ===
  dict.fromkeys: {'A': ['Aziz'], 'B': ['Aziz'], 'C': ['Aziz']}
  [[]] * 3:      [['vazifa'], ['vazifa'], ['vazifa']]
  ✅ defaultdict: {'A': ['Aziz']}, ✅ comprehension: [[], [], []]

=== 4. Klass atributi — hamma nusxalar uchun bitta ===
  a.qosh('non') → b.mahsulotlar: ['non'], bitta obyekt: True
  ✅ __init__ da: False

=== 5. Murakkab konfiguratsiyada umumiy qismlar ===
  bitta obyekt 4 joyda: ["root['web']['portlar']", "root['api']['portlar']", "root['zaxira'][0]", "root['zaxira'][1]['portlar']"]
  web ga 8080 qo'shildi → api portlari: [80, 443, 8080]

Nima ko'rsatdi: 2.2-bo'lim. umumiylar() ning o'zi ham id ni faqat tuzilma tirik bo'lgan paytda ishlatadi — 11.1-darsdagi qoidaga mos.

Misol 2 — O'zgartirish va qayta bog'lash jadvali

python
"""Har amal uchun: id o'zgardimi va taxallus o'zgarishni ko'rdimi — list, tuple, str, set, dict."""


def sina(nom, boshlangich, amal_matni):
    muhit = {"x": boshlangich}
    muhit["taxallus"] = muhit["x"]
    eski_id = id(muhit["x"])
    oldingi = repr(muhit["taxallus"])
    exec(amal_matni, {}, muhit)
    yangi_obyekt = id(muhit["x"]) != eski_id
    taxallus_ozgardi = repr(muhit["taxallus"]) != oldingi
    tur = "qayta bog'lash" if yangi_obyekt else "o'zgartirish"
    return f"  {nom:6} {amal_matni:22} {tur:15} id o'zgardi: {str(yangi_obyekt):5}  taxallus ko'rdi: {taxallus_ozgardi}"


print("=== list ===")
for amal in ["x.append(4)", "x[0] = 9", "x[:] = [7, 7]", "x += [4]", "x = x + [4]", "x = [1, 2, 3]",
             "x.sort(reverse=True)", "x = sorted(x)", "del x[0]"]:
    print(sina("list", [1, 2, 3], amal))

print("\n=== tuple, str, int — o'zgarmas ===")
for nom, qiymat, amal in [("tuple", (1, 2), "x += (3,)"), ("str", "ab", "x += 'c'"), ("int", 5000, "x += 1"),
                          ("str", "ab", "x = x.upper()")]:
    print(sina(nom, qiymat, amal))

print("\n=== set, dict ===")
for nom, qiymat, amal in [("set", {1, 2}, "x.add(3)"), ("set", {1, 2}, "x |= {3}"), ("set", {1, 2}, "x = x | {3}"),
                          ("dict", {"a": 1}, "x['b'] = 2"), ("dict", {"a": 1}, "x |= {'b': 2}"),
                          ("dict", {"a": 1}, "x = x | {'b': 2}"), ("dict", {"a": 1}, "x.update(c=3)")]:
    print(sina(nom, qiymat, amal))

Natijaning muhim qismi:

text
=== list ===
  list   x.append(4)            o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  list   x[0] = 9               o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  list   x[:] = [7, 7]          o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  list   x += [4]               o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  list   x = x + [4]            qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False
  list   x = [1, 2, 3]          qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False
  list   x.sort(reverse=True)   o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  list   x = sorted(x)          qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False
  list   del x[0]               o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True

=== tuple, str, int — o'zgarmas ===
  tuple  x += (3,)              qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False
  str    x += 'c'               qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False
  int    x += 1                 qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False
  str    x = x.upper()          qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False

=== set, dict ===
  set    x.add(3)               o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  set    x |= {3}               o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  set    x = x | {3}            qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False
  dict   x['b'] = 2             o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  dict   x |= {'b': 2}          o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True
  dict   x = x | {'b': 2}       qayta bog'lash  id o'zgardi: True   taxallus ko'rdi: False
  dict   x.update(c=3)          o'zgartirish    id o'zgardi: False  taxallus ko'rdi: True

Nima ko'rsatdi: 2.1-bo'lim — += o'zgaruvchan turlarda o'zgartirish, o'zgarmaslarda qayta bog'lash; x = x + ... va x = x | ... esa har doim yangi obyekt.

Misol 3 — "O'zgarmas" obyektlar va inkapsulyatsiya sizishi

python
"""frozen dataclass va property da += tuzog'i; kirish/chiqishdagi sizish; himoya nusxalari va o'zgarmas ko'rinishlar."""

from dataclasses import FrozenInstanceError, dataclass, field
from types import MappingProxyType

print("=== 1. ⚠️ frozen dataclass: xato berdi, lekin o'zgardi ===")


@dataclass(frozen=True)
class Sozlama:
    ruxsatlar: list = field(default_factory=list)


s = Sozlama(["o'qish"])
try:
    s.ruxsatlar += ["o'chirish"]
except FrozenInstanceError as e:
    print(f"  FrozenInstanceError: {e}")
print(f"  s.ruxsatlar: {s.ruxsatlar}   ⚠️ __iadd__ allaqachon ishladi")
s.ruxsatlar.append("admin")
print(f"  append ga hech qanday to'siq yo'q: {s.ruxsatlar}")

print("\n=== 2. ⚠️ Setter'siz property ===")


class Hisob:
    def __init__(self):
        self._tarix = []

    @property
    def tarix(self):
        return self._tarix


h = Hisob()
try:
    h.tarix += ["soxta yozuv"]
except AttributeError as e:
    print(f"  AttributeError: {e}")
print(f"  h._tarix: {h._tarix}")

print("\n=== 3. ✅ Chuqur o'zgarmas dataclass ===")


@dataclass(frozen=True)
class SozlamaToza:
    ruxsatlar: tuple = ()
    chegaralar: MappingProxyType = field(default_factory=lambda: MappingProxyType({}))

    def __post_init__(self):
        object.__setattr__(self, "ruxsatlar", tuple(self.ruxsatlar))
        object.__setattr__(self, "chegaralar", MappingProxyType(dict(self.chegaralar)))


manba_ruxsat = ["o'qish"]
manba_chegara = {"sorov": 100}
t = SozlamaToza(manba_ruxsat, manba_chegara)
manba_ruxsat.append("o'chirish")
manba_chegara["sorov"] = 999
print(f"  manba o'zgargandan keyin: {t.ruxsatlar}, {dict(t.chegaralar)}")
for tavsif, amal in [("ruxsatlar += ", lambda: setattr(t, "ruxsatlar", t.ruxsatlar + ("x",))),
                     ("ruxsatlar.append", lambda: t.ruxsatlar.append("x")),
                     ("chegaralar['sorov'] =", lambda: t.chegaralar.__setitem__("sorov", 1))]:
    try:
        amal()
    except (FrozenInstanceError, AttributeError, TypeError) as e:
        print(f"  {tavsif:24} → {type(e).__name__}")

print("\n=== 4. Inkapsulyatsiya sizishi va himoya nusxalari ===")


class GuruhYomon:
    MAKS = 3

    def __init__(self, talabalar):
        if len(talabalar) > self.MAKS:
            raise ValueError("guruh to'la")
        self._talabalar = talabalar

    @property
    def talabalar(self):
        return self._talabalar


class GuruhToza:
    MAKS = 3

    def __init__(self, talabalar):
        talabalar = tuple(talabalar)
        if len(talabalar) > self.MAKS:
            raise ValueError("guruh to'la")
        self._talabalar = talabalar

    @property
    def talabalar(self):
        return self._talabalar

    def qosh(self, ism):
        if len(self._talabalar) >= self.MAKS:
            raise ValueError("guruh to'la")
        self._talabalar += (ism,)


for klass in (GuruhYomon, GuruhToza):
    royxat = ["Aziz", "Dilnoza"]
    g = klass(royxat)
    royxat.append("Bobur")
    royxat.append("Malika")
    try:
        g.talabalar.append("Sardor")
    except AttributeError:
        pass
    print(f"  {klass.__name__:11} kirish sizishi + chiqish sizishi → {len(g.talabalar)} talaba (MAKS {klass.MAKS}): {list(g.talabalar)}")

Natijaning muhim qismi:

text
=== 1. ⚠️ frozen dataclass: xato berdi, lekin o'zgardi ===
  FrozenInstanceError: cannot assign to field 'ruxsatlar'
  s.ruxsatlar: ["o'qish", "o'chirish"]   ⚠️ __iadd__ allaqachon ishladi
  append ga hech qanday to'siq yo'q: ["o'qish", "o'chirish", 'admin']

=== 2. ⚠️ Setter'siz property ===
  AttributeError: property 'tarix' of 'Hisob' object has no setter
  h._tarix: ['soxta yozuv']

=== 3. ✅ Chuqur o'zgarmas dataclass ===
  manba o'zgargandan keyin: ("o'qish",), {'sorov': 100}
  ruxsatlar +=             → FrozenInstanceError
  ruxsatlar.append         → AttributeError
  chegaralar['sorov'] =    → AttributeError

=== 4. Inkapsulyatsiya sizishi va himoya nusxalari ===
  GuruhYomon  kirish sizishi + chiqish sizishi → 5 talaba (MAKS 3): ['Aziz', 'Dilnoza', 'Bobur', 'Malika', 'Sardor']
  GuruhToza   kirish sizishi + chiqish sizishi → 2 talaba (MAKS 3): ['Aziz', 'Dilnoza']

Nima ko'rsatdi: 2.3, 2.4-bo'limlar. GuruhYomon da tekshiruv bir marta ishladi, keyin chaqiruvchi ro'yxatni tashqaridan to'ldirib yubordi; GuruhToza kirishda tuple ga o'girgani uchun ikkala teshik ham yopildi.

Misol 4 — Amaliy: iteratsiya paytida o'zgartirish va graf nusxalash

Buyurtmalar tizimida bekor qilinganlarini tozalash, kutilayotganlarni guruhlash va sozlamalar grafini saqlash — havolalar bilan bog'liq uch real vazifa.

python
"""list/dict/set ni iteratsiya paytida o'zgartirish; to'g'ri naqshlar; deepcopy, pickle va JSON umumiy qism va sikllar bilan."""

import copy
import json
import pickle

print("=== 1. ⚠️ Ro'yxatdan iteratsiya paytida o'chirish ===")
buyurtmalar = ["bekor", "bekor", "yangi", "bekor", "tolangan", "bekor"]
yomon = list(buyurtmalar)
for holat in yomon:
    if holat == "bekor":
        yomon.remove(holat)
print(f"  for + remove:            {yomon}   ⚠️ xato yo'q, lekin 'bekor' qoldi")
ikkinchi = list(buyurtmalar)
for holat in list(ikkinchi):
    if holat == "bekor":
        ikkinchi.remove(holat)
print(f"  ✅ nusxa ustida:          {ikkinchi}")
uchinchi = list(buyurtmalar)
taxallus = uchinchi
uchinchi[:] = [h for h in uchinchi if h != "bekor"]
print(f"  ✅ royxat[:] = [...]:     {uchinchi}, taxallus ham yangilandi: {taxallus is uchinchi}")

print("\n=== 2. dict va set ===")
holatlar = {"#1": "bekor", "#2": "yangi", "#3": "bekor"}
try:
    for kalit in holatlar:
        if holatlar[kalit] == "bekor":
            del holatlar[kalit]
except RuntimeError as e:
    print(f"  dict: RuntimeError: {e}")
for kalit in holatlar:
    holatlar[kalit] = holatlar[kalit].upper()
print(f"  qiymatlarni o'zgartirish ruxsat: {holatlar}")
teglar = {"shoshilinch", "katta"}
try:
    for t in teglar:
        teglar.add(t + "!")
except RuntimeError as e:
    print(f"  set: RuntimeError: {e}")
holatlar = {"#1": "bekor", "#2": "yangi", "#3": "bekor"}
for kalit in [k for k, v in holatlar.items() if v == "bekor"]:
    del holatlar[kalit]
print(f"  ✅ avval kalitlarni yig'ish: {holatlar}")

print("\n=== 3. Graf: umumiy qism va sikl ===")
standart = {"valyuta": "UZS", "soliq": 12}
sozlama = {"toshkent": standart, "samarqand": standart, "ota": None}
sozlama["ota"] = sozlama


def tekshir(nom, nusxa):
    umumiy = nusxa["toshkent"] is nusxa["samarqand"]
    sikl = nusxa["ota"] is nusxa
    print(f"  {nom:14} umumiy qism saqlandi: {umumiy}, sikl saqlandi: {sikl}, aslidan mustaqil: {nusxa['toshkent'] is not standart}")


tekshir("copy.copy", copy.copy(sozlama))
tekshir("copy.deepcopy", copy.deepcopy(sozlama))
tekshir("pickle", pickle.loads(pickle.dumps(sozlama)))
try:
    json.dumps(sozlama)
except ValueError as e:
    print(f"  json (sikl bilan): ValueError: {e}")
siklsiz = {"toshkent": standart, "samarqand": standart}
j = json.loads(json.dumps(siklsiz))
print(f"  json (siklsiz): umumiy qism saqlandi: {j['toshkent'] is j['samarqand']}")
j["toshkent"]["soliq"] = 15
print(f"  ⚠️ json nusxasida toshkent soliqi o'zgartirildi → samarqand: {j['samarqand']['soliq']} (endi bog'lanmagan)")

Natijaning muhim qismi:

text
=== 1. ⚠️ Ro'yxatdan iteratsiya paytida o'chirish ===
  for + remove:            ['yangi', 'tolangan', 'bekor']   ⚠️ xato yo'q, lekin 'bekor' qoldi
  ✅ nusxa ustida:          ['yangi', 'tolangan']
  ✅ royxat[:] = [...]:     ['yangi', 'tolangan'], taxallus ham yangilandi: True

=== 2. dict va set ===
  dict: RuntimeError: dictionary changed size during iteration
  qiymatlarni o'zgartirish ruxsat: {'#2': 'YANGI', '#3': 'BEKOR'}
  set: RuntimeError: Set changed size during iteration
  ✅ avval kalitlarni yig'ish: {'#2': 'yangi'}

=== 3. Graf: umumiy qism va sikl ===
  copy.copy      umumiy qism saqlandi: True, sikl saqlandi: False, aslidan mustaqil: False
  copy.deepcopy  umumiy qism saqlandi: True, sikl saqlandi: True, aslidan mustaqil: True
  pickle         umumiy qism saqlandi: True, sikl saqlandi: True, aslidan mustaqil: True
  json (sikl bilan): ValueError: Circular reference detected
  json (siklsiz): umumiy qism saqlandi: False
  ⚠️ json nusxasida toshkent soliqi o'zgartirildi → samarqand: 12 (endi bog'lanmagan)

Nima ko'rsatdi: 2.5, 2.6-bo'limlar. Ro'yxat xatosiz, lekin noto'g'ri natija berdi — bu dict va set dagi RuntimeError dan xavfliroq. JSON orqali "chuqur nusxa" esa umumiy sozlamani ikki mustaqil nusxaga ajratib, dasturning ma'nosini o'zgartirdi.


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

Noto'g'ri fikr To'g'risi
"x += y har doim x = x + y" O'zgaruvchan turlarda joyida o'zgartirish — taxalluslar ham ko'radi
"[[0]*3]*3 — uch mustaqil qator" Bitta ro'yxatga uch havola
"frozen dataclass ichidagi hamma narsa o'zgarmas" Faqat maydonlarni qayta bog'lash taqiqlangan
"Xato ko'tarilgan bo'lsa, hech narsa o'zgarmagan" obj.maydon += [...] — o'zgarish xatodan oldin bo'ladi
"Setter'siz property — faqat o'qish" Qaytarilgan o'zgaruvchan obyektni o'zgartirish mumkin
"Ro'yxatdan iteratsiya paytida o'chirish — xato beradi" Jim, noto'g'ri natija
"JSON orqali chuqur nusxa — deepcopy bilan bir xil" Umumiy qismlar ajraladi, sikllar xato beradi
"Klass atributidagi ro'yxat — har nusxaning o'ziniki" Barcha nusxalar bo'lishadi

6. Keng tarqalgan xatolar va yechimlari

1. Ichma-ich ro'yxatni ko'paytirish

python
jadval = [[0] * ustunlar] * qatorlar            # ❌
jadval = [[0] * ustunlar for _ in range(qatorlar)]   # ✅

2. dict.fromkeys bilan o'zgaruvchan qiymat

python
guruhlar = dict.fromkeys(nomlar, [])            # ❌
guruhlar = {n: [] for n in nomlar}              # ✅

3. Klass darajasidagi o'zgaruvchan atribut

python
class Foydalanuvchi:
    teglar = []                                 # ❌ umumiy

class Foydalanuvchi:
    def __init__(self):
        self.teglar = []                        # ✅

4. Qabul qilingan ro'yxatni to'g'ridan-to'g'ri saqlash

python
def __init__(self, elementlar):
    self.elementlar = elementlar                # ❌ chaqiruvchi keyin o'zgartiradi

    self.elementlar = list(elementlar)          # ✅ (yoki tuple)

5. Ichki holatni qaytarish

python
@property
def buyurtmalar(self):
    return self._buyurtmalar                    # ❌ tashqaridan append

    return tuple(self._buyurtmalar)             # ✅

6. frozen obyektda o'zgaruvchan maydon

python
@dataclass(frozen=True)
class Marshrut:
    bekatlar: list                              # ❌ ichi o'zgaradi

    bekatlar: tuple                             # ✅ + __post_init__ da tuple(...)

7. Iteratsiya paytida o'chirish

python
for x in royxat:
    if yomon(x):
        royxat.remove(x)                        # ❌ o'tkazib yuboradi

royxat[:] = [x for x in royxat if not yomon(x)] # ✅

8. JSON bilan chuqur nusxa

python
nusxa = json.loads(json.dumps(sozlama))         # ❌ umumiy qismlar, sikllar, turlar yo'qoladi
nusxa = copy.deepcopy(sozlama)                  # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 3.1, 7.2-darslar (o'tilgan): yorliq modeli va argument uzatish
  • 6.18-dars (o'tilgan): copy va deepcopy
  • 8.5, 8.17-darslar (o'tilgan): klass va nusxa atributlari, dataclass(frozen=True)
  • 10.14-dars (o'tilgan): __iadd__, t[0] += [x] tuzog'i
  • 11.4–11.5-darslar: havolalar soni va sikllar — obyekt qachon o'ladi
  • 11.6-dars: o'zgaruvchan standart argument — taxallusning maxsus holati
  • 14-qism: iplar orasida umumiy o'zgaruvchan obyektlar va poyga holati
  • 24-qism: NumPy kesmalari — nusxa emas, ko'rinish (view)

8. Eng yaxshi amaliyotlar

  1. Har amalda so'rang: obyekt o'zgaradimi yoki nom qayta bog'lanadimi?

  2. Ichma-ich o'zgaruvchan tuzilmalarni comprehension bilan yarating — ko'paytirish bilan emas.

  3. O'zgaruvchan qiymatlarni klass darajasida emas, __init__ da.

  4. Qabul qilingan o'zgaruvchan argumentni saqlashdan oldin nusxalang yoki o'zgarmas turga o'giring.

  5. Ichki holatni o'zgarmas ko'rinish sifatida qaytaring — tuple, frozenset, MappingProxyType.

  6. "O'zgarmas" obyektlarda maydonlarni ham o'zgarmas qiling — frozen sayoz.

  7. Iteratsiya paytida konteynerni o'zgartirmang — nusxa yoki yangi konteyner.

  8. Chuqur nusxa uchun deepcopy — JSON faqat serializatsiya uchun.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
from dataclasses import dataclass, field, FrozenInstanceError
1.  a = [1, 2]
    b = a
    a += [3]
    print(b)
2.  a = [1, 2]
    b = a
    a = a + [3]
    print(b)
3.  a = (1, 2)
    b = a
    a += (3,)
    print(b, a is b)
4.  m = [[0] * 2] * 2
    m[1][0] = 5
    print(m)
5.  d = dict.fromkeys("ab", [])
    d["a"].append(1)
    print(d)
6.  class K:
        x = []
    K().x.append(1)
    print(K().x)
7.  @dataclass(frozen=True)
    class F:
        l: list = field(default_factory=list)
    f = F()
    try:
        f.l += [1]
    except FrozenInstanceError:
        pass
    print(f.l)
8.  r = [1, 2, 2, 3]
    for x in r:
        if x == 2:
            r.remove(x)
    print(r)
9.  d = {"a": 1}
    for k in d:
        d[k] = 2
    print(d)
10. s = [1, 2]
    t = s
    s[:] = [9]
    print(t, s is t)
11. x = [1]
    y = [x, x]
    y[0].append(2)
    print(y)
12. import copy
    x = [1]
    y = copy.deepcopy([x, x])
    print(y[0] is y[1], y[0] is x)
Javoblar
  1. [1, 2, 3] — += joyida o'zgartirdi
  2. [1, 2] — a + [3] yangi ro'yxat
  3. (1, 2) False — kortej o'zgarmas, a qayta bog'landi
  4. [[5, 0], [5, 0]] — bitta ro'yxat ikki marta
  5. {'a': [1], 'b': [1]}
  6. [1] — klass atributi umumiy
  7. [1] — xato berildi, lekin ro'yxat o'zgardi
  8. [1, 2, 3] — ikkinchi 2 o'tkazib yuborildi
  9. {'a': 2} — qiymatni o'zgartirish ruxsat
  10. [9] True — joyida almashtirish
  11. [[1, 2], [1, 2]]
  12. True False — deepcopy umumiy qismni saqlaydi, lekin asldan ajraladi

Vazifa 2: Xatolarni tuzating

python
1.  def yangi_taxta(n):
        return [["."] * n] * n

2.  class Kutubxona:
        kitoblar = {}
        def qosh(self, isbn, nom):
            self.kitoblar[isbn] = nom

3.  @dataclass(frozen=True)
    class Marshrut:
        bekatlar: list
    m = Marshrut(bekatlar)
    bekatlar.append("Chilonzor")          # m ga ta'sir qilmasligi kerak edi

4.  def faol_emaslarni_ochir(foydalanuvchilar):
        for f in foydalanuvchilar:
            if not f["faol"]:
                foydalanuvchilar.remove(f)

5.  def konfiguratsiya_nusxasi(cfg):
        return json.loads(json.dumps(cfg))    # cfg da datetime va umumiy qismlar bor
Javoblar
python
1.  def yangi_taxta(n):
        return [["."] * n for _ in range(n)]

2.  class Kutubxona:
        def __init__(self):
            self.kitoblar = {}                # har nusxaga o'z lug'ati
        def qosh(self, isbn, nom):
            self.kitoblar[isbn] = nom

3.  @dataclass(frozen=True)
    class Marshrut:
        bekatlar: tuple
        def __post_init__(self):
            object.__setattr__(self, "bekatlar", tuple(self.bekatlar))

4.  def faol_emaslarni_ochir(foydalanuvchilar):
        foydalanuvchilar[:] = [f for f in foydalanuvchilar if f["faol"]]

5.  def konfiguratsiya_nusxasi(cfg):
        return copy.deepcopy(cfg)             # turlar, umumiy qismlar va sikllar saqlanadi

Vazifa 3: taxalluslar_xaritasi(tuzilma)

Misol 1 dagi umumiylar ni kengaytiring:

  1. Sikllarni aniqlasin va alohida ko'rsatsin ("root['ota'] → root")
  2. dataclass va __dict__ li obyektlar ichiga ham kirsin
  3. Har umumiy obyekt uchun "xavf darajasi" — o'zgaruvchan bo'lsa yuqori, tuple/frozenset bo'lsa past
  4. Natijani daraxt ko'rinishida chop etsin

Vazifa 4: Chuqur o'zgarmas konvertor

muzlat(obj) funksiyasini yozing:

  1. list → tuple, set → frozenset, dict → MappingProxyType (rekursiv)
  2. Umumiy qismlarni saqlasin (bir obyekt — bir natija, memo)
  3. Sikllarda cheksiz rekursiyaga tushmasin (sikl bo'lsa ValueError, chunki kortejda sikl yasab bo'lmaydi — nega?)
  4. eritish(obj) — teskari amal
  5. @dataclass(frozen=True) uchun __post_init__ da avtomatik ishlatiladigan dekorator

Vazifa 5: Himoyalangan kolleksiya

Buyurtmalar klassini yozing:

  1. Ichida list, tashqariga faqat o'qish uchun ko'rinish (collections.abc.Sequence asosida, nusxa olmasdan)
  2. qosh, bekor_qil — tekshiruvlar bilan
  3. Tashqi kod iteratsiya qilayotgan paytda qosh chaqirilsa — RuntimeError ("kolleksiya iteratsiya paytida o'zgardi"), dict kabi (versiya hisoblagichi bilan)
  4. Nusxa va ko'rinish usullarining tezligini 100 000 element bilan solishtiring

Vazifa 6: Nusxalash strategiyalari solishtiruvi

Bir xil sozlamalar grafi uchun (umumiy qismlar, sikl, datetime, Decimal, o'z klassingiz nusxasi):

  1. copy.copy, copy.deepcopy, pickle, json (+ default=str), marshal natijalarini jadvalda solishtiring: umumiy qism, sikl, turlar, tezlik
  2. __deepcopy__ yozib, klassingiz uchun ba'zi maydonlarni nusxalamaslikni (masalan, ulanish obyekti) ta'minlang
  3. Qaysi vaziyatda qaysi usul to'g'ri ekanini xulosa qiling

Vazifa 7: O'ylash

Python'da barcha qiymatlar havola orqali ushlanadi va o'zgarmaslik sayoz. Rust'da esa egalik (ownership) va qarz olish (borrowing) tizimi bor, funksional tillarda (Haskell, Clojure) ma'lumotlar sukut bo'yicha o'zgarmas. Bu yondashuvlar taxallus xatolarini qanday hal qiladi va Python dasturchisi ulardan nimani o'rganishi mumkin?

Javob

Qisqa javob: taxallus xatosi — ikki joydan bir o'zgaruvchan obyektga ta'sir qilish va biri ikkinchisidan bexabar bo'lishi. Rust buni kompilyator darajasida taqiqlaydi: bir vaqtda yo bitta o'zgartiruvchi, yo ko'p o'qiydigan havola. Funksional tillar esa o'zgartirishni umuman olib tashlaydi: "o'zgartirish" yangi qiymat yaratadi, eski qiymat esa hech qachon o'zgarmaydi. Python ikkala kafolatni ham bermaydi, lekin dasturchi shu g'oyalarni konvensiya va o'zgarmas turlar orqali qo'llay oladi.

1. Rust: egalik va qarz olish

rust
let mut royxat = vec![1, 2, 3];
let birinchi = &royxat[0];      // o'qish uchun qarz
royxat.push(4);                 // ❌ kompilyatsiya xatosi: o'qish qarzi tirik paytda o'zgartirish
  • Har qiymatning bitta egasi bor
  • &T — ko'p o'qish havolalari, &mut T — faqat bitta o'zgartirish havolasi, ikkalasi bir vaqtda emas
  • Taxallus + o'zgartirish xatolari, iteratsiya paytida o'zgartirish va ma'lumotlar poygasi kompilyatsiyada ushlanadi
  • O'rganish qiyin; ba'zi tuzilmalar (graflar, ikki tomonlama bog'langan ro'yxatlar) murakkab (Rc<RefCell<T>>)

2. Funksional tillar: o'zgarmas ma'lumot

clojure
(def sozlama {:portlar [80 443]})
(def yangi (update sozlama :portlar conj 8080))
; sozlama — o'zgarmagan, yangi — {:portlar [80 443 8080]}
  • "O'zgartirish" — yangi qiymat; eski qiymat abadiy bir xil
  • Tuzilmaviy bo'lishish (structural sharing): yangi versiya eski versiyaning o'zgarmagan qismlarini qayta ishlatadi — nusxalash arzon
  • Taxallus xavfsiz — umumiy qismni hech kim o'zgartira olmaydi; iplar orasida qulf kerak emas
  • Ba'zi algoritmlar sekinroq yoki boshqacha yozilishi kerak; xotira yuki

3. Python holati

  • Hamma narsa havola, o'zgarmaslik sayoz, kompilyator taxalluslarni tekshirmaydi
  • O'zgarmas turlar bor (tuple, frozenset, str, frozen dataclass), lekin ichma-ich o'zgarmaslik avtomatik emas
  • Tuzilmaviy bo'lishuvchi kolleksiyalar standart kutubxonada yo'q (uchinchi tomon kutubxonalari bor, masalan pyrsistent)

4. Python dasturchisi nimani o'rganishi mumkin

Rust'dan — egalik g'oyasi:

  • Har o'zgaruvchan obyektning aniq egasi bo'lsin: kim uni o'zgartirishga haqli
  • Chegara orqali o'tganda (funksiya argumenti, qaytarish qiymati, klassga saqlash) — nusxa yoki o'zgarmas ko'rinish (2.4)
  • Iteratsiya paytida o'zgartirmaslik — Rust kompilyatori taqiqlaydigan narsani qo'lda taqiqlash

Funksional tillardan — o'zgarmas qiymatlar:

  • Qiymat obyektlarini (Pul, Sana, Sozlama) chuqur o'zgarmas qiling
  • "O'zgartirish" o'rniga yangi obyekt qaytaring: dataclasses.replace(obj, maydon=...), obj | {"yangi": 1}, sorted(x)
  • O'zgartirishni chekkaga suring: yadro sof funksiyalar, holat esa kichik, aniq joylarda

5. Amaliy qoidalar

Qoida Qaysi g'oya
Argumentni saqlashdan oldin nusxalang Egalik
Ichki holatni tuple/MappingProxyType sifatida qaytaring Qarz — faqat o'qish
Qiymat obyektlari — frozen + o'zgarmas maydonlar O'zgarmaslik
dataclasses.replace, ` ` operatori
Iplar orasida o'zgarmas obyektlar uzating Poygasiz bo'lishish
Iteratsiya paytida o'zgartirmang Qarz qoidasi

6. Xulosa

  1. Taxallus xatosi — bir o'zgaruvchan obyektga bexabar ikki havola
  2. Rust buni egalik va qarz olish qoidalari bilan kompilyatsiyada taqiqlaydi
  3. Funksional tillar o'zgartirishni olib tashlab, tuzilmaviy bo'lishish bilan samaradorlikni saqlaydi
  4. Python kafolat bermaydi — lekin egalik chegaralari, o'zgarmas qiymat obyektlari va yangi qiymat qaytarish odati bilan xuddi shu xavfsizlikni amalda olish mumkin
  5. Eng ko'p foyda — chegaralarda (API, klass kiritish/chiqarishi, iplar) aniq qoidalar

Nimani mustahkamlaydi: 2.1–2.6-bo'limlar.


Xulosa

Bu darsda havola semantikasini butun til bo'ylab ko'rib chiqdik.

Eng muhim uch fikr:

  1. Har amal yo obyektni o'zgartiradi, yo nomni qayta bog'laydi. Nuqta, qavs va mutatsiya metodlari (x.append, x[i] =, x.a =, x[:] =) obyektni o'zgartiradi va barcha taxalluslar buni ko'radi; yalang'och nomga = faqat nomni qayta bog'laydi. += o'zgaruvchan turlarda joyida o'zgartiradi, o'zgarmaslarda yangi obyekt yaratadi. Konteyner elementlari ham havola — [[0]*3]*3, [[]]*n, dict.fromkeys(k, []) va klass atributidagi ro'yxat bitta obyektni ko'p joyga qo'yadi.

  2. O'zgarmaslik sayoz, chegaralar esa himoya talab qiladi. tuple, frozen dataclass, setter'siz property va MappingProxyType faqat o'z havolalarini himoyalaydi; obj.maydon += [...] ichki ro'yxatni o'zgartirib, keyin xato beradi. Qabul qilingan o'zgaruvchan argumentni saqlashdan oldin nusxalang, ichki holatni o'zgarmas ko'rinish sifatida qaytaring va qiymat obyektlarining maydonlarini ham o'zgarmas turlarga o'giring.

  3. Iteratsiya va nusxalash — havolalar grafi bilan ishlash. Ro'yxatni iteratsiya paytida o'zgartirish jim noto'g'ri natija beradi, dict va set esa RuntimeError ko'taradi — nusxa ustida iteratsiya qiling yoki yangi konteyner yig'ing. deepcopy va pickle umumiy qismlar va sikllarni saqlaydi, JSON esa umumiy qismlarni ajratib, sikllarda xato beradi.

Keyingi darsda obyektlar qachon o'lishini o'rganamiz: havolalar hisobi (reference counting), sys.getrefcount, __del__ qachon chaqiriladi, qaysi amallar havola qo'shadi va nima uchun obyekt kutilganidan uzoq yashashi mumkin.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
11.3-dars: Havola semantikasi — IlmHamroh