__repr__,__str__,__eq__,__lt__,__hash__və operator metodlarını düzgün yazmaq@propertyilə yoxlama əlavə etmək, sinif və statik metodları yerində işlətmək- Çoxlu varislikdə MRO-nu və
super()-in işini izah etmək abc.ABCilə abstrakt baza sinfi yaratmaq
Nə üçün + ədədləri toplayır, sətirləri birləşdirir, siyahıları isə calayır? Nə üçün len(), in, sorted() və print() tamamilə fərqli tipli obyektlərlə işləyir? Çünki Python hər belə əməliyyatı xüsusi metodun çağırışına çevirir: a + b yazılışı a.__add__(b), len(x) isə x.__len__() olur. Bu metodlara dunder metodlar da deyilir (ingiliscə double underscore — ikiqat alt xətt). Onları öz sinfində yazsan, obyektləri daxili tiplər kimi davranacaq. Bu qaydalar toplusu Python verilənlər modeli adlanır.
__repr__ və __str__: obyekt özünü necə təqdim edir
Hər sinif <__main__.Money object at 0x7f…> kimi bir şey çap edən __repr__ metodunu irsən alır — sazlama üçün faydasızdır. __repr__ birmənalı olmalı və proqramçılar üçün yazılmalıdır; ideal halda obyekti yaradan koda oxşayır. __str__ isə istifadəçilər üçün oxunaqlı variantdır. print() və f-sətirlər __str__-dən, interaktiv konsol, siyahılar və !r çevirməsi isə __repr__-dən istifadə edir. Yalnız __repr__ yazılıbsa, str() da onu işlədir.
class Money:
def __init__(self, amount, currency='AZN'):
self.amount = amount
self.currency = currency
def __repr__(self):
return f'Money({self.amount!r}, {self.currency!r})'
def __str__(self):
return f'{self.amount:.2f} {self.currency}'
price = Money(12.5)
print(price)
print(repr(price))
print([price, Money(3, 'USD')])
print(f'{price} | {price!r}')▸ Gözlənilən nəticə
12.50 AZN Money(12.5, 'AZN') [Money(12.5, 'AZN'), Money(3, 'USD')] 12.50 AZN | Money(12.5, 'AZN')
Bərabərlik, sıralama və heş
__eq__ olmasa, == obyektlərin eyniliyini yoxlayır: eyni verilənli iki obyekt bərabər sayılmır. Digər operandın tipi dəstəklənmirsə, __eq__ xəta qaldırmamalı, xüsusi **NotImplemented qiymətini qaytarmalıdır: onda Python əks əməliyyatı digər obyektdə sınayır, o da alınmasa, eyniliyi müqayisə edir. functools.total_ordering** dekoratoru __eq__ və __lt__ əsasında <=, > və >= operatorlarını özü yaradır:
from functools import total_ordering
@total_ordering
class Money:
def __init__(self, amount, currency='AZN'):
self.amount, self.currency = amount, currency
def __eq__(self, other):
if not isinstance(other, Money):
return NotImplemented
return (self.amount, self.currency) == (other.amount, other.currency)
def __lt__(self, other):
if not isinstance(other, Money) or other.currency != self.currency:
return NotImplemented
return self.amount < other.amount
def __hash__(self):
return hash((self.amount, self.currency))
prices = [Money(5), Money(2.5), Money(10)]
print([m.amount for m in sorted(prices)])
print(Money(5) == Money(5), Money(5) >= Money(2.5), Money(5) == 5)
print(len({Money(1), Money(1)}))▸ Gözlənilən nəticə
[2.5, 5, 10] True True False 1
Operatorların yüklənməsi və konteyner protokolu
Hesab əməlləri də eyni cür işləyir. a + b üçün Python əvvəlcə a.__add__(b) çağırır; o, NotImplemented qaytarsa, b.__radd__(a) — əks metod sınanır. Aşağıda 2 * v yazılışına məhz buna görə __rmul__ lazımdır: int vektorlar haqqında heç nə bilmir. __bool__ if v: yazılışının mənasını, __abs__ isə daxili abs() funksiyasının davranışını müəyyən edir:
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f'Vector({self.x}, {self.y})'
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, k):
return Vector(self.x * k, self.y * k)
__rmul__ = __mul__
def __abs__(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __bool__(self):
return bool(self.x or self.y)
v = Vector(3, 4)
print(v + Vector(1, 1), v * 2, 2 * v)
print(abs(v), bool(Vector(0, 0)))▸ Gözlənilən nəticə
Vector(4, 5) Vector(6, 8) Vector(6, 8) 5.0 False
| Yazılış | Çağırılan metod |
|---|---|
a + b, a - b, a * b | __add__, __sub__, __mul__ (__radd__, …) |
a == b, a < b | __eq__, __lt__ |
len(a), a[i], x in a | __len__, __getitem__, __contains__ |
for x in a | __iter__ (__getitem__) |
abs(a), bool(a), hash(a) | __abs__, __bool__, __hash__ |
str(a), repr(a), a() | __str__, __repr__, __call__ |
a += b üçün Python əvvəlcə __iadd__-i (obyekti yerindəcə dəyişə bilən variantı) axtarır, tapmasa, __add__-ə keçir. Buna görə += siyahını yerində dəyişir, kortej və sətir üçün isə yeni obyekt yaradır. __call__ metodu obyekti funksiya kimi çağırmağa imkan verir: obj().
Sinfin kolleksiya kimi də davrana bilər. __len__ len()-ə, __getitem__ indekslərə və kəsiklərə (kəsik obyekti birbaşa siyahıya ötürülür), __contains__ isə in operatoruna xidmət edir. Hətta for da işləyir: sinifdə __iter__ yoxdursa, Python IndexError görünənə qədər __getitem__-i 0, 1, 2… ilə çağırır:
class Playlist:
def __init__(self, *songs):
self._songs = list(songs)
def __len__(self):
return len(self._songs)
def __getitem__(self, index):
return self._songs[index]
def __contains__(self, song):
return song.lower() in (s.lower() for s in self._songs)
p = Playlist('Morning', 'Rain', 'Finale')
print(len(p), p[0], p[-1], p[1:])
print('rain' in p)
for song in p:
print('>', song)▸ Gözlənilən nəticə
3 Morning Finale ['Rain', 'Finale'] True > Morning > Rain > Finale
@property, @classmethod və @staticmethod
Xassə (property) çöldən adi atribut kimi görünür, amma onu oxuyanda və ya ona qiymət verəndə metod işləyir. Bu, sinifdən istifadə edən kodu dəyişmədən sonradan yoxlama əlavə etməyə imkan verir — t.celsius = -300 yenə də adi mənimsətmədir. Diqqət et ki, __init__ də setter-dən keçir, ona görə yeni obyekt belə səhv vəziyyətdə yarana bilməz. fahrenheit kimi setter-i olmayan xassə yalnız oxunur və hər dəfə yenidən hesablanır:
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError('below absolute zero')
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9 / 5 + 32
t = Temperature(25)
print(t.celsius, t.fahrenheit)
try:
t.celsius = -300
except ValueError as e:
print('error:', e)▸ Gözlənilən nəticə
25 77.0 error: below absolute zero
Sinif metodu (@classmethod) nüsxə yox, sinfin özünü cls kimi alır. Onun əsas işi from_csv kimi alternativ konstruktorlardır: metod cls(...) çağırdığı üçün törəmə sinif avtomatik olaraq öz tipində obyekt alır. Statik metod (@staticmethod) nə self, nə də cls alır — məntiqcə sinfə aid olduğu üçün onun ad sahəsində yaşayan adi funksiyadır. Bütün nüsxələrin paylaşdığı count sinif atributuna da diqqət et:
class Student:
count = 0
def __init__(self, name, grade):
self.name, self.grade = name, grade
Student.count += 1
@classmethod
def from_csv(cls, line):
name, grade = line.split(',')
return cls(name, int(grade))
@staticmethod
def is_valid_grade(grade):
return 1 <= grade <= 11
def __repr__(self):
return f'{type(self).__name__}({self.name!r}, {self.grade})'
class Graduate(Student):
pass
print(Student.from_csv('Aysel,9'), Graduate.from_csv('Murad,11'))
print(Student.count, Student.is_valid_grade(12))▸ Gözlənilən nəticə
Student('Aysel', 9) Graduate('Murad', 11)
2 FalseVarislik, MRO və super()
Çoxlu varislikdə Python-a metodları axtarmaq üçün qayda lazımdır. O, MRO-nu (method resolution order — metodların axtarış sırası) C3 alqoritmi ilə hesablayır: törəmə sinif valideynlərindən əvvəl gəlir, valideynlər yazıldıqları sıranı saxlayır, hər sinif isə yalnız bir dəfə görünür. Onu __mro__-da görmək olar. Əsas məqam: **super() «mənim valideynim» yox, «real obyektin MRO-sunda növbəti sinif» deməkdir**:
class Base:
def save(self):
print('Base.save')
class Logged(Base):
def save(self):
print('Logged: before')
super().save()
class Validated(Base):
def save(self):
print('Validated: checking')
super().save()
class Order(Logged, Validated):
pass
print([c.__name__ for c in Order.__mro__])
Order().save()▸ Gözlənilən nəticə
['Order', 'Logged', 'Validated', 'Base', 'object'] Logged: before Validated: checking Base.save
Logged birbaşa Base-dən törəyib, amma onun super().save() çağırışı Validated.save()-i işə saldı, çünki Order-in MRO-sunda növbəti sinif odur. Bunun sayəsində Base.save() düz bir dəfə icra olundu. Miksin (mixin) adlanan belə «əməkdaşlıq edən» siniflər yalnız hər metod super() çağıranda və heç biri valideyni Base.save(self) kimi birbaşa çağırmayanda işləyir.
Abstrakt baza sinfi törəmə siniflərin yerinə yetirməli olduğu müqaviləni təyin edir. abc.ABC-dən törəyən və @abstractmethod ilə işarələnmiş metodları olan sinfin nüsxəsini, törəmə sinif onların hamısını reallaşdırmayınca, yaratmaq olmur — xəta işin ortasında yox, obyekt yaradılanda üzə çıxır:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self): ...
def describe(self):
return f'{type(self).__name__} with area {self.area():.2f}'
class Rect(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
print(Rect(3, 4.5).describe())
try:
Shape()
except TypeError as e:
print(e)▸ Gözlənilən nəticə
Rect with area 13.50 Can't instantiate abstract class Shape without an implementation for abstract method 'area'
Money sinfinə __add__, __radd__ və __eq__ əlavə et ki, iki məbləği toplamaq, siyahını sum() ilə cəmləmək və nəticəni müqayisə etmək mümkün olsun. Yad tiplər üçün __add__ NotImplemented qaytarmalıdır.
class Money:
def __init__(self, amount):
self.amount = amount
def __repr__(self):
return f'Money({self.amount})'
# add __add__, __radd__ and __eq__ here
wallet = [Money(5), Money(10), Money(2)]
print(Money(1) + Money(2))
print(sum(wallet))
print(sum(wallet) == Money(17))▸ Gözlənilən nəticə
Money(3) Money(17) True
Stack sinfinə __len__ və __repr__ əlavə et. bool(s) üçün ayrıca metod yazma — boş stek özü False sayılmalıdır.
class Stack:
def __init__(self):
self._items = []
def push(self, item):
self._items.append(item)
def pop(self):
return self._items.pop()
# add __len__ and __repr__ here
s = Stack()
print(bool(s))
s.push('a')
s.push('b')
print(s, len(s), bool(s))
print(s.pop(), s)▸ Gözlənilən nəticə
False Stack(['a', 'b']) 2 True b Stack(['a'])
Əsas fikirlər
- Operatorlar və daxili funksiyalar xüsusi metodları çağırır:
a + b→__add__,len(x)→__len__,x in c→__contains__. __repr__proqramçılar,__str__istifadəçilər üçündür; konteynerlər__repr__-i göstərir.__eq__yad tiplər üçünNotImplementedqaytarmalıdır;__eq__təyin etmək, əlavə etməsən,__hash__-ı silir.@propertyatribut yazılışının arxasında yoxlama gizlədir;@classmethodclsalır (alternativ konstruktorlar),@staticmethodheç birini almır.super()real obyektin MRO-su ilə hərəkət edir;ABC-nin abstrakt metodları nüsxə yaradılmazdan əvvəl reallaşdırılmalıdır.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.
print([obj]) obyekti siyahının içində göstərəndə hansı metoddan istifadə edir?