Ачааллж байна...
Түвшин 3-т тавтай морил. Энэ түвшний нэр нь OOP-ийн эзэн. Course 2-т та class бичиж, __init__-ээр объект үүсгэж, method, attribute, удамшил хэрэглэсэн. Өмнөх хоёр түвшинд бид for loop ба функцийн дотоод амьдралыг нээсэн шиг энэ түвшинд class-ийн дотоод хэрэгслүүдийг нээнэ. Эхний бүлэг class-ийн гүнзгий хэрэгслүүдэд зориулагдсан: method-ын өөр төрлүүд, class-ийн түвшний төлөв, dataclass-ийн гүн боломжууд. Эхний хичээл бол classmethod.
Асуудлаас эхэлье. Course 2-ын class бүр объект үүсгэх ганц хаалгатай байсан: __init__. Та Student("Бат", 95) гэж бичиж, нэр ба оноог шууд өгдөг байсан. Гэтэл бодит програмд өгөгдөл ийм цэвэрхэн хэлбэрээр ирдэггүй. Тэр CSV файлаас "Бат,95" гэсэн мөр хэлбэрээр ирж болно. Course 3-ын FastAPI-ийн адил JSON-оос {"name": "Бат", "score": 95} гэсэн dict хэлбэрээр ирж болно. Хичээл 3.4-ийн log шиг урт мөрөөс задлагдах хэрэгтэй байж болно. Хэлбэр бүрийг объект болгохын тулд хаа нэгтээ хөрвүүлэх код бичих ёстой. Асуулт нь: тэр кодыг хаана бичих вэ?
classmethod бол энэ асуултын Python-ий хариулт. Энэ нь объект биш, class-ийг өөрийг нь эхний argument болгон хүлээн авдаг method юм. Түүний хамгийн чухал хэрэглээ бол alternative constructor буюу "өөр хэлбэрийн өгөгдлөөс объект үүсгэх нэмэлт хаалга". Энэ хичээлд бид from_string, from_dict гэх мэт хаалгуудыг бүтээж, classmethod-ын эхний argument cls-ийг тэвчээртэйгээр ойлгоно. Мөн яагаад class-ийн нэрийг шууд бичихийн оронд cls ашиглах ёстойг удамшлын жишээгээр харна.
Орон сууцны том байшинг төсөөлөөд үзээрэй. Байшин гол хаалгатай: оршин суугчид, зочид түүгээр ордог. Энэ бол __init__. Гэвч байшинд өөр хаалгууд ч бий. Хүргэлтийн хаалганаас курьер хайрцаг авчирч, ачааг задалж, бэлэн зүйлийг байшин руу оруулна. Гаражийн хаалганаас машинтай хүн ороод, машинаа тавиад, байшин руу гарна. Хаалга бүр өөр төрлийн ирэлтэд зориулагдсан, гэхдээ бүгд эцэст нь ижил байшингийн дотор хүргэнэ.
Нэг чухал нарийн ширийн зүйл: туслах хаалганууд өөр өөрсдийн гэсэн "байшингийн дотор" байдаггүй. Хүргэлтийн хаалга ачааг задлаад, бэлэн зүйлийг гол хаалганы коридор руу дамжуулна. Байшинд орох бүх зам эцэст нь нэг гол коридороор дамждаг. Ингэснээр байшингийн дүрэм, аюулгүй байдлын шалгалт бүгд нэг газарт байна.
classmethod бол туслах хаалга. from_string "Бат,95" гэсэн мөрийг хүлээн авч, түүнийг задалж, нэр ба оноо гаргаж, тэдгээрийг гол хаалга __init__ руу дамжуулна. from_dict dict хүлээн авч мөн адил хийнэ. Бүх хаалга эцэст нь __init__-ээр дамждаг тул объект бүр ижил дүрмээр бүтээгдэнэ.
Course 2-ын хэв маягийн энгийн class-аас эхэлье:
python
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __repr__(self):
return f"Student({self.name!r}, {self.score})"
s = Student("Бат", 95)
print(s)Student('Бат', 95)__repr__ нь Course 2-оос танил тусгай method: объектыг хэвлэх эсвэл interactive горимд харуулах үед хэрэглэгдэх тайлбар. Түүнийг бичсэнээр бидний trace-үүд уншигдахуйц болно.
Одоо өгөгдөл "Бат, 95" гэсэн мөр хэлбэрээр ирдэг гэж бодъё. classmethod нэмье:
python
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_string(cls, text):
name, score = text.split(",")
return cls(name.strip(), int(score))
def __repr__(self):
return f"Student({self.name!r}, {self.score})"
s = Student.from_string("Бат, 95")
print(s)
print(s.score + 5)Student('Бат', 95)
100Кодыг уншъя. from_string-ийн дээр @classmethod бий: энэ бол decorator, Бүлэг 5-ын мэдлэг энд шууд хэрэглэгдэнэ. Түүний эхний argument self биш, cls. Мөрийг таслалаар хувааж, хоёр хэсгийг name ба score-д задалж, нэрийн хоёр талын зайг strip()-ээр арилгаж, оноог int болгоно. Дараа нь cls(name.strip(), int(score)) дуудна. Энэ бол гол хаалга: шинэ объект үүсгэж, __init__-ийг дуудна. Үр дүнгийн объектыг буцаана. Сүүлийн мөр оноо жинхэнэ тоо болсныг баталж байна: 95 + 5 = 100, string байсан бол алдаа гарах байсан.
from_string-ийг хэрхэн дуудсаныг анзаараарай: Student.from_string(...), class-ийн нэрээр, объектгүйгээр. Энгийн method-ыг объектоор дуудах ёстой: s.study(). classmethod-ыг class-аар дуудна, учир нь объект хараахан байхгүй. Тэр бол объектыг үүсгэх хаалга.
cls гэж юу вэ
Course 2-т self-ийг анх ойлгох нь хэцүү байсныг санаж байгаа байх: s.study() гэж бичихэд Python s-ийг study-ийн эхний argument болгон далдуур өгдөг. cls ч яг ижил зарчмаар ажилладаг, ганц ялгаатай: Python объектын оронд class-ийг өгнө. Шалгая:
python
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def show_cls(cls):
print("cls:", cls)
print("cls is Student:", cls is Student)
Student.show_cls()cls: <class '__main__.Student'>
cls is Student: Truecls бол Student class өөрөө, Student-ийн хуулбар ч биш, Student-ийн объект ч биш. <class '__main__.Student'> нь “main модульд тодорхойлогдсон Student class” гэсэн утгатай. Тэгэхээр from_string дотор cls(...) нь Student(...)-тэй яг ижил. Python-д class бол дуудагдаж чаддаг объект, дуудахад шинэ объект үүсгэдэг. Хичээл 5.4-т property ба dataclass дээр бид энэ санаатай аль хэдийн танилцсан.
self ба cls-ийн ялгааг нэг өгүүлбэрт: self бол нэг объект, cls бол бүх объектуудын үүсгэгч class. Хоёулаа уламжлалт нэр, Python тэдгээрийг шаарддаггүй, гэхдээ бүх Python хөгжүүлэгч тэдгээрийг хэрэглэдэг тул та ч мөн хэрэглэнэ. Яагаад class гэж нэрлэж болохгүй вэ гэвэл class бол Python-ий түлхүүр үг, хувьсагчийн нэр байж чадахгүй. Тиймээс уламжлал ёсоор “cls” гэж товчилдог.
Trace: туслах хаалгаар орох
Student.from_string("Бат, 95") дуудагдахад:
Алхам 1 (дуудлага). Python Student class-ийн from_string-ийг олно. Тэр classmethod гэдгийг мэдэж, эхний argument болгон Student class-ийг далдуур нэмнэ. from_string-ийн frame-д: cls = Student class, text = "Бат, 95".
Алхам 2 (задлалт). text.split(",") нь ["Бат", “ 95”] буцаана. frame-д: cls, text, name = "Бат", score = “ 95”. score нь хоосон зайтай string хэвээр.
Алхам 3 (гол хаалга руу). cls(name.strip(), int(score)). Эхлээд argument-ууд бодогдоно: "Бат" ба 95. int() string-ийн хоёр талын зайг өөрөө хүлээн зөвшөөрдөг тул “ 95” нь 95 болно. Дараа нь cls дуудагдана, тэр Student. Python шинэ хоосон Student объект үүсгэж, түүний __init__-ийг self = шинэ объект, name = "Бат", score = 95-аар дуудна.
Алхам 4 (__init__). self.name, self.score оноогдоно. __init__ дуусна. Санах ойд: Student объект, name = "Бат", score = 95.
Алхам 5 (буцах). from_string тэр объектыг буцааж, s нэрэнд оноогдоно.
Энэ trace-ийн гол санаа Алхам 3-т байна: туслах хаалга өөрөө объект бүтээдэггүй, тэр өгөгдлийг бэлтгээд гол хаалга руу дамжуулдаг. Хэрэв хожим __init__-д шалгалт нэмбэл, жишээлбэл оноо 0-100-ийн хооронд байх ёстой гэвэл, from_string-ээр үүсгэсэн объектууд ч автоматаар шалгагдана. Нэг коридор, нэг дүрэм.
Жишээ 1: яагаад cls, Student биш
from_string дотор cls(...)-ийн оронд шууд Student(...) бичиж болох байсан. Одоохондоо үр дүн ижил. Ялгаа удамшлын үед гарна. Course 2-оос танил удамшлаар Student-ээс шинэ class үүсгэе:
python
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_string(cls, text):
name, score = text.split(",")
return cls(name.strip(), int(score))
def __repr__(self):
return f"{type(self).__name__}({self.name!r}, {self.score})"
class GraduateStudent(Student):
def thesis_ready(self):
return self.score >= 90
g = GraduateStudent.from_string("Сараа, 93")
print(g)
print(g.thesis_ready())GraduateStudent('Сараа', 93)
TrueGraduateStudent нь Student-ээс from_string-ийг өвлөсөн бөгөөд өөрийн thesis_ready method нэмсэн. __repr__ одоо class-ийн нэрийг хатуу биш, type(self).__name__-ээр авдаг тул объект бүр өөрийн жинхэнэ class-ийн нэрийг харуулна. GraduateStudent.from_string(...) дуудагдахад Python cls-д GraduateStudent-ийг өгнө: тэр classmethod-ыг дуудсан class. Тэгэхээр cls(...) нь GraduateStudent объект үүсгэнэ, thesis_ready ажиллана.
Одоо from_string дотор cls-ийн оронд Student хатуу бичсэн бол юу болохыг харъя:
python
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_string(cls, text):
name, score = text.split(",")
return Student(name.strip(), int(score))
def __repr__(self):
return f"{type(self).__name__}({self.name!r}, {self.score})"
class GraduateStudent(Student):
def thesis_ready(self):
return self.score >= 90
g = GraduateStudent.from_string("Сараа, 93")
print(g)
print(g.thesis_ready())Student('Сараа', 93)
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'thesis_ready'Бид GraduateStudent-ийн хаалгаар орсон ч энгийн Student гарч ирсэн. from_string cls-ийг хүлээн авсан ч хэрэглээгүй. Хамгийн аюултай нь эхний print мөр: алдаагүй, ердөө “Student” гэсэн нэр л буруу. Хэрэв тэр объект thesis_ready-г дуудагдахгүй газар хэрэглэгдвэл алдаа хэзээ ч илрэхгүй, програм буруу төрлийн объекттой чимээгүй ажиллана. Дүрэм: classmethod дотор объект үүсгэхдээ үргэлж cls(...) ашигла. Энэ нь таны бичсэн туслах хаалгууд бүх удамшсан class-д автоматаар зөв ажиллахыг баталгаажуулна.
Жишээ 2: from_dict — JSON ба API-аас ирэх өгөгдөл
Course 3-т та FastAPI-тай ажиллахдаа JSON өгөгдөл хүлээн авсан. Python-д JSON задлагдахад dict болдог. dict-ээс объект үүсгэх хаалга нэмье:
python
class Student:
def __init__(self, name, score, group="тодорхойгүй"):
self.name = name
self.score = score
self.group = group
@classmethod
def from_dict(cls, data):
return cls(
name=data["name"],
score=int(data["score"]),
group=data.get("group", "тодорхойгүй"),
)
def __repr__(self):
return f"{type(self).__name__}({self.name!r}, {self.score}, {self.group!r})"
api_rows = [
{"name": "Бат", "score": 95, "group": "А"},
{"name": "Сараа", "score": "88", "email": "saraa@example.com"},
]
students = [Student.from_dict(row) for row in api_rows]
for s in students:
print(s)Student('Бат', 95, 'А')
Student('Сараа', 88, 'тодорхойгүй')from_dict бодит өгөгдлийн гурван асуудлыг шийдэж байна. Нэгдүгээрт, хоёр дахь мөрөнд оноо “88” гэсэн string хэлбэрээр ирсэн: int() түүнийг тоо болгосон. Гадаад эх сурвалжаас ирэх өгөгдөлд төрөл үргэлж найдвартай биш. Хоёрдугаарт, хоёр дахь мөрөнд group байхгүй: data.get("group", "тодорхойгүй") анхдагч утга өгсөн. Гуравдугаарт, хоёр дахь мөрөнд бидэнд хэрэггүй email түлхүүр бий: from_dict хэрэгтэй түлхүүрүүдийг л нэрээр нь сонгосон тул email зүгээр л үл тоогдсон.
Сүүлийн мөрөнд list comprehension-д from_dict-ийг дуудсан. Хэрэв Хичээл 3.1-ийн дүрмийг санавал, бэлэн нэртэй функцтэй map ч ялах байсан: list(map(Student.from_dict, api_rows)). classmethod-ыг class-аар дамжуулан авахад тэр cls-ээ аль хэдийн бэхэлсэн, нэг argument авдаг функц болно. Тэгэхээр map, sorted-ийн key зэрэг газарт шууд өгч болно.
Бусдын кодонд return cls(**data) гэсэн богино хэлбэр харж магадгүй. **data нь dict-ийг түлхүүр үгийн argument болгон задлана, Хичээл 5.1-ийн **kwargs-ийн эсрэг тал. Энэ нь data-ийн түлхүүрүүд __init__-ийн параметрүүдтэй яг таарч байгаа үед ажилладаг. Гэвч хоёр дахь мөрийн email шиг илүү түлхүүр байвал TypeError гарна, “88” шиг буруу төрлийг ч засахгүй. Гадаад өгөгдөлд хэрэгтэй түлхүүрүүдийг нэрээр сонгох нь илүү найдвартай.
Жишээ 3: стандарт сан дахь туслах хаалганууд
Одоо та classmethod-ыг таньдаг болсон тул Python-ий стандарт санд түүнийг хаа сайгүй харна. Хамгийн тод жишээ бол datetime:
python
from datetime import datetime
a = datetime(2026, 10, 9, 8, 15, 40)
b = datetime.fromisoformat("2026-10-09 08:15:40")
c = datetime.now()
print(a == b)
print(type(b).__name__, type(c).__name__)True
datetime datetimedatetime(...) бол гол хаалга: он, сар, өдрийг тусад нь авна. datetime.fromisoformat(...) бол string-ийн туслах хаалга, бидний from_string-тэй яг ижил санаа. datetime.now() ч classmethod: ямар ч өгөгдөл авахгүй, одоогийн цагаас объект үүсгэнэ. Гурвуулаа datetime объект буцаана. Бусад жишээ: dict.fromkeys(["a", "b"], 0) түлхүүрийн жагсаалтаас dict үүсгэнэ, int.from_bytes(...) байтаас тоо үүсгэнэ. Нэрийн хэв маягийг анзаараарай: “from” эсвэл “from_”-оор эхэлсэн бол энэ нь ихэвчлэн туслах хаалга. Python нийгэмлэгийн энэ уламжлалыг та ч дагах нь зүйтэй.
Мөн Хичээл 3.2-т бид огноонуудыг string хэлбэрээр харьцуулсныг санаарай: тэнд “9/10/2026” гэх мэт хэлбэрт string харьцуулалт буруу ажиллана гэж анхааруулсан. datetime.fromisoformat бол тэр асуудлын шийдэл: огноог string-ээс жинхэнэ огноо объект болгоод, тэдгээрийг найдвартай харьцуулах.
Жишээ 4: Түвшин 1-тэй холбох — from_line ба from_file
Хичээл 3.4-ийн log боловсруулагчид бид parse_line функцээр мөр бүрийг dict болгосон. Одоо dict-ийн оронд class ашиглаж, туслах хаалгануудыг Түвшин 1-ийн generator-тэй хослуулъя:
python
class LogRecord:
def __init__(self, time, level, user, duration_ms):
self.time = time
self.level = level
self.user = user
self.duration_ms = duration_ms
@classmethod
def from_line(cls, line):
date, time, level, rest = line.split(" ", 3)
fields = dict(pair.split("=", 1) for pair in rest.split() if "=" in pair)
return cls(time, level, fields["user"], int(fields["duration_ms"]))
@classmethod
def from_file(cls, path):
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#"):
yield cls.from_line(line)
def __repr__(self):
return f"LogRecord({self.time}, {self.level}, {self.user}, {self.duration_ms}ms)"
from itertools import islice
for record in islice(LogRecord.from_file("app.log"), 3):
print(record)LogRecord(00:00:00, INFO, dorj, 38ms)
LogRecord(00:00:00, INFO, bat, 161ms)
LogRecord(00:00:00, WARNING, anu, 17ms)Энд хоёр туслах хаалга бий. from_line ганц мөрөөс нэг объект үүсгэнэ, Хичээл 3.4-ийн parse_line-ийн class хувилбар. from_file бол сонирхолтой: тэр classmethod ба generator. Файлыг мөр мөрөөр уншиж, хоосон болон “#”-оор эхэлсэн мөрүүдийг алгасаад, мөр бүрийг cls.from_line(...)-ээр объект болгож yield хийнэ. cls.from_line гэж cls-ээр дуудсан нь удамшсан class-д ч зөв from_line-ийг дуудах баталгаа. Хичээл 2.4-ийн islice эхний гурван объектыг аваад зогсоно, файлын үлдсэн хэсэг уншигдахгүй. Энэ нь ганц объект биш, объектуудын урсгал үүсгэдэг туслах хаалга: Түвшин 1-ийн санах ойн хэмнэлт ба Түвшин 3-ын class нэг дор нийлж байна. Таны app.log-ийн мөрүүд өөр байж болно.
Хүсвэл Student-д from_csv_file(path) гэсэн classmethod generator нэмж, CSV файлын мөр бүрээс from_string-ээр объект үүсгэдэг болгож болно. Гарчгийн мөрийг алгасахын тулд Хичээл 1.3-ын next(f) загварыг ашиглаж болно. Сонирхвол Жишээ 1-ийн GraduateStudent-д from_dict-ийг дуудаж, ямар төрлийн объект гарахыг таамаглаад шалгаарай. Мөн classmethod-ыг объектоор дуудаж үзэх нь сонирхолтой: s.from_string("Ану, 77"). Энэ ажиллах уу, cls юу болох вэ? Python энэ тохиолдолд cls-д объектын class-ийг өгдөг. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
TypeError: Student.from_string() missing 1 required positional argument: ‘text’. “Student.from_string()-д шаардлагатай ‘text’ argument дутуу байна” гэсэн утгатай. @classmethod мөрийг мартсан үед гарна. Decorator-гүй бол from_string энгийн функц бөгөөд Student.from_string("Бат, 95") дуудлагад Python ямар ч зүйл далдуур нэмэхгүй. Тэгэхээр "Бат, 95" string cls параметрт очиж, text хоосон үлдэнэ. Мессеж cls-ийн тухай огт дурдахгүй тул будлиантай. classmethod-той холбоотой алдаа гарвал хамгийн түрүүнд decorator байгаа эсэхийг шалгаарай.
ValueError: not enough values to unpack (expected 2, got 1). "Задлах утга хүрэлцэхгүй: 2 хүлээсэн, 1 ирлээ" гэсэн утгатай. from_string-д таслалгүй мөр өгөгдөхөд гарна, жишээлбэл "Бат 95". split(“,”) ганц элементтэй жагсаалт буцааж, хоёр хувьсагчид задлах боломжгүй. Олон таслалтай мөр, жишээлбэл "Бат, 95, А", “too many values to unpack (expected 2)” гэсэн эсрэг алдаа өгнө. Гадаад өгөгдлийг хүлээн авдаг туслах хаалга ийм алдааг өөрөө барьж, илүү тодорхой мессежтэй ValueError шидэх нь сайн дадал: “from_string: 'нэр,оноо' хэлбэр хүлээсэн, 'Бат 95' ирлээ”. Хичээл 6.4-т бид өөрсдийн тодорхой мессежүүд хэрхэн хэрэглэгчид тусалдгийг харсан.
ValueError: invalid literal for int() with base 10: ‘ 9x’. “10-тын тооллын системд int()-д хүчингүй утга: ‘ 9x’” гэсэн утгатай. Оноо тоо биш байхад гарна. Мессеж нь асуудалтай утгыг хашилтанд харуулж, тэр ч бүү хэл хоосон зайг ч оруулдаг тул юу ирснийг яг харж болно.
TypeError: Student.init() got an unexpected keyword argument ‘email’. “Student.init()-д таамаглаагүй ‘email’ түлхүүр үгийн argument ирлээ” гэсэн утгатай. from_dict-ийг cls(**data) гэж бичсэн, data-д __init__-ийн мэдэхгүй түлхүүр байх үед гарна. Шийдэл нь Жишээ 2-ын загвар: хэрэгтэй түлхүүрүүдийг нэрээр сонгох.
Удамшсан class-аас буруу төрлийн объект гарах. Мессежгүй алдаа, Жишээ 1-ийн урхи. classmethod дотор cls-ийн оронд class-ийн нэрийг хатуу бичсэн үед удамшсан class-ийн туслах хаалга эцэг class-ийн объект үүсгэнэ. Шинж тэмдэг нь print(type(obj)) хүлээсэн class-ийн оронд эцэг class-ийг харуулах, эсвэл удамшсан class-д нэмсэн method-уудын AttributeError. Шийдэл: classmethod дотор class-ийн нэрийг огт бичихгүй, үргэлж cls.
classmethod нь @classmethod decorator-оор тодорхойлогдож, эхний argument болгон объектын оронд class-ийг өөрийг нь, уламжлалт нэрээр cls-ийг хүлээн авдаг method. Тэр class-аар дуудагдана: Student.from_string(...). Хамгийн чухал хэрэглээ нь alternative constructor: өгөгдлийг өөр хэлбэрээс, string, dict, мөр, файлаас бэлтгэж, cls(...)-ээр гол хаалга __init__ руу дамжуулдаг туслах хаалга. Бүх хаалга __init__-ээр дамждаг тул объект бүр ижил дүрмээр бүтээгдэнэ. classmethod дотор үргэлж cls ашиглана, учир нь удамшсан class-аар дуудахад cls нь тэр удамшсан class болж, зөв төрлийн объект үүснэ. Гадаад өгөгдлийн туслах хаалга төрлийг хөрвүүлж, дутуу түлхүүрт анхдагч өгч, илүү түлхүүрийг үл тоох ёстой. datetime.fromisoformat, datetime.now, dict.fromkeys зэрэг стандарт сангийн “from”-оор эхэлсэн method-ууд энэ загварыг дагадаг. classmethod generator байж, объектуудын урсгал үүсгэж болно.
Одоо бид method-ын хоёр төрлийг мэднэ: объект хүлээн авдаг энгийн method ба class хүлээн авдаг classmethod. Python-д гурав дахь төрөл бий: юу ч хүлээн авдаггүй staticmethod. Дараагийн хичээлд түүнийг танилцуулж, гурван төрлийг нэг class дээр зэрэгцүүлэн харуулна. Хамгийн чухал нь хэзээ аль нь хэрэгтэйг шийдэх энгийн, шударга дүрэм гаргана, ингэснээр та method бичих бүрдээ зөв төрлийг итгэлтэйгээр сонгож чадна.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.