Це фрагмент книги Python з нуля, яка допоможе вам навчитися програмуванню з нуля. Ви можете знайти його на Allegro, Empik та в інтернет-книгарнях.
Оскільки ми переходимо до складніших тем, почнімо з ключових понять у мові Python: класи та об’єкти.
Якщо Ти на мить виглянеш за сторінки цієї книги, Ти, ймовірно, побачиш багато об’єктів. Я бачу ноутбук, чашку з кавою, нотатки, планшет для малювання... Наш світ наповнений об’єктами. Так само і в програмуванні — ми оперуємо об’єктами. Деякі з них дуже прості, наприклад рядки або логічні значення. Однак ми можемо визначати власні об’єкти. Це робиться за допомогою класів.
Визначення класів — дуже важливе, оскільки дозволяє нам думати про певні абстракції. В інтернет-магазині — це користувачі, продавці, товари тощо. У грі — гравці, противники та предмети. У медичному застосунку — пацієнти, лікарі, рецепти, направлення. Саме завдяки класам ми можемо виражати такі поняття та оперувати ними. Вони також визначають, як об’єкти всередині класів виглядатимуть і поводитимуться.
Клас — це свого роду шаблон для створення об’єктів. За аналогією, клас — це рецепт, а предмет — приготована страва. Клас визначає, що об’єкт повинен містити і як поводитися.
Почнемо з найпростішого варіанту, яким є порожній клас. Такий клас нічого не міститиме, але матиме назву, завдяки якій ми зможемо створювати об’єкти. Ми створюємо клас словом class, після чого вказуємо назву класу, ставимо двокрапку та визначаємо тіло цього класу. Поки що ми визначаємо порожній клас, тому в його тілі ми розмістимо лише pass.
Щоб створити за допомогою класу об’єкт, ми використовуємо назву класу та дужки. Це схоже на виклик функції, яка повертає окремий об’єкт. У прикладі нижче на нього буде вказувати змінна cookie.
classCookie:passcookie = Cookie()
Клас нагадує рецепт страви, а об’єкт — це конкретна порція їжі, приготована за цим рецептом.
На цей момент наш клас порожній, тому об’єкт не дуже цікавий. Так буває далеко не завжди. Об’єкти можуть мати приписані їм змінні та функції. Вони називаються атрибутами, а функції в класі називаються методами. Незабаром ми про них поговоримо, але поки що почнемо з називання класів.
Називання класів
Називаючи класи, ми можемо використовувати ті ж символи, що і для змінних та функцій: малі й великі літери та символ підкреслення _. Однак конвенція іменування дещо інша. Для функцій та змінних ми використовували snake_case. У випадку класів ми використовуємо PascalCase (або UpperCamelCase), тобто ми починаємо кожне слово з великої літери, не використовуючи пробіли та підкреслення.
Ось кілька прикладів класів із відповідними іменами:
User;
Invoice;
OrderReceipt;
NeuralNetwork.
Вправа: Називання класів
Чи правильно названі наступні класи?
Personal_Invoice;
UserAddress;
Carengine;
doctor;
Dog.
Відповіді в кінці книги.
Об’єктні змінні
Ми можемо призначити об’єкту змінну з певним значенням. Це значення стосуватиметься лише цього одного об’єкта. Щоб посилатися на змінну в об’єкті, ми повинні вказати і об’єкт, і змінну, розділивши їхні назви крапкою. Наприклад, для посилання на змінну type в об’єкті cookie1, ми використаємо cookie1.type. Це застосовується і для присвоєння значення, і для його отримання.
Об’єктні змінні рідко створюються поза класом, як у наведеному вище прикладі. Це навіть вважається поганою практикою. Вони частіше створюються у рамках методів, зокрема методу ініціалізатора. Ми дійдемо до цього крок за кроком.
Вправа: Класи і об’єктні змінні
Визнач клас Player, який представлятиме гравців гри. Надай йому змінну points зі значенням 0. Виведи кількість балів (має складати 0), а потім додай один бал до атрибута points та виведи кількість балів ще раз (тепер вона має становити 1).
Відповіді в кінці книги.
Методи
Усередині класів ми можемо визначати функції. Такі функції називаються методи. Ми визначаємо їх у тілі класу, а їхній перший параметр — це посилання на об’єкт, для якого ми будемо викликати цей метод. Цей параметр повинен називатися self. Коли ми викликаємо метод, ми починаємо з об’єкта, потім ставимо крапку, назву методу та дужки з аргументами.
classUser:defcheer(self):print(f"Привіт, мене звати {self.name}")defsay_hello(self, other): name=self.name;print(f"Привіт, {other}, мене звати {name}")user = User()user.name ="Мацєк"user.cheer()# Привіт, мене звати Мацєкuser.say_hello("Марта")# Привіт, Марта, мене звати Мацєк
Об’єкт self можна використати також для зміни атрибутів даного об’єкта.
У наведених вище прикладах ми вказували атрибути об’єкта відразу після його створення. Такий підхід дуже небезпечний, адже користувач, наприклад, може забути визначити один із обов’язкових атрибутів. Натомість буде набагато краще, якщо ми встановимо значення цих атрибутів за допомогою конструктора.
Конструктор та ініціалізатор
При створенні нового об’єкта ми ставимо дужки після назви класу. Ця дужка — це виклик функції, яка формує об’єкт і називається конструктором. Ця функція виконує ряд кроків, необхідних для створення об’єкта, включаючи виклик спеціального методу __init__ [^201_1] з нашого класу. Цей метод називається ініціалізатором. У його тілі ми визначаємо, що має статися під час створення об’єкта. Найчастіше тут ми вказуємо атрибути об’єкта.
Кількість параметрів функції __init__ визначає, скільки аргументів має входити до виклику конструктора (тобто в дужках, які ми ставимо після назви класу, коли створюємо об’єкт). Отже, якщо у функції __init__ ми додамо параметр name, тоді під час створення об’єкта ми більше не зможемо залишати порожні дужки. Ми повинні вказати там аргумент, який буде назвою. Характерна риса функції __init__ — це те, що вона очікує певних параметрів, а потім призначає їх об’єкту як атрибути з тими ж назвами.
classUser:def__init__(self, name): self.name = name
user1 = User("Мацєк")user2 = User("Марта")print(user1.name)# Мацєкprint(user2.name)# Марта
Ініціалізатор може містити багато параметрів. Ми можемо використовувати їх як завгодно для визначення атрибутів, хоча найчастіше значення параметрів встановлюються безпосередньо для однойменних атрибутів, як у випадку з name і surname у прикладі нижче. Значення атрибута full_name розраховується на основі name і surname. Значення points визначається як 0.
classPlayer:def__init__(self, name, surname): self.name = name
self.surname = surname
self.full_name =f"{name}{surname}" self.points =0player = Player("Міхал","Мазур")print(player.name)# Міхалprint(player.surname)# Мазурprint(player.full_name)# Міхал Мазурprint(player.points)# 0
Вправа: Банківський рахунок
Створи клас, який представляє банківський рахунок. Яка назва класу буде вдалою? Цей клас повинен мати атрибут balance, який вказує суму коштів на цьому рахунку. Він також повинен містити методи:
deposit, який додає вказану суму грошей до balance,
withdraw, який за достатньої кількості коштів віднімає суму від balance, і повертає True, а в інакшому випадку — False.
Крім того, створи два об’єкти, які представляють банківські рахунки, й перевір, чи зміниться баланс одного з них, якщо Ти покладеш або знімеш гроші з іншого.
Відповіді в кінці книги.
Об’єкти та змінні
Тут я хочу підкреслити, що кожен об’єкт є окремою сутністю. Те, що вони мають схожий вигляд, не означає, що вони впливають одне на одного. Тому в наведеному нижче прикладі змінна name в об’єкті user1 не матиме жодного впливу на user2.
З іншого боку, якщо у нас є дві змінні, які вказують на один об’єкт, ми можемо змінити його, використовуючи будь-яку з них. Після цього зміняться значення обох змінних, тому що змінилося щось, на що ці дві змінні вказують.
Спочатку дві змінні вказують на той самий об’єкт, потім змінна user1 починає вказувати на інший.
Приватні елементи
У Python є домовленість, що якщо ми не хочемо, щоб атрибути та методи використовувалися поза межами цього класу, їхні назви починаються з символу підкреслення _. Такі атрибути та методи ми називаємо приватними. Вони повинні використовуватися виключно в інших методах того ж класу.
Технічно такі елементи всеодно будуть доступні, проте слід уникати їх використання поза класом. Є багато причин робити певні атрибути приватними. Наприклад, у класі BankAccount з попередньої вправи ми можемо захотіти стежити, щоб баланс рахунку ніколи не падав нижче нуля. Ми могли би зробити це, зробивши balance приватним, а також повертаючи його значення методом get_balance, а в методі withdraw — контролюючи стан рахунку.
Атрибути класу
Іноді ми хочемо створити змінну або функцію, яка пов’язана не з об’єктом, а з класом. Іншими словами, вона буде спільною для всіх об’єктів цього класу.
Щоб створити змінну класу, достатньо визначити її в тілі цього класу. Щоб послатися на неї, ми спочатку використовуємо назву класу, потім ставимо крапку, а потім вказуємо назву цієї змінної. Тому не потрібно створювати жодних об’єктів.
Але тут на нас чекає сюрприз. Таким методом ми також можемо запитувати об’єкт, і він поверне своє значення. Є умова: не повинно бути об’єктної змінної з таким же ім’ям. Це означає, що коли у прикладі нижче ми вперше запитуємо score1.points, то отримуємо значення змінної класу, а коли вдруге — об’єктну змінну.
Однак і тут є пастка. Коли хтось змінить значення змінної класу, усі об’єкти, які не мають об’єктної змінної з такою ж назвою, повертатимуть нові значення.
Іншими словами, якщо ми визначили атрибути з однаковими назвами для класу та об’єкта, тоді першими зчитуватимуться атрибути об’єкта, а якщо їх немає — з класу. Коли після об’єкта ми вказуємо назву змінної та використовуємо символ присвоєння, ми встановлюємо значення об’єктної змінної, а не змінної класу.
Змінні класу часто використовуються для зберігання значень, які не повинні змінюватися. Наприклад, клас, який обчислює розмір податку, може визначатися сталими значеннями податкових ставок.
classTaxCalculator: VAT =0.23# ...
Зверни увагу, що я використав лише великі літери. У випадку сталих значень, тобто значень, встановлених розробником, які не змінюються, використовуються великі літери, тобто запис, відомий як SCREAMING_SNAKE_CASE.
Іноді утворюються класи виключно для зберігання сталих значень. Наприклад, у третій частині, присвяченій написанню гри "Змійка", нам потрібно буде якимось чином визначити напрямок руху. Для цього ми будемо використовувати клас Direction зі змінними, які відповідатимуть за окремі напрямки. Ці змінні можна було б визначити інакше, але такий спосіб забезпечує нам чіткість.
classDirection: UP =1 DOWN =2 LEFT =3 RIGHT =4# Приклад використанняdirection = Direction.UP
if direction == Direction.UP: direction = Direction.DOWN
Функції, присвоєні класу, а не об’єкту, називаються статичними. Вони визначаються так само, як звичайні методи, але не мають параметра self. Ми також повинні ставити перед ними @staticmethod. Ми можемо викликати ці методи в класі, вони не потребують об’єкта.
classCounter: num =0def__init__(self):print("Створюю") Counter.num +=1@staticmethoddefprint_counter():print(f"Створено {Counter.num}")c1 = Counter()# Створююc2 = Counter()# Створююc3 = Counter()# СтворююCounter.print_counter()# Створено 3
Перевірка класу об’єкта
Щоб перевірити, у якому класі створено об’єкт, ми можемо використовувати функцію type, яка повертає об’єкт, що називається типом. Ми вже познайомилися із типами в розділі Основні значення, де розглянули str, int, float і bool. Назви класів — це також типи, тому ми можемо перевірити, чи об’єкт створено за допомогою класу Cookie, порівнюючи його тип із Cookie. Типи порівнюються за допомогою слова is. Ми вже познайомилися з ним, обговорюючи значення None у розділі Змінні. Якщо ми хочемо перевірити, чи типи відрізняються одне від одного, ми використовуємо is not.
У розділі Основні значення ми познайомилися з рядками, числовими та логічними значеннями. Вони також є об’єктами, тому мають клас, конструктор і методи. Зосередьмося на класі str, який використовується для створення рядків. Назва str не дуже типова для класів, але врешті-решт рядок — це особливе значення. Його конструктор дозволяє перетворювати об’єкти іншого типу в об’єкт класу str[^201_3].
b =Trueprint(type(b))# <class ‘bool’>str3 =str(b)print(type(str3))# <class ‘str’>
Коли ми передаємо функції print об’єкт, який має інший тип, ніж str, він замінюється за допомогою конструктора. Він також використовується f-рядками, наприклад, у нещодавно використаному f"Створено {Counter.num}".
Клас str визначає певні методи, які ми можемо викликати для будь-якого об’єкта типу str. Ось найважливіші з них:
upper повертає текст із перетворенням усіх малих літер на великі;
lower повертає текст із перетворенням усіх великих літер на малі;
capitalize повертає текст, у якому перша літера замінюється великою;
title повертає текст, у якому перші літери кожного слова замінюються на великі;
replace повертає текст із заміною всіх повторень одного слова на інше.
У цьому розділі ми познайомилися з класами. Це дуже важлива частина програмування мовою Python, тож класи будуть корисними для нас впродовж всієї книги. А тепер перейдімо до особливого і дуже корисного класу — списку.
[^201_1]: "init" — скорочення від "initialization", тобто "ініціалізатор". На початку та в кінці цієї назви міститься по два підкреслення.
[^201_2]: Елемент — це поняття, яке включає класи, змінні та функції.
[^201_3]: Як ми переконаємося в розділі Оператори, це робиться за допомогою спеціального методу __str__.
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.