Ачааллж байна...
Одоо бид @ тэмдгийг эцэст нь тайлна.
Өмнөх хичээлийн төгсгөлд би танд нэг зүйлийг харуулсан бөгөөд "бүү айгаарай, зөвхөн хараарай" гэж хэлсэн. Одоо тэр код руу буцаж, түүнийг БҮРЭН, УДААН тайлбарлана.
Гэхдээ эхлээд хамгийн чухал өгүүлбэрийг хэлье, учир нь бүх зүйл түүн дээр тулгуурлана:
@decoratorгэсэн мөр ньfunc = decorator(func)гэсэн мөртэй ЯГ ИЖИЛ утгатай.
Тэр л болоо. Өөр илбэ байхгүй. @ бол зөвхөн богино бичлэг (программистууд түүнийг "syntactic sugar" — синтаксийн чихэр гэж нэрлэдэг).
Хэрэв та энэ нэг өгүүлбэрийг ойлговол — та decorator-ыг ойлголоо. Үлдсэн бүх зүйл нь зөвхөн дэлгэрэнгүй тайлбар юм.
Decorator -ын мөн чанарыг ойлгох хамгийн сайн зүйрлэл нь энэ.
Танд бэлэг байна — жишээ нь ном. Тэр ном өөрөө бүрэн гүйцэд, ашиглахад бэлэн зүйл.
Одоо та түүнийг боох цаасаар боолоо. Гоё цаас, тууз, карт.
Хамгийн чухал зүйл: ном ӨӨРЧЛӨГДӨӨГҮЙ. Тэр нь яг тэр ном хэвээрээ, яг тэр хуудсуудтай, яг тэр агуулгатай. Та түүнийг нэг ч үсэг өөрчлөөгүй.
Гэвч ХҮЛЭЭН АВАГЧИЙН ТУРШЛАГА өөрчлөгдсөн. Одоо тэр нь боолт нээж, гэнэтийн баяр мэдэрч, дараа нь номоо олно.
Decorator бол яг тэр боох цаас:
Эх функц (бэлэг) — өөрчлөгдөхгүй, түүний код нэг ч мөр өөрчлөгдөхгүй
Decorator (боох цаас) — түүнийг тойрч, өмнө нь ба дараа нь ямар нэг зүйл нэмнэ
Дуудагч — боолтыг дууддаг, гэхдээ бэлэг нь дотор нь ажиллана
Тэгэхээр decorator нь функцийн кодыг өөрчлөхгүйгээр, түүний зан төлөвийг өргөтгөдөг.
Энэ бол маш хүчирхэг санаа. Та бусдын бичсэн функцийг ч, өөрийн бичсэн функцийг ч — кодод нь ХҮРЭЛГҮЙГЭЭР — өөрчилж чадна.
@ -гүйгээрЭхлээд @ тэмдэггүйгээр бичье. Тэгвэл юу болж байгаа нь бүрэн тодорхой болно.
python
def burkhuul(func):
def dotood():
print("--- Эхэлж байна ---")
func()
print("--- Дууслаа ---")
return dotood
def mendchilekh():
print("Сайн байна уу")
mendchilekh()Сайн байна ууОдоохондоо burkhuul нь ашиглагдаагүй. mendchilekh нь ердийнхөөрөө ажиллаж байна.
Одоо түүнийг боолоо:
python
mendchilekh = burkhuul(mendchilekh)
mendchilekh()--- Эхэлж байна ---
Сайн байна уу
--- Дууслаа ---Юу болов? АЛХАМ АЛХМААР дагая.
Алхам 1: burkhuul(mendchilekh) дуудагдав. func параметр нь ЭХ mendchilekh функцийг хүлээж авав.
Алхам 2: burkhuul дотор dotood функц ҮҮСЭВ. Тэр нь func -ыг САНАЖ байна (closure — Бүлэг 10.1).
Алхам 3: burkhuul нь dotood -ыг БУЦААВ.
Алхам 4: mendchilekh = ... — тэр буцаасан функцийг mendchilekh гэсэн нэрэнд ОЛГОВ.
Одоо хамгийн чухал зүйл: mendchilekh нэр нь одоо ЭХ функцийг заахаа больсон. Тэр нь dotood -ыг заана.
Эх функц АЛГА БОЛООГҮЙ — тэр нь dotood -ийн closure дотор func гэсэн нэрээр амьд байна. Гэвч гаднаас түүнд шууд хандах боломжгүй.
Алхам 5: mendchilekh() дуудахад — dotood() ажиллана:
"--- Эхэлж байна ---" хэвлэнэ
func() дуудна → эх функц ажиллаж "Сайн байна уу" хэвлэнэ
"--- Дууслаа ---" хэвлэнэ
Бэлэг нь боолт дотор ажиллаж байна.
@ -тайpython
def burkhuul(func):
def dotood():
print("--- Эхэлж байна ---")
func()
print("--- Дууслаа ---")
return dotood
@burkhuul
def mendchilekh():
print("Сайн байна уу")
mendchilekh()--- Эхэлж байна ---
Сайн байна уу
--- Дууслаа ---ЯГ ИЖИЛ ҮР ДҮН.
@burkhuul гэсэн мөр нь mendchilekh = burkhuul(mendchilekh) гэсэн мөрийг ОРЛОСОН.
Ялгаа нь зөвхөн ХОЁР зүйлд:
1. Богино. Нэг мөр, функцийн нэрийг ХОЁР УДАА бичих шаардлагагүй.
2. ТОД. @burkhuul нь функцийн ЯГ ДЭЭР байна. Уншигч нь функцийг харах бүрдээ "аан, энэ нь боогдсон байна" гэж шууд мэднэ.
Хоёр дахь нь илүү чухал. Хэрэв mendchilekh = burkhuul(mendchilekh) гэсэн мөр нь файлын доод хэсэгт, зуун мөрийн дараа байсан бол — уншигч түүнийг анзаарахгүй байж болно. @ нь тэр мэдээллийг функцтэй ХАМТ байлгадаг.
Дээрх burkhuul нь нэг том сул талтай: тэр нь зөвхөн аргументгүй функцтэй ажиллана.
python
@burkhuul
def mendchilekh_nertei(ner: str):
print(f"Сайн байна уу, {ner}")
mendchilekh_nertei("Болд")Traceback (most recent call last):
File "main.py", line 15, in <module>
mendchilekh_nertei("Болд")
TypeError: burkhuul.<locals>.dotood() takes 0 positional arguments but 1 was givendotood() takes 0 positional arguments but 1 was given — "dotood нь аргумент авдаггүй, гэвч нэгийг өглөө."
Логиктой. mendchilekh_nertei нь одоо dotood юм. dotood нь def dotood(): гэж аргументгүй бичигдсэн. Тиймээс "Болд" -ыг хүлээж авч чадахгүй.
Шийдэл нь Бүлэг 3.3-аас ирнэ: *args, **kwargs.
python
def burkhuul(func):
def dotood(*args, **kwargs):
print("--- Эхэлж байна ---")
ur_dun = func(*args, **kwargs)
print("--- Дууслаа ---")
return ur_dun
return dotood
@burkhuul
def mendchilekh_nertei(ner: str) -> None:
print(f"Сайн байна уу, {ner}")
@burkhuul
def niilber(a: int, b: int) -> int:
return a + b
@burkhuul
def barааnii_medeelel(ner: str, une: int = 0) -> str:
return f"{ner}: {une:,} төгрөг"
mendchilekh_nertei("Болд")
print()
print(niilber(5, 3))
print()
print(barааnii_medeelel("Ном", une=15000))--- Эхэлж байна ---
Сайн байна уу, Болд
--- Дууслаа ---
--- Эхэлж байна ---
--- Дууслаа ---
8
--- Эхэлж байна ---
--- Дууслаа ---
Ном: 15,000 төгрөгГурван ӨӨР функц, гурвуулаа ажиллаж байна. Аргументтай, аргументгүй, keyword-той, буцаах утгатай — бүгд.
Гурван чухал өөрчлөлтийг задалъя.
def dotood(*args, **kwargs): — Бүлэг 3.3-аас: "*args нь дурын тооны positional аргументыг цуглуулна, **kwargs нь дурын тооны keyword аргументыг цуглуулна."
Тэгэхээр dotood нь одоо ЮУ Ч хүлээж авна.
ur_dun = func(*args, **kwargs) — Бүлэг 3.3-аас: "дуудалт дотор од нь ЗАДАЛНА."
Тэгэхээр dotood нь цуглуулсан бүх аргументыг эх функцэд ЯГ ТЭР ХЭВЭЭР нь дамжуулж байна.
return ur_dun — энэ мөрийг мартах нь ХАМГИЙН түгээмэл алдаа.
Хэрэв түүнийг мартвал:
python
def burkhuul(func):
def dotood(*args, **kwargs):
print("--- Эхэлж байна ---")
func(*args, **kwargs) # буцаалт БАЙХГҮЙ
print("--- Дууслаа ---")
return dotood
@burkhuul
def niilber(a: int, b: int) -> int:
return a + b
print(niilber(5, 3))--- Эхэлж байна ---
--- Дууслаа ---
NoneNone гарлаа, 8 биш.
Эх функц нь 8 буцаасан, гэвч dotood нь түүнийг ХАЯСАН. dotood өөрөө юу ч буцаагаагүй тул None буцаасан.
Санамж: боолт нь эх функцийн буцаалтыг ЗААВАЛ дамжуулах ёстой. Бэлгийг боохдоо түүнийг хаяж болохгүй.
Бүлэг 3.3-ын "дамжуулах" хэв маяг энд яг ажиллаж байна. Тэр үед би ингэж хэлсэн:
"Энэ хэв маягийг сайн санаж яваарай. Бүлэг 10-д та decorator бичихэд яг ийм код бичих болно."
Одоо тэр цаг ирлээ.
Одоо бүх decorator-ын СУУРЬ загварыг тодорхой бичье. Та түүнийг цээжлэх шаардлагагүй, гэхдээ түүнийг таних чадвартай болох ёстой.
python
def decorator_ner(func):
def dotood(*args, **kwargs):
# 1. ӨМНӨ нь юу нэмэх вэ
ur_dun = func(*args, **kwargs) # 2. ЭХ функцийг дуудна
# 3. ДАРАА нь юу нэмэх вэ
return ur_dun # 4. Буцаалтыг дамжуулна
return dotoodДөрвөн хэсэг:
Өмнөх ажил — эх функц ажиллахаас өмнө
Эх функцийг дуудах — *args, **kwargs -аар БҮХ аргументыг дамжуулна
Дараах ажил — эх функц дууссаны дараа
Буцаалтыг дамжуулах — return ur_dun
Хэрэв та зөвхөн ӨМНӨХ ажил хийх гэж байвал — 3-ыг орхи. Хэрэв зөвхөн ДАРААХ ажил — 1-ийг орхи.
Энэ загварыг санаж яваарай. Практикт decorator-ын 90% нь яг ийм харагддаг.
@property -ыг ойлголооОдоо буцаж хараарай.
Бүлэг 8.4-т бид ингэж бичсэн:
python
class TegshUnguult:
def __init__(self, urt: float, urgun: float) -> None:
self.urt = urt
self.urgun = urgun
@property
def talbai(self) -> float:
return self.urt * self.urgunОдоо та мэднэ: @property гэсэн мөр нь ойролцоогоор дараах кодтой ижил:
python
talbai = property(talbai)property бол функц (үнэндээ class, гэхдээ ижил зарчмаар ажилладаг). Тэр нь таны talbai method-ыг АВЧ, түүнийг тусгай объект болгож боож, буцаадаг.
Тэр тусгай объект нь Python-д "энэ нь method биш, ТАЛБАР шиг хандах ёстой" гэж хэлдэг.
Илбэ алга. Зөвхөн функц авч, функц (объект) буцааж байна.
Ижил зүйл @classmethod дээр:
python
@classmethod
def from_dict(cls, d: dict) -> "Gishuun":
return cls(**d)нь
python
from_dict = classmethod(from_dict)Ижил зарчим.
@dataclass — жаахан өөрОдоо @dataclass -ыг харъя (Бүлэг 9.1):
python
@dataclass
class Zarlaga:
ner: str
dun: intнь
python
Zarlaga = dataclass(Zarlaga)Анзаараарай: энд ФУНКЦ биш, CLASS боогдож байна.
Decorator нь ЯМАР Ч зүйл дээр ажиллаж чадна — функц, class, ялгаагүй. Тэр нь зөвхөн "энэ зүйлийг ав, өөрчил, буцаа" гэсэн зарчим юм.
dataclass нь class-ыг авч, түүний type hint-үүдийг уншиж, __init__, __repr__, __eq__ method-уудыг НЭМЖ, дараа нь класс-ыг буцаадаг.
Илбэ алга. Зөвхөн class авч, өөрчилж, буцааж байна.
functools.wraps — нэг асуудалОдоо нэг нарийн асуудлыг харъя.
python
def burkhuul(func):
def dotood(*args, **kwargs):
return func(*args, **kwargs)
return dotood
@burkhuul
def mendchilekh(ner: str) -> str:
"""Хүнтэй мэндчилдэг функц."""
return f"Сайн байна уу, {ner}"
print(mendchilekh.__name__)
print(mendchilekh.__doc__)dotood
NoneАсуудал: функцийн НЭР нь dotood болж хувирсан! Мөн түүний тайлбар (docstring) алга болсон.
Логиктой — mendchilekh нэр нь одоо dotood функцийг заана. dotood -ийн нэр нь "dotood", түүнд docstring байхгүй.
Энэ нь ихэвчлэн асуудал биш, гэхдээ дебаг хийхэд төөрөгдүүлдэг. Мөн зарим хэрэгсэл (documentation үүсгэгч, test framework) нь функцийн нэр дээр найддаг.
Шийдэл: functools.wraps.
python
import functools
def burkhuul(func):
@functools.wraps(func)
def dotood(*args, **kwargs):
return func(*args, **kwargs)
return dotood
@burkhuul
def mendchilekh(ner: str) -> str:
"""Хүнтэй мэндчилдэг функц."""
return f"Сайн байна уу, {ner}"
print(mendchilekh.__name__)
print(mendchilekh.__doc__)mendchilekh
Хүнтэй мэндчилдэг функц.Нэр ба тайлбар сэргэлээ.
@functools.wraps(func) нь "эх функцийн нэр, тайлбар, бусад мета-мэдээллийг боолт руу ХУУЛ" гэсэн заавар.
Анзаараарай: тэр нь өөрөө DECORATOR — dotood дээр @ -тай хэрэглэгдэж байна. Decorator дотор decorator!
Энэ курс нь functools.wraps -ыг ЗААВАЛ шаардахгүй. Гэхдээ мэргэжлийн кодод тэр нь бараг үргэлж байдаг тул мэдэж байгаарай. Хэрэв та бусдын decorator-ыг харахад @functools.wraps(func) гэсэн мөр байвал — "аан, тэр нь эх функцийн нэрийг хамгаалж байна" гэж ойлгоно.
Хэрэв сонирхвол: @ -гүйгээр decorator бичээд, дараа нь @ -тай бичээд, ХОЁУЛАА ЯГ ИЖИЛ ажиллаж байгааг өөрөө батлаарай. Тэр туршилт нь @ бол зөвхөн богино бичлэг гэдгийг бүрэн ойлгуулна.
Дараа нь return ur_dun -ыг ЗОРИУДААР устгаад, буцаах утгатай функц дуудаад, None гарахыг хараарай. Тэр алдаа нь practice-д маш түгээмэл тул нэг удаа өөрөө үүсгэсэн нь дээр.
Мөн хүсвэл: өөрийн бичсэн decorator-ыг ХОЁР өөр функцэд хэрэглээд, хоёулаа ажиллаж байгааг хараарай. Нэг боолт, олон бэлэг.
1. return ur_dun мартах
python
def dotood(*args, **kwargs):
func(*args, **kwargs) # буцаалт хаягдлааБуцаах утгатай функц нь None буцаана. Хамгийн түгээмэл алдаа.
2. *args, **kwargs мартах
python
def dotood(): # аргумент авахгүй
func()TypeError: dotood() takes 0 positional arguments but 1 was givenАргументтай функц ажиллахгүй. Бүлэг 3.3-ын "дамжуулах" хэв маягийг ашигла.
3. Дуудалт дотор од мартах
python
def dotood(*args, **kwargs):
return func(args, kwargs) # од БАЙХГҮЙ!Одгүй бол args (tuple) ба kwargs (dict) нь ХОЁР аргумент болж дамжина, задлагдахгүй.
TypeError: niilber() takes 2 positional arguments but ...Санамж (Бүлэг 3.3): тодорхойлолт дээр од нь САВЛАНА, дуудалт дээр од нь ЗАДАЛНА. Хоёуланд нь од хэрэгтэй.
4. return dotood дээр хаалт бичих
python
def burkhuul(func):
def dotood(*args, **kwargs):
return func(*args, **kwargs)
return dotood() # ХААЛТТАЙ!TypeError: dotood() missing ... argumentsDecorator нь ФУНКЦ буцаах ёстой, түүний үр дүнг биш. Хаалтгүй. (Бүлэг 10.1-ийн сургамж.)
5. Decorator-ыг ХААЛТТАЙ хэрэглэх
python
@burkhuul() # ХААЛТТАЙ!
def mendchilekh():
...TypeError: burkhuul() missing 1 required positional argument: 'func'@burkhuul нь хаалтгүй. @burkhuul() гэж бичихэд Python нь burkhuul -ыг АРГУМЕНТГҮЙ дуудахыг оролдоно.
(Аргументтай decorator ч бий — тэдгээр нь @burkhuul(argument) гэж бичигддэг. Гэхдээ тэдгээр нь илүү нарийн бөгөөд энэ курсэд шаардлагагүй.)
6. Decorator нь функцийг АЖИЛЛУУЛНА гэж бодох
python
@burkhuul
def mendchilekh():
print("Сайн байна уу")Энэ мөр нь mendchilekh -ыг АЖИЛЛУУЛАХГҮЙ. Тэр зөвхөн түүнийг БООЖ байна. Ажиллуулахын тулд mendchilekh() гэж дуудна.
Гэхдээ анхаараарай: burkhuul НЬ ажиллана — яг тэр мөчид. Хэрэв burkhuul дотор print байвал, тэр нь импорт хийх үед хэвлэгдэнэ.
@decorator гэсэн мөр нь func = decorator(func) -тэй ЯГ ИЖИЛ. @ бол зөвхөн богино бичлэг. Илбэ БАЙХГҮЙ.
Decorator = функц авч, функц буцаадаг функц. Түүний бүх бүрэлдэхүүн хэсгийг та Бүлэг 3.5 ба 10.1-д сурсан.
Бэлгийг боох цаас: эх функц (бэлэг) нь ӨӨРЧЛӨГДӨХГҮЙ. Decorator (боолт) нь түүнийг тойрч, өмнө нь ба дараа нь ямар нэг зүйл нэмнэ.
СТАНДАРТ ЗАГВАР:
python
def decorator_ner(func):
def dotood(*args, **kwargs):
# өмнөх ажил
ur_dun = func(*args, **kwargs)
# дараах ажил
return ur_dun
return dotood*args, **kwargs (Бүлэг 3.3) нь ЗААВАЛ хэрэгтэй — эс тэгвээс зөвхөн аргументгүй функцтэй ажиллана.
return ur_dun -ыг МАРТАЖ БОЛОХГҮЙ — эс тэгвээс буцаах утга нь хаягдаж, None болно. Хамгийн түгээмэл алдаа.
Decorator нь ФУНКЦ ба CLASS хоёуланд нь ажиллана. @dataclass нь class-ыг боож байна.
Одоо та мэднэ: @property → property(func). @classmethod → classmethod(func). @dataclass → dataclass(cls).
@functools.wraps(func) нь эх функцийн нэр, тайлбарыг хамгаална. Заавал биш, гэхдээ мэргэжлийн кодод бараг үргэлж байдаг.
@decorator нь ХААЛТГҮЙ. @decorator() нь өөр зүйл.
Дараагийн хичээлд бид ӨӨРӨӨ бодит decorator бичнэ. Тэр нь функцийн ажиллах хугацааг хэмждэг бөгөөд практикт үнэхээр хэрэгтэй хэрэгсэл юм. Мөн бид түүнийг Бүлэг 5-ийн set/list хурдны туршилтад ашиглаж, decorator нь яагаад ЛОГИК ба ХЭМЖИЛТИЙГ салгах гоё арга болохыг харна.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.