Ачааллж байна...
Курс 1-д та функц дотор хувьсагч үүсгэж байсан бөгөөд тэр нь ажилладаг байсан. Гэхдээ хэзээ нэгэн цагт та функц дотор үүсгэсэн хувьсагчаа функцээс гадна ашиглах гэж оролдоод, "энэ нэр байхгүй байна" гэсэн алдаа авч байсан байх. Тэр үед та магадгүй "аан, ингэж болдоггүй юм байна" гэж хүлээж аваад цааш явсан байх. Одоо бид тэр зан төлөвийн ЯАГААДЫГ бүрэн ойлгоно.
Тэр асуултын хариулт нь scope гэдэг ойлголт юм. Scope гэдэг нь "нэг хувьсагч ХААНААС харагдаж байна вэ" гэсэн асуултын хариулт бөгөөд Python-д маш тодорхой дүрмүүд байдаг. Энэ нэр томьёог англиар нь хэвээр ашиглана, учир нь та documentation, алдааны мессеж, бусдын кодод яг ийм үгээр л уулзана. Монголоор ойролцоогоор "хамрах хүрээ" гэж орчуулж болно.
Энэ хичээл бол Бүлэг 3-ын хамгийн ойлголтын шинжтэй хичээл. Шинэ бичлэг бараг байхгүй, харин ард нь юу болж байгааг ойлгох тухай юм. Тэгсэн ч гэсэн энэ нь маш чухал: Бүлэг 10-д decorator бичихэд scope-ыг ойлгоогүй бол код нь илбэ мэт харагдана. Мөн энэ хичээлд бид Python-ы хамгийн төөрөгдүүлдэг алдааны нэг болох UnboundLocalError-ыг задлан тайлбарлана.
Бүлэг 1-ийн virtual environment-ийн зүйрлэл рүү буцъя — тэнд бид тусдаа өрөөнүүдийн тухай ярьсан. Одоо ижил төстэй, гэхдээ өөр зүйрлэл ашиглая.
Таны байшин байна гэж төсөөлье. Байшингийн ТӨВ ТАНХИМД том ширээ байгаа бөгөөд түүн дээр байгаа зүйлсийг байшингийн хэн ч харж чадна. Хэрэв та тэнд ном тавьбал, ямар ч өрөөнөөс гарч ирсэн хүн тэр номыг харна.
Одоо байшинд өрөөнүүд бий. Өрөө бүрд өөрийн жижиг ширээ байна. Та нэг өрөөнд ороод, өрөөний ширээн дээр цаас тавьбал — тэр цаасыг зөвхөн ТЭР ӨРӨӨНД байгаа хүн л харна. Гадна талын хүн харахгүй. Түүнээс ч сонирхолтой нь: та өрөөнөөс гарахад тэр цаас алга болно. Өрөө хоосорч, дараагийн удаа орох үед шинэ, хоосон ширээ угтана.
Python-д яг ийм. Программын хамгийн гадна талд үүсгэсэн хувьсагч нь global scope-д байна — түүнийг хаанаас ч уншиж болно. Функц дотор үүсгэсэн хувьсагч нь local scope-д байна — түүнийг зөвхөн тэр функц дотроос харна, функц дуусахад устана.
python
def mendchilekh():
mesej = "Сайн байна уу"
print(mesej)
mendchilekh()
print(mesej)Эхний хоёр мөр ажиллана:
Сайн байна ууДараа нь алдаа:
Traceback (most recent call last):
File "main.py", line 7, in <module>
print(mesej)
NameError: name 'mesej' is not definedNameError: name 'mesej' is not defined гэдэг нь "mesej нэртэй зүйл олдсонгүй" гэсэн үг.
Юу болов? mesej хувьсагч нь функц ДОТОР үүссэн. Тэр нь local хувьсагч — зөвхөн функцийн өрөөнд оршин байсан. Функц ажиллаж дуусахад тэр өрөө хоосорч, mesej устсан. Тиймээс функцээс гадна print(mesej) гэж дуудахад Python тийм нэртэй юу ч олохгүй байна.
Энэ бол алдаа биш, харин ЗОРИУДЫН зохион байгуулалт. Хэрэв бүх функцийн дотоод хувьсагч гадагшаа алдагддаг байсан бол зуун функцтэй программ дотор хувьсагчийн нэрс хоорондоо мөргөлдөж, хэн ямар нэр ашигласныг хянах боломжгүй болох байсан. Local scope нь функц бүрийг тусдаа, аюулгүй ажлын талбар болгож өгдөг.
Үүний бас нэг сайхан үр дагавар: хоёр өөр функц ижил нэртэй хувьсагч ашиглаж болно, тэд хоорондоо огт мөргөлдөхгүй.
python
def negdugeer():
too = 10
print(f"negdugeer дотор: {too}")
def hoyordugaar():
too = 999
print(f"hoyordugaar дотор: {too}")
negdugeer()
hoyordugaar()
negdugeer()Үр дүн:
negdugeer дотор: 10
hoyordugaar дотор: 999
negdugeer дотор: 10Хоёр функц хоёулаа too гэсэн нэр ашиглаж байгаа ч тэдгээр нь ТЭС ӨӨР хоёр хувьсагч юм — өөр өөр өрөөний ширээн дээрх өөр өөр цаас. Нэг нь нөгөөгөө өөрчлөхгүй. Мөн гурав дахь дуудалт дээр negdugeer нь дахин 10 -ыг харуулж байна — өмнөх дуудалтын үлдэгдэл байхгүй, өрөө бүрэн шинэчлэгдсэн.
Одоо эсрэг чиглэл. Функц дотроос ГАДНА талын хувьсагчийг уншиж болох уу?
python
mesej = "Сайн байна уу"
def mendchilekh():
print(mesej)
mendchilekh()Үр дүн:
Сайн байна ууАжиллаж байна. mesej нь программын хамгийн гадна талд, global scope-д үүссэн. Функц дотроос түүнийг УНШИЖ чадаж байна.
Яагаад ингэж байна вэ? Python нь нэрийг хайхдаа ТОДОРХОЙ ДАРААЛАЛ баримталдаг:
Эхлээд local scope-д хайна — "энэ функц дотор ийм нэр байна уу?"
Олдохгүй бол global scope-д хайна — "программын гадна талд ийм нэр байна уу?"
Олдохгүй бол Python-ы built-in нэрсээс хайна — print, len, sum гэх мэт.
Хаанаас ч олдохгүй бол NameError гаргана.
Дээрх жишээнд mesej нь функц дотор байхгүй тул Python гадагшаа хараад global scope-оос олж авсан.
Зүйрлэлээр: та өрөөнд зогсоод хэрэгтэй зүйлээ хайж байна. Эхлээд өрөөнийхөө ширээг хараарай. Тэнд байхгүй бол хаалгаа онгойлгоод төв танхим руу хараарай. Тэнд ч байхгүй бол... алдаа.
Чухал зүйл: харагдац НЭГ ЧИГЛЭЛТЭЙ. Функц гадагшаа харж чадна, гэхдээ гадна тал функц рүү харж чаддаггүй. Өрөөнд байгаа хүн танхим руу харж чадна, гэхдээ танхимд байгаа хүн хаалттай өрөө рүү харж чадахгүй.
Одоо хамгийн сонирхолтой, хамгийн төөрөгдүүлдэг хэсэг рүү орно.
Уншиж чадаж байгаа тул өөрчилж ч чадах байх гэж бодоод үзье:
python
tootsuu = 0
def nemekh():
tootsuu = tootsuu + 1
print(f"Функц дотор: {tootsuu}")
nemekh()
print(f"Функц гадна: {tootsuu}")Хүлээгдэж байсан үр дүн нь 1 ба 1 байх ёстой мэт санагдана. Гэвч:
Traceback (most recent call last):
File "main.py", line 9, in <module>
nemekh()
File "main.py", line 5, in nemekh
tootsuu = tootsuu + 1
UnboundLocalError: cannot access local variable 'tootsuu' where it is not associated with a valueUnboundLocalError: cannot access local variable 'tootsuu' where it is not associated with a value гэдэг нь "tootsuu нэртэй local хувьсагчид хандаж байна, гэхдээ тэр хувьсагчид утга олгогдоогүй байна" гэсэн үг.
Энэ мессеж эхэндээ утгагүй мэт санагдана. Бид tootsuu = 0 гэж дээр нь бичсэн шүү дээ! Тэгээд яагаад "утга олгогдоогүй" гэж байна вэ? Энд нэг чухал дүрэм нуугдаж байгаа тул удаашруулъя.
Дүрэм: хэрэв функц дотор ямар нэг нэрэнд УТГА ОЛГОЖ байвал (тэнцүү тэмдгийн зүүн талд гарч ирвэл), Python тэр нэрийг БҮХЭЛ ФУНКЦИЙН ТУРШ local гэж үзнэ.
Энэ шийдвэрийг Python нь функцийг ажиллуулахаас ӨМНӨ, кодыг уншиж байх үедээ гаргадаг. Тэр tootsuu = ... гэсэн мөрийг хараад, "аан, энэ функц tootsuu нэртэй local хувьсагчтай юм байна" гэж шийднэ. Тэр шийдвэр нь функцийн БҮХ мөрөнд, тэр дундаа тэнцүү тэмдгийн БАРУУН талд ч хамаарна.
Тэгэхээр tootsuu = tootsuu + 1 гэсэн мөр ажиллах үед юу болох вэ? Python эхлээд баруун талыг тооцоолох гэж оролдоно. Баруун талд tootsuu гэсэн нэр байна. Python түүнийг LOCAL гэж үзэж байгаа тул local хувьсагч дотроос хайна. Гэвч тэр local хувьсагчид хараахан утга олгогдоогүй байна — олгох гэж байгаа мөр нь яг энэ мөр өөрөө! Тиймээс Python "утга байхгүй хувьсагчийг ашиглах гэж байна" гэж алдаа гаргаж байна.
Global tootsuu = 0 нь энэ бүх үйл явцад ОГТ оролцоогүй. Python түүн рүү харах ч үгүй, учир нь tootsuu нь local гэж аль хэдийн шийдэгдсэн.
Энэ бол Python-ы хамгийн олон эхлэгчийг зовоосон алдаануудын нэг. Хэрэв та үүнийг эхний уншлагаар бүрэн ойлгоогүй бол санаа зовох хэрэггүй — дахин уншаарай, эсвэл жишээг өөрийн терминал дээр ажиллуулж үзээрэй. Ойлголт нь ирнэ.
Дараах ХОЁР функцийг харьцуулъя. Ялгаа нь ганцхан тэнцүү тэмдэг байна.
python
tootsuu = 0
def zuvkhun_unshikh():
print(f"Уншиж байна: {tootsuu}")
def uurchlukh_gej_oroldokh():
tootsuu = tootsuu + 1
zuvkhun_unshikh()Уншиж байна: 0Эхний функц АЖИЛЛАНА. Учир нь тэр tootsuu-д утга ОЛГОХГҮЙ, зөвхөн уншиж байна. Python түүнийг local гэж үзэхгүй тул гадагшаа хараад global-аас олж авна.
Хоёр дахь функц ажиллуулбал алдаа гарна — учир нь тэнд утга олголт байгаа тул tootsuu нь local болж хувирсан.
Санах арга: Уншихад global хангалттай. Бичихэд Python нь автоматаар local гэж үзнэ.
Хэрэв та үнэхээр global хувьсагчийг функц дотроос өөрчлөхийг хүсвэл, Python-д тийм арга бий. global гэсэн түлхүүр үгийг ашиглана.
python
tootsuu = 0
def nemekh():
global tootsuu
tootsuu = tootsuu + 1
print(f"Функц дотор: {tootsuu}")
nemekh()
nemekh()
nemekh()
print(f"Функц гадна: {tootsuu}")Үр дүн:
Функц дотор: 1
Функц дотор: 2
Функц дотор: 3
Функц гадна: 3global tootsuu гэсэн мөр нь Python-д ингэж хэлж байна: "энэ функц дотор tootsuu гэсэн нэр гарч ирвэл, шинэ local хувьсагч бүү үүсгэ — ГАДНА талын, global-ыг л ашигла." Одоо утга олголт нь global хувьсагчид шууд нөлөөлж байна.
Тэр мөр нь функцийн ЭХЭНД, tootsuu -г ашиглахаас ӨМНӨ байх ёстой. Мөн анзаараарай, global tootsuu = 0 гэж бичдэггүй — зөвхөн нэрийг л зарладаг, утга олголт нь тусдаа мөрөнд явна.
Одоо кодыг ажиллуулахад юу болж байгааг харъя: гурван дуудалт бүр нэг л global хувьсагчийг өөрчилж байна. Функц дуусахад утга нь устахгүй, хэвээр үлдэж байна. Тиймээс гурван дуудалтын дараа tootsuu нь 3 болсон.
global -ыг бараг хэрэглэдэггүй вэОдоо би танд чухал зүйл хэлье: дээрх кодыг би ЗӨВХӨН global юу хийдгийг харуулах гэж бичсэн. Практикт ингэж бичих ёсгүй.
Мэргэжлийн Python код дотор global түлхүүр үг бараг харагддаггүй. Олон туршлагатай программист түүнийг олон жил бичээгүй байдаг. Яагаад?
Шалтгаан 1: функц урьдчилан таамаглах боломжгүй болдог. Ердийн функц бол нэг жор шиг — та ямар орц өгснөөс хамаарч ямар үр дүн гарахыг мэднэ. global ашигласан функц бол өөр зүйл: тэр нь дуудагдах бүрдээ өөр үр дүн өгч болно, учир нь тэр гадна талын өөрчлөгдөж байгаа төлөвөөс хамаарна. Дээрх жишээнд nemekh() нь эхний удаа 1, хоёр дахь удаа 2 хэвлэсэн — ижил дуудалт, өөр үр дүн.
Шалтгаан 2: алдаа хайхад аймшигтай. Программ таван зуун мөр урт болоод, tootsuu нь буруу утгатай болсон гэж бодъё. Хэн түүнийг өөрчилсөн бэ? Хариулт: global tootsuu гэж бичсэн ЛЮБОЙ функц. Та бүх функцийг нэг нэгээр нь шалгах хэрэгтэй болно. Харин local хувьсагч бол зөвхөн нэг функц дотор л өөрчлөгдөж чадна — хайх талбар нь маш жижиг.
Шалтгаан 3: тест хийхэд хэцүү. Функцийг тусад нь туршихын тулд та global төлөвийг бэлдэх, дараа нь цэвэрлэх хэрэгтэй болно.
Зөв шийдэл нь юу вэ? Утгыг ДАМЖУУЛЖ, БУЦААХ.
python
def nemekh(tootsuu):
return tootsuu + 1
tootsuu = 0
tootsuu = nemekh(tootsuu)
tootsuu = nemekh(tootsuu)
tootsuu = nemekh(tootsuu)
print(f"Эцсийн утга: {tootsuu}")Үр дүн:
Эцсийн утга: 3Ижил үр дүн, гэхдээ одоо nemekh функц нь цэвэр болсон. Тэр нь орц авч, үр дүн буцаадаг, өөр юу ч хийдэггүй. Ижил орц өгвөл ҮРГЭЛЖ ижил үр дүн буцаана. Гадна ертөнцөд нөлөөлөхгүй. Түүнийг уншихад юу хийхийг нь бүрэн мэдэж байна.
Энэ бол мэргэжлийн Python-ы гол зарчмуудын нэг: функц нь орцоороо дамжуулан ярьж, буцаалтаараа дамжуулан хариулна. Гадна талын хувьсагчийг чимээгүй өөрчлөх нь муу зан.
Практик дүрэм: global -ыг бүү хэрэглэ. Хэрэв танд функцууд хооронд төлөв хуваалцах хэрэгтэй болвол, тэр нь ихэвчлэн class хэрэгтэй байгаагийн шинж (Курс 1-ээс санаж байгаа байх, Бүлэг 7-9-д гүнзгийрүүлнэ).
Тэгвэл яагаад би global -ыг заасан юм бэ? Хоёр шалтгаанаар. Нэгдүгээрт, та бусдын кодод (ялангуяа хуучин, эсвэл сурах бичгийн жишээ кодод) түүнийг харах болно — тэр үед юу болж байгааг ойлгох ёстой. Хоёрдугаарт, global -ыг ойлгосноор UnboundLocalError -ын учир шалтгаан тодорхой болдог.
Одоо нэг нарийн зүйлийг тодруулъя, учир нь энэ нь эхлэгчдийг байнга төөрөгдүүлдэг.
Дээр бид "функц дотроос global хувьсагчийг өөрчилж болохгүй" гэж хэлсэн. Гэтэл дараах код АЖИЛЛАНА:
python
ajluud = []
def ajil_nemekh(ner):
ajluud.append(ner)
ajil_nemekh("Сүү авах")
ajil_nemekh("Тайлан бичих")
print(ajluud)Үр дүн:
['Сүү авах', 'Тайлан бичих']global бичээгүй ч global жагсаалт өөрчлөгдсөн байна. Зөрчил үү?
Үгүй. Ялгааг сайн хараарай. Функц дотор бид ajluud = ... гэж УТГА ОЛГООГҮЙ. Бид ajluud.append(...) гэж дуудсан — өөрөөр хэлбэл байгаа объектын дотор талыг өөрчилсөн, шинэ объект үүсгээгүй.
Дүрэм эргэн санацгаая: утга олголт (тэнцүү тэмдэг) л Python-д "энэ нэр local" гэж бодуулдаг. .append() бол утга олголт биш, method дуудалт юм. Тиймээс Python нь ajluud нэрийг ердийн журмаар хайж, global-аас олж, тэр объект дээр append дуудна.
Зүйрлэлээр: төв танхимд байгаа хайрцгийг та өрөөнөөсөө гарч ирэн НЭЭЖ, дотор нь юм хийж чадна — хайрцаг өөрөө хэвээрээ, зөвхөн агуулга нь өөрчлөгдөж байна. Гэхдээ та тэр хайрцгийг ӨӨР хайрцгаар СОЛИХ гэж оролдвол (ajluud = [] гэж дахин олгох) — тэр бол өөр үйлдэл, түүнийг хийхэд global хэрэгтэй.
Ялгааг харуулъя:
python
ajluud = ["хуучин ажил"]
def sanamsargui_solikh():
ajluud = [] # энэ нь ШИНЭ local жагсаалт үүсгэнэ
ajluud.append("шинэ")
print(f"Функц дотор: {ajluud}")
sanamsargui_solikh()
print(f"Функц гадна: {ajluud}")Үр дүн:
Функц дотор: ['шинэ']
Функц гадна: ['хуучин ажил']Global жагсаалт хөндөгдөөгүй. Функц дотор ajluud = [] гэсэн мөр нь ШИНЭ, local жагсаалт үүсгэсэн бөгөөд бүх дараагийн үйлдэл түүн дээр л явсан. Функц дуусахад тэр local жагсаалт устсан.
Энэ ялгаа нарийн, гэхдээ маш чухал. Мэргэжлийн код бичихдээ энэ хоёрыг андуурах нь чимээгүй алдаанд хүргэдэг. Хамгийн аюулгүй зөвлөгөө нь өмнөхтэй ижил: global хувьсагч дээр функц дотроос ажиллахаас аль болох зайлсхий. Жагсаалтаа параметрээр дамжуулж, өөрчилсөн хувилбарыг нь буцаа.
Хэрэв сонирхвол: UnboundLocalError-ыг өөрөө үүсгэж үзээрэй. Гурав дахь жишээний кодыг өөрийн терминал дээр ажиллуулж, алдааг өөрийн нүдээр хараарай. Дараа нь global нэмээд ажиллахыг нь харж, эцэст нь global-гүйгээр, утгыг дамжуулж буцаах цэвэр аргаар дахин бичээд үзээрэй. Гурван хувилбарыг өөрийн гараар бичих нь энэ хичээлийн бүх агуулгыг бататгана.
Мөн хүсвэл: Курс 1-д бичсэн ямар нэг программаа нээгээд, global гэсэн үг байгаа эсэхийг хайж үзээрэй. Хэрэв байвал түүнийг арилгаж, утга дамжуулах аргаар дахин бичиж болох эсэхийг бодож үзээрэй.
1. Local хувьсагчид гаднаас хандах
python
def f():
x = 10
f()
print(x)Traceback (most recent call last):
File "main.py", line 6, in <module>
print(x)
NameError: name 'x' is not definedLocal хувьсагч функц дуусахад устдаг. Хэрэв танд утга гадна хэрэгтэй бол түүнийг буцаа: return x, дараа нь x = f().
2. UnboundLocalError
UnboundLocalError: cannot access local variable 'tootsuu' where it is not associated with a valueДээр дэлгэрэнгүй үзсэн. Шалтгаан: функц дотор тэр нэрэнд утга олгосон тул Python түүнийг local гэж үзсэн, гэтэл уншихыг оролдох үед утга нь хараахан алга. Гурван боломжит засвар:
Утгыг параметрээр дамжуул (хамгийн зөв): def nemekh(tootsuu): return tootsuu + 1
global tootsuu гэж зарла (ажиллана, гэхдээ зөвлөхгүй)
Функц дотор өөр нэр ашигла
3. Global-ыг өөрчилсөн гэж эндүүрэх
python
tootsuu = 0
def nemekh():
tootsuu = 100
nemekh()
print(tootsuu)0Алдаа гарахгүй, гэхдээ юу ч өөрчлөгдөөгүй. Функц дотор ШИНЭ local хувьсагч үүсээд, функц дуусахад устсан. Global tootsuu хөндөгдөөгүй. Ийм алдаа хамгийн аюултай нь — программ ажиллаж байгаа мэт харагдаж, гэхдээ юу ч хийхгүй.
4. Built-in нэрийг дарж бичих
python
list = [1, 2, 3]
shine = list("abc")Traceback (most recent call last):
File "main.py", line 3, in <module>
shine = list("abc")
TypeError: 'list' object is not callableTypeError: 'list' object is not callable гэдэг нь "list объектыг функц шиг дуудаж болохгүй" гэсэн үг. Юу болов? Та list гэсэн нэрийг өөрийн жагсаалтаараа ДАРЖ БИЧСЭН. Python-ы нэр хайх дараалалд global scope нь built-in-ээс ӨМНӨ ирдэг тул одоо list гэдэг нь Python-ы функц биш, таны жагсаалт болсон.
Ижил урхи str, dict, sum, max, type, id гэх мэт бүх built-in нэрсэд бий. Засвар: built-in нэрсийг хувьсагчийн нэр болгож бүү ашигла. Ихэнх код бичих орчин (VS Code гэх мэт) тэдгээрийг өөр өнгөөр тодотгодог тул анзаарахад хялбар.
Scope гэдэг нь "нэг хувьсагч хаанаас харагдаж байна вэ" гэсэн асуулт.
Функц дотор үүсгэсэн хувьсагч нь local — зөвхөн тэр функцээс харагдана, функц дуусахад устана.
Программын гадна талын хувьсагч нь global — хаанаас ч УНШИГДАНА.
Python нэр хайх дараалал: local → global → built-in → NameError.
Харагдац нэг чиглэлтэй: функц гадагшаа харна, гадна тал функц рүү харахгүй.
Гол дүрэм: функц дотор нэрэнд УТГА ОЛГОВОЛ, Python тэр нэрийг бүхэл функцийн турш local гэж үзнэ. Тиймээс tootsuu = tootsuu + 1 нь UnboundLocalError өгдөг.
global түлхүүр үг нь энэ дүрмийг цуцалдаг — гэхдээ түүнийг бараг хэзээ ч ашиглах ёсгүй.
Зөв арга: утгыг параметрээр дамжуул, үр дүнг буцаа. Ижил орц → ижил үр дүн. Функц урьдчилан таамаглах боломжтой байх ёстой.
.append() гэх мэт method дуудалт нь утга олголт БИШ — тиймээс global жагсаалтыг global бичихгүйгээр өөрчилж чадна. Гэхдээ түүнийг ч бас зайлсхийх нь дээр.
Дараагийн хичээл бол Бүлэг 3-ын хамгийн чухал хичээл. Тэнд бид нэг гэнэтийн санаа руу орно: функц бол өөрөө УТГА юм. Түүнийг хувьсагчид олгож болно, жагсаалтад хийж болно, өөр функцэд аргумент болгож дамжуулж болно. Энэ санаа эхэндээ хачирхалтай санагдана, гэхдээ тэр нь Бүлэг 10-ын decorator руу хөтлөх ЯГ ТЭР хаалга юм — @ тэмдгийн ард нуугдаж байгаа бүх илбэ нь энэ нэг санаанаас урган гардаг.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.