У цій статті ви збираєтеся створити програму таблиць множення, використовуючи можливості об’єктно-орієнтованого програмування (ООП) на Python.
Ви попрактикуєтеся в основних концепціях ООП і вивчите, як їх використовувати в повнофункціональній програмі.
Python є багатопарадигмальною мовою програмування, а це означає, що ми як розробники можемо вибрати найкращий варіант для кожної ситуації та проблеми. Коли ми говоримо про об’єктно-орієнтоване програмування, ми маємо на увазі одну з найбільш використовуваних парадигм для створення масштабованих програм за останні десятиліття.
Основи ООП
Ми швидко розглянемо найважливішу концепцію ООП у Python — класи.
Клас — це шаблон, у якому ми визначаємо структуру та поведінку об’єктів. Цей шаблон дозволяє нам створювати екземпляри, які є не чим іншим, як окремими об’єктами, створеними відповідно до складу класу.
Простий клас книги з атрибутами title і color буде визначено наступним чином.
class Book: def __init__(self, title, color): self.title = title self.color = color
Якщо ми хочемо створити екземпляри книги класів, ми повинні викликати клас і передати йому аргументи.
# Instance objects of Book class blue_book = Book("The blue kid", "Blue") green_book = Book("The frog story", "Green")
Гарним представленням нашої поточної програми було б:
Приголомшливо те, що коли ми перевіряємо тип екземплярів blue_book і green_book, ми отримуємо «Book».
# Printing the type of the books print(type(blue_book)) # <class '__main__.Book'> print(type(green_book)) # <class '__main__.Book'>
Отримавши кристально чіткі поняття, ми можемо розпочати створення проекту 😃.
Заява про проект
За словами, під час роботи розробниками/програмістами більшість часу не витрачається на написання коду thenewstack ми витрачаємо лише третину свого часу на написання чи рефакторинг коду.
Інші дві третини ми витратили на читання чужого коду та аналіз проблеми, над якою працюємо.
Тож для цього проекту я згенерую формулювання проблеми, і ми проаналізуємо, як із нього створити нашу програму. Як наслідок, ми створюємо повний процес, від обдумування рішення до його застосування з кодом.
Учитель початкових класів хоче гру для перевірки навичок множення для учнів 8-10 років.
У грі повинні бути життя та система очок, де студент починає з 3 життів і має набрати певну кількість очок, щоб виграти. Програма повинна показувати повідомлення «втратити», якщо студент виснажує всі свої життя.
Гра повинна мати два режими, випадкове множення та табличне множення.
Перший має дати учневі випадкове множення від 1 до 10, і він/вона має відповісти правильно, щоб отримати очко. Якщо цього не відбувається, учень втрачає живе, і гра продовжується. Студент виграє лише тоді, коли він/вона досягає 5 балів.
Другий режим має відображати таблицю множення від 1 до 10, де студент повинен ввести результат відповідного множення. Якщо студент програє 3 рази, він/вона програє, але якщо він/вона завершить два столи, гра закінчується.
Я знаю, що вимоги можуть бути трохи більшими, але я обіцяю вам, що ми вирішимо їх у цій статті 😁.
Розділяй і володарюй
Найважливішим умінням у програмуванні є вирішення проблем. Це тому, що вам потрібно мати план, перш ніж почати зламувати код.
Я завжди пропоную взяти більшу проблему та розділити її на менші, які можна легко та ефективно вирішити.
Тому, якщо вам потрібно створити гру, почніть з розбиття її на найважливіші частини. Ці підпроблеми буде набагато легше вирішити.
Саме тоді ви зможете мати ясність того, як виконати та інтегрувати все за допомогою коду.
Отже, давайте зробимо графік того, як гра буде виглядати.
Ця графіка встановлює зв’язки між об’єктами нашої програми. Як ви бачите, два основні об’єкти – випадкове множення та табличне множення. І єдине, що у них спільно, це атрибути Points і Lives.
Маючи на увазі всю цю інформацію, давайте розберемося з кодом.
Створення ігрового класу Parent
Коли ми працюємо з об’єктно-орієнтованим програмуванням, ми шукаємо найчистіший спосіб уникнути повторення коду. Це називається СУХИЙ (не повторюйся).
Примітка. Ця мета не пов’язана з написанням меншої кількості рядків коду (якість коду не слід вимірювати цим аспектом), а абстрагувати логіку, яка найчастіше використовується.
Відповідно до попередньої ідеї, батьківський клас нашої програми повинен встановити структуру та бажану поведінку двох інших класів.
Подивимося, як це буде зроблено.
class BaseGame: # Lenght which the message is centered message_lenght = 60 description = "" def __init__(self, points_to_win, n_lives=3): """Base game class Args: points_to_win (int): the points the game will need to be finished n_lives (int): The number of lives the student have. Defaults to 3. """ self.points_to_win = points_to_win self.points = 0 self.lives = n_lives def get_numeric_input(self, message=""): while True: # Get the user input user_input = input(message) # If the input is numeric, return it # If it isn't, print a message and repeat if user_input.isnumeric(): return int(user_input) else: print("The input must be a number") continue def print_welcome_message(self): print("PYTHON MULTIPLICATION GAME".center(self.message_lenght)) def print_lose_message(self): print("SORRY YOU LOST ALL OF YOUR LIVES".center(self.message_lenght)) def print_win_message(self): print(f"CONGRATULATION YOU REACHED {self.points}".center(self.message_lenght)) def print_current_lives(self): print(f"Currently you have {self.lives} livesn") def print_current_score(self): print(f"nYour score is {self.points}") def print_description(self): print("nn" + self.description.center(self.message_lenght) + "n") # Basic run method def run(self): self.print_welcome_message() self.print_description()
Ого, це здається досить величезним класом. Дозвольте мені пояснити це глибоко.
Перш за все, давайте розберемося з атрибутами класу та конструктором.
По суті, атрибути класу – це змінні, створені всередині класу, але поза межами конструктора чи будь-якого методу.
Тоді як атрибути екземпляра є змінними, створеними лише всередині конструктора.
Основною відмінністю між цими двома є обсяг. тобто атрибути класу доступні як з об’єкта екземпляра, так і з класу. З іншого боку, атрибути екземпляра доступні лише з об’єкта екземпляра.
game = BaseGame(5) # Accessing game message lenght class attr from class print(game.message_lenght) # 60 # Accessing the message_lenght class attr from class print(BaseGame.message_lenght) # 60 # Accessing the points instance attr from instance print(game.points) # 0 # Accesing the points instance attribute from class print(BaseGame.points) # Attribute error
Інша стаття може глибше зануритися в цю тему. Залишайтеся на зв’язку, щоб прочитати його.
Функція get_numeric_input використовується, щоб запобігти введенню користувачем будь-яких нечислових даних. Як ви могли помітити, цей метод призначений для запиту у користувача, доки він не отримає числове введення. Це ми будемо використовувати пізніше на заняттях з дитиною.
Методи друку дозволяють уникнути повторення друку одного й того самого кожного разу, коли в грі відбувається подія.
І останнє, але не менш важливе: метод run — це просто оболонка, яку використовуватимуть класи випадкового множення та таблиці множення для взаємодії з користувачем і забезпечення функціональності всього.
Створення занять дитини
Після того, як ми створили цей батьківський клас, який визначає структуру та деякі функціональні можливості нашої програми, настав час побудувати фактичні класи ігрового режиму, використовуючи силу успадкування.
Клас випадкового множення
Цей клас запускатиме «перший режим» нашої гри. Звичайно, він використовуватиме випадковий модуль, який дасть нам можливість запитувати у користувача випадкові операції від 1 до 10. Ось чудова стаття про випадкові (та інші важливі модулі) 😉.
import random # Module for random operations
class RandomMultiplication(BaseGame): description = "In this game you must answer the random multiplication correctlynYou win if you reach 5 points, or lose if you lose all your lives" def __init__(self): # The numbers of points needed to win are 5 # Pass 5 "points_to_win" argument super().__init__(5) def get_random_numbers(self): first_number = random.randint(1, 10) second_number = random.randint(1, 10) return first_number, second_number def run(self): # Call the upper class to print the welcome messages super().run() while self.lives > 0 and self.points_to_win > self.points: # Gets two random numbers number1, number2 = self.get_random_numbers() operation = f"{number1} x {number2}: " # Asks the user to answer that operation # Prevent value errors user_answer = self.get_numeric_input(message=operation) if user_answer == number1 * number2: print("nYour answer is correctn") # Adds a point self.points += 1 else: print("nSorry, your answer is incorrectn") # Substracts a live self.lives -= 1 self.print_current_score() self.print_current_lives() # Only get executed when the game is finished # And none of the conditions are true else: # Prints the final message if self.points >= self.points_to_win: self.print_win_message() else: self.print_lose_message()
Ось ще один великий клас 😅. Але, як я зазначав раніше, справа не в кількості рядків, а в тому, наскільки він читабельний і ефективний. І найкраще в Python полягає в тому, що він дозволяє розробникам створювати чистий і читабельний код, наче вони розмовляють звичайною англійською.
У цьому класі є одна річ, яка може вас збентежити, але я поясню це якомога простіше.
# Parent class def __init__(self, points_to_win, n_lives=3): "... # Child class def __init__(self): # The numbers of points needed to win are 5 # Pass 5 "points_to_win" argument super().__init__(5)
Конструктор дочірнього класу викликає суперфункцію, яка в той же час посилається на батьківський клас (BaseGame). По суті, це повідомляє Python:
Заповніть атрибут «points_to_win» батьківського класу 5!
Немає необхідності розміщувати self всередині частини super().__init__() лише тому, що ми викликаємо super у конструкторі, і це призведе до зайвості.
Ми також використовуємо функцію super у методі run, і ми побачимо, що відбувається в цьому фрагменті коду.
# Basic run method # Parent method def run(self): self.print_welcome_message() self.print_description() def run(self): # Call the upper class to print the welcome messages super().run() .....
Як ви можете помітити метод run у батьківському класі, надрукуйте привітання та повідомлення з описом. Але було б гарною ідеєю зберегти цю функціональність, а також додати додаткові в дочірні класи. Згідно з цим, ми використовуємо super для запуску всього коду батьківського методу перед запуском наступної частини.
Інша частина функції запуску досить проста. Він запитує у користувача номер із повідомленням про операцію, на яку він/вона має відповісти. Потім результат порівнюється з реальним множенням і, якщо вони рівні, додає очко, якщо вони не знімають 1 життя.
Варто сказати, що ми використовуємо цикли while-else. Це виходить за рамки цієї статті, але я опублікую одну про це за кілька днів.
Нарешті, get_random_numbers використовує функцію random.randint, яка повертає випадкове ціле число у вказаному діапазоні. Потім він повертає кортеж із двох випадкових цілих чисел.
Клас випадкового множення
«Другий режим» повинен відображати гру у форматі таблиці множення та переконатися, що користувач правильно відповідає щонайменше на 2 таблиці.
Для цього ми знову використаємо потужність super і змінимо атрибут батьківського класу points_to_win на 2.
class TableMultiplication(BaseGame): description = "In this game you must resolve the complete multiplication table correctlynYou win if you solve 2 tables" def __init__(self): # Needs to complete 2 tables to win super().__init__(2) def run(self): # Print welcome messages super().run() while self.lives > 0 and self.points_to_win > self.points: # Gets two random numbers number = random.randint(1, 10) for i in range(1, 11): if self.lives <= 0: # Ensure that the game can't continue # if the user depletes the lives self.points = 0 break operation = f"{number} x {i}: " user_answer = self.get_numeric_input(message=operation) if user_answer == number * i: print("Great! Your answer is correct") else: print("Sorry your answer isn't correct") self.lives -= 1 self.points += 1 # Only get executed when the game is finished # And none of the conditions are true else: # Prints the final message if self.points >= self.points_to_win: self.print_win_message() else: self.print_lose_message()
Як ви розумієте, ми лише модифікуємо метод запуску цього класу. Це магія успадкування, ми записуємо логіку, яку використовуємо, у кількох місцях і забуваємо про це 😅.
У методі run ми використовуємо цикл for для отримання чисел від 1 до 10 і створюємо операцію, яку показує користувач.
Знову, якщо життя вичерпано або досягнуто очок, необхідних для перемоги, цикл while перерветься, і відобразиться повідомлення про виграш або програш.
ТАК, ми створили два режими гри, але поки що, якщо ми запустимо програму, нічого не станеться.
Отже, давайте завершимо програму, реалізувавши вибір режиму та створивши екземпляри класів залежно від цього вибору.
Реалізація вибору
Користувач зможе вибрати, в якому режимі хоче грати. Тож давайте подивимося, як це реалізувати.
if __name__ == "__main__": print("Select Game mode") choice = input("[1],[2]: ") if choice == "1": game = RandomMultiplication() elif choice == "2": game = TableMultiplication() else: print("Please, select a valid game mode") exit() game.run()
Спочатку ми просимо користувача вибрати між 1 або 2 режимами. Якщо введені дані недійсні, сценарій припиняє роботу. Якщо користувач вибере перший режим, програма запустить режим гри «Випадкове множення», а якщо він вибере другий, запустить режим «Табличне множення».
Ось як це буде виглядати.
Висновок
Вітаю, ти просто створити програму Python з об’єктно-орієнтованим програмуванням.
Весь код доступний у Репозиторій Github.
У цій статті ви навчилися:
- Використовуйте конструктори класів Python
- Створіть функціональну програму за допомогою ООП
- Використовуйте функцію super у класах Python
- Застосовувати основні поняття про спадкування
- Реалізація атрибутів класу та екземпляра
Щасливого кодування 👨💻
Далі ознайомтеся з найкращими Python IDE для підвищення продуктивності.