Python-应用计算思维(二)

101 阅读1小时+

Python 应用计算思维(二)

原文:zh.annas-archive.org/md5/D0EC374B077B4C90AC07697977B27176

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章:探索问题分析

在这一章中,我们将深入探讨问题分析,同时运用我们所学的一些知识,比如逻辑推理、布尔逻辑和算法设计。在这一章中,我们将通过问题定义、分解和分析来解决问题。

在本章中,我们将涵盖以下主题:

  • 理解问题定义

  • 学习分解问题

  • 分析问题

为了进一步理解问题,我们需要看一个更复杂的问题,并定义它,以便开始算法设计过程。在本章中,您将学习如何定义问题并分解问题,以便设计算法。在这个过程中,您还将学习 Python 中的字典。阅读完本章后,您将能够使用计算思维过程来设计和创建解决复杂问题的算法。

技术要求

您需要最新版本的 Python 来运行本章的代码。您可以在此处找到本章使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter05

理解问题定义

正如我们在第二章中讨论的,计算思维的要素,计算思维使用四个要素来解决问题:

  • 问题分解:这是将数据分解的过程。

  • 模式识别:这是找到相似性或模式的过程。

  • 抽象:这个元素处理泛化模式。

  • 算法设计:这是我们为解决问题定义一组指令的地方。

在本节中,为了更多地了解如何分析问题,我们将分析一个更大的问题,并经过需要创建算法的步骤。为了能够创建算法,我们必须分析问题,并清楚地确定我们要解决什么问题。也就是说,我们的算法是为了什么? 为什么我们需要构建它? 查看问题的分解,然后定义我们需要的东西,将在最后为我们提供更好的算法。

我们将在下一节中解决一个问题。

问题 5A – 创建一个在线商店

让我们来看看以下问题。您正在开设一家在线商店。它还处于起步阶段,但您将有三种不同类型的商品可供选择。它们是钥匙扣、水瓶和 T 恤。对于这个特定的问题,我们将经历一个三步过程:

  1. 做出假设

  2. 需要考虑的事项

  3. 构建字典

我们将在接下来的部分中看到前面的步骤。

做出假设

让我陈述一些关于我们将要使用的这家商店的假设:

  • 这是一家为客户提供商品的公司,供客户与他们的客户分享。

  • 每个项目都可以有标志和/或个性化信息,比如姓名、电子邮件和电话号码。

现在我们将进入下一节,讨论需要考虑的事项。

需要考虑的事项

现在让我们在开始制定算法之前,看看您需要考虑的一些事项:

  • 商品是否个性化?

  • 个性化是否按字符、行或项目收费?

  • 价格是固定的,还是当客户批量购买时会发生变化?

  • 如果客户订购多种类型的商品,是否会有折扣?

  • 每个项目的基准价格是多少?

前面的观点并不是我们可以讨论的唯一问题。但当我们分解问题时,这些是我们将开始研究的问题。

在那之前,让我们讨论一下如何在程序中为每个项目包含信息。如果你还记得[第三章](B15413_03_Final_SK_ePub.xhtml#_idTextAnchor056),理解算法和算法思维,我们可以使用Python中的字典来保存我们的商品菜单。在这种情况下,我们有钥匙扣、水瓶和 T 恤。

建立一个字典

在我们看这个问题所提出的复杂性并分解信息之前,我们可以建立自己的字典。我们可以使字典中每个项目的价格都是基础价格(不包含任何定制或折扣的价格),如下所示:

  • 每个钥匙扣的成本:$0.75

  • T 恤成本:$8.50

  • 每个水瓶的成本:$10.00

现在让我们建立字典。记住,你可以在没有字典的情况下做到这一点,但是创建一个字典可以让你在以后有必要时更新定价。你也可以创建函数来解决这个问题。我们正在使用逻辑和字典来解决这个问题。以下代码向你展示了如何建立一个字典:

ch5_storeDictionary.py

online_store = {
    'keychain': 0.75,
    'tshirt': 8.50,
    'bottle': 10.00
    }
print(online_store)

从前面的代码片段中,请记住这里不需要print()函数,但我经常使用它来确保代码在我继续构建算法的同时能正常工作。还要注意变量的名称——keychaintshirtbottle——都是简化的。

这是输出的样子:

{'keychain': 0.75, 'tshirt': 8.5, 'bottle': 10.0}

输出显示给我的是每个变量的价格都被正确保存了。我使用print函数来测试我的字典,并确保它在我开始从字典中获取所需内容之前能够正确运行。

这有助于我们在编写代码时重复使用变量。拥有这些简单易识别的变量将允许我们在不添加错误的情况下更改和添加算法。

在本节中,我们了解到问题分析和定义帮助我们确定如何最好地设计我们的解决方案。记住,当我们面对问题时,我们在编写算法之前和算法中使用的定义对于我们的设计和最终产品至关重要。现在让我们来看看问题的分解。

学习分解问题

当我们分解问题时,我们正在确定算法需要为我们提供什么。最终用户需要看到一些无缝的东西。看看图 5.1中的流程图;这是一个基本的决策流程图,帮助我们设计我们的算法。

首先让我们做另一个假设,即,如果用户输入超过 10,价格将会更低。在这种情况下,我们只会做少于 10 或大于或等于 10。然而,如果你需要进一步细分,你可以添加更多情况,比如以下情况:

  • 少于或等于 10

  • 超过 10 但小于或等于 50

  • 50 或更多

你可以有尽可能多的情况。对于这个算法,我们将保持两种情况,因为我们还必须包括个性化成本,我们不想创建一个过于复杂的算法。

下图向你展示了算法的流程图:

图 5.1 – 初始决策流程图

图 5.1 – 初始决策流程图

正如你在前面的图表中所看到的,这不是一个完成的流程图。在我们做出关于 T 恤的决定之后,我们需要转向瓶子。我们如何编写算法将取决于我们想要输出什么。现在,我们提供给用户的是当他们结账离开你创建的在线商店时会得到的信息。

在下一节中,让我们使用前面的流程图来创建一个算法。

将流程图转换为算法

图 5.1中的图表使我们能够查看我们正在编写的算法的决策过程。在编写算法时,我们将要关注以下关键点:

  • 字典和输入:输入可以在算法内部或由用户输入;字典是在算法内部构建的。这意味着,要使用字典,我们必须在算法中定义它,然后才能使用它。

  • 成本:这是每件物品的基本成本。

  • 个性化成本:这是添加到基本成本中的成本。

我们现在将在接下来的部分详细查看前面的要点。

构建字典并提供输入

在添加任何复杂性之前,让我们看看如何获取每件物品的价格并将其用作基本价格。我们需要计算每种物品的数量。以下代码向您展示了这一点:

ch5_storeQuantities.py

online_store = {
    'keychain': 0.75,
    'tshirt': 8.50,
    'bottle': 10.00
    }
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
choicekey = int(input('How many keychains will you be purchasing? If not purchasing keychains, enter 0\. '))
choicetshirt = int(input('How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. '))
choicebottle = int(input('How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. '))
print('You are purchasing ' + str(choicekey) + ' keychains, ' + str(choicetshirt) + ' t-shirts, and ' + str(choicebottle) + ' water bottles.')

从前面的代码片段中可以看到,我们在字典下添加了变量。这将在以后很有用。这些变量被命名为choicekeychoicetshirtchoicebottle。命名变量使我们能够返回并根据需要更改代码。在这种情况下,每个变量都要求从运行程序的人那里获取输入,以获取他们订购的钥匙扣、T 恤和水瓶的数量。再次强调,解决这个问题有多种方法,但我们正在利用到目前为止学到的知识来创建一个算法解决方案。

当我们对3个钥匙扣、0件 T 恤和10个水瓶运行前面的代码时,这是我们的输出:

How many keychains will you be purchasing? If not purchasing keychains, enter 0\. 3
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. 0
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. 10
You are purchasing 3 keychains, 0 t-shirts, and 10 water bottles.

正如您所看到的,我们有一个接受用户输入的程序,然后向用户确认他们为每件物品所做的选择。

让我们看看关于成本的下一节。

对成本进行更改

现在让我们添加成本的变化。假设如果顾客购买超过 10 件物品,那么更新后的成本如下:

  • 钥匙扣:$0.65

  • T 恤:$8.00

  • 水瓶:$8.75

为了进行前述更改,我们可以让程序更新成本差异,如下所示:

ch5_storeCost.py

online_store = {
    'keychain': 0.75,
    'tshirt': 8.50,
    'bottle': 10.00
    }
choicekey = int(input('How many keychains will you be purchasing? If not purchasing keychains, enter 0\. '))
choicetshirt = int(input('How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. '))
choicebottle = int(input('How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. '))
print('You are purchasing ' + str(choicekey) + ' keychains, ' + str(choicetshirt) + ' t-shirts, and ' + str(choicebottle) + ' water bottles.')
if choicekey > 9:
    online_store['keychain'] = 0.65
if choicetshirt > 9:
    online_store['tshirt'] = 8.00
if choicebottle > 9:
    online_store['bottle'] = 8.75
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
print(online_store)

现在我们已经更新了代码,我想打印出我的进展,以确保代码正常工作并进行更改。在这种情况下,我想确保如果总数大于 10,成本会更新。(也就是说,当顾客订购超过 10 件物品时,它会将每件物品的成本更新为更低的成本。)前面代码的输出如下:

How many keychains will you be purchasing? If not purchasing keychains, enter 0\. 10
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. 14
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. 10
You are purchasing 10 keychains, 14 t-shirts, and 10 water bottles.
{'keychain': 0.65, 'tshirt': 8.0, 'bottle': 8.75}

您现在可以从前面的输出中看到,字典已根据用户提供的总数进行了更新。

现在我们需要继续并提供成本。我们可以提供总物品成本或整个购买的总成本,或两者(让我们两者都做)。看一下以下代码片段:

ch5_storeTotals.py

keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
print('You are purchasing ' + str(choicekey) + ' keychains, ' + str(choicetshirt) + ' t-shirts, and ' + str(choicebottle) + ' water bottles.')

前面的代码片段被添加,以便我们可以有一个print语句来确认用户的输入。通过在代码末尾打印该语句,我们正在与用户核对程序是否能正确读取数字,以及用户是否输入了正确的数字。

我们可以继续编写代码以添加每件物品的成本:

ch5_storeTotals.py

totalkey = choicekey * keychain
totaltshirt = choicetshirt * tshirt
totalbottle = choicebottle * bottle
grandtotal = totalkey + totaltshirt + totalbottle
print('Keychain total: $' + str(totalkey))
print('T-shirt total: $' + str(totaltshirt))
print('Water bottle total: $' + str(totalbottle))
print('Your order total: $' + str(grandtotal))

在前面的代码片段的末尾的print语句提供了每件物品的总成本以及整个订单的总成本的细目。在询问所有物品的输入后,代码然后打印每件物品成本的小计。前面代码的结果如下:

How many keychains will you be purchasing? If not purchasing keychains, enter 0\. 10
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. 7
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. 14
You are purchasing 10 keychains, 7 t-shirts, and 14 water bottles.
Keychain total: $6.5
T-shirt total: $59.5
Water bottle total: $122.5
Your order total: $188.5

现在我们已经有了没有个性化的物品总数,我们需要考虑如果订购了个性化,需要考虑个性化的成本。

在下一节中,让我们看看个性化成本是多少,以及在继续之前我们需要做出的决定。

添加个性化

目前,让我们将钥匙扣、T 恤和水瓶的个性化限制为二进制问题,即用户要么想要个性化,要么不想要。我们不考虑个性化的分层成本,这可能是您见过的。如果您想要添加分层,您需要做出更多决策,比如选择字体的成本,个性化的长度等等。目前我们不考虑这些,但随时可以添加到这段代码中以解决这些类型的自定义。让我们为个性化添加另一个假设:

  • 1 美元的钥匙扣

  • T 恤 5 美元

  • 水瓶 7.50 美元

我们需要创建前述条件,然后将它们实施到我们的变量中。让我们逐部分查看代码。以下文件包含我们将分解的每个部分。

回想一下,我们的算法首先要求输入购买物品的数量。以下代码片段获取用户输入,以便考虑个性化:

ch5_storePersonalize.py

perskey = input('Will you personalize the keychains for an additional $1.00 each? Type yes or no. ')
perstshirt = input('Will you personalize the t-shirts for an additional $5.00 each? Type yes or no. ')
persbottle = input('Will you personalize the water bottles for an additional $7.50 each? Type yes or no. ')
if perskey == ('yes' or 'Yes'):
    online_store['keychain'] = online_store['keychain'] + 1.00
if perstshirt == ('yes' or 'Yes'):
    online_store['tshirt'] = online_store['tshirt'] + 5.00
if persbottle == ('yes' or 'Yes'):
    online_store['bottle'] = online_store['bottle'] + 7.50
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
totalkey = choicekey * keychain
totaltshirt = choicetshirt * tshirt
totalbottle = choicebottle * bottle
grandtotal = totalkey + totaltshirt + totalbottle

前面的代码片段询问用户关于个性化的二进制问题。在获取输入后,代码根据用户输入做出一些决策,并定义keychaintshirtbottle变量以及选择的总数。接下来的代码片段使用总数打印出每个购买物品的信息以及最终总数:

print('Keychain total: $' + str(totalkey))
print('T-shirt total: $' + str(totaltshirt))
print('Water bottle total: $' + str(totalbottle))
print('Your order total: $' + str(grandtotal))

从前面的代码中可以注意到,keychaintshirtbottle变量是在基于总数和个性化的自定义之后定义的。记住,在算法设计中,顺序很重要。如果我们在程序中较早地定位这些变量,随后的个性化等条件将不会影响这些变量。

因此,为了能够获得我们变量所需的一切,我们需要在定义一些影响它们的条件之后定义它们,比如自定义。看一下前面的代码,注意变量的定义位置。随时可以通过更改变量定义的位置来玩弄代码,看看最终结果是否会改变。

这是一个具有钥匙扣决策过程的视觉流程图:

图 5.2 - 钥匙扣决策流程

图 5.2 - 钥匙扣决策流程

正如您从前面的图表中所看到的,这仅适用于钥匙扣。我们需要为另外两个变量重复这个过程。在图表中,您可以看到物品的决策过程。首先,用户指示购买的物品数量,然后指示他们是否会个性化。

根据每个答案,程序计算总数。例如,如果没有个性化,总数会在决策树中更早地计算出来。我们可以使用函数(如我之前提到的)重写这个程序,以简化一些过程。目前,我们专注于学习如何分解问题,分析条件,以及如何设计算法以考虑多个决策。记得完成其他物品的图表,以便在设计算法时决策过程更容易编码。

在本节中,我们学习了如何使用流程图创建算法。我们还学习了为我们的算法构建字典。

在我们继续之前,让我们看看分析问题的过程。在创建这个算法时,我们分解了问题。然而,在我们下一章之前,有一些问题分析的关键组成部分值得我们考虑。

分析问题

在分析问题时,有一些步骤可以帮助我们确保我们正在创建最佳算法:

  • 清楚地阅读和理解问题。

  • 确定解决方案的主要目的。

  • 确定问题的约束。

  • 确定决策流程。

  • 建立可能解决问题的算法。

  • 为问题确定最佳的算法工具。

  • 经常测试算法片段。

  • 验证算法是否提供了已确定问题的解决方案。

如果我们回到我们的问题,我们在整个章节中都经历了这个过程:

  • 我们有一个有三种商品的在线商店。

  • 商品成本取决于购买数量。

  • 商品价格也取决于个性化定制。

  • 我们创建了流程图来帮助我们确定决策过程以及如何编写代码。

  • 我们通过代码行验证了我们的代码,这些代码行允许我们多次检查算法是否产生了正确的响应。

  • 根据需要重新访问和重新排序代码片段。

  • 我们验证了算法的输出是否符合我们所确定的问题。

前面的过程需要重复,也就是说,这不是一个线性过程。有时我们会编写一个算法,然后重新访问决策流程图,进行调整,然后再次处理算法。

在处理更大的问题时,分析我们的问题在多个停止点变得更加清晰。我们应该在测试之前写上几百行代码吗? 不!想象一下有 300 行代码,只在第 20 行发现一个错误,这个错误会一直传递到算法的其余部分。

在每个可能的进度点进行测试将使我们能够发现长期成本的小错误。记住,第一次几乎不可能编写完美的算法。我们都会犯错误,无论大小,所以继续测试和分析我们的进展非常重要。

在离开本章之前,让我们再看一个问题并再次经历这个过程。

问题 5B - 分析一个简单的游戏问题

你想设计一个猜数字游戏。用户必须猜一个随机数。

让我们从定义我们的问题开始,这种情况下是一个游戏。让我们确定已知信息:

  • 计算机需要随机选择一个数字。

  • 用户需要输入一个数字。

  • 计算机将不得不检查用户输入是否与随机生成的数字匹配。

现在,这还不够!如果我第一次猜不中,我会输吗?我有多少次机会?随机数是 1 到 10 之间还是 1 到 500 之间? 在我们开始编码之前,我们需要做一些决定。让我们添加一些参数:

  • 数字在 1 到 100 之间。

  • 用户将有 5 次猜测的机会。

  • 计算机会告诉用户答案是太高还是太低。

现在我们有了这些参数,我们可以创建一个决策流程图:

图 5.3 - 猜数字游戏的决策流程图

图 5.3 - 猜数字游戏的决策流程图

从前面的图表中可以看出,图表并不完整。这是因为我们将使用一些逻辑使过程重复 5 次。我们稍后会详细介绍。现在,请注意决策。首先,程序生成一个数字(但不会显示)。然后用户输入一个猜测,要么正确,要么错误。如果正确,用户就赢得了游戏。如果不正确,程序会告诉用户答案是太低还是太高,并要求新的猜测。然后过程将根据需要重复。现在,让我们编写算法。

首先,让我们生成随机数并让用户猜测。为随机生成的数字和用户输入添加print()函数,以便您可以看到信息是否正常工作。记住,我们以后会把它们拿掉,但是反复检查我们的代码作为问题分析过程的一部分非常重要。以下代码将执行相同的操作:

ch5_guess1.py

import random as rand
compnumber = rand.randint(1, 100)
print(compnumber)
usernumber = int(input('Choose a number between 1 and 100\. You'll get 5 guesses or you lose! '))
print(usernumber)

你会注意到前面的代码中导入了random模块。我们还将其导入为rand。这只是为了节省时间和空间。在 Python 中,当你导入一个模块时,你可以给它重命名。random模块给了我们一个在我们选择的范围内生成数字的方法。

rand.randint(1, 100)代码行包括1100。这些是随机数生成器的端点或限制。rand函数是指模块,如前所述,而randint(a, b)指的是ab之间的随机整数(包括ab)。

多次运行代码,看看计算机生成的数字每次都会发生变化。以下是三个测试案例:

  • 以下是前面代码的测试案例 1:
27
Choose a number between 1 and 100\. You'll get 5 guesses or you lose! 10
10

如你从前面的输出中看到的,27是计算机生成的随机数,而10是用户输入的数字。

  • 以下是前面代码的测试案例 2 的结果:
68
Choose a number between 1 and 100\. You'll get 5 guesses or you lose! 65
65

如你从前面代码的输出中看到的,68compnumber变量的值,而用户(我)输入的数字是65。如此接近,但又如此遥远!

  • 以下是测试案例 3 的输出:
50
Choose a number between 1 and 100\. You'll get 5 guesses or you lose! 23
23

正如你从前面的输出中看到的,计算机选择了数字50,而用户输入了23

对于我们游戏的最终版本,我们不会打印出计算机的数字。那是作弊!现在,我们只是在测试。

让我们再添加一个条件,即第一次猜测是否正确。为此,我们将必须验证compnumber == usernumber。在进入额外的重复和逻辑之前,我们将再次测试这一点,所以我们只需说如果是真的,那么你赢了;如果是假的,那么你输了:

ch5_guess2.py

import random as rand
compnumber = rand.randint(1, 100)
usernumber = int(input('Choose a number between 1 and 100\. You'll get 5 guesses or you lose! '))
if compnumber == usernumber:
    print('You win!')
else:
    print('You lose!')

让我们说我在第一次尝试时失败了。但是我不会放弃,因为这可能需要 100 次或更多次。当你运行程序时,它看起来像这样:

Choose a number between 1 and 100\. You'll get 5 guesses or you lose! 35
You lose!

现在让我们谈谈重复一行代码。我们给用户 5 次猜测。我们如何在 Python 中做到这一点?

在 Python 中,我们可以使用for循环来迭代代码。我们知道我们有 5 次猜测,所以我们将使用类似for number in range(5):这样的东西来开始我们的逻辑,如下面的代码所示:

ch5_guess3.py

import random as rand
compnumber = rand.randint(1, 100)
i = 5
for number in range(5):
    usernumber = int(input('Choose a number between 1 and 100\. You have ' + str(i) + ' guesses left. '))
    if compnumber == usernumber:
        print('You win!')
    else:
        i = i - 1
print('You're out of guesses! You lose! ')

从前面的代码中,你注意到i*变量了吗?*我们使用这个变量,这样用户就知道他们还剩下多少次猜测。所以如果我们有 5 次猜测,代码将从i = 5开始;然后,如果用户猜错了,它将使用i = i - 1这一行,提醒用户他们现在还剩下 4 次猜测,依此类推。看看我们运行该程序时会发生什么:

Choose a number between 1 and 100\. You have 5 guesses left. 14
Choose a number between 1 and 100\. You have 4 guesses left. 98
Choose a number between 1 and 100\. You have 3 guesses left. 48
Choose a number between 1 and 100\. You have 2 guesses left. 12
Choose a number between 1 and 100\. You have 1 guesses left. 54
You're out of guesses! You lose!

现在,我们并不是真的很公平。如前所述,我们希望每次用户尝试猜测时都给出一个提示。现在我们有了检查它们是否相等的条件,我们可以添加一个elif条件来检查它是大还是小。下面的代码显示了这一点:

ch5_guess4.py

import random as rand
compnumber = rand.randint(1, 100)
i = 5
for number in range(5):
    usernumber = int(input('Choose a number between 1 and 100\. You have ' + str(i) + ' guesses left. '))
    if compnumber == usernumber:
        print('You win!')
        exit()
    elif compnumber > usernumber:
        print('Your number is too small!')
        i = i - 1
    elif compnumber < usernumber:
        print('Your number is too large!')
        i = i - 1
print('You're out of guesses! You lose! ')

前面的代码现在为用户提供了一些反馈。如果数字大于计算机生成的数字,用户会收到反馈'你的数字太大了!',如果用户的数字小于计算机生成的数字,那么他们会收到反馈'你的数字太小了!'。如果用户赢了,我们还使用了exit()代码。这是因为我们希望游戏在我们赢了时停止。

这给了我们一个战胜这个游戏的机会,看看现在的输出是什么样子:

Choose a number between 1 and 100\. You have 5 guesses left. 50
Your number is too small!
Choose a number between 1 and 100\. You have 4 guesses left. 75
Your number is too large!
Choose a number between 1 and 100\. You have 3 guesses left. 65
Your number is too small!
Choose a number between 1 and 100\. You have 2 guesses left. 70
Your number is too large!
Choose a number between 1 and 100\. You have 1 guesses left. 68
You win!

现在看看当我们输掉游戏时会发生什么:

Choose a number between 1 and 100\. You have 5 guesses left. 10
Your number is too small!
Choose a number between 1 and 100\. You have 4 guesses left. 40
Your number is too large!
Choose a number between 1 and 100\. You have 3 guesses left. 20
Your number is too small!
Choose a number between 1 and 100\. You have 2 guesses left. 30
Your number is too small!
Choose a number between 1 and 100\. You have 1 guesses left. 35
Your number is too large!
You're out of guesses! You lose!

正如你所看到的,你得到了一个不同的最终消息。我承认我尝试了很多次才赢得了一场比赛,所以我才得到了下面的输出,但你可以看到第二次猜测是正确的游戏:

Choose a number between 1 and 100\. You have 5 guesses left. 10
Your number is too small!
Choose a number between 1 and 100\. You have 4 guesses left. 90
You win!

我们将用最后一个算法来停止这个游戏。如果我们愿意,我们实际上可以让这个游戏变得更好,但它完成了我们需要它完成的工作。你可以考虑对你的游戏进行一些改变,如下所示:

  • 添加一个选项,提醒用户已经猜过的数字。

  • 增加一个选项,提醒用户忽略了先前的提示(所以如果用户给出一个太小的数字,然后给出一个更小的数字,电脑会提醒他们)。

我相信你可以尝试更多的定制。但现在,我们经历了那个问题,并遵循了我们在分析问题时应该考虑的要点:

  1. 我们阅读并理解了问题。

  2. 我们确定了目的——创建一个电脑玩家对用户玩家的猜数字游戏。

  3. 我们确定了问题的约束条件——数字范围、猜测次数和提供提示。

  4. 我们创建了一个决策流程图。

  5. 我们编写并建立了解决问题的算法。

  6. 我们看了如何创建一个简单的算法,可以迭代而不必为每个条件单独编写。

  7. 我们在多个点测试了算法。

  8. 我们验证了算法对于胜利和失败的运行是否准确。

在这里你看不到的是我在展示算法之前经历了多少错误。在写作过程中,我不得不使用前面的步骤来帮助我识别错误,检查最佳算法,并迭代程序。这是一个我们将继续使用的过程。

总结

在本章中,我们讨论了问题定义、分解和分析。我们使用问题来帮助我们经历识别问题、将其分解为相关部分和确定约束条件的过程,并分析我们的算法。我们使用流程图来帮助我们学习在设计算法时的决策过程以及如何组织思路。

我们学会了经常测试我们的算法。这为我们提供了识别错误的技能和理解,而不是等到我们有太多行代码时再等待。我们使用了一个在线商店和一个猜数字游戏来帮助我们了解 Python 中的一些功能。在整个过程中,我们使用布尔代码来验证输入,我们使用嵌套的if语句,我们学会了如何在解决所提出的问题时使用字典。

此外,我们有机会使用字典来处理使用用户输入和变量的算法。使用该算法使我们能够灵活定义一些变量,并在运行算法时或在算法内部编辑变量。

在下一章中,我们将深入探讨解决方案的过程和设计,更深入地研究更复杂的问题和 Python 语言。

第六章:设计解决方案和解决方案过程

在本章中,我们将使用先前学到的内容,如问题的分析和计算思维过程,来设计解决方案。我们将结合逻辑处理来创建决策过程的视觉表示,以指导我们的算法设计。讨论的视觉表示包括图表流程图和其他有用的过程。在本章中,我们将学习解决方案设计的关键要素;如何创建、使用和应用图表来处理和设计解决方案;我们将看看如何将解决方案设计过程应用于各种问题。

在本章中,我们将涵盖以下主题:

  • 设计解决方案

  • 绘制解决方案

  • 创建解决方案

为了进一步了解算法和解决方案设计,我们需要更仔细地研究问题的前端。我们将从深入讨论设计解决方案的过程开始。

技术要求

您将需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter06

设计解决方案

当我们设计解决方案时,我们经常使用设计思维模型,即使我们并不总是意识到。设计思维由不同的模型描述,但我们将看到最常见的五步模型。

与计算思维结合,设计思维过程可以帮助我们在开始绘制解决方案之前发展我们的想法。需要注意的是,我们不像在计算思维中那样线性地进行设计思维过程。想想计算思维的步骤:

  • 问题分解

  • 模式识别

  • 抽象

  • 算法设计

我们在之前的章节中定义了所有这些步骤,最近是在第五章的介绍中,探索问题分析。再次看到它们,我们知道在编写和设计算法时可以返回到分解。这就是我们所说的非线性过程。

设计思维模型也是如此。它是由斯坦福大学哈索-普拉特纳设计学院设计的。该模型的主要步骤包括以下内容:

  • 共情:从受众或利益相关者的角度理解问题。

  • 定义:确定目标、需要做出的决策、引入的任何偏见以及与问题相关的任何细节。

  • 构思:进行头脑风暴,与我们将在本章的下一节中进行的图表化工作相呼应。

  • 原型:设计算法解决方案并经常检查它。

  • 测试:在整个过程中经常检查您的算法,并根据需要返回到以前的步骤。

正如您所看到的,我已经调整了设计思维模型,使其更符合计算思维过程。当我们使用这些模型并将它们结合在一起时,主要目标是将更难的问题分解为更简单的部分,以便我们可以设计和解决最佳算法。这并不取代计算思维。它只是提供了更好的想法,让我们了解如何处理这个过程。以下图表可以帮助演示这个过程可能如何工作:

图 6.1 - 设计思维模型

图 6.1 - 设计思维模型

正如您所看到的,与最常见的线性模型不同,前面的模型将过程显示为循环。也就是说,回到共情可以发生在任何时候,因此在这些步骤之间来回移动是使用设计思维模型的最佳方式。

让我们看一个场景,在这个场景中,我们将设计思维与计算思维结合使用。

问题 1-营销调查

假设您正在与一家营销公司合作,他们要求您设计一份调查,以收集有关网站的反馈。以下是您可能要经历的一些步骤:

  1. 确定利益相关者:这包括您将调查的人员以及调查后将使用信息的人员,例如。

  2. 确定问题:这是您定义希望从调查中获得的信息的地方。

  3. 设计调查:这不仅包括您确定的问题,还包括调查的美学外观。

  4. 信息收集:这是您决定如何与将填写调查的人沟通的地方,例如电子邮件、网站上的链接或类似方式。

  5. 数据分析:您可以编写 Python 算法来帮助您进行数据分析,包括根据收集的数据创建表格和图表。

  6. 数据共享:这是您将计划向最初的利益相关者展示的可视化、报告和数据呈现的地方。

让我们明确一点:这是对过程的过度简化。但是假设您意识到需要为调查增加另一组。比如,最初只从学校的学生那里获得反馈,但后来意识到您想要增加教师和家长。那么,您将回到步骤 1,确定您的其他信息将受到哪些影响。您可能想要改变调查的外观,或者为成年人和儿童添加不同的调查。您可能需要添加只针对某一组的问题,这会影响您在调查算法中的决策。

现在让我们看看设计思维过程中的这些步骤。

对于我们的问题,确定利益相关者和问题是设计思维模型的步骤 123的一部分:共情定义构思。构建算法既是原型测试的一部分,也是步骤 45。将人员添加到调查中会让我们回到步骤 1-3。循环重复,直到我们为我们的情景拥有一个可行的算法。在整个计算思维模型中,并使用其中的元素,您将使用嵌入其中的设计思维过程。这是决策过程的自然部分。

现在我们已经看过设计思维模型,让我们来看看如何使用图解解决方案来直观地表示决策过程。

图解解决方案

当我们设计算法时,我们经常使用图表和流程图来帮助我们分析过程,并直观地看到我们的决策是在哪里做出的。这些图表使我们能够创建更好的算法。您会记得,在第五章中,当我们正在建立一个商店时,我们创建了一个流程图(图 5.1图 5.2)。

创建这些图表的过程因开发人员或编码人员而异。例如,我通常会为问题创建头脑风暴,然后从中制作流程图。为了了解这个过程,让我们回到本章前面的调查问题。看看以下头脑风暴。它并不完整,因为您可以添加许多子主题。这个头脑风暴假设我们正在调查利益相关者,以评估和分享对学校网站的反馈。

图 6.2 -头脑风暴图

图 6.2 -头脑风暴图

从图表中可以看出,有许多考虑要做。实际的调查设计可能由我们作为程序员提供,或者我们可能参与设计调查。如果我们有了调查,我们的头脑风暴可能会有所不同,因为我们在问题中导航,并决定如何最好地将它们放置在算法中。这是共情过程的一部分。我们从多个角度、多个利益相关者的角度来看待我们的信息,并决定如何编写算法来帮助我们达到我们的目标。头脑风暴这样的非正式图表的目的是让我们在尝试创建更详细和有组织的流程图之前开始组织想法。当我们工作在图表上时,我们正在定义和构思我们的算法。这就是为什么在开始直接编码之前勾画出我们的计划是很重要的。

关于流程图,当我们讨论在 Python 中创建商店时,上一章中我们看到了一些流程图。现在让我们看一下基于一些决策的决策流程图。

重要的是要注意,从零开始创建调查可能是困难的。部分原因是可能有依赖于彼此的问题。例如,假设你要求用户说明他们是否赞成颜色选择。如果他们赞成,你可以继续。但如果他们不赞成,你可能想提供其他颜色方案供审查。这个问题只会出现在选择选项的人身上。如果我们要处理头脑风暴的所有信息,我们的流程图可能会相当复杂,所以我们将专注于头脑风暴的外观类别中的一些问题。看一下下面的流程图:

图 6.3 - 调查的一个元素的流程图

图 6.3 - 调查的一个元素的流程图

从流程图中可以看出,有些事情并不清晰,比如完成一个问题后会发生什么,每个决定后你会去哪里等等。当我创建流程图时,有时会添加箭头来帮助我看到每个步骤之后会发生什么。下面的流程图显示了添加的一些箭头:

图 6.4 - 带箭头的流程图

图 6.4 - 带箭头的流程图

从前面的流程图可以看出,并非所有箭头都被添加,但要仔细看颜色方案。如果用户同意颜色方案,那么他们直接进入字体部分。如果他们不同意,他们会被显示选项。假设一次只显示一个选项,那么用户在选择喜欢的选项后会进入字体部分。还可以添加提示,询问用户是否想再次查看选项,这会将他们带回备选方案 1。箭头可以被添加以显示这些细节。

这完全取决于作为开发人员和程序员的你自己最容易理解的方式。如果你是作家,把这些当作你的日记笔记。你组织想法的方式可能是个人的,只要确保你的最终结果和算法可以被意图使用的人轻松使用。

现在让我们看看如何将所有内容整合在一起,并为一些问题设计解决方案。

创建解决方案

当我们面对问题时,我们希望创建解决方案,解决我们所提供的信息,并提供一切所需并且易于用户理解的算法。在本节中,我们将利用本章学到的内容来设计解决问题的解决方案。

当我们使用我们的头脑风暴和流程图创建这些解决方案时,我们应该考虑以下内容:

  • 我们计划的解决方案是否解决了问题?

  • 解决方案设计是否显示了算法成功的清晰路径?

如果对这些问题的答案是“是”,那么我们可以开始编写解决方案。请记住,我们需要尽可能经常地测试算法。在编写算法时,请记住以下几点:

  • 添加注释以标识您可能需要返回的部分,并清楚地帮助识别和定义您的变量、字典、函数和任何关键组件。

  • 检查一下您是否有任何错误,比如第五章**中讨论的那些错误,探索问题分析。

  • 尽可能经常运行您的程序以测试错误。

对于解决方案过程,我们将使用一个与本章早期工作的调查略有不同的问题。随着我们在本书中的学习,我们将解决您可以用于该问题的组件,比如添加图像、显示图形等。但是现在,让我们继续使用一些更基本的 Python 来练习创建解决方案的过程。

问题 2 - 比萨订单

我知道 - 食物。但这是演示逻辑和算法创建的最佳方式之一,所以请耐心等待。假设我们有一家比萨店。我们只卖一种类型的面团,因为我们是一种特色的地方。我们有两种不同尺寸的比萨:个人和家庭。有两种酱料选项:番茄酱和大蒜奶油。有三种奶酪选项:无奶酪、普通奶酪和额外奶酪。

选择的五种配料有:蘑菇、意大利辣香肠、洋葱和辣椒。不,我们的比萨店不会放橄榄。

让我们解决这个问题。我们希望有一个算法来捕捉用户选择的比萨订单选项。我们现在不考虑成本和订单中的其他项目,比如额外的比萨、饮料、甜点等。

这是我们知道的:

  • 尺寸:个人或家庭

  • 酱料:番茄酱或大蒜奶油

  • 奶酪:无奶酪、普通奶酪、额外奶酪

  • 配料:蘑菇、意大利辣香肠、洋葱、辣椒

现在我们有了这些,让我们看一下带有信息的流程图:

图 6.5 - 比萨店决策流程图

图 6.5 - 比萨店决策流程图

正如您所看到的,该图显示了这个特定问题的一个相当线性决策过程。我们还没有考虑的一件事是询问用户是否希望进行任何更改。这可能需要在每个步骤中发生。比如在选择奶酪时改变主意,改为选择番茄酱而不是大蒜奶油酱。您需要有一种方法可以返回,因此我们需要在创建算法时牢记这一点。

请记住,我们目前仅使用文本代码,因此我们现在将使用用户的数字和字母输入。但是,有办法将 Python 整合到更强大的算法中,这些算法包括图像、按钮等。

看一下算法中的以下代码片段:

ch6_pizzeria.py

#Get input for your variables for size and sauce first. 
size_choice = str(input("Is this a personal or family pizza? Type personal or family. "))
sauce_choice = str(input("Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. "))
if sauce_choice == "g":
    sauce = "garlic cream"
else:
    sauce = "marinara"
#The cheese choice will dictate a few more options. Define the variable first.                
cheese_choice = str(input("Would you like cheese on your pizza? Type y for yes and n for no. "))

请注意,在代码片段中,我们首先定义了尺寸和酱料。我在这里重申,有其他处理这个特定逻辑过程的方法。例如,我们可以将一些变量保存到字典中,并使用数组。目前,我们正在使用到目前为止学到的知识来创建我们的算法,但是在本书的后面,我们将有机会了解其他方法。

前面的代码片段有最终的奶酪选择。无论选择哪个选项,我们都需要对配料做出决定。这将需要发生两次,因为我们需要为“是”和“否”都需要。

看一下以下代码片段,这是前面代码的延续:

ch6_Pizzeria.py

#Toppings need to happen whether or not you want cheese. 
if cheese_choice == "y":
    cheese2_choice = str(input("Would you like regular cheese or extra cheese? Type r for regular and e for extra cheese. "))
    if cheese2_choice == "r":
        cheese = "regular cheese"
    else:
        cheese = "extra cheese"
    toppings1_input = str(input("Would you like mushrooms on your pizza? Type y for yes and n for no. "))
    if toppings1_input == "y":
        toppings1 = "mushrooms"
    else:
        toppings1 = "no mushrooms"
else:
    cheese = "no cheese"    
if cheese_choice == "n":
    toppings1_input = str(input("Would you like mushrooms on your pizza? Type y for yes and n for no. "))
    if toppings1_input == "y":
        toppings1 = "mushrooms"
    else:
        toppings1 = "no mushrooms"
print("You want a " + size_choice + " pizza with " + sauce + " sauce, " + cheese + ", and " + toppings1 + ".")

正如您从代码片段中看到的,我们只使用了蘑菇。在选择家庭尺寸、大蒜酱、普通奶酪和蘑菇后,这个特定代码的输出如下:

Is this a personal or family pizza? Type personal or family. family
Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. g
Would you like cheese on your pizza? Type y for yes and n for no. y
Would you like regular cheese or extra cheese? Type r for regular and e for extra cheese. r
Would you like mushrooms on your pizza? Type y for yes and n for no. y
You want a family pizza with garlic cream sauce, regular cheese, and mushrooms.

使用提供的代码并查看输出,尝试组合其余四种配料的代码。我猜,如果您正在制作自己的比萨饼,欢迎您更改这里提供的选项。只需将橄榄留给自己。

现在,如前所述,我们可能需要返回并进行更改。让我们看一段为您做到这一点的代码片段:

ch6_Pizzeria2.py

ready_end = str(input("Do you need to make any changes? Type y for yes and n for no. "))
if ready_end == "y":
    size_choice = str(input("Is this a personal or family pizza? Type personal or family. "))
    sauce_choice = str(input("Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. "))
    if sauce_choice == "g":
        sauce = "garlic cream"
    else:
        sauce = "marinara"

    cheese_choice = str(input("Would you like cheese on your pizza? Type y for yes and n for no. "))

从代码片段中可以看出,需要对所需的更改进行决策。如果是,则再次提出问题。如果不是,则为用户打印选项。查看完整运行程序的以下输出:

Is this a personal or family pizza? Type personal or family. family
Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. g
Would you like cheese on your pizza? Type y for yes and n for no. n
Would you like mushrooms on your pizza? Type y for yes and n for no. y
Do you need to make any changes? Type y for yes and n for no. y
Is this a personal or family pizza? Type 1 for personal and 2 for family. family
Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. m
Would you like cheese on your pizza? Type y for yes and n for no. n
Would you like mushrooms on your pizza? Type y for yes and n for no. y
You want a family pizza with marinara sauce, no cheese, and mushrooms.

如代码所示,问题被问了两次,因为我们在选项中做了更改。根据您想要问这个问题的频率,您需要继续重复部分代码。有简化的方法,我们将在 Python 语言程序章节(第八章,Python 简介)和后续章节中更深入地讨论这些选项。

在我们继续之前,让我们再看一个问题,重新经历一遍设计过程。

问题 3 - 延迟和 Python

我在 Python 中遇到的第一个问题之一是创建一个根据所选颜色而有不同反应的算法。这类似于如果您正在创建交通灯时会遇到的情况。每个灯的延迟都不同。所以让我们创建一个解决这个问题的算法。我们将使其成为用户选择的颜色,介于绿色、黄色和红色之间,只是为了保持交通灯的主题。因此,让我们做一些假设:

  • 绿色将意味着 5 秒的延迟

  • 黄色将意味着 2 秒的延迟

  • 红色将意味着 4 秒的延迟

这些特定延迟没有特定的原因;我只是想让它们都在 5 秒以内。现在,假设我们正在玩一个游戏,用户必须选择一种颜色。如果他们选择黄色或红色,他们将会有延迟,然后会再次被问及。目标是从程序中获得“您赢了!您现在可以走了”的消息。因此,让我们为此创建一个流程图:

图 6.6 - 交通灯游戏的流程图

图 6.6 - 交通灯游戏的流程图

从流程图中可以看出,如果选择黄色或红色,游戏将重新开始。现在我们已经了解了游戏的基本情况,我们需要编写代码。

重要提示:

为了能够使用延迟,我们需要导入time库。使用代码import time来做到这一点。要包含延迟,我们使用代码time.sleep()

让我们看一段代码片段:

ch6_sleep.py

import time
print("Let's play a game. Choose a color to learn your destiny. Choose wisely or you'll have to start over. ")
i = 0
while i < 4:
    color = str(input("Choose a color: red, green, or yellow. "))
    if color == "green":
        print("You must wait 5 seconds to learn your fate.")
        time.sleep(5)
        print("You win! Excellent choice!")
        break
    elif color == "yellow":
        print("You must wait 2 seconds to learn your fate.")
        time.sleep(2)
        print("You lose! You must start over.")
        i = i + 1
    else:
        print("You must wait 4 seconds to learn your fate.")
        time.sleep(4)
        print("You lose! You must start over.")
        i = i + 1

如您所见,该算法包含了我们在之前章节中讨论循环、布尔语句等时看到的一些代码。如果用户没有赢得游戏,这段特定代码将在三轮后返回到开头。我们使用if-elif-else语句来处理颜色情况。游戏进行三轮的输出如下:

Let's play a game. Choose a color to learn your destiny. Choose wisely or you'll have to start over. 
Choose a color: red, green, or yellow. yellow
You must wait 2 seconds to learn your fate.
You lose! You must start over.
Choose a color: red, green, or yellow. red
You must wait 4 seconds to learn your fate.
You lose! You must start over.
Choose a color: red, green, or yellow. green
You must wait 5 seconds to learn your fate.
You win! Excellent choice!

从游戏输出中可以看出,所有三轮都已经玩过了。每个延迟都按照陈述发生,您需要自己测试,因为我无法用文本显示时间延迟。

制作流程图使得创建这个算法比我一开始读问题后立即编写代码更简单。在编写算法之前,习惯于详细阐述您需要的过程非常重要。设计解决方案可能是一个漫长而乏味的过程,但我们在开始时越有条理,我们的算法就会越好。

总结

在本章中,我们讨论了如何设计、绘制和创建解决问题的解决方案。我们讨论了设计思维的非线性过程,以了解如何最好地设计解决方案。设计思维模型是一个五步过程:共情定义构思原型测试。在计算思维过程中使用这个五步过程可以帮助我们避免许多问题和陷阱。

我们还创建了头脑风暴和流程图,以建立我们的算法决策过程来解决问题。

在下一章中,我们将利用我们对算法设计和解决方案设计的知识,来识别解决方案中的挑战并调试程序。

第七章:识别解决方案中的挑战

在本章中,我们将评估算法和图表,同时学习如何避免一些常见错误,并确定是否可以对现有算法进行可能的调整以简化它。我们将根据问题描述评估解决方案,以验证解决方案是否与问题一致。我们将学习如何识别解决方案设计过程中的陷阱。值得注意的是,我们将在本书的第二部分应用 Python 和计算思维,以及本书的第三部分使用计算思维和 Python 进行数据处理、分析和应用中深入探讨本章的内容,进一步深入了解Python编程语言。

要了解调试,让我们提醒自己计算思维过程并不是线性的。即使我们从原始问题出发,有时我们会重新定义问题,或者需要根据算法所针对的人口变化或者我们想要调整算法设计来调整泛化。但有时,我们会在设计并使用算法后解决问题。根据我们的角色,我们将评估算法的错误、需要的更改等等。了解如何找到和分析错误可以帮助我们,无论我们是绝对的 Python 初学者还是在职业生涯中深入研究。

在本章中,您将学习如何识别和修复程序中的错误,以及如何避免算法设计中的陷阱。

在本章中,我们将涵盖以下主题:

  • 识别算法设计中的错误

  • 调试算法

  • 比较解决方案

  • 完善和重新定义解决方案

技术要求

您需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter07

识别算法设计中的错误

对于任何编码人员来说,算法中的错误都是生活中的一个事实。熟悉犯错是很重要的。正如在第五章中提到的,探索问题分析,以及第六章中提到的,解决过程和设计,测试算法并经常测试是一个好习惯。等到完成了数百或数千行代码才测试某些东西是灾难的预兆。是的,我曾经在复制一个游戏时根本没有测试。直到我复制了 4585 行代码。我当时很年轻。说实话,我从未找到我犯的错误。我重新开始,并在每个角落都开始测试。第二次成功了,但我浪费了数周时间复制一切(那是从一本书上复制的——当时还没有 GitHub),然后试图找出错误。所以请不要成为我。请测试您的算法。

现在,在进行调试和处理代码之前,让我们先看看在解决问题时可能遇到的错误。

在本节中,我们将重点关注以下两类广泛的错误:语法错误和逻辑错误。

语法错误

有时语法错误被称为解析错误。当我们忘记缩进、添加冒号、为字符串添加引号等等时,就会产生错误。让我们看看以下各种类型的语法错误。

使用冒号

在 Python 中,冒号用于分隔条件、创建循环等等。冒号是一种告诉算法下一步是这个特定代码块的方式。当我们在 Python 中引入冒号时,它会自动缩进我们代码的下一行。但如果我们忘记在需要的地方包括冒号,程序将无法成功运行。让我们看一个语法错误的例子:

for i in range(1, 10)
    print(i)

如果我们运行这段代码,会得到一个错误消息,说invalid syntax。下面的截图显示了当我们尝试运行这个程序时出现的弹出窗口:

图 7.1 - 错误弹出窗口

图 7.1 - 错误弹出窗口

如果我们从 Python shell 中运行这个程序,错误会如何显示:

SyntaxError: invalid syntax

正如你所看到的,Python 程序会在我们的代码中包含错误时提醒我们。

请注意,在代码中range后面缺少一个冒号。现在,看一下以下代码中的修正语法:

ch7_syntaxerror1.py

for i in range(1, 10):
    print(i)

当我们运行修正后的代码时,程序会运行并打印出数字 1 到 9,如下所示:

1
2
3
4
5
6
7
8
9

你可能还记得range函数不包括上限端点。如果我们想打印数字 10,我们的范围需要是range(1, 11)

现在,让我们看一看 Python 中使用的其他标点符号,可能会导致一些错误,即括号、嵌套括号和括号。

使用嵌套括号和括号

除了涉及冒号的语法错误外,还有嵌套括号的错误。我们必须始终检查每个开括号是否有闭括号。对于括号也是如此。让我们看一下下面的代码,其中包含括号错误:

name = str(input('What is your name? ')
print(name)

正如你所看到的,名称定义中有两个开括号,但只有一个闭括号。当我们运行该程序时,Python 会报告一个无效的语法错误。当我们在 Python shell 或解释器中运行该程序时,会发生什么:

SyntaxError: invalid syntax

现在这是没有错误的相同代码,注意我们甚至将str()去掉了,因为它是不需要的,这样简化了我们的代码,同时消除了错误。

ch7_syntaxerror2.py

name = input('What is your name? ')
print(name)

现在当我们运行代码时,程序会要求输入名称,然后打印出来。输出如下所示:

What is your name? Monique
Monique

正如你所看到的,程序现在可以正常运行了。

在[第三章](B15413_03_Final_SK_ePub.xhtml#_idTextAnchor056)理解算法和算法思维中,我们使用字典创建了一个带有每个菜单项定价的菜单。字典包含括号,用于表示字典开始和结束的位置。让我们看一下几行代码:

cars = {
    "Hyundai": "Kona",
    "Honda": "CR-V",
    "Toyota": "Camry"

print(cars)

如果我们看一下程序,字典缺少闭括号},所以我们会得到一个语法错误,就像我们之前的例子一样。以下片段显示了已纠正的程序:

ch7_syntaxerror3.py

cars = {
    "Hyundai": "Kona",
    "Honda": "CR-V",
    "Toyota": "Camry"
    }
print(cars)

正如你所看到的,一旦添加了括号,程序就会运行并打印出以下输出:

{'Hyundai': 'Kona', 'Honda': 'CR-V', 'Toyota': 'Camry'}

字典中的每个条目都打印在一行中,用逗号分隔。在编写算法时,添加print语句是有帮助的,以确保我们没有任何错误。一旦测试过,我通常会删除不必要的打印函数,但在编写长算法并需要测试以避免问题时,它们确实很有用。

在 Python 编写算法时,还有许多其他错误。让我们再看看一些语法错误。

其他语法错误

在更长的程序中,可能会引入许多其他语法错误。例如,如果你看一下我们刚刚使用的字典,忘记逗号也会创建一个语法错误。通常,当我们尝试运行程序时,这些语法错误会很快被识别出来。Python 会突出显示缩进的位置或者括号缺失的地方。语法错误通常很容易识别,但还有许多其他类型的错误。

逻辑错误

在[第四章](B15413_04_Final_SK_ePub.xhtml#_idTextAnchor071)理解逻辑推理中,我们讨论了可能遇到的逻辑错误:

  • 在等式或语句中使用错误的变量

  • 使用错误的运算符来测试条件

  • 在检查条件时使用错误的缩进

现在我们将看一下逻辑中的其他错误,这些错误在 Python 中有特定的调用,以及每个错误代表什么。

逻辑错误也称为运行时错误。下表显示了 Python 中一些内置错误以及它们的表示:

表 7.1 - 异常和原因/描述表表 7.1 - 异常和原因/描述表

表 7.1 - 异常和原因/描述表

如你所见,在 Python 中有许多不同类型的错误被标记为异常。你可以通过运行以下代码获取 Python 异常列表:

ch7_errors.py

print(dir(locals()['__builtins__']))

当我们运行该代码时,输出提供了以下错误值:

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

如前所述,这些是 Python 中的内置异常。有一种方法可以定义我们自己的异常,但在这本书中我们不会涉及到它们。

请注意,这些不是我们在编程时会遇到的唯一错误。我们可能会因为自己在计算中犯的错误而出现错误,就像我们在第四章中讨论的那样,理解逻辑推理。我们可能会在布尔逻辑中引入错误。目标是尽量避免这些错误,使我们的程序能够正常运行。记住,测试你的算法,并经常测试。

现在让我们看一些带有错误的算法,并尝试识别错误,以便我们可以纠正它们。

调试算法

在 Python 中,我们可以使用breakpoint()函数(内置的)运行调试器。我们可以将这段代码引入到我们的程序中,并在我们对代码不确定的地方插入它。添加breakpoint()将检查错误和 bug。当我们运行breakpoint()函数时,我们会得到一个pdb输出,代表Python 调试器。需要注意的是,这个内置函数出现在Python 3.7和更新版本中。Python 3.6和更早版本的先前调试器是pdb.set_trace()

当我们运行调试器时,我们可以使用四个命令:

  • c:继续执行

  • q:退出调试器/执行

  • n:在函数内部执行下一行的步骤

  • s:在这个函数或被调用的函数中执行下一行的步骤

让我们看一下代码,并运行所列出的每个命令:

ch7_debugger.py

number = 5
number2 = 'five'
print(number)
breakpoint()
print(number2)

看看这段代码,你可以看到print(number)后面的breakpoint()命令。代码将正常运行,直到达到breakpoint()命令。在这个阶段,执行停止。如果我们按下c键,那么它将继续运行程序。看看输出是什么样子。

请注意,在代码中有两个斜杠之间有三个点,/…/。这是因为路径可能会因计算机不同而不同。你的路径将包括程序所在的完整路径:

5
> /Users/.../Python/ch7_debugger.py(8)<module>()
-> print(number2)
(Pdb) c
five

如你所见,它继续打印字符串five,因为它只是继续运行程序。现在让我们看看当我们运行q命令时的输出,它会退出程序:

5
> /Users/.../Python/ch7_debugger.py(8)<module>()
-> print(number2)
(Pdb) q
Traceback (most recent call last):
  File "/Users/.../Python/ch7_debugger.py", line 8, in <module>
    print(number2)
  File "/Users/.../Python/ch7_debugger.py", line 8, in <module>
    print(number2)
bdb.BdbQuit

正如你所看到的,一旦我们使用q命令,由于程序退出,我们会得到一个回溯错误。它打印了breakpoint()代码上面的行,但没有打印第二个print(number2)命令。现在,让我们看看当我们输入n时会发生什么,它应该会带我们到下一行:

5
> /Users/.../Python/ch7_debugger.py(8)<module>()
-> print(number2)
(Pdb) n
five
--Return--
> /Users/.../Python/ch7_debugger.py(8)<module>()->None
-> print(number2)
(Pdb)

如你所见,当我们输入n时,程序继续运行并打印第二个命令行。当这样做时,你可以看到-> None输出和运行的代码:print(number2)。最后,让我们看一下稍微改变的代码,看看在运行调试器时使用s会发生什么:

ch7_debugger2.py

number = 5
number2 = 'five'
print(number)
breakpoint()
print(str(number) + number2)

当我们运行这个程序和调试器时,如果我们使用s,我们会得到以下输出:

5
> /Users/.../Python/ch7_debugger2.py(8)<module>()
-> print(number + " " + number2)
(Pdb) s
TypeError: unsupported operand type(s) for +: 'int' and 'str'
> /Users/.../Python/ch7_debugger2.py(8)<module>()
-> print(number + " " + number2)
(Pdb)

如你所见,程序遇到了TypeError并提供了更多信息。我尝试将整数和字符串组合在一起。因此,我们需要修复代码以正确运行。在我这样做之前,让我们看看当我尝试使用c继续代码时会发生什么:

5
> /Users/.../Python/ch7_debugger2.py(8)<module>()
-> print(number + " " + number2)
(Pdb) c
Traceback (most recent call last):
  File "/Users/.../Python/ch7_debugger2.py", line 8, in <module>
    print(number + " " + number2)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

如你所见,我可以从两个命令中获得相同的信息,程序的响应略有不同。要解决这个问题,我必须将数字转换为字符串,在print行中可以使用以下代码来实现:

ch7_debugger3.py

number = 5
number2 = 'five'
print(number)
breakpoint()
print(str(number) + " " + number2)

现在,我已经修复了代码,使得打印行中的项目都是字符串,当我使用c继续时,输出如下:

5
> /Users/.../Python/ch7_debugger3.py(8)<module>()
-> print(str(number) + " " + number2)
(Pdb) c
5 five 

如你所见,程序现在打印了正确的信息,将数字作为字符串与five字符串组合在一起。双引号在它们之间添加了一个空格,我们以前已经见过,但当我们在第八章中查看 Python 基础知识时,将再次讨论。

现在,让我们看一下相同问题的一些解决方案,以便分析它们。

比较解决方案

当我们看问题时,我已经提到在 Python 中有多种方法可以做同样的事情。根据我们试图实现的目标,一些命令可能比我们的算法中的其他命令更好。让我们首先看一下一个问题的几种解决方案。

问题 1 - 打印偶数

你被要求编写一个算法,根据用户提供的范围打印偶数。也就是说,如果用户输入范围为 2 到 20,那么程序将打印 2, 4, 6, 8, 10, 12, 14, 16, 18 和 20。让我们假设如果端点是偶数,我们希望包括端点。

让我们看一下两种可能解决方案中的第一种。记住,一个解决方案可能不比另一个更好。很大程度上取决于你的完整算法的目标。*列表更合适吗?字典?函数?*当我们设计解决方案时,这些问题很重要。

算法解决方案 1 - 打印偶数

回想一下,我们将接受用户输入来创建一个给定范围内的偶数列表。看一下以下代码,它要求用户输入,然后打印出数字:

ch7_evenalgorithm1.py

print("This program will print the even numbers for any range of numbers provided.")
endpoint1 = int(input("What is the lower endpoint of your range? "))
endpoint2 = int(input("What is the upper endpoint of your range? "))
endpoint2 = endpoint2 + 1
for i in range(endpoint1, endpoint2):
    if i % 2 == 0:
        print(i)

注意,endpoint2被转换为endpoint2 + 1。这是因为如果我们不添加1,那么如果它是一个偶数,上限端点将不会被包括在内。程序还以用户的打印消息开始,说明程序的功能是什么。

当我用端点26运行这个程序时,我得到以下输出:

This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 2
What is the upper endpoint of your range? 6
2
4
6

如你所见,两个端点都是偶数且包括在内。如果我们用端点39运行程序,我们得到以下输出:

This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 3
What is the upper endpoint of your range? 9
4
6
8

尽管终点现在在技术上是10,但范围的上限并不包括在内,因此在10以下的最大偶数是8。现在,我可以为一个更大的范围运行这个程序,但是范围越大,滚动以获取所有数字就越困难。因此,让我们看一种不同的方法来获取我们的偶数。

算法解决方案 2 - 打印偶数

正如我们从前面的例子中看到的,每个偶数都被打印到不同的行。让我们看看是否可以改变这一点,而是创建一个列表。Python 中的列表可以是空的。我们可以为它们使用任何名称,然后将它们等于括号内的项目或只是空括号。

例如,我可以创建一个名为evenNumbers = []的空列表。让我们看看以下算法中的情况:

ch7_evenalgorithm2.py

print("This program will print the even numbers for any range of numbers provided.")
endpoint1 = int(input("What is the lower endpoint of your range? "))
endpoint2 = int(input("What is the upper endpoint of your range? "))
endpoint2 = endpoint2 + 1
evenNumbers = []
for i in range(endpoint1, endpoint2):
    if i % 2 == 0:
        evenNumbers.append(i)

print(evenNumbers)

你可以看到代码的前几行是相同的。在这个特定的代码中唯一的区别是数字的打印方式。列表是在for循环之前创建的。然后,使用evenNumbers.append(i)代码将每个数字附加到列表中。最后,我们打印我们的列表以获得以下输出:

This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 2
What is the upper endpoint of your range? 10
[2, 4, 6, 8, 10]

如你所见,所有偶数都包含在一个列表中,这比一个接一个地打印更容易阅读。想象一下,如果你必须打印范围在 300-1,000 之间的偶数。当我们运行程序时,列表会使阅读更容易。对于第二个算法,输出如下:

This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 300
What is the upper endpoint of your range? 1000
[300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, 480, 482, 484, 486, 488, 490, 492, 494, 496, 498, 500, 502, 504, 506, 508, 510, 512, 514, 516, 518, 520, 522, 524, 526, 528, 530, 532, 534, 536, 538, 540, 542, 544, 546, 548, 550, 552, 554, 556, 558, 560, 562, 564, 566, 568, 570, 572, 574, 576, 578, 580, 582, 584, 586, 588, 590, 592, 594, 596, 598, 600, 602, 604, 606, 608, 610, 612, 614, 616, 618, 620, 622, 624, 626, 628, 630, 632, 634, 636, 638, 640, 642, 644, 646, 648, 650, 652, 654, 656, 658, 660, 662, 664, 666, 668, 670, 672, 674, 676, 678, 680, 682, 684, 686, 688, 690, 692, 694, 696, 698, 700, 702, 704, 706, 708, 710, 712, 714, 716, 718, 720, 722, 724, 726, 728, 730, 732, 734, 736, 738, 740, 742, 744, 746, 748, 750, 752, 754, 756, 758, 760, 762, 764, 766, 768, 770, 772, 774, 776, 778, 780, 782, 784, 786, 788, 790, 792, 794, 796, 798, 800, 802, 804, 806, 808, 810, 812, 814, 816, 818, 820, 822, 824, 826, 828, 830, 832, 834, 836, 838, 840, 842, 844, 846, 848, 850, 852, 854, 856, 858, 860, 862, 864, 866, 868, 870, 872, 874, 876, 878, 880, 882, 884, 886, 888, 890, 892, 894, 896, 898, 900, 902, 904, 906, 908, 910, 912, 914, 916, 918, 920, 922, 924, 926, 928, 930, 932, 934, 936, 938, 940, 942, 944, 946, 948, 950, 952, 954, 956, 958, 960, 962, 964, 966, 968, 970, 972, 974, 976, 978, 980, 982, 984, 986, 988, 990, 992, 994, 996, 998, 1000]

我之所以只打印这一个而不是第一个算法,是因为第一个算法需要很多页,我们不想在这本书中浪费纸张。你可以看到其中一个比另一个更容易使用和更合适,因为更容易阅读更大的数字组。

这就是为什么我们需要审视我们所有的算法,并确定它们是否是表达我们所需的最佳方式。虽然有些算法可以工作,但它们可能不是最佳解决方案,有时这是可以接受的。但有时,进行一些更改,有时甚至是添加几行代码,就像我们在算法 2中所做的那样,可以显著改变我们的输出,并对我们更有帮助。

当我们比较这两个算法时,我们也在不断完善和重新定义我们的解决方案,这在下一节中我们会做更多。

精炼和重新定义解决方案

如果我们长时间观察算法,总是可以找到方法来完善和重新定义它们。想想我们手机上的应用有多少更新。总有人在玩弄这些应用,使它们更安全,增加游戏的关卡,更新艺术文件等等。作为程序员/编码人员,我们总是在努力让我们的工作变得更好。

我们将从一个算法开始这一节。以下程序打印出三只宠物的名字:

ch7_pets1.py

cat = "Whiskers"
dog = "King Kong"
bird = "Pirate"
print("The cat's name is " + cat + ", the dog's name is " + dog + \
      ", and the bird's name is " + bird + ".")

这个简单的代码中包含了一切,所以这次没有用户输入。你可以看到在print()命令中的dog +后使用了\字符。这个反斜杠允许我们在下一行添加剩余的代码,这样我们可以更容易地阅读它。

代码的输出如下:

The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.

正如你所看到的,这是一个简单的句子,带有宠物名字。

现在,假设我们有一只猫、一只狗和一只鸟,但它们的名字不一样。我们可以使用一个带有三个参数的函数。请记住,我们将在第八章中详细介绍函数的定义和信息,Python 简介。现在,让我们看看带有函数的算法。我们将函数命名为myPets()。看看以下算法:

ch7_pets2.py

def myPets(cat, dog, bird):
    print("The cat's name is " + cat + ", the dog's name is " + dog +\
          ", and the bird's name is " + bird + ".")
myPets(cat = "Whiskers", dog = "King Kong", bird = "Pirate")

这个算法看起来与上一个非常相似,只是名字的定义在代码的最后一行。调用函数时,使用该行的信息来填写上面算法行中的定义的空白。输出看起来与上一个代码相同:

The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.

现在,正如你所看到的,这只打印了一个函数,因为我们只提供了一个信息,但我们可以随时调用函数,使用任意数量的值。看看这个算法:

ch7_pets3.py

def myPets(cat, dog, bird):
    print("The cat's name is " + cat + ", the dog's name is " + dog +\
          ", and the bird's name is " + bird + ".")
myPets(cat = "Whiskers", dog = "King Kong", bird = "Pirate")
myPets(cat = "Mimi", dog = "Jack", bird = "Peyo")
myPets(cat = "Softy", dog = "Leila", bird = "Oliver")

正如你所看到的,现在函数将被调用三次。我们只有一个print()命令,但是函数定义意味着每次调用函数时都会使用print()命令。看看输出是什么样子的:

The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.
The cat's name is Mimi, the dog's name is Jack, and the bird's name is Peyo.
The cat's name is Softy, the dog's name is Leila, and the bird's name is Oliver.

请注意,当我们调用函数时,使用了三组宠物名字,打印了三个不同的句子。

当我们编写算法时,重要的是要考虑我们现在需要什么,以及我们将来可能需要什么。对于一个实例来说,使用第一个算法是可以的,但是如果我们想要为社区中的每个人或者教室中的每个学生运行算法,那么第二个算法更有帮助。重新定义我们的需求并完善我们的算法有助于改进我们从程序中得到的东西。

请注意,正如前面提到的,我们将在第八章中更多地讨论函数,Python 简介。我们将讨论的其中一件事是为未知数量的参数创建一个函数。*例如,如果我只有一只狗和一只鸟呢?*我们可以通过对算法进行一些更改来解决这个问题。我们很快就会研究这个问题。目前,我们只是稍微了解了为什么有时需要比较算法并重新定义和重新设计它们以更好地满足我们的需求。

摘要

在本章中,我们讨论了算法设计中的错误以及如何调试解决方案。我们还学习了如何比较解决方案,并在需要时对解决方案进行改进和重新设计。阅读完本章后,您应该更了解算法中的语法错误以及如何在Python 3.7及以上版本中使用breakpoint()命令来使用调试器。内置调试器为您提供了四种操作:c = 继续q = 退出n = 下一行s = 步进

使用调试器可以帮助我们确定代码中可能出错的地方。我们可以在代码的任何位置添加这行代码来确定问题所在。

我们还研究了提供相同输出但使用不同代码的算法。通过比较算法解决方案,我们可以确定哪些更有用,哪些更适合我们的问题或情况,以及为什么我们应该选择其中一个而不是另一个。请记住,算法是指令列表。在广泛使用算法的情况下,知道使用哪些指令至关重要。某些解决方案可能比其他解决方案更适合您的问题。考虑算法的目的、算法中的代码片段以及它们将如何在更大的算法中使用,并相应地做出决定。每个问题和每个解决方案都是独一无二的。

当我们完成本书的第一部分计算思维导论时,我们已经了解了计算思维过程,始终关注可能的场景,以帮助我们理解该过程的有用性,如何进行头脑风暴并为决策创建流程图,以及如何设计我们的算法。在第二部分应用 Python 和计算思维中,我们将更深入地研究 Python 语言,以便能够解决更复杂的问题,比如处理数据和函数的问题。我们还将更深入地了解 Python 编程语言,并将在后续章节中将这些知识应用于多种类型的问题。

第二部分:应用 Python 和计算思维

在选择编程语言时有多种选择。Python 编程语言是一种强大的开源面向对象的编程语言。它是一种高级编程语言,拥有越来越多的库和包,不断扩展其功能。我们可以使用 Python 来编写从简单的数学算法到复杂的数据科学算法,甚至机器学习的程序。了解 Python 也意味着了解计算思维的多个领域。

在本节中,您将学习如何在使用 Python 编程语言解决问题时使用计算思维过程。

本节包括以下章节:

  • 第八章,Python 简介

  • 第九章,理解输入和输出以设计解决方案算法

  • 第十章,控制流

  • 第十一章,使用计算思维和 Python 解决简单挑战

第八章:Python 简介

在本章中,我们将学习 Python 的命令和功能,并将它们应用到问题中。当我们深入第二部分应用 Python 和计算思维的第一章时,我们将使用更复杂的 Python 编程。在本章中,我们将更多地关注语言,而其余章节将侧重于应用。

在本章中,我们将涵盖以下主题:

  • 介绍 Python

  • 使用字典和列表

  • 使用变量和函数

  • 学习文件、数据和迭代

  • 使用面向对象的编程

当我们深入研究 Python 编程语言时,请记住,一些内容已经在之前的章节中涵盖过,比如我们在研究计算思维过程时使用的字典和函数。本章将帮助您更轻松地找到满足您计算思维问题需求的 Python 命令的关键信息。

技术要求

您需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter08

介绍 Python

由于其易用性,Python 是增长最快的编程语言之一。Python 的吸引力之一是,我们可以用更少的代码行和更简单的语言和语法编写与 C、C++和 Java 等语言相同的程序。Python 的另一个吸引力在于它是可扩展的,这意味着我们可以为其添加功能和功能。

虽然并非所有功能都是内置的,但我们可以使用库来添加我们需要的功能。这些库可以下载并使用。例如,如果我们想要处理数据和数据科学,我们可以下载一些库,比如PandasNumPyMatplotlibSciPyScikit Learn等。但在我们深入研究这些库之前,让我们先了解一下 Python 语言的工作原理并学习其基础知识。

Python 具有一些内置的引用函数。下表按字母顺序列出了这些函数:

表 8.1 - Python 内置函数

表 8.1 - Python 内置函数

虽然我们不会在本书中涵盖所有的函数,但在研究 Python 及其算法时,我们会使用其中一些函数。让我们从这里列出的一些数学函数开始。

数学内置函数

在 Python 中,一些数学函数已经内置,比如abs()eval()max()min()sum()函数。这些并非所有内置的数学函数,但我们将仔细研究这些特定函数,以了解 Python 如何处理它们。

abs()函数将帮助我们找到一个数的绝对值,无论是整数还是浮点数。请看下面的代码片段:

ch8_absFunction.py

x = abs(-3.89)
print(x)

当我们运行这个程序时,我们将得到–3.89的绝对值。请记住,一个数的绝对值是它到 0 的距离。运行这个程序时,请看一下输出:

3.89

由于绝对值始终是正数,当我们运行abs(–3.89)时,我们得到3.89

另一个有用的数学函数是eval()函数。我们可以在这个函数中定义一个变量,然后调用 Python 来使用该值评估代数表达式。在 Python shell 中,我们可以首先定义变量如下:

>>> p = 2

现在变量已经定义,我们可以用任何表达式调用eval()函数。例如,请看下面的输入和输出:

>>> eval('2 * p - 1')

如您所见,Python 使用了先前定义的p2,并替换然后评估表达式以产生这个输出:

3

Python 程序也可以作为计算器使用,因此你可以像平常一样进行数学运算。以下是一些例子:

>>> 10-8

如你所见,Python 知道将破折号视为减法,并产生数学表达式的结果:

2

在下一个案例中,Python 将+解释为数学符号,并提供了求和表达式的结果:

>>> 4+5

输出是:

9

注意最后一个表达式10**5。在 Python 中,我们可以使用两个星号(**)表示指数:

>>> 10**5

输出是:

100000

现在,让我们看看max()函数。这个函数可以用在可迭代的列表上,但我们可以用只有两个数字的列表来测试它:

>>> max(12, 30)

你可以清楚地看到输出是什么:

30

让我们看另一个例子:

>>> max(100, 10)

这是输出:

100

从输出中可以看出,该函数总是选择最大的项。你可以添加第三个项,该函数仍然会选择最大值。这些内置函数在某种程度上很聪明,它们被编码为能够处理所提供的内容 - 例如两个或三个项 - 而无需明确告诉 Python 我们将引入多少项:

>>> max(230, 812, 109)

获得的输出是:

812

如你所见,我们不必向内置函数添加任何内容来找到三个值的最大值。

min()函数则相反;它选择最小值。看一下下面的最小函数:

>>> min(230, 812, 109)

这是输出:

109

如你所见,该函数使用了与最大函数相同的列表,但这次输出是109,这是该组数字的最小值。

还有其他数学函数,比如sum()。如果你是 Python 初学者,建议你尝试使用这些函数来了解它们的工作方式。这些函数将成为你算法的关键,因为你设计计算思维问题的解决方案。当我们研究其他 Python 功能时,比如字典和数组,我们也会使用其中一些函数。

使用字典和列表

在深入研究字典和列表之前,重要的是要注意 Python 的内置函数中不包含数组。我们可以使用列表,并对列表执行许多传统的数组函数。但是,对于数组的更强大功能,需要使用库,例如 NumPy。

Python 有四种集合数据类型,如下所示:

  • 列表:有序且可更改;可以有重复项

  • 元组:有序且不可更改;可以有重复项

  • 集合:无序且无索引;不能有重复项

  • 字典:无序、可更改和有索引;不能有重复项

如前所述,我们暂时不会涉及 NumPy 库或其他数据库。现在,我们将专注于字典和列表。

定义和使用字典

你可能还记得我们在第三章中使用了字典,理解算法和算法思维,当时我们创建了一个项目菜单。Python 中的字典是具有以下三个特征的集合:

  • 它们是无序的。

  • 它们是可更改的。

  • 它们由键索引。

字典是以值对的形式组织的。例如,我们可以有一个包含州和它们的首府值对的字典。看一下下面的字典:

ch8_dictionary1.py

states = {
    'Ohio':'Columbus',
    'Delaware':'Dover',
    'Pennsylvania':'Harrisburg',
    'Vermont':'Montpelier'
    }
print(states)

如果我们在没有条件的情况下将这个字典打印出来,我们会得到以下输出:

{'Ohio': 'Columbus', 'Delaware': 'Dover', 'Pennsylvania': 'Harrisburg', 'Vermont': 'Montpelier'}

正如你所看到的,每个值对都一次打印出来。就像我们以这种方式构建了字典一样,Python 也有一个内置的dict()函数。可以使用该函数构建相同的字典,如下所示:

ch8_dictionary2.py

states = dict([
    ('Ohio','Columbus'),
    ('Delaware','Dover'),
    ('Pennsylvania','Harrisburg'),
    ('Vermont','Montpelier')
])
print(states)

你可以看到,这两个例子中字典的构造方式非常相似,语法上有一些变化,比如第一个实例中使用冒号,而第二个实例中使用括号和逗号。然而,print语句产生了相同的结果。

据说字典是键值对,因为第一个项目是键,第二个配对项目是值。因此,对于OhioColumbusOhio,而Columbus。我们可以使用键来调用任何值。例如,我可以调用states['Ohio'],它应该返回'Columbus'。看一下代码:

>>> states['Ohio']

这是输出:

'Columbus'

我们可以为字典中的任何键值对做到这一点。但是,如果我们尝试调用字典中没有的键,比如Alabama,那么我们会得到以下错误:

>>> states['Alabama']

这导致显示以下错误:

Traceback (most recent call last):
  File "<pyshell#23>", line 1, in <module>
    states['Alabama']
KeyError: 'Alabama'

请注意,这会导致KeyError,在第七章的错误列表中提到,在解决方案中识别挑战,在逻辑错误部分。但是假设我们确实想要添加Alabama和首都Montgomery。我们可以使用以下代码来实现:

>>> states['Alabama'] = 'Montgomery'

我们可以在输入以下代码后再次调用字典:

>>> print(states)

这给了我们以下输出:

{'Ohio': 'Columbus', 'Delaware': 'Dover', 'Pennsylvania': 'Harrisburg', 'Vermont': 'Montpelier', 'Alabama': 'Montgomery'}

请注意,字典在字典末尾添加了'Alabama':'Montgomery'键值对。

我们还可以使用del代码删除键值对,如下所示:

>>> del states['Delaware']

现在,如果我们继续打印states字典,Delaware就不再在列表中了:

>>> print(states)

删除州之后的输出:

{'Ohio': 'Columbus', 'Pennsylvania': 'Harrisburg', 'Vermont': 'Montpelier', 'Alabama': 'Montgomery'}

现在,您可以继续添加项目或删除它们,而无需进入主算法。

使用字典,您还可以向一个键添加多个值。假设我玩三种运动(我没有)。我可以在字典中列出它们,并将它们与一个键值匹配。看一下以下算法:

ch8_dictionary3.py

miscellaneous = {
    'sports' : ['tennis', 'bowling', 'golf'],
    'age' : '40',
    'home' : 'lake'
    }

我们可以使用print(miscellaneous)打印完整的字典,如下所示:

>>> print(miscellaneous)

这就是我们得到输出的方式:

{'sports': ['tennis', 'bowling', 'golf'], 'age': '40', 'home': 'lake'}

如您所见,打印的字典包括所有值。如果我只想打印运动项目,那么我可以使用miscellaneous['sports']代码:

>>> miscellaneous['sports']

这是输出:

['tennis', 'bowling', 'golf']

请注意,我们根本没有使用print()函数。我们使用了我们称之为miscellaneous的字典,并调用了'sports'键。请注意,我们之所以能够得到这个结果,是因为我们在 Python 的IDLE命令窗口中调用了字典。如果您想将其包含在您的算法中,您仍然需要使用print()函数。

虽然我们在这里不会详细介绍字典的所有功能,但您可以看到它们如何有助于创建包含键值对的算法。Python 允许我们添加和修改字典,调用值等,而无需访问整个字典来实现这一点。

接下来,让我们来看看列表。

定义和使用列表

Python 中的列表是有序且可更改的。我们可以为任何事物创建列表,比如动物的类型、颜色、水果、数字,或者真的任何我们想要的东西。由于我们可以有重复的值,我们可以有一个只说apple, apple, apple的三个苹果的列表。例如,看一下所示的列表:

ch8_list1.py

fruits = ['apple','apple','apple']
print(fruits)

当我们打印这个列表时,所有项目都包括在内。输出如下:

['apple', 'apple', 'apple']

正如您所看到的,我们有相同的值三次。我们无法在字典中做到这一点,因为字典不允许重复成员。

现在,让我们看看我们可以用列表做什么。让我们从以下动物列表开始:

ch8_list2.py

animals = ['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']

列表中的第一项是'dog'。列表有索引,从 0 开始。因此,如果我们打印animals[0],我们会得到dog。我们可以在这里检查:

>>> print(animals[0])

这是索引 0 的输出:

dog

列表中有六个项目,但最后一个索引是[5]。因此,要打印elephant,我们需要使用该索引进行打印:

>>> print(animals[5])
elephant

您还可以在列表中使用负数索引。[–1]索引是指最后一个索引,因此它代表elephant,与索引[5]相同:

>>> print(animals[-1])
elephant

[–2]索引是指倒数第二个索引,因此是tiger,以此类推。

我们还可以通过指定索引范围来打印列表中的多个项目。看一下以下代码:

>>> print(animals[1:4])

这是输出:

['cat', 'bird', 'lion']

如你所见,我们打印了列表中的第二个项目,对应索引[1],以及接下来的两个项目。

使用列表,我们还可以添加项目,替换项目,删除项目,检查长度等等。要添加一个项目,我们需要使用append()方法。让我们把duck项目添加到我们的列表中:

>>> animals.append('duck')
>>> print(animals)

这是添加的项目:

['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant', 'duck']

请注意,我们的列表现在在列表末尾有duck。但是假设我们想要移除bird并用butterfly替换它:

首先,我们必须确定bird的索引。该索引是2,因为它是列表中的第三个项目:

>>> animals[2] = 'butterfly'
>>> print(animals)

这是输出,bird被替换了:

['dog', 'cat', 'butterfly', 'lion', 'tiger', 'elephant', 'duck']

列表现在包含butterfly

删除一个项目相当简单,我们只需使用remove()方法和我们想要删除的项目:

>>> animals.remove('lion')
>>> print(animals)

如你所见,lion已经从列表中移除了:

['dog', 'cat', 'butterfly', 'tiger', 'elephant', 'duck']

我们还可以通过索引使用pop()方法删除一个项目。使用索引1会移除列表中的第二个项目。参考以下代码:

>>> animals.pop(1)
'cat'

然后,我们可以尝试以下操作:

>>> print(animals)
['dog', 'butterfly', 'tiger', 'elephant', 'duck']

请注意,Python 识别了从列表中弹出的索引1处的项目。当我们再次打印列表时,该项目就不再存在了。如果我们想要移除最后一个项目,我们不必指定索引。参考以下代码:

>>> animals.pop()
'duck'

然后,我们可以尝试以下操作:

>>> print(animals)
['dog', 'butterfly', 'tiger', 'elephant']

如前所述,当没有指定索引时,列表上的最后一个项目会被弹出并从列表中移除。

还有一种方法可以从列表中删除一个项目,那就是使用del关键字:

>>> del animals[1]
>>> print(animals)

这是输出:

['dog', 'tiger', 'elephant']

我们的列表失去了列表中的第二个项目。我们还可以使用del关键字完全删除列表:

>>> del animals
>>> print(animals)

这个错误是输出在这里:

Traceback (most recent call last):
  File "<pyshell#49>", line 1, in <module>
    print(animals)
NameError: name 'animals' is not defined

如你所见,我不能再打印列表,因为它没有定义。请注意,我们收到了一个NameError描述name 'animals' is not defined。这是第七章中提到的另一个错误,在解决方案中识别挑战

重要提示:

在接下来的几个代码示例中,我再次运行了ch8_list2.py文件中的原始代码,以获得我的原始列表。

我们可以使用clear()方法清空整个列表,而不是消除实际的列表。

>>> animals.clear()
>>> print(animals)

这是我们得到的输出:

[]

在这种情况下,列表现在打印为空列表,而不会给出错误消息。这是因为列表仍然存在并且已定义,只是空的。

现在,假设我想要知道我的列表的长度。我们可以使用len()来找到列表的长度。同样,我回到原始列表运行以下代码:

>>> print(len(animals))

我们得到以下输出:

6

我们的原始列表包含六个元素 - 也就是六种动物。

现在,让我们定义另一个包含颜色的列表:

ch8_list3.py

animals = ['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']
colors = ['blue', 'red', 'yellow']
print(animals)
print(colors)

这个算法的输出是两个列表。现在,如果我们想要合并这两个列表,我们可以使用extend()方法。

让我们看看如何将colors附加到animals列表中:

>>> animals.extend(colors)
>>> print(animals)

这是追加后的列表:

['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant', 'blue', 'red', 'yellow']

我们的列表现在包含了所有的动物和所有的颜色。我们也可以使用以下方法将colorsanimals扩展。不同之处在于颜色会首先出现在我们的列表中:

>>> colors.extend(animals)
>>> print(colors)

现在列表的样子是这样的:

['blue', 'red', 'yellow', 'dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']

我们还可以对列表进行排序,当我们想要有一个按字母顺序排列的列表或者想要对数字列表进行排序时,这是很有帮助的。看一下以下算法中的两个列表:

ch8_list4.py

animals = ['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']
numbers = [4, 1, 5, 8, 2, 4]
print(animals)
print(numbers)

这是它们未排序时的样子:

['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']
[4, 1, 5, 8, 2, 4]

让我们对两个列表进行排序,看看会发生什么:

>>> numbers.sort()
>>> print(numbers)

我们将得到以下输出:

[1, 2, 4, 4, 5, 8]

然后,我们可以尝试以下操作:

>>> animals.sort()
>>> print(animals)

我们得到了这个输出:

['bird', 'cat', 'dog', 'elephant', 'lion', 'tiger']

正如你所看到的,数字按从小到大的顺序排序,而动物按字母顺序排序。

让我们谈一下为什么这样很有帮助。想象一下,你的网站上显示了来自 Python 列表的项目列表。它们都被排序并完美地显示出来。但现在让我们说你想添加更多的项目。使用我们讨论过的方法,将它们以任何顺序添加到你的列表中会更容易,然后我们可以对它们进行排序,以便它们继续按字母顺序排列。

当然,这些不是我们可以用列表做的唯一的事情。Python 允许我们以许多用户友好的方式使用列表,并且与其他编程语言中的数组类似地进行操作。当我们需要以其他方式使用它们时,我们可以搜索包含这些功能的库。

在 Python 编程语言中,列表和字典都很重要。在本节中,你看到字典使用键值对,而列表包括值。两者都可以使用内置在 Python 编程语言中的方法和函数进行编辑。当我们将 Python 应用于更复杂的计算思维问题时,你将再次看到它们,这在本书的第三部分使用计算思维和 Python 进行数据处理、分析和应用中。

现在,让我们看看如何在 Python 中使用变量和函数。

使用变量和函数

在 Python 中,我们使用变量来存储一个值。然后我们可以使用这个值来执行操作,评估表达式,或者在函数中使用它们。函数在算法中被调用时给出一系列指令。许多函数在其中包括变量。所以,让我们首先看看如何定义和使用变量,然后再看看 Python 函数。

Python 中的变量

Python 没有声明变量的命令。我们可以通过给它们命名并将它们设置为我们想要的任何值来创建变量。让我们看一个包含多个变量的算法:

ch8_variables.py

name = 'Marcus'
b = 10
country_1 = 'Greece'
print(name)
print(b)
print(country_1)

正如你所看到的,我们可以使用字母、更长的名称,甚至在变量命名中包括下划线。但是,我们不能以数字开头命名变量。当我们运行这个程序时,我们得到以下输出:

Marcus
10
Greece

每个变量都被打印出来,没有任何问题。如果我们使用数字来开始任何变量名,我们会得到一个错误。但是,如果我将country_1变量命名为_country,那将是 Python 中一个可接受的变量名。

现在,让我们看看我们可以用变量做什么。

组合变量

变量允许我们做的一件事是在print语句中组合它们。例如,我可以创建一个打印Marcus Greeceprint语句。为了做到这一点,我可以使用+字符,如下所示:

>>> print(name + ' ' + country_1)

请注意,在两个+字符之间,有' '。这是为了添加一个空格,这样我们的print语句就不会看起来像MarcusGreece。现在,让我们在print语句中组合bname

>>> print(b + name)

这将给我们一个错误消息:

Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    print(b + name)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

请注意,错误说明了TypeError: unsupported operand types for +。它还说明了我们有'int''str',这意味着我们正在组合两种不同的数据类型。

为了能够在print语句中组合这两种数据类型,我们可以将int(我们的b变量)转换为str。看起来是这样的:

>>> print(str(b) + ' ' + name)

这很容易给我们想要的结果:

10 Marcus

现在我们已经将它们都变成了字符串,我们可以在print函数中将它们组合起来。

有时,我们会想一次创建多个变量。Python 允许我们用一行代码来做到这一点:

ch8_variables2.py

a, b, c, d = 'John', 'Mike', 'Jayden', 'George'
print(a)
print(b)
print(c)
print(d)

当我们运行这个算法时,我们得到以下输出:

John
Mike
Jayden
George

正如你所看到的,当我们打印a变量时,等号右边的第一个值被打印出来。算法的第一行分配了四个不同的值给四个不同的变量。

现在让我们看看函数,因为我们需要它们来进一步讨论变量。

使用函数

第七章在解决方案中识别挑战中,我们编写了一个算法,打印出任何给定数字范围内的偶数。我们将通过定义一个函数来重新访问这个问题。让我们看一下偶数问题的算法:

ch8_evenNumbers.py

def evenNumbers(i, j):
    a = i - 1
    b = j + 1
    for number in range(a, b):
        if number % 2 == 0:
            print(number)
            a = a + 1

如果我们在 Python 中运行此程序,将不会有输出;但是,我们现在可以为任何数字范围运行该函数。请注意,我们在此函数中添加了ab变量。这样在程序运行时就会包括端点。

让我们看看当我们运行范围为(2, 10)(12, 25)时会发生什么:

>>> evenNumbers(2, 10)

输出如下:

2
4
6
8
10

然后,我们可以尝试以下操作:

>>> evenNumbers(12, 25)

输出如下:

12
14
16
18
20
22
24

正如您所看到的,我们无需进入算法来调用函数,一旦我们运行了函数,我们就可以在 Python shell 中为任何范围调用它。与以前一样,如果我们的范围太大,shell 将显示一个非常长的数字列表。因此,我们可以定义另一个变量,这次是一个列表,并在函数内部将值附加到该列表:

ch8_evenNumbers2.py

listEvens = []
def evenNumbers(i, j):
    a = i - 1
    b = j + 1
    for number in range(a, b):
        if number % 2 == 0:
            listEvens.append(number)
            a = a + 1
    print(listEvens)

请注意,我们在函数外定义了列表。我们可以以任何方式做。它可以存在于函数内部或外部。区别在于,即使未调用函数,列表也存在;也就是说,一旦运行算法,我可以调用列表,它将为空,或者调用函数,它将使用列表。如果在函数外部,它是全局变量。如果在内部,只有在调用函数时才存在。

让我们现在尝试运行范围为(10, 50)的程序:

>>> evenNumbers(10, 50)

这是输出:

[10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50]

正如您所看到的,1050之间的偶数现在包括在列表中并打印出来。

让我们看另一个接受(name)字符串参数的函数的示例:

ch8_nameFunction.py

def nameFunction(name):
    print('Hello ' + name)

一旦运行了这个算法,我们就可以为任何名称调用该函数。看一下:

>>> nameFunction('Sofia')

我们得到以下输出:

Hello Sofia

让我们输入另一个名称:

>>> nameFunction('Katya')

我们将看到以下输出:

Hello Katya

正如您所看到的,一旦在算法中定义了一个函数并运行了算法,我们可以根据需要调用该函数。

现在,我们在先前的偶数示例中使用了迭代,所以是时候更仔细地看看文件、数据和迭代了,让我们暂停一下并看看。当我们进行迭代时,我们将遇到更多的函数示例。

学习有关文件、数据和迭代的知识

在本节中,我们将看看如何处理文件、数据和迭代与 Python。这将为我们提供有关如何使用算法来打开、编辑和运行已存在的 Python 文件或新文件的信息。通过迭代,我们将学习如何根据某些条件重复代码行,限制算法的某个部分将运行多少次或在什么条件下运行。我们将首先从文件开始。

在 Python 中处理文件

在 Python 中,与文件相关的主要函数是open()函数。如果要在相同目录中打开文件,可以使用以下命令行:

fOpen = open('filename.txt')

如果文件位于另一个位置,您需要找到文件的路径并在命令中包含该路径,如下所示:

fOpen = open('C:/Documents/filename.txt')

除了打开文件之外,我们还可以告诉程序做一些其他事情。它们列在下面:

  • r:用于打开文件进行读取(这是默认值,因此不需要包含);如果文件不存在,则创建新文件。

  • w:用于打开文件进行写入;如果文件不存在,则创建新文件。

  • a:用于打开文件以进行追加;如果文件不存在,则创建新文件。

  • x:用于创建指定的文件;如果文件已经存在,则返回错误。

除了列出的方法之外,还有两件事可以用于标识文件需要如何处理 - 即作为二进制还是文本:

  • t:文本(这是默认值,因此如果未指定,则默认为文本)

  • b: 二进制(用于二进制模式 - 例如图像)

看一下以下代码。由于这段代码是单行的,而且针对每个路径都是特定的,所以没有包含在存储库中:

fOpen = open('filename.txt', 'rt')

前面的代码与先前的代码相同,fOpen = open('filename.txt')。由于rt是默认值,不包括它们会导致算法的相同执行。

如果我们想要打开一个文件进行写入,我们可以使用fOpen = open('filename.txt', 'w')代码。在 Python 中关闭文件,我们使用close()代码。

文本文件的一些附加方法包括在以下列表中。这不是一个详尽的列表,但包含了一些最常用的方法:

  • read(): 可以用来读取文件的行;使用read(3)读取前 3 个数据。

  • tell(): 用于找到当前位置的字节数。

  • seek(): 将光标移动到原始/初始位置。

  • readline(): 读取文件的每一行。

  • detach(): 用于将底层二进制缓冲区与TextIOBase分离并返回它。

  • readable(): 如果可以读取文件流,则返回True

  • fileno(): 用于返回文件的整数编号。

正如你所看到的,你可以使用 Python 来操纵并从文本文件中获取信息。例如,当向现有文本文件添加代码行时,这可能会很有用。现在,让我们来看看 Python 中的数据。

Python 中的数据

在我们开始处理数据之前,让我们澄清一下,我们不是在讨论数据类型,这是我们在第一章中讨论的,计算机科学基础。在本章中,我们将主要以列表的形式查看数据和与数据交互的方式。

让我们看看我们可以用数据做些什么。

首先,从存储库中获取ch8_survey.txt文件。你将需要它用于下一个算法。

假设你让你的朋友们在蓝色、红色和黄色之间为一个团体标志进行选择。ch8_survey.txt文件包含了这些投票的结果。Python 允许我们操纵这些数据。例如,文件的第一行说Payton – Blue。我们可以使该行打印出Payton 投票支持蓝色。让我们看看算法:

ch8_surveyData.py

with open("ch8_survey.txt") as file:
    for line in file:
        line = line.strip()
        divide = line.split(" - ")
        name = divide[0]
        color = divide[1]
        print(name + " voted for " + color)

让我们分解我们的代码,以了解发生了什么:

  • 第一行打开了调查文件并保持打开状态。

  • 下一行,for line in file,将遍历文件中的每一行执行以下命令。

  • 然后算法将信息分割在破折号处。第一部分被定义为名字(name = divide[0]),第二部分被定义为颜色(color = divide[1])。

  • 最后,代码打印出了带有投票支持文本的行。

看一下以下输出:

Payton voted for Blue
Johnny voted for Red
Maxi voted for Blue
Jacky voted for Red
Alicia voted for Blue
Jackson voted for Yellow
Percy voted for Yellow

正如你所看到的,现在每一行都经过调整,去掉了破折号,并包括了投票支持短语。但是如果你想要计算投票呢?我们也可以为此编写一个算法:

ch8_surveyData2.py

print("The votes for Blue are in.")
blues = 0
with open("ch8_survey.txt") as file:
    for line in file:
        line = line.strip()
        name, color = line.split(' - ')
        if color == "Blue":
            blues = blues + 1
print(blues)

正如你所看到的,我们正在通过验证每一行并使用if color == "Blue"代码来计算投票。当我们运行这个算法时,我们得到以下输出:

The votes for Blue are in.
3 

正如你所看到的,该算法打印出了初始的print()命令,然后打印出了蓝色的计数投票。

我们也可以处理数据,找出诸如均值、中位数和众数之类的东西,但现在我们不会详细讨论。如果将来我们在某个应用问题中需要它们,我们会使用它们。然而,大多数这些特定于数据的问题将使用库来简化一些算法和计算。

现在,让我们再谈一下迭代,我们一直在使用,但需要进一步定义以更好地理解其用法。

在算法中使用迭代

在我们进入迭代之前,让我们定义一下术语。迭代意味着重复。当我们在算法中使用迭代时,我们正在重复步骤。想想我们之前在问题中使用过的for循环,比如偶数问题——算法迭代了一系列数字。它对我们范围内的所有数字进行了重复。

让我们看另一个例子:

ch8_colorLoop.py

colors = ['blue', 'green', 'red']
for color in colors:
    print(color)

该算法中的迭代是它将为原始颜色列表中的每种颜色重复执行print过程。

这是输出:

blue
green
red

如你所见,每种颜色都被打印出来了。

我们还可以从用户那里获取输入,然后迭代执行一些操作。例如,看看以下算法:

ch8_whileAlgorithm.py

ask = int(input("Please type a number less than 20\. "))
while ask > 0:
    print(ask * 2)
    ask = ask – 1

如你所见,我们将输入变量定义为ask。然后,只要ask大于0,我们就打印出加倍的数字并将数字减1

输出如下:

Please type a number less than 20\. 8
16
14
12
10
8
6
4
2

输出显示了通过将初始数字加倍而创建的列表,即8 x 2 = 16,然后继续直到ask变量不再大于0

接下来,让我们看看如何可以迭代遍历多个列表。该算法使用两个列表,然后使用两者的信息打印出一个语句:

ch8_Iterations.py

jewelry = ['ring', 'watch', 'necklace', 'earrings', 'bracelets'] 
colors = ['gold', 'silver', 'blue', 'red', 'black']
for j, c in zip(jewelry, colors):
    print("Type of jewelry: %s in %s color. " %(j, c))

当我们运行算法时,我们得到以下输出:

Type of jewelry: ring in gold color. 
Type of jewelry: watch in silver color. 
Type of jewelry: necklace in blue color. 
Type of jewelry: earrings in red color. 
Type of jewelry: bracelets in black color.

看一下print语句:print("Type of jewelry: %s in %s color." %(j, c))%s符号将分别被(j, c)的值替换。因此,第一个%s符号获取j的项目,第二个%s符号获取c的项目,其中j是来自珠宝列表的项目,c是来自颜色列表的颜色。

如你所见,我们可以以多种方式迭代列表。这只是一些示例,让我们熟悉循环和如何在我们的算法中使用信息。随着我们深入更复杂的问题,算法将变得更复杂,所以我们将重新讨论本章和之前章节中的许多主题。

在进入下一章之前,我们需要看一下面向对象编程OOP)。

使用面向对象编程

面向对象编程是一种将数据结构化为对象的方法。Python 程序是一种面向对象的程序,它将算法结构化为对象,以便根据属性和行为对它们进行打包。要了解面向对象编程,我们需要知道如何做以下事情:

  • 创建类

  • 使用类创建对象

  • 使用类继承来建模系统

在 Python 中,我们使用类来打包数据。要理解类,让我们创建一个:

>>> class Books:
	pass

然后我们可以调用Books()类并获取类在计算机上保存的位置。请注意,我的输出将与您的不同,因为类将保存在不同的位置:

>>> Books()

这是我的输出:

<__main__.Books object at 0x000001DD27E09DD8>

现在我们已经创建了类,我们可以向类添加书籍对象:

>>> a = Books()
>>> b = Books()

这些实例中的每一个都是Books()中的一个独特对象。如果我们要比较它们,由于它们是不同的,a == b将返回False

现在,让我们看一个创建地址簿的类。通过创建类,我们可以根据需要添加条目:

ch8_addressBook.py

class Entry:
    def __init__(self, firstName, lastName, phone):
        self.firstName = firstName
        self.lastName = lastName
        self.phone = phone

在这个算法中,我们创建了一个代表地址簿中条目的Entry类。一旦我们运行算法,我们就可以向地址簿添加条目并调用它们的信息。例如,看看以下代码:

>>> Johnny = Entry('John', 'Smith', '201-444-5555')

这段代码将Johnny输入地址簿。现在,我们可以分别调用 Johnny 的名字、姓氏和电话号码,如下所示:

>>> Johnny.firstName

这是我的输出:

'John'

我们可以调用姓氏:

>>> Johnny.lastName

这是获得的输出:

'Smith'

我们还可以调用电话号码:

>>> Johnny.phone

我们可以看到这个输出:

'201-444-5555'

我们可以添加任意多的条目,然后根据需要调用它们:

>>> max = Entry('Max', 'Minnow', '555-555-5555')
>>> emma = Entry('Emma', 'Dixon', '211-999-9999')
>>> emma.phone

这是我们的输出:

'211-999-9999'

添加条目后,我们可以调用姓氏:

>>> max.lastName

我们得到这个输出:

'Minnow'

如你所见,我们添加了两个新条目,然后调用了 Emma 的电话号码和 Max 的姓氏。

一旦我们有了类,我们就可以有一个类继承其他类的方法和属性。因为这些类继承了属性和方法,原始类被称为父类,而新类被称为子类

回到我们的地址簿,我们可以通过将类传递给新类来创建一个子类。我知道,这听起来很混乱,但看一下这个算法:

>>> class Job(Entry):
	pass

现在我们也可以使用子类添加更多项目。看下面的例子:

>>> engineer = Job('Justin', 'Jackson', '444-444-4444')

我们可以用类做更多的事情,但让我们尝试通过解决一个问题并设计一个算法,使用至少学到的一些组件来总结这一章。

问题 1 - 创建一个书库

假设你有很多书,想创建一个存储有关书籍信息的算法。你想记录每本书的标题、作者、出版日期和页数。为这种情况创建一个算法。

首先,让我们思考一下问题:

  • 我拥有的书的数量不断变化,所以我希望创建一个可以根据需要添加信息的东西。

  • 我也希望能够删除我不再拥有的书。

虽然我们可以使用库,但对于这个特定的问题,最好的解决方案是一个类。让我们从创建一个名为Books的类开始:

ch8_BookLibrary.py

class Books:

    def __init__(self, title, author, pubDate, pages):
        self.title = title
        self.author = author
        self.pubDate = pubDate
        self.pages = pages
book1 = Books('The Fundamentals of Fashion Design', 'Sorger & Udale', '2006', '176')
book2 = Books('Projekt 1065: A Novel of World War II', 'Gratz', '2016', '309')

如您所见,我们已经定义了我们的类。该类有四个参数:titleauthorpubDatepages。在定义之后,添加了两本书。当我们运行这个程序时,实际上什么都不会发生,但我们可以调用任一本书的信息:

>>> book1.title

我们将得到以下输出:

'The Fundamentals of Fashion Design'

我们将调用book2的出版日期:

>>> book2.pubDate

我们得到这个输出:

'2016'

您可以看到,我现在可以在运行算法后调用每本书保存的任何元素。

现在,让我们看看如何在 Python shell 中添加第三本书:

>>> book3 = Books('peanut butter dogs', 'Murray', '2017', '160')

由于书已经添加,我们也可以调用关于这本书的信息:

>>> book3.title

我们得到这个输出:

'peanut butter dogs'

我们可以调用book3的总页数:

>>> book3.pages

我们将得到以下输出:

'160'

如您所见,我们可以在算法中添加书籍到我们的类中,或者在 Python shell 中运行算法后添加书籍。现在,让我们看看另一个问题。

问题 2 - 组织信息

我们被要求创建一个算法,它接受三个数字作为输入,并提供这些数字的总和。我们可以用多种方法来做到这一点,但让我们看看如何使用eval()函数:

ch8_Sums.py

a = int(input("Provide the first number to be added. "))
b = int(input("Please provide the second number to be added. "))
c = int(input("Provide the last number to be added. "))
print(eval('a + b + c'))

请注意,我们将每个输入变量定义为int类型。这样定义是为了正确执行评估。

这是我们算法的输出:

Provide the first number to be added. 1
Please provide the second number to be added. 2
Provide the last number to be added. 3
6

如果我们忘记为每个数字添加类型,函数会将其评估为123,因为它只是将每个字符串添加到下一个字符串。所以,如果我们的输入是JohnMaryJack,我们的输出将是JohnMaryJack

我们之前没有涉及sum()函数。让我们看看如何使用该函数:

ch8_Sums2.py

a = int(input("Provide the first number to be added. "))
b = int(input("Please provide the second number to be added. "))
c = int(input("Provide the last number to be added. "))
numbers = []
numbers.append(a)
numbers.append(b)
numbers.append(c)
print(sum(numbers))

在这种情况下使用sum需要我们将输入添加到列表中,因为sum()可以处理可迭代对象,比如列表。虽然这种解决方案代码更多,但输出与我们的eval()函数完全相同,如下所示:

Provide the first number to be added. 1
Please provide the second number to be added. 2
Provide the last number to be added. 3
6

如您所见,我们使用不同的 Python 函数得到了与之前相同的答案。在我们继续下一章之前,让我们再看一个问题。

问题 3 - 循环和数学

对于这个问题,我们必须创建一个算法,打印出给定范围内所有数字的平方。记住,我们可以单独打印每个数字,但如果范围很大,最好我们有一个列表。我们还需要在范围内进行迭代,并且如果我们想要包括端点,我们必须将最大值加 1。

看一下以下算法:

ch8_SquareLoops.py

print("This program will print the squares of the numbers in a given range of numbers.")
a = int(input("What is the minimum of your range? "))
b = int(input("What is the maximum of your range? "))
Numbers = []
b = b + 1
for i in range(a, b):
    j = i**2
    Numbers.append(j)
    i = i + 1
print(Numbers)

注意我们在算法中添加了一个j变量。我们没有使用i = i**2,因为那样会改变i的值,这会影响算法中的迭代。通过使用j,我们可以使用i来遍历给定的范围。让我们来看看我们的输出:

This program will print the squares of the numbers in a given range of numbers.
What is the minimum of your range? 4
What is the maximum of your range? 14
[16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196]

该算法打印出我们提供范围的平方列表。它还有一个初始的print语句,解释了代码将要做的事情。

现在我们已经看了一些例子,让我们来看看父类和子类以及继承是如何工作的。

使用继承

在 Python 中,我们可以使用继承将方法和属性从一个类传递到另一个类。父类具有将被继承的方法和属性。子类继承自父类。父类是一个对象,子类也是一个对象,所以我们正在定义类的属性。

让我们看看如何使用一个科学例子。我们将创建一个名为mammals的类。并不是所有的哺乳动物都是胎生的。哺乳动物在产下活仔时是胎生的。不是胎生的哺乳动物是鸭嘴兽。鸭嘴兽产卵而不是产下活仔。如果我们要为此编写一个算法,我们希望动物继承父类的特征 - 在这种情况下是mammals。让我们看看下面的代码片段:

ch8_mammals.py

class mammals:
     def description(self):
       print("Mammals are vertebrate animals.")

     def viviparous(self):
       print("Mammals are viviparous, but some are not.")

class monkey(mammals):
     def viviparous(self):
       print("Monkeys are viviparous.")

class platypus(mammals):
     def viviparous(self):
       print("The platypus is not viviparous. It's an egg-laying mammal.")

obj_mammals = mammals()
obj_monkey = monkey()
obj_platypus = platypus()

obj_mammals.description()
obj_mammals.viviparous()

obj_monkey.description()
obj_monkey.viviparous()

obj_platypus.description()
obj_platypus.viviparous()

从前面的代码中,注意到mammals()类使用了一个描述,然后是有关哺乳动物和胎生的信息。monkey使用了与mammals类相同的描述,但是包括了一个不同的胎生声明。platypus也是一样的情况。monkeyplatypus类都是mammals类的子类。

这三个类,父类和两个子类,然后被简化为一个变量,以便通过调用该变量来使用。最后,算法打印出了父类和两个子类的描述和胎生声明。让我们来看看输出:

Mammals are vertebrate animals.
Mammals are viviparous, but some are not.
Mammals are vertebrate animals.
Monkeys are viviparous.
Mammals are vertebrate animals.
The platypus is not viviparous. It's an egg-laying mammal.

正如你所看到的,所有三个类都使用了相同的描述。这是因为我们没有对子类的描述进行任何更改。当我们定义类时,我们只改变了想要与父类不同的部分。其他所有内容都是从父类继承而来的。

在 Python 中,父类和子类被广泛用于多种目的。例如,在游戏中,你可能会有一些具有相同特征的敌人。我们可以创建一个包含所有共同特征的父类,然后将所有敌人的个体特征作为子类进行更改,而不是分别定义每个敌人的所有特征。这只是继承可以使用的方式之一。还有许多其他用途,它可以帮助我们节省时间,并通过只用父类定义它们来避免错误。

现在我们已经有了一些类的经验,并学习了面向对象编程,让我们结束本章。

摘要

在本章中,我们讨论了 Python 编程的基础知识。我们研究了一些内置函数,使用了字典和列表,使用了变量和函数,学习了有关文件、数据和迭代的知识,并学习了类和面向对象编程。

正如我们在本章和解决以前的问题时提到的,Python 为我们提供了多种解决同一问题的方法。其中一个例子就是在本章的“问题 2 - 组织信息”部分提供的,我们在两种不同的算法中使用了eval()sum()函数来产生相同的结果。随着我们继续学习 Python 以及如何编写我们的算法,选择使用哪些函数、变量、参数等将开始变得自然而然。本章中一些更具挑战性的概念和内容涉及数据分析,比如我们在介绍 Python 中的数据时使用的调查,以及类。在这些领域进行一些练习是很重要的,这样你就能更好地理解这些主题。

在这一章之后,你现在可以使用内置函数,区分列表、字典和类,并解决结合多个不同概念的问题,比如变量、迭代和列表。在下一章中,我们将更深入地研究输入和输出,以帮助我们设计算法。