使用 OOP 构建 Python 乘法表应用程序
已发表: 2021-04-24在本文中,您将使用 Python 中的面向对象编程 (OOP) 的强大功能构建一个乘法表应用程序。
您将练习OOP的主要概念,以及如何在功能齐全的应用程序中使用它们。
Python 是一种多范式编程语言,这意味着我们作为开发人员可以为每种情况和问题选择最佳选项。 当我们谈论面向对象编程时,我们指的是过去几十年中构建可扩展应用程序最常用的范式之一。
面向对象的基础
我们将快速浏览 Python 中最重要的 OOP 概念,即类。
类是一个模板,我们在其中定义对象的结构和行为。 该模板允许我们创建Instances ,它们只不过是按照类的组合创建的单个对象。
一个简单的书类,具有标题和颜色属性,将定义如下。
class Book: def __init__(self, title, color): self.title = title self.color = color如果我们想创建类 book 的实例,我们必须调用类并将参数传递给它。
# 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'>在明确了这些概念之后,我们就可以开始构建项目了。
项目说明
在作为开发人员/程序员工作时,大部分时间都没有花在编写代码上,根据 newstack,我们只花三分之一的时间编写或重构代码。
我们花了另外三分之二的时间阅读其他人的代码并分析我们正在解决的问题。
所以对于这个项目,我将生成一个问题陈述,我们将分析如何从中创建我们的应用程序。 因此,我们正在制定完整的流程,从思考解决方案到将其应用到代码中。
一位小学老师想要一个游戏来测试 8 到 10 岁学生的乘法技能。
游戏必须有一个生命和一个积分系统,学生从3个生命开始,必须达到一定的积分才能获胜。 如果学生耗尽他/她的所有生命,程序必须显示“失败”信息。
游戏必须有两种模式,随机乘法和表格乘法。
第一个应该给学生一个从 1 到 10 的随机乘法,他/她必须正确回答才能赢得一分。 如果这没有发生,学生就会输掉一场比赛,比赛继续。 学生只有在她/他达到 5 分时才能获胜。
第二种模式必须显示一个从 1 到 10 的乘法表,学生必须在其中输入相应乘法的结果。 如果学生失败 3 次,他/她输了,但如果她/他完成了两张桌子,则游戏结束。
我知道需求可能会大一点,但我向您保证,我们将在本文中解决它们。
分而治之
编程中最重要的技能是解决问题。 这是因为在开始对代码进行黑客攻击之前,您需要有一个计划。
我总是建议把更大的问题分解成更小的问题,这样既容易又有效。
因此,如果您需要创建游戏,请先将其分解为最重要的部分。 这些子问题将更容易解决。
届时,您就可以清楚地了解如何执行所有内容并将其与代码集成。
因此,让我们绘制一张游戏图。

该图形建立了我们应用程序对象之间的关系。 如您所见,两个主要对象是Random multiplication和Table multiplication 。 他们唯一共享的是属性点和生命。
考虑到所有这些信息,让我们进入代码。
创建父游戏类
当我们使用面向对象编程时,我们会寻找最干净的方法来避免代码重复。 这称为 DRY(不要重复自己)。
注意:这个目标与编写更少的代码行无关(代码质量不能用那个方面来衡量),而是抽象最常用的逻辑。
按照前面的思路,我们应用的父类必须建立其他两个类的结构和期望的行为。
让我们看看它是如何完成的。
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} lives\n") def print_current_score(self): print(f"\nYour score is {self.points}") def print_description(self): print("\n\n" + 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 correctly\nYou 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 correct\n") # Adds a point self.points += 1 else: print("\nSorry, your answer is incorrect\n") # 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)子类的构造函数调用super函数,同时引用父类(BaseGame)。 它基本上是在告诉 Python:
将父类的“points_to_win”属性填入5!
没有必要仅仅因为我们在构造函数中调用 super 就将self放入super().__init__()部分,这会导致冗余。
我们还在 run 方法中使用了super函数,我们将看到那段代码中发生了什么。
# 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运行父方法的所有代码。
run 函数的另一部分非常简单。 它要求用户提供一个号码以及他/她必须响应的操作消息。 然后将结果与实际乘法进行比较,如果它们相等,则加一分,如果它们不减 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 correctly\nYou 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 方法。 这就是继承的神奇之处,我们把在多处使用的逻辑写一次,就忘掉了。
在 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 创建功能性应用
- 在 Python 类中使用 super 函数
- 应用继承的基本概念
- 实现类和实例属性
快乐编码
接下来,探索一些最好的 Python IDE,以提高生产力。
