IlmHamroh
Python kursi/Ilgor til imkoniyatlari11/17-dars27 daqiqa
Mundarija (22)

10.11-dars: functools.wraps va metama'lumot

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


1. Kirish va motivatsiya

Flask ilovasiga huquq tekshiruvi qo'shildi:

python
def login_kerak(funksiya):
    def orovchi(*args, **kwargs):
        if not joriy_foydalanuvchi():
            abort(401)
        return funksiya(*args, **kwargs)
    return orovchi

@app.route("/profil")
@login_kerak
def profil(): ...

@app.route("/sozlamalar")
@login_kerak
def sozlamalar(): ...

Ilova ishga tushmaydi:

AssertionError: View function mapping is overwriting an existing endpoint function: orovchi

Flask marshrut nomini funksiyaning __name__ idan oladi. Ikkala funksiya ham endi orovchi deb ataladi.

FastAPI'da boshqacha, lekin xuddi shu sababli buziladi: u endpoint parametrlarini inspect.signature orqali o'qiydi va (*args, **kwargs) ni ko'radi — so'rov parametrlari yo'qoladi. help(profil) hujjatni ko'rsatmaydi, pickle funksiyani saqlay olmaydi, jurnal va metrikalarda hamma funksiya bir xil nomda.

Yechim bitta qator:

python
def login_kerak(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        ...

Lekin wraps "sehrli tuzatish" emas. U aniq atributlarni ko'chiradi, __wrapped__ havolasini qo'shadi — va ba'zi narsalarni o'zgartirmaydi: traceback'dagi nom, jurnaldagi funcName, profilerdagi nom. Imzoni o'zgartiradigan dekoratorda esa wraps yolg'on imzo ko'rsatadi.

Bu darsda:

  • Funksiya metama'lumoti nima va uni kim ishlatadi
  • update_wrapper va wraps aynan nima qiladi
  • Nima ko'chirilmaydi: __code__, traceback, funcName
  • __wrapped__ zanjiri, inspect.unwrap, testlarda chetlab o'tish
  • Imzoni o'zgartiradigan dekorator va __signature__
  • __dict__ ko'chirish tartibi, pickle va multiprocessing

2. Nazariya — chuqur tushuntirish

2.1. Funksiya metama'lumoti

Atribut Nima Kim ishlatadi
__name__ Qisqa nom Flask endpoint, jurnallar, ro'yxatlar, repr
__qualname__ To'liq nom (Klass.metod, f.<locals>.g) pickle, repr, xato xabarlari
__module__ Modul nomi pickle, help, Sphinx
__doc__ Hujjat satri help(), IDE, Sphinx, doctest
__annotate__ / __annotations__ Tur annotatsiyalari (3.14 da kechiktirilgan, PEP 649) Tur tekshiruvchilar, FastAPI, Pydantic
__type_params__ Umumiy tur parametrlari (def f[T]) Tur tekshiruvchilar
__dict__ Ixtiyoriy atributlar Dekoratorlar, freymvorklar belgilari
imzo inspect.signature(f) FastAPI, pytest fixture'lari, click/typer, help()
__code__ Kod obyekti: co_name, co_qualname, fayl, qator Traceback, logging funcName, profiler

Oddiy o'rovchi funksiyada bularning hammasi o'rovchiniki: nomi orovchi, hujjati None, imzosi (*args, **kwargs).

2.2. update_wrapper aynan nima qiladi

python
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotate__', '__type_params__')       # Python 3.14
WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
    for nom in assigned:
        try:
            qiymat = getattr(wrapped, nom)
        except AttributeError:
            pass                                    # yo'q atribut — o'tkazib yuboriladi
        else:
            setattr(wrapper, nom, qiymat)
    for nom in updated:
        getattr(wrapper, nom).update(getattr(wrapped, nom, {}))
    wrapper.__wrapped__ = wrapped                   # ⭐ oxirida — __dict__ ustiga yozmasin
    return wrapper

wraps — shu funksiyaning dekorator shakli:

python
def wraps(wrapped, assigned=..., updated=...):
    return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)

Ya'ni @wraps(f) — argumentli dekorator (10.9-dars): wraps(f) dekorator qaytaradi, u orovchi ga qo'llanadi.

Uch nozik joy:

  1. Ko'chirish, havola emas. __name__ va boshqalar def paytida bir marta nusxalanadi. Keyin f.__doc__ o'zgartirilsa, o'rovchi bilmaydi
  2. __dict__ — update. Asl funksiyaning atributlari o'rovchiga qo'shiladi (qiymatlar umumiy obyektlar)
  3. Yo'q atributlar jim o'tkaziladi — partial obyekti yoki C funksiyani ham o'rash mumkin

2.3. wraps nimani o'zgartirmaydi

O'rovchining kod obyekti o'zgarmaydi. Shuning uchun:

Joy wraps bilan ham ko'rinadi
Traceback freymi orovchi
logging dagi %(funcName)s orovchi (agar jurnal o'rovchi ichida yozilsa)
cProfile / py-spy orovchi
f.__code__.co_qualname login_kerak.<locals>.orovchi
f.__defaults__, f.__kwdefaults__ O'rovchiniki (None)
f.__globals__ Dekorator yozilgan modulniki

Traceback'da ikkala freym ham ko'rinadi — orovchi va asl funksiya — va bu yaxshi: xato qaysi qatlamda chiqqanini bilasiz.

inspect.getsource(f) esa __wrapped__ ga ergashadi va asl funksiyaning manbasini qaytaradi.

2.4. __wrapped__ va inspect

python
@kesh
@jurnal
def hisobla(x): ...

hisobla.__wrapped__                 # jurnal o'rovchisi
hisobla.__wrapped__.__wrapped__     # asl hisobla
inspect.unwrap(hisobla)             # asl hisobla — zanjir oxirigacha
Vosita __wrapped__ ga ergashadimi
inspect.signature(f) (follow_wrapped=False bilan — yo'q)
inspect.unwrap(f) oxirigacha (stop= bilan to'xtatish mumkin)
inspect.getsource(f)
help(f) imzo uchun
f.__code__, traceback

Zanjir uziladi: o'rtadagi bitta dekorator wraps ishlatmasa, unwrap o'sha joyda to'xtaydi va imzo yana (*args, **kwargs) bo'ladi.

Testlarda chetlab o'tish:

python
def test_hisob_mantiq():
    natija = hisobla.__wrapped__(5)     # keshsiz, jurnalsiz — faqat mantiq

__wrapped__ xavfsizlik chegarasi emas: @login_kerak li funksiyani f.__wrapped__() bilan tekshiruvsiz chaqirish mumkin. Himoya o'rovchida emas, chaqiruv yo'lida bo'lishi kerak.

2.5. Imzoni o'zgartiradigan dekorator

python
def ulanish_ber(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        with pool.ol() as ulanish:
            return funksiya(ulanish, *args, **kwargs)   # birinchi argumentni o'zi beradi
    return orovchi

@ulanish_ber
def foydalanuvchi(ulanish, id): ...

inspect.signature(foydalanuvchi)     # (ulanish, id)  ❌ yolg'on — chaqiruvchi faqat id beradi

wraps asl imzoni ko'rsatadi, lekin haqiqiy chaqiruv imzosi boshqa. FastAPI kabi freymvork ulanish ni so'rov parametri deb o'ylaydi.

Yechim — __signature__:

python
imzo = inspect.signature(funksiya)
orovchi.__signature__ = imzo.replace(parameters=list(imzo.parameters.values())[1:])

inspect.signature avval __signature__ ga qaraydi va zanjir bo'ylab yurishni to'xtatadi.

2.6. __dict__ ko'chirish tartibi

python
def hisobla(funksiya):
    def orovchi(*args):
        orovchi.soni += 1
        return funksiya(*args)
    orovchi.soni = 0
    return functools.update_wrapper(orovchi, funksiya)   # ⚠️ oxirida

Agar funksiya da ham soni atributi bo'lsa (masalan, u ham shu dekorator bilan o'ralgan va allaqachon chaqirilgan bo'lsa), update_wrapper o'rovchining soni = 0 ini asl funksiyaning qiymati bilan ustiga yozadi — yangi hisoblagich noldan boshlanmaydi.

@functools.wraps(funksiya) ni def orovchi ustiga qo'ying — ko'chirish avval bo'ladi, o'z atributlaringiz keyin o'rnatiladi.

2.7. pickle va multiprocessing

pickle funksiyani nomi bo'yicha saqlaydi: __module__ + __qualname__. Yuklashda shu nom bilan moduldan qidiradi.

  • wraps siz: __qualname__ = jurnal.<locals>.orovchi → PicklingError: Can't pickle local object
  • wraps bilan: __qualname__ = hisobla → moduldagi hisobla nomi — o'sha o'rovchining o'zi →

Shuning uchun multiprocessing.Pool.map(bezatilgan_funksiya, ...) faqat wraps bilan ishlaydi.

2.8. Qachon wraps ishlatmaslik yoki moslash

  • Dekorator butunlay boshqa API qaytarsa (masalan, funksiyani property ga yoki buyruq obyektiga aylantirsa) — asl hujjat va imzo chalg'itadi
  • Faqat ba'zi atributlar kerak bo'lsa — assigned= bilan tanlang: @wraps(f, assigned=("__module__", "__name__", "__qualname__"))
  • Asl __dict__ o'rovchiga tushmasligi kerak bo'lsa — updated=()

Standart kutubxonada lru_cache, cache, singledispatch, contextmanager — hammasi update_wrapper ishlatadi.


3. Tez ma'lumotnoma

Shablon

python
import functools

def dekorator(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        return funksiya(*args, **kwargs)
    return orovchi

Ko'chiriladi (3.14)

__module__  __name__  __qualname__  __doc__  __annotate__  __type_params__
__dict__ (update)     + __wrapped__ = asl

Ko'chirilmaydi

__code__ (traceback, logging funcName, profiler)  __defaults__  __globals__

inspect

python
inspect.signature(f)                       # __signature__ → __wrapped__ bo'ylab
inspect.signature(f, follow_wrapped=False) # o'rovchining o'zi
inspect.unwrap(f)                          # zanjir oxiri
f.__signature__ = imzo.replace(parameters=...)   # imzoni o'zgartiruvchi dekoratorda

Sinf asosidagi dekorator

python
class Dek:
    def __init__(self, f):
        functools.update_wrapper(self, f)

4. Batafsil misollar

Misol 1 — Nima yo'qoladi va kim zarar ko'radi

python
"""wraps siz va wraps bilan: nom, hujjat, imzo, annotatsiya, help, pickle, nom bo'yicha ro'yxat."""

import functools
import inspect
import pickle
import pydoc


def yalang(funksiya):
    def orovchi(*args, **kwargs):
        return funksiya(*args, **kwargs)
    return orovchi


def toza(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        return funksiya(*args, **kwargs)
    return orovchi


@yalang
def narx_yalang(mahsulot: str, soni: int = 1) -> int:
    """Mahsulot narxini hisoblaydi."""
    return 12_000 * soni


@toza
def narx_toza(mahsulot: str, soni: int = 1) -> int:
    """Mahsulot narxini hisoblaydi."""
    return 12_000 * soni


print("=== 1. Atributlar ===")
for f in (narx_yalang, narx_toza):
    print(f"  __name__={f.__name__!r}, __qualname__={f.__qualname__!r}")
    print(f"    __doc__={f.__doc__!r}")
    print(f"    imzo: {inspect.signature(f)}")
    print(f"    annotatsiyalar: {f.__annotations__}")

print("\n=== 2. help() ning birinchi qatorlari ===")
for f in (narx_yalang, narx_toza):
    qatorlar = [q.strip() for q in pydoc.render_doc(f, renderer=pydoc.plaintext).splitlines()[1:] if q.strip()]
    print(f"  {qatorlar[:2]}")

print("\n=== 3. pickle — nom bo'yicha saqlash ===")
for f in (narx_yalang, narx_toza):
    try:
        qayta = pickle.loads(pickle.dumps(f))
        print(f"  {f.__name__}: ✅ saqlandi va yuklandi, o'sha obyekt: {qayta is f}")
    except pickle.PicklingError as e:
        print(f"  {f.__name__}: ❌ PicklingError: {str(e).split(' <')[0]}")

print("\n=== 4. Nom bo'yicha ro'yxat (Flask endpoint'lariga o'xshash) ===")


class Marshrutlar:
    def __init__(self):
        self.endpointlar = {}

    def qosh(self, yol, funksiya):
        nom = funksiya.__name__
        if nom in self.endpointlar and self.endpointlar[nom][1] is not funksiya:
            raise AssertionError(
                f"View function mapping is overwriting an existing endpoint function: {nom}"
            )
        self.endpointlar[nom] = (yol, funksiya)


for dekorator in (yalang, toza):
    m = Marshrutlar()

    def profil():
        return "profil"

    def sozlamalar():
        return "sozlamalar"

    try:
        m.qosh("/profil", dekorator(profil))
        m.qosh("/sozlamalar", dekorator(sozlamalar))
        print(f"  {dekorator.__name__}: ✅ {list(m.endpointlar)}")
    except AssertionError as e:
        print(f"  {dekorator.__name__}: ❌ AssertionError: {e}")

print("\n=== 5. Argumentlarni imzo bo'yicha bog'lash (FastAPI/pytest uslubi) ===")
sorov = {"mahsulot": "olma", "soni": "3"}
for f in (narx_yalang, narx_toza):
    imzo = inspect.signature(f)
    kwargs = {nom: p.annotation(sorov[nom]) for nom, p in imzo.parameters.items()
              if nom in sorov and p.annotation is not inspect.Parameter.empty}
    print(f"  {f.__name__}: imzodan olingan argumentlar {kwargs} → {f(**kwargs) if kwargs else 'parametrlar topilmadi'}")

Natijaning muhim qismi:

text
=== 1. Atributlar ===
  __name__='orovchi', __qualname__='yalang.<locals>.orovchi'
    __doc__=None
    imzo: (*args, **kwargs)
    annotatsiyalar: {}
  __name__='narx_toza', __qualname__='narx_toza'
    __doc__='Mahsulot narxini hisoblaydi.'
    imzo: (mahsulot: str, soni: int = 1) -> int
    annotatsiyalar: {'mahsulot': <class 'str'>, 'soni': <class 'int'>, 'return': <class 'int'>}

=== 2. help() ning birinchi qatorlari ===
  ['orovchi(*args, **kwargs)']
  ['narx_toza(mahsulot: str, soni: int = 1) -> int', 'Mahsulot narxini hisoblaydi.']

=== 3. pickle — nom bo'yicha saqlash ===
  orovchi: ❌ PicklingError: Can't pickle local object
  narx_toza: ✅ saqlandi va yuklandi, o'sha obyekt: True

=== 4. Nom bo'yicha ro'yxat (Flask endpoint'lariga o'xshash) ===
  yalang: ❌ AssertionError: View function mapping is overwriting an existing endpoint function: orovchi
  toza: ✅ ['profil', 'sozlamalar']

=== 5. Argumentlarni imzo bo'yicha bog'lash (FastAPI/pytest uslubi) ===
  orovchi: imzodan olingan argumentlar {} → parametrlar topilmadi
  narx_toza: imzodan olingan argumentlar {'mahsulot': 'olma', 'soni': 3} → 36000

Nima ko'rsatdi: 2.1, 2.7-bo'limlar. 5-bo'limda narx_yalang imzosi (*args, **kwargs) bo'lgani uchun so'rovdan birorta ham parametr topilmadi — imzoga tayanadigan freymvork bunday funksiyaga argument uzata olmaydi.

Misol 2 — update_wrapper ichidan va nima o'zgarmaydi

python
"""O'z update_wrapper imiz; traceback, logging funcName va kod obyekti; __dict__ tartibi tuzog'i."""

import functools
import io
import logging
import sys
import traceback


def mening_update_wrapper(wrapper, wrapped,
                          assigned=functools.WRAPPER_ASSIGNMENTS,
                          updated=functools.WRAPPER_UPDATES):
    for nom in assigned:
        try:
            qiymat = getattr(wrapped, nom)
        except AttributeError:
            pass
        else:
            setattr(wrapper, nom, qiymat)
    for nom in updated:
        getattr(wrapper, nom).update(getattr(wrapped, nom, {}))
    wrapper.__wrapped__ = wrapped
    return wrapper


def mening_wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES):
    return functools.partial(mening_update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)


print("=== 1. Asli bilan solishtirish ===")
print(f"  WRAPPER_ASSIGNMENTS: {functools.WRAPPER_ASSIGNMENTS}")
print(f"  WRAPPER_UPDATES:     {functools.WRAPPER_UPDATES}")


def hisobla[T](x: T, *, belgi: str = "") -> T:
    """Qiymatni qaytaradi."""
    return x


hisobla.versiya = "1.2"


def tekshir(wraps_funksiyasi):
    @wraps_funksiyasi(hisobla)
    def orovchi(*args, **kwargs):
        return hisobla(*args, **kwargs)
    return {nom: getattr(orovchi, nom, "YO'Q")
            for nom in ("__name__", "__qualname__", "__doc__", "__type_params__", "versiya", "__wrapped__")}


asli, bizniki = tekshir(functools.wraps), tekshir(mening_wraps)
for nom in asli:
    qiymat = asli[nom].__name__ if nom == "__wrapped__" else asli[nom]
    print(f"  {nom:16} {qiymat!r:28} bir xil: {asli[nom] == bizniki[nom]}")

print("\n=== 2. partial va C funksiyalarni ham o'rash mumkin (yo'q atributlar o'tkaziladi) ===")
ikkilik = functools.partial(int, base=2)


@functools.wraps(ikkilik)
def ikkilikdan(matn):
    return ikkilik(matn)


print(f"  partial dan: __name__={ikkilikdan.__name__!r} (partial da __name__ yo'q — o'rovchiniki qoldi), "
      f"__wrapped__ is ikkilik: {ikkilikdan.__wrapped__ is ikkilik}")


@functools.wraps(len)
def uzunlik(x):
    return len(x)


print(f"  len dan: __name__={uzunlik.__name__!r}, __module__={uzunlik.__module__!r}")

print("\n=== 3. ⭐ Kod obyekti o'zgarmaydi: traceback va logging ===")
jurnal_bufer = io.StringIO()
ishlovchi = logging.StreamHandler(jurnal_bufer)
ishlovchi.setFormatter(logging.Formatter("funcName=%(funcName)s | %(message)s"))
jurnal = logging.getLogger("misol2")
jurnal.addHandler(ishlovchi)
jurnal.setLevel(logging.INFO)
jurnal.propagate = False


def kuzat(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        jurnal.info(f"{funksiya.__name__} chaqirildi")
        return funksiya(*args, **kwargs)
    return orovchi


@kuzat
def bol(a, b):
    jurnal.info("bo'linmoqda")
    return a / b


try:
    bol(1, 0)
except ZeroDivisionError:
    freymlar = [f.name for f in traceback.extract_tb(sys.exc_info()[2])]
print(f"  bol.__name__: {bol.__name__}, bol.__code__.co_qualname: {bol.__code__.co_qualname}")
print(f"  traceback freymlari: {freymlar}")
for qator in jurnal_bufer.getvalue().splitlines():
    print(f"  jurnal: {qator}")
print(f"  __defaults__: o'rovchida {bol.__defaults__}, __kwdefaults__: {bol.__kwdefaults__}")

print("\n=== 4. ⚠️ __dict__ tartibi: update_wrapper oxirida o'z atributni yozib yuboradi ===")


def hisoblagich_oxirida(funksiya):
    def orovchi(*args):
        orovchi.soni += 1
        return funksiya(*args)
    orovchi.soni = 0
    return functools.update_wrapper(orovchi, funksiya)


def hisoblagich_boshida(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args):
        orovchi.soni += 1
        return funksiya(*args)
    orovchi.soni = 0
    return orovchi


for dek in (hisoblagich_oxirida, hisoblagich_boshida):
    def salom():
        return "salom"

    ichki = dek(salom)
    ichki(); ichki(); ichki()                   # ichki hisoblagich allaqachon 3 da
    tashqi = dek(ichki)                         # yangi hisoblagich 0 dan boshlanishi kerak
    tashqi()
    print(f"  {dek.__name__}: tashqi soni={tashqi.soni} (1 bo'lishi kerak), ichki soni={ichki.soni}")

print("\n=== 5. ⚠️ Ko'chirish — nusxa, keyingi o'zgarish yetib bormaydi; __dict__ qiymatlari esa umumiy ===")


def asl():
    """eski hujjat"""


asl.teglar = ["api"]
o = functools.wraps(asl)(lambda: None)
asl.__doc__ = "yangi hujjat"
asl.teglar.append("v2")
print(f"  o.__doc__: {o.__doc__!r}   o.teglar: {o.teglar}   bitta ro'yxat: {o.teglar is asl.teglar}")

Natijaning muhim qismi:

text
=== 1. Asli bilan solishtirish ===
  WRAPPER_ASSIGNMENTS: ('__module__', '__name__', '__qualname__', '__doc__', '__annotate__', '__type_params__')
  WRAPPER_UPDATES:     ('__dict__',)
  __name__         'hisobla'                    bir xil: True
  __qualname__     'hisobla'                    bir xil: True
  __doc__          'Qiymatni qaytaradi.'        bir xil: True
  __type_params__  (T,)                         bir xil: True
  versiya          '1.2'                        bir xil: True
  __wrapped__      'hisobla'                    bir xil: True

=== 2. partial va C funksiyalarni ham o'rash mumkin (yo'q atributlar o'tkaziladi) ===
  partial dan: __name__='ikkilikdan' (partial da __name__ yo'q — o'rovchiniki qoldi), __wrapped__ is ikkilik: True
  len dan: __name__='len', __module__='builtins'

=== 3. ⭐ Kod obyekti o'zgarmaydi: traceback va logging ===
  bol.__name__: bol, bol.__code__.co_qualname: kuzat.<locals>.orovchi
  traceback freymlari: ['<module>', 'orovchi', 'bol']
  jurnal: funcName=orovchi | bol chaqirildi
  jurnal: funcName=bol | bo'linmoqda
  __defaults__: o'rovchida None, __kwdefaults__: None

=== 4. ⚠️ __dict__ tartibi: update_wrapper oxirida o'z atributni yozib yuboradi ===
  hisoblagich_oxirida: tashqi soni=4 (1 bo'lishi kerak), ichki soni=4
  hisoblagich_boshida: tashqi soni=1 (1 bo'lishi kerak), ichki soni=4

=== 5. ⚠️ Ko'chirish — nusxa, keyingi o'zgarish yetib bormaydi; __dict__ qiymatlari esa umumiy ===
  o.__doc__: 'eski hujjat'   o.teglar: ['api', 'v2']   bitta ro'yxat: True

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

Misol 3 — __wrapped__ zanjiri va imzo

python
"""unwrap, uzilgan zanjir, testda chetlab o'tish, xavfsizlik ogohlantirishi, imzoni o'zgartiruvchi dekorator."""

import functools
import inspect


def jurnal(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        return funksiya(*args, **kwargs)
    return orovchi


def eski_uslub(funksiya):
    def orovchi(*args, **kwargs):
        return funksiya(*args, **kwargs)
    return orovchi


chaqiruvlar = {"asl": 0}


@functools.lru_cache(maxsize=None)
@jurnal
def kvadrat(n: int) -> int:
    chaqiruvlar["asl"] += 1
    return n * n


print("=== 1. Zanjir ===")
f, qadam = kvadrat, 0
while True:
    try:
        oz_imzosi = inspect.signature(f, follow_wrapped=False)
    except ValueError:
        oz_imzosi = "yo'q (C da yozilgan obyekt)"
    print(f"  {qadam}: {type(f).__name__:20} o'z imzosi: {oz_imzosi}")
    if not hasattr(f, "__wrapped__"):
        break
    f, qadam = f.__wrapped__, qadam + 1
print(f"  inspect.unwrap: {inspect.unwrap(kvadrat).__qualname__}, imzo: {inspect.signature(kvadrat)}")

print("\n=== 2. ⚠️ Bitta wraps siz dekorator zanjirni uzadi ===")


@jurnal
@eski_uslub
@jurnal
def kub(n: int) -> int:
    return n ** 3


print(f"  kub.__name__: {kub.__name__!r}   (tashqi jurnal ichidagi o'rovchi nomini ko'chirdi)")
print(f"  inspect.unwrap(kub): {inspect.unwrap(kub).__qualname__}")
print(f"  inspect.signature(kub): {inspect.signature(kub)}")

print("\n=== 3. Testda keshni chetlab o'tish ===")
kvadrat(4); kvadrat(4); kvadrat(4)
print(f"  kesh bilan 3 chaqiruv → asl funksiya {chaqiruvlar['asl']} marta ishladi")
kvadrat.__wrapped__(4)
kvadrat.__wrapped__(4)
print(f"  __wrapped__ bilan yana 2 → asl funksiya {chaqiruvlar['asl']} marta")
print(f"  kesh statistikasi: {kvadrat.cache_info()}")

print("\n=== 4. ⚠️ __wrapped__ xavfsizlik chegarasi emas ===")
JORIY = {"foydalanuvchi": "mehmon"}


def admin_kerak(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        if JORIY["foydalanuvchi"] != "admin":
            raise PermissionError("admin kerak")
        return funksiya(*args, **kwargs)
    return orovchi


@admin_kerak
def bazani_tozala():
    return "baza tozalandi"


try:
    bazani_tozala()
except PermissionError as e:
    print(f"  bazani_tozala(): ❌ {e}")
print(f"  bazani_tozala.__wrapped__(): {bazani_tozala.__wrapped__()}   ⚠️ tekshiruvsiz")

print("\n=== 5. ⚠️ Imzoni o'zgartiruvchi dekorator ===")


class Ulanish:
    def sorov(self, sql, *p):
        return f"{sql} {p}"


def ulanish_ber(yashir_imzo):
    def dekorator(funksiya):
        @functools.wraps(funksiya)
        def orovchi(*args, **kwargs):
            return funksiya(Ulanish(), *args, **kwargs)
        if yashir_imzo:
            imzo = inspect.signature(funksiya)
            orovchi.__signature__ = imzo.replace(parameters=list(imzo.parameters.values())[1:])
        return orovchi
    return dekorator


for yashir in (False, True):
    @ulanish_ber(yashir)
    def foydalanuvchi(ulanish: Ulanish, id: int, *, toliq: bool = False) -> str:
        return ulanish.sorov("SELECT * FROM users WHERE id=?", id)

    imzo = inspect.signature(foydalanuvchi)
    holat = "bor" if yashir else "yo'q"
    print(f"  __signature__ {holat}: imzo {imzo}")
    try:
        bog = imzo.bind(42)
        print(f"    imzo.bind(42) → {dict(bog.arguments)}; chaqiruv: {foydalanuvchi(42)}")
    except TypeError as e:
        print(f"    imzo.bind(42) → ❌ TypeError: {e}   (lekin foydalanuvchi(42) ishlaydi: {foydalanuvchi(42)})")
print(f"  unwrap baribir asl funksiyaga yetadi: {inspect.unwrap(foydalanuvchi).__name__}, "
      f"asl imzo: {inspect.signature(inspect.unwrap(foydalanuvchi))}")

Natijaning muhim qismi:

text
=== 1. Zanjir ===
  0: _lru_cache_wrapper   o'z imzosi: yo'q (C da yozilgan obyekt)
  1: function             o'z imzosi: (*args, **kwargs) -> int
  2: function             o'z imzosi: (n: int) -> int
  inspect.unwrap: kvadrat, imzo: (n: int) -> int

=== 2. ⚠️ Bitta wraps siz dekorator zanjirni uzadi ===
  kub.__name__: 'orovchi'   (tashqi jurnal ichidagi o'rovchi nomini ko'chirdi)
  inspect.unwrap(kub): eski_uslub.<locals>.orovchi
  inspect.signature(kub): (*args, **kwargs)

=== 3. Testda keshni chetlab o'tish ===
  kesh bilan 3 chaqiruv → asl funksiya 1 marta ishladi
  __wrapped__ bilan yana 2 → asl funksiya 3 marta
  kesh statistikasi: CacheInfo(hits=2, misses=1, maxsize=None, currsize=1)

=== 4. ⚠️ __wrapped__ xavfsizlik chegarasi emas ===
  bazani_tozala(): ❌ admin kerak
  bazani_tozala.__wrapped__(): baza tozalandi   ⚠️ tekshiruvsiz

=== 5. ⚠️ Imzoni o'zgartiruvchi dekorator ===
  __signature__ yo'q: imzo (ulanish: __main__.Ulanish, id: int, *, toliq: bool = False) -> str
    imzo.bind(42) → ❌ TypeError: missing a required argument: 'id'   (lekin foydalanuvchi(42) ishlaydi: SELECT * FROM users WHERE id=? (42,))
  __signature__ bor: imzo (id: int, *, toliq: bool = False) -> str
    imzo.bind(42) → {'id': 42}; chaqiruv: SELECT * FROM users WHERE id=? (42,)
  unwrap baribir asl funksiyaga yetadi: foydalanuvchi, asl imzo: (ulanish: __main__.Ulanish, id: int, *, toliq: bool = False) -> str

Nima ko'rsatdi: 2.4, 2.5-bo'limlar. 2-bo'limda kub.__name__ 'orovchi' bo'ldi: tashqi jurnal to'g'ri ishladi, lekin u o'z ichidagi — eski_uslub ning nomsiz o'rovchisini ko'chirdi.

Misol 4 — Amaliy: imzoga tayanadigan mini freymvork

FastAPI uslubidagi kichik ilova: endpoint funksiyasining imzosi bo'yicha so'rov parametrlari o'qiladi, turga o'giriladi va funksiyaga uzatiladi. Dekoratorlar: huquq tekshiruvi, keshlash va ulanish berish. Qaysi dekorator freymvorkni buzishini ko'ramiz.

python
"""Imzo bo'yicha argument bog'lash; wraps siz dekorator, imzoni o'zgartiruvchi dekorator va __signature__."""

import functools
import inspect


class Ilova:
    def __init__(self):
        self.yollar = {}

    def get(self, yol):
        def dekorator(funksiya):
            imzo = inspect.signature(funksiya)
            for p in imzo.parameters.values():
                if p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD):
                    raise TypeError(
                        f"{yol}: '{funksiya.__name__}' imzosida *{p.name} — parametrlarni aniqlab bo'lmaydi "
                        f"(dekoratorda functools.wraps unutilganmi?)"
                    )
            self.yollar[yol] = (funksiya, imzo)
            return funksiya
        return dekorator

    def sorov(self, yol, **parametrlar):
        funksiya, imzo = self.yollar[yol]
        argumentlar = {}
        for nom, p in imzo.parameters.items():
            if nom in parametrlar:
                tur = p.annotation if p.annotation is not p.empty else str
                try:
                    argumentlar[nom] = tur(parametrlar[nom])
                except ValueError:
                    return 422, f"{nom}: {tur.__name__} kutilgan, {parametrlar[nom]!r} keldi"
            elif p.default is p.empty:
                return 422, f"{nom}: majburiy parametr yo'q"
        try:
            return 200, funksiya(**argumentlar)
        except PermissionError as e:
            return 403, str(e)


JORIY = {"rol": "admin"}


def admin_kerak(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        if JORIY["rol"] != "admin":
            raise PermissionError("faqat admin")
        return funksiya(*args, **kwargs)
    return orovchi


def kesh_eski(funksiya):
    saqlangan = {}

    def orovchi(*args, **kwargs):
        kalit = (args, tuple(sorted(kwargs.items())))
        if kalit not in saqlangan:
            saqlangan[kalit] = funksiya(*args, **kwargs)
        return saqlangan[kalit]
    return orovchi


def kesh(funksiya):
    return functools.wraps(funksiya)(kesh_eski(funksiya))


class Baza:
    def __init__(self):
        self.mahsulotlar = {1: ("olma", 12_000), 2: ("nok", 15_000), 3: ("anor", 30_000)}


def baza_ber(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        return funksiya(Baza(), *args, **kwargs)
    imzo = inspect.signature(funksiya)
    orovchi.__signature__ = imzo.replace(parameters=list(imzo.parameters.values())[1:])
    return orovchi


def baza_ber_imzosiz(funksiya):
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        return funksiya(Baza(), *args, **kwargs)
    return orovchi


ilova = Ilova()


@ilova.get("/mahsulot")
@kesh
@baza_ber
def mahsulot(baza: Baza, id: int, valyuta: str = "UZS") -> str:
    nom, narx = baza.mahsulotlar[id]
    return f"{nom}: {narx:,} {valyuta}"


@ilova.get("/hisobot")
@admin_kerak
def hisobot(oy: int, yil: int = 2026) -> str:
    return f"{yil}-{oy:02d} hisoboti tayyor"


@ilova.get("/imzosiz")
@baza_ber_imzosiz
def imzosiz(baza: Baza, id: int) -> str:
    return baza.mahsulotlar[id][0]


print("=== 1. Ro'yxatga olingan yo'llar va imzolar ===")
for yol, (_, imzo) in ilova.yollar.items():
    print(f"  {yol:12} {imzo}")

print("\n=== 2. So'rovlar ===")
for yol, parametrlar in [
    ("/mahsulot", {"id": "2"}),
    ("/mahsulot", {"id": "3", "valyuta": "USD"}),
    ("/mahsulot", {"id": "ikki"}),
    ("/mahsulot", {}),
    ("/hisobot", {"oy": "9"}),
    ("/imzosiz", {"id": "1"}),
]:
    print(f"  GET {yol} {parametrlar} → {ilova.sorov(yol, **parametrlar)}")
JORIY["rol"] = "mehmon"
print(f"  GET /hisobot (mehmon) → {ilova.sorov('/hisobot', oy='9')}")

print("\n=== 3. ❌ wraps siz dekorator ro'yxatga olishda ushlanadi ===")
try:
    @ilova.get("/eski")
    @kesh_eski
    def eski(id: int) -> str:
        return str(id)
except TypeError as e:
    print(f"  TypeError: {e}")

print("\n=== 4. Zanjir bo'ylab ma'lumot ===")
print(f"  mahsulot.__name__={mahsulot.__name__!r}, getsource birinchi qatori: {inspect.getsource(mahsulot).splitlines()[0]!r}")
print(f"  unwrap: {inspect.unwrap(mahsulot).__name__}, "
      f"asl imzo: {inspect.signature(inspect.unwrap(mahsulot))}")

Natijaning muhim qismi:

text
=== 1. Ro'yxatga olingan yo'llar va imzolar ===
  /mahsulot    (id: int, valyuta: str = 'UZS') -> str
  /hisobot     (oy: int, yil: int = 2026) -> str
  /imzosiz     (baza: __main__.Baza, id: int) -> str

=== 2. So'rovlar ===
  GET /mahsulot {'id': '2'} → (200, 'nok: 15,000 UZS')
  GET /mahsulot {'id': '3', 'valyuta': 'USD'} → (200, 'anor: 30,000 USD')
  GET /mahsulot {'id': 'ikki'} → (422, "id: int kutilgan, 'ikki' keldi")
  GET /mahsulot {} → (422, "id: majburiy parametr yo'q")
  GET /hisobot {'oy': '9'} → (200, '2026-09 hisoboti tayyor')
  GET /imzosiz {'id': '1'} → (422, "baza: majburiy parametr yo'q")
  GET /hisobot (mehmon) → (403, 'faqat admin')

=== 3. ❌ wraps siz dekorator ro'yxatga olishda ushlanadi ===
  TypeError: /eski: 'orovchi' imzosida *args — parametrlarni aniqlab bo'lmaydi (dekoratorda functools.wraps unutilganmi?)

=== 4. Zanjir bo'ylab ma'lumot ===
  mahsulot.__name__='mahsulot', getsource birinchi qatori: '@ilova.get("/mahsulot")'
  unwrap: mahsulot, asl imzo: (baza: __main__.Baza, id: int, valyuta: str = 'UZS') -> str

Nima ko'rsatdi: 2.4, 2.5-bo'limlar — freymvork imzoga tayansa, wraps siz dekorator ro'yxatga olishda to'xtatiladi, imzoni o'zgartiruvchi dekorator esa __signature__ siz so'rov parametrini noto'g'ri talab qiladi.


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

Noto'g'ri fikr To'g'risi
"wraps o'rovchini asl funksiyaga aylantiradi" Faqat 6 ta atributni ko'chiradi, __dict__ ni yangilaydi va __wrapped__ qo'shadi
"wraps bilan traceback'da asl nom ko'rinadi" Kod obyekti o'zgarmaydi — orovchi ko'rinadi
"wraps havola saqlaydi — asl funksiya o'zgarsa, o'rovchi ham" def paytida bir marta nusxalanadi
"inspect.signature o'rovchining haqiqiy imzosini beradi" __wrapped__ bo'ylab asl imzoni beradi
"wraps imzoni har doim to'g'ri ko'rsatadi" Imzoni o'zgartiruvchi dekoratorda yolg'on — __signature__ kerak
"Bitta wraps siz dekorator zarar qilmaydi" Butun __wrapped__ zanjirini uzadi
"__wrapped__ — ichki tafsilot, uni ishlatmaslik kerak" Testlar va inspect uchun ochiq vosita; faqat xavfsizlik uchun tayanmang
"pickle funksiya kodini saqlaydi" Nomini saqlaydi — wraps siz lokal nom topilmaydi

6. Keng tarqalgan xatolar va yechimlari

1. wraps ni unutish

python
def jurnal(f):
    def orovchi(*args, **kwargs): ...        # ❌ __name__='orovchi', imzo (*args, **kwargs)
    return orovchi

def jurnal(f):
    @functools.wraps(f)                     # ✅
    def orovchi(*args, **kwargs): ...
    return orovchi

2. wraps ni qavssiz yozish

python
@functools.wraps                            # ❌ orovchi = wraps(orovchi) — endi partial obyekti
def orovchi(*args, **kwargs): ...
# f(5) → update_wrapper(5, wrapped=orovchi) → AttributeError: 'int' object has no attribute '__module__' ...

@functools.wraps(f)                         # ✅ argumentli dekorator
def orovchi(*args, **kwargs): ...

3. update_wrapper ni o'z atributlaridan keyin chaqirish

python
orovchi.soni = 0
return functools.update_wrapper(orovchi, f)  # ❌ f.soni bo'lsa — ustiga yozadi

@functools.wraps(f)                           # ✅ ko'chirish avval
def orovchi(...): ...
orovchi.soni = 0

4. Imzoni o'zgartiruvchi dekoratorda __signature__ siz

python
@functools.wraps(f)
def orovchi(*args): return f(ulanish, *args)   # ❌ imzo (ulanish, id) — yolg'on

orovchi.__signature__ = imzo.replace(parameters=list(imzo.parameters.values())[1:])  # ✅

5. Sinf asosidagi dekoratorda metama'lumotni unutish

python
class Kesh:
    def __init__(self, f):
        self.f = f                           # ❌ help(obj) — Kesh klassining hujjati

class Kesh:
    def __init__(self, f):
        functools.update_wrapper(self, f)    # ✅
        self.f = f

6. __wrapped__ ga xavfsizlikni ishonib topshirish

python
@admin_kerak
def ochir(): ...
# ❌ ochir.__wrapped__() — tekshiruvsiz

Muhim tekshiruvni chaqiruv yo'liga qo'ying (freymvork middleware, servis qatlami) va __wrapped__ ni faqat testlarda ishlating.

7. Jurnalda nom uchun funcName ga tayanish

python
logging.Formatter("%(funcName)s")          # ❌ o'rovchi ichidagi yozuvda 'orovchi'

jurnal.info("chaqirildi", extra={"funksiya": f.__qualname__})   # ✅ nomni aniq uzating

8. multiprocessing da wraps siz funksiya

python
with Pool() as p:
    p.map(bezatilgan, malumotlar)          # ❌ PicklingError: Can't pickle local object

Dekoratorda functools.wraps — va bezatilgan funksiya modul darajasida bo'lsin.


7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 7.14-dars (o'tilgan): docstring va type hints — wraps ularni saqlaydi
  • 10.8–10.10-darslar (o'tilgan): dekoratorlar, update_wrapper sinf asosidagi dekoratorda
  • 10.13-dars: lru_cache, cache — __wrapped__ va cache_info
  • 15-qism: logging — funcName, extra
  • 14, 16-qismlar: multiprocessing va pickle
  • 17-qism: pytest fixture'lari imzo bo'yicha; testda __wrapped__
  • 20, 22-qismlar: FastAPI — endpoint imzosi va bog'liqliklar; Flask endpoint nomlari

8. Eng yaxshi amaliyotlar

  1. Har o'rovchi funksiyada @functools.wraps(funksiya). Istisnosiz qoida.

  2. @wraps ni def orovchi ustiga qo'ying, update_wrapper ni oxirida emas.

  3. Sinf asosidagi dekoratorda functools.update_wrapper(self, funksiya).

  4. Imzoni o'zgartirsangiz — __signature__ ni tuzating.

  5. Testlarda __wrapped__ bilan mantiqni alohida tekshiring, lekin xavfsizlikni unga ishonib topshirmang.

  6. Traceback va jurnallarda o'rovchi freymi ko'rinishini kuting — bu normal. Nom kerak bo'lsa, aniq uzating.

  7. Freymvork uchun dekorator yozsangiz, inspect.signature bilan test qiling.

  8. Uchinchi tomon dekoratorlarini tekshiring: inspect.unwrap asl funksiyaga yetadimi — yetmasa, zanjir uzilgan.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import functools, inspect

def yalang(f):
    def w(*a, **k):
        return f(*a, **k)
    return w

def toza(f):
    @functools.wraps(f)
    def w(*a, **k):
        return f(*a, **k)
    return w

def asl(x, y=2):
    """Qo'shadi."""
    return x + y
1.  print(yalang(asl).__name__, toza(asl).__name__)
2.  print(yalang(asl).__doc__, toza(asl).__doc__)
3.  print(inspect.signature(toza(asl)))
4.  print(inspect.signature(toza(asl), follow_wrapped=False))
5.  t = toza(asl)
    print(t.__wrapped__ is asl, hasattr(yalang(asl), "__wrapped__"))
6.  t = toza(yalang(asl))
    print(t.__name__, inspect.signature(t))
7.  t = yalang(toza(asl))
    print(t.__name__, hasattr(t, "__wrapped__"))
8.  t = toza(toza(asl))
    print(inspect.unwrap(t) is asl, t.__wrapped__ is asl)
9.  t = toza(asl)
    print(t.__code__.co_name)
10. asl.teg = "api"
    t = toza(asl)
    print(t.teg)
11. t = toza(asl)
    asl.__doc__ = "Yangi."
    print(t.__doc__)
12. t = toza(asl)
    print(t.__qualname__, t(1))
Javoblar
  1. w asl
  2. None Qo'shadi.
  3. (x, y=2) — __wrapped__ bo'ylab
  4. (*a, **k) — o'rovchining o'z parametrlari
  5. True False
  6. w (*a, **k) — ichki yalang zanjirni uzdi, tashqi toza uning nomini ko'chirdi
  7. w False — tashqi yalang hech narsa ko'chirmadi va __wrapped__ qo'shmadi; ichki toza ning __wrapped__ i ichkarida qoldi
  8. True False — t.__wrapped__ — ichki o'rovchi
  9. w — kod obyekti o'zgarmaydi
  10. api — __dict__ yangilandi
  11. Qo'shadi. — nusxa def paytida olingan
  12. asl 3

Vazifa 2: Xatolarni tuzating

python
1.  def vaqt(f):
        @functools.wraps
        def orovchi(*args, **kwargs):
            return f(*args, **kwargs)
        return orovchi

2.  def sanagich(f):
        def orovchi(*args):
            orovchi.soni += 1
            return f(*args)
        orovchi.soni = 0
        functools.update_wrapper(orovchi, f)
        return orovchi

3.  class Qayta:
        def __init__(self, f):
            self.f = f
        def __call__(self, *a):
            return self.f(*a)

4.  def joriy_foydalanuvchi_ber(f):
        @functools.wraps(f)
        def orovchi(*args, **kwargs):
            return f(sessiya.foydalanuvchi, *args, **kwargs)
        return orovchi

    @app.get("/profil")
    @joriy_foydalanuvchi_ber
    def profil(foydalanuvchi, tafsilot: bool = False): ...

5.  def test_narx():
        # narx() @login_kerak va @kesh bilan bezatilgan
        with login_qilingan("admin"):
            assert narx(5) == 60_000
            assert narx(5) == 60_000   # "keshsiz tekshirdik" deb o'ylanadi
Javoblar
python
1.  def vaqt(f):
        @functools.wraps(f)             # qavs va argument unutilgan edi
        def orovchi(*args, **kwargs):
            return f(*args, **kwargs)
        return orovchi

2.  def sanagich(f):
        @functools.wraps(f)             # ko'chirish avval — f.soni bo'lsa ham orovchi.soni = 0 saqlanadi
        def orovchi(*args):
            orovchi.soni += 1
            return f(*args)
        orovchi.soni = 0
        return orovchi

3.  class Qayta:
        def __init__(self, f):
            functools.update_wrapper(self, f)
            self.f = f
        def __call__(self, *a):
            return self.f(*a)

4.  def joriy_foydalanuvchi_ber(f):
        @functools.wraps(f)
        def orovchi(*args, **kwargs):
            return f(sessiya.foydalanuvchi, *args, **kwargs)
        imzo = inspect.signature(f)
        orovchi.__signature__ = imzo.replace(parameters=list(imzo.parameters.values())[1:])
        return orovchi

5.  def test_narx_mantiq():
        asl = inspect.unwrap(narx)      # login va keshsiz sof mantiq
        assert asl(5) == 60_000

    def test_narx_kesh():
        with login_qilingan("admin"):
            narx.cache_clear()
            narx(5); narx(5)
            assert narx.cache_info().hits == 1

Vazifa 3: @imzoni_saqla

Dekorator yozuvchilar uchun yordamchi dekorator yozing: imzoni_saqla(olib_tashla=0, qosh=()):

  1. olib_tashla — asl imzoning boshidan nechta parametr o'rovchi tomonidan beriladi
  2. qosh — o'rovchi qo'shadigan nomli parametrlar (inspect.Parameter ro'yxati), masalan vaqt_chegarasi: float = 5.0
  3. functools.wraps va to'g'ri __signature__ ni birga o'rnatsin
  4. inspect.signature(f).bind(...) bilan testlar yozing

Vazifa 4: Metama'lumot auditi

audit(modul) funksiyasini yozing:

  1. Moduldagi barcha funksiya va metodlarni ko'rib chiqsin
  2. Har biri uchun: __name__ modul atributi nomiga mosmi, inspect.unwrap qancha qatlam ochdi, zanjir uzilganmi (__code__.co_name ≠ __name__ va __wrapped__ yo'q)
  3. (*args, **kwargs) imzoli, lekin hujjatida parametrlar ta'riflangan funksiyalarni "shubhali" deb belgilasin
  4. Natijani jadval qilib chiqarsin; o'zingizning 10.8–10.10-darslardagi dekoratorlaringiz bilan sinab ko'ring

Vazifa 5: Imzoga tayanadigan CLI

Misol 4 dagi Ilova g'oyasini buyruq qatori uchun qiling:

  1. @cli.buyruq — funksiya imzosidan argumentlar yasaydi: pozitsion parametrlar — majburiy argumentlar, sukut qiymatlilari — --nom qiymat, bool — --bayroq
  2. Turga o'girish annotatsiya bo'yicha
  3. help — hujjat satri va imzodan avtomatik
  4. @vaqt_olch, @qayta_urin(3) va imzoni o'zgartiruvchi @sozlama_ber bilan birga ishlashini tekshiring

Vazifa 6: Traceback'ni tozalash

Dekorator yozing: @yashirin_qatlam. U bezatilgan funksiyada xato chiqsa:

  1. Traceback'dan o'z o'rovchi freymini olib tashlasin (e.with_traceback(tb.tb_next))
  2. Faqat o'z freymini — boshqa dekoratorlarning freymlariga tegmasin
  3. Debug rejimida (YASHIRMA=1 muhit o'zgaruvchisi) freymlar saqlansin
  4. Bu yondashuvning xavflarini yozing: qachon xatoni tuzatishni qiyinlashtiradi

Vazifa 7: O'ylash

functools.wraps ni har dekoratorda yozish kerak — bu Python'ning dizayn kamchiligimi? Nega til dekoratorlar uchun metama'lumotni avtomatik ko'chirmaydi? Boshqa imkoniyatlar (masalan, @ sintaksisi o'zi ko'chirsa, yoki funksiya "o'rovchi ekanini" e'lon qilsa) qanday muammolarga olib kelardi?

Javob

Qisqa javob: @dek — f = dek(f) ning qisqa yozuvi, va dekorator istalgan obyekt qaytarishi mumkin (10.8-dars): o'rovchi funksiya, o'sha funksiya, property, klass nusxasi, hatto boshqa turdagi qiymat. Til qaytarilgan obyekt asl funksiyaning "o'rniga" turadimi yoki umuman boshqa narsami — bilmaydi. Avtomatik ko'chirish ko'p holatda noto'g'ri bo'lardi. Shuning uchun Python bu qarorni dekorator muallifiga qoldirgan va uni bitta qatorga qisqartiruvchi standart vosita bergan.

1. Dekorator nima qaytaradi — til bilmaydi

python
@property
def narx(self): ...              # property obyekti — __name__ yo'q, __doc__ esa fget dan olinadi

@functools.lru_cache
def f(): ...                     # C obyekt — o'z atributlari, cache_info

@royxatga_ol
def g(): ...                     # o'sha funksiya — ko'chirish kerak emas

@(lambda f: f())
def SOZLAMA(): ...               # lug'at — __name__ o'rnatib bo'lmaydi

@ avtomatik ko'chirsa, property, lug'at yoki int ga __name__, __doc__ yozishga urinib, xato berar yoki jim noto'g'ri ishlar edi.

2. Avtomatik ko'chirish har doim to'g'ri emas

  • Imzoni o'zgartiruvchi dekorator (Misol 4) — asl imzoni ko'chirish yolg'on bo'lardi
  • Yangi API qaytaradigan dekorator (masalan, funksiyani buyruq obyektiga aylantiradigan click.command) — asl hujjat va nom boshqa ma'noga ega
  • __dict__ ko'chirish — asl funksiyadagi belgilar o'rovchiga tushishi har doim ham kerak emas (2.6)

Ya'ni "o'rovchi asl funksiyaning shaffof o'rinbosari" — ko'p tarqalgan, lekin yagona emas holat.

3. Nega update_wrapper va __wrapped__ shunday yo'l bilan kelgan

functools.update_wrapper va wraps Python 2.5 da (PEP 309 bilan kelgan functools modulining bir qismi sifatida) qo'shilgan. __wrapped__ atributi keyinroq — Python 3.2 da — qo'shildi va inspect.signature (PEP 362, 3.3) unga ergashadigan qilindi. Bu bosqichma-bosqich yondashuv: avval nomi va hujjat, keyin introspeksiya uchun asl funksiyaga havola, keyin imzo protokoli. Har qadam ixtiyoriy va orqaga mos bo'lib qoldi.

4. Muqobil dizaynlar va ularning muammolari

a) @ o'zi ko'chirsin. 1-bo'limdagi barcha holatlar buziladi yoki maxsus qoidalar kerak bo'ladi ("faqat funksiya qaytsa", "faqat nomi orovchi bo'lsa") — tilga yashirin sehr qo'shiladi.

b) Funksiya "o'rovchi" deb e'lon qilsin (masalan, maxsus wrapper def sintaksisi). Yangi kalit so'z, lekin baribir sinf asosidagi dekoratorlar, partial, C funksiyalar uchun alohida yo'l kerak bo'lardi. functools.wraps allaqachon shu "e'lon" vazifasini bajaradi — faqat kutubxona darajasida.

c) O'rovchi avtomatik ravishda asl funksiyaga ishonib topshirsin (proksi). Bu Java/Spring proksilariga o'xshaydi (10.8-dars, Vazifa 7) — self va identifikatsiya (is), pickle, traceback bilan yangi murakkabliklar paydo bo'ladi.

5. Python falsafasi nuqtai nazaridan

"Explicit is better than implicit" — @functools.wraps(f) qatori "bu o'rovchi f ning o'rnini egallaydi" degan aniq bayonot. Uni o'qigan odam nima bo'layotganini ko'radi; tushirib qoldirilgan bo'lsa, linterlar (masalan, flake8-bugbear kabi plaginlar yoki kod ko'rib chiqish) buni aniqlashi mumkin.

6. Amaliy yechimlar

Takrorlanishni kamaytirish uchun:

  • Dekorator shabloni — jamoa uchun bitta dekorator_yasa yordamchisi (10.9-dars, Vazifa 7)
  • Uchinchi tomon kutubxonalari — wrapt metodlar, deskriptorlar va imzolarni to'g'ri saqlaydigan dekoratorlar yasaydi
  • Testlar — freymvork uchun yozilgan dekoratorlarda inspect.signature tekshiruvi (Misol 4)

7. Xulosa

  1. Dekorator istalgan obyekt qaytarishi mumkin — til metama'lumotni qachon ko'chirish to'g'riligini bilmaydi
  2. Avtomatik ko'chirish imzoni o'zgartiruvchi va yangi API qaytaruvchi dekoratorlarda yolg'on bo'lardi
  3. update_wrapper, __wrapped__ va inspect.signature bosqichma-bosqich, ixtiyoriy protokol sifatida rivojlangan
  4. @functools.wraps(f) — "shaffof o'rinbosar" haqidagi aniq bayonot, Python'ning "aniq yashirindan yaxshi" tamoyiliga mos
  5. Takrorlanishni yordamchilar, kutubxonalar va testlar bilan kamaytirish mumkin

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda dekoratorlarda funksiya metama'lumotini saqlashni o'rgandik.

Eng muhim uch fikr:

  1. functools.wraps — aniq ro'yxat bo'yicha nusxa. U __module__, __name__, __qualname__, __doc__, __annotate__, __type_params__ ni ko'chiradi, __dict__ ni yangilaydi va __wrapped__ ni qo'shadi. Busiz Flask endpoint nomlari to'qnashadi, FastAPI va pytest imzoni ko'rmaydi, help() hujjatni yo'qotadi va pickle lokal funksiyani saqlay olmaydi. @wraps ni def orovchi ustiga qo'ying — update_wrapper oxirida chaqirilsa, o'z atributlaringizni ustiga yozadi.

  2. wraps kod obyektini o'zgartirmaydi. Traceback freymi, logging dagi funcName va profiler baribir orovchi ni ko'rsatadi. Ko'chirish def paytida bir marta bo'ladi, __defaults__ va __globals__ o'rovchiniki qoladi. inspect.signature, getsource va help esa __wrapped__ zanjiri bo'ylab asl funksiyaga yetadi — lekin bitta wraps siz dekorator zanjirni uzadi.

  3. __wrapped__ — testlar uchun vosita, imzo esa va'da. inspect.unwrap bilan kesh va huquq qatlamlarisiz mantiqni tekshirish mumkin, lekin shu sabab __wrapped__ xavfsizlik chegarasi bo'la olmaydi. Dekorator imzoni o'zgartirsa (argumentni o'zi bersa), wraps yolg'on imzo ko'rsatadi — __signature__ ni tuzating, aks holda imzoga tayanadigan freymvork noto'g'ri parametr talab qiladi.

Keyingi darsda closure'larni chuqur o'rganamiz: dekoratorlarning ichki qavatlari tashqi o'zgaruvchilarni qanday eslab qoladi, hujayra (cell) obyektlari, kechikkan bog'lanish va closure bilan klass orasidagi tanlov.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.11-dars: functools.wraps va metama'lumot — IlmHamroh