Перейти до змісту

Класи і екземпляри класів

Насамперед зауважимо, що в Python існує дві реалізації класів: так звані "класи старого типу" і "класи нового типу". Цей "розподіл" починається з Python версії 2.2. В класи нового типу було внесено відносно суттєві зміни задля покращення реалізації ООП. В Python починаючи з версії 3.0 підтримуються лише класи нового типу, класи старого типу не підтримуються як застарілі. В даному курсі ми розглядаємо лише класи нового типу.

Створення класів

В мові програмування Python класи створюють за допомогою інструкції class, після якої вказують им'я класа, потім ставиться двокрапка, далі з нового рядка і з відступом реалізується тіло класа:

class NameOfClass:
    pass

Імена класів прийнято записувати в PascalCase.

Тіло класа може містити будь-який валідний код Python. Тіло буде виконано при створенні класа:

>>> class Printer:
...     print('Це клас Printer!')
...
Це клас Printer!
>>>

Так само як для функцій, для класа можна створити документацію. Docstring розміщується одразу після заголовка класа:

>>> class POI:
...     '''Point of Interest.
...
...     Attributes:
...         lon: float - longtitude
...         lat: float - latitude
...         title: str - ...
...     '''
...     pass
...
>>> help(POI)
Help on class POI in module __main__:

class POI(builtins.object)
 |  Point of Interest.
 |
 |  Attributes:
 |      lon: float - longtitude
 |      lat: float - latitude
 |      title: str - ...
 |
 |  Data descriptors defined here:
...

Клас як модуль

В Python клас можна представити подібно модулю. Так само як у модулі у ньому можуть бути свої дані (змінні зі значеннями) і функції. Однак у випадку класів використовується дещо інша термінологія. Імена, визначені в класі, називаються атрибутами (attribute) цього класа. Атрибути-дані часто називають полями і деколи властивостями (property). Атрибути-функції називаються методами (method). Кількість полів і методів у класі може бути довільною.

Так само як у модулі у класа є власний простір імен. Доступ до атрибутів класа здійснюється через ім'я класа. Після імені класа ставлять крапку і далі ім'я атрибута:

>>> class Adder:
...     n = 5
...     def add(value):
...             return value + Adder.n
...
>>> Adder.n
5
>>> Adder.add(4)
9
>>>

У вищенаведеному прикладі імена n і add — це атрибути клас Adder.

Клас як створювач екземплярів

Створення екземпляра класа називають інстанціюванням (instantiation).

Екземпляр створюється шляхом виклику класа за його именем. Оскільки у програмному коді важливо не згубити посилання на щойно створений об'єкт, то зазвичай його пов'язують зі змінною. Отже створення об'єкта найчастіше виглядає так:

>>> a = Adder()
>>> a
<__main__.Adder object at 0x0000018D3207ECD0>
>>> type(a)
<class '__main__.Adder'>
>>> some_another_instance = Adder()
>>>

Атрибути екземплярів

Так само як класи екземпляри можуть мати власні атрибути.

Клас створює екземпляри, які у певному сенсі можна назвати його спадкоємцями. Це означає, що якщо у екземпляра немає власного атрибута, то інтерпретатор шукає його на один рівень вище, тобто у класі.

Поля екземплярів

Створимо клас:

>>> class Adder:
...     n = 5
...     def add(value):
...             return value + Adder.n
...
>>>

Створимо екземпляр цього класа:

>>> a = Adder()
>>>

Спробуємо отримати доступ до атрибута n екземпляра a:

>>> a.n
5
>>>

Але якщо ми присвоюємо екземпляру класа поле з таким самим ім'ям як у класі, то воно перевизначає (як би "перекриває") поле класа:

>>> a.n = 'Some number'
>>> a.n
'Some number'
>>> Adder.n
5
>>>

Тут змінні a.n і Adder.n — це дві різні змінні. Перша знаходиться у просторі імен екземпляра класа Adder, друга — у просторі імен самого класа Adder.

Методи

Щодо методів, то вони також наслідуються екземплярами класа. У вищенаведеному прикладі у екземпляра a немає власного метода add, отже інтерпретатор шукає його в класі Adder. Спробуємо:

>>> a.add(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add() takes 1 positional argument but 2 were given
>>>

Інтерпретатор повідомляє нам, що add() приймає тільки один аргумент, а було передано два. Звідки ж взявся другий аргумент, якщо методу add() було передано тільки одне число 4?

Від кожного класа може бути породжено багато його екземплярів. Методи класа найчастіше призначаються для обробки вже конкретних екземплярів класа. Таким чином, коли викликається метод, йому треба передати конкретний екземпляр класа, який він буде опрацьовувати.

Зрозуміло, що екземпляр, що передається — це об'єкт, до якого застосовується метод. Вираз a.add() інтерпретатор виконує наступним чином:

  1. Шукаю атрибут add() в екземплярі a. Не знайшов.
  2. Тоді йду шукати у клас Adder, так як він створив екземпляр a.
  3. Тут знайшов метод. Передаю йому екземпляр, до якого цей метод треба застосувати, а також аргумент, що вказано у дужках.

Іншими словами, вираз

a.add(4)

перетворюється у вираз

Adder.add(a, 4)

Таким чином інтерпретатор спробував передати у метод add() класа Adder два аргумента — екземпляр a і число 4. Але ми запрограмували метод add() так, щоо він приймає тільки один параметр. В Python визначення методів не передбачається прийняття об'єкта як зрозуміле за замовчуванням. Об'єкт що приймається треба вказувати явно при оголошенні метода.

За згодою у Python для посилання на об'єкт використовується ім'я self. Ось так повинен виглядати метод add(), якщо ми плануємо викликати його через екземпляри класа:

>>> class Adder:
...     n = 5
...     def add(self, value):
...             return value + self.n
...
>>>

Змінна self зв'язується з об'єктом, до якого було застосовано даний метод, і через цю змінну ми отримуємо доступ до атрибутів екземпляра. Коли цей же метод застосовується до іншого екземпляра, то self зв'яжеться вже з саме цим іншим екземпляром, і через цю змінну будуть вилучатись тільки його поля.

Приклад:

>>> class Adder:
...     n = 5
...     def add(self, v):
...             return v + self.n
...
>>> a = Adder()
>>> b = Adder()
>>> a.n = 10
>>> a.add(3)
13
>>> b.add(4)
9
>>>

Тут від класа Adder створюється два екземпляра – a та b. Для екземпляра a створюється власне поле n. Екземпляр b, не має такого поля, отже успадковує його від класа Adder. Переконаємось у цьому:

>>> a.n is Adder.n
False
>>> b.n is Adder.n
True
>>>

У методі add() вираз self.n – це звернення до поля n переданого об'єкта, і не важливо, на якому рівні його буде знайдено — у екземплярі чи у класі.

Зміна полів об'єкта

В Python об'єкту можна не тільки перевизначати поля і методи, успадковані від класа, але можна додавати нові, яких немає у класі:

>>> a.test = 'Hi'
>>> a.test
'Hi'
>>> Adder.test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Adder' has no attribute 'test'
>>>

Однак у програмуванні так не прийнято, тому що тоді об'єкти одного класа будуть відрізнятись між собою по набору атрибутів. Це затруднить автоматизацію їх обробки, внесе у програму хаос.

Back to top