Створіть програму таблиці множення Python за допомогою ООП

У цій статті ви збираєтеся створити програму таблиць множення, використовуючи можливості об’єктно-орієнтованого програмування (ООП) на 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 використовується, щоб запобігти введенню користувачем будь-яких нечислових даних. Як ви могли помітити, цей метод призначений для запиту у користувача, доки він не отримає числове введення. Це ми будемо використовувати пізніше на заняттях з дитиною.

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

  6 найкращих бухгалтерських програм для некомерційних організацій

І останнє, але не менш важливе: метод 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 для запуску всього коду батьківського методу перед запуском наступної частини.

  Як обійти несанкціоноване повідомлення пристрою ADB

Інша частина функції запуску досить проста. Він запитує у користувача номер із повідомленням про операцію, на яку він/вона має відповісти. Потім результат порівнюється з реальним множенням і, якщо вони рівні, додає очко, якщо вони не знімають 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 для підвищення продуктивності.