Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Funksiya metama'lumoti
- 2.2. update_wrapper aynan nima qiladi
- 2.3. wraps nimani o'zgartirmaydi
- 2.4. __wrapped__ va inspect
- 2.5. Imzoni o'zgartiradigan dekorator
- 2.6. __dict__ ko'chirish tartibi
- 2.7. pickle va multiprocessing
- 2.8. Qachon wraps ishlatmaslik yoki moslash
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Nima yo'qoladi va kim zarar ko'radi
- Misol 2 — update_wrapper ichidan va nima o'zgarmaydi
- Misol 3 — __wrapped__ zanjiri va imzo
- Misol 4 — Amaliy: imzoga tayanadigan mini freymvork
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
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:
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: orovchiFlask 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:
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_wrappervawrapsaynan 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,picklevamultiprocessing
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
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 wrapperwraps — shu funksiyaning dekorator shakli:
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:
- Ko'chirish, havola emas.
__name__va boshqalardefpaytida bir marta nusxalanadi. Keyinf.__doc__o'zgartirilsa, o'rovchi bilmaydi __dict__—update. Asl funksiyaning atributlari o'rovchiga qo'shiladi (qiymatlar umumiy obyektlar)- Yo'q atributlar jim o'tkaziladi —
partialobyekti 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
@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:
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
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 beradiwraps asl imzoni ko'rsatadi, lekin haqiqiy chaqiruv imzosi boshqa. FastAPI kabi freymvork ulanish ni so'rov parametri deb o'ylaydi.
Yechim — __signature__:
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
def hisobla(funksiya):
def orovchi(*args):
orovchi.soni += 1
return funksiya(*args)
orovchi.soni = 0
return functools.update_wrapper(orovchi, funksiya) # ⚠️ oxiridaAgar 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.
wrapssiz:__qualname__=jurnal.<locals>.orovchi→PicklingError: Can't pickle local objectwrapsbilan:__qualname__=hisobla→ moduldagihisoblanomi — 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
propertyga 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
import functools
def dekorator(funksiya):
@functools.wraps(funksiya)
def orovchi(*args, **kwargs):
return funksiya(*args, **kwargs)
return orovchiKo'chiriladi (3.14)
__module__ __name__ __qualname__ __doc__ __annotate__ __type_params__
__dict__ (update) + __wrapped__ = aslKo'chirilmaydi
__code__ (traceback, logging funcName, profiler) __defaults__ __globals__inspect
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 dekoratordaSinf asosidagi dekorator
class Dek:
def __init__(self, f):
functools.update_wrapper(self, f)4. Batafsil misollar
Misol 1 — Nima yo'qoladi va kim zarar ko'radi
"""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:
=== 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} → 36000Nima 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
"""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:
=== 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: TrueNima ko'rsatdi: 2.2, 2.3, 2.6-bo'limlar.
Misol 3 — __wrapped__ zanjiri va imzo
"""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:
=== 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) -> strNima 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.
"""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:
=== 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') -> strNima 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
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 orovchi2. wraps ni qavssiz yozish
@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
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 = 04. Imzoni o'zgartiruvchi dekoratorda __signature__ siz
@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
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 = f6. __wrapped__ ga xavfsizlikni ishonib topshirish
@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
logging.Formatter("%(funcName)s") # ❌ o'rovchi ichidagi yozuvda 'orovchi'
jurnal.info("chaqirildi", extra={"funksiya": f.__qualname__}) # ✅ nomni aniq uzating8. multiprocessing da wraps siz funksiya
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 —
wrapsularni saqlaydi - 10.8–10.10-darslar (o'tilgan): dekoratorlar,
update_wrappersinf asosidagi dekoratorda - 10.13-dars:
lru_cache,cache—__wrapped__vacache_info - 15-qism:
logging—funcName,extra - 14, 16-qismlar:
multiprocessingvapickle - 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
Har o'rovchi funksiyada
@functools.wraps(funksiya). Istisnosiz qoida.@wrapsnidef orovchiustiga qo'ying,update_wrapperni oxirida emas.Sinf asosidagi dekoratorda
functools.update_wrapper(self, funksiya).Imzoni o'zgartirsangiz —
__signature__ni tuzating.Testlarda
__wrapped__bilan mantiqni alohida tekshiring, lekin xavfsizlikni unga ishonib topshirmang.Traceback va jurnallarda o'rovchi freymi ko'rinishini kuting — bu normal. Nom kerak bo'lsa, aniq uzating.
Freymvork uchun dekorator yozsangiz,
inspect.signaturebilan test qiling.Uchinchi tomon dekoratorlarini tekshiring:
inspect.unwrapasl funksiyaga yetadimi — yetmasa, zanjir uzilgan.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
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
w aslNone Qo'shadi.(x, y=2)—__wrapped__bo'ylab(*a, **k)— o'rovchining o'z parametrlariTrue Falsew (*a, **k)— ichkiyalangzanjirni uzdi, tashqitozauning nomini ko'chirdiw False— tashqiyalanghech narsa ko'chirmadi va__wrapped__qo'shmadi; ichkitozaning__wrapped__i ichkarida qoldiTrue False—t.__wrapped__— ichki o'rovchiw— kod obyekti o'zgarmaydiapi—__dict__yangilandiQo'shadi.— nusxadefpaytida olinganasl 3
Vazifa 2: Xatolarni tuzating
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'ylanadiJavoblar
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 == 1Vazifa 3: @imzoni_saqla
Dekorator yozuvchilar uchun yordamchi dekorator yozing: imzoni_saqla(olib_tashla=0, qosh=()):
olib_tashla— asl imzoning boshidan nechta parametr o'rovchi tomonidan beriladiqosh— o'rovchi qo'shadigan nomli parametrlar (inspect.Parameterro'yxati), masalanvaqt_chegarasi: float = 5.0functools.wrapsva to'g'ri__signature__ni birga o'rnatsininspect.signature(f).bind(...)bilan testlar yozing
Vazifa 4: Metama'lumot auditi
audit(modul) funksiyasini yozing:
- Moduldagi barcha funksiya va metodlarni ko'rib chiqsin
- Har biri uchun:
__name__modul atributi nomiga mosmi,inspect.unwrapqancha qatlam ochdi, zanjir uzilganmi (__code__.co_name≠__name__va__wrapped__yo'q) (*args, **kwargs)imzoli, lekin hujjatida parametrlar ta'riflangan funksiyalarni "shubhali" deb belgilasin- 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:
@cli.buyruq— funksiya imzosidan argumentlar yasaydi: pozitsion parametrlar — majburiy argumentlar, sukut qiymatlilari —--nom qiymat,bool—--bayroq- Turga o'girish annotatsiya bo'yicha
help— hujjat satri va imzodan avtomatik@vaqt_olch,@qayta_urin(3)va imzoni o'zgartiruvchi@sozlama_berbilan birga ishlashini tekshiring
Vazifa 6: Traceback'ni tozalash
Dekorator yozing: @yashirin_qatlam. U bezatilgan funksiyada xato chiqsa:
- Traceback'dan o'z o'rovchi freymini olib tashlasin (
e.with_traceback(tb.tb_next)) - Faqat o'z freymini — boshqa dekoratorlarning freymlariga tegmasin
- Debug rejimida (
YASHIRMA=1muhit o'zgaruvchisi) freymlar saqlansin - 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
@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_yasayordamchisi (10.9-dars, Vazifa 7) - Uchinchi tomon kutubxonalari —
wraptmetodlar, deskriptorlar va imzolarni to'g'ri saqlaydigan dekoratorlar yasaydi - Testlar — freymvork uchun yozilgan dekoratorlarda
inspect.signaturetekshiruvi (Misol 4)
7. Xulosa
- Dekorator istalgan obyekt qaytarishi mumkin — til metama'lumotni qachon ko'chirish to'g'riligini bilmaydi
- Avtomatik ko'chirish imzoni o'zgartiruvchi va yangi API qaytaruvchi dekoratorlarda yolg'on bo'lardi
update_wrapper,__wrapped__vainspect.signaturebosqichma-bosqich, ixtiyoriy protokol sifatida rivojlangan@functools.wraps(f)— "shaffof o'rinbosar" haqidagi aniq bayonot, Python'ning "aniq yashirindan yaxshi" tamoyiliga mos- 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:
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 vapicklelokal funksiyani saqlay olmaydi.@wrapsnidef orovchiustiga qo'ying —update_wrapperoxirida chaqirilsa, o'z atributlaringizni ustiga yozadi.wrapskod obyektini o'zgartirmaydi. Traceback freymi,loggingdagifuncNameva profiler baribirorovchini ko'rsatadi. Ko'chirishdefpaytida bir marta bo'ladi,__defaults__va__globals__o'rovchiniki qoladi.inspect.signature,getsourcevahelpesa__wrapped__zanjiri bo'ylab asl funksiyaga yetadi — lekin bittawrapssiz dekorator zanjirni uzadi.__wrapped__— testlar uchun vosita, imzo esa va'da.inspect.unwrapbilan kesh va huquq qatlamlarisiz mantiqni tekshirish mumkin, lekin shu sabab__wrapped__xavfsizlik chegarasi bo'la olmaydi. Dekorator imzoni o'zgartirsa (argumentni o'zi bersa),wrapsyolg'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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!