16-bo‘lim
OOP - meros, inkapsulyatsiya, polimorfizm
Obyektga yo'naltirilgan dasturlashning uchta ustuni: meros olish, ma'lumotni yashirish, bir xil interfeys va abstrakt klasslar.
Ushbu bo‘lim mundarijasi
O'tgan bo'limda klass yaratishni o'rgandik. Endi OOP ning haqiqiy kuchini beradigan uchta tamoyilni ko'ramiz.
Meros (inheritance) #
Meros - mavjud klass asosida yangisini yaratish. Takrorlanuvchi kodni yozmaslikning eng samarali usuli.
class Xodim:
"""Barcha xodimlar uchun asosiy klass."""
def __init__(self, ism, oylik):
self.ism = ism
self.oylik = oylik
def malumot(self):
return f"{self.ism} - {self.oylik:,} so'm"
def oylik_hisobla(self):
return self.oylik
class Dasturchi(Xodim):
"""Xodim klassidan meros olgan dasturchi."""
def __init__(self, ism, oylik, til, tajriba_yil):
super().__init__(ism, oylik) # ota klass konstruktorini chaqiramiz
self.til = til
self.tajriba_yil = tajriba_yil
def oylik_hisobla(self):
"""Har bir tajriba yili uchun 5% ustama."""
ustama = self.oylik * 0.05 * self.tajriba_yil
return self.oylik + ustama
def kod_yozish(self):
return f"{self.ism} {self.til} tilida kod yozmoqda..."
dasturchi = Dasturchi("Husanboy", 12_000_000, "Python", 3)
print(dasturchi.malumot()) # ota klassdan meros
print(dasturchi.kod_yozish()) # o'ziga xos metod
print(f"{dasturchi.oylik_hisobla():,.0f} so'm") # qayta yozilgan metod
Husanboy - 12,000,000 so'm
Husanboy Python tilida kod yozmoqda...
13,800,000 so'm
super() nima qiladi? #
super() ota klassga murojaat qiladi. Uni chaqirish odatda majburiy:
class Menejer(Xodim):
def __init__(self, ism, oylik, jamoa_soni):
super().__init__(ism, oylik) # ism va oylik ota klassda o'rnatiladi
self.jamoa_soni = jamoa_soni # bu esa faqat menejerda bor
def oylik_hisobla(self):
"""Jamoadagi har bir odam uchun 500 000 bonus."""
bonus = self.jamoa_soni * 500_000
return super().oylik_hisobla() + bonus # ota metodni ham ishlatamiz
menejer = Menejer("Sardor", 15_000_000, 8)
print(f"{menejer.oylik_hisobla():,} so'm")
19,000,000 so'm
Polimorfizm #
Polimorfizm - "ko'p shakllilik". Bir xil metod nomi turli klasslarda turlicha ishlaydi.
class Amaliyotchi(Xodim):
def __init__(self, ism, oylik, murabbiy):
super().__init__(ism, oylik)
self.murabbiy = murabbiy
def oylik_hisobla(self):
return self.oylik * 0.5 # amaliyotchi yarim stavka oladi
jamoa = [
Dasturchi("Husanboy", 12_000_000, "Python", 3),
Menejer("Sardor", 15_000_000, 8),
Amaliyotchi("Malika", 5_000_000, "Husanboy"),
Xodim("Bekzod", 8_000_000),
]
print("=" * 50)
print(f"{'OYLIK VEDOMOSTI':^50}")
print("=" * 50)
print(f"{'Xodim':<16}{'Lavozim':<16}{'Oylik':>18}")
print("-" * 50)
jami = 0
for xodim in jamoa:
# Bir xil metod - har bir klass o'zicha bajaradi
summa = xodim.oylik_hisobla()
jami += summa
lavozim = type(xodim).__name__
print(f"{xodim.ism:<16}{lavozim:<16}{summa:>18,.0f}")
print("-" * 50)
print(f"{'JAMI:':<32}{jami:>18,.0f}")
print("=" * 50)
==================================================
OYLIK VEDOMOSTI
==================================================
Xodim Lavozim Oylik
--------------------------------------------------
Husanboy Dasturchi 13,800,000
Sardor Menejer 19,000,000
Malika Amaliyotchi 2,500,000
Bekzod Xodim 8,000,000
--------------------------------------------------
JAMI: 43,300,000
==================================================
Siklda if isinstance(xodim, Dasturchi): ... kabi tekshiruvlar yo'q. Yangi xodim turi qo'shsangiz, sikl kodini umuman o'zgartirmaysiz. Bu OOP ning eng katta amaliy foydasi.
Inkapsulyatsiya #
Inkapsulyatsiya - ichki tafsilotlarni yashirish. Python'da bu kelishuv orqali amalga oshiriladi:
| Yozuv | Ma'nosi | Tashqaridan kirish |
|---|---|---|
ism | Ochiq (public) | Mumkin |
_ism | Himoyalangan (protected) | Texnik jihatdan mumkin, lekin tegmang |
__ism | Yopiq (private) | Nomi o'zgartiriladi, kirish qiyin |
class Hisob:
def __init__(self, egasi, balans):
self.egasi = egasi # ochiq
self._bank_kodi = "SFRM" # ichki foydalanish uchun
self.__pin = "1234" # yopiq
def pin_tekshir(self, kiritilgan):
return self.__pin == kiritilgan
def pin_ozgartir(self, eski, yangi):
if not self.pin_tekshir(eski):
raise ValueError("Eski PIN noto'g'ri")
if len(yangi) != 4 or not yangi.isdigit():
raise ValueError("PIN 4 ta raqamdan iborat bo'lishi kerak")
self.__pin = yangi
return "PIN o'zgartirildi"
hisob = Hisob("Husanboy", 500_000)
print(hisob.egasi) # ochiq - muammosiz
print(hisob._bank_kodi) # ishlaydi, lekin yaxshi amaliyot emas
try:
print(hisob.__pin) # yopiq - xato
except AttributeError as xato:
print(f"Xato: {xato}")
print(hisob.pin_tekshir("1234"))
print(hisob.pin_ozgartir("1234", "9876"))
Husanboy
SFRM
Xato: 'Hisob' object has no attribute '__pin'
True
PIN o'zgartirildi
__pin aslida _Hisob__pin nomiga o'zgartiriladi (name mangling) va uni hisob._Hisob__pin orqali o'qish mumkin. Python falsafasi: "biz hammamiz kattalarmiz" - kodda niyat ko'rsatiladi, majburlash emas. Pastki chiziq - "bunga tegmang" degan belgi.
Abstrakt klasslar #
Ba'zan asosiy klassdan obyekt yaratish mantiqsiz. "Xodim" - mavhum tushuncha, aniq lavozim bo'lishi kerak.
from abc import ABC, abstractmethod
class Shakl(ABC):
"""Barcha geometrik shakllar uchun abstrakt asos."""
def __init__(self, nomi):
self.nomi = nomi
@abstractmethod
def yuza(self):
"""Har bir shakl buni o'zi amalga oshirishi SHART."""
pass
@abstractmethod
def perimetr(self):
pass
def hisobot(self):
"""Bu metod barcha shakllar uchun umumiy."""
return (f"{self.nomi:<14} yuza: {self.yuza():>9.2f} "
f"perimetr: {self.perimetr():>9.2f}")
class Togriturtburchak(Shakl):
def __init__(self, eni, boyi):
super().__init__("To'rtburchak")
self.eni = eni
self.boyi = boyi
def yuza(self):
return self.eni * self.boyi
def perimetr(self):
return 2 * (self.eni + self.boyi)
class Aylana(Shakl):
PI = 3.14159
def __init__(self, radius):
super().__init__("Aylana")
self.radius = radius
def yuza(self):
return self.PI * self.radius ** 2
def perimetr(self):
return 2 * self.PI * self.radius
class Uchburchak(Shakl):
def __init__(self, a, b, c):
super().__init__("Uchburchak")
self.a, self.b, self.c = a, b, c
def yuza(self):
yarim = self.perimetr() / 2
return (yarim * (yarim - self.a) * (yarim - self.b) * (yarim - self.c)) ** 0.5
def perimetr(self):
return self.a + self.b + self.c
shakllar = [
Togriturtburchak(10, 5),
Aylana(7),
Uchburchak(3, 4, 5),
]
for shakl in shakllar:
print(shakl.hisobot())
print()
print(f"Jami yuza: {sum(s.yuza() for s in shakllar):.2f}")
# Abstrakt klassdan obyekt yaratib bo'lmaydi
try:
Shakl("Noaniq")
except TypeError as xato:
print(f"\nXato: {xato}")
To'rtburchak yuza: 50.00 perimetr: 30.00
Aylana yuza: 153.94 perimetr: 43.98
Uchburchak yuza: 6.00 perimetr: 12.00
Jami yuza: 209.94
Xato: Can't instantiate abstract class Shakl without an implementation for abstract methods 'perimetr', 'yuza'
Ko'p martalik meros #
Python bir nechta klassdan meros olishga ruxsat beradi:
class Yozuvchi:
def yozish(self):
return "Faylga yozilmoqda..."
class Oquvchi:
def oqish(self):
return "Fayldan o'qilmoqda..."
class FaylBoshqaruvchi(Yozuvchi, Oquvchi):
"""Ikkala imkoniyatni ham oladi."""
def __init__(self, nomi):
self.nomi = nomi
boshqaruvchi = FaylBoshqaruvchi("hisobot.txt")
print(boshqaruvchi.yozish())
print(boshqaruvchi.oqish())
print(FaylBoshqaruvchi.__mro__)
Faylga yozilmoqda...
Fayldan o'qilmoqda...
(<class '__main__.FaylBoshqaruvchi'>, <class '__main__.Yozuvchi'>, <class '__main__.Oquvchi'>, <class 'object'>)
Ikkala ota klassda bir xil nomli metod bo'lsa, Python MRO (Method Resolution Order) qoidasiga ko'ra chapdagisini tanlaydi. Bu chalkashlikka olib keladi. Amalda ko'pincha kompozitsiya (obyektni atribut sifatida saqlash) yaxshiroq yechim bo'ladi.
isinstance va issubclass #
dasturchi = Dasturchi("Husanboy", 12_000_000, "Python", 3)
print(isinstance(dasturchi, Dasturchi)) # aynan shu klassmi
print(isinstance(dasturchi, Xodim)) # ota klass ham hisoblanadi
print(isinstance(dasturchi, Menejer))
print(issubclass(Dasturchi, Xodim))
True
True
False
True
Kompozitsiya - merosga muqobil #
class Dvigatel:
def __init__(self, quvvat):
self.quvvat = quvvat
def ishga_tushir(self):
return f"Dvigatel ishga tushdi ({self.quvvat} ot kuchi)"
class Mashina:
"""Mashina dvigateldan meros olmaydi - u dvigatelga EGA."""
def __init__(self, model, dvigatel_quvvati):
self.model = model
self.dvigatel = Dvigatel(dvigatel_quvvati) # kompozitsiya
def haydash(self):
return f"{self.model}: {self.dvigatel.ishga_tushir()}"
mashina = Mashina("Cobalt", 106)
print(mashina.haydash())
Cobalt: Dvigatel ishga tushdi (106 ot kuchi)
- Dasturchi bu xodim (
is-a) → meros. - Mashinada bor dvigatel (
has-a) → kompozitsiya.
Shubhalansangiz, kompozitsiyani tanlang - u moslashuvchanroq.
Transport abstrakt klassini yarating:
- Abstrakt metodlar:
tezlik_hisobla()vayoqilgi_sarfi(). - Umumiy metod:
malumot(). - Undan
Avtomobil,VelosipedvaSamolyotklasslarini meros oling. - Har biri abstrakt metodlarni o'zicha bajarsin.
- Uchala obyektni ro'yxatga solib, siklda
malumot()ni chaqiring - polimorfizmni ko'ring.
Xulosa #
- Meros kodni takrorlamaslik imkonini beradi;
super()ota klassga murojaat qiladi. - Polimorfizm - bir xil metod turli klasslarda turlicha ishlaydi,
iftekshiruvlari kerak emas. - Inkapsulyatsiya Python'da kelishuv orqali:
_himoyalangan,__yopiq. - Abstrakt klass (
ABC) avlodlarni ma'lum metodlarni yozishga majbur qiladi. - "is-a" bo'lsa meros, "has-a" bo'lsa kompozitsiya tanlang.
Keyingi bo'limda Python'ning ilg'or imkoniyatlarini - generatorlar va dekoratorlarni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.