Ачааллж байна...
Өмнөх хичээлд Strategy pattern "юу хийх" сонголтыг гадагш гаргасан: хүргэлтийн төлбөрийг аль аргаар тооцох вэ. Энэ хичээлийн pattern өөр төрлийн сонголттой ажиллана: “аль объектыг үүсгэх”. Нэр нь Factory буюу "үйлдвэр": объект үүсгэх ажлыг нэг газарт, нэг хаалганы цаана төвлөрүүлэх. Объект хэрэгтэй код "надад ийм төрлийн юм хэрэгтэй" гэж хэлдэг, харин аль class-ийг яг ямар тохиргоотойгоор үүсгэхийг үйлдвэр шийддэг.
Та энэ санааг аль хэдийн хэрэглэсэн. Хичээл 7.1-ийн classmethod туслах хаалганууд, from_string, from_dict, from_file, бол factory-ийн хамгийн энгийн хэлбэр: нэг class-ийн объектыг өөр өөр хэлбэрийн өгөгдлөөс үүсгэх. Энэ хичээлд тэр санаа "томорно": үйлдвэр нэг class-ийн объект биш, хэд хэдэн өөр class-ийн аль нэгийг сонгож үүсгэнэ. Сонголт тохиргоонд үндэслэнэ: тохиргооны файлд “csv” гэж бичигдсэн бол нэг class, “json” бол өөр class.
Шударгаар тэмдэглэе: Gang of Four-ын номд “Factory” гэсэн нэр хоёр өөр pattern-д хэрэглэгддэг: Factory Method ба Abstract Factory. Хоёулаа удамшлын нарийн бүтэцтэй, C++-ийн хязгаарлалтуудад зориулагдсан. Python кодонд хамгийн түгээмэл хэлбэр нь илүү энгийн: factory функц ба бүртгэл. Бид энэ хэлбэрийг бүтээх бөгөөд өмнөх хичээлийн функцүүдийн dict-ийн санаа энд шууд хэрэглэгдэнэ, ердөө dict-ийн утгууд функц биш, class байна. Хичээл 8.3-ын сургамжийг санаарай: class ч callable.
Машин түрээсийн газрын үйлчилгээний ширээг төсөөлөөд үзээрэй. Та ширээн дээр очиж "жийп хэрэгтэй, гурван хоногоор" гэнэ. Та машины зогсоол руу өөрөө очиж, аль машиныг авахаа сонгож, бензинийг нь шалгаж, бичиг баримтыг бөглөхгүй. Ажилтан таны хүсэлтийг сонсоод, аль машин тохирохыг шийдэж, бэлтгэж, танд түлхүүрийг өгнө. Та зөвхөн ангиллыг хэлсэн, ажилтан тодорхой машиныг сонгосон.
Энэ системийн хоёр гол шинж чанар бий. Нэгдүгээрт, бүх машин ижил удирдлагатай: жолооны хүрд, тоормос, хурдасгагч. Тиймээс та аль машин авснаас үл хамааран жолоодож чадна. Энэ бол Бүлэг 8-ын протоколын санаа: үйлдвэрийн гаргасан объектууд бүгд ижил method-уудтай. Хоёрдугаарт, түрээсийн газар шинэ ангилал, жишээлбэл цахилгаан машин нэмбэл, үйлчлүүлэгчид юу ч өөрчлөгдөхгүй: тэд ширээн дээр "цахилгаан" гэж хэлэх л болно. Машины зогсоол өргөжсөн, ширээний ард ажлын журам өөрчлөгдсөн, харин үйлчлүүлэгчийн туршлага ижил.
Factory pattern бол тэр ширээ. Объект хэрэгтэй код ангилал эсвэл тохиргоог хэлнэ, үйлдвэр аль class-ийг үүсгэхийг шийдэж, бэлэн объектыг өгнө.
Бидний жишээ: өгөгдөл импортлогч. Дэлгүүрийн борлуулалтын мэдээ өөр өөр эх сурвалжаас, өөр өөр хэлбэрээр ирдэг: нягтлангийн хүснэгтээс CSV файл, онлайн дэлгүүрийн API-аас JSON файл, кассын тэмдэглэлээс энгийн текст файл. Хэлбэр тус бүрийг уншдаг class бичье. Бүгд ижил гэрээг дагана: path хүлээн авдаг __init__, мөр бүрийг dict болгон yield хийдэг load() method. Ингэснээр тэдгээр машинууд шиг ижил удирдлагатай.
Туршилтын гурван файл хэрэгтэй. sales.csv:
product,amount
Ном,25000
Дэвтэр,1500sales.json:
[
{"product": "Үзэг", "amount": 800},
{"product": "Цүнх", "amount": 45000}
]notes.txt:
Захиалга 1 хүлээн авлаа
Захиалга 2 цуцлагдлааОдоо гурван loader class:
python
import csv
import json
from pathlib import Path
class CsvLoader:
def __init__(self, path, delimiter=","):
self.path = Path(path)
self.delimiter = delimiter
def load(self):
with open(self.path, encoding="utf-8", newline="") as f:
for row in csv.DictReader(f, delimiter=self.delimiter):
row["amount"] = int(row["amount"])
yield row
class JsonLoader:
def __init__(self, path):
self.path = Path(path)
def load(self):
with open(self.path, encoding="utf-8") as f:
data = json.load(f)
for row in data:
yield row
class LinesLoader:
def __init__(self, path):
self.path = Path(path)
def load(self):
with open(self.path, encoding="utf-8") as f:
for number, line in enumerate(f, start=1):
line = line.strip()
if line:
yield {"line": number, "text": line}Гурван стандарт сангийн хэрэгсэл хэрэглэгдсэн. pathlib.Path бол файлын замыг string биш, объект болгон төлөөлдөг: удахгүй түүний .suffix attribute-ыг хэрэглэнэ. csv.DictReader CSV файлын эхний мөрийг гарчиг гэж уншиж, дараагийн мөр бүрийг гарчгийн нэрсээр түлхүүрлэсэн dict болгоно. CSV-д бүх утга string байдаг тул CsvLoader amount-ийг int болгоно, ингэснээр JSON-ийн гаралттай ижил хэлбэртэй болно. Энэ бол чухал дизайны шийдвэр: гэрээ нь зөвхөн method-уудын нэр биш, тэдгээрийн гаралтын хэлбэр ч. json.load() JSON файлыг Python list ба dict болгоно. load() бүх гурван class-д generator: Түвшин 1-ийн санах ойн хэмнэлт хэвээр.
Одоо эдгээрийг хэрэглэх код. Хамгийн шууд арга нь format-аар нь сонгох:
python
def import_sales(path, fmt):
if fmt == "csv":
loader = CsvLoader(path)
elif fmt == "json":
loader = JsonLoader(path)
elif fmt == "txt":
loader = LinesLoader(path)
else:
raise ValueError(f"Танигдаагүй формат: {fmt}")
return list(loader.load())Энэ нь Хичээл 9.2-ын if-elif гинжтэй ижил асуудалтай, гэхдээ илүү ноцтой хэлбэрээр. Ихэнх програмд объект үүсгэх ийм код олон газарт давтагддаг: импортын тайланд, өдрийн шалгалтанд, тестэд. Шинэ формат нэмэгдэх бүрт тэр бүх газрыг олж засах хэрэгтэй, нэгийг нь мартах эрсдэлтэй.
Хичээл 9.2-ын dict загвараар үйлдвэр бүтээе. dict-ийн утгууд функц биш, class байна:
python
LOADERS = {
"csv": CsvLoader,
"json": JsonLoader,
"txt": LinesLoader,
}
def create_loader(path, fmt):
cls = LOADERS.get(fmt)
if cls is None:
known = ", ".join(sorted(LOADERS))
raise ValueError(f"'{fmt}' форматын loader алга. Боломжтой: {known}")
return cls(path)
for path, fmt in [("sales.csv", "csv"), ("sales.json", "json"), ("notes.txt", "txt")]:
loader = create_loader(path, fmt)
rows = list(loader.load())
print(type(loader).__name__, len(rows), rows[0])CsvLoader 2 {'product': 'Ном', 'amount': 25000}
JsonLoader 2 {'product': 'Үзэг', 'amount': 800}
LinesLoader 2 {'line': 1, 'text': 'Захиалга 1 хүлээн авлаа'}create_loader бол үйлдвэр: түрээсийн газрын ширээ. Тэр format-аар class-ийг хайж, олдвол дуудаж, бэлэн объектыг буцаана. Loop дотор объект хэрэгтэй код аль class-ийг үүсгэхийг огт мэдэхгүй: тэр зөвхөн create_loader-ийг дуудаж, үр дүнгийн load()-ийг дууддаг. CSV-ийн amount одоо int болсныг анзаараарай: 25000, хашилтгүй.
Trace: class-ийг сонгож, дуудах нь
create_loader("sales.json", "json") дуудагдахад:
Алхам 1 (dict-ээс хайх). LOADERS.get("json") нь JsonLoader class объектыг буцаана. Хичээл 7.1-ийн cls шиг: class өөрөө, түүний объект биш. create_loader-ийн frame-д: path = “sales.json”, fmt = “json”, cls = JsonLoader class.
Алхам 2 (class дуудагдах). cls(path). cls нэр JsonLoader class-ийг заадаг тул энэ нь JsonLoader("sales.json")-тэй яг ижил: шинэ объект үүсч, __init__ ажиллаж, self.path-ийг тохируулна. Санах ойд: JsonLoader объект, path = Path(“sales.json”).
Алхам 3 (буцах). Объект буцна. Файл хараахан нээгдээгүй: __init__ зөвхөн замыг хадгалсан. Файл load() generator анх асуух үед л нээгдэнэ, Хичээл 2.3-ын read_lines шиг.
Энэ trace-ийг өмнөх хичээлийнхтэй харьцуулаарай. Тэнд dict-ээс функц гарч ирээд, strategy(order) гэж дуудагдсан. Энд dict-ээс class гарч ирээд, cls(path) гэж дуудагдсан. Хэлбэр яг ижил, учир нь Python-д функц ч, class ч callable. Ялгаа нь зөвхөн дуудлагын үр дүнд: функц тооцоолсон утга буцаадаг, class шинэ объект буцаадаг. Strategy бол "сонгосон үйлдлийг хийх", Factory бол "сонгосон төрлийн объект бүтээх". Python-д хоёулаа нэг dict, нэг хайлт, нэг дуудлага.
Жишээ 1: бүртгэлийн class decorator
Хичээл 9.2-ын шиг, LOADERS dict-ийг гараар бөглөх нь шинэ loader нэмэхэд dict-д бүртгэхээ мартах эрсдэлтэй. Бүртгэлийн decorator-ыг class-д хэрэглэе. Хичээл 5.4-т dataclass class decorator байсныг санаарай: class-ийг авч, түүнийг буцаадаг:
python
LOADERS = {}
def loader_for(*formats):
def register(cls):
for fmt in formats:
LOADERS[fmt] = cls
return cls
return register
@loader_for("csv")
class CsvLoader:
...
@loader_for("json")
class JsonLoader:
...
@loader_for("txt", "log")
class LinesLoader:
...
print(LOADERS){'csv': <class '__main__.CsvLoader'>, 'json': <class '__main__.JsonLoader'>, 'txt': <class '__main__.LinesLoader'>, 'log': <class '__main__.LinesLoader'>}Class-уудын бие өмнөхтэй ижил тул “...”-оор товчилсон. loader_for хэд хэдэн format хүлээн авна, Хичээл 4.3-ын *args: LinesLoader “txt” ба “log” хоёр нэрээр бүртгэгдсэн, нэг class хоёр формат уншиж чадна. register дотоод функц class-ийг өөрчлөхгүй, бүртгээд буцаана. Хичээл 5.1-ийн тодорхойлох цагийн мэдлэгийг санаарай: бүртгэл class тодорхойлогдох мөчид, файл импортлогдох үед хийгдэнэ. Тэгэхээр create_loader анх дуудагдахаас өмнө LOADERS бүрэн бөглөгдсөн байна. Шинэ формат нэмэх нь шинэ class ба түүний дээр нэг decorator мөр: create_loader болон түүнийг хэрэглэдэг бүх код хөндөгдөхгүй. Хичээл 9.2-ын "өргөтгөлд нээлттэй, өөрчлөлтөд хаалттай" зарчим.
Жишээ 2: тохиргоонд суурилсан үйлдвэр
Бодит програмд "аль формат" гэсэн мэдээлэл ихэвчлэн тохиргооны файлаас ирдэг, JSON эсвэл YAML-аас уншсан dict хэлбэрээр. Мөн тохиргоо нэмэлт тохиргоог агуулж болно: зарим CSV файлууд таслалын оронд цэг таслал ашигладаг. Үйлдвэрийг тохиргоог бүхэлд нь хүлээн авдаг болгоё:
python
def create_loader(config):
options = dict(config)
path = options.pop("path")
fmt = options.pop("format", None) or Path(path).suffix.lstrip(".").lower()
cls = LOADERS.get(fmt)
if cls is None:
known = ", ".join(sorted(LOADERS))
raise ValueError(f"'{fmt}' форматын loader алга. Боломжтой: {known}")
return cls(path, **options)Мөр бүрийг уншъя. dict(config) тохиргооны хуулбар үүсгэнэ: бид түлхүүрүүдийг устгах тул анхны тохиргоог өөрчлөхгүй байхын тулд, Хичээл 7.3-ын хуваалцсан өгөгдлийн урхинаас сэргийлж. options.pop("path") замыг авч, dict-ээс хасна. options.pop("format", None) форматыг авна, байхгүй бол None. Хэрэв формат заагаагүй бол файлын өргөтгөлөөс таана: Path("sales.csv").suffix нь “.csv”, lstrip(“.”) цэгийг арилгаж, lower() жижиг үсэг болгоно. Эцэст нь cls(path, **options): Хичээл 7.1-д харсан ** задлалт, үлдсэн бүх тохиргоог түлхүүр үгийн argument болгон class-д дамжуулна.
python
configs = [
{"path": "sales.csv"},
{"path": "sales.json"},
{"path": "branch_export.dat", "format": "csv", "delimiter": ";"},
]
for config in configs:
loader = create_loader(config)
print(type(loader).__name__, getattr(loader, "delimiter", "-"))CsvLoader ,
JsonLoader -
CsvLoader ;Эхний хоёр тохиргоо формат заагаагүй, үйлдвэр өргөтгөлөөс таасан. Гурав дахь нь “.dat” өргөтгөлтэй, танигдахгүй, тиймээс формат тодорхой зааж, мөн delimiter тохиргоо нэмсэн. Үйлдвэр delimiter-ийг CsvLoader-т дамжуулсан. getattr()-ийн гурав дахь argument нь attribute байхгүй үед өгөх анхдагч утга: JsonLoader-т delimiter байхгүй.
Энэ үйлдвэр одоо програмын бүх импортын шийдвэрийг агуулж байна: формат таах, class сонгох, тохиргоо дамжуулах. Хэрэв маргааш форматыг файлын эхний байтаас таах шаардлага гарвал ганц функцийг засна.
Жишээ 3: үйлдвэр ба pipeline
Үйлдвэрийн гаргасан объектууд бүгд ижил гэрээг дагадаг нь Түвшин 1-ийн хэрэгслүүдтэй нэгтгэхэд хамгийн их үр шимээ өгнө. Олон эх сурвалжийн борлуулалтыг нэг урсгал болгож нийт дүнг бодъё:
python
from itertools import chain
sources = [{"path": "sales.csv"}, {"path": "sales.json"}]
all_rows = chain.from_iterable(create_loader(c).load() for c in sources)
total = sum(row["amount"] for row in all_rows)
print(f"Нийт борлуулалт: {total:,} төгрөг")Нийт борлуулалт: 72,300 төгрөг25,000 + 1,500 + 800 + 45,000. Хичээл 2.4-ийн chain.from_iterable нь эх сурвалж бүрийн load() generator-ийг нэг урсгал болгосон, Хичээл 2.2-ын generator expression ба sum() тэдгээрийг нэмсэн. Энэ хоёр мөр CSV ба JSON-ийн ялгааг огт мэдэхгүй. Хэрэв CsvLoader amount-ийг int болгоогүй бол энд “unsupported operand type(s) for +: ‘int’ and ‘str’” гэсэн алдаа гарах байсан: гэрээ нь гаралтын хэлбэрийг ч хамардгийн бодит нотолгоо. Санах ойн хувьд: файл бүр нэг нэгээр нээгдэж, мөр бүр нэг нэгээр дамжиж байна.
Жишээ 4: classmethod үйлдвэр
Хичээл 7.1-ийн classmethod-ууд ба энэ хичээлийн үйлдвэрийг нэг зүйлд нийлүүлж болно. Хэрэв бүх loader-ууд нийтлэг эцэг class-тай бол үйлдвэрийг тэр class-ийн classmethod болгох нь түгээмэл хэлбэр:
python
class Loader:
registry = {}
@classmethod
def register(cls, *formats):
def decorator(subclass):
for fmt in formats:
cls.registry[fmt] = subclass
return subclass
return decorator
@classmethod
def from_path(cls, path, **options):
fmt = Path(path).suffix.lstrip(".").lower()
subclass = cls.registry.get(fmt)
if subclass is None:
raise ValueError(f"'{fmt}' форматын loader алга")
return subclass(path, **options)
@Loader.register("csv")
class CsvLoader(Loader):
...
loader = Loader.from_path("sales.csv")
print(type(loader).__name__)CsvLoaderLoader.from_path(...) нь Хичээл 7.1-ийн туслах хаалга шиг харагдаж байна, гэхдээ одоо тэр Loader-ийн объект биш, удамшсан class-ийн объектыг буцаадаг. Бүртгэл registry class attribute-д хадгалагддаг. Хичээл 7.3-ын анхааруулгыг санаарай: энэ dict бүх удамшсан class-д хуваалцагдана. Энд энэ нь яг хүссэн зүйл, бүх loader нэг бүртгэлд, гэхдээ тестэд өөрчлөлт бусад тестэд нөлөөлж болохыг анхаарах хэрэгтэй. Энэ хэлбэр Python-ий сангуудад түгээмэл. Функц ба модулийн түвшний dict-тэй Жишээ 1-ийн хувилбартай харьцуулахад энэ нь нэг нэрийн дор бүх зүйлийг цуглуулдаг, гэвч нийтлэг эцэг class шаарддаг. Эцэг class аль хэдийн байгаа бол classmethod хэлбэр, байхгүй бол функц ба dict хангалттай.
Factory хэзээ хэрэггүй вэ
Хэрэв таны програмд ганцхан loader байгаа бол үйлдвэр хэрэггүй: CsvLoader(path) гэж шууд бичнэ. Хэрэв хоёр class байгаа ч тэдгээрийг үргэлж кодын тодорхой газарт, тогтмолоор сонгодог бол шууд үүсгэх нь илүү тодорхой. Factory объектын төрлийг ажиллах явцад, тохиргоо, хэрэглэгчийн сонголт, файлын төрлөөс хамааруулан сонгох шаардлага гарахад, эсвэл объект үүсгэх ижил шийдвэр олон газарт давтагдаж эхлэхэд л зардлаа нөхдөг.
Хүсвэл “tsv” буюу табаар тусгаарлагдсан формат нэмж үзээрэй: шинэ class бичих шаардлагатай юу, эсвэл CsvLoader-ийг тусгай тохиргоотойгоор бүртгэж болох уу? Хичээл 3.3-ын partial санаа өгч магадгүй: LOADERS["tsv"] = partial(CsvLoader, delimiter="\t"). partial объект callable тул үйлдвэр түүнийг class шиг дуудна. Сонирхвол create_loader-т тохиргоо шалгалт нэмж, “.json” файлд delimiter өгвөл ойлгомжтой алдаа өгдөг болгож болно. Мөн тестэд хуурамч loader ашиглах аргыг туршиж үзэх: LOADERS["fake"] = FakeLoader нэмж тестлээд, тест дууссаны дараа устгахгүй бол дараагийн тестүүдэд юу болох вэ? Хичээл 6.3-ын temporary_setting context manager энэ асуудлыг шийдэх нэг арга. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
ValueError: ‘xlsx’ форматын loader алга. Боломжтой: csv, json, log, txt. Энэ бол бидний мессеж бөгөөд зөв ажиллаж байна: танигдаагүй форматыг тодорхой мэдээлж, боломжтой сонголтуудыг харуулж байна. Хэрэв формат хүлээгдэж байсан бол loader class нь бүртгэгдээгүй байж магадгүй: decorator мөр байгаа эсэх, тэр class-ийг агуулсан модуль импортлогдсон эсэхийг шалгаарай. Бүртгэл class тодорхойлогдох үед хийгддэг тул импортлогдоогүй модулийн class-ууд бүртгэлд байхгүй.
TypeError: JsonLoader.init() got an unexpected keyword argument ‘delimiter’. “JsonLoader.init()-д таамаглаагүй ‘delimiter’ түлхүүр үгийн argument ирлээ” гэсэн утгатай. Тохиргоонд сонгосон class-ийн дэмждэггүй тохиргоо байхад cls(path, **options) мөрөнд гарна. Үйлдвэр тохиргоог бүхэлд нь дамжуулдаг тул тохиргоо ба class-ийн хооронд тохирол байх ёстой. Шийдэл нь тохиргоог засах, эсвэл үйлдвэрт тохиргоог шалгах алхам нэмж, ойлгомжтой мессеж өгөх.
KeyError: ‘path’. “‘path’ түлхүүр олдсонгүй” гэсэн утгатай. Тохиргоонд path байхгүй үед options.pop("path") мөрөнд гарна. Энэ алдааны мессеж хэрэглэгчид бага зүйл хэлдэг тул үйлдвэр тохиргооны заавал байх ёстой түлхүүрүүдийг эхэлж шалгаж, "тохиргоонд ‘path’ заавал байх ёстой" гэх мэт тодорхой ValueError өгөх нь сайн дадал.
FileNotFoundError: [Errno 2] No such file or directory: ‘sales.json’. "Ийм файл эсвэл хавтас байхгүй" гэсэн утгатай. Анзаарах ёстой нарийн зүйл: энэ алдаа create_loader дуудагдах үед биш, load() generator-ийг анх давтах үед гарна. Trace-ийн Алхам 3-ыг санаарай: __init__ зөвхөн замыг хадгалдаг. Тэгэхээр traceback үйлдвэрийн мөрийг биш, давталтын мөрийг заана, Хичээл 2.4-ийн chain-ийн "оройтож гарах алдаа"-тай ижил. Хэрэв файл байгаа эсэхийг эрт шалгах шаардлагатай бол __init__ дотор self.path.exists()-ийг шалгаж болно.
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0). "Утга хүлээсэн: 1-р мөр, 1-р багана (0-р тэмдэгт)" гэсэн утгатай. JSON loader хоосон эсвэл JSON биш файлыг уншихад гарна, жишээлбэл файлын өргөтгөл “.json” боловч дотор нь CSV эсвэл хоосон. Мессеж асуудал эхэлсэн байрлалыг яг заадаг. Энэ нь өргөтгөлөөс формат таах аргын хязгаарлалт: өргөтгөл худлаа хэлж болно.
TypeError: ‘NoneType’ object is not callable. “NoneType объектыг дуудах боломжгүй” гэсэн утгатай. Бүртгэлийн decorator-ын register функц return cls мөрийг мартсан үед class-ийг нэрээр нь шууд үүсгэх гэж оролдоход гарна: CsvLoader("sales.csv"). Decorator None буцаасан тул CsvLoader нэр None-ыг заана. Сонирхолтой нь үйлдвэрээр дамжуулбал ажиллана, учир нь бүртгэлд class зөв хадгалагдсан. Ийм "заримдаа ажилладаг" алдаа будлиантай тул бүртгэлийн decorator class-ийг заавал буцаах ёстойг санаарай, Хичээл 5.4-ийн register-ийн адил.
Factory pattern объект үүсгэх шийдвэрийг нэг газарт төвлөрүүлдэг: объект хэрэгтэй код ангилал эсвэл тохиргоог хэлдэг, үйлдвэр аль class-ийг яг ямар тохиргоотойгоор үүсгэхийг шийддэг. Хичээл 7.1-ийн classmethod туслах хаалга нэг class-ийн хамгийн энгийн үйлдвэр. Энэ хичээлийн үйлдвэр хэд хэдэн class-ийн аль нэгийг сонгодог. Python хэлбэр нь Strategy-тэй ижил: нэрээр class-уудыг хадгалсан dict, хайлт, дуудлага, учир нь class ч callable. Бүртгэлийн class decorator шинэ class-ийг тодорхойлох мөчид нь бүртгэж, кодыг өргөтгөлд нээлттэй, өөрчлөлтөд хаалттай болгодог. Тохиргоонд суурилсан үйлдвэр формат таах, class сонгох, нэмэлт тохиргоог **-оор дамжуулахыг нэг функцэд нэгтгэдэг. Үйлдвэрийн гаргасан бүх объект ижил гэрээг, method-уудын нэрээс гадна гаралтын хэлбэрийг ч, дагах ёстой, тэгвэл тэдгээр chain, sum зэрэг Түвшин 1-ийн хэрэгслүүдтэй саадгүй нэгдэнэ. Нийтлэг эцэг class байвал үйлдвэрийг түүний classmethod болгож болно. Объектын төрлийг ажиллах явцад сонгох шаардлагагүй бол үйлдвэр хэрэггүй.
Дараагийн хичээл бол Түвшин 3-ын хамтарсан бүтээл бөгөөд гурав дахь pattern-ыг бүтээнэ: Observer. Энэ pattern нэг объектод болсон үйл явдлын тухай олон "сонсогч"-д мэдэгдэх асуудлыг шийддэг: захиалга баталгаажихад имэйл илгээх, агуулахыг шинэчлэх, тайланд бүртгэх, гэхдээ захиалгын код тэдгээрийн аль нэгийг ч мэдэхгүйгээр. Бид жижиг мэдэгдлийн систем бүтээнэ: сонсогчид нь Хичээл 8.3-ын callable-ууд, бүртгэл нь энэ хичээлийн dict-ийн санаа, мөн бүгд pytest тесттэй, Git branch дээр commit хийгдэнэ. Түвшин 3-ын бүх хэрэгсэл тэнд нэг бодит програмд нийлнэ.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.