Ачааллж байна...
Энэ бүлгийн өмнөх гурван хичээлд бид decorator-ыг бүх хэлбэрээр нь бүтээсэн: энгийн, wraps-тай, argument-тай. Тэр бүх хичээлд функц бүрийг ганцхан decorator боож байсан. Бодит кодонд нэг функц дээр хэд хэдэн decorator давхарлах нь түгээмэл: хугацааг хэмжиж, мөн кэшлэх. Бүртгэж, мөн дахин оролдох. Хичээл 5.2-ын бүртгэлийн системд бид аль хэдийн хоёр decorator давхарласан боловч тэдгээрийн хоорондын харилцааг дараа тайлбарлана гэж хойш тавьсан. Энэ хичээлийн эхний хагас тэр амлалтыг биелүүлнэ.
Decorator давхарлахыг англиар stacking буюу "овоолох" гэж нэрлэдэг. Гол асуулт нь дараалал. Хоёр decorator-ын аль нь эхэлж боож, аль нь эхэлж ажилладаг вэ? Энэ нь зөвхөн онолын асуулт биш: дарааллыг солиход програмын зан чанар өөрчлөгддөг, заримдаа чимээгүй эвдэрдэг. Бид Хичээл 5.1-ийн “@d нь f = d(f)” томьёог давхарлалтад өргөтгөж, хариултыг trace-ээр олно.
Хичээлийн хоёр дахь хагас нь хуучин танилуудтай дахин уулзалт. Course 2-оос эхлэн та @property, @dataclass, Хичээл 3.3-аас хойш @lru_cache-ийг хэрэглэж ирсэн. Тэр үед тэдгээр нь "хэрэглэх л хэрэгтэй" ид шид байсан. Одоо та decorator-ын бүх механизмыг мэддэг болсон тул тэдгээрийг дотроос нь харах цаг ирлээ. Тэд бидний бичсэн decorator-уудаас хэрхэн ялгаатай, хэрхэн төстэй вэ? Хариулт таныг гайхшруулж магадгүй: тэдгээрийн нэг нь функц биш класс, нөгөө нь функц биш класс боодог.
Олон давхар боодолтой бэлгийг төсөөлөөд үзээрэй. Ном худалдан авсан дэлгүүр эхлээд номыг нимгэн хамгаалалтын хальсаар боосон. Дараа нь та түүнийг өнгөтэй цаасаар боосон. Эцэст нь шуудангаар явуулахын тулд картон хайрцагт хийсэн. Боолтын дараалал: хальс, цаас, хайрцаг. Дотроос гадагш. Номонд хамгийн ойр давхарга хамгийн түрүүнд боогдсон.
Найз тань илгээмжийг хүлээн авахад эсрэг дарааллаар задлана: эхлээд хайрцаг, дараа нь цаас, дараа нь хальс, эцэст нь ном. Гаднаас дотогш. Хамгийн сүүлд боогдсон давхарга хамгийн түрүүнд задлагдана. Номыг уншаад буцааж хадгалах бол, эсрэгээр, эхлээд хальс, дараа нь цаас гэх мэтээр гарч ирнэ.
Энэ хоёр дараалал, боолтын дараалал ба задлалтын дараалал, decorator давхарлалтын бүх нууцыг агуулна. Мөн нэг чухал зүйл бий: давхаргуудын дарааллыг солих нь үр дүнг өөрчилнө. Хайрцгийг цаасан дотор хийвэл шуудангийн ажилтан гоё цаасыг урж, хайрцгийн хаяг харагдахгүй. Давхарга бүр өөрийн үүрэгтэй бөгөөд тэр үүрэг байрлалаасаа хамаардаг.
Хоёр цагийг, боолт ба дуудлагыг, хоёуланг нь харахын тулд Хичээл 5.3-ын мэдлэгээр argument-тай decorator бичье. Тэр нэр хүлээн авч, боолт хийх үед ба дуудлагын үед нэрээ хэвлэдэг.
python
from functools import wraps
def layer(name):
def decorator(func):
print(f" [боолт] {name} нь {func.__name__}-ийг боож байна")
@wraps(func)
def wrapper(*args, **kwargs):
print(f" {name}: орлоо")
result = func(*args, **kwargs)
print(f" {name}: гарлаа")
return result
return wrapper
return decorator
@layer("ГАДНА")
@layer("ДОТОР")
def greet():
print(" greet ажиллаж байна")
print("--- дуудлага ---")
greet() [боолт] ДОТОР нь greet-ийг боож байна
[боолт] ГАДНА нь greet-ийг боож байна
--- дуудлага ---
ГАДНА: орлоо
ДОТОР: орлоо
greet ажиллаж байна
ДОТОР: гарлаа
ГАДНА: гарлааГаралтын хоёр хэсгийг харьцуулаарай. Боолтын үед ДОТОР эхэлсэн: def-д хамгийн ойр, доод decorator. Дуудлагын үед ГАДНА эхэлж орсон, ДОТОР дараа нь. Анхны функцийн дараа эсрэг дарааллаар гарсан: эхлээд ДОТОР, дараа нь ГАДНА. Энэ бол яг зүйрлэлийн илгээмж: дотроос гадагш боогдсон, гаднаас дотогш задлагдсан, буцахдаа дотроос гадагш гарсан.
Нэг жижиг зүйл: боолтын хоёр мөр хоёулаа “greet-ийг боож байна” гэж хэлсэн. Гэвч ГАДНА давхарга анхны greet-ийг биш, ДОТОР давхаргын wrapper-ийг боосон. Яагаад greet гэж хэвлэгдсэн бэ? Учир нь ДОТОР-ын wrapper wraps-аар greet-ийн шошгыг авсан. Хичээл 5.2-ын стандарт энд ч ажиллаж байна: ГАДНА давхарга түүний доор байгаа зүйлийг нэрээр нь зөв таньсан.
Trace: давхарласан @-ийг задлах
Хичээл 5.1-ийн томьёог давхарлалтад хэрэглэе. Доорх хоёр код яг ижил:
python
@layer("ГАДНА")
@layer("ДОТОР")
def greet():
...python
def greet():
...
greet = layer("ГАДНА")(layer("ДОТОР")(greet))Хоёр дахь хэлбэрийг дотроос гадагш уншина. Python decorator-уудыг def-ээс эхлэн дээш, доод талаас дээш хэрэглэдэг. Алхам алхмаар:
Алхам 1 (def greet). Анхны greet функц үүснэ. Нэрэнд хараахан оноогдоогүй.
Алхам 2 (тохиргооны цаг, хоёр давхарга). Python @ мөр бүрийн илэрхийллийг дээрээс доош бодно: layer("ГАДНА") ба layer("ДОТОР") хоёр тохируулсан decorator үйлдвэрлэнэ. Тэдгээрийг D_гадна ба D_дотор гэж нэрлэе. Энэ шат юу ч хэвлэхгүй, учир нь layer-ийн гадна давхарга юу ч хэвлэдэггүй.
Алхам 3 (боолтын цаг, ДОТОР). D_дотор анхны greet-ийг хүлээн авч, “[боолт] ДОТОР...” хэвлээд wrapper_дотор-ыг буцаана. wrapper_дотор-ын үүргэвчинд: func = анхны greet, name = "ДОТОР".
Алхам 4 (боолтын цаг, ГАДНА). D_гадна wrapper_дотор-ыг хүлээн авч, “[боолт] ГАДНА...” хэвлээд wrapper_гадна-ыг буцаана. wrapper_гадна-ын үүргэвчинд: func = wrapper_дотор, name = "ГАДНА".
Алхам 5 (нэрийн хавтан). wrapper_гадна greet нэрэнд оноогдоно.
Алхам 6 (дуудлагын цаг). greet() нь wrapper_гадна-г дуудна. Тэр "ГАДНА: орлоо" хэвлэж, үүргэвчнээсээ func-ийг дуудна: wrapper_дотор. Тэр "ДОТОР: орлоо" хэвлэж, өөрийн func-ийг дуудна: анхны greet. greet хэвлэнэ. Дараа нь гинж эсрэг тийш буцна: wrapper_дотор "ДОТОР: гарлаа", wrapper_гадна "ГАДНА: гарлаа".
Энэ гинжийг wraps-ийн __wrapped__ тэмдэглэлээр шууд харж болно:
python
import inspect
print(greet.__closure__[0].cell_contents)
print(greet.__wrapped__)
print(greet.__wrapped__.__wrapped__)
print(inspect.unwrap(greet))<function greet at 0x7f5b2a1c4f40>
<function greet at 0x7f5b2a1c4f40>
<function greet at 0x7f5b2a1c4e00>
<function greet at 0x7f5b2a1c4e00>Эхний хоёр мөр ижил объект: wrapper_дотор, greet гэсэн шошготой. Гурав дахь мөр өөр хаягтай: тэр анхны greet. __wrapped__ гинжийг хоёр удаа дагаж бид боодлын хамгийн ёроолд хүрсэн. inspect.unwrap() гинжийг автоматаар эцэс хүртэл дагадаг. Хаягууд таны компьютер дээр өөр байна, гэхдээ эхний хоёр ба сүүлийн хоёр хоорондоо ижил байх ёстой.
Дүрмийг нэг өгүүлбэрт: боолт доороос дээш, дуудлага дээрээс доош. Хамгийн дээд decorator хамгийн гадна давхарга бөгөөд дуудлагыг хамгийн түрүүнд хүлээн авдаг.
Жишээ 1: хэмжилт ба кэш
Хичээл 5.2-ын timed ба memo-г нэг удаан функц дээр хоёр дарааллаар давхарлая. Үр дүнгийн ялгаа нь хэмжилт юуг хэмжиж байгааг өөрчилнө.
python
import time
from functools import wraps
def timed(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f" {func.__name__}: {time.perf_counter() - start:.4f} секунд")
return result
return wrapper
def memo(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@timed
@memo
def slow_square_a(n):
time.sleep(0.5)
return n * n
@memo
@timed
def slow_square_b(n):
time.sleep(0.5)
return n * n
print("timed гадна:")
slow_square_a(9)
slow_square_a(9)
print("memo гадна:")
slow_square_b(9)
slow_square_b(9)Миний компьютер дээрх нэг ажиллуулалт:
timed гадна:
slow_square_a: 0.5006 секунд
slow_square_a: 0.0000 секунд
memo гадна:
slow_square_b: 0.5007 секундЭхний функцэд timed гадна давхарга. Дуудлага бүр түүгээр орно: эхний дуудлага memo-оор дамжиж жинхэнэ функцийг дуудаад 0.5 секунд, хоёр дахь нь memo-ийн дэвтрээс уншаад бараг 0. timed хэрэглэгчийн мэдрэх хугацааг хэмжиж байна: кэштэй ч, кэшгүй ч.
Хоёр дахь функцэд memo гадна. Эхний дуудлага memo-оор орж, дэвтэрт байхгүй тул доорх timed-ийг дуудна, тэр хэмжиж хэвлэнэ. Хоёр дахь дуудлага memo-оор орж, дэвтэрт олдсон тул timed-ийг огт дуудахгүй, юу ч хэвлэгдэхгүй. timed зөвхөн жинхэнэ тооцооллын хугацааг хэмжиж байна.
Аль нь зөв вэ? Хоёулаа, гэхдээ өөр асуултад хариулна. "Хэрэглэгчдэд энэ функц хэр хурдан санагддаг вэ" гэвэл timed гадна. "Кэш алдагдах үед тооцоолол хэр удаан вэ" гэвэл timed дотор. Decorator давхарлахдаа "гадна давхарга юуг харах вэ?" гэж асуух нь дарааллыг сонгох шийдвэрлэх асуулт.
Жишээ 2: бүртгэл ба дахин оролдлого
Хичээл 5.3-ын retry ба Хичээл 5.2-ын log_calls-ийг давхарлая. Энд ялгаа нь бүртгэлийн утгыг өөрчилнө.
python
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f" [log] {func.__name__} дуудагдлаа")
return func(*args, **kwargs)
return wrapper
fetch_once = make_flaky(2)
@log_calls
@retry(times=3)
def fetch_a():
return fetch_once()
print("log гадна:")
print(fetch_a())log гадна:
[log] fetch_a дуудагдлаа
1/3 оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (1-р дуудлага)
2/3 оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (2-р дуудлага)
өгөгдөл ирлээmake_flaky ба retry нь Хичээл 5.3-аас, энгийн хувилбараараа. log_calls гадна тул нэг дуудлага нэг бүртгэл гаргасан: "хэрэглэгч fetch_a-г нэг удаа дуудсан". Retry-ийн гурван оролдлого бүртгэлд харагдахгүй. Одоо дарааллыг сольё:
python
fetch_once = make_flaky(2)
@retry(times=3)
@log_calls
def fetch_b():
return fetch_once()
print("retry гадна:")
print(fetch_b())retry гадна:
[log] fetch_b дуудагдлаа
1/3 оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (1-р дуудлага)
[log] fetch_b дуудагдлаа
2/3 оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (2-р дуудлага)
[log] fetch_b дуудагдлаа
өгөгдөл ирлээОдоо retry гадна, тэр дотор талын бүх зүйлийг гурван удаа дуудна, log_calls ч мөн. Бүртгэл оролдлого бүрийг харуулна. Бодит системд эхнийх нь "манай API хэдэн хүсэлт хүлээн авсан бэ"-д, хоёр дахь нь "гадны сервер рүү бид хэдэн удаа хүсэлт явуулсан бэ"-д хариулна. Хоёулаа хэрэгтэй мэдээлэл боловч өөр.
Жишээ 3: бүртгэгч хаана байх ёстой вэ — чимээгүй эвдрэл
Хичээл 5.2-ын бүртгэлийн системд бид register-ийг дээр, log_calls-ийг доор тавьсан. Яагаад? Эсрэгээр нь тавиад юу болохыг харъя.
python
commands = {}
def register(func):
commands[func.__name__] = func
return func
@log_calls
@register
def backup():
return "нөөцлөлт дууслаа"
print("Шууд дуудлага:")
print(backup())
print("Бүртгэлээр дуудлага:")
print(commands["backup"]())Шууд дуудлага:
[log] backup дуудагдлаа
нөөцлөлт дууслаа
Бүртгэлээр дуудлага:
нөөцлөлт дууслааTrace: register доод давхарга тул эхэлж ажиллана. Тэр анхны backup-ийг commands-д бүртгэж, өөрчлөхгүйгээр буцаана. Дараа нь log_calls түүнийг боож, backup нэр боодолтой хувилбарыг заана. Тэгэхээр програм хоёр өөр функц агуулж байна: backup нэрээр боодолтой, commands-д боодолгүй. Шууд дуудахад бүртгэл ажилласан, бүртгэлээр дуудахад ажиллаагүй. Python ямар ч анхааруулга өгөхгүй. Хэрэв таны программын бүх командыг commands-аар дууддаг бол log_calls-ийн бүх ажил дэмий болно.
Дүрэм: функцийг ямар нэг газар хадгалдаг decorator, тухайлбал бүртгэгч, хамгийн дээд давхарга байх ёстой, ингэснээр бүх давхаргатай эцсийн хувилбарыг хадгална. Course 3-ын FastAPI-ийн @app.get(...) яг ийм бүртгэгч: функцийг URL замд бүртгэдэг. Тиймээс FastAPI-д бусад decorator-ыг үргэлж @app.get-ийн доор бичдэг.
lru_cache
Хичээл 3.3-т бид @lru_cache(maxsize=None) бичсэн. Одоо түүнийг бүрэн уншиж чадна: lru_cache бол argument-тай decorator, Хичээл 5.3-ын гурван давхар бүтэцтэй. lru_cache(maxsize=None) тохиргоог авч decorator буцаана, тэр нь fib-ийг авч кэштэй wrapper буцаана. Wrapper wraps-ийн механизмаар fib-ийн нэр, тайлбарыг хадгалж, __wrapped__ тохируулсан. Энэ бүгдийг бид Хичээл 5.2-т шалгасан. Ганц ялгаа нь lru_cache-ийн wrapper Python функц биш, C хэл дээр бичигдсэн тусгай объект бөгөөд дэвтэр нь closure-ийн хайрцагт биш, тэр объектын дотор амьдардаг. Мөн тэр wrapper-т cache_info() ба cache_clear() гэсэн нэмэлт method-ууд бий. Энэ нь decorator-ын буцаах зүйл заавал энгийн функц байх албагүйг харуулна: дуудагдаж чаддаг ямар ч объект болж болно.
Давхарлалтын мэдлэг lru_cache-тэй ажиллахад нэг практик сургамж өгнө. Хэрэв lru_cache-ийн дээр өөр decorator тавибал гадна давхарга cache_info-г нуух болно. Үүнийг Түгээмэл алдаанууд хэсэгт харна.
property
Course 2-т та class-ийн attribute-ыг method мэт тооцоолж, гэхдээ хаалтгүй хандахын тулд @property бичсэн. Одоо түүнийг задалъя.
python
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
r = Rectangle(4, 5)
print(r.area)
print(type(Rectangle.__dict__["area"]))20
<class 'property'>Хичээл 5.1-ийн томьёогоор @property def area нь area = property(area). Гэвч хоёр дахь мөр гайхмаар зүйлийг харуулж байна: Rectangle-ийн area нь функц биш, property төрлийн объект. property бол функц биш, класс. property(area) нь шинэ property объект үүсгэж, area функцийг дотор нь хадгалдаг. Класс өөрөө дуудагдаж чаддаг тул decorator болж чадна. Энэ нь lru_cache-ийн сургамжийг баталж байна: decorator бол "нэг зүйл хүлээн авч, өөр зүйл буцаадаг дуудагдаж чаддаг юу ч".
property объект attribute-ийн хандалтыг хэрхэн "барьж" авдаг вэ? Өөрөөр хэлбэл r.area гэж бичихэд Python яагаад property-г буцаахын оронд дотор нь хадгалсан функцийг дуудаад үр дүнг өгдөг вэ? Энэ бол descriptor гэдэг Python-ий гүн механизмын ажил. Танилцуулга хичээлд хэлсэнчлэн descriptor-ууд энэ сургалтын хүрээнээс гадуур, хамгийн сүүлийн хичээлд дараагийн алхам болгон нэрлэгдэх болно. Одоохондоо мэдэхэд хангалттай зүйл: property бол тусгай зан чанартай объект, decorator нь түүнийг үүсгэх товчлол.
Одоо setter. Course 2-т та @area.setter гэсэн хэлбэрийг харсан байх. Width-д шалгалттай setter нэмье:
python
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value <= 0:
raise ValueError(f"Өргөн эерэг байх ёстой, {value} ирлээ")
self._width = value
r = Rectangle(4, 5)
r.width = 10
print(r.width)
r.width = -310
Traceback (most recent call last):
...
ValueError: Өргөн эерэг байх ёстой, -3 ирлээ@width.setter-ийг задалбал: width = width.setter(width). Энд decorator нь width.setter, өөрөөр хэлбэл өмнөх мөрөнд үүссэн property объектын method. Тэр method шинэ функцийг setter болгон авч, getter ба setter хоёулаа бүхий шинэ property объект буцаана. Тэр шинэ объект width нэрэнд оноогдож, хуучнаа солино. Энэ нь @ мөрийн ард ямар ч илэрхийлэл, тэр дундаа объектын method бичиж болдгийг харуулж байна. Мөн хоёр функц хоёулаа width гэж нэрлэгдсэн байх ёстойн шалтгааныг тайлбарлана: хоёр дахь def нэрийн хавтанг солиход шинэ property ижил нэрэнд очих ёстой. __init__ доторх self.width = width мөр ч setter-ээр дамжиж шалгагдана. Тэгэхээр Rectangle(-1, 5) ч мөн алдаа өгнө. Жинхэнэ утга нь доогуур зураастай _width-д хадгалагдаж байгааг анзаараарай. property ба хадгалах attribute ижил нэртэй байвал setter өөрийгөө дуудаж, хязгааргүй рекурс үүсгэнэ.
dataclass
Сүүлийн танил хамгийн өөр. Course 2-т та @dataclass-ийг class-ийн дээр бичиж, __init__, __repr__-ийг гараар бичихгүйгээр авсан.
python
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p = Point(3, 4)
print(p)
print(p == Point(3, 4))
print("__init__" in Point.__dict__)Point(x=3, y=4)
True
Truedataclass нь class decorator: функц биш, class-ийг хүлээн авдаг. Томьёо ижил: @dataclass class Point нь Point = dataclass(Point). dataclass Point class-ийг авч, түүний type hint-үүдийг уншаад, x ба y-д тохирох __init__, __repr__, __eq__ method-уудыг үүсгэж class-д нэмнэ. Сүүлийн мөр __init__ Point-д бий гэдгийг баталж байна, гэтэл бид түүнийг бичээгүй.
Бидний бичсэн decorator-уудаас нэг чухал ялгаа бий. Тэд wrapper гэсэн шинэ функц буцаадаг байсан. dataclass шинэ class буцаадаггүй, ижил class-ийг өөрчлөөд буцаадаг:
python
class Plain:
x: int
y: int
result = dataclass(Plain)
print(result is Plain)TrueБоодол биш, засвар. Энэ нь Жишээ 3-ын register-тэй төстэй: register ч функцийг өөрчлөлгүй буцаадаг байсан. Decorator-ын томьёо хангалттай ерөнхий тул "боож шинийг буцаах", "өөрчлөөд ижлийг буцаах", "зүгээр бүртгээд ижлийг буцаах" бүгд боломжтой. @dataclass(frozen=True) гэсэн хаалттай хэлбэр бол Хичээл 5.3-ын argument-тай decorator. Энэ ба dataclass-ийн бусад гүн боломжуудыг Хичээл 7.4-т бүрэн судална.
Хүсвэл Анхны жишээний layer-ийг гурван давхарлаж, "ГАДНА", "ДУНД", "ДОТОР" гэж нэрлээд, гаралтыг урьдчилан бичсэний дараа ажиллуулж үзээрэй. Таны таамаг таарч байна уу? Сонирхвол property-г @-гүйгээр бичиж үзэж болно: area = property(get_area) гэж class дотор, ингэснээр decorator товчлолыг бүрэн тойрно. Мөн Жишээ 1-ийн хоёр дарааллыг рекурсив fib дээр туршиж, хэдэн мөр хэвлэгдэхийг тоолох нь Хичээл 4.3-ын нэрийн хавтангийн мэдлэгийг давхарлалттай нэгтгэх сонирхолтой даалгавар. Анхааруулга: timed гадна байвал рекурсив дуудлага бүр хэвлэгдэх тул fib(30) биш, fib(10) гэх мэт жижиг тоо сонгоорой. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
AttributeError: ‘function’ object has no attribute ‘cache_info’. “function объектод ‘cache_info’ attribute байхгүй” гэсэн утгатай. lru_cache-ийн дээр өөр decorator, тухайлбал timed тавиад fib.cache_info() дуудах үед гарна. fib нэр одоо timed-ийн wrapper-ийг заадаг бөгөөд тэр энгийн Python функц, cache_info method-гүй. wraps нэр, тайлбарыг хуулсан ч lru_cache объектын method-уудыг хуулдаггүй. Шийдлүүд: fib.__wrapped__.cache_info() гэж нэг давхарга дотогш орох, эсвэл lru_cache-ийг хамгийн дээд давхарга болгох. Аль нь тохирохыг Жишээ 1-ийн "гадна давхарга юуг харах вэ" асуулт шийднэ.
Бүртгэлээр дуудахад decorator-ууд ажиллахгүй байх. Мессежгүй алдаа, Жишээ 3-ын эвдрэл. Бүртгэгч decorator доод давхаргад байгаа тул боодолгүй функцийг хадгалсан. Шинж тэмдэг нь: функцийг шууд дуудахад бүртгэл, кэш зэрэг ажилладаг, харин бүртгэл, router, plugin систем зэргээр дамжуулан дуудахад ажилладаггүй. Шийдэл нь бүртгэгчийг хамгийн дээд давхарга болгох.
AttributeError: property ‘area’ of ‘Rectangle’ object has no setter. “Rectangle объектын ‘area’ property-д setter байхгүй” гэсэн утгатай. Python 3.10 ба өмнөх хувилбарт “can’t set attribute”. Зөвхөн getter-тэй property-д утга оноох гэж оролдоход гарна: r.area = 50. Энэ нь ихэвчлэн санаатай: area нь width ба height-аас тооцоологддог тул шууд оноох утгагүй. Хэрэв setter санаатайгаар бичсэн атлаа энэ алдаа гарвал setter-ийн функцийн нэрийг шалгаарай. @width.setter def set_width(...) гэж өөр нэрээр бичвэл шинэ property set_width нэрэнд очиж, width setter-гүй хэвээр үлдэнэ.
TypeError: ‘int’ object is not callable. “int объектыг дуудах боломжгүй” гэсэн утгатай. property-г method мэт хаалттай дуудахад гарна: r.area(). property-ийн зорилго нь хаалтгүй хандалт: r.area аль хэдийн функцийг дуудаж, 20 гэсэн int буцаасан. Дараа нь () тэр int-ийг дуудах гэж оролдоно. Шийдэл: property-д хаалт бичихгүй.
RecursionError: maximum recursion depth exceeded. property-ийн setter дотор ижил нэртэй attribute-д оноох үед гарна: setter дотор self.width = value гэж бичвэл. Тэр мөр дахин setter-ийг дуудаж, тэр нь дахин өөрийгөө дууддаг тул хязгааргүй рекурс үүснэ. Шийдэл нь бодит утгыг өөр нэрэнд, уламжлал ёсоор доогуур зураастай self._width-д хадгалах.
Хэд хэдэн decorator давхарлахад @a @b def f нь f = a(b(f)) гэсэн утгатай. Боолт доороос дээш явагддаг: def-д хамгийн ойр decorator эхэлж боодог. Дуудлага дээрээс доош явагддаг: хамгийн дээд decorator хамгийн гадна давхарга бөгөөд дуудлагыг эхэлж хүлээн авна. __wrapped__ гинж ба inspect.unwrap() давхаргуудыг ёроол хүртэл дагадаг. Дарааллыг солих нь зан чанарыг өөрчилдөг: timed кэшээс гадна байвал хэрэглэгчийн мэдрэх хугацааг, дотор байвал жинхэнэ тооцооллыг хэмжинэ; log_calls retry-ээс гадна байвал дуудлагыг, дотор байвал оролдлогыг бүртгэнэ. Функцийг хадгалдаг decorator хамгийн дээд давхарга байх ёстой. lru_cache бол argument-тай decorator, wrapper нь method-тай C объект. property бол класс бөгөөд @width.setter нь объектын method-ыг decorator болгон хэрэглэдэг. dataclass бол class decorator бөгөөд шинэ боодол биш, ижил class-ийг засаж буцаадаг. Decorator гэдэг нь "дуудагдаж чаддаг юу ч, нэг зүйл хүлээн авч, өөр зүйл буцаадаг".
Ингээд Бүлэг 5 дуусч, @ тэмдэг таны бүрэн эзэмшилд орлоо. Дараагийн бүлэг өөр нэг танил тэмдэг рүү шилжинэ: with. Course 1-ээс хойш та with open(...) гэж файл нээж, Course 3-т өгөгдлийн сангийн холболтыг with-ээр удирдсан. Хичээл 2.3-т бид with блок generator дотор хэрхэн нээлттэй үлддэгийг харсан. Дараагийн хичээлд with-ийн ард байгаа протоколыг нээж, алдаа гарсан үед ч with юуг баталгаажуулдгийг бүрэн ойлгоно. Энэ бол Бүлэг 1-ийн iterator протоколын ах дүү, ижил санаа: тодорхой нэртэй хоёр method бичвэл таны объект Python-ий тусгай хэллэгтэй ажилладаг болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.