IlmHamroh
Python kursi/Obyekt modeli va xotira6/10-dars24 daqiqa
Mundarija (20)

11.6-dars: O'zgaruvchan standart argument tuzog'i

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


1. Kirish va motivatsiya

7.3-darsda mashhur tuzoqni batafsil ko'rdik: def qosh(x, royxat=[]) — sukut qiymat bir marta yaratiladi, __defaults__ da saqlanadi va barcha chaqiruvlar uni bo'lishadi. Sentinel va None yechimlarini, B006 ogohlantirishini va sukut qiymatni ataylab ishlatish holatlarini o'rgandik.

Endi bu tuzoqqa obyekt modeli ko'zi bilan qaraymiz. Chunki u faqat funksiya parametrlarida emas. Mana dataclass tekshiruvidan "o'tib ketgan" kod:

python
@dataclass
class Buyurtma:
    raqam: int
    savat: Savat = Savat()        # ✅ dataclass xato bermadi

b1, b2 = Buyurtma(1), Buyurtma(2)
b1.savat.qosh("non")
b2.savat.mahsulotlar              # ['non'] — boshqa mijozning savati!

dataclass list, dict va set sukutlarini taqiqlaydi — lekin o'z klassingiz nusxasini tanimaydi. Tuzoqning mohiyati o'zgarmagan: bir marta yaratilgan o'zgaruvchan obyekt ko'p egaga bo'lishilgan.

Bu darsda tuzoqni uch darajada ko'ramiz: bytecode'da sukut qiymat qachon yaratiladi; qaysi boshqa joylarda (klass, dataclass, NamedTuple, partial, __init__) xuddi shu mexanizm ishlaydi; va bunday holatlarni avtomatik topuvchi vosita qanday yoziladi.

Bu darsda:

  • Bytecode: sukut qiymatlar def bajarilganda qanday quriladi
  • Ichma-ich def va sikldagi def — har safar yangi sukut
  • Tuzoqning boshqa shakllari: __init__, dataclass, NamedTuple, partial, klass atributlari
  • Sukut qiymat — yashirin havola: xotira va iplar
  • Funksiyani yangi sukutlar bilan klonlash (testlar uchun)
  • Avtomatik aniqlovchi: inspect va ast bilan

2. Nazariya — chuqur tushuntirish

2.1. Bytecode: sukut qachon quriladi

python
def tashqi():
    def ichki(a=(1, 2), b=[1, 2]):
        ...
    return ichki

dis.dis(tashqi):

LOAD_CONST        ((1, 2))          ← o'zgarmas kortej — kompilyatsiyada tayyor konstanta
LOAD_SMALL_INT    1
LOAD_SMALL_INT    2
BUILD_LIST        2                 ← ro'yxat HAR SAFAR def bajarilganda quriladi
BUILD_TUPLE       2                 ← sukutlar korteji
LOAD_CONST        <code object ichki>
MAKE_FUNCTION
SET_FUNCTION_ATTRIBUTE 1 (defaults) ← kortej funksiyaning __defaults__ iga yoziladi
STORE_FAST        ichki

Uch fakt:

  1. Sukut ifodalari def qatori bajarilganda hisoblanadi — funksiya chaqirilganda emas
  2. Natija funksiya obyektining __defaults__ (pozitsion) va __kwdefaults__ (faqat nomli) atributlarida havola sifatida saqlanadi
  3. Funksiya chaqirilganda sukut ifodasi qayta bajarilmaydi — __defaults__ dagi obyekt parametrga bog'lanadi

2.2. Qachon "bir marta" va qachon "har safar"

def qayerda def necha marta bajariladi Sukut obyekt
Modul darajasida Import paytida bir marta Bitta — dastur yashagan paytgacha
Klass tanasida (metod) Klass yaratilganda bir marta Bitta — barcha nusxalar uchun
Boshqa funksiya ichida Tashqi funksiya har chaqirilganda Har yangi ichki funksiya — yangi sukut
Sikl ichida Har iteratsiyada Har funksiya — o'z sukuti
lambda x=[]: ... lambda ifodasi bajarilganda Xuddi def kabi

Sikl ichidagi def ning sukuti alohida bo'lsa ham, funksiya tanasidagi tashqi o'zgaruvchi (i) kechikkan bog'lanishga uchraydi (10.12-dars) — ikki mexanizmni chalkashtirmang.

2.3. Tuzoqning boshqa shakllari

Mexanizm bir xil: bir marta yaratilgan o'zgaruvchan obyekt ko'p joyga bog'lanadi.

Shakl Kod Kim bo'lishadi
__init__ sukuti def __init__(self, teglar=[]): self.teglar = teglar Sukut bilan yaratilgan barcha nusxalar
Klass atributi class K: teglar = [] Barcha nusxalar (11.3-dars)
dataclass maydoni savat: Savat = Savat() Barcha nusxalar — list/dict/set dan tashqari turlar tekshirilmaydi
NamedTuple maydoni teglar: list = [] Barcha nusxalar — hech qanday tekshiruv yo'q
functools.partial partial(f, []) Shu partial ning barcha chaqiruvlari
Modul konstantasi STANDART_TEGLAR = ["yangi"] qaytarilib, o'zgartirilsa Butun dastur
dict.fromkeys(k, []) Barcha kalitlar (11.3-dars)

dataclass qoidasi (3.11+): sukut qiymat xeshlanmaydigan bo'lsa (__hash__ None) — ValueError: mutable default ... use default_factory. Oddiy klass nusxasining xeshi bor — tekshiruvdan o'tadi.

Yechimlar:

  • dataclass — field(default_factory=Savat)
  • NamedTuple — o'zgarmas sukut ((), frozenset()), yoki None + fabrika metodi
  • __init__ — teglar=None → self.teglar = list(teglar) if teglar is not None else []
  • partial — o'zgaruvchan argumentni bog'lamang yoki har chaqiruvda yangi yarating

2.4. Sukut qiymat — yashirin havola

__defaults__ dagi obyekt funksiya yashagan paytgacha tirik (11.4-dars):

  • Xotira: def f(x, kesh={}) — kesh cheksiz o'sadi va hech qachon bo'shamaydi
  • Iplar: bir nechta ip bir sukut ro'yxatga yozsa — poyga holati va aralash ma'lumot (14-qism)
  • Testlar: birinchi test sukutni o'zgartirsa, keyingi testlar "iflos" holatda boshlanadi — natija testlar tartibiga bog'liq

2.5. Funksiyani yangi sukutlar bilan klonlash

Funksiya — kod obyekti + globals + sukutlar + closure. Yangi sukutlar bilan nusxa:

python
import types
toza = types.FunctionType(f.__code__, f.__globals__, f.__name__,
                          f.__defaults__ and tuple(copy.deepcopy(f.__defaults__)),
                          f.__closure__)
toza.__kwdefaults__ = copy.deepcopy(f.__kwdefaults__)

Bu tuzatish va testlar uchun vosita (masalan, uchinchi tomon kodidagi "iflos" sukutli funksiyani testlar orasida tiklash) — ishlab chiqarish kodida sukutni to'g'ri yozish kerak.

2.6. Nega Python shunday ishlaydi

  • Soddalik va tezlik: sukut bir marta hisoblanadi, chaqiruvda qo'shimcha ish yo'q
  • Erta bog'lash foydali: lambda i=i: i — qiymatni def paytida "qotirish" (10.12-dars)
  • Izchillik: def — bajariladigan ifoda; sukut ifodasi ham shu paytda bajariladi

Kechikkan (har chaqiruvda hisoblanadigan) sukutlar uchun PEP 671 taklifi bor (def f(x=>[])), lekin u tilga qabul qilinmagan.


3. Tez ma'lumotnoma

Mexanizm

def bajarilganda → sukut ifodasi hisoblanadi → __defaults__ / __kwdefaults__
chaqiruvda → o'sha obyekt parametrga bog'lanadi
o'zgarmas konstanta → co_consts dan; ro'yxat/lug'at → BUILD_LIST/BUILD_MAP def paytida

Qachon bir marta

modul def, metod (klass tanasi)          → bitta sukut
ichki def (har tashqi chaqiruv), sikl def → har safar yangi

Boshqa shakllar

__init__(self, x=[])        dataclass: x: K = K()        NamedTuple: x: list = []
partial(f, [])              class K: x = []              dict.fromkeys(k, [])

Yechimlar

python
def f(x=None): x = [] if x is None else x
field(default_factory=Savat)
self.teglar = list(teglar) if teglar is not None else []

Tekshirish

python
f.__defaults__, f.__kwdefaults__
dataclasses.fields(K)[i].default
NamedTuple._field_defaults

4. Batafsil misollar

Misol 1 — Bytecode va hayot davri

python
"""Sukut qiymat def paytida quriladi: dis, __defaults__ identifikatsiyasi, ichki va sikldagi def, xotira o'sishi."""

import dis


def opkodlar(funksiya):
    return [i.opname for i in dis.get_instructions(funksiya)
            if i.opname in ("LOAD_CONST", "BUILD_LIST", "BUILD_MAP", "BUILD_TUPLE", "MAKE_FUNCTION",
                            "SET_FUNCTION_ATTRIBUTE", "CALL")]


def tashqi():
    def ichki(a=(1, 2), b=[1, 2], c={"k": 1}):
        return a, b, c
    return ichki


print("=== 1. def paytidagi amallar ===")
print(f"  {opkodlar(tashqi)}")
print("  o'zgarmas kortej — LOAD_CONST, ro'yxat va lug'at — har def da BUILD_LIST / BUILD_MAP")

print("\n=== 2. Modul darajasidagi funksiya: bitta sukut ===")


def qosh(qiymat, royxat=[]):
    royxat.append(qiymat)
    return royxat


natijalar = [qosh(i) for i in range(3)]
print(f"  natijalar: {natijalar}")
print(f"  hammasi bitta obyekt: {all(n is qosh.__defaults__[0] for n in natijalar)}")

print("\n=== 3. Ichki def: har tashqi chaqiruv — yangi sukut ===")
f1, f2 = tashqi(), tashqi()
f1()[1].append(99)
print(f"  f1 sukuti o'zgartirildi → f1: {f1()[1]}, f2: {f2()[1]}, bitta obyekt: {f1.__defaults__[1] is f2.__defaults__[1]}")
print(f"  o'zgarmas kortej esa bitta konstanta: {f1.__defaults__[0] is f2.__defaults__[0]}")

print("\n=== 4. Sikldagi def: alohida sukut, lekin kechikkan i ===")
funksiyalar = []
for i in range(3):
    def yozuv(x=[]):
        x.append(i)
        return x
    funksiyalar.append(yozuv)
print(f"  sukutlar alohida: {funksiyalar[0].__defaults__[0] is not funksiyalar[1].__defaults__[0]}")
print(f"  natijalar: {[f() for f in funksiyalar]}   ⚠️ i — global, chaqiruv paytidagi qiymat")
funksiyalar2 = []
for i in range(3):
    def yozuv2(x=None, i=i):
        x = [] if x is None else x
        x.append(i)
        return x
    funksiyalar2.append(yozuv2)
print(f"  ✅ i=i va x=None: {[f() for f in funksiyalar2]}")

print("\n=== 5. Sukut — yashirin havola: kesh cheksiz o'sadi ===")


def narx_ol(mahsulot, _kesh={}):
    if mahsulot not in _kesh:
        _kesh[mahsulot] = len(mahsulot) * 1000
    return _kesh[mahsulot]


for i in range(10_000):
    narx_ol(f"mahsulot-{i}")
print(f"  10 000 noyob so'rovdan keyin sukut lug'atida: {len(narx_ol.__defaults__[0])} yozuv — hech qachon bo'shamaydi")

Natijaning muhim qismi:

text
=== 1. def paytidagi amallar ===
  ['LOAD_CONST', 'BUILD_LIST', 'LOAD_CONST', 'BUILD_MAP', 'BUILD_TUPLE', 'LOAD_CONST', 'MAKE_FUNCTION', 'SET_FUNCTION_ATTRIBUTE']
  o'zgarmas kortej — LOAD_CONST, ro'yxat va lug'at — har def da BUILD_LIST / BUILD_MAP

=== 2. Modul darajasidagi funksiya: bitta sukut ===
  natijalar: [[0, 1, 2], [0, 1, 2], [0, 1, 2]]
  hammasi bitta obyekt: True

=== 3. Ichki def: har tashqi chaqiruv — yangi sukut ===
  f1 sukuti o'zgartirildi → f1: [1, 2, 99], f2: [1, 2], bitta obyekt: False
  o'zgarmas kortej esa bitta konstanta: True

=== 4. Sikldagi def: alohida sukut, lekin kechikkan i ===
  sukutlar alohida: True
  natijalar: [[2], [2], [2]]   ⚠️ i — global, chaqiruv paytidagi qiymat
  ✅ i=i va x=None: [[0], [1], [2]]

=== 5. Sukut — yashirin havola: kesh cheksiz o'sadi ===
  10 000 noyob so'rovdan keyin sukut lug'atida: 10000 yozuv — hech qachon bo'shamaydi

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

Misol 2 — Tuzoqning boshqa shakllari

python
"""__init__, dataclass (tekshiruvdan o'tadigan maxsus klass), NamedTuple, partial, modul konstantasi."""

import functools
from dataclasses import dataclass, field
from typing import NamedTuple


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

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


print("=== 1. __init__ sukuti ===")


class Mijoz:
    def __init__(self, ism, teglar=[]):
        self.ism = ism
        self.teglar = teglar


a, b = Mijoz("Aziz"), Mijoz("Dilnoza")
a.teglar.append("VIP")
print(f"  Dilnoza teglari: {b.teglar}, bitta ro'yxat: {a.teglar is b.teglar}")


class MijozToza:
    def __init__(self, ism, teglar=None):
        self.ism = ism
        self.teglar = list(teglar) if teglar is not None else []


a, b = MijozToza("Aziz"), MijozToza("Dilnoza")
a.teglar.append("VIP")
print(f"  ✅ None + list(): Dilnoza teglari: {b.teglar}")

print("\n=== 2. dataclass: list taqiqlanadi, maxsus klass — yo'q ===")
try:
    @dataclass
    class Buyurtma1:
        raqam: int
        savat: list = []
except ValueError as e:
    print(f"  list sukut: ValueError: {e}")


@dataclass
class Buyurtma2:
    raqam: int
    savat: Savat = Savat()


b1, b2 = Buyurtma2(1), Buyurtma2(2)
b1.savat.qosh("non")
print(f"  ⚠️ Savat() sukut: b2 savati {b2.savat.mahsulotlar}, bitta obyekt: {b1.savat is b2.savat}")
print(f"     Savat xeshlanadimi: {Savat.__hash__ is not None} — tekshiruv shu bo'yicha")


@dataclass
class Buyurtma3:
    raqam: int
    savat: Savat = field(default_factory=Savat)


b1, b2 = Buyurtma3(1), Buyurtma3(2)
b1.savat.qosh("non")
print(f"  ✅ default_factory: b2 savati {b2.savat.mahsulotlar}")

print("\n=== 3. NamedTuple — tekshiruv umuman yo'q ===")


class Nuqta(NamedTuple):
    x: int
    teglar: list = []


p1, p2 = Nuqta(1), Nuqta(2)
p1.teglar.append("boshlang'ich")
print(f"  p2.teglar: {p2.teglar}, _field_defaults: {Nuqta._field_defaults}")

print("\n=== 4. functools.partial ===")


def yozuvga_qosh(qiymat, jurnal):
    jurnal.append(qiymat)
    return jurnal


tizim_jurnali = functools.partial(yozuvga_qosh, jurnal=[])
tizim_jurnali("ishga tushdi")
tizim_jurnali("ulanish")
print(f"  partial ning barcha chaqiruvlari bitta ro'yxatga: {tizim_jurnali.keywords['jurnal']}")

print("\n=== 5. Modul konstantasini qaytarish ===")
STANDART_RUXSATLAR = ["o'qish"]


def ruxsatlar(rol):
    if rol == "mehmon":
        return STANDART_RUXSATLAR
    return STANDART_RUXSATLAR + ["yozish"]


ruxsatlar("mehmon").append("o'chirish")
print(f"  mehmon ruxsatlari endi: {ruxsatlar('mehmon')}, konstanta: {STANDART_RUXSATLAR}")
STANDART_RUXSATLAR_TOZA = ("o'qish",)
print(f"  ✅ tuple konstanta: {STANDART_RUXSATLAR_TOZA} — append imkonsiz")

Natijaning muhim qismi:

text
=== 1. __init__ sukuti ===
  Dilnoza teglari: ['VIP'], bitta ro'yxat: True
  ✅ None + list(): Dilnoza teglari: []

=== 2. dataclass: list taqiqlanadi, maxsus klass — yo'q ===
  list sukut: ValueError: mutable default <class 'list'> for field savat is not allowed: use default_factory
  ⚠️ Savat() sukut: b2 savati ['non'], bitta obyekt: True
     Savat xeshlanadimi: True — tekshiruv shu bo'yicha
  ✅ default_factory: b2 savati []

=== 3. NamedTuple — tekshiruv umuman yo'q ===
  p2.teglar: ["boshlang'ich"], _field_defaults: {'teglar': ["boshlang'ich"]}

=== 4. functools.partial ===
  partial ning barcha chaqiruvlari bitta ro'yxatga: ['ishga tushdi', 'ulanish']

=== 5. Modul konstantasini qaytarish ===
  mehmon ruxsatlari endi: ["o'qish", "o'chirish"], konstanta: ["o'qish", "o'chirish"]
  ✅ tuple konstanta: ("o'qish",) — append imkonsiz

Nima ko'rsatdi: 2.3-bo'lim. dataclass faqat xeshlanmaydigan sukutlarni ushlaydi — oddiy klass nusxasi object dan xesh meros olgani uchun tekshiruvdan o'tdi.

Misol 3 — O'zgaruvchan sukutlarni avtomatik topish

python
"""Ish vaqtida (inspect, dataclasses, NamedTuple) va statik (ast) tekshiruv: har ikki usul nimani topadi va nimani o'tkazib yuboradi."""

import ast
import dataclasses
import functools
import inspect
import textwrap
import types
from typing import NamedTuple

OZGARMAS = (int, float, complex, str, bytes, bool, type(None), tuple, frozenset, type, types.FunctionType, range)


def ozgaruvchanmi(qiymat):
    if isinstance(qiymat, OZGARMAS):
        return isinstance(qiymat, tuple) and any(ozgaruvchanmi(x) for x in qiymat)
    return True


def ish_vaqtida_tekshir(nomlar_maydoni, modul_nomi):
    topilgan = []
    for nom, obj in nomlar_maydoni.items():
        if not isinstance(obj, functools.partial) and getattr(obj, "__module__", None) != modul_nomi:
            continue                                   # import qilingan narsalar — boshqa modulniki
        funksiyalar = []
        if inspect.isfunction(obj):
            funksiyalar.append((nom, obj))
        elif inspect.isclass(obj):
            if dataclasses.is_dataclass(obj):
                for f in dataclasses.fields(obj):
                    if f.default is not dataclasses.MISSING and ozgaruvchanmi(f.default):
                        topilgan.append(f"{nom}.{f.name}: dataclass sukuti {type(f.default).__name__}")
            if issubclass(obj, tuple) and hasattr(obj, "_field_defaults"):
                for maydon, qiymat in obj._field_defaults.items():
                    if ozgaruvchanmi(qiymat):
                        topilgan.append(f"{nom}.{maydon}: NamedTuple sukuti {type(qiymat).__name__}")
            for mnom, m in vars(obj).items():
                if inspect.isfunction(m):
                    if not (dataclasses.is_dataclass(obj) and mnom == "__init__"):   # maydonlar yuqorida
                        funksiyalar.append((f"{nom}.{mnom}", m))
                elif not mnom.startswith("_") and not callable(m) and not hasattr(type(m), "__get__") \
                        and ozgaruvchanmi(m) and not dataclasses.is_dataclass(obj):
                    topilgan.append(f"{nom}.{mnom}: klass atributi {type(m).__name__}")
        elif isinstance(obj, functools.partial):
            for q in list(obj.args) + list(obj.keywords.values()):
                if ozgaruvchanmi(q):
                    topilgan.append(f"{nom}: partial argumenti {type(q).__name__}")
        for fnom, f in funksiyalar:
            imzo = inspect.signature(f)
            for p in imzo.parameters.values():
                if p.default is not p.empty and ozgaruvchanmi(p.default):
                    topilgan.append(f"{fnom}({p.name}=...): sukut {type(p.default).__name__}")
    return topilgan


MANBA = textwrap.dedent('''
    from dataclasses import dataclass, field
    from typing import NamedTuple
    import functools

    class Savat:
        def __init__(self):
            self.m = []

    def qosh(x, royxat=[]):
        royxat.append(x)

    def toza(x, royxat=None, *, teglar=()):
        return x

    def nomli(*, sozlama={}):
        return sozlama

    class Mijoz:
        standart_teglar = ["yangi"]
        def __init__(self, ism, teglar=set()):
            self.teglar = teglar

    @dataclass
    class Buyurtma:
        raqam: int
        savat: Savat = Savat()
        teglar: list = field(default_factory=list)

    class Nuqta(NamedTuple):
        x: int
        izohlar: list = []

    jurnal = functools.partial(print, [])
''')

print("=== 1. Ish vaqtida (kod bajarilgach) ===")
nomlar = {"__name__": "manba"}
exec(MANBA, nomlar)
for qator in ish_vaqtida_tekshir({k: v for k, v in nomlar.items() if not k.startswith("__")}, "manba"):
    print(f"  {qator}")


class StatikTekshiruvchi(ast.NodeVisitor):
    OZGARUVCHAN_TUGUNLAR = (ast.List, ast.Dict, ast.Set, ast.ListComp, ast.DictComp, ast.SetComp)
    OZGARUVCHAN_CHAQIRUVLAR = {"list", "dict", "set", "defaultdict", "deque", "bytearray"}

    def __init__(self):
        self.topilgan = []

    def _xavfli(self, tugun):
        if isinstance(tugun, self.OZGARUVCHAN_TUGUNLAR):
            return True
        return isinstance(tugun, ast.Call) and isinstance(tugun.func, ast.Name) \
            and tugun.func.id in self.OZGARUVCHAN_CHAQIRUVLAR

    def visit_FunctionDef(self, tugun):
        for sukut in tugun.args.defaults + [d for d in tugun.args.kw_defaults if d is not None]:
            if self._xavfli(sukut):
                self.topilgan.append(f"{tugun.lineno}-qator {tugun.name}(): sukut {ast.unparse(sukut)}")
        self.generic_visit(tugun)

    def visit_ClassDef(self, tugun):
        for element in tugun.body:
            qiymat = getattr(element, "value", None)
            if isinstance(element, (ast.Assign, ast.AnnAssign)) and qiymat is not None and self._xavfli(qiymat):
                self.topilgan.append(f"{element.lineno}-qator {tugun.name}: klass darajasida {ast.unparse(qiymat)}")
        self.generic_visit(tugun)


print("\n=== 2. Statik (ast, kod bajarilmasdan) ===")
t = StatikTekshiruvchi()
t.visit(ast.parse(MANBA))
for qator in t.topilgan:
    print(f"  {qator}")

print("\n=== 3. Solishtirish ===")
print("  ish vaqtida: Savat() dataclass sukutini va partial argumentini topdi (obyekt turini biladi)")
print("  statik: Savat() ni ko'rmadi (qaysi klass o'zgaruvchanligini bilmaydi), lekin kodni bajarmaydi")

Natijaning muhim qismi:

text
=== 1. Ish vaqtida (kod bajarilgach) ===
  qosh(royxat=...): sukut list
  nomli(sozlama=...): sukut dict
  Mijoz.standart_teglar: klass atributi list
  Mijoz.__init__(teglar=...): sukut set
  Buyurtma.savat: dataclass sukuti Savat
  Nuqta.izohlar: NamedTuple sukuti list
  jurnal: partial argumenti list

=== 2. Statik (ast, kod bajarilmasdan) ===
  10-qator qosh(): sukut []
  16-qator nomli(): sukut {}
  20-qator Mijoz: klass darajasida ['yangi']
  21-qator __init__(): sukut set()
  32-qator Nuqta: klass darajasida []

=== 3. Solishtirish ===
  ish vaqtida: Savat() dataclass sukutini va partial argumentini topdi (obyekt turini biladi)
  statik: Savat() ni ko'rmadi (qaysi klass o'zgaruvchanligini bilmaydi), lekin kodni bajarmaydi

Nima ko'rsatdi: 2.3-bo'lim. Ikki usul bir-birini to'ldiradi: statik tahlil (ruff ning B006, B008, RUF012 qoidalari ham shunday) importsiz ishlaydi, ish vaqtidagi tekshiruv esa haqiqiy obyektlarni ko'radi.

Misol 4 — Amaliy: sukutdagi holat, testlar va to'g'ri muqobillar

Uchinchi tomon kutubxonasidagi funksiya o'zgaruvchan sukutda kesh saqlaydi va testlar bir-biriga ta'sir qiladi. Uni testlar orasida qanday tiklash va o'z kodimizda qaysi muqobil to'g'riligini ko'ramiz.

python
"""Sukutdagi kesh testlarni bog'laydi; types.FunctionType bilan toza klon; muqobillar: lru_cache, klass, closure."""

import copy
import types
from functools import lru_cache


def kurs_ol(valyuta, _kesh={}, _sorovlar=[]):
    """'Uchinchi tomon' funksiyasi: tarmoq so'rovlarini sukutdagi keshda saqlaydi."""
    if valyuta not in _kesh:
        _sorovlar.append(valyuta)
        _kesh[valyuta] = {"USD": 12_600, "EUR": 13_700}.get(valyuta, 0)
    return _kesh[valyuta]


def test_bitta_sorov(f):
    f("USD")
    f("USD")
    sorovlar = f.__defaults__[1]
    return "✅" if sorovlar == ["USD"] else f"❌ so'rovlar: {sorovlar}"


def test_ikki_valyuta(f):
    f("USD")
    f("EUR")
    sorovlar = f.__defaults__[1]
    return "✅" if sorovlar == ["USD", "EUR"] else f"❌ so'rovlar: {sorovlar}"


def toza_nusxa(f):
    yangi = types.FunctionType(f.__code__, f.__globals__, f.__name__,
                               copy.deepcopy(f.__defaults__), f.__closure__)
    yangi.__kwdefaults__ = copy.deepcopy(f.__kwdefaults__)
    yangi.__doc__ = f.__doc__
    return yangi


TOZA_HOLAT = toza_nusxa(kurs_ol)                   # hali hech kim chaqirmagan paytdagi nusxa

print("=== 1. ⚠️ Testlar tartibga bog'liq ===")
print(f"  test_ikki_valyuta: {test_ikki_valyuta(kurs_ol)}")
print(f"  test_bitta_sorov:  {test_bitta_sorov(kurs_ol)}   (oldingi test sukutdagi ro'yxatni to'ldirib qo'ygan)")

print("\n=== 2. ✅ Har test uchun toza klon ===")
print(f"  test_ikki_valyuta: {test_ikki_valyuta(toza_nusxa(TOZA_HOLAT))}")
print(f"  test_bitta_sorov:  {test_bitta_sorov(toza_nusxa(TOZA_HOLAT))}")
print(f"  asl funksiya hali ham iflos: {kurs_ol.__defaults__[1]}")

print("\n=== 3. O'z kodimizda — ochiq muqobillar ===")
tarmoq = []


@lru_cache(maxsize=128)
def kurs_lru(valyuta):
    tarmoq.append(valyuta)
    return {"USD": 12_600, "EUR": 13_700}.get(valyuta, 0)


class KursXizmati:
    def __init__(self, manba):
        self._manba = manba
        self._kesh = {}
        self.sorovlar = []

    def ol(self, valyuta):
        if valyuta not in self._kesh:
            self.sorovlar.append(valyuta)
            self._kesh[valyuta] = self._manba(valyuta)
        return self._kesh[valyuta]


def kurs_yasa(manba):
    kesh = {}

    def ol(valyuta):
        if valyuta not in kesh:
            kesh[valyuta] = manba(valyuta)
        return kesh[valyuta]

    ol.tozala = kesh.clear
    return ol


manba = {"USD": 12_600, "EUR": 13_700}.get
for v in ["USD", "USD", "EUR"]:
    kurs_lru(v)
print(f"  lru_cache: so'rovlar {tarmoq}, {kurs_lru.cache_info()}; testda cache_clear()")
xizmat = KursXizmati(manba)
for v in ["USD", "USD", "EUR"]:
    xizmat.ol(v)
print(f"  klass: so'rovlar {xizmat.sorovlar}; testda yangi KursXizmati(soxta_manba)")
ol = kurs_yasa(manba)
print(f"  closure: {ol('USD')}, {ol('USD')}; testda yangi kurs_yasa() yoki ol.tozala()")

print("\n=== 4. Sukut qiymatni ataylab ishlatish — erta bog'lash ===")
tugmalar = [lambda raqam=raqam: f"tugma {raqam}" for raqam in range(3)]
print(f"  {[t() for t in tugmalar]}   (o'zgarmas int — xavfsiz)")

Natijaning muhim qismi:

text
=== 1. ⚠️ Testlar tartibga bog'liq ===
  test_ikki_valyuta: ✅
  test_bitta_sorov:  ❌ so'rovlar: ['USD', 'EUR']   (oldingi test sukutdagi ro'yxatni to'ldirib qo'ygan)

=== 2. ✅ Har test uchun toza klon ===
  test_ikki_valyuta: ✅
  test_bitta_sorov:  ✅
  asl funksiya hali ham iflos: ['USD', 'EUR']

=== 3. O'z kodimizda — ochiq muqobillar ===
  lru_cache: so'rovlar ['USD', 'EUR'], CacheInfo(hits=1, misses=2, maxsize=128, currsize=2); testda cache_clear()
  klass: so'rovlar ['USD', 'EUR']; testda yangi KursXizmati(soxta_manba)
  closure: 12600, 12600; testda yangi kurs_yasa() yoki ol.tozala()

=== 4. Sukut qiymatni ataylab ishlatish — erta bog'lash ===
  ['tugma 0', 'tugma 1', 'tugma 2']   (o'zgarmas int — xavfsiz)

Nima ko'rsatdi: 2.4, 2.5-bo'limlar — sukutdagi holat testlarni tartibga bog'laydi; klon bilan izolyatsiya mumkin, lekin o'z kodimizda holatni lru_cache, klass yoki closure'da ochiq saqlash to'g'ri yo'l.


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

Noto'g'ri fikr To'g'risi
"Sukut qiymat har chaqiruvda hisoblanadi" def bajarilganda bir marta — __defaults__ da
"Tuzoq faqat def f(x=[]) da" __init__, dataclass, NamedTuple, partial, klass atributlari — hammasida
"dataclass o'zgaruvchan sukutlarni to'liq taqiqlaydi" Faqat xeshlanmaydiganlarni (list, dict, set)
"NamedTuple o'zgarmas — sukuti ham xavfsiz" Kortej o'zgarmas, ichidagi ro'yxat esa yo'q
"Ichki funksiyadagi sukut ham bitta" Har tashqi chaqiruv yangi funksiya va yangi sukut
"Sikldagi def lar bir xil sukutni bo'lishadi" Har biri o'ziniki; lekin tanadagi tashqi o'zgaruvchi kechikkan
"Sukutdagi kesh — zararsiz optimallash" Cheksiz o'sadi, testlarni va iplarni bog'laydi
"O'zgarmas sukut ((), 0, None) ham xavfli" Yo'q — xavf faqat o'zgaruvchan obyektda

6. Keng tarqalgan xatolar va yechimlari

1. __init__ da o'zgaruvchan sukutni to'g'ridan-to'g'ri saqlash

python
def __init__(self, teglar=[]):
    self.teglar = teglar                        # ❌

def __init__(self, teglar=None):
    self.teglar = list(teglar) if teglar is not None else []   # ✅ nusxa ham

2. dataclass da maxsus klass nusxasi sukut

python
@dataclass
class Buyurtma:
    savat: Savat = Savat()                      # ❌ tekshiruvdan o'tadi, lekin bo'lishiladi

    savat: Savat = field(default_factory=Savat) # ✅

3. NamedTuple da ro'yxat sukut

python
class Nuqta(NamedTuple):
    teglar: list = []                           # ❌

    teglar: tuple = ()                          # ✅

4. partial ga o'zgaruvchan argument

python
jurnalga = partial(qosh, jurnal=[])             # ❌ hamma chaqiruv bitta ro'yxatga

jurnal = []                                     # ✅ aniq nom — egalik ko'rinib turadi
jurnalga = partial(qosh, jurnal=jurnal)

5. Modul konstantasini to'g'ridan-to'g'ri qaytarish

python
STANDART = ["o'qish"]
def ruxsatlar():
    return STANDART                             # ❌ chaqiruvchi o'zgartiradi

STANDART = ("o'qish",)                          # ✅ yoki return list(STANDART)

6. Sukutda vaqt yoki tasodif

python
def yozuv(matn, vaqt=datetime.now()):           # ❌ import paytidagi vaqt (7.3-dars)

def yozuv(matn, vaqt=None):
    vaqt = vaqt or datetime.now()               # ✅

7. Sukutdagi kesh

python
def narx(m, _kesh={}): ...                      # ❌ cheksiz o'sish, testlar bog'lanadi

@lru_cache(maxsize=1024)                        # ✅ chegarali va tozalanadigan
def narx(m): ...

8. None sukut va or bilan bo'sh qiymatni yo'qotish

python
def f(royxat=None):
    royxat = royxat or []                       # ❌ chaqiruvchining bo'sh ro'yxati [] yangisi bilan almashadi

    royxat = [] if royxat is None else royxat   # ✅ bo'sh ro'yxat — haqiqiy argument

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 7.3-dars (o'tilgan): tuzoqning asosiy mexanizmi va yechimlari
  • 8.17-dars (o'tilgan): dataclass, field(default_factory=...)
  • 10.12-dars (o'tilgan): erta bog'lash — lambda i=i ning foydali tomoni
  • 11.3–11.4-darslar (o'tilgan): taxalluslar va yashirin havolalar
  • 12-qism: MAKE_FUNCTION, SET_FUNCTION_ATTRIBUTE va kod obyektlari
  • 13-qism: pydantic modellari — sukutlarni har nusxa uchun nusxalaydi
  • 14-qism: iplar orasida umumiy o'zgaruvchan sukut — poyga holati
  • 17-qism: testlar izolyatsiyasi; B006, B008 qoidalari
  • 18-qism: ruff bilan avtomatik tekshiruv

8. Eng yaxshi amaliyotlar

  1. Sukut qiymatlar o'zgarmas bo'lsin — None, (), frozenset(), sonlar, satrlar.

  2. dataclass da o'zgaruvchan maydonlar uchun har doim default_factory — list bo'lmasa ham.

  3. __init__ da qabul qilingan to'plamni nusxalang.

  4. None sukutni is None bilan tekshiring, or bilan emas.

  5. Holatli kesh — ochiq joyda: lru_cache, klass atributi yoki closure; sukutda emas.

  6. partial va konstantalarga o'zgaruvchan obyekt bog'lamang.

  7. ruff ning B006/B008/RUF012 qoidalarini yoqing va ish vaqtidagi tekshiruvni testlarga qo'shing.

  8. Erta bog'lash kerak bo'lsa — faqat o'zgarmas qiymatlar bilan (lambda i=i).


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
from dataclasses import dataclass
from typing import NamedTuple
import functools
1.  def f(x=[]):
        x.append(1)
        return len(x)
    print(f(), f(), f([]))
2.  print(f.__defaults__)
3.  def g(n, x=None):
        x = [] if x is None else x
        x.append(n)
        return x
    print(g(1), g(2))
4.  class K:
        def __init__(self, t=[]):
            self.t = t
    a = K()
    b = K()
    a.t.append("x")
    print(b.t, a.t is b.t)
5.  class S:
        pass
    @dataclass
    class D:
        s: S = S()
    print(D().s is D().s)
6.  try:
        @dataclass
        class E:
            l: list = []
    except ValueError:
        print("ValueError")
7.  class N(NamedTuple):
        t: list = []
    N().t.append(1)
    print(N().t)
8.  fs = []
    for i in range(3):
        def h(x=[]):
            return x
        fs.append(h)
    print(fs[0]() is fs[1]())
9.  def yasa():
        def ichki(x=[]):
            x.append(1)
            return x
        return ichki
    print(yasa()(), yasa()())
10. p = functools.partial(lambda x: x.append(1) or x, [])
    print(p(), p())
11. def v(t=(1, 2)):
        return t
    print(v() is v())
12. def w(*, k=[]):
        k.append(0)
        return k
    w()
    print(w.__kwdefaults__)
Javoblar
  1. 1 2 1 — uchinchi chaqiruv o'z ro'yxatini berdi
  2. ([1, 1],) — sukut ikki marta o'zgargan
  3. [1] [2]
  4. ['x'] True
  5. True — S() bir marta yaratilgan va xeshlanadi, dataclass tekshirmadi
  6. ValueError
  7. [1] — NamedTuple sukuti bo'lishiladi
  8. False — har def o'z sukutiga ega
  9. [1] [1] — har yasa() yangi ichki funksiya
  10. [1, 1] [1, 1] — ikkala chaqiruv bitta ro'yxatni o'zgartirib, uni qaytardi; print ikkalasini bajarilgandan keyin chiqaradi
  11. True — bitta kortej konstanta
  12. {'k': [0]}

Vazifa 2: Xatolarni tuzating

python
1.  @dataclass
    class Sessiya:
        foydalanuvchi: str
        savat: Savat = Savat()
        sozlama: dict = None

2.  class Hisobot:
        def __init__(self, qatorlar=[], filtrlar={}):
            self.qatorlar = qatorlar
            self.filtrlar = filtrlar

3.  class Marshrut(NamedTuple):
        nomi: str
        bekatlar: list = []

4.  def jurnal(xabar, darajalar=["INFO"], vaqt=time.time()):
        return f"[{vaqt}] {darajalar[0]}: {xabar}"

5.  def birlashtir(asosiy, qoshimcha=None):
        qoshimcha = qoshimcha or {}
        asosiy.update(qoshimcha)
        return asosiy
Javoblar
python
1.  @dataclass
    class Sessiya:
        foydalanuvchi: str
        savat: Savat = field(default_factory=Savat)
        sozlama: dict = field(default_factory=dict)   # None va keyin tekshirish o'rniga

2.  class Hisobot:
        def __init__(self, qatorlar=None, filtrlar=None):
            self.qatorlar = list(qatorlar) if qatorlar is not None else []
            self.filtrlar = dict(filtrlar) if filtrlar is not None else {}

3.  class Marshrut(NamedTuple):
        nomi: str
        bekatlar: tuple = ()

4.  def jurnal(xabar, darajalar=("INFO",), vaqt=None):
        vaqt = time.time() if vaqt is None else vaqt
        return f"[{vaqt}] {darajalar[0]}: {xabar}"

5.  def birlashtir(asosiy, qoshimcha=None):
        natija = dict(asosiy)                         # chaqiruvchi lug'atini o'zgartirmaymiz
        if qoshimcha is not None:
            natija.update(qoshimcha)
        return natija

Vazifa 3: muzlatilgan_sukutlar dekoratori

Funksiya dekoratori yozing:

  1. Har chaqiruvda __defaults__ va __kwdefaults__ dagi o'zgaruvchan obyektlarning nusxasini parametrga bersin (asl sukutlar hech qachon o'zgarmasin)
  2. Nusxalash usuli: sayoz (copy.copy) yoki chuqur (copy.deepcopy) — parametr
  3. inspect.signature va functools.wraps saqlansin
  4. Tezlikni oddiy funksiya bilan solishtiring va qachon bu dekorator o'rinli ekanini yozing

Vazifa 4: ruff qoidasi analogi

Misol 3 dagi statik tekshiruvchini kengaytiring:

  1. field(default=[]), dict.fromkeys(..., []), partial(f, []) holatlarini topsin
  2. Sukutda funksiya chaqiruvini (datetime.now(), uuid4()) aniqlasin (B008 analogi)
  3. Loyiha papkasidagi barcha .py fayllarni aylanib, hisobotni fayl:qator formatida chiqarsin
  4. # noqa: SUKUT izohi bilan belgilangan qatorlarni o'tkazib yuborsin

Vazifa 5: Test izolyatori

Pytest fixture'iga o'xshash kontekst menejer yozing: with sukutlarni_tikla(modul):

  1. Blokka kirishda moduldagi barcha funksiya va metodlarning sukutlarini deepcopy qilib saqlasin
  2. Blokdan chiqqanda ularni tiklasin (__defaults__ va __kwdefaults__ ga yozish)
  3. dataclass maydonlari va NamedTuple._field_defaults ni ham qamrab olsin
  4. Misol 4 dagi testlarni shu menejer bilan izolyatsiya qiling

Vazifa 6: Iplar va sukut

  1. def yig(qiymat, royxat=[]) funksiyasini 8 ip × 10 000 chaqiruv bilan ishga tushiring
  2. Natijaviy ro'yxat uzunligini va aralashgan tartibni tahlil qiling
  3. Xuddi shuni har ipga alohida ro'yxat berib takrorlang
  4. Nega bu xato bitta ipli testlarda ko'rinmasligini tushuntiring

Vazifa 7: O'ylash

JavaScript, Ruby, Kotlin va C++ da sukut qiymat ifodasi har chaqiruvda (yoki har chaqiruv joyida) hisoblanadi. Python esa uni def paytida bir marta hisoblaydi. Bu tanlovning afzallik va kamchiliklari nimada? PEP 671 dagi "kechikkan sukutlar" (def f(x=>[])) taklifi qanday muammoni hal qilardi va qanday savollar tug'diradi?

Javob

Qisqa javob: Python'da def — oddiy bajariladigan ifoda, sukut ifodasi ham shu paytda bir marta hisoblanadi. Bu sodda model (funksiya obyektida tayyor qiymatlar korteji) va arzon chaqiruv beradi, qiymatni "qotirish" (erta bog'lash) imkonini ham beradi. Narxi — o'zgaruvchan obyektlar bilan klassik tuzoq va None + tekshiruvning takrorlanuvchi shabloni. Boshqa tillar sukutni chaqiruv paytida hisoblaydi — tuzoq yo'q, lekin sukut ifodasi har safar qayta bajariladi va erta bog'lash uchun boshqa usul kerak.

1. Boshqa tillar

JavaScript:

javascript
function qosh(x, royxat = []) {
    royxat.push(x);
    return royxat;
}
qosh(1);   // [1]
qosh(2);   // [2] — har chaqiruvda yangi massiv

Sukut ifodasi chaqiruv paytida hisoblanadi va hatto oldingi parametrlarga murojaat qila oladi: function f(a, b = a * 2).

Ruby: def qosh(x, royxat = []) — xuddi shunday, har chaqiruvda yangi massiv.

Kotlin: fun qosh(x: Int, royxat: MutableList<Int> = mutableListOf()) — sukut chaqiruvda hisoblanadi.

C++: void f(std::vector<int> v = {}) — sukut argument har chaqiruv joyida qo'yiladi va hisoblanadi.

2. Python tanlovining afzalliklari

Afzallik Tafsilot
Sodda model Funksiya = kod + __defaults__ korteji; hech qanday "yashirin ifoda" saqlanmaydi
Arzon chaqiruv Sukut qayta hisoblanmaydi
Erta bog'lash lambda i=i: i — sikl o'zgaruvchisini qotirish (10.12-dars)
Introspeksiya inspect.signature haqiqiy qiymatlarni ko'rsatadi, help() ularni chop etadi
Izchillik def — bajariladigan ifoda, dekoratorlar va klass tanasi bilan bir xil vaqt modeli

3. Kamchiliklari

  • O'zgaruvchan sukut tuzog'i — eng ko'p uchraydigan boshlang'ich xatolardan biri
  • x=None + if x is None: x = [] shabloni har joyda takrorlanadi
  • Sukutda vaqt, tasodif yoki boshqa parametrga bog'liq qiymatni ifodalab bo'lmaydi
  • Tuzoq dataclass, NamedTuple, partial kabi boshqa joylarda ham qaytadan paydo bo'ladi (bu dars)

4. PEP 671: kechikkan sukutlar

Taklif (muallifi Chris Angelico) yangi sintaksis qo'shadi:

python
def qosh(x, royxat=>[]):              # har chaqiruvda yangi ro'yxat
def kesish(s, boshi=0, oxiri=>len(s)):  # oldingi parametrga bog'liq

Hal qiladigan muammolar:

  • None shablonisiz yangi o'zgaruvchan sukut
  • Sukutni boshqa parametrlardan hisoblash
  • inspect.signature da haqiqiy niyat ko'rinadi (None emas)

Tug'diradigan savollar:

  • __defaults__ da nima saqlanadi — hisoblanmagan ifoda qanday ifodalanadi, help() va inspect uni qanday ko'rsatadi?
  • Ikki xil sukut semantikasi (= va =>) tilni murakkablashtiradi va o'rgatishni qiyinlashtiradi
  • Chaqiruvchi "sukutni ishlat" deb aniq aytmoqchi bo'lsa (masalan, argumentlarni uzatib yuboruvchi o'rovchilarda) — None bilan bo'lgani kabi qiymat yo'q
  • Mavjud kod bilan orqaga moslik va vositalar (tur tekshiruvchilar, IDE)

Taklif muhokama qilingan, lekin tilga qabul qilinmagan — hozircha None/sentinel shabloni va dataclass dagi default_factory asosiy yechim.

5. Amaliy xulosa

  1. Python modelini qabul qiling: sukut — def paytidagi qiymat
  2. O'zgaruvchan yoki dinamik sukut uchun None/sentinel yoki default_factory
  3. Linter qoidalari (B006, B008, RUF012) tuzoqni avtomatik ushlaydi
  4. Erta bog'lash kerak bo'lsa — faqat o'zgarmas qiymatlar bilan

6. Xulosa

  1. JavaScript, Ruby, Kotlin va C++ sukutni chaqiruvda hisoblaydi — tuzoq yo'q, lekin har chaqiruvda qo'shimcha ish
  2. Python sukutni def paytida bir marta hisoblaydi — sodda, arzon, introspeksiyaga qulay va erta bog'lashga imkon beradi
  3. Narxi — o'zgaruvchan sukut tuzog'i va None shabloni
  4. PEP 671 kechikkan sukutlarni taklif qiladi, lekin semantika va vositalar bo'yicha savollar tug'diradi va tilga qabul qilinmagan
  5. Amalda — o'zgarmas sukutlar, default_factory va linter

Nimani mustahkamlaydi: 2.1–2.6-bo'limlar.


Xulosa

Bu darsda o'zgaruvchan standart argument tuzog'ini obyekt modeli nuqtai nazaridan ko'rib chiqdik.

Eng muhim uch fikr:

  1. Sukut qiymat def bajarilganda quriladi va funksiya obyektida yashaydi. Bytecode'da o'zgarmas kortej tayyor konstanta, ro'yxat va lug'at esa har def bajarilganda BUILD_LIST/BUILD_MAP bilan quriladi va __defaults__/__kwdefaults__ ga yoziladi. Modul va klass darajasidagi funksiyalar uchun bu bir marta, ichki funksiyalar va sikldagi def lar uchun esa har safar — sikldagi tashqi o'zgaruvchining kechikkan bog'lanishi esa alohida masala.

  2. Tuzoq faqat funksiya parametrlarida emas. __init__ sukuti, klass atributi, dataclass maydoni, NamedTuple maydoni, functools.partial argumenti va qaytarilgan modul konstantasi — hammasi bir marta yaratilgan o'zgaruvchan obyektni ko'p egaga bo'lishadi. dataclass faqat xeshlanmaydigan sukutlarni ushlaydi: oddiy klass nusxasi tekshiruvdan o'tadi — default_factory ni har doim ishlating.

  3. Sukutdagi holat — yashirin, cheksiz yashovchi havola. U xotirani to'playdi, iplarni va testlarni bir-biriga bog'laydi. Testlarda funksiyani types.FunctionType bilan yangi sukutlar bilan klonlash mumkin, lekin o'z kodingizda holatni lru_cache, klass yoki closure'da ochiq saqlang va ruff qoidalari hamda inspect/ast asosidagi tekshiruvlar bilan tuzoqni avtomatik ushlang.

Keyingi darsda interning va kichik obyektlar keshini o'rganamiz: nega a is b ba'zi sonlar va satrlar uchun True, sys.intern qachon foydali, va kompilyator konstantalarni qanday birlashtiradi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
11.6-dars: O'zgaruvchan standart argument tuzog'i — IlmHamroh