Създайте приложение за таблица за умножение на Python с ООП

В тази статия ще изградите приложение за таблици за умножение, като използвате силата на обектно-ориентираното програмиране (ООП) в Python.

Ще практикувате основните концепции на ООП и как да ги използвате в напълно функционално приложение.

Python е мултипарадигмен език за програмиране, което означава, че ние като разработчици можем да изберем най-добрия вариант за всяка ситуация и проблем. Когато говорим за обектно-ориентирано програмиране, имаме предвид една от най-използваните парадигми за изграждане на мащабируеми приложения през последните десетилетия.

Основите на ООП

Ще хвърлим бърз поглед към най-важната концепция на ООП в Python, класовете.

Класът е шаблон, в който дефинираме структурата и поведението на обектите. Този шаблон ни позволява да създаваме екземпляри, които не са нищо друго освен индивидуални обекти, създадени според състава на класа.

Прост клас книга с атрибутите заглавие и цвят ще бъде дефиниран по следния начин.

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 пъти, той/тя губи, но ако завърши две маси, играта приключва.

Знам, че изискванията може би са малко по-големи, но ви обещавам, че ще ги разрешим в тази статия 😁.

Разделяй и владей

Най-важното умение в програмирането е решаването на проблеми. Това е така, защото трябва да имате план, преди да започнете да хаквате кода.

  Как да накарате мобилните отметки на Chrome да се показват на работния плот на Chrome

Винаги предлагам да вземем по-големия проблем и да го разделим на по-малки, които могат да бъдат лесно и ефективно решени.

Така че, ако трябва да създадете игра, започнете, като я разделите на най-важните части от нея. Тези подпроблеми ще бъдат много по-лесни за решаване.

Точно тогава можете да имате яснотата как да изпълните и интегрирате всичко с код.

Така че нека направим графика как ще изглежда играта.

Тази графика установява връзките между обектите на нашето приложение. Както можете да видите, двата основни обекта са произволно умножение и таблично умножение. И единственото нещо, което споделят, са атрибутите точки и животи.

Имайки предвид цялата тази информация, нека навлезем в кода.

Създаване на игровия клас 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, за да изпълним целия код на родителския метод, преди да изпълним следващото парче.

  Какво представлява жълтият триъгълник на Outlook?

Другата част от функцията за изпълнение е доста проста. Той иска от потребителя номер със съобщението за операцията, на която трябва да отговори. След това резултатът се сравнява с реалното умножение и ако са равни, добавя точка, ако не отнемат 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 клас конструктори
  • Създайте функционално приложение с OOP
  • Използвайте функцията super в класовете на Python
  • Приложете основните понятия за наследяване
  • Внедрете атрибути на клас и екземпляр

Приятно кодиране 👨‍💻

След това разгледайте някои от най-добрите Python IDE за по-добра производителност.