Ачааллж байна...
Түвшин 4 эхэлж байна. Энэ түвшинд таны программ анх удаа гадаад ертөнцтэй ярина — интернэттэй, бусад программтай.
Гэвч эхлээд бид нэг зүйлийг эцэст нь тайлах ёстой: @ тэмдэг.
Та түүнийг аль хэдийн ХЭДЭН УДАА ашигласан:
@property (Бүлэг 8.4) — method-ыг талбар шиг харагдуулна
@dataclass (Бүлэг 9.1) — __init__, __repr__, __eq__ -ыг автоматаар үүсгэнэ
@classmethod (Бүлэг 9.3) — method-ыг class-д хамааруулна
Тэдгээр бүрд би ижил зүйл хэлсэн: "тэр нь decorator, Бүлэг 10-д бүрэн тайлбарлана."
Одоо тэр цаг болжээ.
Гэхдээ decorator-ыг ойлгохын тулд эхлээд НЭГ санааг бүрэн эзэмших ёстой: функц дотор функц тодорхойлж, түүнийг БУЦААХ.
Тэр санаа нь энэ курсийн хамгийн хэцүү зүйлүүдийн нэг байх магадлалтай. Би түүнийг шударгаар хүлээн зөвшөөрч байна. Хэрэв эхний уншлагаар ойлгохгүй бол — тэр нь хэвийн. Дахин уншаарай, жишээг өөрийн терминал дээр ажиллуулж үзээрэй. Тэр нь ирнэ.
Бүлэг 3.5-ыг санацгаая. Тэнд бид ХОЁР зүйл сурсан:
1. Функцийг УТГА болгож дамжуулж болно.
python
def hoyor_udaa_duudakh(func):
func()
func()
def mendchilekh():
print("Сайн байна уу")
hoyor_udaa_duudakh(mendchilekh) # хаалтгүй!2. sorted(neres, key=len) — len функцийг дамжуулж байна.
Тэр үед би ингэж хэлсэн:
"Decorator гэдэг нь зүгээр л ФУНКЦ ХҮЛЭЭЖ АВДАГ, ФУНКЦ БУЦААДАГ ФУНКЦ юм."
Та ЭХНИЙ хагасыг мэднэ — функц хүлээж авах. Одоо ХОЁР ДАХЬ хагасыг сурна — функц БУЦААХ.
Бүлэг 3.5-ын жорын зүйрлэлийг гүнзгийрүүлье.
Одоохондоо бид жорыг ДАГАЖ хоол хийж, эсвэл жорыг найздаа ӨГЧ байсан.
Одоо шинэ зүйл: та жор бичдэг ТОГООЧ болно.
Хэн нэгэн танд ирээд: "Надад амтлагчгүй хоолны жор хэрэгтэй" гэж хэлнэ. Та суугаад, ШИНЭ жор бичиж, түүнд өгнө. Тэр хүн одоо жортой боллоо — тэр түүнийг гэртээ аваачиж, хүссэн үедээ ашиглана.
Та хоол хийгээгүй. Та ЖОР бүтээсэн.
Функц дотор функц тодорхойлж, түүнийг буцаах нь яг тэр: та ажил хийгээгүй, харин АЖИЛ ХИЙХ АРГЫГ бүтээж, буцаасан.
Хамгийн энгийнээс эхэлье.
python
def gadna() -> None:
print("Гадна функц эхлэв")
def dotood() -> None:
print("Дотоод функц ажиллав")
dotood()
print("Гадна функц дууслаа")
gadna()Гадна функц эхлэв
Дотоод функц ажиллав
Гадна функц дууслааФункц дотор функц тодорхойлж болдог. Ямар ч онцгой бичлэг байхгүй — зүгээр л def -ийг догол мөртэй бичсэн.
dotood нь gadna -ийн ДОТООД хувьсагч мэт ажиллаж байна. Бүлэг 3.4-ийн scope -ыг санаж байна уу?
"Функц дотор үүсгэсэн хувьсагч нь LOCAL — зөвхөн тэр функцээс харагдана, функц дуусахад устана."
dotood нь функц ч гэсэн ижил дүрэмд захирагдана:
python
def gadna() -> None:
def dotood() -> None:
print("Дотоод функц")
dotood()
gadna()
dotood()Дотоод функц
Traceback (most recent call last):
File "main.py", line 8, in <module>
dotood()
NameError: name 'dotood' is not definedNameError: name 'dotood' is not defined — Бүлэг 3.4-ээс танил алдаа!
dotood нь gadna -гийн дотор амьдарч байна. Гадна талаас түүнд хандаж болохгүй. gadna() дуусахад тэр устна.
Функц бол зүгээр л утга (Бүлэг 3.5). Тиймээс тэр нь бусад утгуудтай ижил scope-ын дүрэмд захирагдана.
Одоо ГОЛ санаа руу орно.
Хэрэв dotood нь зүгээр л утга бол — түүнийг БУЦААЖ болох уу?
python
def gadna():
def dotood() -> None:
print("Дотоод функц ажиллав")
return dotood # ХААЛТГҮЙ!
func = gadna()
print(func)
print(type(func))
func()<function gadna.<locals>.dotood at 0x000001A2B3C4D5E0>
<class 'function'>
Дотоод функц ажиллавГурван зүйлийг анхааралтай хараарай.
Нэгдүгээрт, return dotood — ХААЛТГҮЙ.
Бүлэг 3.5-ын сургамж: "хаалт бүх ялгааг үүсгэдэг."
return dotood — функц ӨӨРИЙГ буцаана (жорын цаас)
return dotood() — функцийг АЖИЛЛУУЛЖ, түүний үр дүнг буцаана (бэлэн хоол)
Бид жорын цаасыг буцаж байна.
Хоёрдугаарт, func = gadna() — ХААЛТТАЙ.
Энд бид gadna -г АЖИЛЛУУЛЖ байна. Тэр нь ажиллаад, dotood -ыг буцаана. Тэгэхээр func нь одоо dotood функцийг агуулж байна.
Гуравдугаарт, print(func) нь ФУНКЦ гэж хэлж байна.
<function gadna.<locals>.dotood at 0x...> — "энэ бол gadna -гийн ДОТОРХ dotood нэртэй функц."
Хамгийн сүүлд func() гэж дуудахад тэр функц ажиллаж байна.
Тэгэхээр юу болов?
gadna() дуудагдав
Тэр нь dotood функцийг ҮҮСГЭВ
Тэр нь dotood -ыг БУЦААВ
gadna дууслаа
Гэвч dotood нь func гэсэн нэрээр АМЬД ҮЛДЛЭЭ
func() дуудахад dotood ажиллав
Функц нь функц үйлдвэрлэлээ.
Одоо жинхэнэ сонирхолтой зүйл болно.
python
def urjuulegch_uusgekh(too: int):
def urjuulekh(x: int) -> int:
return x * too # ГАДНА талын too -г ашиглаж байна!
return urjuulekh
hoyor_dakhin = urjuulegch_uusgekh(2)
gurav_dakhin = urjuulegch_uusgekh(3)
print(hoyor_dakhin(10))
print(hoyor_dakhin(50))
print(gurav_dakhin(10))
print(gurav_dakhin(50))20
100
30
150Энэ бол гайхалтай зүйл. Удаашруулъя.
urjuulegch_uusgekh(2) гэж дуудахад:
too параметр нь 2 утга авав
urjuulekh функц ҮҮСЭВ — тэр нь x * too гэж бичигдсэн
urjuulekh БУЦААГДАВ
urjuulegch_uusgekh ДУУСЛАА
Одоо асуулт: urjuulegch_uusgekh дууссан тул too нь УСТСАН БАЙХ ЁСТОЙ (Бүлэг 3.4: local хувьсагч функц дуусахад устдаг).
Тэгвэл hoyor_dakhin(10) дуудахад x * too нь яаж ажиллаж байна вэ? too хаана байна?
Хариулт: Python нь түүнийг ХАДГАЛЖ ҮЛДСЭН.
Функц нь өөрийн үүссэн орчны хувьсагчдыг САНАЖ байдаг. Тэр санамжийг closure гэж нэрлэдэг (англиар "хаалт", "бүрхэвч").
hoyor_dakhin нь зөвхөн функц БИШ — тэр нь функц + түүний санаж байгаа орчин юм. Тэр орчинд too = 2 байна.
gurav_dakhin нь ТУСДАА closure — түүний орчинд too = 3 байна.
Хоёулаа ижил кодтой (x * too), гэхдээ ӨӨР санамжтай.
Closure -ийг ойлгоход туслах зүйрлэл.
Ном уншиж байна гэж төсөөлье. Та номыг хаахдаа закладка үлдээнэ. Дараа нь номыг тавиур дээр тавина.
Хэдэн сарын дараа буцаж ирэхэд — номыг нээхэд закладка тэнд байна. Та яг тэр хуудаснаас үргэлжлүүлнэ.
Закладка нь номтой ХАМТ явсан. Тэр нь тавиур дээр, ном дотор амьд үлдсэн.
Closure бол яг тэр: функц нь өөрийн үүссэн орчны "закладка" -г өөртөө авч явдаг. Гадна функц дууссан ч, тэр закладка амьд үлдэнэ.
Одоо чухал зүйлийг тодруулъя. Closure бүр нь ТУСДАА санамжтай.
python
def toolch_uusgekh():
too = 0
def nemekh() -> int:
nonlocal too
too = too + 1
return too
return nemekh
toolch1 = toolch_uusgekh()
toolch2 = toolch_uusgekh()
print(toolch1())
print(toolch1())
print(toolch1())
print()
print(toolch2())
print(toolch2())1
2
3
1
2toolch1 ба toolch2 нь ТУСДАА тоолууртай.
toolch1 нь 3 хүртэл тоолсон. toolch2 нь өөрийн, шинэ тоолуураас (1-ээс) эхэлсэн.
Хоёулаа ижил кодоос үүссэн ч, тус бүр нь ӨӨРИЙН closure -тэй.
nonlocal too — энэ бол шинэ түлхүүр үг.
Бүлэг 3.4-ийн дүрмийг санаж байна уу?
"Функц дотор нэрэнд УТГА ОЛГОВОЛ, Python тэр нэрийг бүхэл функцийн турш LOCAL гэж үзнэ."
Тэгэхээр too = too + 1 гэж бичихэд Python нь too -ыг nemekh -ийн LOCAL хувьсагч гэж үзэх байсан. Дараа нь too + 1 -ыг тооцоолохыг оролдоод — UnboundLocalError гарах байсан.
Яг тэр алдааг Бүлэг 3.4-т харсан.
nonlocal too нь Python-д ингэж хэлж байна: "энэ too нь local БИШ. Тэр нь ГАДНА талын функцийнх. Түүнийг ашигла."
global -той харьцуулъя (Бүлэг 3.4):
global x — "программын хамгийн ГАДНА талын x"
nonlocal x — "ГАДНА талын ФУНКЦИЙН x"
nonlocal нь global -ээс хамаагүй аюулгүй, учир нь тэр нь зөвхөн closure дотор л ажилладаг. Тэр нь глобал төлөв үүсгэхгүй.
Гэхдээ анхаараарай: практикт nonlocal нь ХОВОР ашиглагддаг. Хэрэв танд өөрчлөгддөг төлөв хэрэгтэй бол — ихэвчлэн class нь илүү зөв шийдэл (Бүлэг 7-9). Би түүнийг зөвхөн closure яаж ажилладгийг харуулах гэж заасан.
Closure нь зөвхөн онолын тоглоом биш. Тэр нь бодит ажилд хэрэгтэй.
python
def khuvi_khyalbarchlagch(khuvi: float):
def tootsoolokh(dun: int) -> int:
return int(dun * (1 + khuvi))
return tootsoolokh
tatvartai = khuvi_khyalbarchlagch(0.1)
khyamdraltai = khuvi_khyalbarchlagch(-0.2)
print(f"15,000 + 10% татвар = {tatvartai(15000):,}")
print(f"15,000 - 20% хямдрал = {khyamdraltai(15000):,}")15,000 + 10% татвар = 16,500
15,000 - 20% хямдрал = 12,000Хоёр өөр функц үүслээ, тус бүр нь өөрийн хувьтай.
Мөн sorted(key=...) -тэй хослуулж болно (Бүлэг 3.5):
python
def talbaraar_erembelekh(talbar: str):
def key_func(d: dict) -> int | str:
return d[talbar]
return key_func
suragchid = [
{"ner": "Болд", "onoo": 85},
{"ner": "Сараа", "onoo": 92},
{"ner": "Төмөр", "onoo": 78},
]
onoogoor = sorted(suragchid, key=talbaraar_erembelekh("onoo"), reverse=True)
nereer = sorted(suragchid, key=talbaraar_erembelekh("ner"))
print([s["ner"] for s in onoogoor])
print([s["ner"] for s in nereer])['Сараа', 'Болд', 'Төмөр']
['Болд', 'Сараа', 'Төмөр']talbaraar_erembelekh("onoo") нь ФУНКЦ буцаана — тэр функц нь talbar = "onoo" гэдгийг санаж байна. Дараа нь sorted нь түүнийг ашиглана.
Гурван бүлгийн мэдлэг нэг мөрөнд: функц бол утга (3.5), closure (10.1), sorted(key=) (3.5).
Одоо би танд нэг зүйлийг харуулъя. Бүү айгаарай — зөвхөн ХАРААРАЙ.
python
def burkhuul(func):
def dotood():
print("Өмнө нь ямар нэг зүйл")
func()
print("Дараа нь ямар нэг зүйл")
return dotood
def mendchilekh():
print("Сайн байна уу")
mendchilekh = burkhuul(mendchilekh)
mendchilekh()Өмнө нь ямар нэг зүйл
Сайн байна уу
Дараа нь ямар нэг зүйлЭнэ бол DECORATOR.
Тэр нь @ тэмдэггүй бичигдсэн — гэхдээ тэр нь ЯГ ТЭР зүйл юм.
Кодыг задалъя:
def burkhuul(func): — ФУНКЦ ХҮЛЭЭЖ АВЧ байна (Бүлэг 3.5).
def dotood(): — ФУНКЦ ДОТОР ФУНКЦ (энэ хичээл).
func() — closure! dotood нь func -ыг САНАЖ байна (энэ хичээл).
return dotood — ФУНКЦ БУЦААЖ байна (энэ хичээл).
mendchilekh = burkhuul(mendchilekh) — эх функцийг АВЧ, боож, шинэ нэрээр (ижил нэрээр!) хадгалж байна.
Одоо mendchilekh нь ЭХНИЙ функц БИШ — тэр нь dotood (боолт). Түүнийг дуудахад, боолт ажиллана: өмнөх зүйл → эх функц → дараагийн зүйл.
Тэгээд @ тэмдэг нь юу вэ?
python
@burkhuul
def mendchilekh():
print("Сайн байна уу")ЯГ ижил зүйл. @burkhuul гэсэн мөр нь mendchilekh = burkhuul(mendchilekh) гэсэн мөртэй ЯГ ИЖИЛ утгатай.
Тэр л болоо. @ бол зөвхөн БОГИНО БИЧЛЭГ.
Дараагийн хичээлд бид үүнийг бүрэн, удаан, олон жишээгээр тайлбарлана. Гэхдээ бүх бүрэлдэхүүн хэсэг нь одоо таны гарт байна.
Хэрэв сонирхвол: urjuulegch_uusgekh жишээг өөрөө бичиж, дөрөв дахин үржүүлэгч, арав дахин үржүүлэгч үүсгэж үзээрэй. Тэдгээр нь ТУСДАА санамжтай гэдгийг батлаарай.
Мөн хүсвэл: return dotood -ыг return dotood() болгож (хаалт нэмж) өөрчилөөд юу болохыг хараарай. Функцийн оронд түүний ҮР ДҮН буцах болно — тэр нь огт өөр зүйл. Хаалт бүх ялгааг үүсгэдэг.
1. return -д хаалт бичих
python
def gadna():
def dotood():
print("Дотоод")
return dotood() # ХААЛТТАЙ!
func = gadna()
func()Дотоод
Traceback (most recent call last):
File "main.py", line 8, in <module>
func()
TypeError: 'NoneType' object is not callableАнзаараарай: "Дотоод" НЭГ УДАА хэвлэгдсэн — учир нь dotood() нь return дээр АЖИЛЛАСАН. Дараа нь түүний буцаасан None нь func -т очиж, func() дуудахад алдаа гарсан.
Засвар: хаалтыг ав. return dotood.
2. Дотоод функцэд гаднаас хандах
python
def gadna():
def dotood():
print("Дотоод")
dotood()
dotood()NameError: name 'dotood' is not definedДотоод функц нь LOCAL. Гадна талаас харагдахгүй. Хэрэв хэрэгтэй бол — БУЦАА.
3. nonlocal мартах
python
def toolch_uusgekh():
too = 0
def nemekh() -> int:
too = too + 1 # nonlocal БАЙХГҮЙ!
return too
return nemekh
toolch = toolch_uusgekh()
print(toolch())UnboundLocalError: cannot access local variable 'too' where it is not associated with a valueБүлэг 3.4-ээс танил алдаа! Утга олгосон тул Python нь too -ыг local гэж үзсэн. Засвар: nonlocal too.
4. Closure нь утгыг ХУУЛДАГ гэж бодох
python
def uusgekh():
tootsuud = []
for i in range(3):
def func():
return i
tootsuud.append(func)
return tootsuud
funcs = uusgekh()
print([f() for f in funcs])[2, 2, 2]Гэнэтийн зүйл! Гурван функц бүгд 2 буцаалаа, 0, 1, 2 биш.
Учир нь closure нь утгыг ХУУЛДАГГҮЙ — тэр нь ХУВЬСАГЧИЙГ санадаг. Loop дуусахад i нь 2 болсон. Гурван функц бүгд ТЭР ижил i -г харж байна.
Засвар: default утга ашиглан утгыг тэр дор нь барих (Бүлэг 3.1):
python
def func(i=i): # default утга нь ТЭР ДОР НЬ тооцоологдоно
return i[0, 1, 2]Энэ бол нарийн урхи. Практикт ховор тааралддаг, гэхдээ мэдэж байх нь зүйтэй.
Функц дотор функц тодорхойлж болно — ямар ч онцгой бичлэг хэрэггүй, зөвхөн догол мөртэй def.
Дотоод функц нь LOCAL — гадна талаас харагдахгүй (Бүлэг 3.4-ийн scope). Хэрэгтэй бол БУЦАА.
return dotood — функц ӨӨРИЙГ буцаана. return dotood() — түүнийг АЖИЛЛУУЛЖ, үр дүнг буцаана. Хаалт бүх ялгааг үүсгэдэг.
CLOSURE: дотоод функц нь өөрийн үүссэн орчны хувьсагчдыг САНАЖ байдаг — гадна функц дууссан ч.
Closure бүр нь ТУСДАА санамжтай. urjuulegch_uusgekh(2) ба urjuulegch_uusgekh(3) нь хоёр өөр функц, хоёр өөр too.
nonlocal x — "гадна талын ФУНКЦИЙН x" (global нь программын хамгийн гадна тал). Closure дотор утга ӨӨРЧЛӨХӨД хэрэгтэй.
Практикт nonlocal нь ХОВОР — өөрчлөгддөг төлөв хэрэгтэй бол ихэвчлэн class нь илүү зөв.
DECORATOR-ын бүх бүрэлдэхүүн хэсэг одоо таны гарт байна:
Функц хүлээж авах (Бүлэг 3.5)
Функц дотор функц (энэ хичээл)
Closure — эх функцийг санах (энэ хичээл)
Функц буцаах (энэ хичээл)
@burkhuul гэсэн мөр нь mendchilekh = burkhuul(mendchilekh) -тэй ЯГ ИЖИЛ. @ бол зөвхөн богино бичлэг.
Дараагийн хичээлд бид @ тэмдгийг эцэст нь тайлна. Та @property, @dataclass, @classmethod -ыг харах бүрдээ "аан, тэр нь функц авч, функц буцааж байна" гэж мэдэх болно. Мөн бид бэлгийг боох цаас гэсэн зүйрлэлээр decorator-ын мөн чанарыг бүрэн ойлгуулна: боолт нь бэлгийг ӨӨРЧЛӨХГҮЙ, зөвхөн түүн дээр ямар нэг зүйл НЭМДЭГ.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.