Якщо ми досі програмували у функціональному, декларативному чи імперативному стилі, перехід до обʼєктно-орієнтованого програмування (ООП) може видатися трохи незвичним.
Підхід ООП пропонує програмісту думати про задачу як про один або кілька objects, які взаємодіють між собою, зберігають стан і діють над даними.
Обʼєкти можуть представляти реальні сутності (як-от машини чи коти) або абстрактніші поняття (як-от цілі числа, транспортні засоби чи ссавці).
Кожен обʼєкт стає окремим екземпляром у памʼяті компʼютера й представляє частину загальної моделі.
Classes - це визначення нових типів обʼєктів, з яких створюються нові instances обʼєктів.
Часто вони поєднують дані з кодом або функціями, які працюють із цими даними.
У цьому сенсі класи - це креслення або набори інструкцій, з яких можна створити й використовувати багато обʼєктів схожого типу.
Складна програма може мати багато класів, кожен з яких будує багато різних варіантів обʼєктів.
Процес побудови обʼєкта з класу називається instantiation (або створенням екземпляра класу).
Визначення класу в Python досить просте:
class MyClass:
# Class body goes here
Поля класу (інакше відомі як properties, attributes, data members або variables) можна додати до тіла класу:
class MyClass:
number = 5
string = "Hello!"
Екземпляр (обʼєкт) MyClass можна створити й привʼязати до імені, викликавши клас (так само, як викликається функція):
>>> new_object = MyClass()
# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>
Class attributes спільні для всіх обʼєктів (або екземплярів), створених із класу, і доступні через dot notation, тобто через крапку ., поставлену після імені обʼєкта й перед іменем атрибута:
>>> new_object = MyClass()
# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5
# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'
# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()
>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>
# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True
Атрибути класу визначаються в тілі самого класу, перед будь-якими іншими методами. Вони належать класу, що дозволяє спільно використовувати їх між екземплярами класу. Оскільки ці атрибути спільні, до їхнього значення можна звертатися й змінювати його безпосередньо з класу. Зміна значення атрибутів класу змінює значення для всіх обʼєктів, створених із цього класу:
>>> obj_one = MyClass()
>>> obj_two = MyClass()
# Accessing a class attribute from an object.
>>> obj_two.number
5
# Accessing the class attribute from the class itself.
>>> MyClass.number
5
# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27
# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27
>>> obj_two.number
27
Мати купу обʼєктів із постійно синхронізованими даними не надто корисно.
На щастя, обʼєкти, створені з класу, можна налаштувати під себе за допомогою власних instance attributes (або властивостей, змінних чи полів екземпляра).
Як випливає з назви, атрибути екземпляра унікальні для кожного обʼєкта й можуть змінюватися незалежно.
__init__()
Спеціальний «дундер-метод» (скорочено від «метод із подвійним підкресленням») __init__() використовується, щоб налаштовувати екземпляри класу, і ним можна ініціалізувати атрибути чи властивості екземпляра обʼєктів.
Через свою роль в ініціалізації атрибутів екземпляра __init__() також називають class constructor або initializer.
__init__() приймає один обовʼязковий параметр під назвою self, який указує на щойно ініціалізований або створений обʼєкт.
Дані для атрибутів чи властивостей екземпляра потім можна передати як аргументи __init__() після параметра self.
Нижче MyClass має атрибути екземпляра під назвами location_x і location_y.
Як бачимо, ці два атрибути присвоєно першому та другому індексам аргументу location (кортежу), переданого в __init__().
Атрибути location_x і location_y для екземпляра класу тепер ініціалізуються під час створення екземпляра класу й обʼєкта:
class MyClass:
# These are class attributes, variables, or fields.
number = 5
string = "Hello!"
# This is the class "constructor", with a "location" parameter that is a tuple.
def __init__(self, location):
# This is an instance or object property, attribute, or variable.
# Note that we are unpacking the tuple argument into two separate instance variables.
self.location_x = location[0]
self.location_y = location[1]
# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))
# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))
# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1
>>> new_object_two.location_x
-8
Зауважмо, що під час ініціалізації MyClass вище достатньо передати один аргумент: Python сам подбає про передавання self, коли клас викликають.
method - це function, привʼязана або до самого класу (відома як class method, про неї йтиметься в одній із наступних вправ), або до екземпляра класу (обʼєкта).
Методи, які працюють з обʼєктом (екземпляром), потрібно визначати з self як першим параметром.
Решту параметрів ми можемо визначити так само, як для «звичайної» чи незвʼязаної функції:
class MyClass:
number = 5
string = "Hello!"
# Class constructor.
def __init__(self, location):
# Instance properties
self.location_x = location[0]
self.location_y = location[1]
# Instance method. Note "self" as first parameter.
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
Як і у випадку з доступом до атрибутів, щоб викликати метод, достатньо поставити . після імені обʼєкта й перед іменем методу.
Викликаному методу не потрібна копія обʼєкта як перший параметр: Python автоматично підставляє self:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)
# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)
До атрибутів класу можна звертатися зсередини методів екземпляра так само, як і ззовні класу:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances from within an instance.
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5
>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6
У попередніх концептуальних вправах і заготовках практичних вправ ми, напевно, бачили ключове слово pass у тілі функцій замість справжнього коду.
Ключове слово pass - це синтаксично допустима заглушка: воно не дає Python видати синтаксичну помилку для порожнього визначення функції чи класу.
По суті, це спосіб сказати інтерпретатору Python: «Не хвилюйся! Я обовʼязково напишу тут код, просто ще не зробив цього».
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
# This will compile and run without error, but has no current functionality.
def pending_functionality(self):
# Stubbing or placholding the body of this method.
pass
Еллен створює гру, у якій гравець має битися з прибульцями.
Вона щойно дізналася про обʼєктно-орієнтоване програмування (ООП) і прагне скористатися тим, що може дати її програмі використання classes.
На радість Еллен, ми запропонували їй допомогу, і вона доручила нам запрограмувати прибульців, з якими має битися гравець.
Визначте клас Alien з конструктором, який приймає два параметри <x_coordinate> і <y_coordinate> та записує їх у змінні екземпляра x_coordinate і y_coordinate.
Кожен прибулець також починає з рівнем здоровʼя 3, тож змінну health теж потрібно ініціалізувати.
>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3
Тепер кожен прибулець має самостійно відстежувати власну позицію та здоровʼя.
hit
Еллен хотіла б, щоб class Alien мав метод hit, який під час виклику зменшує здоровʼя обʼєкта прибульця на 1.
Так вона зможе просто викликати <alien>.hit(), замість того щоб щоразу змінювати здоровʼя прибульця вручну.
Вирішуйте самі, чи hit() знижує здоровʼя до нуля, чи нижче за нуль.
>>> alien = Alien(0, 0)
# Initialized health value.
>>> alien.health
3
# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2
is_alive
Зауважмо, що якщо здоровʼя й далі зменшуватиметься, то в якийсь момент воно, найімовірніше, досягне 0 (а то й менше!).
Було б добре додати метод is_alive, який Еллен зможе швидко викликати, щоб перевірити, чи прибулець... ну... живий. 😉
<alien>.is_alive() має повертати булеве значення (англ. Boolean).
>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False
teleport
У грі Еллен прибульці вміють телепортуватися!
Потрібно написати метод teleport, який приймає нові значення x_coordinate і y_coordinate та відповідно змінює координати прибульця.
>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4
collision_detection
Очевидно, що якщо прибульців можна у щось влучити, то вони мають уміти визначати, коли таке зіткнення сталося.
Однак алгоритми виявлення зіткнень бувають непростими, і ми ще не знаємо, як реалізувати такий.
Еллен сказала, що зробить це пізніше, але все одно хотіла б, щоб метод collision_detection був у класі як нагадування про те, що цю функціональність треба допрацювати.
Він має приймати якусь змінну (ймовірно, інший обʼєкт), але це все, що ми поки що знаємо.
Потрібно подбати про те, щоб додавання визначення методу до класу не спричиняло помилок під час виклику:
>>> alien.collision_detection(other_object)
>>>
Еллен повернулася з новим проханням. Вона хоче відстежувати, скільки прибульців було створено за весь час існування гри. Каже, що це якось повʼязано із системою нарахування очок.
Наприклад:
>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2
Еллен захоплена тим, що ми вже зробили, але має ще одне прохання.
Вона хотіла б окрему функцію (поза класом Alien()), яка створює list з обʼєктів Alien(), отримуючи масив позицій (як tuples).
Наприклад:
>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
... print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
Зареєструйтеся на Exercism, щоб вивчати й опановувати Python, а також 17 концепцій146 вправ та справжнє наставництво від людей, і все це безкоштовно.