Ачааллж байна...
Энэ хичээлээр Бүлэг 8 дуусна. Мөн энэ хичээлд та @ тэмдгийг АНХ УДАА харах болно — тэр бол Python-ы хамгийн танигдсан, мөн хамгийн нууцлаг тэмдгүүдийн нэг юм.
Гэхдээ эхлээд асуудлаа тодорхойлъё.
Курс 1-ээс class-д ХОЁР төрлийн зүйл байдгийг та мэднэ:
Talbar (attribute) — өгөгдөл. Хаалтгүй хандана.
python
print(nom.garchig)Method — үйлдэл. Хаалттай дуудна.
python
print(nom.medeelel())Ялгаа нь тодорхой мэт санагдана. Гэвч заримдаа тэр нь эвгүй байдалд ордог.
Жишээ нь тэгш өнцөгт class бичье:
python
class TegshUnguult:
def __init__(self, urt: float, urgun: float) -> None:
self.urt = urt
self.urgun = urgun
def talbai(self) -> float:
return self.urt * self.urgun
tu = TegshUnguult(10, 5)
print(tu.urt) # талбар — хаалтгүй
print(tu.urgun) # талбар — хаалтгүй
print(tu.talbai()) # method — ХААЛТТАЙ10
5
50Эвгүй байдал: urt, urgun нь хаалтгүй, гэвч talbai нь хаалттай. Гэтэл хэрэглэгчийн хувьд гурвуулаа ИЖИЛ ТӨРЛИЙН зүйл — тэдгээр нь бүгд тэгш өнцөгтийн ШИНЖ ЧАНАР юм.
Яагаад талбай нь өөрөөр биччигдэх ёстой вэ? Тэр нь зөвхөн ТООЦООЛОГДДОГ гэдгээрээ л ялгаатай. Гэвч тэр нь дотоод нарийвчлал — хэрэглэгчид хамаагүй байх ёстой.
property нь яг энэ асуудлыг шийднэ. Тэр нь method-ыг ТАЛБАР шиг харагдуулна.
Хүнтэй ярьж байна гэж төсөөлье.
Асуулт: "Та хэдэн настай вэ?" — тэр хүн хариулна. Та түүнээс ЯМАР НЭГ ЗҮЙЛ ХИЙХИЙГ гуйгаагүй, зөвхөн МЭДЭЭЛЭЛ асуусан.
Үйлдэл: "Хаалгыг онгойлгож өгөөч." — тэр хүн ЮМ ХИЙНЭ. Ертөнц өөрчлөгдөнө.
Хоёрын ялгаа тодорхой. Асуулт нь юуг ч өөрчлөхгүй, зөвхөн мэдээлэл өгнө. Үйлдэл нь юуг нэгийг өөрчилнө.
Одоо чухал зүйл: хүн насаа ЯАЖ мэдэж байгаа нь танд хамаагүй. Тэр цээжээрээ санаж байж болно. Эсвэл төрсөн он сараа хараад тооцоолж байж болно. Эсвэл гар утаснаасаа хайж байж болно. Танд зөвхөн ХАРИУЛТ хэрэгтэй.
property бол яг тэр санаа. Хэрэглэгчийн хувьд tu.talbai нь ЗҮГЭЭР Л ШИНЖ ЧАНАР. Тэр нь хадгалагдсан уу, тооцоологдсон уу — хамаагүй. Хэрэглэгч зөвхөн УТГЫГ хүсэж байна.
python
class TegshUnguult:
def __init__(self, urt: float, urgun: float) -> None:
self.urt = urt
self.urgun = urgun
@property
def talbai(self) -> float:
return self.urt * self.urgun
@property
def perimetr(self) -> float:
return 2 * (self.urt + self.urgun)
tu = TegshUnguult(10, 5)
print(tu.urt)
print(tu.urgun)
print(tu.talbai) # ХААЛТГҮЙ!
print(tu.perimetr) # ХААЛТГҮЙ!10
5
50
30tu.talbai — хаалтгүй. Яг л ердийн талбар шиг.
Гэвч ард нь ЖИНХЭНЭ method ажиллаж байна. Тэр нь self.urt * self.urgun гэж тооцоолж байна.
Хоёр зүйлийг анзаараарай.
Нэгдүгээрт, @property гэсэн мөр. Тэр нь method-ын ЯГ ДЭЭР, өөрийн мөрөнд байна. Догол мөр нь method-той ижил.
Хоёрдугаарт, method нь ердийнхөөрөө бичигдсэн — def talbai(self) -> float:. Ямар ч онцгой зүйл алга.
Тэр @property гэсэн мөр нь бүх зүйлийг өөрчилж байна. Тэр нь Python-д "энэ method-ыг ТАЛБАР шиг ажиллуул" гэж хэлж байна.
@ тэмдэг гэж юу вэОдоо шударгаар хэлье: @property нь DECORATOR юм.
Decorator гэдэг нь юу вэ? Тэр нь функцийг авч, өөрчилж, буцаадаг функц.
Санаж байна уу, Бүлэг 3.5-д би "функц бол утга" гэж хэлсэн? Функцийг хувьсагчид олгож, жагсаалтад хийж, өөр функцэд дамжуулж болно гэж. Тэгээд би "энэ бол decorator руу орох хаалга" гэж хэлсэн.
Decorator бол яг тэр. @property гэсэн мөр нь ойролцоогоор дараах кодтой ижил утгатай:
python
talbai = property(talbai)Өөрөөр хэлбэл: talbai функцийг ав, property() гэдэг зүйлээр боож, үр дүнг буцааж talbai гэсэн нэрэнд олго.
Одоо тэр тайлбар танд бүрэн ойлгомжгүй байх — тэр зүгээр. Би түүнийг зөвхөн "энэ нь илбэ БИШ, энэ нь ердийн Python код" гэдгийг харуулах гэж бичсэн.
Decorator-ыг Бүлэг 10-д БҮРЭН тайлбарлана. Тэнд та @ тэмдгийн ард яг юу болж байгааг мөр мөрөөр нь харна, мөн өөрөө decorator бичнэ.
Одоохондоо @property -ыг ЗААВАР гэж ойлгоод өнгөрөөрэй: "энэ method-ыг талбар шиг ажиллуул." Түүнийг ашиглахад ямар ч асуудалгүй, ойлгох шаардлагагүй.
Гэхдээ нэг зүйлийг санаж яваарай: @ тэмдэг харах бүрдээ — тэр бол decorator. Мөн @dataclass (Бүлэг 9), @app.get("/") (Бүлэг 15) — тэдгээр нь БҮГД ижил механизм дээр ажилладаг. Бүлэг 10 нь тэдгээр бүгдийг нэг дор тайлна.
Одоо property -ийн ЖИНХЭНЭ ашиг тусыг харъя. Тэр нь зөвхөн гоо сайхны асуудал БИШ.
Асуудлыг эхлүүлье. Танд Zarlaga class байна, дүн ба ангилалтай. Одоо ТУСЛАХ КОМПАНИЙН системд түүнийг ашиглаж байна.
Хувилбар 1: талбар болгож хадгалсан.
python
class Zarlaga:
def __init__(self, dun: int, tatvariin_khuvi: float) -> None:
self.dun = dun
self.tatvariin_khuvi = tatvariin_khuvi
self.niit = dun + int(dun * tatvariin_khuvi) # ХАДГАЛСАН
z = Zarlaga(10000, 0.1)
print(z.niit)11000Ажиллаж байна. Гэвч одоо асуудал:
python
z.dun = 20000 # дүнг өөрчиллөө
print(z.niit) # нийт нь юу болох вэ?11000БУРУУ! niit нь хуучин хэвээрээ. Тэр нь __init__ -д НЭГ УДАА тооцоологдсон бөгөөд дараа нь хэзээ ч шинэчлэгдээгүй.
Энэ бол маш түгээмэл, маш аюултай алдаа. Өгөгдөл нь зөрчилдөж байна: dun нь 20000, гэвч niit нь 10000-ийн үндсэн дээр тооцоологдсон.
Хувилбар 2: property.
python
class Zarlaga:
def __init__(self, dun: int, tatvariin_khuvi: float) -> None:
self.dun = dun
self.tatvariin_khuvi = tatvariin_khuvi
@property
def niit(self) -> int:
return self.dun + int(self.dun * self.tatvariin_khuvi)
z = Zarlaga(10000, 0.1)
print(z.niit)
z.dun = 20000
print(z.niit)11000
22000ЗӨВ! niit нь дуудагдах БҮРДЭЭ ДАХИН тооцоологдож байна. Тэр нь ХЭЗЭЭ Ч хуучрахгүй.
Энэ бол property -ийн хамгийн чухал ашиг тус: тооцоологдсон утга нь ҮРГЭЛЖ ШИНЭХЭН байна.
Дүрэм: хэрэв утга нь өөр талбаруудаас ТООЦООЛОГДДОГ бол — түүнийг хадгалж БОЛОХГҮЙ. Property болго.
Одоохондоо бидний property-ууд нь зөвхөн УНШИГДАЖ байна. Тэдэнд утга олгож болох уу?
python
tu = TegshUnguult(10, 5)
tu.talbai = 100Traceback (most recent call last):
File "main.py", line 2, in <module>
tu.talbai = 100
AttributeError: property 'talbai' of 'TegshUnguult' object has no setterproperty 'talbai' of 'TegshUnguult' object has no setter гэдэг нь "talbai property-д утга олгогч (setter) байхгүй" гэсэн үг.
Анхдагчаар property нь ЗӨВХӨН УНШИГДАНА. Энэ нь ихэнхдээ ЗӨВ — талбайд утга олгох нь утгагүй. Хэрэв та талбайг 100 болгохыг хүсвэл — урт эсвэл өргөнийг өөрчлөх ёстой. Аль нэгийг нь? Python мэдэхгүй.
Гэвч заримдаа утга олгох нь УТГАТАЙ байдаг. Тэр үед setter нэмнэ.
python
class Temperatur:
def __init__(self, tselsi: float) -> None:
self._tselsi = tselsi
@property
def tselsi(self) -> float:
return self._tselsi
@tselsi.setter
def tselsi(self, utga: float) -> None:
if utga < -273.15:
raise ValueError("Температур абсолют тэгээс доош байж болохгүй")
self._tselsi = utga
@property
def farengeit(self) -> float:
return self._tselsi * 9 / 5 + 32
@farengeit.setter
def farengeit(self, utga: float) -> None:
self.tselsi = (utga - 32) * 5 / 9
t = Temperatur(25)
print(f"{t.tselsi}°C = {t.farengeit}°F")
t.tselsi = 100
print(f"{t.tselsi}°C = {t.farengeit}°F")
t.farengeit = 32
print(f"{t.tselsi}°C = {t.farengeit}°F")25°C = 77.0°F
100°C = 212.0°F
0.0°C = 32.0°FГайхалтай зүйл болж байна. Та farengeit -д утга олгоход tselsi нь АВТОМАТААР шинэчлэгдэж байна. Хоёр нь ҮРГЭЛЖ зөв хамааралтай.
Кодыг задалъя.
self._tselsi — доогуур зураастай нэр. Энэ бол Python-ы заншил: доогуур зураасаар эхэлсэн нэр нь "ДОТООД, гаднаас хүрч болохгүй" гэсэн утгатай.
Python нь түүнийг АЛБАДАН хамгаалахгүй (та t._tselsi гэж хандаж болно), гэхдээ тэр нь "ингэж хийвэл өөрөө хариуцна" гэсэн анхааруулга юм. Яг л type hint шиг — заавар, албадлага биш.
@tselsi.setter — энэ бол setter-ийн бичлэг. Тэр нь @property -той төстэй ч гэсэн property_name.setter гэсэн хэлбэртэй.
Anхаар: setter-ийн method нь ИЖИЛ НЭРТЭЙ байх ЁСТОЙ (def tselsi(self, utga)). Хэрэв өөр нэр бичвэл ажиллахгүй.
raise ValueError(...) — энэ нь "алдаа зориудаар үүсгэ" гэсэн үг. raise -ыг Бүлэг 13-т бүрэн үзнэ. Одоохондоо "буруу утга ирвэл программыг зогсоо" гэж ойлгоод өнгөрөөрэй.
Дээрх жишээнд setter нь ШАЛГАЛТ хийж байгааг анзаараарай:
python
if utga < -273.15:
raise ValueError("Температур абсолют тэгээс доош байж болохгүй")Туршиж үзье:
python
t = Temperatur(25)
t.tselsi = -300Traceback (most recent call last):
File "main.py", line 2, in <module>
t.tselsi = -300
File "main.py", line 10, in tselsi
raise ValueError("Температур абсолют тэгээс доош байж болохгүй")
ValueError: Температур абсолют тэгээс доош байж болохгүйБуруу утга ХОРИГЛОГДЛОО.
Энэ бол property -ийн жинхэнэ хүч. Ердийн талбар дээр та ЯМАР Ч утга олгож болно:
python
class Temperatur:
def __init__(self, tselsi: float) -> None:
self.tselsi = tselsi # ердийн талбар
t = Temperatur(25)
t.tselsi = -1000000 # ямар ч шалгалт байхгүй!
print(t.tselsi)-1000000Утгагүй температур. Программ гомдохгүй. Дараа нь тооцоолол буруу гарна, та яагаад гэдгийг мэдэхгүй.
Property нь ХИЛИЙН ЦЭГ үүсгэдэг. Утга орж ирэх БҮРД шалгалт ажиллана.
Бүлэг 6.4-ийн "хилийн цэг бүрд цэвэрлэ" гэсэн зарчим class-ын түвшинд илэрч байна.
Одоо бодит хэрэглээ.
python
class Zuil:
def __init__(self, garchig: str, on: int) -> None:
self.garchig = garchig
self.on = on
self._zeeldsen = False
@property
def zeeldsen(self) -> bool:
return self._zeeldsen
@property
def baidal(self) -> str:
return "зээлсэн" if self._zeeldsen else "боломжтой"
@property
def nas(self) -> int:
return 2026 - self.on
def zeeldekh(self) -> bool:
if self._zeeldsen:
return False
self._zeeldsen = True
return True
def butsaakh(self) -> None:
self._zeeldsen = False
def __str__(self) -> str:
return f"{self.garchig} ({self.on}, {self.nas} настай) — {self.baidal}"
nom = Zuil("Монголын нууц товчоо", 1240)
print(nom)
print(f"Зээлэгдсэн үү: {nom.zeeldsen}")
nom.zeeldekh()
print(nom)Монголын нууц товчоо (1240, 786 настай) — боломжтой
Зээлэгдсэн үү: False
Монголын нууц товчоо (1240, 786 настай) — зээлсэнГурван property байна:
zeeldsen — зөвхөн УНШИГДАНА. Setter байхгүй тул nom.zeeldsen = True гэж бичих БОЛОМЖГҮЙ.
Яагаад ингэж хийсэн бэ? Учир нь зээлэх нь ЖИНХЭНЭ ҮЙЛДЭЛ бөгөөд дүрэмтэй (аль хэдийн зээлэгдсэн зүйлийг дахин зээлж болохгүй). Тэр дүрэм нь zeeldekh() method-д байна.
Хэрэв zeeldsen нь ердийн талбар байсан бол:
python
nom.zeeldsen = True # дүрмийг ТОЙРЧ ГАРЛААХэн ч дүрмийг тойрч чадах байсан. Property нь тэр хаалгыг ХААЖ байна.
baidal — тооцоологдсон текст. Үргэлж шинэхэн.
nas — тооцоологдсон тоо. Хэрэв энэ нь талбар байсан бол, жил өнгөрөх бүрд буруу болох байсан.
Дүрэм: ТӨЛӨВ өөрчлөх нь ҮЙЛДЭЛ (method). ТӨЛӨВ унших нь ШИНЖ ЧАНАР (property).
Property ашигла:
Утга нь өөр талбаруудаас ТООЦООЛОГДДОГ (talbai, nas, niit)
Утга олгоход ШАЛГАЛТ хэрэгтэй (tselsi -ийн setter)
Талбарыг ЗӨВХӨН УНШИГДАХ болгохыг хүсэж байна (zeeldsen)
Утга нь ҮРГЭЛЖ ШИНЭХЭН байх ёстой
Property БҮҮ ашигла:
Энгийн, шалгалтгүй, тооцоолол шаардлагагүй талбар. Зүгээр л self.garchig = garchig бич. Хэрэггүй property нь зөвхөн хог.
Тооцоолол нь УДААН бол. Хэрэглэгч objekt.utga гэж бичихдээ ХУРДАН хариулт хүлээж байна. Хэрэв ард нь секунд шаардах ажил байвал — тэр нь method байх ёстой (objekt.utga_tootsoolokh()), учир нь хаалт нь "энд ажил хийгдэж байна" гэж дохио өгдөг.
Дуудалт нь ертөнцийг ӨӨРЧИЛДӨГ бол. Property уншихад ямар ч зүйл өөрчлөгдөх ЁСГҮЙ. Хэрэв өөрчлөгддөг бол — method бич.
Санамж: objekt.talbar гэсэн код нь уншигчид "энэ бол хурдан, аюулгүй, хажуугийн үр дагаваргүй" гэж амлаж байна. Тэр амлалтыг эвдэж болохгүй.
Хэрэв сонирхвол: TegshUnguult class-д @property -гүйгээр talbai method бичээд, дараа нь @property нэмээд, хоёр хувилбарыг харьцуулж үзээрэй. tu.talbai() ба tu.talbai — ялгаа нь зөвхөн хаалт.
Дараа нь Zarlaga -ийн "хадгалсан vs тооцоологдсон" жишээг өөрөө ажиллуулж, буруу үр дүнг өөрийн нүдээр хараарай. Тэр туршилт нь property-ийн ач холбогдлыг хамгийн тод харуулна.
Мөн хүсвэл: Temperatur class-д kelvin гэсэн гурав дахь property нэмээд үзээрэй (Кельвин = Цельс + 273.15). Гурван нэгж нь автоматаар хамааралтай байх болно.
1. Property-г хаалттай дуудах
python
print(tu.talbai())TypeError: 'float' object is not callable'float' object is not callable — "тоог функц шиг дуудаж болохгүй." tu.talbai нь аль хэдийн ТОО буцаасан, дараа нь та түүнийг дуудах гэж оролдсон. Property нь ХААЛТГҮЙ.
2. @property мартах
python
class TegshUnguult:
def talbai(self) -> float:
return self.urt * self.urgun
print(tu.talbai)<bound method TegshUnguult.talbai of <__main__.TegshUnguult object at 0x...>>Алдаа гарахгүй, гэхдээ утгагүй зүйл хэвлэгдэнэ. Та функц ӨӨРИЙГ нь хэвлэж байна, түүний үр дүнг биш (Бүлэг 3.5-ын "хаалт бүх ялгааг үүсгэдэг" сургамж).
3. Setter-гүй property-д утга олгох
AttributeError: property 'talbai' of 'TegshUnguult' object has no setterАнхдагчаар property нь зөвхөн уншигдана. Хэрэв утга олгох шаардлагатай бол @talbai.setter нэм.
4. Setter-ийн нэрийг өөрчлөх
python
@property
def tselsi(self) -> float:
return self._tselsi
@tselsi.setter
def tselsi_tavikh(self, utga: float) -> None: # ӨӨР нэр!
self._tselsi = utgaSetter нь ажиллахгүй. Ижил нэртэй байх ЁСТОЙ.
5. Хязгааргүй давталт (ХАМГИЙН АЮУЛТАЙ)
python
class Temperatur:
def __init__(self, tselsi: float) -> None:
self.tselsi = tselsi
@property
def tselsi(self) -> float:
return self.tselsi # ӨӨРИЙГӨӨ дуудаж байна!Traceback (most recent call last):
...
RecursionError: maximum recursion depth exceededRecursionError: maximum recursion depth exceeded гэдэг нь "хэт олон удаа өөрийгөө дуудсан" гэсэн үг.
Юу болов? tselsi property нь self.tselsi -ыг унших гэж оролдсон. Гэвч self.tselsi нь property өөрөө! Тэгэхээр тэр өөрийгөө дуудсан, дахин дуудсан, мөнхөд.
Засвар: ДОТООД талбарт ӨӨР нэр өг — доогуур зураастай:
python
class Temperatur:
def __init__(self, tselsi: float) -> None:
self._tselsi = tselsi # доогуур зураас!
@property
def tselsi(self) -> float:
return self._tselsi # доогуур зураастай нэрийг уншЭнэ бол property-тэй ажиллах хамгийн түгээмэл алдаа. Дүрэм: property нь _нэр, дотоод талбар нь _нэр — тэдгээр нь ӨӨР байх ЁСТОЙ.
6. Хэрэггүй property бичих
python
class Nom:
def __init__(self, garchig: str) -> None:
self._garchig = garchig
@property
def garchig(self) -> str:
return self._garchigАжиллана, гэхдээ ХЭРЭГГҮЙ. Шалгалт байхгүй, тооцоолол байхгүй. Зүгээр л self.garchig = garchig бич.
(Java, C# гэх мэт хэлээс ирсэн программистууд энэ алдааг байнга гаргадаг — тэдгээр хэлэнд бүх талбарт getter/setter бичих заншилтай. Python-д тийм биш. Энгийн талбар нь бүрэн зөв. Property-г зөвхөн ХЭРЭГТЭЙ болоход нэм.)
@property нь method-ыг ТАЛБАР шиг харагдуулна — хаалтгүй хандана: tu.talbai.
@ тэмдэг нь DECORATOR. Тэр нь илбэ биш — Бүлэг 10-д бүрэн тайлбарлана. Одоохондоо "заавар" гэж ойлгоод өнгөрөөрэй.
Гол ашиг тус 1: ҮРГЭЛЖ ШИНЭХЭН. Тооцоологдсон утгыг талбарт хадгалбал — эх утга өөрчлөгдөхөд тэр ХУУЧРАНА. Property нь дуудагдах бүрд дахин тооцоолно.
Гол ашиг тус 2: ШАЛГАЛТ. @нэр.setter -ээр утга олгоход шалгалт хийж, буруу утгыг хориглож болно.
Гол ашиг тус 3: ЗӨВХӨН УНШИГДАХ. Setter байхгүй бол утга олгох боломжгүй → дүрмийг тойрч гарах хаалга хаагдана.
Setter-ийн бичлэг: @нэр.setter, method нь ИЖИЛ НЭРТЭЙ байх ёстой.
_нэр (доогуур зураастай) нь "ДОТООД, гаднаас хүрч болохгүй" гэсэн заншил. Python албадан хамгаалахгүй.
RecursionError — хамгийн түгээмэл алдаа. Property нь өөрийгөө дууддаг. Засвар: дотоод талбарт _ -тэй өөр нэр өг.
Property ашигла: тооцоологдсон утга, шалгалт хэрэгтэй, зөвхөн уншигдах ёстой.
Property БҮҮ ашигла: энгийн талбар (хэрэггүй), удаан тооцоолол (method байх ёстой), ертөнцийг өөрчилдөг дуудалт (method).
ТӨЛӨВ өөрчлөх = method. ТӨЛӨВ унших = property.
Бүлэг 8 дууслаа. Та одоо special method-уудыг эзэмшлээ: __str__, __repr__, __eq__, __hash__, __len__, __getitem__, property. Мөн та протокол гэдэг гүнзгий санааг ойлголоо — Python-ы бүх "илбэ" нь тэр л зарчим дээр баригдсан.
Гэвч та мөн хэр их код бичих хэрэгтэйг ч бас мэдэрсэн. Нэг энгийн class-д __init__, __repr__, __eq__, __hash__ — дөрвөн method, бараг бүгд механик, class бүрд давтагдана.
Бүлэг 9-д тэр асуудал БҮРМӨСӨН шийдэгдэнэ. dataclass нь тэдгээр бүх method-ыг АВТОМАТААР үүсгэнэ. Та зөвхөн талбаруудаа зарлана — Python үлдсэнийг нь хийнэ. Мөн тэнд та @dataclass гэсэн decorator-ыг ашиглана, тэгээд эцэст нь Бүлэг 9-ийн guided build — номын сангийн систем — бүх зүйлийг нэгтгэнэ.
Мөн Бүлэг 9-д би нэг чухал зүйлийг чангаар хэлнэ: dataclass + type hint + default утга — энэ гурвалсан нь ЯГ ТЭР хэлбэр бөгөөд FastAPI-гийн Pydantic ашигладаг. Тэр нь санамсаргүй биш.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.