
Це фрагмент книги Python з нуля, яка допоможе вам навчитися програмуванню з нуля. Ви можете знайти його на Allegro, Empik та в інтернет-книгарнях.
+, -, * тощо) або оператори порівняння (>, ==, > = тощо) і побачили їх використання для основних значень. А як щодо класів, які ми визначаємо самі? Чи можуть такі класи також використовувати оператори? Відповідь: так! Однак необхідно визначити, як ці оператори повинні поводитися. Для цього Python використовує методи зі спеціальними назвами, наприклад, __eq__ або __gt__. Коли вони визначені, будуть дозволені деякі додаткові операції для даного класу.__add__ та __gt__, дозволяють визначити новий оператор для класу. Інші, наприклад __str__ та __eq__, визначають поведінку функцій або структур, які використовує Python. На прикладах все стане зрозумілим. Усі ці спеціальні методи зазвичай використовуються в проєктах і бібліотеках, а також значною мірою визначають зручність використання Python.print, ми побачили прекрасний результат. Однак, коли ми створили власний клас, введений текст вже не дуже допоможе.print(1) # 1 print("AAA") # AAA class User: def __init__(self, name): self.name = name user = User("Alojzy") print(user) # <__main__.User object at 0x1109c6b20>
__str__ [^204_1]. Це спеціальний метод, який Python використовує, коли ми хочемо перетворити об’єкт на рядок. За замовчуванням відображається повна назва об’єкта (включно з його розташуванням) і адреса його пам’яті (це нас не повинно цікавити). Однак ми можемо замінити його таким чином, щоб він повертав більше корисної інформації про об’єкт. Щоб це зробити, нам слід визначити метод __str__ і повернути з нього значення рядка, яке має представляти об’єкт.class User: def __init__(self, name): self.name = name def __str__(self): return f"User(name={self.name})" user = User("Алоїз") print(user) # User(name=Алоїз)
print використовує конструктор str, який використовує метод __str__ нашого об’єкта, щоб перетворити його на рядок. Отже, цей метод має спеціальне значення. Однак його не слід застосовувати напряму. Замість того, щоб викликати метод __str__, краще використовувати конструктор str або f-рядок.print("Користувач: " + user.__str__()) # Користувач: User(name=Алоїз) print("Користувач: " + str(user)) # Користувач: User(name=Алоїз) print(f"Користувач: {user}") # Користувач: User(name=Алоїз)
l = [1, "A", True] print(l) # [1, 'A', True]
__str__, а він змінює кожен збережений об’єкт на рядок. Тут, однак, варто зауважити, що спосіб представлення цих елементів дещо інший, ніж при використанні str. Наприклад, для рядка str він повертає виключно його вміст, а коли ми показуємо рядок у списку, він береться в лапки. Це тому, що використовується "офіційне" представлення об’єкта у вигляді рядка, яке ми отримуємо за допомогою функції repr [^204_2] і визначаємо за допомогою методу __repr__.print(repr("A")) # 'A' print(str("A")) # A print("A".__repr__()) # 'A' print("A".__str__()) # A
__str__ можуть бути просто ім’я та прізвище. Репрезентацією __repr__ - повна інформація про назву класу та його значення.class FullName: def __init__(self, name, surname): self.name = name self.surname = surname def __str__(self): return f"{self.name} {self.surname}" def __repr__(self): return "FullName("+\ f"name={repr(self.name)}, "+\ f"surname={repr(self.surname)})" player = FullName("Алоїз", "Москала") print(player) # Алоїз Москала print(str(player)) # Алоїз Москала print(repr(player)) # FullName(name='Алоїз', surname='Москала')
У прикладі вище я розділив рядок на кілька лінійок через обмежену ширину коду в книзі. Операції, розбиті на кілька лінійок, повинні бути або взяті в дужки, або завершуватися символами\. Це потрібно для того, щоб інтерпретатор розглядав наступний рядок як продовження попереднього.
__repr__ працював так само, як __str__, що можна отримати шляхом виразу __repr__ = __str__.class Position: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return f"({self.x}, {self.y})" __repr__ = __str__ # або # def __repr__(self): # return self.__str__() position = Position(10, 20) print(position) # (10, 20) print(repr(position)) # (10, 20)
__eq__ [^204_3], який визначає, чи два об’єкти дорівнюють одне одному. Отже він використовується при порівнянні двох об’єктів за допомогою == або !=. Якщо ми не визначимо цей метод, два різні об’єкти цього класу ніколи не дорівнюватимуть одне одному. Для класів без визначеного методу __eq__ оператор == повертає True, лише якщо з обох боків є абсолютно однаковий об’єкт (наприклад оператор is). Тому в прикладі нижче user1 == user1 повертається True, але вже при user1 == user2 — False, хоча атрибути обох об’єктів ідентичні.class User: def __init__(self, name): self.name = name user1 = User("Алек") user2 = User("Алек") user3 = User("Болек") print(user1 == user1) # True print(user1 == user2) # False print(user1 == user3) # False print(user1 is user1) # True print(user1 is user2) # False print(user1 is user3) # False print(user1 != user1) # False print(user1 != user2) # True print(user1 != user3) # True
__eq__, він повинен містити параметри self та other, які представляють об’єкти для порівняння. Він також має повертати логічне значення, яке відповідає на запитання, чи об’єкти дорівнюють одне одному (True), чи ні (False). Зазвичай ми починаємо з перевірки, чи other є того ж типу, а потім порівнюємо його властивості. Для перевірки типу ми використовуємо функцію isinstance. Як перший аргумент ми використовуємо об’єкт, тип якого ми хочемо перевірити, а як другий — назву класу.class A: pass class B: pass a = A() print(isinstance(a, A)) # True print(isinstance(a, B)) # False b = B() print(isinstance(b, A)) # False print(isinstance(b, B)) # True
__eq__, достатньо перевірити тип параметра other. Потім ми почергово порівнюємо значення важливих для нас атрибутів.class User: def __init__(self, name): self.name = name def __eq__(self, other): return ( isinstance(other, User) and other.name == self.name ) user1 = User("Алек") user2 = User("Алек") user3 = User("Болек") print(user1 == user1) # True print(user1 == user2) # True print(user1 == user3) # False print(user1 != user1) # False print(user1 != user2) # False print(user1 != user3) # True
# Оператор `is` не змінює поведінку print(user1 is user1) # True print(user1 is user2) # False print(user1 is user3) # False
Position разом із реалізованим методом __eq__:class Position: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): return ( isinstance(other, Position) and self.x == other.x and self.y == other.y ) def __str__(self): return f"({self.x}, {self.y})" __repr__ = __str__ position1 = Position(10, 20) position2 = Position(10, 20) position3 = Position(1, 2) print(position1 == position2) # True print(position1 == position3) # False
У реальних проєктах, коли ми визначаємо__eq__, ми повинні також визначити__hash__. Однак пояснення цього методу виходить за рамки цієї книги.
Money з атрибутами amount і currency. Їхні значення повинні бути вказані в конструкторі. Два об’єкти повинні бути рівними, коли значення обох полів однакові. Перетворення на рядок має повертати суму та валюту, розділені пробілом. Офіційна репрезентація об’єкта має показувати його назву та атрибути amount і currency.money1 = Money(10.0, "PLN") money2 = Money(10.0, "PLN") money3 = Money(20.0, "PLN") money4 = Money(10.0, "EUR") print(money1 == money1) # True print(money1 == money2) # True print(money1 == money3) # False print(money1 == money4) # False print(money1) # 10.0 PLN print(money2) # 10.0 PLN print(money3) # 20.0 PLN print(money4) # 10.0 EUR print(repr(money1)) # Money(amount=10.0, currency='PLN') print(repr(money3)) # Money(amount=20.0, currency='PLN') print(repr(money4)) # Money(amount=10.0, currency='EUR')
False.class FakeMoney: def __init__(self, amount, currency): self.amount = amount self.currency = currency money = Money(10, "PLN") fakeMoney = FakeMoney(10, "PLN") print(money == fakeMoney) # False
__add__ [^204_4], __sub__ [^204_5] чи __mul__ [^204_6].class Position: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return f"({self.x}, {self.y})" def __add__(self, other): return Position(self.x + other.y, self.y + other.y) def __sub__(self, other): return Position(self.x - other.y, self.y - other.y) def __mul__(self, other): return Position(self.x * other, self.y * other) p1 = Position(1.0, 2.0) p2 = Position(3.0, 4.0) p3 = p1 + p2 print(p3) # (5.0, 6.0) p4 = p1 * 3 print(p4) # (3.0, 6.0)
>, <, > = і <=, ми повинні визначити методи __lt__ [^204_7] чи __le__ [^204_8].class Position: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return f"({self.x}, {self.y})" def __lt__(self, other): self_mag = (self.x ** 2) + (self.y ** 2) other_mag = (other.x ** 2) + (other.y ** 2) return self_mag < other_mag def __le__(self, other): self_mag = (self.x ** 2) + (self.y ** 2) other_mag = (other.x ** 2) + (other.y ** 2) return self_mag <= other_mag p1 = Position(1.0, 2.0) p2 = Position(3.0, 4.0) print(p1 > p2) # False print(p1 < p2) # True print(p1 < p1) # False print(p1 >= p2) # False print(p1 <= p2) # True print(p1 <= p1) # True
__gt__ [^204_9] і __ge__ [^204_10]. Метод __gt__ має повернути значення, протилежне до__le__ (оскільки a > b має повертати те саме значення, що й not (a <= b)), а метод __ge__ — значення, протилежне до __lt__ (оскільки a > = b має повернути те ж значення, що й not (a < b)).__getattr__ [^204_11] дозволяє нам вирішити, що має статися, коли ми спробуємо прочитати атрибут, якого не існує. У наступному прикладі клас Echo не містить жодних атрибутів, але коли ми запитуємо Ааа, Ооо і Хей, він відповідає текстом "Echo: " та назвою атрибута (параметр item містить назву атрибута).class Echo: def __getattr__(self, item): return f"Echo: {item}" echo = Echo() print(echo.Aaa) # Echo: Ааа print(echo.Ooo) # Echo: Ооо print(echo.Hej) # Echo: Хей
[^204_2]: "repr" — скорочення від англ. "representation", репрезентація, представлення, тобто те, як має бути представлений об’єкт.
[^204_3]: "eq" — скорочення від англ. "equals", "дорівнює".
[^204_4]: "додати" — скорочення від англ. "addition", "додавання".
[^204_5]: "sub" — скорочення від англ. "subtraction", "віднімання".
[^204_6]: "mul" — скорочення від англ. "multiplication", "множення".
[^204_7]: "lt" — скорочення від англ. "less than", "менше ніж".
[^204_8]: "le" — скорочення від англ. "less or equal", "менше або дорівнює".
[^204_9]: "gt" — скорочення від англ. "greater than", "більше ніж".
[^204_10]: "ge" — скорочення від англ. "greater or equal", "більшe або дорівнює".
[^204_11]: "getattr" — скорочення від англ. "get attribute", "візьми атрибут".
