Це фрагмент книги Python з нуля, яка допоможе вам навчитися програмуванню з нуля. Ви можете знайти його на Allegro, Empik та в інтернет-книгарнях.
У розділі Основні значення ми дізналися про базові оператори, такі як математичні оператори (+, -, * тощо) або оператори порівняння (>, ==, > = тощо) і побачили їх використання для основних значень. А як щодо класів, які ми визначаємо самі? Чи можуть такі класи також використовувати оператори? Відповідь: так! Однак необхідно визначити, як ці оператори повинні поводитися. Для цього Python використовує методи зі спеціальними назвами, наприклад, __eq__ або __gt__. Коли вони визначені, будуть дозволені деякі додаткові операції для даного класу.
У цьому розділі ми дізнаємося про найважливіші з методів зі спеціальними назвами. Деякі з них, наприклад __add__ та __gt__, дозволяють визначити новий оператор для класу. Інші, наприклад __str__ та __eq__, визначають поведінку функцій або структур, які використовує Python. На прикладах все стане зрозумілим. Усі ці спеціальні методи зазвичай використовуються в проєктах і бібліотеках, а також значною мірою визначають зручність використання Python.
__str__
Коли ми передали рядок або число як аргумент у функцію print, ми побачили прекрасний результат. Однак, коли ми створили власний клас, введений текст вже не дуже допоможе.
print(1)# 1print("AAA")# AAAclassUser:def__init__(self, name): self.name = name
user = User("Alojzy")print(user)# <__main__.User object at 0x1109c6b20>
Від чого це залежить? Від методу __str__ [^204_1]. Це спеціальний метод, який Python використовує, коли ми хочемо перетворити об’єкт на рядок. За замовчуванням відображається повна назва об’єкта (включно з його розташуванням) і адреса його пам’яті (це нас не повинно цікавити). Однак ми можемо замінити його таким чином, щоб він повертав більше корисної інформації про об’єкт. Щоб це зробити, нам слід визначити метод __str__ і повернути з нього значення рядка, яке має представляти об’єкт.
classUser:def__init__(self, name): self.name = name
def__str__(self):returnf"User(name={self.name})"user = User("Алоїз")print(user)# User(name=Алоїз)
Як це працює? Функція print використовує конструктор str, який використовує метод __str__ нашого об’єкта, щоб перетворити його на рядок. Отже, цей метод має спеціальне значення. Однак його не слід застосовувати напряму. Замість того, щоб викликати метод __str__, краще використовувати конструктор str або f-рядок.
У Python існує ще багато подібних методів зі спеціальним значенням. Усі вони використовуються різними вбудованими засобами мови.
__repr__
Коли ми відображаємо список, цей метод виводить елементи списку.
l =[1,"A",True]print(l)# [1, 'A', True]
Це тому, що список має метод __str__, а він змінює кожен збережений об’єкт на рядок. Тут, однак, варто зауважити, що спосіб представлення цих елементів дещо інший, ніж при використанні str. Наприклад, для рядка str він повертає виключно його вміст, а коли ми показуємо рядок у списку, він береться в лапки. Це тому, що використовується "офіційне" представлення об’єкта у вигляді рядка, яке ми отримуємо за допомогою функції repr [^204_2] і визначаємо за допомогою методу __repr__.
print(repr("A"))# 'A'print(str("A"))# Aprint("A".__repr__())# 'A'print("A".__str__())# A
Хорошим прикладом різниці між двома методами може бути клас, який представляє ім’я та прізвище. Репрезентацією __str__ можуть бути просто ім’я та прізвище. Репрезентацією __repr__ - повна інформація про назву класу та його значення.
У прикладі вище я розділив рядок на кілька лінійок через обмежену ширину коду в книзі. Операції, розбиті на кілька лінійок, повинні бути або взяті в дужки, або завершуватися символами \. Це потрібно для того, щоб інтерпретатор розглядав наступний рядок як продовження попереднього.
Коли ми визначаємо об’єкти, то найчастіше хочемо, щоб __repr__ працював так само, як __str__, що можна отримати шляхом виразу __repr__ = __str__.
Ще одним важливим методом є __eq__ [^204_3], який визначає, чи два об’єкти дорівнюють одне одному. Отже він використовується при порівнянні двох об’єктів за допомогою == або !=. Якщо ми не визначимо цей метод, два різні об’єкти цього класу ніколи не дорівнюватимуть одне одному. Для класів без визначеного методу __eq__ оператор == повертає True, лише якщо з обох боків є абсолютно однаковий об’єкт (наприклад оператор is). Тому в прикладі нижче user1 == user1 повертається True, але вже при user1 == user2 — False, хоча атрибути обох об’єктів ідентичні.
classUser:def__init__(self, name): self.name = name
user1 = User("Алек")user2 = User("Алек")user3 = User("Болек")print(user1 == user1)# Trueprint(user1 == user2)# Falseprint(user1 == user3)# Falseprint(user1 is user1)# Trueprint(user1 is user2)# Falseprint(user1 is user3)# Falseprint(user1 != user1)# Falseprint(user1 != user2)# Trueprint(user1 != user3)# True
Коли ми визначимо __eq__, він повинен містити параметри self та other, які представляють об’єкти для порівняння. Він також має повертати логічне значення, яке відповідає на запитання, чи об’єкти дорівнюють одне одному (True), чи ні (False). Зазвичай ми починаємо з перевірки, чи other є того ж типу, а потім порівнюємо його властивості. Для перевірки типу ми використовуємо функцію isinstance. Як перший аргумент ми використовуємо об’єкт, тип якого ми хочемо перевірити, а як другий — назву класу.
У реальних проєктах, коли ми визначаємо __eq__, ми повинні також визначити __hash__. Однак пояснення цього методу виходить за рамки цієї книги.
Вправа: __str__, __repr__ i __eq__
Створи клас Money з атрибутами amount і currency. Їхні значення повинні бути вказані в конструкторі. Два об’єкти повинні бути рівними, коли значення обох полів однакові. Перетворення на рядок має повертати суму та валюту, розділені пробілом. Офіційна репрезентація об’єкта має показувати його назву та атрибути amount і currency.
Python підтримує багато операторів, а наші класи можуть скористатися ними, перевизначаючи різні спеціальні методи. Ці можливості використовує багато розробників пакетів. Чудовим прикладом є Pandas - пакет, який широко використовується людьми, які працюють із даними (аналітиками, інженерами даних, статистиками). Ми побачимо, як він працює, в розділі Аналіз даних у четвертій частині. Однак, щоб дати Тобі певне уявлення про наявні можливості, я наведу кілька прикладів надписання різних операторів.
Якщо ми хочемо уможливити математичні операції між об’єктами, нам слід визначити такі функції, як __add__ [^204_4], __sub__ [^204_5] чи __mul__ [^204_6].
Ми також могли би записати їх як __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 містить назву атрибута).
Як бачиш, Python має дуже цікавий функціонал, який дає можливість надавати об’єктам особливої поведінки. Це дозволяє виконувати різноманітні операції над об’єктами, що забезпечує чудові можливості для розробників пакетів. Ми повернемося до цього, коли почнемо використовувати різні пакети.
[^204_1]: "str" — скорочення від англ. "string", "рядок".
[^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", "візьми атрибут".
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.
Roman has experience in different fields: from running his own catering company to working as a professional lawyer for over 13 years. Recently working on becoming a DevOps engineer.