Ачааллж байна...
Түвшин 2-т тавтай морил. Энэ түвшний нэр нь Function-ийн эзэн бөгөөд бид энэ сургалтын хамгийн хэцүү санаануудын нэгээр эхэлнэ: closure. Шударгаар хэлье. Closure-ийг анх удаа уншаад бүрэн ойлгодог хүн цөөхөн. Туршлагатай хөгжүүлэгчид ч энэ санааг хэд хэдэн удаа эргэж харж байж л "одоо ойлголоо" гэж мэдэрдэг. Тиймээс энэ хичээлийг хоёр, гурван удаа унших шаардлага гарвал энэ нь таны сул тал биш, сэдвийн хэвийн зам юм. Бид маш удаан, trace-ээр алхам алхмаар явна.
Course 2-т та функцийн дотор өөр функц тодорхойлж болдгийг харсан. Тэр үед бид үүнийг ихэвчлэн decorator-ыг хэрэглэх зорилгоор, ерөнхий түвшинд л авч үзсэн. Энэ хичээлд бид нэг энгийн атлаа гайхмаар асуулт тавина. Гадна функц ажиллаж дуусахад түүний бүх локал хувьсагч устах ёстой. Энэ бол Python-ий үндсэн дүрэм. Гэтэл гадна функц дотоод функцээ буцааж өгвөл, тэр дотоод функц гадна функцийн хувьсагчдыг дуудсаар байна. Устсан ёстой хувьсагч хаана амьд үлдсэн бэ? Хариулт нь closure.
Энэ хичээлийн гол санаа: closure гэдэг нь өөрийн төрсөн орчны хувьсагчдыг өөртэйгөө хамт авч явдаг функц юм. Англи үг “close over” буюу "дээгүүр нь хаах" гэсэн утгаас гаралтай: функц өөрийн хэрэглэдэг гадаад хувьсагчдыг "бүрхэж аваад" өөртөө хадгалдаг. Энэ хичээлд бид тэр хадгалалтыг нүдээр харах арга сурна. Python функцийн "үүргэвч"-ийг нээж, дотор нь яг юу байгааг харуулдаг.
Closure-ийг ойлгохын тулд Python хувьсагчийн нэрийг хаанаас хайдгийг бүрэн мэдэх хэрэгтэй. Course 1-2-т та local ба global хувьсагчийн ялгааг мэдсэн. Гэвч бүрэн зураг дөрвөн түвшинтэй бөгөөд англиар LEGB гэж товчилдог. Функц доторх кодонд ямар нэг нэр гарч ирэхэд Python түүнийг энэ дарааллаар хайна:
L, Local: одоо ажиллаж байгаа функцийн өөрийн хувьсагчид, түүний argument-ууд ба дотор нь оноосон нэрс.
E, Enclosing: хэрэв энэ функц өөр функцийн дотор тодорхойлогдсон бол тэр гадна функцийн хувьсагчид. Олон давхар үүрлэсэн бол дотроос гадагш нэг нэгээр.
G, Global: файлын дээд түвшинд, ямар ч функцийн гадна тодорхойлогдсон нэрс.
B, Built-in: Python-д бэлэн байдаг print, len, range зэрэг нэрс.
Python эхний олдсон газраа зогсдог. Хэрэв дөрвөн түвшинд нэг ч олдохгүй бол NameError гарна. Энэ хичээлийн бүх санаа E буюу Enclosing түвшинд оршдог. Course 2-т бид энэ түвшинг бараг хөндөөгүй. Одоо түүнийг гол дүр болгоно.
python
language = "Python"
def outer():
level = "гүнзгий"
def inner():
course = "Course 5"
print(course, level, language, len(language))
inner()
outer()Course 5 гүнзгий Python 6inner()-ийн доторх print мөрөнд дөрвөн нэр байна, тус бүр өөр түвшнээс олдсон. course нь Local, inner өөрөө оноосон. level нь inner-д байхгүй тул Python нэг шат гарч outer-ийн хувьсагчдаас олсон, тэгэхээр Enclosing. language нь Local ч, Enclosing ч биш, файлын дээд түвшинд байгаа тул Global. len нь хаана ч оноогдоогүй тул Built-in-ээс олдсон. Энэ жишээнд inner() нь outer() дотор, outer ажиллаж байх үед дуудагдсан тул level байгаа нь гайхмаар биш. Гайхал удахгүй ирнэ.
Гэрээсээ анх удаа алс холын сургууль руу явж буй хүүхдийг төсөөлөөд үзээрэй. Явахын өмнө ээж нь түүнд үүргэвч бэлдэж өгнө: гэрийн хаяг бичсэн цаас, гэр бүлийн зураг, эмээгийн хийсэн боорцог. Хүүхэд гэрээсээ гарч, хаалга хаагдана. Хэдэн сарын дараа гэрийнхэн нүүж, тэр байшин хоосорч ч магадгүй. Гэвч хүүхдийн үүргэвч дотор гэрийнх нь зүйлс хэвээр байна. Хэн нэгэн "чи хаанаас ирсэн бэ?" гэж асуухад тэр үүргэвчээ нээж, хаягийн цаасыг гаргаж үзүүлж чадна.
Closure бол яг ийм үүргэвчтэй функц. Гадна функц бол гэр. Дотоод функц бол тэр гэрт төрсөн хүүхэд. Гадна функц дотоод функцээ return-ээр гадагш явуулахад Python тэр дотоод функцэд үүргэвч бэлдэж өгнө. Дотор нь дотоод функцийн хэрэглэдэг гадна функцийн хувьсагчид байна. Гадна функц дуусч, түүний "гэр" буюу локал хувьсагчдын орон зай хаагдана. Гэвч дотоод функц үүргэвчтэйгээ явсаар, хэрэгтэй үедээ дотроос нь утга гаргаж авна.
Энэ зүйрлэлд гурван нарийн ширийн зүйл бий бөгөөд хичээлийн туршид тус бүрийг баталгаажуулна. Нэгдүгээрт, үүргэвчинд зөвхөн хүүхдийн хэрэглэх зүйлс л хийгдэнэ. Гэрийн бүх эд хогшил биш. Хоёрдугаарт, нэг гэрээс хоёр хүүхэд өөр өөр цагт явбал тус бүр өөрийн үүргэвчтэй. Гуравдугаарт, хамгийн нарийн нь: үүргэвч дотор зүйлийн хуулбар биш, харин тэр зүйлийг хадгалдаг хайрцаг өөрөө байдаг. Энэ сүүлийн санааны үр дагаврыг Жишээ 3-т харна.
Гадна функц нь дотоод функцийг дуудахгүй, харин түүнийг буцаана. Энэ бол closure үүсэх мөч.
python
def make_greeter(greeting):
def greet(name):
return f"{greeting}, {name}!"
return greet
hello = make_greeter("Сайн байна уу")
print(hello("Бат"))
print(hello("Сараа"))Сайн байна уу, Бат!
Сайн байна уу, Сараа!Кодыг уншъя. make_greeter нэг argument greeting авна. Түүний дотор greet гэсэн функц тодорхойлогдоно. greet өөрөө нэг argument name авах бөгөөд greeting-ийг ашиглана. make_greeter-ийн сүүлийн мөр return greet нь greet-ийг дуудаагүй, хаалтгүй, функцийг утга мэт буцааж байна. Хичээл 3.1-ээс хойш бид функцийг утга мэт map, key-д өгсөн. Энд функцийн буцаах утга нь өөрөө функц.
Trace: үүргэвч хэрхэн бэлдэгддэг вэ
Энэ кодыг санах ойн түвшинд мөрдье. Энд хоёр төрлийн орон зай харагдана: функц ажиллах үеийн түр зуурын frame буюу "гэр", мөн функц объект ба түүний үүргэвч.
Алхам 1 (def make_greeter). Python make_greeter функц объектыг үүсгэж, global түвшинд нэр онооно. Түүний бие хараахан ажиллаагүй. greet хараахан байхгүй.
Алхам 2 (make_greeter("Сайн байна уу") дуудагдах). make_greeter-т шинэ frame үүснэ, түүний гэр. Гэрт: greeting = "Сайн байна уу".
Алхам 3 (def greet мөр ажиллах). Python greet функц объектыг үүсгэнэ. Энэ мөчид тэр greet-ийн кодыг харж, түүнд өөрийн Local биш, гадна гэрээс ирэх нэр байгааг, тухайлбал greeting-ийг таньдаг. Тиймээс greet-д үүргэвч бэлдэж, дотор нь greeting-ийн хайрцгийг хийнэ. Гэрт: greeting = "Сайн байна уу", greet = функц объект. greet-ийн үүргэвчинд: greeting-ийн хайрцаг, дотор нь "Сайн байна уу".
Алхам 4 (return greet). greet функц объект гадагш гарна. make_greeter дуусч, түүний frame буюу гэр хаагдана. Ердийн үед greeting энэ мөчид устах байсан. Гэвч greeting-ийн хайрцаг greet-ийн үүргэвчинд хадгалагдсан тул хайрцаг амьд үлдэнэ. Гэр: алга. hello нэрээр хадгалагдсан greet-ийн үүргэвчинд: greeting-ийн хайрцаг, дотор нь "Сайн байна уу".
Алхам 5 (hello("Бат")). greet-ийн шинэ frame үүснэ. Түүний гэрт: name = "Бат". f-string greeting-ийг хайна. Local-д байхгүй. Enclosing: make_greeter-ийн гэр аль хэдийн хаагдсан ч greet үүргэвчээ нээж, тэндээс "Сайн байна уу"-г олно. "Сайн байна уу, Бат!" буцна. greet-ийн frame хаагдана, үүргэвч хэвээр.
Алхам 6 (hello("Сараа")). Шинэ frame, name = "Сараа". Ижил үүргэвч, ижил greeting. "Сайн байна уу, Сараа!".
Энэ trace-ийн гол мөч бол алхам 4: гэр хаагдсан ч хайрцаг амьд үлдсэн. Enclosing түвшний нэр хайлт гадна функцийн гэр нээлттэй байхад гэрээс, гэр хаагдсаны дараа үүргэвчээс хийгддэг. Үнэндээ хоёр тохиолдолд Python ижил хайрцгийг ашигладаг. Ялгаа нь гэр хаагдсаны дараа тэр хайрцгийг өөр хэн ч барихгүй, зөвхөн үүргэвч л барина.
Үүргэвчийг нээж харах
Энэ бол төсөөлөл биш. Python функцийн үүргэвчийг бидэнд шууд үзүүлдэг. Функц бүр __closure__ гэсэн attribute-тай, дотор нь хайрцгууд байна. Мөн функцийн кодонд __code__.co_freevars гэсэн attribute бий, тэр нь үүргэвчинд байгаа хувьсагчдын нэрсийг хэлнэ. Free variable гэдэг нь функцийн хэрэглэдэг боловч өөрөө оноогоогүй, гадна талаас ирдэг хувьсагч. Яг үүргэвчинд хийгдэх ёстой нэрс.
python
hello = make_greeter("Сайн байна уу")
print(hello)
print(hello.__code__.co_freevars)
print(hello.__closure__)
print(hello.__closure__[0].cell_contents)<function make_greeter.<locals>.greet at 0x7f1a2b3c4d50>
('greeting',)
(<cell at 0x7f1a2b3e1f10: str object at 0x7f1a2b3e2a30>,)
Сайн байна ууМөр бүрийг уншъя. Эхний мөр функцийн бүтэн нэрийг харуулна: make_greeter.<locals>.greet. Энэ нь “make_greeter-ийн локал орчинд төрсөн greet” гэсэн утгатай. Python функцийн гарал үүслийг нэрэнд нь бичиж хадгалдаг. Хоёр дахь мөр үүргэвчинд байгаа нэрсийг харуулна: зөвхөн greeting. Гурав дахь мөр үүргэвчийг өөрийг нь харуулна: нэг элементтэй tuple, элемент нь cell. Cell бол бидний "хайрцаг"-ийн жинхэнэ нэр. Англиар "нүд" гэсэн утгатай, хувьсагчийн утгыг хадгалдаг жижиг сав. Хаяг нь таны компьютер дээр өөр гарна. Дөрөв дэх мөр хайрцгийн доторх зүйлийг гаргаж үзүүлнэ: cell_contents нь "Сайн байна уу".
Ингээд хичээлийн гол асуултын хариулт тодорхой боллоо. Устах ёстой greeting хувьсагч hello функцийн __closure__ доторх cell-д амьд үлдсэн байв. Өдөр тутмын кодонд __closure__-ийг хэзээ ч бичихгүй. Гэхдээ closure-ийн тухай эргэлзээ төрөх бүрт үүргэвчийг ингэж нээж харах нь хамгийн найдвартай шалгалт юм.
Жишээ 1: зөвхөн хэрэглэгдэх зүйл үүргэвчинд орно
Зүйрлэлийн эхний нарийн ширийн зүйлийг шалгая: үүргэвчинд зөвхөн дотоод функцийн хэрэглэдэг хувьсагчид орно.
python
def make_label(prefix, suffix, unused):
separator = " | "
secret = "энэ хэзээ ч хэрэглэгдэхгүй"
def label(text):
return prefix + separator + text + suffix
return label
tag = make_label("[", "]", 999)
print(tag("мэдээ"))
print(tag.__code__.co_freevars)[ | мэдээ]
('prefix', 'separator', 'suffix')make_label-ийн гэрт таван хувьсагч байсан: prefix, suffix, unused, separator, secret. Гэвч tag-ийн үүргэвчинд гурав л орсон, тухайлбал label-ийн кодонд гарч ирдэг гурав. unused ба secret make_label дуусахад жирийн журмаар устсан. Python үүргэвчийг функц үүсэх мөчид кодыг шинжилж бэлддэг: "энэ функц ямар гадаад нэр хэрэглэдэг вэ?" Энэ нь санах ойн хувьд ухаалаг. Хэрэв гадна функц том файлыг санах ойд уншсан ч дотоод функц түүнийг хэрэглэхгүй бол файл closure-ийн улмаас санах ойд гацахгүй.
Эсрэг талын тохиолдлыг ч харъя: гадна функцийн юу ч хэрэглэдэггүй дотоод функц.
python
def make_plain():
def plain(x):
return x * 2
return plain
p = make_plain()
print(p.__closure__)None__closure__ нь None. Хоосон tuple ч биш, None. Үүргэвч огт бэлдэгдээгүй. Техникийн хувьд plain нь closure биш, зүгээр л өөр функцийн дотор тодорхойлогдсон функц. Closure гэдэг нэр зөвхөн гадаад хувьсагч барьж буй функцэд л хамаарна.
Жишээ 2: нэг гэр, олон хүүхэд, олон үүргэвч
Зүйрлэлийн хоёр дахь нарийн ширийн зүйл: гадна функцийг дуудах бүр шинэ гэр, шинэ хүүхэд, шинэ үүргэвч үүсгэнэ.
python
def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
with_vat = make_multiplier(1.1)
print(double(50), triple(50), with_vat(50))
print(double.__closure__[0].cell_contents)
print(triple.__closure__[0].cell_contents)
print(double.__closure__[0] is triple.__closure__[0])100 150 55.00000000000001
2
3
FalseTrace-ийг товч бичье. make_multiplier(2) дуудагдахад нэг гэр үүсч, factor = 2. multiply функц төрж, factor-ийн хайрцгийг үүргэвчиндээ хийнэ. Гэр хаагдана. Энэ функцийг double гэж нэрлэв. make_multiplier(3) дуудагдахад шинэ гэр, factor = 3, шинэ multiply функц, шинэ үүргэвч. Энэ нь triple. with_vat ч мөн адил, өөрийн хайрцагтай. Гурван функц нэг ижил кодыг хуваалцдаг ч гурван өөр үүргэвчтэй.
Сүүлийн мөр үүнийг шууд баталж байна: double ба triple-ийн хайрцгууд is шалгалтаар өөр объект. Хэрэв нэг үүргэвч хуваалцдаг байсан бол True гарах байсан. Гурав дахь мөрийн 55.00000000000001 бол closure-тэй холбоогүй, бутархай тооны тооцооллын ердийн нарийвчлалын онцлог. Мөнгөний тооцоонд round() хэрэглэхийг Course 1-2-оос санаж байгаа байх.
Энэ загварыг function factory буюу "функцийн үйлдвэр" гэж нэрлэдэг: тохиргоо авч, тэр тохиргоог үүргэвчиндээ авч явдаг шинэ функц үйлдвэрлэдэг функц. Хичээл 3.3-ын partial-ийг санаж байна уу? partial(apply_discount, rate=0.10) бол яг ижил санаа: тохиргоог бэхэлсэн шинэ функц. partial бол бэлэн хэрэгсэл бол closure нь тэр санааг өөрөө бүтээх арга. Үнэндээ partial-ийг closure-оор хэдэн мөрөнд бичиж болно.
Жишээ 3: үүргэвчинд хуулбар биш, хайрцаг байдаг
Одоо зүйрлэлийн хамгийн нарийн хэсэг. Үүргэвчинд хувьсагчийн утгын хуулбар биш, тэр хувьсагчийг хадгалдаг хайрцаг өөрөө байдаг. Хэрэв хайрцгийн доторх зүйл функц төрсний дараа, гэр хаагдахаас өмнө өөрчлөгдвөл функц шинэ утгыг харна.
python
def make_message():
text = "эхний утга"
def show():
return text
text = "сүүлийн утга"
return show
print(make_message()())сүүлийн утгаmake_message()() бичлэгт хоёр хос хаалт байна: эхнийх нь make_message-ийг дуудаж show-г авна, хоёр дахь нь тэр show-г шууд дуудна. Trace: make_message-ийн гэрт text = "эхний утга". show функц төрж, үүргэвчиндээ text-ийн хайрцгийг хийнэ. Анхаар: хайрцгийг, "эхний утга" гэсэн string-ийн хуулбарыг биш. Дараа нь гэрт text = "сүүлийн утга" ажиллахад ижил хайрцгийн агуулга солигдоно. show-ийн үүргэвчинд тэр хайрцаг байгаа тул show дуудагдахад "сүүлийн утга"-г харна. Хэрэв үүргэвчинд хуулбар байсан бол "эхний утга" гарах байсан.
Энэ нь сонирхолтой нарийн ширийн зүйл мэт санагдаж магадгүй ч Python-ий хамгийн алдартай урхиудын нэгийн шалтгаан юм:
python
def make_buttons():
buttons = []
for i in range(3):
def on_click():
return f"{i}-р товч дарагдлаа"
buttons.append(on_click)
return buttons
for button in make_buttons():
print(button())2-р товч дарагдлаа
2-р товч дарагдлаа
2-р товч дарагдлааБид 0, 1, 2 гэж хүлээсэн ч гурав нь бүгд 2 гэж хэлэв. Trace: make_buttons-ийн гэрт i гэсэн нэг хувьсагч, нэг хайрцаг бий. Loop-ийн эхний давталтад i = 0, on_click төрж, i-ийн хайрцгийг үүргэвчиндээ хийнэ. Хоёр дахь давталтад i = 1 болж, ижил хайрцгийн агуулга солигдоно. Шинэ on_click төрж, мөн ижил хайрцгийг үүргэвчиндээ хийнэ. Гурав дахь давталтад i = 2 болж, гурав дахь on_click ч ижил хайрцгийг авна. Loop дуусахад хайрцагт 2 байна. Гурван функц гурван өөр үүргэвчтэй ч гурвын доторх хайрцаг нэг. Функцүүд дуудагдах үед, loop аль эрт дууссан хойно, тэд бүгд хайрцгийг нээж 2-ыг харна.
Шийдэл нь давталт бүрт шинэ гэр үүсгэх, тэгвэл шинэ хайрцаг гарна. Жишээ 2-ын function factory яг үүнийг хийдэг:
python
def make_handler(number):
def on_click():
return f"{number}-р товч дарагдлаа"
return on_click
def make_buttons():
return [make_handler(i) for i in range(3)]
for button in make_buttons():
print(button())0-р товч дарагдлаа
1-р товч дарагдлаа
2-р товч дарагдлааmake_handler-ийг дуудах бүр шинэ гэр, шинэ number хайрцаг үүсгэнэ. Гурван on_click гурван өөр хайрцагтай, тус бүрд өөрийн тоо. Хэрэв та бусдын кодонд def on_click(i=i): эсвэл lambda i=i: ... гэсэн хачин бичлэг харвал энэ нь ижил урхийг тойрох өөр арга: анхдагч утга функц тодорхойлогдох мөчид тооцоологдож хадгалагддаг тул хуулбар болдог. Ажилладаг ч factory хувилбар санааг илүү тодорхой илэрхийлдэг.
Хүсвэл Жишээ 2-ын make_multiplier-ээс гурван функц үүсгээд, тус бүрийн __closure__[0].cell_contents-ийг хэвлэж, тэдгээр нь таны өгсөн тохиргоог үнэхээр барьж байгааг өөрийн нүдээр шалгаарай. Сонирхвол хоёр гадаад хувьсагч хэрэглэдэг closure бичээд __code__.co_freevars ба __closure__ хоёрын дарааллыг харьцуулж болно: нэрсийн дараалал ба хайрцгийн дараалал тохирч байна уу? Мөн товчны урхийн эхний хувилбарт print(buttons[0].__closure__[0] is buttons[2].__closure__[0]) гэж нэмж, гурван функц нэг хайрцаг хуваалцаж байгааг True-ээр баталж болно. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
TypeError: ‘NoneType’ object is not callable. “NoneType объектыг дуудах боломжгүй” гэсэн утгатай. Гадна функцийн төгсгөлд return greet мөрийг мартсан үед гарна. Функц return-гүйгээр дуусвал None буцаадаг тул hello = make_greeter("Сайн уу") нь hello-д None онооно. Дараа нь hello("Бат") гэхэд None-ыг дуудах гэж оролдоно. Traceback hello-г дуудсан мөрийг заах боловч алдааны жинхэнэ эх үүсвэр нь гадна функцийн төгсгөл. Шийдэл нь гадна функц дотоод функцээ буцааж байгааг шалгах.
TypeError: make_greeter.<locals>.greet() missing 1 required positional argument: ‘name’. “make_greeter-ийн локал greet()-д шаардлагатай ‘name’ argument дутуу байна” гэсэн утгатай. return greet биш return greet() гэж хаалттай бичсэн үед гарна. Энэ нь greet-ийг буцаахын оронд яг тэр мөчид argument-гүйгээр дуудах гэж оролдож байна. Мессеж дэх make_greeter.<locals>.greet нь бидний __closure__-ийг харахдаа уншсан бүтэн нэр бөгөөд аль дотоод функцэд алдаа гарсныг яг заадаг. Шийдэл: функцийг утга мэт буцаахдаа хаалт бичихгүй.
NameError: name ‘greet’ is not defined. “greet гэдэг нэр тодорхойлогдоогүй” гэсэн утгатай. Дотоод функцийг гадна функцийн гаднаас шууд нэрээр нь дуудах гэж оролдоход гарна: make_greeter-ийн дараа greet("Бат") гэж бичих. greet бол make_greeter-ийн гэрийн локал нэр бөгөөд гэр хаагдахад нэр ч алга болно. Функц объект амьд үлдэх цорын ганц зам нь return-ээр гадагш гарч, гадна талын нэрэнд оноогдох юм. LEGB-ийн хайлт дотроос гадагш явдаг, гаднаас дотогш хэзээ ч явдаггүй.
Бүх closure сүүлийн утгыг харах. Мессежгүй алдаа, Жишээ 3-ын товчны урхи. Loop дотор closure үүсгэхэд бүгд loop хувьсагчийн нэг хайрцгийг хуваалцаж, loop дууссаны дараах утгыг харна. Шинж тэмдэг нь: жагсаалтын бүх функц ижил үр дүн өгөх, ихэвчлэн хамгийн сүүлийн давталтынх. Шийдэл нь давталт бүрт шинэ гэр үүсгэдэг function factory ашиглах. Энэ урхи lambda-д ч яг адил хамаарна: [lambda: i for i in range(3)] дотор ч мөн бүгд 2 буцаана.
Python нэрсийг LEGB дарааллаар хайдаг: Local, Enclosing, Global, Built-in. Closure бол Enclosing түвшний хувьсагчдыг өөртэйгөө авч явдаг функц юм. Гадна функц дотоод функцээ буцаахад Python дотоод функцэд үүргэвч бэлдэж, түүний хэрэглэдэг гадаад хувьсагчдыг cell буюу хайрцагт хийж хадгална. Гадна функцийн frame хаагдсан ч хайрцгууд амьд үлдэнэ. Үүргэвчийг __closure__, нэрсийг __code__.co_freevars-аар нээж харж болно. Зөвхөн хэрэглэгдэх хувьсагчид үүргэвчинд орно. Гадна функцийн дуудлага бүр шинэ гэр, шинэ хайрцаг үүсгэнэ, энэ нь function factory загварын үндэс. Үүргэвчинд хуулбар биш хайрцаг байдаг тул loop дотор үүсгэсэн closure-ууд нэг хайрцгийг хуваалцаж, сүүлийн утгыг хардаг урхи бий. Шийдэл нь factory.
Энэ хичээлийн бүх closure үүргэвчнээсээ зөвхөн уншсан. Хэрэв closure хайрцгийн доторх зүйлийг өөрчлөх гэж оролдвол юу болох вэ? Жишээлбэл, дуудагдах бүрдээ нэгээр нэмэгддэг тоолуур. Хичээл 3.3-т бид ийм тоолуурыг global түлхүүр үгээр бичсэн бөгөөд түүнийг илүү цэвэр шийдлээр солино гэж амласан. Дараагийн хичээлд бид closure дотроос үүргэвчийн хайрцгийг өөрчлөхөд Python ямар хачин алдаа өгдгийг харж, түүнийг Монгол хэлээр уншаад, nonlocal түлхүүр үгээр засах болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.