Ачааллж байна...
Түвшин 2-ын сүүлийн бүлэгт тавтай морил. Өмнөх хоёр бүлэгт бид функцүүдийг дотроос нь эзэмшсэн: closure, decorator, @ тэмдгийн бүх давхарга. Энэ бүлэг өөр нэг танил тэмдэг рүү шилжинэ: with. Course 1-ээс хойш та файлыг with open(...) as f: гэж нээж ирсэн. Course 3-т өгөгдлийн сангийн session, HTTP client зэргийг with-ээр удирдсан байх магадлалтай. Хичээл 2.3-т бид generator дотор with блок хөлдсөн хэвээр файлыг нээлттэй байлгадгийг харсан. Энэ бүх үед with нэг амлалтыг биелүүлдэг байв: блок хэрхэн дууссанаас үл хамааран цэвэрлэгээ заавал хийгдэнэ. Энэ хичээлд тэр амлалтын ард юу байгааг нээнэ.
Хариулт Бүлэг 1-ийн хариулттай маш төстэй. Тэнд for loop-ийн ард __iter__ ба __next__ гэсэн хоёр тусгай method-той протокол байгааг олсон. with-ийн ард ч мөн хоёр method-той протокол бий: __enter__ ба __exit__. Энэ протоколыг дагадаг объектыг context manager гэж нэрлэдэг. Англи үг “context” нь "орчин, нөхцөл" гэсэн утгатай. Context manager бол тодорхой орчныг бэлдэж, блок дуусахад түүнийг цэвэрлэдэг объект: файлыг нээж, дараа нь хаадаг, түр хавтас үүсгэж, дараа нь устгадаг.
Энэ хичээл context manager-ийг хэрэглэгчийн талаас бүрэн ойлгоход зориулагдсан: with ямар дарааллаар юу дууддаг вэ, as-ийн ард ямар утга ирдэг вэ, алдаа гарахад юу болдог вэ. Хамгийн чухал нь with ямар баталгаа өгдгийг бид нүдээр харж, туршилтаар батална. Дараагийн хоёр хичээлд бид өөрсдийн context manager-ийг бүтээнэ: эхлээд class-аар, дараа нь Бүлэг 2-ын generator-ийн тусламжтайгаар гурван мөрөнд.
Нууцлалтай серверийн өрөөг төсөөлөөд үзээрэй. Хаалганы гадна харуул сууж, зузаан дэвтэр хөтөлдөг. Өрөөнд орох хүн бүр харуулын дэвтэрт нэрээ бичиж, түлхүүр авна. Өрөөнөөс гарахад харуул гарсан цагийг тэмдэглэж, түлхүүрийг буцааж авна. Энэ бол өрөөний хатуу дүрэм.
Энэ дүрмийн хамгийн чухал хэсэг бол "хэрхэн гарсан нь хамаагүй" гэсэн заалт. Хүн ажлаа дуусгаад тайван гарч болно. Галын дохио дуугарахад гүйж гарч болно. Ажлын явцад бие нь муудаж, хамт олон түүнийг дамнуурган дээр гаргаж болно. Аль ч тохиолдолд харуул гарсныг тэмдэглэж, түлхүүрийг хураана. Түлхүүр хэзээ ч хэний ч халаасанд мартагдахгүй. Өрөөний нууцлал энэ баталгаан дээр тулгуурладаг.
with блок бол тэр өрөө. __enter__ бол орохдоо нэр бичих, түлхүүр авах. __exit__ бол гарахдаа тэмдэглүүлж, түлхүүрээ буцаах. Блокоос гарах зам хэд хэдэн бий: хэвийн дуусах, алдаа шидэгдэх, функцийн return, loop-ийн break. with аль ч замаар гарсан __exit__ дуудагдахыг баталгаажуулна. Файлын хувьд "түлхүүр буцаах" нь файлыг хаах, түр хавтасны хувьд хавтсыг устгах.
Эхлээд файл объект context manager протоколыг дагадаг эсэхийг Бүлэг 1-ийн аргаар шалгая:
python
f = open("notes.txt", "w", encoding="utf-8")
print(hasattr(f, "__enter__"))
print(hasattr(f, "__exit__"))
print(f.__enter__() is f)
f.close()True
True
TrueФайл объект хоёр method-тай. Гурав дахь мөр сонирхолтой: файлын __enter__ өөрийгөө буцаадаг. Бүлэг 1-д iterator-ийн __iter__ өөрийгөө буцаадагтай төстэй. Энэ нь with open(...) as f дахь f яагаад файл объект өөрөө байдгийг тайлбарлана. Удахгүй үүнийг илүү нарийн харна.
Одоо баталгааг шалгая. Файлд бичих явцад санаатайгаар алдаа шидье:
python
try:
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("эхний мөр\n")
print("Блок дотор, хаагдсан уу:", f.closed)
raise ValueError("бичих явцад алдаа гарлаа")
f.write("энэ мөр хэзээ ч бичигдэхгүй\n")
except ValueError as error:
print("Барьсан:", error)
print("Блокийн дараа, хаагдсан уу:", f.closed)Блок дотор, хаагдсан уу: False
Барьсан: бичих явцад алдаа гарлаа
Блокийн дараа, хаагдсан уу: Truef.closed нь файл хаагдсан эсэхийг True/False-оор хэлдэг attribute. Блок дотор файл нээлттэй байв. Дараа нь ValueError шидэгдэж, блокийн үлдсэн хэсэг алгасагдав. Гэвч алдаа with-ээс гарч, гадна талын except-д хүрэхээс өмнө файл хаагдсан. Сүүлийн мөр үүнийг баталж байна. Алдаа замдаа серверийн өрөөний харуулын хажуугаар өнгөрч, харуул түлхүүрийг хураасан. Мөн "эхний мөр" файлд бичигдсэн байна: хаах үйлдэл буферт хуримтлагдсан өгөгдлийг диск рүү бичдэг.
Харьцуулахын тулд with-гүйгээр ижил зүйл хийе:
python
try:
f = open("notes.txt", "w", encoding="utf-8")
f.write("эхний мөр\n")
raise ValueError("бичих явцад алдаа гарлаа")
f.close()
except ValueError as error:
print("Барьсан:", error)
print("Хаагдсан уу:", f.closed)Барьсан: бичих явцад алдаа гарлаа
Хаагдсан уу: Falsef.close() мөр алдааны дараа байсан тул хэзээ ч ажиллаагүй. Файл нээлттэй үлдсэн. Энэ жижиг скриптэд програм дуусахад Python файлыг эцэст нь хаана. Гэвч олон цаг ажилладаг сервер програмд хүсэлт бүрт ийм алдаа гарвал нээлттэй файлууд хуримтлагдаж, үйлдлийн систем "хэт олон файл нээлттэй байна" гэсэн алдаа өгөх хүртэл өснө. Бичигдсэн өгөгдөл ч диск рүү бүрэн бичигдээгүй байж болно. Энэ бол with шийддэг асуудал.
with-ийн дараалал файлын хувьд нүдэнд харагддаггүй. Үүнийг харахын тулд протоколын хоёр method-ыг хэвлэлттэйгээр бичсэн маш жижиг "тагнуул" class ашиглая. Class-аар context manager бичихийг дараагийн хичээлд бүрэн судлах тул одоохондоо зөвхөн хэвлэлтүүдийг дагаарай.
python
class Spy:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f" [{self.name}] __enter__ дуудагдлаа")
return f"{self.name}-ийн түлхүүр"
def __exit__(self, exc_type, exc_value, traceback):
status = exc_type.__name__ if exc_type else "алдаагүй"
print(f" [{self.name}] __exit__ дуудагдлаа: {status}")
print("with-ийн өмнө")
with Spy("Өрөө") as key:
print(" блок дотор, as-ийн утга:", key)
print("with-ийн дараа")with-ийн өмнө
[Өрөө] __enter__ дуудагдлаа
блок дотор, as-ийн утга: Өрөө-ийн түлхүүр
[Өрөө] __exit__ дуудагдлаа: алдаагүй
with-ийн дарааАлхам алхмаар:
Алхам 1 (Spy("Өрөө")). with мөрийн илэрхийлэл бодогдож, Spy объект үүснэ. Үүнийг manager гэж нэрлэе. Энэ бол context manager өөрөө.
Алхам 2 (__enter__). Python manager-ийн __enter__-ийг дуудна. Тэр хэвлэж, "Өрөө-ийн түлхүүр" гэсэн string буцаана.
Алхам 3 (as key). __enter__-ийн буцаасан утга key нэрэнд оноогдоно. Анхаар: key нь manager биш, __enter__-ийн үр дүн. Spy-ийн хувьд эдгээр нь санаатайгаар өөр байлгасан бөгөөд энэ ялгааг Жишээ 2-т бодит хэрэгсэл дээр харна.
Алхам 4 (блок). Блокийн бие ажиллана.
Алхам 5 (__exit__). Блок хэвийн дууссан тул Python manager-ийн __exit__-ийг гурван None argument-тай дуудна. Spy "алдаагүй" гэж хэвлэнэ.
Одоо блок дотор алдаа шидье:
python
try:
with Spy("Өрөө") as key:
print(" блок эхэллээ")
raise KeyError("тоноглол олдсонгүй")
print(" энэ мөр ажиллахгүй")
except KeyError as error:
print("Гадна барьсан:", error) [Өрөө] __enter__ дуудагдлаа
блок эхэллээ
[Өрөө] __exit__ дуудагдлаа: KeyError
Гадна барьсан: 'тоноглол олдсонгүй'Алхам 4-т KeyError шидэгдэж, блокийн үлдсэн мөр алгасагдана. Гэвч алдаа гадагш гарахаас өмнө Python manager-ийн __exit__-ийг дуудаж, гурван argument-аар алдааны мэдээллийг өгнө: алдааны төрөл KeyError, алдааны объект өөрөө, traceback. Spy төрлийн нэрийг хэвлэв. __exit__ юу ч буцаагаагүй буюу None буцаасан тул Python алдааг үргэлжлүүлэн гадагш дамжуулж, гадна талын except түүнийг барив. __exit__ алдааг "залгиж" болох эсэх, гурван argument-ын нарийн утга, энэ бүгд дараагийн хичээлийн сэдэв. Энэ хичээлд гол зүйл нь: алдаа гарсан ч __exit__ дуудагдсан бөгөөд алдааны тухай мэдээлэлтэйгээр дуудагдсан.
with-ийг try/finally болгон задлах
Бүлэг 1-д бид for loop-ийг while, try, next болгон задалж, for-ийн бүх нууцыг ил гаргасан. with-ийг ч ижил аргаар задалж болно. Нарийн ширийн бүх зүйлийг нь орхиж, гол бүтцийг бичвэл:
python
manager = Spy("Өрөө")
key = manager.__enter__()
try:
print(" блок дотор:", key)
finally:
manager.__exit__(None, None, None)Course 2-оос танил finally блок нь try блок хэрхэн дууссанаас үл хамааран заавал ажилладаг: хэвийн дууссан, алдаа шидсэн, return эсвэл break хийсэн. with бол үндсэндээ “enter-ийг дуудаад, блокийг try дотор хийж, exit-ийг finally дотор дуудах” гэсэн загварын товчлол. Жинхэнэ with үүнээс арай нарийн: алдаа гарсан үед __exit__-д None-ын оронд алдааны мэдээллийг өгдөг, мөн __exit__-ийн буцаах утгыг шалгадаг. Гэвч баталгааны эх үүсвэр нь finally. Файлын хувьд with-гүй хамгийн найдвартай код нь:
python
f = open("notes.txt", "w", encoding="utf-8")
try:
f.write("эхний мөр\n")
finally:
f.close()Энэ ажилладаг бөгөөд with бий болохоос өмнө Python хөгжүүлэгчид яг ингэж бичдэг байсан. with нь хоёр давуу талтай. Нэгдүгээрт, богино бөгөөд цэвэрлэгээг мартах боломжгүй: та close() бичихгүй, тэр context manager-т суусан. Хоёрдугаарт, цэвэрлэгээний логик нэг газарт, context manager дотор амьдарна: файлыг хэрхэн хаах нь файл объектын асуудал, таных биш.
Жишээ 1: return ба break ч хаалгаар гардаг
Алдаа бол блокоос гарах цорын ганц гэнэтийн зам биш. Функцийн дундаас return хийх, loop-ийн дундаас break хийх нь ч блокийг дуусахаас өмнө орхино. with эдгээрт ч баталгаа өгнө.
python
def first_long_line(path):
with open(path, encoding="utf-8") as f:
for line in f:
if len(line) > 40:
return line.strip(), f
return None, f
line, file_obj = first_long_line("app.log")
print(line)
print("Хаагдсан уу:", file_obj.closed)2026-10-09 00:00:00 INFO user=dorj action=view duration_ms=38
Хаагдсан уу: TrueХичээл 3.4-ийн app.log-ийг ашигласан. Функц урт мөр олмогц for loop ба with блокийн дундаас шууд return хийсэн. Шалгахын тулд бид санаатайгаар файл объектыг ч буцаасан. return with блокийг орхих мөчид __exit__ дуудагдаж, файл хаагдсан. Сүүлийн мөр үүнийг баталж байна. Хэрэв with-ийн оронд энгийн f = open(...) ашигласан бол return-ээс өмнө close() дуудах газар байхгүй тул файл нээлттэй үлдэх байсан. Ижил баталгаа loop доторх break, continue-д ч хамаарна. "Хэрхэн гарсан нь хамаагүй" гэсэн серверийн өрөөний дүрэм.
Жишээ 2: as-ийн утга нь manager биш
Трейсийн Алхам 3-т as нь __enter__-ийн буцаасан утгыг авдаг гэж хэлсэн. Файлын хувьд энэ ялгаа харагддаггүй, учир нь __enter__ өөрийгөө буцаадаг. Стандарт сангийн tempfile.TemporaryDirectory дээр ялгаа тод харагдана. Энэ context manager түр хавтас үүсгэж, блок дуусахад түүнийг доторх бүх зүйлтэй хамт устгадаг. Тестэд эсвэл түр файлтай ажиллахад маш хэрэгтэй.
python
import os
import tempfile
manager = tempfile.TemporaryDirectory()
with manager as folder:
print("folder-ийн төрөл:", type(folder).__name__)
print("manager-ийн төрөл:", type(manager).__name__)
report = os.path.join(folder, "report.txt")
with open(report, "w", encoding="utf-8") as f:
f.write("түр тайлан")
print("Блок дотор байгаа юу:", os.path.exists(report))
print("Блокийн дараа байгаа юу:", os.path.exists(folder))folder-ийн төрөл: str
manager-ийн төрөл: TemporaryDirectory
Блок дотор байгаа юу: True
Блокийн дараа байгаа юу: FalseContext manager бол TemporaryDirectory объект. Гэвч түүний __enter__ өөрийгөө биш, үүсгэсэн хавтасны замыг string болгон буцаадаг. Тиймээс folder нь string. Энэ нь ухаалаг дизайн: блок дотор танд объект биш, зам хэрэгтэй. Хавтас дотор файл бичигдэж, блок дотор байгаа нь батлагдсан. Блок дуусахад __exit__ хавтсыг бүх агуулгатай нь устгасан. Сүүлийн мөр үүнийг баталж байна. Үүнийг with-гүйгээр бичвэл try/finally ба shutil.rmtree дуудлага шаардлагатай болно. Энд нэг мөр л хангалттай.
Мөн нэг with блок дотор өөр нэг with үүрлэсэн байгааг анзаараарай. Файл хаагдаж, дараа нь хавтас устсан. Энэ дараалал зүйн хэрэг: хавтсыг устгахаас өмнө доторх файл хаагдсан байх ёстой.
Жишээ 3: нэг with-д олон manager ба гарах дараалал
Хоёр файлтай зэрэг ажиллах, жишээлбэл нэгийг уншиж нөгөөд бичих нь түгээмэл. Олон with-ийг үүрлэхийн оронд нэг with мөрөнд таслалаар бичиж болно. Python 3.10-аас хойш тэдгээрийг хаалтанд хийж, олон мөрөнд хуваах боломжтой:
python
with (
open("app.log", encoding="utf-8") as source,
open("errors.log", "w", encoding="utf-8") as target,
):
for line in source:
if " ERROR " in line:
target.write(line)Энэ нь app.log-ийн ERROR мөрүүдийг errors.log руу хуулна. Хоёр файл хоёулаа блок дуусахад хаагдана. Гэвч ямар дарааллаар? Тагнуулаар шалгая:
python
with Spy("А") as a, Spy("Б") as b:
print(" блок дотор") [А] __enter__ дуудагдлаа
[Б] __enter__ дуудагдлаа
блок дотор
[Б] __exit__ дуудагдлаа: алдаагүй
[А] __exit__ дуудагдлаа: алдаагүйЗүүнээс баруун тийш нээгдэж, баруунаас зүүн тийш, эсрэг дарааллаар хаагдсан. Энэ нь өмнөх хичээлийн decorator давхарлалт, илгээмжийн давхаргуудтай яг ижил: сүүлд нээгдсэн нь эхэлж хаагдана. Үүний учир нь Б-г А-гаас хамааралтай гэж үзэх ёстой. Жишээлбэл А өгөгдлийн сангийн холболт, Б тэр холболтоор нээгдсэн transaction бол transaction холболтоос өмнө хаагдах ёстой. Үнэндээ таслалтай with нь үүрлэсэн хоёр with-тэй яг ижил: А-ийн блок дотор Б-ийн блок.
Жишээ 4: таны аль хэдийн мэддэг context manager-ууд
Одоо та with-ийг Python-ий хаа сайгүй танина. Course 3-т өгөгдлийн сантай ажиллахдаа with Session(engine) as session: эсвэл HTTP хүсэлт илгээхдээ with httpx.Client() as client: хэлбэрийн код бичсэн бол тэдгээр нь context manager бөгөөд __exit__ нь холболтыг хааж, нөөцийг суллаж байсан. Бүлэг 13-т та with lock: гэж бичих болно: lock-ийг авч, блок дуусахад заавал суллах. Бүлэг 11-д exception дизайны хичээлд цэвэрлэгээний гурван аргыг, finally, else ба context manager-ийг харьцуулна. Зарчим үргэлж ижил: нөөц авч, ашиглаж, юу ч болсон заавал буцааж өгөх. Нөөц гэдэг нь файл, хавтас, сүлжээний холболт, түгжээ, өгөгдлийн сангийн transaction, тэр байтугай түр зуурын тохиргоо ч байж болно.
Хүсвэл Spy class-ийг хуулж, with блок дотроос return хийдэг жижиг функц бичиж үзээрэй. __exit__ хэвлэлт return-ээс өмнө гарах уу, дараа гарах уу? Сонирхвол Жишээ 3-ын хоёр Spy-тай with блокт алдаа шидэж, хоёр __exit__ хоёулаа алдааны төрлийг харж байгаа эсэхийг шалгаж болно. Мөн Хичээл 2.3-ын read_lines generator-ийг Spy-аар солиод, generator-ийг дундуур нь орхиход __exit__ хэзээ дуудагдахыг ажиглах нь сонирхолтой: тэр шууд дуудагдах уу, эсвэл generator хаягдах үед үү? Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
ValueError: I/O operation on closed file. "Хаагдсан файл дээр оролт гаралтын үйлдэл хийлээ" гэсэн утгатай. Хичээл 2.3-ын танил, гэвч энэ удаа өөр шалтгаанаар. with блокийн дараа, догол мөрөөс гадна f-ийг ашиглах гэж оролдоход гарна:
python
with open("notes.txt", encoding="utf-8") as f:
first = f.readline()
rest = f.read()f нэр блокийн дараа ч бий, гэвч түүний заасан файл хаагдсан. Python-д with блок хувьсагчийг устгадаггүй, зөвхөн __exit__ дууддаг. Шийдэл нь файлтай хийх бүх ажлыг блок дотор, зөв догол мөрөнд байрлуулах.
TypeError: ‘str’ object does not support the context manager protocol. “str объект context manager протоколыг дэмждэггүй” гэсэн утгатай. Python 3.10 ба өмнөх хувилбарт “AttributeError: enter”. __enter__ ба __exit__-гүй объектыг with-д өгөхөд гарна. Хамгийн түгээмэл шалтгаан нь функцийн оронд түүний үр дүн биш, буруу зүйл өгөх: жишээлбэл файлын замыг шууд with "notes.txt" as f: гэж бичих, open()-ийг мартах. Мессеж дэх төрлийн нэр юуг өгснийг хэлнэ.
TypeError: ‘builtin_function_or_method’ object does not support the context manager protocol. Өмнөхийн хувилбар, гэвч мессеж нь илүү будлиантай. open-ийг хаалтгүйгээр бичсэн үед гарна: with open as f:. open функц өөрөө context manager биш, түүний дуудлагын үр дүн буюу файл объект нь context manager. Хичээл 5.1-ийн "функц ба түүний дуудлагын ялгаа" энд ч ижил: with-д нөөцийг өгөх ёстой, нөөц үйлдвэрлэгчийг биш.
AttributeError: ‘str’ object has no attribute ‘cleanup’. “str объектод ‘cleanup’ attribute байхгүй” гэсэн утгатай. as-ийн утгыг manager гэж андуурахад гарна. Жишээлбэл with tempfile.TemporaryDirectory() as tmp: дотор tmp.cleanup() гэж хавтсыг гараар цэвэрлэх гэж оролдох. tmp нь __enter__-ийн буцаасан зам буюу string, TemporaryDirectory объект биш. Хэрэв manager өөрөө хэрэгтэй бол Жишээ 2-ын адил түүнийг with-ээс өмнө нэрэнд хадгална. Гэхдээ ихэнх тохиолдолд түүнийг гараар цэвэрлэх шаардлагагүй, __exit__ хийнэ. Context manager бүр __enter__-ээсээ юу буцаадгийг баримт бичигт нь бичсэн байдаг: файл өөрийгөө, TemporaryDirectory замыг, Lock True-г.
with бол context manager протоколын хэрэглээ бөгөөд протокол нь хоёр method-той: __enter__ ба __exit__. with эхлээд manager-ийн __enter__-ийг дуудаж, түүний буцаасан утгыг as-ийн нэрэнд онооно. Энэ утга manager өөрөө байж ч болно, өөр зүйл ч байж болно. Дараа нь блок ажиллана. Блок хэрхэн дууссанаас үл хамааран, хэвийн, алдаагаар, return эсвэл break-ээр, __exit__ заавал дуудагдана. Алдаа гарсан бол __exit__ алдааны төрөл, объект, traceback-ийг хүлээн авна. with нь үндсэндээ enter-ийг дуудаж, блокийг try-д хийж, exit-ийг finally-д дуудах загварын товчлол. Нэг with-д олон manager бичихэд тэдгээр зүүнээс баруун тийш нээгдэж, эсрэг дарааллаар хаагдана. Файл, түр хавтас, өгөгдлийн сангийн session, түгжээ бүгд энэ протоколыг дагадаг.
Энэ хичээлд бид Spy class-ийн тусламжтайгаар протоколыг тагнуулж харсан. Дараагийн хичээлд class-аар context manager бичихийг жинхэнээр сурна. Бид кодын хэсгийн ажиллах хугацааг хэмждэг Timer context manager бүтээнэ. Хичээл 5.1-ийн timed decorator функцийг бүхэлд нь хэмждэг бол Timer дурын мөрүүдийг хэмжинэ. Мөн энэ хичээлд хойш тавьсан асуултуудад хариулна: __exit__-ийн гурван argument яг юу вэ, __exit__ True буцаавал юу болох вэ, алдааг залгих нь хэзээ зөв, хэзээ аюултай вэ.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.