Вступ
Ще одна річ, яку ви дізнаєтеся – це те, що програмісти є ліниві. Якщо вже щось придумали і зробили до вас, тоді чому ви повинні це робити чи придумувати ще один раз?
Функції якраз і є одним із тих інструментів, що допомагають не робити одне і те ж двічі. Придумали і написали свій код один перший раз, оформили його у вигляді функції, а тоді використовуємо цю функцію знову і знову у своєму коді у найрізноманітніших місцях. Уникаючи дублювання коду. Зручно, так?
Звичайно функції мають свої обмеження. Функції не можуть зберігати ніякої інформації чи змінних – вони лише містять логіку. Кожен новий раз, коли ви викликатимете функцію, вона починатиметься з “чистого листа”, не пам’ятаючи свого попереднього виклику. Проте, у деяких випадках, функції та змінні є взаємопов’язані між собою, і потребують взаємодії. Наприклад, уявіть, що ви володієте ключкою для гольфу. Вона має певні характеристики (змінні) як от довжина, матеріал стержня, розмір ручки. Вона також виконує певні функції, наприклад: розгойдування, удар по кульці, або функція зламу (при злості від невдачі ;). Щоб правильно користуватися ключкою і використовувати її правильно згідно її функцій, нам необхідно знати параметри (змінні) функції: її довжину, розмір ручки, матеріал ключки, і т.д.
Вище описані дії ключки можуть з легкістю бути описані з допомогою Пітонівських функцій, які ми уже освоїли у попередніх уроках. Вхідні параметри (аргументи) впливають на ефект функції (значення що вона повертає). Але що робити, якщо потрібно щоб функція могла змінити значення змінної? Що стається, якщо кожного разу коли ми використовуємо ключку для гольфу – ключка стає слабшою, покриття ручки по-трохи стирається, ви стаєте трохи засмученим і ключка перестає вам подобатися? Фунція нажаль не може впливати на значення змінної. Функція може лише отримувати набір параметрів, і, відповідно до них, повертати певне значення. Але не змінювати самі вхідні параметри, у нашому випадку параметри ключки. Нам потрібен спосіб групування функцій та змінних, що тісно пов’язані, в одному місці. Так, щоб вони могли взаємодіяти між собою, а також впливати одне на одного.
Є шанси, що ви також маєте не одну, а декілька ключок для гольфу. Без Класів нам треба буде писати тонни коду для кожної окремої ключки. Це незручно, особливо розуміючи, що насправді усі ключки подібні між собою, мають однакові функції, і відрізняються лише по своїх параметрах (змінних). Ідеяльним підходом для нас буде мати дизайн базової ключки для гольфу. І кожного разу, коли ми з вами створюємо нову ключку, ми просто встановлюємо її атрибути (функції, змінні) – довжину, матеріал, вагу, і т.д.
Або, що якщо ви хочете ключку, яка матиме додаткові параметри? Можливо ви захотіли встановити годинник на вашу ключку… Чи означає це, що ми повинні створити для цього цілком нову ключку з нуля? Ми б тоді мали написати код спочатку для базової ключки + ще раз код для нашої нової ключки, і ще код для годинника на ключці для нашого нового дизайну. Чи не було б краще, якщо б ми просто взяли дизайн нашої базової ключки, і просто додали код годинника до неї?
Усі вище описані проблеми вирішуються інструментом, що має назву – Об’єктно-Орієнтоване Програмування (ООП). ООП складає для нас набір функцій та змінних разом у такий спосіб, що вони можуть бачити і співпрацювати один з одним, бути легко продубльованими, і з легкістю зміненими при потребі. І для цього всього ми використовуємо річ, що має назву ‘Клас‘.
Мали трохи довгого вступу, адже тема ООП непроста і потребує підготовки та додаткових пояснень. А тепер давайте вже нарешті до діла…
Створення Класу
Що таке клас? Про клас можемо думати, як про заготовку, план, проект. Сам по собі клас нічого не робить і не означає, він лише описує як щось зробити чи створити. З цієї заготовки (класу) ми можемо створювати багато об’єктів – які ми технічно називаємо прикладами, зразками, інстанціями, екземплярами.
Отже, як ми створюємо так звані “класи”? Дуже просто, використовуючи оператор ‘class’:
1 2 3 4 5 |
# визначаємо клас class class_name: [вираз 1] [вираз 2] [і т.д.] |
Не дуже зрозуміло? Це нормально, нижче давайте тепер глянемо на конкретний приклад класу, який визначає Прямокутник (англ. Rectangle)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# приклад класу Прямокутника class Rectangle: def __init__(self, x, y): self.x = x self.y = y description = "Цей прямокутник поки немає означення" author = "Цей прямокутник поки немає автора" def area(self): """Обчислюємо площу прямокутника""" return self.x * self.y def perimeter(self): """Обчислюємо периметр прямокутника""" return 2 * self.x + 2 * self.y def describe(self, text): """Встановлюємо означення прямокутника""" self.description = text def authorName(self, text): """Встановлюємо автора прямокутника""" self.author = text def scaleSize(self, scale): """Змінюємо розміри прямокутника згідно отриманого співвідношення""" self.x = self.x * scale self.y = self.y * scale |
Те, що ми щойно написали – це визначення прямокутника (його змінні – параметри, атрибути) та операції, які ми можемо виконувати над прямокутником (функції – це його методи, методи класу). Це дуже важливо зрозуміти – поки ми ще не створили жодного прямокутника, а лише описали що таке прямокутник, і що з ним можна робити. Прямокутник має ширину (x), довжину (y), знаючи його довжину та ширину, ми можемо з вами обчислити його площу та периметр. Код не запускається при визначенні класу – ми просто описуємо змінні та функції (методи) класу.
Функція під назвою __init__ запускається, коли ми створюємо екземпляр (приклад, зразок) класу Rectangle, тобто коли ми, використовуючи заготовочку (клас) і створюємо сам Прямокутник. Пізніше ми з вами краще зрозуміємо, як працює __init__.
Через ключове слово ‘self’ ми звертаємося до методів (функцій) та атрибутів (змінних) класу будучи всередині класу. Ви можете бачити, що слово ‘self’ є першим параметром у кожній функції класу. Кожна функція чи змінна оголошена на першому рівні вкладення у визначенні класу автоматично “запихається” у ‘self’. Тому, щоб далі в коді класу можна було доступитися до змінних та функцій класу, ми використовуємо ‘self’ слово, після якого також ставимо крапку, щоб отримати потрібну змінну чи функцію з класу. Наприклад: self.variable_name поверне на значення атрибуту класу під назвою ‘variable_name’.
Використання Класу
Дуже класно і чудово, що тепер ми з вами можемо написати клас, але що далі ми можемо з ним робити? Ось тут маємо нарешті приклад, який показує як можна скористатися визначеним вище класом Прямокутника, щоб створити справжній ексземпляр (зразок, приклад) прямокутника:
1 |
my_rect = Rectangle(60, 35) |
Що ми зробили у рядочку вище? Давайте трошки детальніше розберемося…
Функція __init__ прийшла у дію у прикладі вище. Ми створили екземпляр класу спершу згадуючи ім’я класу (у цьому випадку наш клас називається Rectangle), і тоді, в дужках, значення, які ми передаємо функції __init__. Функція __init__ викликається з переданими їй параметрами (в дужках) і ця функція повертає нам новостворений екземпляр класу Rectangle, який у свою чергу ми призначаємо змінній my_rect.
Ми можемо думати про екземпляр класу, my_rect, як про окремий автономний набір змінних та функцій. У подібний спосіб, у який ми з вами використовували ключове слово ‘self’, щоб доступитися до функцій та змінних екземпляру класу зсередини коду класу, ми використовуємо змінну, якій призначили екземпляр класу (my_rect), щоб отримати доступ до змінних та функцій екземпляру класу, ззовні. Продовжуючи попередній приклад, ось яким чином ми можемо скористатися функціоналом та змінними екземпляру прямокутника маючи один із екземплярів його у змінній my_rect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# знаходимо площу нашого прямокутника print my_rect.area() # тепер шукаємо його периметр print my_rect.perimeter() # тепер опишемо наш прямокутник (встановимо опис) my_rect.describe(u"Це великий прямокутник") # а тепер зменшимо прямокутник удвічі my_rect.scaleSize(0.5) # і порахуємо та виведемо на екран площу нового зменшеного прямокутника print my_rect.area() |
Можемо бачити, що там де ми б використовували слово ‘self’ зсередини визначення класу, у попередньому прикладі ми користувалися назвою змінною, які призначили екземпляр даного класу. Цю змінну ми використали, щоб переглядати (отримувати) та встановлювати (змінювати) змінні всередині класу, а також, щоб отримати доступ до функцій класу (методів).
Ми не є обмеженими у кількості інстансів (екземплярів) класу – ми можемо створювати їх стільки, скільки завгодно. Наприклад я можу зробити отак:
1 2 |
long_rect = Rectangle(120, 10) short_rect = Rectangle(30, 10) |
створивши “довгий” та “короткий” прямокутники, і кожен з них матимуть свій власний набір функцій та змінних всередині – вони повністю незалежні один від одного. Тож ми з вами необмежені у кількості об’єктів, що ми можемо створити маючи напередодні визначений клас Rectangle.
Жаргон – Термінологія
Об’єктно-Орієнтоване Програмування (далі – ООП) має ряд термінів, концепцій та підходів, з якими нам варта ознайомитися та використовувати надалі для полегшення свого життя:
- коли ми вперше оголошуємо і описуємо клас, тоді ми з вами ВИЗНАЧАЄМО його (подібно до функцій)
- можливість групувати подібні функції та змінні разом в класі називається ІНКАПСУЛЯЦІЄЮ
- слово ‘class‘ (клас) використовується для опису коду, в якому він є визначеним (так само як слово ‘function’ для опису коду всередині тіла функції), але часом класом називають і екземпляр (інстанс, зразок, об’єкт) цього класу – це може трохи заплутувати, то ж переконайтеся, що ви розумієте у якій формі ми з вами говоримо про класи.
- змінна всередині класу зазвичай називається АТРИБУТОМ
- функція визначена всередині класу називається МЕТОДОМ
- сам клас належить до тієї ж категорї речей як змінні, списки, словники, і т.д. Саме тому самі Класи також є об’єктами, як зрештою і все інше у мові Пітон
- також клас можуть називати ‘СТРУКТУРОЮ ДАНИХ‘, адже він містить дані, та методі для їхньої обробки
Унаслідування
Давайте на хвилинку повернемося до вступу цього уроку. Ми знаємо, що класи групують (збирають докупи) змінні та функції, також знані як Атрибути та Методи, таким чином, що дані та код для їхньої обробки усі є в одному місці. Ми можемо створити будь-яку кількість інстансів (екземплярів) класу, саме тому нам не потрібно писати новий код для кожного нового об’єкту, що ми хочемо створити. Але що робити, якщо ми хочемо додати кілька нових властивостей нашій ключці для гольфу? Ось тут і приходить нам на допомогу УНАСЛІДУВАННЯ.
Мова Python дозволяє використовувати принцип унаслідування дуже легко. Ми визначаємо новий клас базуючись на іншому, ‘батьківському’ класі. Наш новий клас отримує усі дані та логіку від батьківського класу, а також ми можемо додати нові речі до нього. Якщо якийсь атрибут чи метод нового класу має таке саме ім’я як у батьківському класі, тоді він ‘перекриває’ батьківський атрибути чи метод. Ще пам’ятаєте клас Rectangle?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# приклад класу Прямокутника class Rectangle: def __init__(self, x, y): self.x = x self.y = y description = "Цей прямокутник поки немає означення" author = "Цей прямокутник поки немає автора" def area(self): """Обчислюємо площу прямокутника""" return self.x * self.y def perimeter(self): """Обчислюємо периметр прямокутника""" return 2 * self.x + 2 * self.y def describe(self, text): """Встановлюємо означення прямокутника""" self.description = text def authorName(self, text): """Встановлюємо автора прямокутника""" self.author = text def scaleSize(self, scale): """Змінюємо розміри прямокутника згідно отриманого співвідношення""" self.x = self.x * scale self.y = self.y * scale |
Якщо б ми захотіли з вами визначити новий клас, нехай це буде Квадрат (Square), базований на нашому класі Прямокутник (Rectangle), тоді це виглядало б наступним чином:
1 2 3 4 5 |
class Square(Rectangle): def __init__(self, x): self.x = x self.y = x |
Бачите, що цього разу ми практично повторили запис визначення Прямокутника, але цього разу ми визначили наш метод __init__ трішки по іншому. Він у нас тепер приймає лише ‘self’ та ‘x’. Ніякого ‘y’. У квадрата сторони рівні. Тому в цьому методі обидва атрибути (ширина та довжина – x та y) набувають однакового значення.
А саме унаслідування відбулося у першій же стрічці визначення класу: після назви нового класу Square, в дужках ми згадали назву класу з якого унаслідуємось – Rectangle. Отже, що ми отримали – усі дані та функціонал попередньо визначеного Прямокутника (Rectangle), але з рівними сторонами і при цьому описавши тепер лише один метод __init__. Усе решта ми отримали з вами можна сказати ‘на халяву’ унаслідувавшись від класу прямокутника!
А давайте тепер спробуємо описати ще один новий клас, цього разу вже унаслідуємось від щойно визначеного Square. Наш новий клас буде представляти вдвічі більший квадрат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# наш "Подвійний Квадрат" буде виглядав отак # ___________ # | | | # | | | # |_____|_____| class DoubleSqare(Square): def __init__(self, x): self.x = 2 * x self.y = x def perimeter(self): return 2 * self.x + 3 * self.y |
Цього разу ми також перевизначили функцію ‘perimeter’, оскільки ми маємо третю лінію посередині нашої фігури. Спробуйте створити екземпляр (інстанс) даного класу, і поеспериментуйте з ним, викличте кілька методів. Зокрема периметр.
Вказівники
Повертаючись назад до змінних, коли ми кажемо, що одна змінна рівна іншій, наприклад var1 = var2, тоді змінна зліва від знаку ‘дорівнює’ отримує значення змінної справа від знаку рівності. Із інстансами (екземплярами) класу, ця операція проходить трошки по іншому – ім’я зліва рівняння стає інстансом класу справа від знаку дорівнює. То ж у виразі instance2 = instance1, змінна instance2 “вказує” на екземпляр класу instance1. Таким чином ми маємо 2 ім’я (дві змінних), і обидва вказують на один екземпляр (інстанс) класу, і ми можемо звернутися до даного інстанса використовуючи будь-яке з даних двох імен.
В інших мовах ви можете робити подібні речі використовуючи так звані ‘вказівники’ (pointers), проте в Пітоні це все стається “за сценою”.
Якщо ситуація із вказівниками не дуже зрозуміла – не переживайте, це нормально. І не критично на даний момент. З часом розуміння прийде. Зараз основне зрозуміти і запам’ятати, що таке клас, як його визначити, створити з нього інстанс, та як цей інстанс використовувати.
На завершення…
Ось і усе щодо класів! Про класи і загалом ООП можна розказувати ще багато і багато цікавого. Тема обширна і популярна в сучасному програмуванні. Але для початку того, що є у нашому уроці і те що ми з вами щойно пройшли – цілком достатньо, щоб вже починати використовувати класи в Пітоні.
Думаю цей урок викликав у вас море запитань. То ж сміло пишіть в коментах, будемо розбиратись. А також виправляти та доповнювати даний урок необхідними матеріалами, щоб ООП для початківців було простішим та доступнішим.
Молодці!
По-маленьку підходимо до завершення курсу. Залишося 3 простіших уроки – це Пітонівські модулі, файловий ввід та вивід, і обробка помилок. Отже, в наступному уроці ми з вами глянемо ближче на модулі в мові Python, як їх писати, використовувати, імпортувати.
Дякую. Ви зуміли втиснути саму сіль наскільки це можливо описати таку тему, в такій короткій статті 🙂
Виправте помилку помилку в тексті:
“Прямокутника, щоб створити справжній ексземпляр”
Доброго дня!
Як зробити так, щоб екземпляр класу був функцією?
Тобто, щоб можна було створити список функцій a[1], a[2], a[3]; а потім цей список застосувати до списку значень: с[1], c[2], c[3]…
Можливо запитання не по темі. Але для чого потрібне подвійне нижнє підкреслення в назві функції?
def __init__(self, x, y):
self.x = x
self.y = y
це вбудована назва. просто так придумали дизайнери мови. + метод приватний і зазвичай своїм кодом не викликається на пряму
Надрукував перший приклад з прямокутником. Попередньо вписав ютф-8
Запустив через пітон, і воно мені показало:
====================== RESTART: D:\Python\Rectangle.py ======================
Я так розумію, крім того, що написано на сайті я мав додати ще щось в код? Бо я не розумію, в чому може бути помилка.
Дякую.
P.S. Вінда 10, Пітон 2.7.11
уявлення не маю що таке RESTART, на вінді вже років 10 не був. а на 10-ій ще більше 😉
Там де напочатку клас Rectangle, то тільки опис параметрів класу, а використання вказане нижче. Потрібно з’єднати декілька кусків коду, щоб щось побачити після запуску. Restart пише напочатку запуску IDLE, потім йде сама програма, тобто вивід даних. Ти скопіював тільки опис, вивіду там не має, тому нічого не відбулося після запуску, і після Restart нічого не має.
“Фунція нажаль не може впливати на значення змінної”
Правильно буде так – на жаль.
Описка
“А саме унаслідування відбулося у першій же стрічкі визначення класу: після назви нового класу Square, в дужках ми згадали назву класу з якого унаслідуємось – Square.”
Ймовірно, мало б бути: “……в дужках ми згадали назву класу з якого унаслідуємось – Rectangle.”
дякую. виправив.
1) В прикладі:
class Square(Rectangle):
def __init__(self, x):
self.x = x
self.y = y
очевидно малось на увазі self.y = x
2) В прикладі:
class DoubleSqare(Square):
def __init__(self, x):
self.x = 2 * x
self.x = x
очевидно замість self.x = x малось на увазі self.y = x
поправив