杜克大学-Python-实践笔记-全-

杜克大学 Python 实践笔记(全)

001:欢迎来到课堂 🐍

在本节课中,我们将一起认识课程团队,了解本课程的设计理念、学习目标以及为什么选择Python作为入门编程语言。

你是否曾思考过,编程对你和你周围人的生活能产生多么巨大的潜力?

大家好,我是杜克大学教授苏珊·罗杰,我将与我的几位同事一起担任本课程的讲师。欢迎来到我们的课程。

大家好,我是杜克大学教授克里斯滕·史蒂文斯·马丁内斯。如果你曾希望自信地遨游数字世界,创建有趣而令人惊叹的编程项目,或者只是想提升你的技术素养,那么你来对地方了。本课程专为绝对零基础的初学者设计,不需要任何先前的编程经验。

大家好,我是杜克大学讲师约塞尼亚·维拉科。通过这门课程,我们将为编程世界带来一个独特的视角。我们不仅教你如何创建能运行的东西,更教你如何创建对你而言有意义的东西。

大家好,我是哈佛大学计算机科学博士生埃斯特·布朗,也是本课程的客座讲师。与我们同行,你将不仅仅是学习写代码。我们会深入探讨程序为何如此运行、计算的历史以及激动人心的计算挑战。你将了解到像凯瑟琳·约翰逊这样的传奇人物是如何运用她的计算技能做出贡献的。在本课程结束时,你将能够设计并执行你自己的Python项目。

现在你可能在想,为什么是Python?Python语法简单易读,非常适合初学者。此外,Python应用领域广泛,例如网站开发、数据分析,甚至人工智能。通过教授Python,我们为你打下坚实的基础,并让你有机会探索科技领域许多有趣的方向。

编程远不止让程序运行起来。它关乎预见潜在的缺陷,考虑更广泛的影响,并思考:我的代码如何不仅为我工作,也能为所有人服务?

我们来自许多不同的背景,这让我们能以多种不同的视角看待编程。我们共同设计了一门课程,它包含实用的例子和清晰、包容的教学方式,以确保每个人都感到受欢迎。

在我上第一门编程课之前,我并不确定编程是否适合我,但我决定无论如何尝试一下,结果发现我热爱编程。通过参加这门课程,你可能会惊讶于自己能取得的成就以及这段旅程能带来的快乐。

说得太好了。无论你的目标是从事科技行业、提升现有技能,还是仅仅希望开启个人技术项目,我们的课程都为你提供了实现目标所需的工具包。

我们对这段旅程感到兴奋,并希望你成为其中的一部分。那么,你准备好潜入Python编程的精彩世界了吗?让我们开始吧。

002:你的编程环境-VS Code演示 🖥️

在本节课中,我们将学习编程所需的核心工具——编程环境。你将了解什么是集成开发环境(IDE),以及为什么我们选择Visual Studio Code(VS Code)作为本课程的工具。我们还将熟悉VS Code界面的主要组成部分。

什么是编程环境? 🛠️

编程意味着你需要一个专门为编写代码而设计的工作空间,更具体地说,你需要一个编程环境。

编程环境是一个软件应用程序,你可以在其中编写代码,并且通常可以直接运行代码。它通常被称为集成开发环境,简称IDE。

市面上有许多种编程环境。在本课程中,我们将使用Visual Studio Code,也称为VS Code。关于VS Code和本Coursera课程的一个很棒的特点是,VS Code可以直接在网页上使用。这意味着你不需要在自己的电脑上安装任何软件。你只需要一个网络浏览器和互联网连接,就可以开始编程了。

为什么使用编程环境? 🤔

使用编程环境,有点像你使用一个应用程序来帮助你写文章,它会检查你的拼写和语法,但功能要强大得多。

它通过检查错误和拼写错误来帮助你编写代码,并在你开始输入时提供建议。它还可以直接在那里运行你的代码并显示结果。此外,由于VS Code集成在Coursera平台中,它还会自动为你的代码评分。

所有这些优点,正是我们为本课程选择VS Code的原因。它在一个地方集成了你学习本课程所需的一切。

认识VS Code界面 📋

这是一张Coursera平台上VS Code的截图。让我们来了解一下这个编程环境的重要部分。

首先,我们来看看文件资源管理器。这个区域以文件夹的形式展示你所有的文件。在这个例子中,我们有一个名为 Hello World.py 的文件,它位于名为 Lab 1 的文件夹内。随着你在课程中的进展,你会看到更多的文件和文件夹。

接下来是代码编辑器。这里是大部分“魔法”发生的地方。你将在这里通过起草和修改代码来编辑你的代码文件。本幻灯片上的例子展示了一行代码。

最后是终端。在这里,你将与你的代码交互,并看到你所有辛勤工作的成果——也就是你代码的输出。

我们知道这一切一开始可能看起来让人不知所措,但别担心。在接下来的几个视频中,我们将带你完成你的第一个程序和实验。通过一些练习,一切都会变得越来越熟悉。

总结 📝

本节课我们一起学习了编程环境的基础知识。我们了解到编程环境(IDE)是编写和运行代码的专用软件,并介绍了本课程将使用的VS Code。我们熟悉了VS Code的三个核心区域:文件资源管理器代码编辑器终端。记住,你无需安装任何软件,只需通过浏览器即可使用Coursera上的VS Code开始你的编程之旅。在接下来的课程中,我们将动手实践,编写你的第一行代码。

003:Python中的打印操作 🖨️

在本节课中,我们将要学习Python编程中最基础且重要的操作之一:打印。通过打印,我们可以让程序在运行时显示信息,这对于理解代码执行过程和调试至关重要。

什么是打印? 🤔

上一节我们介绍了课程目标,本节中我们来看看什么是打印。

打印是一种让我们能够看到Python正在执行什么操作的方式。当我们打印某些内容时,我们可以在Python控制台中看到这些内容。

以下是执行任何操作前Python控制台的截图。

而这是在我们运行程序、告诉Python打印“Hello World”这句话之后,输出将会呈现的样子。

或者更清晰地说,这是终端中放大的输出。它显示了文本“Hello World”,这正是代码指示Python要显示的内容。

为什么需要打印? 💡

我们已经了解了打印的基本概念,接下来探讨一下它的重要性。

通过使用打印,我们可以在代码运行时观察其执行情况。打印对于确保代码正在执行你期望的操作非常有用。

如果代码正在为你执行某些任务,例如进行计算或运行游戏,打印就是Python向你展示正在发生事情的方式。

如何打印? 🛠️

理解了为什么需要打印后,本节中我们来看看具体如何操作。

要打印内容,我们使用print函数。我们将在后续课程中详细介绍函数是什么。目前,重要的是代码首先写出单词print,然后紧跟一个左括号(中间没有空格)。

接下来是你想要打印的内容,最后是一个右括号。

以下是打印操作的基本语法结构:

print(要打印的内容)

可以打印什么? 📝

我们已经学会了打印的语法,那么可以打印哪些内容呢?

可以打印任何以及所有内容。让我们从打印文本开始讨论,在Python中,文本被称为字符串

一个字符串由单引号或双引号开始和结束来表示。使用哪种引号并不重要,只要它们匹配即可。否则,Python将无法找到字符串的结尾。

例如,如果你想打印经典且历史悠久的“Hello World”——这是计算机最早打印的字符串之一。

整个代码行将是:print,紧接着左括号(无空格),引号(本例中使用单引号),单词“Hello World”,另一个单引号,然后是一个右括号。

你将在第一次实验课中尝试打印内容。

关于引号的注意事项 ⚠️

你可能会好奇为什么有两种选项,为什么我可以使用单引号或双引号。

那么,如果你的字符串内部包含引号呢?例如,字符串“it's raining”或“she said "hello"”。为了表示这些字符串,你的代码可以使用另一种引号来包裹整个字符串。

如果你想在字符串中同时使用两种引号,我们将在后续课程中讨论。

总结 📚

本节课中我们一起学习了Python中的打印操作。我们了解了打印是观察程序运行状态的基本工具,掌握了使用print()函数打印字符串的方法,并认识了单引号和双引号在定义字符串时的作用与选择策略。现在,你可以开始尝试打印,这是你编程之旅的第一步。

004:如何运行Python程序演示 🚀

在本节课中,我们将学习如何运行你的第一个Python程序。我们将通过完成第一个实验来演示整个过程,从访问实验环境到编写、运行并调试简单的代码。所有操作都将在浏览器中完成,无需安装任何软件。


访问实验环境

上一节我们介绍了课程背景,本节中我们来看看如何进入并操作实验环境。

完成初始视频学习后,你会在第一周末尾看到“Getting Started with Python”部分。你需要点击其中的“Hello World Lab”链接。绿色的对勾表示已完成,但首次进入时你不会看到。

以下是进入实验的步骤:

  1. 点击“Hello World Lab”。
  2. 阅读实验说明。
  3. 点击“Launch Lab”按钮。

点击后,系统会为你打开一个新的浏览器标签页,加载实验环境。这个环境基于Visual Studio Code(简称VS Code),所有操作都将在浏览器内完成,非常方便。


熟悉VS Code界面

现在我们已经进入了实验环境,让我们来熟悉一下VS Code的界面布局。

界面最左侧是活动栏。你需要找到并点击看起来像两页叠放的图标,这是“资源管理器”(Explorer)图标。将鼠标悬停其上会显示“Explorer”提示。如果资源管理器窗口没有显示,点击此图标即可打开。

在资源管理器窗口中,你会看到“OUTLINE”和“LEARN”两个标签页。本实验中,我们关注“LEARN”标签页。这里列出了实验所需的文件和文件夹。对于第一个实验,没有文件夹,只有两个文件:

  • hello_world.py
  • instructions.md

阅读实验说明

在开始编码之前,让我们先查看实验的具体要求。

你需要点击instructions.md文件。为了获得更好的阅读体验(例如查看图片),请右键点击该文件,然后选择“Open Preview”(打开预览)。这样就能看到格式清晰的说明文档和图片了。请仔细阅读这些说明,了解实验目标。


查看并运行初始代码

了解了实验要求后,本节我们来看看初始代码并学习如何运行它。

现在,点击hello_world.py文件来查看代码。界面顶部标签页会显示文件名,下方则会显示文件所在的完整路径。

文件内只有一行代码:

print('hello world')

这行代码的作用是让计算机在屏幕上输出“hello world”这句话。

接下来运行这段代码。在界面右上角找到一个类似播放按钮的三角形,但我们需要点击它旁边的下拉箭头。在弹出的三个选项中,选择“Run Python File in Terminal”(在终端中运行Python文件)。选择这个选项是为了后续学习如何向程序输入数据。

点击后,界面底部会弹出终端窗口,并显示运行结果。你可以确认终端中已经打印出了“hello world”。


修改代码并再次运行

成功运行初始代码后,现在让我们根据实验要求修改它。

实验要求你将“world”替换成你自己的名字。例如,双击代码中的“world”一词,将其修改为“Kristen”。修改后的代码应如下所示:

print('hello Kristen')

修改后,记得点击保存按钮(在Mac上是Command + S,或通过菜单栏“File” -> “Save”)。

关于引号的重要提示:确保名字两侧的单引号')保留。如果你的名字中包含单引号(如O‘Connor),则需要将两边的单引号改为双引号"),例如:

print("hello O'Connor")

保存文件后,再次运行代码。由于VS Code记住了你上次的运行方式,现在可以直接点击那个三角形的播放按钮。运行后,终端会输出“hello Kristen”(或你输入的名字)。


常见错误与调试

在修改代码时,可能会不小心引入错误。本节我们来看看两种常见的语法错误及其表现。

错误1:遗漏开头的单引号
如果错误地删除了开头的单引号,代码会变成:

print(hello Kristen')

VS Code的语法高亮会发生变化(文本颜色变白),并且文件名旁会出现红色标记和数字“1”,表示检测到一个错误。将鼠标悬停在错误代码上会提示“SyntaxError: invalid syntax”(语法错误)。尝试运行会失败并显示同样的错误信息。

错误2:遗漏结尾的单引号
如果错误地删除了结尾的单引号,代码会变成:

print('hello Kristen)

此时,从开头的单引号到行末的所有内容(包括右括号)都会被高亮为字符串颜色。错误提示可能是“SyntaxError: EOL while scanning string literal”(扫描字符串字面值时遇到行尾错误)。EOL代表“End Of Line”。运行也会失败。

解决方法:仔细检查代码,确保字符串被正确的一对引号(单引号或双引号)包围。修复引号后,保存并重新运行即可。


总结

本节课中我们一起学习了运行第一个Python程序的完整流程。我们了解了如何访问Coursera上的实验环境,熟悉了VS Code的基本界面,学会了打开说明文件、查看和修改Python代码,并使用终端运行程序。我们还探讨了因遗漏引号而产生的两种常见语法错误及其表现形式。现在,你已经准备好去完成你的第一个实验了。恭喜,祝你学习愉快!

005:存储简单数据类型-名称与值

在本节课中,我们将要学习如何在计算机中存储数据,以便利用计算机来解决问题。我们将了解Python支持的不同数据类型,并学习如何通过变量为内存位置命名,从而方便地存储和访问数据。

数据类型概述

上一节我们介绍了存储数据的基本概念,本节中我们来看看Python支持哪些基本的数据类型。了解数据类型非常重要,因为它决定了你能对数据进行的所有操作。

Python支持多种数据类型,我们先来看三种最常见的数据类型。

字符串(String)

字符串是由字母、数字和其他字符组成的序列。在Python中,字符串类型简称为 str。字符串必须用单引号 ' 或双引号 " 括起来,引号本身不属于字符串的一部分。

以下是字符串的一些例子:

  • "Hello" 是一个由双引号包围的字符串。
  • 'Welcome to our course.' 是一个由单引号包围的字符串,可以包含空格。
  • "Wasn't 2019 a great year?" 这个字符串内部包含了一个单引号(在单词“Wasn't”中)。为了正确表示,整个字符串必须使用双引号包围。

浮点数(Float)

浮点数用于表示带小数点的数字。在Python中,浮点数类型简称为 float。一个浮点数由数字0-9、一个小数点 . 组成,如果数字为负数,则前面会有一个负号 -

以下是浮点数的一些例子:

  • 38.5
  • -6.0
  • 3.14159265

整数(Integer)

整数用于表示没有小数部分的数字。在Python中,整数类型简称为 int。一个整数由数字0-9组成,如果数字为负数,则前面会有一个负号 -

以下是整数的一些例子:

  • 89
  • -5
  • 0

变量与内存地址

我们已经了解了三种基本数据类型:字符串、浮点数和整数。接下来,我们需要知道如何将这些数据存储在计算机中以便使用。

数据存储在计算机内存的特定地址位置。这类似于现实世界中的建筑地址。例如,“110 Main Street”是“Best Pizza”这家店的地址。在计算机中,内存位置也有编号,我们称之为地址。

然而,让人记住所有数字地址(例如 2450)是非常困难的。因此,Python允许我们为这些内存位置起一个名字,这个名字就是变量

变量是一个用来引用存储在计算机内存中某个值的名称。

Python对变量名有明确的命名规则:

  • 变量名只能由字母、下划线 _ 和数字组成。
  • 变量名必须以字母开头。
  • 变量名应具有描述性,能反映其存储的值。

以下是有效和无效变量名的例子:

  • temperature (有效,由字母组成)
  • bike_price (有效,包含字母和下划线)
  • name3 (有效,包含字母和数字)
  • 3words无效,因为以数字开头)

赋值操作

上一节我们定义了变量,本节中我们来看看如何将值存入变量。我们通过赋值操作符 = 来给变量赋值。请注意,这里的 = 是赋值操作符,而不是数学中的等号。

赋值语句的格式是:先写变量名,然后是赋值操作符 =,最后是要存储的值。

以下是赋值操作的工作原理:

  • 当Python第一次遇到一个变量名时,它会为这个变量分配一个内存位置,并将值存储在那里。
  • 当你再次为同一个变量赋值时,Python会找到它已有的内存位置,并用新值替换旧值。

让我们通过一个例子来逐步理解这个过程。以下是四行Python代码,我们将从上到下逐行执行:

number = 89
value = 6
number = 3
amount = value
  1. 执行第一行 number = 89:Python发现 number 是首次出现,于是为它分配一个内存位置,并将整数值 89 存储进去。
  2. 执行第二行 value = 6:Python发现 value 是首次出现,为它分配一个内存位置,并将整数值 6 存储进去。
  3. 执行第三行 number = 3:Python发现 number 已经存在,于是找到它对应的内存位置,将存储的值从 89 更改为 3
  4. 执行第四行 amount = value:Python发现 amount 是首次出现,为它分配一个内存位置。然后,它计算 = 右边的表达式,即查找变量 value 的值(6),最后将这个值 6 存储到 amount 的内存位置中。

变量的三要素

当你使用变量时,需要关注三个核心要素:名称(Name)类型(Type)值(Value)

  • 名称:变量的标识符,例如 bird
  • 类型:由赋给变量的值决定。例如,如果赋的值是字符串 "blue jay",那么变量的类型就是 str
  • :变量当前存储的具体数据,例如 "blue jay"

考虑这个例子:bird = "blue jay"

  • 变量名是 bird
  • 因为 "blue jay" 是一个字符串,所以变量 bird 的类型是 str
  • 变量 bird 的值是字符串 "blue jay"

总结

本节课中我们一起学习了Python编程的基础知识。我们首先了解了三种基本数据类型:字符串(str)、浮点数(float)和整数(int)。接着,我们探讨了数据如何存储在计算机的内存地址中,并学习了如何使用变量为这些地址起一个易于记忆的名字。最后,我们掌握了通过赋值操作符 = 给变量赋值的方法,并理解了变量的名称、类型和值这三个关键概念。现在,你可以尝试在自己的编程环境中创建一些变量来巩固所学知识了。

006:在变量中存储数据演示 🐍

在本节课中,我们将学习如何在Python中使用变量存储数据。我们将通过编写代码、运行代码,并使用一个名为Python Tutor的可视化工具来逐步理解变量赋值和内存变化的过程。


环境设置与代码编写

首先,我们需要在Visual Studio Code中设置环境并创建一个Python文件。

  1. 打开Visual Studio Code。
  2. 将颜色主题更改为“Light+”。
  3. 在资源管理器中,点击“新建文件”图标。
  4. 将文件命名为 demo_variables.py 并确认。

现在,我们可以开始编写代码。


基础变量赋值与打印

我们将从简单的变量赋值开始,并打印它们的值。

amount = 8
number = amount
print("Amount is:", amount)
print("Number is:", number)

以下是代码执行过程的解释:

  • 第1行:创建变量 amount 并将其值设置为整数 8
  • 第2行:创建变量 number,并将 amount 的值(即 8)赋给它。
  • 第3行:print 语句打印字符串 "Amount is:" 和变量 amount 的值,中间用空格分隔。
  • 第4行:print 语句打印字符串 "Number is:" 和变量 number 的值。

运行此代码,输出结果为:

Amount is: 8
Number is: 8

变量值的重新赋值

上一节我们介绍了基本的变量赋值,本节中我们来看看如何更改变量的值。

bird = "blue jay"
bird2 = "shoveler"
bird = bird2
print("Bird is:", bird)
print("Bird2 is:", bird2)

以下是代码执行过程的解释:

  • 第5行:创建变量 bird 并赋值为字符串 "blue jay"
  • 第6行:创建变量 bird2 并赋值为字符串 "shoveler"
  • 第7行:将变量 bird2 的值("shoveler")重新赋给变量 bird。此时,bird 原有的值 "blue jay" 被覆盖。
  • 第8、9行:打印两个变量的值。

运行此部分代码,输出结果为:

Bird is: shoveler
Bird2 is: shoveler

可以看到,bird 的值已经变为 "shoveler"


使用Python Tutor可视化代码执行

为了更清晰地理解每行代码如何影响变量,我们将使用Python Tutor(pythontutor.com)这个在线工具。

以下是使用Python Tutor的步骤:

  1. 访问 pythontutor.com。
  2. 点击“Start writing and visualizing code now”。
  3. 在代码编辑区粘贴或输入你的Python代码。
  4. 点击“Visualize Execution”按钮。

工具界面主要分为两部分:

  • 左侧:显示你的代码。一个绿色箭头指向刚刚执行完的代码行,一个红色箭头指向即将执行的代码行。
  • 右侧:显示程序的内存状态(Global frame)和输出结果(Output)。在全局帧中,你可以看到每个变量及其当前值。

让我们用之前的完整代码在Python Tutor中逐步执行。点击“Next”按钮,观察每一步中变量的创建和值的变化。你还可以使用“Previous”按钮回退,观察值是如何被改变的。


进一步的变量操作演示

为了加深理解,我们在原有代码中增加一行,观察变量值的独立变化。

amount = 8
number = amount
print("Amount is:", amount)
print("Number is:", number)

bird = "blue jay"
bird2 = "shoveler"
bird = bird2
bird2 = "finch"  # 新增行:重新给bird2赋值
print("Bird is:", bird)
print("Bird2 is:", bird2)

在Python Tutor中执行这段代码:

  1. 执行到第7行(bird = bird2)时,bird 的值变为 "shoveler"
  2. 执行新增的第8行(bird2 = "finch")时,bird2 的值被更新为 "finch"请注意,这个操作不会影响 bird 的值,因为 bird 存储的是 bird2 赋值时刻的值的一个副本,而非引用。
  3. 最后的打印结果将是:
    Bird is: shoveler
    Bird2 is: finch
    

这个例子说明,修改变量 bird2 的值后,变量 bird 的值保持不变,因为它们指向内存中不同的数据。


总结

本节课中我们一起学习了:

  1. 变量的创建与赋值:使用 = 运算符将数据存储在变量中。
  2. 变量的重新赋值:变量可以接收新的值,旧值会被覆盖。
  3. 使用 print 语句:可以打印多个项目,用逗号分隔。
  4. 利用Python Tutor进行可视化调试:这是一个强大的工具,可以逐步执行代码并观察内存中变量的状态变化,帮助我们深入理解代码的执行逻辑。

记住,变量是存储信息的容器,理解数据如何在变量间传递和变化是编程的基础。

007:在Python中使用数学运算符 - 第一部分 🧮

在本节课中,我们将学习Python中的数学运算符。这些运算符就像计算器上的按键,允许我们对变量和常量执行各种数学运算。我们将从最基础的赋值运算符开始,逐步介绍加法、减法、乘法、除法等算术运算符,并学习Python如何遵循特定的运算顺序来求解复杂的表达式。

赋值运算符

上一节我们提到了运算符的基本概念,本节中我们首先来看看一个我们已经见过的运算符:赋值运算符。

赋值运算符(=)用于将值存储在变量中。其基本模式是:变量名位于运算符左侧,要赋给变量的值或表达式位于右侧。

例如:

x = 5

在这个例子中,变量 x 被赋予了整数值 5

算术运算符

了解了如何存储值之后,我们来看看如何操作这些值。以下是Python中主要的算术运算符及其用法。

假设我们有一个变量 x,其值为 5

运算符 名称 描述 示例 结果
+ 加法 将两边的值相加。 x + 2 7
- 减法 从左边的值中减去右边的值。 x - 2 3
* 乘法 将两边的值相乘。 x * 2 10
/ 除法 将左边的值除以右边的值。结果总是浮点数。 x / 2 2.5
% 取模 返回除法运算的余数。 x % 2 1
** 幂运算 将左边的值提升到右边值的幂次方。 x ** 2 25
// 整除 执行除法,但结果向下取整到最接近的整数。 x // 2 2

关于除法的重要说明:普通除法(/)的结果总是浮点数,即使能整除。例如 10 / 5 的结果是 2.0,而不是 2。此外,除以零会导致 ZeroDivisionError

关于取模运算符:它返回除法后的余数。对于 x % 2,计算过程是 5 除以 2 等于 21,因此结果为 1

关于整除:与普通除法不同,如果操作数都是整数,整除(//)的结果是整数(向下取整)。如果操作数包含浮点数,结果则是浮点数。

表达式与赋值

我们已经知道如何将简单的值赋给变量。实际上,赋值运算符的右侧可以是更复杂的表达式。Python会先计算整个表达式的结果,然后将这个单一的结果值存储到变量中。

让我们通过一个例子来理解这个过程:

num = 20
num = num % 3 + 5
  1. 第一行执行后,num 的值为 20
  2. 执行第二行时,Python会先计算表达式 num % 3 + 5 的值。
  3. 计算过程:20 % 3 等于 2,然后 2 + 5 等于 7
  4. 计算结果 7 被赋给变量 num,覆盖了之前的值 20

运算符优先级(运算顺序)

当表达式中包含多个运算符时,Python会遵循严格的运算顺序,这被称为“运算符优先级”。这与数学中的先乘除后加减规则类似。

以下是核心算术运算符的优先级顺序(从高到低):

  1. 括号 ():拥有最高优先级,用于强制改变运算顺序。
  2. 幂运算 **
  3. 乘法 *、除法 /、取模 %、整除 //(优先级相同)
  4. 加法 +、减法 -(优先级相同)

同级运算符的运算规则:当遇到优先级相同的运算符(如 */)时,Python按照从左到右的顺序进行计算。

括号的作用:括号不仅可以强制改变运算顺序,还能让复杂的表达式更清晰易读。如果有多层括号,Python会从最内层的括号开始计算。

运算顺序实例分析

让我们通过两个例子来实践一下运算顺序规则。

示例一:num % 3 + 5 (假设 num = 20)

  1. % 的优先级高于 +,所以先计算 20 % 3,得到 2
  2. 表达式变为 2 + 5
  3. 计算加法,得到最终结果 7

示例二:3.5 + 1.5 * 8 / 2 ** 2

  1. 优先级最高的是幂运算 **2 ** 2 = 4
  2. 接着是乘法 * 和除法 /(同级,从左到右):1.5 * 8 = 12.0
  3. 然后进行除法:12.0 / 4 = 3.0
  4. 最后进行加法:3.5 + 3.0 = 6.5
  5. 最终结果为 6.5

示例三:使用括号改变顺序 (3.5 + 1.5 * (8 / 2)) ** 2

  1. 计算最内层括号:8 / 2 = 4.0
  2. 计算外层括号内的表达式:1.5 * 4.0 = 6.0,然后 3.5 + 6.0 = 9.5
  3. 最后进行幂运算:9.5 ** 2 = 90.25
  4. 最终结果为 90.25

可以看到,通过添加括号,我们完全改变了运算的顺序和最终结果。

总结

本节课中我们一起学习了Python数学运算符的第一部分。我们首先认识了用于存储值的赋值运算符=)。然后,我们详细介绍了七种基本的算术运算符:加法(+)、减法(-)、乘法(*)、除法(/)、取模(%)、幂运算(**)和整除(//),并特别注意了除法总是返回浮点数这一特性。

最后,我们探讨了运算符优先级规则,这是Python求解复杂表达式的关键。记住优先级顺序:括号 > 幂运算 > 乘、除、取模、整除 > 加、减。同级运算从左到右进行,使用括号可以明确或改变运算顺序。理解这些规则对于编写和调试Python代码至关重要。

008:在Python中使用数学运算符 - 第二部分 🧮

在本节课中,我们将要学习字符串数据类型如何与数学运算符交互,以及不同数据类型混合运算时的规则。我们还会介绍一种更简洁的赋值运算符写法。

字符串与运算符

上一节我们介绍了数字类型的基本运算,本节中我们来看看字符串类型。字符串数据类型在使用运算符时的行为与我们目前讨论的运算符有所不同。

只有两种运算符可以用于字符串,即加法(+)和乘法(*)。

字符串加法(连接)

加法运算符将字符串组合在一起以创建一个新字符串。这个过程也称为字符串连接。让我们看一个例子。

"fast " + "train"

加法运算符将按照从左到右的顺序组合两个字符串,创建一个新字符串。因此,此表达式的结果字符串是 "fast train"

以下是另一个例子:

"5" + "4" + "1"

虽然 541 是数字,但由于它们周围有引号,Python 会将它们解释为字符串。

该表达式有两个优先级相同的运算符。根据我们之前的规则,首先计算最左边的运算符。字符串 "5" 加上字符串 "4"。这会产生一个按该顺序组合 "5""4" 的新字符串。你可能会想,为什么我们没有将 5 和 4 相加得到 9?请记住,由于有引号,"5""4" 是字符串,它们不再是整数。因此,此表达式是将两个字符串连接在一起以创建一个新字符串。

与上一个 "fast train" 的例子类似,计算完第一个运算符后,我们继续计算第二个运算符。这里我们有字符串 "54",后跟加法运算符,再后跟字符串 "1"。这两个字符串被组合起来创建新字符串 "541"。所以这个表达式的结果是字符串 "541"

字符串乘法(重复)

另一个可以用于字符串的运算符是乘法运算符。乘法运算符将字符串重复整数次。这可能听起来令人困惑,让我们看一个例子来帮助理解。

"high" * 3

此表达式将创建一个新字符串,将 "high" 中的每个字母复制并重复三次。因此,结果字符串是 "highhighhigh"

请注意,每个 "high" 字符串副本之间没有空格。这是因为除非你明确包含该空格,否则 Python 不会为你添加任何空格。

让我们看另一个例子:

4 * "by "

请注意,在这个例子中,整数在前,字符串在后,顺序并不重要。还要注意,字符串 "by " 末尾有一个空格字符。本质上,该字符串由字符 BY 和一个空格字符组成。

此表达式计算结果为字符串 "by by by by "。这是原始字符串重复四次。与上一个例子不同,这个例子的单词之间有间距。这是因为我们在表达式的原始字符串末尾包含了空格字符。空格字符与 BYE 字符一样,也被重复了四次。

不同数据类型的混合运算

我们在之前的视频中学习了不同的数据类型。当你使用不同的数据类型时,运算符的行为可能与预期不同。

以下是不同数据类型运算的示例:

  • 整数与整数6 + 1 的结果是整数 7
  • 小数与小数8.0 + 2.0 的结果是小数 10.0

那么 3 + 5.0 呢?这里我们有一个整数和一个小数相加。结果是小数字 8.0。为什么?小数的小数部分是信息,即使它只是 .0,我们可能也不想丢失。因此,当将 35.0 相加时,Python 会将 35 相加,并保留 .0,使结果为 8.0

我们可以将整数与字符串相加吗?不可以。尝试将整数和字符串相加,你会看到一条错误消息。收到错误消息是编程的自然组成部分。作为程序员,你会看到无数的错误消息,随着练习的增加,你会更好地理解它们。

在示例中,我们收到了一个类型错误消息,它提醒我们不能使用加法运算符来添加整数和字符串。

那么乘法呢?我们可以将整数与字符串相乘吗?是的,可以。为什么可以相乘而不能相加?老实说,我也不知道。这样做只是 Python 创建者决定允许程序员做的事情,他们认为这会有帮助。通过将整数 3 乘以字符串 "5",你将得到字符串 "5" 重复三次。当你希望一个字符串一遍又一遍地重复时,乘法运算符很有用。

现在有数百万个运算符可以与不同的数据类型一起使用,我们不可能全部展示。因此,我鼓励你自己尝试一下,看看能得到什么。结果可能会让你惊讶。

赋值运算符

我们讨论了算术运算符,现在让我们快速看一下赋值运算符。

查看表格的第一行,我们有我们已经熟悉的传统赋值运算符。

x = x + 2

首先计算 x + 2,然后将结果值存储回变量 x

我们可以使用复合赋值运算符以更短的方式编写相同的表达式。

x += 2

该表达式的工作方式完全相同,只是看起来有点不同。首先计算 x + 2,然后将结果值存储回 x

中间列显示的这些复合赋值运算符适用于我们已经见过的所有其他算术运算符。你不必使用这些,你可以只坚持使用最右列显示的传统赋值运算符。但了解它们的存在及其工作原理是很好的。

随着你在这门课程中的深入,你将接触到更多的运算符,但现在你应该练习将这些运算符与不同的数据类型一起使用,看看你是否能自己预测结果。

总结

本节课中我们一起学习了字符串与加法、乘法运算符的交互,了解了字符串连接和重复的概念。我们还探讨了不同数据类型(如整数、小数、字符串)混合运算时的规则,特别是整数与小数相加会得到小数,而整数与字符串不能相加但可以相乘。最后,我们介绍了复合赋值运算符(如 +=),它们提供了一种更简洁的方式来更新变量的值。掌握这些运算符的用法是进行有效Python编程的基础。

009:在Python中使用数学运算符演示

在本节课中,我们将通过Python Tutor工具,详细学习Python中各种数学运算符的使用方法、优先级规则以及它们与字符串操作符的区别。我们将通过创建变量、构建表达式并逐步执行代码来直观地理解这些概念。

变量创建与赋值

首先,我们创建三个变量并为其赋值。

x = 10.0
y = 5
z = 2

以上代码创建了三个变量:x被赋予浮点数值10.0yz被赋予整数值52

运算符优先级初探

上一节我们创建了变量,本节中我们来看看如何使用这些变量构建表达式,并理解运算符的优先级。

我们将创建一个名为so的变量,用于存储表达式x - y * z的结果。

so = x - y * z

在这个表达式中,我们使用了减法运算符-和乘法运算符*。根据数学规则,乘法运算符的优先级高于减法运算符。因此,计算顺序如下:

  1. 先计算 y * z,即 5 * 2 = 10
  2. 再计算 x - 10,即 10.0 - 10 = 0.0

使用Python Tutor逐步执行代码,可以验证变量so的最终值为0.0。这里的结果是0.0而非0,是因为当一个表达式中包含浮点数时,结果通常也会是浮点数,以保留小数部分的信息。

使用括号改变优先级

如果我们不希望先计算y * z,可以使用括号()来改变默认的运算顺序。

so = (x - y) * z

括号内的表达式拥有最高的优先级。因此,计算顺序变为:

  1. 先计算 (x - y),即 10.0 - 5 = 5.0
  2. 再计算 5.0 * z,即 5.0 * 2 = 10.0

执行后,变量so的值变为10.0

地板除与取模运算符

接下来,我们探讨地板除//和取模%运算符。它们具有相同的优先级。

so = x // 4 % z

当运算符优先级相同时,Python会从左到右进行计算。

  1. 先计算 x // 4,即 10.0 // 410.0 / 4 = 2.5,地板除会向下取整,得到2.0
  2. 再计算 2.0 % z,即 2.0 % 22能被2整除,余数为0.0

因此,表达式的结果是0.0

括号与错误处理

我们可以通过括号强制改变计算顺序,但这有时可能导致错误。

so = x // (4 % z)

计算顺序如下:

  1. 先计算 (4 % z),即 4 % 2 = 0
  2. 再计算 x // 0,即 10.0 // 0

由于除数不能为零,Python会抛出ZeroDivisionError错误。错误信息float divmod提示我们在除法或取模运算中分母为零。修复此错误的方法是确保分母不为零,例如修改表达式为x // (14 % y)

复杂表达式与幂运算

现在,让我们看一个包含加法、除法、括号和幂运算**的更复杂表达式。

so = ((x + 2) / z) ** 2

计算遵循从内到外的原则:

  1. 最内层括号:(x + 2) = 10.0 + 2 = 12.0
  2. 外层括号:(12.0 / z) = 12.0 / 2 = 6.0
  3. 幂运算:6.0 ** 2 = 36.0

执行后,so的值是36.0

字符串与运算符

运算符不仅用于数字,也可用于字符串,但行为完全不同。

字符串重复
使用乘法运算符*可以重复字符串。

animal = “cow”
so = animal * y  # y的值为5

结果so是字符串”cowcowcowcowcow”。注意,字符串之间没有自动添加空格。如果需要空格,必须在原字符串中明确包含,例如 animal = “cow “

字符串拼接
使用加法运算符+可以拼接字符串。

so = “happy” + animal  # animal为”cow”

结果so是字符串”happycow”。同样,如果需要空格,必须显式添加:”happy “ + animal”happy” + “ “ + animal

重要限制
字符串重复要求乘数必须是整数int。使用浮点数float会引发TypeError

so = animal * 2.5  # 会报错:TypeError

总结

本节课中我们一起学习了Python中运算符的核心用法:

  1. 数学运算符(如+, -, *, /, //, %, **)遵循特定的优先级规则,括号()可以改变计算顺序。
  2. 类型影响结果:涉及浮点数的运算通常产生浮点数结果。
  3. 字符串运算符+用于拼接,*用于重复(仅接受整数)。
  4. 实践是关键:理解代码执行的最好方式是亲自动手编写和测试表达式,并尝试解读产生的错误信息,这是提升编程技能的重要途径。

建议你尝试创建自己的表达式,先预测结果,再通过Python Tutor或其它开发环境运行验证,这是巩固知识的最佳方法。

010:类型转换

概述

在本节课中,我们将要学习Python中的类型转换。类型转换是一种在不同数据类型之间进行转换的有效方法。我们将探讨如何确定变量的数据类型,以及如何显式和隐式地进行类型转换。

数据类型回顾

在深入探讨类型转换之前,我们先简要回顾一下Python中的几种基本数据类型。

  • 字符串(str):由引号包围的文本,例如 "hello""Python"
  • 整数(int):没有小数部分的数字,例如 1020500
  • 浮点数(float):包含小数部分的数字,例如 3.780.049

你可以将任何这些数据类型的值赋给一个变量。

确定数据类型

在编程过程中,有时你可能不确定某个变量的当前数据类型。例如,一个变量最初被赋值为字符串,但后来又被重新赋值为整数。为了确定一个变量或值的类型,Python提供了一个非常方便的函数:type()

以下是使用type()函数的方法:

type(变量名或值)

让我们看一些例子:

  • type("hello") 会确定 "hello" 是字符串类型。
  • type(7.75) 会确定这个数字是浮点数类型。
  • type(50) 会确定这个数字是整数类型。
  • type(word) 会确定变量 word 的类型(假设它存储了类似 "cat" 的字符串)。
  • type("10") 会确定 "10" 是字符串类型,因为引号内的数字在Python中被解释为字符串。

为了看到type()函数的结果,我们通常将其与print()函数结合使用。

print(type("hello"))

执行这行代码会在控制台输出类似 <class 'str'> 的结果,表明 "hello" 的数据类型是字符串(str)。在本课程范围内,你可以将 class 理解为数据类型的同义词。

什么是类型转换?

上一节我们介绍了如何确定数据类型,本节中我们来看看如何改变数据的类型。类型转换是指将变量从一种数据类型转换为另一种数据类型的过程。

例如,你有一个包含数字 "10" 的字符串,但你想用它进行数学运算。由于字符串不支持大多数数学运算符,你需要将其转换为整数。这时就需要用到类型转换。

类型转换的基本格式是:目标数据类型(要转换的值或变量)

例如:

  • int("10") 将字符串 "10" 转换为整数 10
  • float(20) 将整数 20 转换为浮点数 20.0

隐式类型转换

类型转换主要分为两类:隐式和显式。你可能已经在不知不觉中使用了隐式类型转换。当Python确定转换不会导致数据丢失时,它会自动执行隐式类型转换。

以下是一些隐式类型转换的例子:

  • x = 8 / 4 结果为 2.0。除法运算符 (/) 总是返回浮点数,因此整数 84 被隐式转换为浮点数进行计算。
  • y = 0.0 + 9 结果为 9.0。当一个浮点数和一个整数相加时,Python会将整数隐式转换为浮点数,然后执行加法。

显式类型转换

与自动发生的隐式转换不同,显式类型转换需要你手动指定要转换的内容以及目标数据类型。这种转换有时可能导致数据丢失。

显式类型转换遵循我们之前提到的格式:目标数据类型(值或变量)

以下是几个例子:

  • x = int(5.9) 将浮点数 5.9 转换为整数 5。Python不会四舍五入,而是直接舍弃小数部分。
  • y = str(0.045) 将浮点数 0.045 转换为字符串 "0.045"
  • z = float(3) 将整数 3 转换为浮点数 3.0

让我们看一个稍复杂的例子:

txt_num = "50.5"  # 这是一个字符串
num = float(txt_num)  # 显式转换为浮点数

现在,变量 num 中存储的是浮点数 50.5

数据丢失的注意事项

在显式类型转换中,数据丢失通常发生在整数和浮点数之间的转换。因为浮点数包含小数部分,而整数只包含整数部分。

  • 从整数转换为浮点数:不会丢失数据。例如,float(5) 得到 5.0,所有信息都得以保留。
  • 从浮点数转换为整数:可能会丢失小数部分的数据。例如,int(2.6) 得到 20.6 被丢弃了。如果你再将结果 2 转换回浮点数,得到的是 2.0,而不是原来的 2.6

这种数据丢失不一定是坏事,可能正是你的意图(例如,你只想要整数部分)。但如果你没有意识到这一点,就可能导致程序出现错误或计算结果不正确。

总结

本节课中我们一起学习了Python中的类型转换。我们首先回顾了如何用type()函数确定变量或值的数据类型。然后,我们探讨了类型转换的概念,区分了自动发生的隐式类型转换和需要手动指定的显式类型转换。最后,我们特别强调了在进行显式类型转换(尤其是从浮点数转换为整数时)可能发生的数据丢失问题。类型转换是编程中非常有用的工具,但使用时务必留意潜在的数据精度变化。

011:变量类型与类型转换演示 🐍

在本节课中,我们将学习如何使用 type() 函数来确定变量的数据类型,以及如何通过类型转换(或称为类型铸造)来改变值的类型。我们将通过创建变量、检查其类型,并演示隐式和显式类型转换的过程来掌握这些核心概念。

创建变量与使用 type() 函数

首先,我们创建一些变量,并使用 type() 函数来确定它们的数据类型。我们将使用 Python Tutor 来可视化变量的值。

以下是我们要创建的变量:

  • nu:赋值为整数 10
  • words:赋值为字符串 "stay curious"
  • pi:赋值为浮点数 3.1415

为了查看 type() 函数的结果,我们需要使用 print() 函数将结果输出。

nu = 10
words = "stay curious"
pi = 3.1415

print(type(nu))
print(type(words))
print(type(pi))

运行上述代码后,输出结果将显示:

  • nu 的类型是 int(整数)
  • words 的类型是 str(字符串)
  • pi 的类型是 float(浮点数)

type() 函数的基本语法是 type(变量或值),它会返回指定变量或值的数据类型。

类型转换(类型铸造)

类型转换允许我们将一个数据类型转换为另一个数据类型。Python 支持两种类型转换:隐式类型转换和显式类型转换。

隐式类型转换

隐式类型转换是 Python 自动执行的类型转换,通常发生在不同数据类型的值进行运算时。例如,当整数与浮点数相乘时,Python 会自动将整数转换为浮点数。

nu = 10
nu = nu * 0.25  # nu 现在是 2.5,类型为 float

在这个例子中,nu 从整数 10 隐式转换为浮点数 2.5

显式类型转换

显式类型转换需要手动指定目标数据类型。以下是常用的类型转换函数:

  • int():转换为整数
  • float():转换为浮点数
  • str():转换为字符串

例如,将浮点数 2.5 显式转换为整数:

nu = 2.5
nu = int(nu)  # nu 现在是 2,类型为 int

需要注意的是,从浮点数转换为整数时,小数部分会被丢弃,这可能导致数据丢失。

字符串拼接与类型转换

在 Python 中,字符串拼接要求所有操作数都是字符串类型。如果尝试将非字符串类型与字符串拼接,会导致类型错误。

例如,以下代码会报错:

nu = 2.5
print("The value of nu is " + nu)  # 错误:类型不匹配

为了解决这个问题,我们需要使用 str() 函数将非字符串类型转换为字符串:

nu = 2.5
print("The value of nu is " + str(nu))  # 正确:显式转换为字符串

数据丢失问题

在类型转换过程中,可能会发生数据丢失。例如,将浮点数 2.5 转换为整数时,小数部分 0.5 会被丢弃。即使之后再将整数转换回浮点数,丢失的数据也无法恢复。

nu = 2.5
nu = int(nu)  # nu 现在是 2
nu = float(nu)  # nu 现在是 2.0,而不是 2.5

因此,在进行类型转换时,需要特别注意数据丢失的问题,确保转换符合预期。

总结

本节课中,我们一起学习了:

  1. 使用 type() 函数确定变量或值的数据类型。
  2. 隐式类型转换:Python 自动执行的类型转换。
  3. 显式类型转换:手动使用 int()float()str() 等函数进行类型转换。
  4. 字符串拼接时的类型转换要求。
  5. 类型转换中可能发生的数据丢失问题。

掌握类型转换是编程中的重要技能,它帮助我们在不同数据类型之间灵活操作,确保代码的正确性和效率。在实际编程中,请根据需求谨慎选择类型转换方式,避免不必要的数据丢失。

012:浮点数-计算机中的十进制数

概述

在本节课中,我们将要学习Python中浮点数(float)的详细知识。我们将了解浮点数的构成、它在计算机内部的表示方式、其强大的表示能力以及一些重要的局限性。通过学习,你将能够更明智地在程序中使用浮点数。

浮点数的构成

我们已经使用浮点数数据类型一段时间了。现在,让我们花几分钟时间更详细地了解什么是浮点数以及它在Python中如何工作。

到目前为止,我们知道在Python中,任何带有小数点的数字都是浮点数。术语“float”是“floating point number”(浮点数)的缩写。在本课程中,我们可能会互换使用“float”和“floating point number”来指代十进制数。

让我们看看构成浮点数的不同部分。这里我们有一个浮点数的例子:-3.495

一个浮点数由三部分组成:

  1. 第一部分是与数字关联的符号,它和数字本身一起存储在计算机内存中。在这个例子中,符号表示这是一个负数。如果数字是正数,Python会省略符号不显示,但正数的符号仍然存在于计算机内存中。
  2. 然后是小数点
  3. 最后,尾数是构成数字本身的所有数字。在这个例子中,尾数是3、4、9和5。

这是到目前为止课程中浮点数一直以来的表示方式。

科学计数法表示

但在Python中,在所有代码背后,你的浮点数是用科学计数法表示的。这是浮点数在计算机中的存储方式。

使用科学计数法,你有:

  • 符号:告诉你数字是正数还是负数。
  • 尾数:构成数字本身的所有数字。
  • 基数:由于我们使用的数字由0到9的数字组成,基数将始终是10。也就是说,我们可以使用0到9这10个唯一数字的组合来表示任何数字。
  • 指数:告诉我们小数点的位置。

浮点数的这两种表示方式是等价的。它们只是书写同一个数字的不同方式。

浮点数的“浮动”特性

就像在经典数学中一样,你可以使用Python表达式移动小数点。这也会改变数值。

例如,你可以看到小数点向右移动了一位。注意科学计数法中的指数值也发生了变化,它从-3变成了-2。小数点再次向右移动,科学计数法中的指数也相应改变以反映这一点。

这就是浮点数被称为“浮动”的原因。通过改变指数,小数点可以在尾数周围“浮动”。

浮点数的表示范围

浮点数是强大的数据类型,可以存储巨大的数字。一个大数字的例子是 1.79E308。这个数字可以用科学计数法表示为 1.79 × 10^308

同样,浮点数也可以存储极小的数字。2.22e-308 就是这样一个例子。用科学计数法表示,这个数字是 2.22 × 10^-308

Python支持的最大浮点数值是正无穷大,简称 inf。让我们看一个例子:

var = float('inf')

注意 inf 是一个被显式类型转换为浮点数的字符串。这是因为无穷大是Python中的一个特殊值。实际上,要将这个值存储到变量中,你需要将其写为字符串并显式类型转换为浮点数。需要指出的是,Python中的无穷大不是无限位数的数字,而是经典数学中已知的正无穷大的概念。

一旦你将无穷大赋值给一个变量(就像我们对 var 所做的那样),该变量将包含一个比Python中任何其他浮点数值都大的值。它是Python能支持的最大浮点数值。

如果 inf 难以记住,你也可以写完整的单词 infinity。编写代码实现期望结果的方法有很多,有偏好是可以的。在这种情况下,你可以写 infinfinity,两者都有效,选择你喜欢的即可。

Python支持的最小值是负无穷大。让我们看一个例子:

var = float('-inf')

与前面的例子类似,负无穷大是Python中的一个特殊值。要将负无穷大赋值给一个变量,你需要写字符串 -inf 并将其类型转换为浮点数。

同样,如果 -inf 难以记住,你也可以写 -infinity

浮点数的局限性

尽管浮点数是引人注目的数据类型,但它们远非完美。因此,在使用浮点数时,你需要记住一些限制。

浮点数可以非常大或非常小,但位数是有限的。因此,如果你的数字太大或太小,你可能会收到错误信息,或者你的数字将被转换为正无穷大或负无穷大。

此外,浮点数并不总是精确的。它们容易受到舍入误差的影响。Python浮点数在大约第15、16或17位数字之前是精确的,具体位数取决于数字本身。在此之后,它们就不能完全被信任了。

除非你编写需要极高精度的高级数学表达式,否则舍入误差不会带来太大问题。

舍入误差示例

接下来,让我们看一些舍入误差的例子。

在经典数学中,2除以3是0.666...,其中6无限循环。这些6没有尽头。为什么这在Python中会成为一个问题?我们知道Python不支持无限位数,那么Python中2除以3的结果是什么?

结果是0,后面跟着小数点,然后是16个数字6。所以,不算数字前面的零,Python将无限个6舍入为只有16个。因此,Python近似地表示了2除以3的解,因为它无法存储完整的解。

你可能也注意到了,既然我们无法表示无限个6,那么将最后一位数字四舍五入为7会更准确。Python并不完美。

那么100.2加100.4呢?直观上,这应该是200.6。但在Python中是多少?在Python中,100.2加100.4是200.6,后面跟着许多零和一个末尾的2。如果我们数一下位数,你会发现2是第17位数字。不知何故,所有尾随的零都被四舍五入成了数字2。这不太合理,对吧?这是浮点数容易受到舍入误差影响的另一个例子。

幸运的是,对我们来说,第17位数字的误差非常小,在大多数(虽然不是所有)程序中不会成为问题。这些错误可能并不总是发生,但它们发生的频率足以让你意识到它们的存在。

类型转换中的舍入问题

让我们看看舍入问题可能出现的另一个地方,那就是在字符串和浮点数之间进行转换时。

快速回顾一下,我们有 float(),括号内是字符串 ‘5.0’。这求值得到浮点值5.0。所以本质上,字符串 ‘5.0’ 被转换成了浮点值5.0。我们可以通过写 str(),括号内写浮点值5.0,将其转换回字符串。这求值得到字符串 ‘5.0’

假设我有一个由0、小数点、16个数字3和末尾两个数字9组成的字符串。将这个长字符串转换为浮点数将产生一个与浮点数据类型近似相同的值。不算任何前导零,我们可以看到主要区别在于最后一位数字(在这种情况下是第16位)被四舍五入为4,而不是3、9、9。在这个例子中,数字被正确地四舍五入了。但如果你期望的是精确的数字转换而不是四舍五入的数字,就可能会出现问题了。

让我们回到0、小数点、16个数字3和末尾两个数字9。现在这不是一个字符串,而是一个我们试图转换为字符串的浮点数。通过将这个浮点值显式类型转换为字符串,我们得到一个表示相同近似值的字符串,但第16位数字被四舍五入为4,而不是3、9、9。再次强调,在数字被转换为字符串之前,这个数字被正确地四舍五入了。但如果你期望的是精确转换,即你的意图是完全没有四舍五入,就可能会出现问题了。

浮点数的历史与进步

尽管有这些限制,浮点数仍然是非常强大的数据类型,你可以从中受益匪浅。可能看起来不是这样,但浮点数据类型多年来已经有了显著的改进。

在20世纪50年代和60年代,美利坚合众国(USA)希望将人送入太空。但要做到这一点,他们需要克服一个大问题:计算机容易发生故障和停电,因此不能信任它们进行太空旅行所需的计算。

美国需要一位杰出的数学家,能够以极高的精度手动解决复杂的数学方程。于是出现了凯瑟琳·约翰逊,一位黑人女数学家。她因能够手工精确解决这些复杂数学方程而获得了广泛认可。她的计算对太空任务的成功至关重要。

当计算机开始进行计算时,她会将计算机的结果与她的计算进行交叉参考,以确定计算机是否正确。每次结果分析出现差异时,结果总是计算机出错了。当美国国家航空航天局(NASA)——负责太空探索的美国政府机构——首次开始使用计算机进行火箭计算时,宇航员约翰·格伦对计算机持谨慎态度。他特别要求凯瑟琳·约翰逊在他飞行前检查所有计算,而他绕月飞行的任务非常成功。

由于种族和性别偏见,凯瑟琳·约翰逊对太空旅行的巨大贡献多年来一直被低估。但她的辛勤工作最终得到了公众的认可。她甚至被授予了总统自由勋章,这是美国平民可以获得的最高荣誉。她还出现在《隐藏人物》一书中,该书后来被拍成了电影。

总结

本节课中,我们一起学习了Python中浮点数的核心知识。我们了解了浮点数的构成部分(符号、尾数、指数),以及它在计算机内部通过科学计数法表示的原理。我们看到了浮点数能够表示极大和极小的数值,但也明确了其关键局限性:有限的精度和不可避免的舍入误差。最后,通过凯瑟琳·约翰逊的故事,我们回顾了高精度计算的历史背景。希望你在享受学习浮点数更多知识的同时,也能在使用它们时牢记这些特性与限制。

013:让你的程序具有交互性 🎯

在本节课中,我们将学习如何让Python程序变得具有交互性。这意味着程序将能够接收来自用户的输入,并根据输入产生不同的结果,而不是每次运行都执行完全相同的操作。

为什么需要程序交互性?

为了使程序具有通用性,即让程序能产生不同且有意义的输出,你需要向程序输入数据。如果没有输入,程序每次运行时都会做同样的事情。虽然可以通过生成随机结果来实现变化,但本节我们专注于学习如何获取用户输入。

获取输入的基本方法:input()函数

在Python中,获取简单数据的一种方法是使用内置的input()函数。当程序运行时,此函数允许你输入数据,供后续代码使用。每次运行程序时,你都可以输入不同的内容。

程序如何知道何时需要输入数据呢?你需要提供一个提示语,它会显示在控制台中,告知用户此时应输入数据。这个提示语被称为“提示符”。

用户与程序交互

大多数程序都设计给某人使用或运行,这个人被称为“用户”。程序应设计得易于用户运行。使用input()函数时,你的代码需要提供一个提示符,向用户解释需要输入什么内容。

input()函数会将用户输入的值转换为字符串数据类型。这个过程也称为“读取用户输入”。一旦程序读取了输入,就可以在程序的其他地方使用这个值。

input()函数语法

以下是使用input()函数的基本语法。由于input()会返回一个值,最好将其存储在一个变量中。

variable_name = input("提示信息:")
  • variable_name:位于赋值运算符(=)左侧的变量名,用于存储输入的值。
  • input():位于赋值运算符右侧的函数。
  • "提示信息:":放在input()函数括号内的字符串,作为提示符显示给用户。

编写清晰、描述性的提示符是一个好习惯,这样用户就不会猜测应该输入什么。

一个简单的例子

让我们看一个具体的例子:

name = input("What is your name? ")
print("Hello, " + name)
print(name + " is a nice name.")

以下是代码执行过程的逐步解析:

  1. 执行第一行代码:这是一个赋值语句。首先执行赋值运算符右侧的input("What is your name? ")
    • input函数在终端显示提示符:What is your name? (注意末尾有一个空格,以便与用户输入的内容分隔开)。
    • 程序暂停,等待用户输入。
  2. 用户输入:假设用户输入Aemla,然后按回车键。程序需要回车键来确认输入结束。
  3. 完成赋值:字符串"Aemla"被存储在变量name中。
  4. 执行第二行代码print函数查找变量name的值("Aemla"),将其与字符串"Hello, "连接,然后打印出:Hello, Aemla
  5. 执行第三行代码:再次查找name的值,构建新字符串并打印:Aemla is a nice name.

将输入存储在变量中的好处是,你可以在程序中多次引用这个值。

处理数字输入 🔢

上一节我们介绍了如何获取文本输入,本节我们来看看如何处理数字输入。如果你想在程序中进行数学计算,就需要获取数字。

如前所述,所有输入在读取到程序中时都是字符串类型。如果你输入一个整数,它也会以字符串形式进入程序。但你可以使用类型转换(type casting)将该字符串转换为整数。

同样,如果你想输入一个浮点数,它也会以字符串形式进入,但你可以将其转换为浮点数。

数字输入示例

假设你想为你和一群朋友购买几张电影票。以下代码询问你想购买多少张票:

tickets = input("How many tickets? ")

无论你输入什么,变量tickets都会被赋予一个字符串值。如果输入的是一个整数,你可以使用int()函数将该字符串转换为实际的整数。

tickets_int = int(tickets)

让我们追踪这个例子,看看实际发生了什么:

  1. 第一行代码运行:提示How many tickets?。假设你输入数字4。虽然你输入的是数字,但程序将其视为字符串"4"
  2. 第二行代码运行int(tickets)将字符串"4"转换为整数4,并存储在变量tickets_int中。

现在,让我们扩展这个例子,增加三行代码来计算四张票的总价:

tickets = input("How many tickets? ")
tickets_int = int(tickets)

price = float(input("Price of one ticket: "))
amount = tickets_int * price
print(amount)

第三行代码发生了很多事情:它读取一张票的价格,并立即将该字符串转换为浮点数。让我们更慢地逐步分析:

  1. 执行price = float(input("Price of one ticket: "))
    • 这是一个赋值语句,先计算右侧的表达式。
    • 表达式中有括号,所以先计算括号内的input("Price of one ticket: ")
    • 程序提示Price of one ticket:,假设用户输入10.50。这个值作为字符串"10.50"被读入。
    • 现在,计算float("10.50"),得到浮点数值10.50
    • 最后,将10.50赋值给变量price
  2. 执行amount = tickets_int * price
    • 查找tickets_int的值(4)和price的值(10.50)。
    • 计算4 * 10.50,得到42.0
    • 将结果42.0存储在变量amount中。
  3. 执行print(amount):打印出42.0

请注意,默认情况下,Python打印浮点数时,只打印小数点后非零的数字,并且小数点后至少保留一位。你可以使用格式化输出来指定显示多少位小数,但这超出了本节的范围。

处理错误的用户输入

用户可能会输入错误的数据。例如,程序询问How many tickets?,用户却输入了单词four

当下一行代码int(tickets)试图将字符串"four"转换为整数时,这会失败,因为只有完全由数字组成的字符串才能转换为整数。

我们可以通过提供更清晰的提示信息来改善这种情况,例如给出一个输入示例:

tickets = input("How many tickets? (e.g., 2): ")

这并不能阻止用户输入four,但希望能引导用户输入数字。我们将在后续课程中学习如何强制用户输入正确类型的输入。

总结 📝

本节课中,我们一起学习了如何让Python程序具有交互性。

  • 我们首先了解了为什么程序需要交互性,以及如何使用input()函数获取用户输入。
  • 接着,我们学习了input()函数的基本语法和如何通过变量存储输入值。
  • 然后,我们重点探讨了如何处理数字输入,关键在于记住:所有输入最初都是字符串。如果这些字符串代表数字,并且你想对它们进行计算,就必须使用int()float()函数将它们转换为相应的数字类型。
  • 最后,我们简要讨论了如何处理可能出现的错误输入,并指出可以通过提供清晰的提示来改善用户体验。

记住,input()函数是连接你的程序和用户的重要桥梁,合理使用它能让你的程序更加灵活和实用。

014:在VS Code中实现程序交互性演示 🖥️

在本节课中,我们将通过一个具体的编程示例,学习如何在VS Code环境中使用Python的input()函数与用户进行交互。我们将创建一个询问宠物信息并计算其“人类年龄”的程序,同时会指出并修正初学者常见的编码错误。


概述

我们将编写一个程序,依次执行以下任务:

  1. 询问用户拥有的宠物类型。
  2. 根据宠物类型,询问其名字。
  3. 询问宠物的实际年龄。
  4. 询问该宠物的一年相当于多少人类年。
  5. 计算并输出宠物的大致“人类年龄”。

在编写过程中,我们会遇到并解决诸如字符串拼接、类型转换和用户提示等常见问题。


第一步:获取宠物类型和名字

首先,我们创建一个新文件,命名为demo_input.py。我们将从获取宠物的基本信息开始。

以下是初始代码,用于询问宠物类型和名字:

pet = input("What type of pet do you have? ")
phrase = "What is your " + pet + "'s name? "
name = input(phrase)
print(name + " is a nice name.")

运行这段代码后,我们发现输出格式有些问题,单词之间缺少空格。因此,我们需要在提示字符串中添加必要的空格。

修正后的代码如下:

pet = input("What type of pet do you have? ")
phrase = "What is your " + pet + "'s name? "
name = input(phrase)
print(name + " is a nice name.")

现在,程序运行流畅,并能根据用户输入的宠物类型(如“cat”或“dog”)动态调整问题。


第二步:获取并显示宠物年龄

上一节我们成功获取了宠物的类型和名字。本节中,我们来看看如何获取宠物的年龄,并将其显示出来。

我们需要添加代码来询问宠物的年龄。由于年龄可能包含小数(例如3.5岁),我们使用float()函数将输入的字符串转换为浮点数。

以下是添加的代码:

years = float(input("What is " + name + "'s age in years? "))
print("Age is " + str(years))

这里需要注意两个关键点:

  1. 变量名一致性:确保打印时使用的变量名(years)与存储年龄的变量名一致。
  2. 类型转换:在打印时,必须使用str()函数将浮点数years转换为字符串,才能与其他字符串拼接。

第三步:计算宠物的“人类年龄”

我们已经获得了宠物的实际年龄。本节中,我们将利用一个换算系数来计算它在“人类年龄”下是多少岁。

首先,我们需要询问用户,对于他/她的宠物,一年相当于多少人类年。我们将这个值称为“乘数”(multiplier)。

以下是实现此功能的代码:

how_many = "One " + pet + " year is how many human years? "
multiplier = int(input(how_many))
age_in_human_years = multiplier * years
print("Your " + pet + " is about " + str(age_in_human_years) + " years old in human years.")

同样,在最后打印时,需要将计算得到的age_in_human_years(一个数字)转换为字符串。


第四步:优化用户体验

程序的基本功能已经实现。本节中,我们来看看如何通过改进提示信息来优化用户体验,防止用户输入错误格式的数据。

我们可以在输入年龄和乘数的提示信息中,加入格式说明,引导用户输入正确类型的数据。

优化后的提示信息如下:

# 询问年龄时,提示输入小数
years = float(input("What is " + name + "'s age in years? (decimal) "))

# 询问乘数时,提示输入整数
how_many = "One " + pet + " year is how many human years? (integer) "
multiplier = int(input(how_many))

这样,用户就能清楚地知道应该输入小数还是整数,从而减少因输入错误导致的程序崩溃。


完整代码示例

以下是整合所有步骤并优化后的完整程序代码:

# 获取宠物类型
pet = input("What type of pet do you have? ")

# 动态生成问题,获取宠物名字
phrase = "What is your " + pet + "'s name? "
name = input(phrase)
print(name + " is a nice name.")

# 获取宠物年龄,提示输入小数
years = float(input("What is " + name + "'s age in years? (decimal) "))
print("Age is " + str(years))

# 获取年龄换算乘数,提示输入整数
how_many = "One " + pet + " year is how many human years? (integer) "
multiplier = int(input(how_many))

# 计算并输出人类年龄
age_in_human_years = multiplier * years
print("Your " + pet + " is about " + str(age_in_human_years) + " years old in human years.")

总结

本节课中我们一起学习了如何在VS Code中构建一个交互式Python程序。我们掌握了以下核心概念和技巧:

  • 使用 input() 函数获取用户输入。
  • 使用 字符串拼接(+ 动态生成提示信息。
  • 使用 类型转换函数,如 float()int(),将输入字符串转换为数字。
  • 使用 str() 函数将数字转换回字符串以便打印。
  • 通过添加清晰的提示信息(如“(decimal)”)来优化用户体验,减少输入错误。

通过这个从简到繁的示例,你不仅学会了input()函数的用法,也了解了实际编程中如何逐步构建、测试和调试一个功能完整的脚本。

Python编程实践:15:在Python Tutor中实现程序交互性演示

在本节课中,我们将学习如何使用Python Tutor工具来可视化地演示一个交互式Python程序的执行过程。我们将看到程序如何接收用户输入、创建变量并输出结果,从而更直观地理解代码的运行逻辑。

上一节我们介绍了如何在Python中接收用户输入,本节中我们来看看如何利用Python Tutor工具来观察这些交互过程。

Python Tutor中的输入方式略有不同,并且我们能够看到所有变量的值。我将复制并粘贴代码到Python Tutor中。

以下是演示代码:

pet = input("What type of pet do you have? ")
phrase = "What is your " + pet + "'s name? "
name = input(phrase)
print(name + " is a nice name for a " + pet + ".")
age = input("What is " + name + "'s age in years? ")
print("Age is " + age)
multiplier = input("How many human years is one " + pet + " year? (Enter an integer) ")
age = float(multiplier) * float(age)
print("Your " + pet + " is about " + str(age) + " years old in human years.")

现在,我们将在Python Tutor中运行这段代码。点击“Visualize Execution”按钮开始执行。

程序开始运行,第一行等待输入。在Python Tutor中,输入框位于下方。假设我们输入“cat”。

点击“Submit”后,可以看到变量pet被创建,其值为“cat”。这表示第一行代码已执行完毕。

接下来,我们点击“Next”执行第二行。变量phrase被赋值,其值为“What is your cat's name?”。同时,顶部的输出区域也显示了第一个问题。

现在准备执行第三行,它将询问第二个输入问题。我们输入猫的名字,例如“Myra Sue”。

提交后,变量name被创建,其值为“Myra Sue”。然后点击“Next”执行第四行的打印语句,输出“Myra Sue is a nice name for a cat.”。

程序继续执行第五行,询问年龄。我们输入“8.1”。

提交后,变量age被创建,其值为字符串“8.1”。点击“Next”执行第六行,打印出“Age is 8.1”。

接下来是第七行,询问换算比例。提示输入一个整数,例如“8”。

提交后,变量multiplier被创建,其值为字符串“8”。然后执行第九行,计算人类年龄:age = float(multiplier) * float(age)

点击“Next”后,变量age被更新为新的浮点数值64.8

最后,执行最后一行打印语句,输出“Your cat is about 64.8 years old in human years.”。

通过这个演示,我们可以看到Python Tutor的几个特点:

  • 输入方式是通过下方的独立输入框完成的。
  • 右侧的“Frames”区域清晰展示了每个变量的创建和值的变化过程。
  • 顶部区域则显示了程序的输出结果。

Python Tutor的优势在于能让你直观地看到所有变量及其值的变化。

现在你已经了解了如何通过输入使程序变得交互。记住两个关键点:

  1. 如果要对输入的数字进行计算,必须先用int()float()函数将其转换为数字类型。
  2. 如果要将数字与字符串组合打印,必须先用str()函数将数字转换为字符串。

本节课中我们一起学习了如何使用Python Tutor来可视化、逐步地观察一个交互式Python程序的执行过程,这有助于加深对变量、输入和输出操作的理解。

016:如何编写程序-设计与规划 🎯

在本节课中,我们将学习如何系统地规划和设计一个程序。我们将通过一个具体的例子——编写一个诗歌生成器——来理解从构思到规划的全过程,并学习如何避免常见的设计陷阱。


概述:程序设计的起点

我们已经学习了一周多的编程知识。现在,让我们退一步,思考如何着手编写一个程序。虽然直接为项目编写代码可能是一种方法,并且看起来是实现目标的最快途径,但这并不总是最佳选择。你可能会发现自己迷失方向或原地打转。有时,制定一个计划会更好,这样你才能纵观全局,并知道如何导航到目的地。

为了帮助你理解为什么计划是个好主意,我们将通过讨论如何完成下一个实验来使本视频内容具体化。这个实验是关于编写一个为你生成诗歌的代码。但这不仅仅是重复编写相同内容的代码。我们添加了一些要求,以便每次运行程序时,生成的诗歌都可能不同,并且我们可以自动测试你的程序,看它是否满足一组小的规范。

以下是实验要求:

  1. 你的程序需要接收四个用户输入。
  2. 前三个输入必须是字符串类型,最后一个输入必须是整数类型。
  3. 诗歌至少有三行长。
  4. 所有字符串输入必须在你的诗歌中至少出现一次。
  5. 在诗歌的某一行中,必须有一个字符串输入重复出现整数输入所指定的次数。

例如,如果字符串输入是 KristencatMiaow,整数输入是 5,诗歌生成器可能会生成这样的诗:

My name is Kristen.
I have a cat.
It says Miaow, Miao, Miao, Mio, Miao, Miaow.

注意最后一行中,字符串 Miaow 出现了五次。

现在,你可能会觉得需要跟踪的事情很多。没关系,这正是本视频要讨论的——如何编写程序。让我们退一步,谈谈编写程序的不同步骤,以便你可以将事情分解成更小的部分。


程序设计活动:一个循环框架 🔄

编写程序的步骤通常包括:设计、规划、实现、验证和调试。然而,这些步骤的流程并非线性的。相反,它们更像是相互关联的活动,可以从一个流向另一个。从一个活动过渡到另一个可能或多或少是常见的,但在任何时间点,你都可能从一个活动转换到另一个,或者同时进行多个活动的一部分。这没关系。

我们教你这些活动,是作为一个有用的框架,帮助你思考编写程序的过程,以免迷失方向。把这些活动想象成我们给你的一张地图。你可以选择任何你想要的路径,但地图至少能帮助你弄清楚你目前在哪里,以及下一步哪个方向可能最有用。


活动一:设计——梦想蓝图 ✨

设计活动可能是你编写程序时应该开始的地方。在这里,你可以大胆梦想,集思广益,思考你希望程序做什么。这个活动也是你思考程序是否应该做某事以及为什么的时候。

开始时,你可以先写出你希望程序做什么的初步尝试,稍后再担心编写代码的细节。现在是思考你希望发生什么,而不是它将如何发生的时候。

诗歌生成器的初步设计

对于诗歌生成实验,程序可以首先询问一个名字。让我们使用第一位计算机程序员的名字:奥古斯塔·艾达·金,洛夫莱斯伯爵夫人,或艾达·洛夫莱斯。所以她的名字是奥古斯塔。

  1. 下一个提示可以询问中间名,在这个例子中是艾达。
  2. 对于最后一个字符串,程序询问姓氏。所以在这个例子中是金。
  3. 最后,对于整数,程序要求给出一个数字,不多说别的。让我们给程序输入整数 4

程序生成的诗歌可能会说:

Augusta Ada King.
Augusta, Augusta, Augusta, Augusta.
Ada, Ada, Ada, Ada.
King, King, King, King.

注意程序首先打印出全名,然后其他行因为数字 4 的输入,将名字的每个部分打印了四次。

评估设计:这是好主意吗?

现在我们有了第一个想法,是时候退一步问问:这是一个好主意吗?当然,这应该让你想知道,“好主意”甚至意味着什么?

为了理解一个设计是否好,你需要问几个问题:

  • 你在尝试做什么? 在这个例子中,你正在尝试编写一个生成诗歌的程序,这似乎相当明显,对吧?让我们深入一点。
  • 你为什么写这个程序?目标是什么? 这个问题的简单答案是因为我们告诉你要这样做。你参加这门课程是因为你想学习计算机科学,而编程是其中的重要部分。所以我们在教你用 Python 编程。然而,如果你看得更远,编写这样一个程序的潜在原因可能是什么?除了获得好成绩,你还能尝试用这个程序实现什么目标?
  • 这个程序是为谁设计的? 这个问题有点模糊。你可以问,谁可以轻松使用这个程序,谁不能?这个程序的影响是什么?

现在,你可能在想:我为什么要关心这个?这只是一个程序。但这不是我们要表达的重点。你是程序的设计者,所以你可以选择你的程序如何影响他人,因为你的程序对他人的影响就是你正在产生的影响。

重新审视设计:发现问题

所以,如果你回过头去看最初想法的输入,你看到了什么?这个程序是为谁设计的?我首先看到的是,这个程序对于名字恰好由三部分组成的人来说会工作得很好。然而,那些名字不是恰好由三部分组成的人呢?你认识没有中间名的人吗?此外,什么是名、中间名和姓?你通常回应的名字放在哪里?你的家族姓氏呢?并非所有文化中名字的顺序都是一样的。

任何不符合这个程序确切期望的人都将难以回答这三个问题。编写程序的人的工作就是问:这样好吗?如果这种困难是不好的,那么设计需要如何改变?这样它就不再是一个问题了。

顺便说一下,在我编写任何代码之前就意识到这个问题,这不是很好吗?如果我首先写了一堆代码,那么改变设计的工作量会大得多。

所以,在我的例子中,我认为这是一个需要解决的问题。我需要修复它,因为我的目标是让人微笑。让所有名字不是由三部分组成的人因为无法回答提示而不开心,这无助于我实现我的目标。我的目标也将帮助我找出如何修复设计,因为有很多方法可以尝试解决这个问题。这就是目标的有用之处。

改进后的设计

考虑到我发现的问题和我的目标,以下是我的新设计:

  1. 程序不再询问某人的名字,而是直接询问用户的名字。由用户决定这意味着什么。但由于提示如此开放,他们可以决定提示是指他们的名、姓、给定的名字,还是完全不同的东西。我本可以更进一步,让提示说:“你希望被怎么称呼?”
  2. 接下来,程序询问一个动物。我喜欢猫,所以我会说 cat
  3. 然后询问一种颜色。这门课程来自杜克大学,所以让我们说 blue,这是杜克大学的校色。
  4. 最后,程序保留原始问题,询问一个整数输入,只要求一个数字。

生成的诗歌会说:

Hello, Kristen, nice to meet you.
Do you think a blue cat is possible?
It'd be awesome, if they were.
Blue cat, blue cat, blue cat.

注意程序仍然满足诗歌生成实验的要求:

  • 程序至少使用了一次所有输入的字符串。
  • 诗歌至少有三行。
  • 诗歌至少有一行,其中一个字符串重复了三次(这是用户输入的整数)。
    此外,我写了一首相当愚蠢的小诗,希望能让人微笑。所以,实验和我的目标都实现了。

设计活动总结

设计活动是一个不受可能性限制,大胆梦想要创造什么的机会。在你进行设计时,你应该回答几个问题:

  • 你在做什么? 在我的例子中,我决定设计一个程序,生成个性化的、愚蠢的诗歌。
  • 你为什么写这个程序? 我想写这个程序是因为我想尝试让人微笑。
  • 这个程序是为谁设计的? 嗯,我的第一个想法是:任何人。但这个程序真的适合任何人吗?从技术上讲,我的设计实际上假设用户有一个名字。但名字比你想象的要复杂得多。有些文化中,人们没有大多数人认为的实际名字。我的设计还假设用户能读懂英语。这是我能在设计中实际解决的问题吗?也许。这个问题值得解决吗?我可能决定不解决,这没关系。重要的是,我正在考虑我的设计的权衡,并承认它们的存在。毕竟,不检查,我怎么知道我的程序正在实现我的目标?如果我进一步思考,我的设计假设用户至少知道一种动物、一种颜色和一个数字。

哇,内容真多。而这只是编程涉及的五个活动中的一个。但别担心,我会在其他活动上花更少的时间。这只是你学习如何编程之旅的开始,学习所有东西都需要时间。


活动二:规划——绘制路线图 🗺️

那么,什么是规划?规划是弄清楚你将如何实现你的设计。最常见的方法是将设计分成不同的部分。这里的“部分”是指整个设计中一个较小的部分,它不依赖于另一个部分,以至于不值得将这个部分再分成两个部分。

例如,如果我有一个完整的程序,你可以从将程序分成两部分开始。然后你应该看每个部分,问自己:这个部分太大了吗?你知道如何为这个部分编写代码吗?或者你需要将这个部分分解成更小的部分,以便你觉得每个部分都是可管理的?如果答案是肯定的,这个部分太大了,那么你就想办法把这个部分分解成更小的部分。

但这都是理论上的。让我们更具体一些。

为诗歌生成器做规划

对于我的诗歌生成器设计,我的计划中有哪些部分?

  1. 第一部分是收集用户输入。 记住,程序需要获取三个字符串和一个整数。
  2. 第二部分是打印诗歌本身。

现在,我们问自己:每个部分太大了吗?

  • 对于第一部分,这需要为程序需要的每个输入使用 input 函数。所以,我可以在我的计划中写一个注释,说明使用 input 函数,并将返回值放入四个不同的变量中。当我说“在我的计划中写”时,我指的是实际把这个写下来,而不仅仅是记在脑子里。注意,注释包含了我将使用的实际变量:nameanimalcolornumber。这样,我就对输入有了概念,并且可以在以后的计划中引用它们。
  • 对于第二部分,这个部分太大了。即使这部分只是打印,但里面有一些复杂的东西。我需要弄清楚如何打印出我诗歌的所有四行。其中一些行在初次看时似乎并不直接明了。

所以,让我们规划诗歌的每一行:

  • 第一行 希望是直接的。那一行只是打印 Hello,,然后使用用户提供的名字。所以我可以直接写下来。
  • 第二行 稍微复杂一点,因为它需要使用两个字符串输入,但我不认为这一行需要比写一个注释来打印使用 coloranimal 变量的句子更进一步的分解。
  • 第三行 是相同的,与输入无关。所以规划起来很直接。
  • 最后一行 是程序将 coloranimal 打印用户给程序的数字那么多次。这是最复杂的。所以,如果你必须停下来问自己:我该如何编写这段代码?这就是为什么在编写代码之前进行规划很重要。既然我不想给出答案,我就添加一个注释,说明使用字符串连接和字符串乘法。

规划之后做什么?

在所有这些规划之后,你如何处理你的计划?我建议把所有内容都作为注释放到实际的代码文件中。我甚至可以直接在代码文件中以注释的形式进行规划,尽管这可能会让你只想写代码,而这并不是这个活动的重点。你的目标是写出你的计划,而不必担心你将如何精确地编写代码。所以,在另一个文件或纸上写出来可能更好。


总结与展望

在本节课中,我们一起学习了编写程序的前两个关键活动:设计规划

  • 设计 是关于“做什么”和“为什么做”的宏观思考。我们通过诗歌生成器的例子,看到了如何从初步构思开始,然后通过问自己“为谁设计?”和“目标是什么?”来发现并改进设计中的潜在问题。一个好的设计应该明确目标,并考虑用户的多样性。
  • 规划 是关于“如何做”的分解工作。我们将一个大的设计目标(生成诗歌)分解为更小的、可管理的任务(收集输入、打印每一行),并为每个任务写下具体的实现思路(如使用哪些变量、函数)。这为下一步编写代码提供了清晰的路线图。

通常,规划之后的下一个步骤是实现,即根据你的计划编写 Python 代码。但为了不泄露所有答案,我们将跳过这部分。在下一个视频中,我们将继续讨论编写程序的最后两个活动:验证调试,帮助你完成从想法到可运行程序的完整旅程。

017:如何编写程序 - 验证与调试 🐛

在本节课中,我们将学习编写程序流程中的最后两个关键活动:验证调试。我们将探讨如何检查程序是否按预期工作,以及当发现问题时如何有效地修复它们。


验证活动

上一节我们讨论了程序的设计与规划。本节中,我们来看看如何验证你的程序。验证活动通常在你至少完成了程序的第一版实现后进行。其核心问题是:程序是否在做它应该做的事?

根据目前所学,主要有两种方式来验证你的程序。

第一种方式是依据规划活动中的计划。
检查程序是否执行了计划中规定的任务。你可以通过向程序提供输入并确认其输出是否正确来完成验证。

以下是验证计划的具体步骤:

  1. 使用设计阶段设定的原始输入,确认程序能输出正确的诗歌。
  2. 为程序提供其他一些输入,并确认生成的诗歌符合预期。

第二种方式是依据设计活动中的初衷。
聚焦于你最初创建程序的目的。例如,我的目标是让人微笑,这也意味着不让人皱眉

那么,应该先进行哪种验证?这取决于具体情况。哪种验证更容易执行?如果发现问题,哪种情况更容易修改?这完全由你决定。这也说明了这些活动更像是相互关联的环节,而非严格的线性步骤。如果你先验证设计,可能就需要返回设计活动进行修改。事实上,在完成初步设计后,直接进入验证活动可能是合理的,因为此时修改设计比在规划和实现之后修改要容易得多。

不过,我们先从更直接的验证计划开始,并举例说明。

为了验证你的计划,请参考原始设计,并提供一些可能的输入。

  • 首先,如前所述,为程序提供原始输入,确认它打印出正确的诗歌。
  • 然后,提供其他输入,例如我们其他讲师的名字:Yessenia, dog, white, 2。程序会生成一首诗:“Ho, Yessenia, nice to meet you. Do you think a white dog is possible? It'd be awesome if they were white dog, white dog.”
  • 再提供输入:Susan, fish, purple, 4。得到:“Ho, Susan, nice to meet you. Do you think a purple fish is possible? It'd be awesome if they were purple fish, purple fish, purple fish, purple fish.”
  • 你可以继续提供其他输入进行确认。

验证设计

现在,让我们谈谈验证设计。你可以采用以下几种方法。

首先,可以邀请他人使用你的程序,并观察他们的反应。

另一种方法是创建“用户画像”。
这些是对虚拟用户的详细描述,就像故事中的人物设定。例如,你可以创建一个名为“Grace Hopper”的画像,代表来自军队、思维非常严谨的女性用户。然后思考:这个人会如何使用程序?程序能为他工作吗?他会输入什么?这些输入有效吗?

最后,在观察了真人使用并思考了不同用户画像的使用体验后,你可以问自己:程序是否达到了你的目标? 在我的案例中,目标是让人微笑,这也意味着不能让他们不开心。


调试活动

假设你在验证过程中发现了代码中的问题,也就是所谓的 Bug。你该怎么办?

首先,需要明确是在哪里发现的Bug。

如果你在验证计划时发现了Bug,这意味着你尝试了不同的输入,并发现某些输入无法正常工作。你应该把这些情况记录下来。

然后,针对每一组有问题的输入和输出,你应该从程序顶部开始,逐行仔细检查代码,寻找代码行为与预期不符的地方。发生这种情况的原因可能有很多。

  • 例如,Bug可能是因为某个变量的值错误。
  • 或者执行的代码逻辑有误。
  • 也可能是打印了错误的内容。

另一方面,如果你在验证设计时发现了Bug,这意味着你现在拥有一个列表,记录了程序在用户使用场景下未能正常工作的情况。这个列表可以作为你提问的信息来源。

  • 例如,程序为什么没有工作?
  • 是程序的错误,还是用户的期望与设计初衷不同?
  • 你能否修改程序,以更好地传达应该发生什么,从而使用户的期望更符合程序行为?
  • 或者,也许用户的期望是合理的,因此程序应该做出改变。你该如何修改设计、计划或实现来改善情况?

调试时要考虑的事情很多,别担心,当你需要帮助回忆时,可以随时回看这个视频。


总结回顾

关于如何编写程序,我们用了两个视频来讲解,内容非常丰富。现在让我们整体回顾一下。

编写程序主要包含五个核心活动,这些活动通常相互关联。虽然存在一定的顺序,但你可能会发现自己是在来回跳跃,而非严格按顺序进行。

  1. 设计活动:这是你大胆构想的地方,确定程序的目标和面向的用户。这个活动帮助你根据目标理清程序应该做什么,但不必过于担心代码的具体样子。
  2. 规划活动:这是你思考代码将做什么的时候,但并非实际编写代码。在此活动中,你将设计转化为程序的不同组成部分,有时会将大块任务分解成更小的部分,以便于理清思路。
  3. 实现活动:这是你真正开始编写代码、实现功能的阶段。但请记住,你并不一定严格遵循上述顺序。
  4. 验证活动:在这里,你检查程序是否在做它应该做的事。这包括定义“应该做什么”的含义。通常,这意味着拿着你的实现,去验证程序是否遵循了你的计划,或者是否实现了你在设计阶段设定的目标。
  5. 调试活动:根据你是在验证计划还是验证设计时发现问题,来学习如何修复代码。

希望本视频对你有用,并让你对如何着手编写程序有了更清晰的认识。对于我们将要让你编写的小型程序,你可能会觉得不需要所有这些活动,或者在每个活动上花费的时间很少。这没关系。

只需将这些活动记在心里,因为最终你会发现,如果没有适当的规划,有些程序会变得过于庞大而难以轻松编写。适时退一步,按照这些活动逐步推进,将有助于确保你编写出真正想要创建的程序。

018:当自动评分器得分为0时该怎么做

概述

在本节课中,我们将学习如何提交程序到自动评分器,以及当首次提交后获得0分时,应如何分析和调试代码。我们将通过一个诗歌生成器程序的例子,演示从错误代码到最终获得满分的过程。


代码提交与初步运行

在开始编写第一个评分实验——诗歌生成器程序之前,建议先暂停视频,仔细阅读实验说明。阅读完毕后,再观看本视频,了解如何提交程序到自动评分器,以及当获得0分时该如何处理。

以下是为演示准备的代码。出于教学目的,部分代码已被隐藏,以便大家有机会独立完成实验。

# 部分代码已隐藏
person = input("What is the name of a person? ")
pet = input("Type of pet: ")
sound = input("The sound: ")
date = input("What's the date? ")
number_string = input("On the number of times to repeat the sound: ")

编写代码时,持续运行程序以检查其是否正常工作是一个好习惯。不应等到最后才运行代码。在提交给自动评分器之前,务必先自行运行检查。

现在运行这段代码。程序会询问人名、宠物类型、声音、日期和重复次数。输入相应信息后,我们遇到了一个类型错误(TypeError),提示“不能将序列乘以非整数类型的字符串”。错误可能指向第7行。

此时,你应该检查代码,尝试找出并修复错误,然后再次运行。但为了演示,我们将直接提交这段代码到自动评分器,看看会发生什么。


首次提交与评分器反馈

提交代码并等待几分钟后,自动评分器给出了反馈:我们得了0分。这很正常,0分只是一个改进代码的机会。本实验需要80分才能通过。

评分器还提供了一条个性化信息:“代码请求了超出必要的用户输入”。这意味着我们调用了太多次 input() 函数。有时这些信息可能难以理解,建议多读几遍。

反馈末尾显示,我们通过了0个“满意”测试和0个“超出预期”测试。请注意,评分器指出的错误(输入过多)并非我们之前遇到的类型错误。这是因为自动评分器一次只显示一个错误,它可能正在处理代码中的另一个问题。


分析错误与查阅实验说明

既然自动评分器提示输入过多,我们需要检查原因。如果不明白,最佳做法是返回实验说明查看具体要求。

在实验说明文件的底部,有一个“要求清单”部分。“满意”级别列出了通过实验(获得80分)必须满足的所有条件。第一个要求是“提示用户输入三个不同的字符串”,紧接着的第二个要求是“提示用户输入一个整数”。

这意味着总共需要调用四次 input() 函数:三次用于字符串,一次用于整数。回顾我们的代码,我们调用了五次 input()(三次字符串、一次日期、一次整数),确实多了一次。因此,我们需要删除对日期的请求。

修改后,代码只询问人名、宠物类型、声音和重复次数。


修复第一个错误并再次提交

修改代码后,再次运行。我们输入“Susan”、“cat”、“Meow”和“3”。然而,程序仍然报类型错误,这次可能指向第6行。

如果根据错误信息仍无法定位问题,可以再次提交给自动评分器,它可能会提供新的线索。

提交后,我们发现分数仍是0,但个性化反馈信息变了!现在它提示“发生了类型错误”。这是一个好迹象,说明我们成功修复了“输入过多”的错误,评分器开始检查下一个问题了。

反馈建议:“验证代码是否为每个使用的运算符使用了正确的数据类型”。这提示我们应该关注运算符和数据类型。


理解并修复类型错误

在我们的代码中,使用了一个乘法运算符:sound * number_stringsound 是一个字符串(宠物发出的声音),而 number_string 本应是一个数字。问题在于,input() 函数返回的始终是字符串类型。因此,number_string 实际上是一个字符串。

我们需要将 number_string 从字符串转换为整数,才能进行字符串与整数的乘法运算。Python不允许字符串与字符串相乘。

修改方法如下:

number = int(number_string)  # 将字符串转换为整数
# 然后使用 sound * number

修复后,再次在本地运行程序。输入“Yesenia”、“Dog”、“Woof”和“4”。这次,诗歌成功生成了,没有出现类型错误。


最终提交与获得满分

确认代码运行正确后,我们将其提交给自动评分器。最终,我们通过了所有“满意”和“超出预期”测试,获得了100分的满分。


一个常见的顺序错误

在结束前,我们来看一个常见的错误。假设我们将请求“重复次数”的 input() 调用移到了代码前面(例如第3行),违反了实验说明中“先三个字符串,后一个整数”的顺序要求。

当我们在本地按照自己编写的提示运行时,程序可能正常工作。但自动评分器只会严格按照实验说明来提供输入:它会给前三个变量提供字符串,给第四个变量(此时是sound)提供一个数字。

当代码尝试将这个数字(比如“3”)赋值给 pet 变量,并后续可能进行不合适的操作(如尝试转换为整数)时,就会引发值错误(ValueError)。自动评分器的反馈会提示“输入顺序不正确”。

这强调了严格遵守实验说明中输入顺序的重要性。自动评分器只遵循实验说明,不会读取你代码中的提示信息。


调试策略总结

调试可能令人沮丧,但它是一项非常重要的技能。在编写代码时,应养成多次在本地运行的习惯,尤其是在提交给自动评分器之前。

如果你提交后得了0分,请不要慌张,这只是一个改进的机会。可以按照以下步骤检查:

  1. 检查输入数量:是否请求了实验说明规定之外的额外输入?
  2. 检查输入顺序:输入请求的顺序是否与实验说明完全一致?
  3. 检查类型与运算符:是否对正确的数据类型使用了正确的运算符?(例如,字符串与整数相乘)
  4. 核对实验要求:你的代码是否满足了“要求清单”中的所有条件?在个性化程序时,请确保核心要求不变。
  5. 仔细阅读反馈:认真阅读自动评分器的反馈信息,它通常会指出可能的问题方向。

多在自己的环境中运行代码,能帮助你熟悉Python的错误信息,并建立独立调试的能力。将自动评分器视为一个工具:在你卡住时提供帮助,或用于验证代码是否符合实验要求。

最重要的是,保持练习,享受编程和调试的过程,逐步构建起这项关键的技能。


总结

本节课中,我们一起学习了如何向自动评分器提交代码,并逐步分析了当获得0分时的完整调试流程。我们从识别“输入过多”错误开始,通过查阅实验说明修正它,然后处理了“类型错误”,最终获得了满分。我们还探讨了输入顺序错误这一常见问题,并总结了一套实用的调试策略。记住,调试是编程的核心部分,每一次错误都是学习和进步的机会。

019:当自动评分器部分扣分时的调试方法 🐛

在本节课中,我们将学习当你的编程作业在自动评分器中未能获得满分时,应如何系统地分析和解决问题。我们将以“诗歌生成器”实验为例,演示如何利用自动评分器的反馈信息和实验要求清单来定位并修复代码中的问题。


上一节我们介绍了编程作业提交的基本流程,本节中我们来看看当自动评分器未给出满分时,具体的调试步骤是什么。

理解问题场景

假设你在实验中已经获得了一些分数,但未达到预期。此时,你需要采取行动。自动评分器的反馈信息和实验的要求清单是开始调试的最佳位置。

在本视频中,我将以诗歌生成器实验为例,展示如何从这两处寻找线索。如果你尚未阅读该实验说明,建议先阅读以了解其具体要求。

以下是我的诗歌生成器实验代码示例。出于教学目的,部分解决方案代码被隐藏,可见部分仅打印了 line1line4。被隐藏的代码是创建这些变量并为其生成字符串的部分。我已添加注释以帮助你理解。

# 代码部分可见:打印诗句行
print(line1)
print(line2)
print(line3)
print(line4)
# 代码部分不可见:处理用户输入并生成 line1, line2, line3, line4 的字符串

运行此代码并回答提示问题后,程序会生成一首小诗。例如,输入“Kristen”、“cat”、“blue”、“3”后,输出如下:

Hello, Kristen, nice to meet you.
Do you like blue?
Do you think a blue cat is possible?
It'd be awesome if they were real.
blue cat, blue cat, blue cat.

可以看到,诗句整合了用户输入。如果提交此代码,自动评分器会给予满分。

模拟扣分场景与初步分析

本次演示的重点是当你只获得部分分数时的应对方法。假设代码在生成变量时意外地没有打印第一行。我们可以通过注释掉第一行的打印语句来模拟此错误。

修改后再次运行程序,输出将缺少第一行问候语。提交此不完整的代码后,自动评分器返回了 53% 的分数,并提示“需要80%或更高才能通过”。

此时,切勿立即回头修改代码。 你应首先查看自动评分器提供的反馈信息,这是修复代码的首要依据。

自动评分器的反馈通常包含关键提示。例如,它可能指出:

“代码应在诗句中使用用户输入的第一个字符串。”

这明确指出了问题所在:我们需要确保第一个输入被打印出来。这符合我们的预期,因为我们注释掉的正是使用第一个输入的那一行。

此外,反馈信息还显示了测试通过情况:

  • 通过了 2 项“满意”测试(共3项)
  • 通过了 2 项“超出预期”测试(共3项)

你可能会疑惑:通过了4项测试(共6项),为何只得了53%?这是因为,你必须通过所有 “满意” 级别的测试(即实验的基本学习目标)才能达到80%的通过线。目前我们只差一项“满意”测试即可通过实验。

修复问题并再次提交

根据反馈,我们修复代码,恢复第一行的打印语句。但假设这次修复意外导致了最后一行(重复字符串的行)消失。再次运行程序,输出变回三行,包含了问候语但缺少了重复行。

提交此版本后,得分变为 87%,通过了实验(>80%)。此时你可以选择结束,因为通过实验表明你已掌握所有核心学习目标。但如果你追求满分,则需要继续查看自动评分器的新反馈。

新的反馈指出:

“诗中应包含一行,将三个输入值中的一个重复指定的次数(由最后一个输入值指定)。”

同时,测试结果更新为:通过了所有3项“满意”测试,但只通过了1项“超出预期”测试(共3项)。

查阅实验要求清单

如果你从自动评分器的消息中无法完全理解问题所在,下一步就是查阅实验说明中的 要求清单

由于我们已经通过了所有“满意”测试,可以跳过基本要求,直接查看“超出预期”部分。逐一阅读,直到找到与“重复字符串”相关的要求:

以下是“超出预期”的要求列表:

  • 打印至少三行长的诗句。
  • 使用字符串拼接来打印诗句行,并添加不属于用户输入的额外词语。
  • 其中一个字符串输入应至少出现在三行不同的诗句中。
  • 你的诗句应包含一个输入字符串,该字符串重复‘数字’次,其中‘数字’是你的输入。

很明显,最后一项要求正是我们未满足的。此时,你应返回代码,确保实现此功能。

在我们的例子中,只需取消注释最后一行重复打印的代码即可。修复后,代码应能根据最后一个输入的数字,重复打印指定的字符串。

最后,提交修复后的代码,获得 100% 的满分。


总结 📝

本节课中我们一起学习了当自动评分器部分扣分时的系统调试方法:

  1. 首先,仔细阅读自动评分器的反馈信息,包括具体的错误提示和测试通过数量,这能帮你快速定位问题核心。
  2. 其次,如果自动评分器的信息不够清晰,务必查阅实验的要求清单,逐项核对“满意”和“超出预期”的要求。
  3. 记住,通过实验(≥80%)意味着你已掌握核心目标,可以决定是否继续争取满分。

调试是编程的重要组成部分,利用好这些工具和提示能有效提升你的问题解决能力。祝大家编程愉快!

020:比较值与关系运算符 🔍

在本节课中,我们将要学习如何让Python根据条件做出决策。这涉及到比较不同的值,例如数字或文本,并根据比较结果决定执行哪些代码。我们将介绍用于比较的“关系运算符”,并了解它们如何与数字和字符串一起工作。

比较数字 🔢

上一节我们介绍了顺序执行的代码。本节中我们来看看如何让代码只在特定条件下运行。例如,如果用户输入的数字小于100,程序打印“这是一个不错的数字”;如果数字大于等于100,则打印“这是一个大数字”。这被称为“条件语句”。

在数学中,我们使用 <> 符号来比较数字。Python也使用完全相同的符号来表示“小于”和“大于”。

对于“小于或等于”和“大于或等于”,数学中是在符号下加一条线。但在键盘上输入这些符号并不方便。因此,Python(以及许多其他编程语言)使用 <=>= 来表示。

那么,如何检查两个值是否“等于”呢?在Python中,单个等号 = 用于给变量赋值。为了进行比较,我们使用两个等号 ==

最后,我们还需要检查两个值是否“不等于”。在Python中,我们使用感叹号后跟等号 != 来表示。

以下是Python中六种关系运算符的总结:

  • <:小于
  • >:大于
  • <=:小于或等于
  • >=:大于或等于
  • ==:等于
  • !=:不等于

使用这些运算符时,将两个值放在运算符两侧,表达式会计算为“是”或“否”。在Python中,我们称之为 True(真)或 False(假)。

让我们看一些例子:

4 < 5    # 计算结果为 True
5 > 10   # 计算结果为 False
8 <= 9   # 计算结果为 True
7 >= 7   # 计算结果为 True
9 == 8   # 计算结果为 False
2 != 2   # 计算结果为 False

除了直接使用数字,你还可以在比较中使用变量:

x = 10.0
x >= 1   # 计算结果为 True

a = 10
b = 1.0
a == b   # 计算结果为 False

请注意,Python在比较时会将整数和浮点数都视为数字,因此 5 == 5.0 的计算结果为 True

比较字符串 📝

关系运算符不仅可以比较数字,还可以比较字符串。比较字符串时,Python使用区分大小写的字母顺序(即字典序)。

以下是理解字符串比较的关键点:

  1. 字母顺序:比较基于字母在字母表中的位置。‘a’ 被认为小于 ‘b’
  2. 大小写敏感:所有大写字母在排序上都位于所有小写字母之前。这意味着 ‘Z’ 小于 ‘a’
  3. 逐字符比较:Python从两个字符串的第一个字符开始比较。如果相同,则比较下一个字符,直到找到不同的字符为止。整个字符串的比较结果就由这一对不同的字符决定。
  4. 长度影响:如果一个字符串是另一个字符串的前缀(例如 ‘T0’‘T00’),则较短的字符串被认为较小。

让我们通过一些例子来巩固理解:

‘a’ < ‘b’          # True,因为 ‘a’ 在字母表中位于 ‘b’ 之前
‘y’ > ‘b’          # True
‘A’ < ‘a’          # True,因为大写字母排在小写字母之前
‘Z’ < ‘a’          # True
‘cab’ < ‘cat’      # True,比较第三个字符 ‘b’ < ‘t’
‘Apple’ > ‘Yon’    # False,因为 ‘Y’(大写)< ‘a’(小写)
‘black’ < ‘car’    # True,比较第一个字符 ‘b’ < ‘c’
‘T0’ < ‘T00’       # True,因为 ‘T0’ 是 ‘T00’ 的前缀
‘that is red’ > ‘that is blue’  # True,比较 ‘r’ > ‘b’

总结 📚

本节课中我们一起学习了Python中的关系运算符,它们用于比较值并产生 TrueFalse 的结果。

我们了解到:

  • 对于数字比较,运算符 <=>= 对应数学中的“小于或等于”和“大于或等于”。
  • 单个等号 = 用于赋值,而双等号 == 用于检查相等性。
  • 不等号 != 使用感叹号和等号组合表示。
  • 当比较字符串时,Python使用区分大小写的字母顺序。所有大写字母被认为小于所有小写字母,并且字母顺序决定了相同大小写字母之间的比较结果。

掌握这些比较操作是编写能够根据不同条件做出判断的程序的基础。

021:选择性执行 - 使用条件语句 🧠

在本节课中,我们将要学习Python中的条件语句。条件语句允许程序根据特定条件是否为真,来决定执行哪些代码。这就像在日常生活中做决定一样,例如“如果下雨,我就带伞”。我们将学习如何将这种逻辑转化为Python代码。

条件语句的基本概念

上一节我们介绍了编程的基本流程,本节中我们来看看如何让程序根据情况做出不同选择。条件语句的核心是“如果...那么...”的逻辑结构。在Python中,这通过 if 语句来实现。

一个条件语句包含两部分:

  • 条件:这是一个结果为 True(真)或 False(假)的表达式。
  • 结果:当条件为真时,将要执行的一段代码。

例如,我们可以用代码判断出门是否需要带保暖衣物。首先,我们需要获取温度信息。

以下是实现此逻辑的步骤:

  1. 使用 input() 函数获取用户输入的温度。
  2. 使用 int() 函数将输入的字符串转换为整数。
  3. 初始化一个变量来存储“要带什么”的信息。
  4. 使用 if 语句判断温度是否高于某个值。
  5. 根据判断结果,更新“要带什么”的变量值。
  6. 最后,打印出结果。

编写第一个 if 语句

让我们将上述逻辑转化为具体的Python代码。假设我们认为华氏65度(约摄氏18度)以上就不需要带任何东西。

temp = int(input("请输入温度(华氏度):"))
what_to_bring = "?"
if temp > 65:
    what_to_bring = "nothing"
print("需要带:" + what_to_bring)

让我们逐行分析这段代码的关键部分:

  • if 关键字:条件语句总是以 if 开始。
  • 条件:紧跟在 if 后面的是一个表达式,本例中是 temp > 65。它会被求值为 TrueFalse
  • 冒号 ::在条件末尾的冒号告诉Python,接下来是属于这个 if 语句的代码块。
  • 缩进:条件为真时要执行的代码必须缩进(通常按一次Tab键或使用4个空格)。这标记了代码块的开始和结束。

当程序运行时,它会从上到下执行。如果 temp > 65 为真,则执行缩进的代码行(what_to_bring = "nothing")。如果为假,则跳过这行代码,直接执行后面的 print 语句。

使用 else 提供备选方案

上面的代码有一个问题:如果温度不高于65度,它只会输出一个问号。为了让程序在条件不满足时也能给出明确建议,我们可以使用 else 子句。

这相当于说:“如果温度大于65度,就什么都不带;否则(else),就带一件毛衣。”

temp = int(input("请输入温度(华氏度):"))
what_to_bring = "?"
if temp > 65:
    what_to_bring = "nothing"
else: # 如果上面的条件不成立,则执行这里的代码
    what_to_bring = "sweater"
print("需要带:" + what_to_bring)

以下是 else 子句的要点:

  • else 关键字:它单独成行,与上方的 if 对齐。
  • 无条件else 后面不跟任何条件,它代表“所有其他情况”。
  • 代码块else 下方的代码同样需要缩进,构成一个代码块。

现在,无论温度如何,程序都会给出一个明确的建议。

处理多种情况:elif

生活中有更多选择。比如,温度在50到65度之间带毛衣,低于50度则需要外套。这时就需要 elif(“else if”的缩写)子句。

我们可以这样构建逻辑:

  1. 如果温度 > 65, 什么都不带。
  2. 否则,如果温度 > 50, 带毛衣。
  3. 否则(即温度 <= 50), 带外套。

对应的Python代码如下:

temp = int(input("请输入温度(华氏度):"))
what_to_bring = "?"
if temp > 65:
    what_to_bring = "nothing"
elif temp > 50: # 检查另一个条件
    what_to_bring = "sweater"
else: # 如果以上条件都不满足
    what_to_bring = "coat"
print("需要带:" + what_to_bring)

elif 的工作方式是:

  • Python会按顺序检查每个 ifelif 的条件。
  • 一旦找到第一个为真的条件,就执行其对应的代码块,然后跳过其余所有 elifelse 子句。
  • 如果所有 if/elif 条件都为假,则执行 else 块(如果存在的话)。

条件语句的顺序很重要

条件的顺序会影响程序的逻辑。考虑下面这个修改过的、顺序错误的例子:

if temp > 50:      # 条件A:检查是否大于50
    what_to_bring = "sweater"
elif temp > 65:    # 条件B:检查是否大于65
    what_to_bring = "nothing"
else:
    what_to_bring = "coat"

如果输入70度,程序会先检查条件A(temp > 50),结果为真,于是决定带毛衣,并完全跳过条件B。这显然不是我们想要的结果,因为70度大于65度,应该什么都不带。

关键点:在安排多个条件时,通常应该从最具体要求最高的条件开始检查。

构建更复杂的条件逻辑

你可以根据需要添加任意多个 elif 子句来创建更精细的决策树。例如,在毛衣和外套之间增加一个“夹克”的选项:

if temp > 65:
    what_to_bring = "nothing"
elif temp > 50:
    what_to_bring = "sweater"
elif temp > 40:   # 新增一个条件
    what_to_bring = "jacket"
else:
    what_to_bring = "coat"

条件语句的通用结构总结如下:

一个完整的 if 语句包含:

  • 1个 必需的 if 子句(开头)。
  • 0个或多个 elif 子句(中间)。
  • 0个或1个 else 子句(末尾,如果存在则必须是最后一个)。

总结

本节课中我们一起学习了Python条件语句的核心知识。我们了解到:

  • if 语句允许程序基于条件做出决策。
  • else 子句提供了当 if 条件不满足时的备选执行路径。
  • elif 子句让我们能够顺序检查多个条件,处理更复杂的情况。
  • 缩进在Python中至关重要,它定义了属于每个条件的代码块。
  • 条件的顺序会影响逻辑,必须仔细设计。

通过掌握 ifelifelse,你现在已经可以让你的程序根据不同的输入和情况智能地执行不同的操作了。

022:if-elif与if-if的区别 🧩

在本节课中,我们将要学习条件语句的两种不同写法:使用 if-elif-else 结构,以及使用多个独立的 if 语句。我们将通过一个具体的例子来理解它们的区别、执行逻辑以及各自的适用场景。

概述

你已经了解了 if 语句的基本概念。现在,让我们暂停一下,探讨如何使用 if 语句以不同的方式实现相同的功能。

if-elif-else 语句的执行逻辑

以下是带有 ifelifelse 子句的通用代码结构:

if condition1:
    # 代码块A
elif condition2:
    # 代码块B
else:
    # 代码块C

当Python执行这个语句时,它会从上到下检查条件。一旦遇到一个为真的条件,就会执行属于该条件的代码块,然后直接跳过整个 if-elif-else 结构,不再检查后续条件。如果所有条件都为假,则会执行 else 子句中的代码。

这意味着,一个 if-elif-else 语句最多只会执行一个代码块。

多个独立 if 语句的执行逻辑

相比之下,以下是使用三个独立 if 语句的通用代码:

if condition1:
    # 代码块A
if condition2:
    # 代码块B
if condition3:
    # 代码块C

使用这段代码,Python会检查每一个 if 语句的条件。只要条件为真,就会执行对应的代码块。即使前一个 if 语句的条件为真,Python仍然会继续检查下一个 if 语句,因为它们是彼此独立的。

这意味着,使用多个独立 if 语句的代码可能执行0到3个代码块。如果所有条件都为假,则一个都不执行;如果所有条件都为真,则全部执行。

示例:根据温度决定携带物品

为了更具体地说明,我们来看一个例子。以下是根据温度决定携带物品的规则表:

温度条件 携带物品
高于65度 什么都不带
50到65度之间 带毛衣
50度或以下 带外套

我们的目标是编写代码,根据用户输入的温度,打印出正确的携带物品。

上一节我们介绍了两种结构的执行逻辑,本节中我们来看看如何用它们实现相同的功能。

方案一:使用 if-elif-else

这是之前我们熟悉的写法,使用一个 if-elif-else 语句:

temperature = int(input("What is the temperature? "))
if temperature > 65:
    what_to_bring = "nothing"
elif temperature > 50:
    what_to_bring = "sweater"
else:
    what_to_bring = "coat"
print(f"Bring {what_to_bring}.")

方案二:使用多个独立 if 语句

我们也可以仅使用 if 子句来实现相同的逻辑:

temperature = int(input("What is the temperature? "))
what_to_bring = "nothing"  # 设置默认值
if temperature <= 65:
    what_to_bring = "sweater"
if temperature <= 50:
    what_to_bring = "coat"
print(f"Bring {what_to_bring}.")

让我们分析一下新版本的思路。代码首先将 what_to_bring 的默认值设为 "nothing"。然后,它随着温度越来越低,逐步检查并更新 what_to_bring 的值。

以下是两种方案的对比:

  • 对于“什么都不带”的情况:
    • 左侧方案使用 if temperature > 65: 子句。
    • 右侧方案没有对应的 if 语句,而是直接将默认值设为 "nothing"
  • 对于“带毛衣”的情况:
    • 左侧方案使用 elif temperature > 50: 子句。
    • 右侧方案使用 if temperature <= 65: 语句。因为如果温度不高于65,就不能保持默认的“什么都不带”。
  • 对于“带外套”的情况:
    • 左侧方案使用 else: 子句,它只在前面所有条件都为假(即温度≤50)时执行。
    • 右侧方案直接使用第二个 if 语句检查 temperature <= 50

尽管实现方式不同,但给定相同的输入,两段代码会打印出相同的结果,这完全没问题。

如何选择?

那么,你应该使用哪种方式呢?是使用一个包含 ifelifelse 的单一语句,还是使用多个独立的 if 语句?

这取决于你。本视频的重点在于向你展示,实现同一目标可以有多种方法,这很正常。对一些人来说,包含所有子句的单一 if 语句更易理解;对另一些人来说,仅使用 if 语句更直观。

重要的是,代码要根据表格中的温度范围,打印出它应该打印的内容。这实际上也是编程让我觉得非常棒的一点:我们都可以瞄准同一个目标,但可以用自己富有创意的方式去实现它。

总结

本节课中我们一起学习了条件语句的两种主要结构。我们了解到:

  1. if-elif-else 结构是一个整体,最多执行一个分支。
  2. 多个独立的 if 语句会逐一检查每个条件,可能执行多个分支。
  3. 两种方法都可以用来解决同一个问题,选择哪一种通常取决于代码的可读性、个人习惯以及具体的逻辑需求。理解它们的区别有助于你写出更清晰、更高效的代码。

023:if语句演示 🐍

在本节课中,我们将学习如何使用 if 语句编写代码。我们将通过一个整理书籍的示例,在 Python Tutor 中逐步演示代码的执行过程,并观察如何根据 if 语句的结果跳过不同的代码行。

概述

我们将编写一个程序,根据作者姓名和书籍是否特殊,来决定一本书应该放在书架的哪个位置以及如何摆放。通过这个例子,我们将展示编写程序时涉及的几个核心活动:设计、规划、实现、验证和调试。

设计活动

首先,我们需要明确程序的目标。假设我们有一个三层的书架,我们希望:

  1. 按作者姓氏的字母顺序将书分配到不同的层。
  2. 将特殊书籍放在所在层的前面以美化书架。

为了理清思路,我们写下一些输入和期望输出的例子。

以下是几个输入输出示例:

  • 输入:作者 Takaucchi,书籍 Salem Moon(非特殊书籍)。
    • 输出:将书放在底层,并按喜好排序。
  • 输入:作者 Jane Austen,书籍 Pride and Prejudice(特殊书籍)。
    • 输出:将书放在顶层其他书籍的前面。
  • 输入:作者 Mercedes Lackey(非特殊书籍)。
    • 输出:将书放在中层,并按喜好排序。

从这些例子中,我们注意到对于特殊书籍,输出中不包含“排序”的指示。

规划活动

上一节我们通过例子明确了程序的功能,本节中我们来规划代码的结构。

根据设计,我们需要从用户那里获取两类信息。以下是我们的计划步骤:

  1. 获取用户输入:询问作者姓名,并询问书籍是否特殊。
  2. 确定输出内容:这分为两部分:
    • 确定书籍放在哪一层。
    • 确定书籍在层内的具体位置(前面还是按序摆放)。
  3. 初始化输出字符串:我们将逐步构建最终要显示的字符串。

为了更清晰地实现“确定输出内容”的步骤,我们进一步规划:

  • 将书籍所属的层(如 topmiddlebottom)存储在一个变量中。
  • 根据书籍是否特殊,构建完整的输出字符串。

实现活动:获取输入与初始化

现在,我们开始将规划转化为代码。首先实现获取用户输入和初始化。

# 1. 获取用户输入
author = input("What is the author's name? ")
is_special = input("Is this a special book? (yes/no) ")

# 2. 初始化输出字符串
output = "Put the book"

我们使用 input() 函数获取用户输入。注意字符串内引号的使用和添加空格以提升可读性。输出字符串初始化为 "Put the book",因为这是所有输出共有的部分。

实现活动:确定书架层

接下来,我们需要根据作者姓名决定书籍放在哪一层。我们按字母顺序将作者分为三组:

  • A-J:顶层
  • K-P:中层
  • Q-Z:底层

我们可以使用字符串比较来实现这个逻辑。

# 3. 确定书籍放在哪一层
if author < 'I':
    shelf = 'top'
elif author < 'Q':
    shelf = 'middle'
else: # 介于 'Q' 和 'Z' 之间
    shelf = 'bottom'

这段代码使用了 ifelifelse 语句。字符串比较基于字母顺序。如果 author < 'I' 为真,说明作者姓氏以 A-H 开头,归为顶层。否则,检查 author < 'Q',为真则归为中层。如果前两个条件都不满足,则执行 else 分支,归为底层。

验证与调试活动

在继续编写之前,我们应该运行现有代码以验证其正确性。我们可以在 Python Tutor 中测试不同的作者输入,观察 shelf 变量的赋值是否符合预期(例如,输入 Takaucchi 应得 bottom,输入 Austen 应得 top,输入 Lackey 应得 middle)。这是一个重要的习惯,有助于及早发现问题。

实现活动:构建最终输出

现在,我们根据书籍是否特殊来构建完整的输出字符串。

# 4. 确定书籍在层内的位置并构建完整输出
if is_special == 'yes':
    output += " in front of the other books on the " + shelf + " shelf."
else:
    output += " on the " + shelf + " shelf and sort as you like."

# 5. 打印最终结果
print(output)

如果 is_special 等于 'yes',我们添加特定字符串,并将之前确定的 shelf 变量值嵌入其中。否则,添加另一段字符串。最后,使用 print() 函数输出结果。

在构建字符串时,需要注意单词之间的空格,我们使用 + " " + 或直接在字符串字面量中包含空格来确保输出格式正确。

总结

本节课中我们一起学习了 if 语句的实践应用。我们完成了一个书籍整理程序,它能够根据作者和书籍特殊性提供摆放建议。更重要的是,我们体验了编写程序的完整流程:从设计示例开始,然后规划代码步骤,接着实现代码,并不时地验证结果,在遇到语法错误时进行调试。记住,这些活动并非严格线性,你可以根据需要来回切换。现在,你可以尝试修改这个代码,用它来解决其他决策问题,例如根据天气决定穿衣,或者根据时间决定吃什么。

024:多因素决策 - 嵌套if与elif 🐦

在本节课中,我们将要学习如何在Python中实现更复杂的决策过程,即处理多个条件。我们将通过一个“猜鸟”游戏来理解如何使用嵌套的if语句和elif子句来构建多因素决策逻辑。

概述

你已经学会了如何使用if语句实现简单的决策。然而,现实中的决策往往更加复杂,可能涉及多个相互关联的条件。例如,在一个猜鸟游戏中,你需要根据鸟的颜色、大小和是否会飞等多个特征来判断鸟的种类。本节将教你如何编写代码来处理这类多因素决策。

嵌套if语句的结构

首先,让我们回顾一下if语句的基本格式,并了解如何将其扩展以处理多个决策。

一个标准的if-elif-else语句结构如下:

if condition1:
    # 代码块1
elif condition2:
    # 代码块2
else:
    # 代码块3

在这个结构中:

  • condition1condition2 是计算结果为TrueFalse的表达式。
  • 如果 condition1True,则执行代码块1
  • 如果 condition1Falsecondition2True,则执行代码块2
  • 如果所有条件都为 False,则执行代码块3
  • 最终只会执行其中一个代码块。

关键在于,代码块1代码块2代码块3本身都可以是另一个if语句。这就是所谓的“嵌套if语句”。

嵌套if语句示例

以下是一个嵌套if语句的示例,其中代码块1被替换为一个新的if语句:

if condition1:
    if condition1A:
        # 代码块1A
    else:
        # 代码块1B
elif condition2:
    # 代码块2
else:
    # 代码块3

请注意代码的缩进:

  • 内部的if语句(第2-5行)相对于外部的if(第1行)缩进了4个空格。
  • 代码块1A(第3行)相对于其所属的内部if语句又缩进了4个空格,总共缩进8个空格。
  • 一致的缩进对于Python理解代码结构至关重要。

在这个例子中:

  • 代码块1A 仅在 condition1 condition1A 都为 True 时执行。
  • 代码块1B 仅在 condition1True condition1AFalse 时执行。

实践:猜鸟游戏 🎯

上一节我们介绍了嵌套if语句的理论结构,本节中我们来看看如何将其应用于一个具体的游戏。

让我们实现一个简化的猜鸟游戏。游戏规则如下:

  • 我们只考虑5种鸟。
  • 鸟的特征只有颜色(红、蓝、黄)和大小(小、大)。
  • 根据用户输入的颜色和大小,程序输出对应的鸟名。

以下是5种鸟及其特征:

鸟名 颜色 大小
北美红雀
猩红鹮
蓝鸲
蓝鹭
黄莺

方案一:先判断颜色

以下是先判断颜色,再在颜色分支内判断大小的代码实现:

# 获取用户输入
color = input(“鸟是什么颜色?(红/蓝/黄): “)
size = input(“鸟是大还是小?(大/小): “)

# 外层if语句:先判断颜色
if color == “蓝”:
    # 嵌套if语句:判断蓝色鸟的大小
    if size == “小”:
        print(“蓝鸲”)
    else: # 假定为“大”
        print(“蓝鹭”)
elif color == “红”:
    # 嵌套if语句:判断红色鸟的大小
    if size == “小”:
        print(“北美红雀”)
    else: # 假定为“大”
        print(“猩红鹮”)
else: # 假定颜色为“黄”
    # 只有一种黄鸟,无需判断大小
    print(“黄莺”)

代码执行流程分析:

  1. 程序首先检查颜色是否为“蓝”。
  2. 如果是,则进入内部的if语句检查大小,并输出“蓝鸲”或“蓝鹭”。
  3. 如果不是蓝色,则检查颜色是否为“红”。
  4. 如果是红色,则进入另一个内部的if语句检查大小,并输出“北美红雀”或“猩红鹮”。
  5. 如果既不是蓝色也不是红色,则执行else子句,直接输出“黄莺”。

方案二:先判断大小

我们也可以改变决策的顺序,先判断大小,再判断颜色。以下是另一种可行的代码结构:

# 获取用户输入
color = input(“鸟是什么颜色?(红/蓝/黄): “)
size = input(“鸟是大还是小?(大/小): “)

# 外层if语句:先判断大小
if size == “小”:
    # 嵌套if语句:判断小鸟的颜色
    if color == “黄”:
        print(“黄莺”)
    elif color == “蓝”:
        print(“蓝鸲”)
    else: # 假定为“红”
        print(“北美红雀”)
else: # 假定大小为“大”
    # 嵌套if语句:判断大鸟的颜色(只有红和蓝)
    if color == “蓝”:
        print(“蓝鹭”)
    else: # 假定为“红”
        print(“猩红鹮”)

这个方案同样能正确运行。它首先根据大小将鸟分为两类,然后在每个类别中根据颜色进行细分。

关于输入验证的思考 🤔

在编写此类程序时,需要考虑用户可能不按规则输入的情况。例如,如果用户输入“绿色”和“中等”,我们的程序会如何处理?

方案一的代码中:

  • color == “蓝”False
  • color == “红”False
  • 程序会进入else子句,输出“黄莺”。

这显然是一个错误的结果。为了完善程序,我们可以:

  1. 增加输入提示的明确性,在input函数中列出有效选项。
  2. 增加输入验证,例如使用循环直到用户输入有效值为止。
  3. 扩展决策逻辑,在elif链中增加对“绿”色的检查。

总结

本节课中我们一起学习了如何使用嵌套的ifelif语句在Python中实现多因素决策。

  • 核心概念:你可以将if语句嵌套在另一个ifelifelse的代码块中,以处理连续的、依赖性的决策。
  • 代码结构:决策的顺序(先判断颜色还是先判断大小)会影响代码的结构,但两种方式都可能实现相同的逻辑。在编码前,用表格列出所有可能情况有助于理清思路。
  • 重要实践:完成代码后,务必测试所有可能的输入组合,确保每个分支都被覆盖到,并考虑如何处理无效输入。

嵌套if语句是解决复杂决策问题的强大工具,熟练掌握它将使你能够编写出更智能、更灵活的程序。

025:处理真或假 - 布尔值与变量 🃏

在本节课中,我们将学习一种新的编程概念:布尔值。布尔值用于表示“真”或“假”,是编程中进行逻辑判断的基础。我们将通过纸牌游戏和甜点选择的例子,来理解如何创建和使用布尔变量。


什么是布尔值?🤔

有时,我们需要记录某件事是否为真。例如,你可能想记录一位朋友是否喜欢玩纸牌。如果他们喜欢,你就会想邀请他们来玩,并告诉他们相关的活动。

为了记录某事是否为真,我们可以使用值 TrueFalse。这被称为布尔值,它是一种独立的数据类型。布尔类型只有两个可能的值:要么是 True,要么是 False。在 Python 中,我们直接写成 TrueFalse。请注意,它们不是字符串,周围没有引号,并且首字母大写。这是 Python 创造者规定的写法。

既然有了新的数据类型,就意味着我们可以有布尔变量,它可以被赋值为 TrueFalse 中的一个。变量也可以被赋值为一个布尔表达式的结果,即任何计算结果为 TrueFalse 的表达式。

让我们看一些例子。


布尔变量示例

在第一个例子中,我们有一个名为 vegetarian 的变量,它被赋值为 True。这意味着 vegetarian 是一个布尔变量。我们可以通过 print(type(vegetarian)) 来验证,它会打印出 class 'bool',表明 vegetarian 是一个布尔变量。boolboolean 的缩写。

再看这段代码:

money_for_ticket = amount > 10

赋值运算符 = 右边的整个部分是一个表达式。我们应该先计算这个表达式,然后将结果赋值给变量 money_for_ticket。表达式 amount > 10 是一个问题:“amount 大于 10 吗?” 假设 amount 的值为 20,那么 amount > 10 的结果就是 True。此时,值 True 就会被赋值给变量 money_for_ticket


应用示例:朋友是否玩纸牌?👫

现在,让我们回到纸牌的例子。我们想知道朋友是否玩纸牌,并编写了一些 Python 代码来询问他们并存储这个信息。

我们首先创建一个名为 cards_play 的布尔变量,并将其初始值设为 False。然后,代码会提示询问朋友是否玩纸牌,并使用 input 函数获取他们的回答。假设他们输入 Y 表示“是”。代码会检查输入是否为 Y,如果是,就将 cards_play 的值改为 True。如果他们输入 N,则 cards_play 的值保持为 False

接下来,我们看看如何使用这个布尔变量 cards_play。我们添加了一个 if 语句来检查 cards_play 是否为 True。如果是,就打印一条邀请朋友来玩纸牌的消息。我们还添加了另一个 if 语句,它直接检查 cards_play 变量本身的值。因为布尔变量本身就是一个布尔表达式,所以可以直接用在 if 条件中。这两种写法是等效的,但最佳实践是直接使用变量本身,如第二个 if 语句所示。


另一个示例:选择甜点 🍰

假设我们有三种甜点:大量的冰淇淋、足够多的蛋糕和少量的甜甜圈。你想给朋友一份他们喜欢的甜点,但由于每种甜点数量有限,你希望给他们你拥有最多的、且他们喜欢的那种。

以下是实现此逻辑的代码步骤:

首先,我们需要弄清楚他们喜欢哪种甜点。代码通过询问用户对蛋糕、甜甜圈和冰淇淋的喜好来完成这一步。对于每个问题,用户的回答(YN)会被用来设置一个对应的布尔变量(如 cake_like)。

这里有一个重要的细节:代码可以将输入和比较合并到一行中。例如:

donut_like = input("Do you like donuts? (Y/N): ") == "Y"

在这行代码中,Python 会先执行 input 函数获取用户输入,然后立即将输入结果与字符串 "Y" 进行比较。如果相等,表达式结果为 True,并赋值给 donut_like;否则为 False

在获取了用户的所有偏好后,我们使用一个大的 if-elif-else 语句来决定提供哪种甜点。我们优先检查他们是否喜欢我们拥有最多的冰淇淋,然后是蛋糕,最后是甜甜圈。如果他们都不喜欢,则告知他们没有其他甜点了。


总结与回顾 📚

让我们总结一下关于布尔值的要点:

  • 布尔值只能是 TrueFalse
  • 在赋值语句中,总是先计算右边的表达式。
  • 如果表达式很长或很复杂,需要逐步分解以确定计算顺序。例如,在 cake_like = input(prompt) == "Y" 中,需要先获取 prompt 的值,然后执行 input 函数得到用户输入的字符串,最后进行字符串比较,得到一个布尔值。

补充:特别的纸牌与杰出女性 💻

在结束之前,我想再谈谈之前展示的那副纸牌。我非常喜欢玩纸牌,同时也热衷于支持计算机科学领域的女性。我和另外两个人共同创建了一副特别的纸牌,每张牌上都印有一位在计算领域有杰出贡献的女性,总计54位,旨在让人们了解这些了不起的女性。

其中一位是 莎菲·戈德瓦塞尔。她是美国麻省理工学院的计算机科学教授,也是以色列魏茨曼科学研究所的数学教授。她是概率加密技术的共同发明者之一。概率加密对于确保数据安全加密至关重要,例如,当你在互联网上登录网站时,它能保护你输入的个人信息在传输过程中的安全。因其在计算机科学领域的诸多贡献,她获得了图灵奖——这是计算机科学领域的最高荣誉。莎菲·戈德瓦塞尔非常了不起。

本节课中,我们一起学习了布尔值的基本概念、如何创建和使用布尔变量,并通过实际例子看到了它们在程序逻辑判断中的应用。

026:嵌套if与elif及布尔变量演示 🐟

在本节课中,我们将学习如何编写一个程序来计算在鱼店购买鱼类的总费用。我们将使用嵌套的if语句和elif来处理多种条件,例如鱼的种类(天使鱼或孔雀鱼)和大小(成年或幼年)。通过这个例子,你将理解如何通过组合条件判断来构建更复杂的程序逻辑。


程序概述

假设我们有一个鱼缸,需要购买更多的鱼,因此我们将前往鱼店。我们将编写一个程序来计算购买鱼类的总费用。通常,购买鱼时不会只买一条,因此我们需要进行一些计算来确定总成本。为简化问题,我们只关注两种鱼:孔雀鱼和天使鱼。

请注意,孔雀鱼的复数是“guppies”,而天使鱼的复数仍是“angelfish”。每种鱼,成年鱼和幼鱼的价格不同。

设置价格

首先,我们在代码顶部添加注释,说明每种鱼的价格。这里使用美元作为货币单位,但你可以根据所在地调整货币和价格。

# 鱼店
# 成年天使鱼:每条5美元
# 幼年天使鱼:每条0.5美元
# 成年孔雀鱼:每条3美元
# 幼年孔雀鱼:每条0.1美元

规划程序步骤

由于程序较为复杂,我们先规划主要步骤,并为每个步骤编写注释,然后再填充具体代码。我们识别出三个主要步骤:

  1. 获取用户输入:确定要购买的鱼种类、大小和数量。
  2. 计算总费用:根据输入信息进行计算。
  3. 打印结果:向用户显示详细的总费用信息。

第一步:获取用户输入

首先,我们需要询问用户想要购买的鱼种类。我们将使用input()函数获取输入,并将其存储在变量中。

# 获取鱼种类
species = input("Type of fish (guppy or angelfish): ")

接下来,询问鱼的大小是成年还是幼年。为了简化用户输入,我们要求用户输入‘a’代表成年,‘b’代表幼年。

# 获取鱼的大小
size = input("Enter a for adult, b for baby: ")

最后,询问用户想要购买的数量,并将输入转换为整数类型,以便后续计算。

# 获取购买数量
number = int(input("How many? "))

第二步:计算总费用

在获取所有必要信息后,我们开始计算总费用。首先,初始化一个变量total为0.0。

# 初始化总费用
total = 0.0

现在,我们需要根据鱼的种类和大小来决定单价。这需要使用嵌套的if语句。首先检查种类是否为“angelfish”。

if species == "angelfish":
    # 嵌套if判断大小
    if size == "a":
        # 成年天使鱼:5美元/条
        total = total + 5.0 * number
    else:
        # 幼年天使鱼:0.5美元/条
        total = total + 0.50 * number

如果种类不是“angelfish”,则使用elif检查是否为“guppy”。

elif species == "guppy":
    # 嵌套if判断大小
    if size == "a":
        # 成年孔雀鱼:3美元/条
        total = total + 3.0 * number
    else:
        # 幼年孔雀鱼:0.1美元/条
        total = total + 0.10 * number

如果用户输入了其他鱼种类,我们使用else分支来告知用户我们没有这种鱼。

else:
    print("We do not have that type of fish.")

第三步:构建并打印结果

计算完成后,我们需要向用户打印一个包含详细信息的字符串,而不是仅仅一个数字。我们将构建一个字符串answer,逐步添加数量、大小、种类和总费用。

# 开始构建结果字符串
answer = "Total cost for " + str(number)

# 根据大小添加“adult”或“baby”
if size == "a":
    answer = answer + " adult "
else:
    answer = answer + " baby "

# 添加鱼种类和总费用
answer = answer + species + " is " + str(total)

# 打印最终结果
print(answer)

程序测试

让我们通过几个例子来测试程序。

测试用例 1:购买7条幼年天使鱼

  • 输入:种类=angelfish, 大小=b, 数量=7
  • 计算:0.50 * 7 = 3.5
  • 输出:Total cost for 7 baby angelfish is 3.5

测试用例 2:购买10条成年孔雀鱼

  • 输入:种类=guppy, 大小=a, 数量=10
  • 计算:3.0 * 10 = 30
  • 输出:Total cost for 10 adult guppy is 30

如果输入了不支持的鱼种类,程序会输出:We do not have that type of fish.


总结

本节课中,我们一起学习了一个综合性的编程示例。我们通过使用嵌套的if语句和elif分支,处理了基于多个条件(鱼的种类和大小)的程序逻辑。我们还练习了如何从用户获取输入、进行数值计算以及构建和打印格式化的输出字符串。这个例子展示了如何将复杂问题分解为多个简单步骤,并组合使用基本编程结构来解决实际问题。

027:嵌套if、elif与布尔变量第二部分演示 🐠

在本节课中,我们将学习如何使用一个布尔变量来优化程序逻辑,确保只有在商店确实拥有顾客想要的鱼时,才计算并打印总价。我们将通过一个具体的例子来演示如何修正程序,避免输出多余或不正确的信息。


上一节我们讨论了基本的条件判断结构。本节中,我们来看看如何通过引入一个标志变量来更精确地控制程序的流程。

假设顾客想购买三条成年小丑鱼,但我们的商店并不出售这种鱼。运行原始程序后,输出结果会包含两条信息:

  1. “我们没有那种鱼。”
  2. “三条成年小丑鱼的总价是 0.0。”

第一条信息是正确的,但第二条信息是多余的,并且可能引起误解。我们的目标是:当商店没有顾客想要的鱼时,只打印第一条信息,而不打印任何关于价格的信息。

为了实现这个目标,我们可以引入一个名为 have_fish 的布尔变量。程序开始时,我们假设商店有这种鱼,将 have_fish 设置为 True。一旦程序在检查中发现商店没有这种鱼,就将 have_fish 的值改为 False。最后,只有在 have_fishTrue 时,才执行计算和打印总价的代码。

以下是实现此逻辑的关键步骤:

  1. 初始化布尔变量:在程序开头,我们声明并初始化这个变量。

    have_fish = True
    
  2. 在适当位置修改变量值:当程序执行到 else 分支(即商店没有顾客指定的鱼)时,除了打印提示信息,还需要将 have_fish 设置为 False

    else:
        print("We do not have that fish.")
        have_fish = False
    
  3. 用布尔变量控制最终输出:将计算和打印总价的所有代码放入一个 if 语句中,这个 if 语句的条件就是检查 have_fish 是否为 True

    if have_fish:
        # 此处缩进所有计算和打印总价的代码
        if size == "adult":
            # ... 计算价格 ...
        # ... 构建答案字符串并打印 ...
    

通过以上修改,程序逻辑变得更加清晰和健壮。当商店没有鱼时,have_fish 变为 False,后续关于价格的计算和打印都不会执行,从而避免了输出矛盾的信息。


让我们通过Python Tutor来可视化整个执行过程,以验证修改后的逻辑是否正确。

首先,我们测试商店没有的鱼(如小丑鱼)。输入“clownfish”、“adult”和数量“3”后,程序会正确打印“We do not have that fish.”,并且不会显示任何关于总价的信息。

接着,我们测试商店拥有的鱼(如孔雀鱼)。输入“guppy”、“adult”和数量“5”后,程序会跳过“我们没有那种鱼”的提示,正常计算并打印“Total cost for 5 adult guppies is 15”。

这两个测试表明,我们的修改是成功的。布尔变量 have_fish 作为一个有效的“开关”,精确地控制了不同情况下的程序行为。


本节课中我们一起学习了如何使用布尔变量作为标志来优化条件逻辑。关键点在于:

  1. 在程序开始时初始化一个布尔变量(通常设为 True)。
  2. 在程序运行过程中,根据条件(例如,发现不匹配项)将其值更新为 False
  3. 最后,根据这个布尔变量的值来决定是否执行某段关键代码(如计算和输出)。

这种方法使得程序逻辑更清晰,避免了在无效情况下执行不必要的操作或产生令人困惑的输出,是编写健壮代码的常用技巧。

028:嵌套if、elif与布尔变量第三部分演示 🐠

在本节课中,我们将学习如何使用布尔变量来优化代码逻辑,特别是结合嵌套的 if 语句和 elif 语句。我们将通过一个鱼类商店的计价程序来演示如何将用户输入转换为更易读、更易维护的布尔值。


上一节我们介绍了嵌套 ifelif 的基本用法,本节中我们来看看如何引入布尔变量来简化条件判断。

现在,我想展示另一种编写部分代码的方法,即使用布尔变量。我将复制现有代码,并对其进行修改。

以下是修改前的原始代码片段:

size = input("Enter A for adult and B for baby: ")

我将创建一个新的变量 size_is_adult,其名称像一个提问:“尺寸是成年鱼吗?”,这表示该变量将存储一个布尔值(TrueFalse)。

修改后的代码如下:

size_is_adult = (input("Enter A for adult and B for baby: ") == 'A')

当用户输入 'A' 时,表达式 input(...) == 'A' 的结果为 True,变量 size_is_adult 被设置为 True。如果用户输入 'B' 或其他任何内容,该表达式结果为 False,变量 size_is_adult 被设置为 False

将变量名从 size 改为 size_is_adult 的优势在于,名称更具描述性,并且明确提示这是一个布尔值。

更改变量名也意味着所有使用 size 变量的地方都需要更新。更新不一定只是更改变量名,我们可能还需要改变变量的使用方式。

例如,在检查鱼类是否为天使鱼之后,我们现在要问一个不同的问题:我们想知道 size_is_adult 是否为 True。我们可以直接检查它的值。

以下是需要更新的代码位置:

  1. 在检查是否为天使鱼后,我们询问尺寸:

    if size_is_adult:
        # 处理成年鱼逻辑
    
  2. 同样,在检查是否为孔雀鱼后,也需要更新:

    if size_is_adult:
        # 处理成年鱼逻辑
    
  3. 在代码后部计算成本时,也需要根据 size_is_adult 进行判断:

    if size_is_adult:
        # 应用成年鱼价格
    else:
        # 应用幼鱼价格
    

让我们运行修改后的代码,看看是否在所有正确的位置都进行了更新。

假设我们输入天使鱼,然后输入 B 代表幼鱼,再输入购买数量 10。程序应正确计算总成本:10 条幼年天使鱼,每条 0.5 元,总计 5 元。

运行结果表明程序工作正常。


本节课中我们一起学习了如何利用布尔变量来优化条件判断逻辑。我们通过一个实例,将用户输入的字符(如 'A''B')转换为更具语义的布尔值(TrueFalse),从而让代码更清晰、更易于维护。我们还回顾了如何将这种布尔变量与嵌套的 ifelif 语句结合使用,以处理复杂的多条件决策场景。

029:复合布尔表达式 🧠

在本节课中,我们将要学习如何组合简单的布尔表达式,以创建更复杂的逻辑判断。我们将重点介绍三种逻辑运算符:andornot,并通过具体的例子来理解它们的工作原理。

概述:理解复合条件

到目前为止,我们已经接触了一些布尔表达式。本节中,我们将看看如何以不同的方式组合它们,形成复合布尔表达式。复合表达式允许我们同时检查多个条件,这在编程中非常有用。

使用 and 运算符

假设你需要判断一个数字是否为自然数。我们定义自然数为任何正整数。这意味着该数字必须同时满足两个条件:是正数 并且 是整数。

以下是判断一个数字是否为自然数的逻辑步骤:

  1. 检查数字是否大于零(正数)。
  2. 检查数字的数据类型是否为整数。
  3. 只有当两个条件都为真时,该数字才是自然数。

让我们通过一个代码示例来理解:

num = 10
is_positive = num > 0
is_int = type(num) == type(5)
is_natural = is_positive and is_int

在这个例子中:

  • is_positive 的值为 True,因为 10 > 0。
  • is_int 的值为 True,因为 type(10)type(5) 都是整数类型。
  • 由于 is_positiveis_int 都为 True,所以 is_natural = True and True 的结果是 True

and 运算符要求其两边的表达式都为真,整个复合表达式才为真。

使用 or 运算符

现在考虑另一个场景:决定是否穿外套。如果天气冷 或者 正在下雨,你就会穿外套。这里,只需要至少一个条件为真即可。

以下是相关的逻辑判断:

  1. 检查天气是否冷。
  2. 检查是否正在下雨。
  3. 如果任一条件为真,就穿外套。

让我们看看代码实现:

temp = "cold"
weather = "sunny"
is_cold = (temp == "cold")
is_raining = (weather == "raining")
wear_coat = is_cold or is_raining

在这个例子中:

  • is_cold 的值为 True
  • is_raining 的值为 False
  • 由于 is_coldTrue,所以 wear_coat = True or False 的结果是 True。你仍然需要穿外套。

or 运算符只要其两边的表达式有一个为真,整个复合表达式就为真。

逻辑运算符真值表

为了更系统地理解 andor,我们可以参考下面的真值表。假设 AB 代表两个任意的布尔表达式。

A B A and B A or B
False False False False
False True False True
True False False True
True True True True

核心规则总结:

  • A and B:仅当 A B 都为 True 时,结果为 True
  • A or B:仅当 A B 都为 False 时,结果为 False。只要有一个为 True,结果就是 True

使用 not 运算符

除了组合条件,我们有时还需要反转一个条件的逻辑值。这时就需要 not 运算符。

not 运算符会取反其后布尔表达式的值:

  • 如果表达式为 Truenot 使其变为 False
  • 如果表达式为 Falsenot 使其变为 True

请看示例:

variable = 10 > 5  # 结果为 True
reverse_var = not variable  # not True 的结果是 False

这里,因为 10 > 5True,所以 not variable 的结果是 False

总结与挑战

本节课我们一起学习了如何使用三种逻辑运算符来构建复合布尔表达式:

  • and:用于检查所有条件是否同时成立。
  • or:用于检查至少一个条件是否成立。
  • not:用于反转一个条件的真假值。

掌握这些运算符是编写复杂程序逻辑的基础。给你的挑战是:思考如何在你自己的编程项目中使用复合布尔表达式。只有通过不断练习,你才能更快速、更直观地理解和运用这些概念。

030:复杂布尔表达式与短路求值 🧠

在本节课中,我们将深入学习Python中复杂布尔表达式的构建与求值规则,特别是运算符的优先级和“短路求值”这一重要概念。理解这些规则能帮助你编写出逻辑正确且运行高效的代码。

运算符优先级回顾

上一节我们介绍了多种运算符,本节中我们来看看它们的优先级顺序。Python的运算符优先级决定了表达式中运算的执行顺序,从高到低排列如下:

  • 最高优先级**(幂运算)
  • 数学运算符*, /, //, %, +, -
  • 比较运算符==, !=, <, >, <=, >=
  • 逻辑非运算符not
  • 逻辑与运算符and
  • 最低优先级or

一个复杂条件的例子

让我们通过一个具体问题来应用这些规则。假设我们需要检查一个数字是否同时满足两个条件:是奇数 并且 在0到100(含)这个范围之外

以下是分析这个问题的步骤:

  1. 判断是否为奇数:检查数字除以2的余数是否不等于0。用代码表示是:num % 2 != 0
  2. 判断是否在范围外:检查数字是否小于0 或者 大于100。用代码表示是:num < 0 or num > 100

要同时满足这两个条件,我们需要用 and 运算符将两个子表达式连接起来。一个初步的代码尝试可能如下所示,但请注意,这段代码包含一个逻辑错误:

num = int(input())
if num % 2 != 0 and num < 0 or num > 100:
    print("这个数字是奇数且在范围外")

逐步求值与错误分析

现在,让我们逐步分析当 num = 200 时,上面代码中复杂布尔表达式的求值过程。

根据优先级规则,and 的优先级高于 or,因此表达式 num % 2 != 0 and num < 0 or num > 100 会被分组为 (num % 2 != 0 and num < 0) or num > 100

  1. 首先计算 and 部分:num % 2 != 0 and num < 0
    • num % 2 != 0200 % 2 等于 0,所以 0 != 0False
    • num < 0200 < 0False
    • False and False 的结果是 False
  2. 现在表达式简化为:False or num > 100
  3. 计算 or 部分:False or num > 100
    • num > 100200 > 100True
    • False or True 的结果是 True

最终,整个条件被判定为 True,程序会打印信息。但逻辑上,200是偶数,并不满足“是奇数”的条件,所以这里出现了错误。问题的根源在于,由于优先级,and 错误地只将“奇数检查”和“小于0检查”绑定在了一起。

使用括号修复逻辑

要修复这个错误,我们需要使用括号 () 来明确指定运算顺序。括号拥有最高的优先级,可以覆盖默认规则。

以下是修正后的代码:

num = int(input())
if num % 2 != 0 and (num < 0 or num > 100):
    print("这个数字是奇数且在范围外")

现在,表达式被正确地分组为 num % 2 != 0 and (num < 0 or num > 100)。当 num = 200 时:

  1. 计算括号内:num < 0 or num > 100 -> False or True -> True
  2. 计算整个表达式:num % 2 != 0 and True -> False and True -> False

条件结果为 False,不会执行打印语句,这符合我们的逻辑预期。

短路求值原理

在计算 num % 2 != 0 and (num < 0 or num > 100) 时,Python 发现第一个子表达式 num % 2 != 0 已经是 False。对于 and 运算符来说,只要有一个操作数为 False,整个表达式的结果就一定是 False。因此,Python 会跳过对第二个子表达式 (num < 0 or num > 100) 的计算。这种优化行为就叫做短路求值

短路求值的规则如下:

  • 对于 A and B
    • 如果 AFalse,则整个表达式必定为 False短路发生,不计算 B
    • 如果 ATrue,则必须计算 B 才能确定结果,不会短路
  • 对于 A or B
    • 如果 ATrue,则整个表达式必定为 True短路发生,不计算 B
    • 如果 AFalse,则必须计算 B 才能确定结果,不会短路

短路求值的应用与最佳实践

短路求值不仅能提升微小的性能,更能避免不必要的计算甚至运行时错误。

请看以下示例:

# 示例1:利用 or 的短路避免除零错误
result = (10 > 5) or (1 / 0 == 1)  # 第一个为True,短路发生,不会计算1/0,结果为True。

# 示例2:and 未短路导致错误
result = (10 < 5) and (1 / 0 == 1)  # 第一个为False,短路发生,不会计算1/0,结果为False。

# 示例3:or 未短路导致错误
result = (10 < 5) or (1 / 0 == 1)  # 第一个为False,必须计算第二个,引发 ZeroDivisionError。

根据短路行为,我们可以得出编写高效布尔表达式的最佳实践:

  • 在使用 and 时,将最可能为 False 的条件放在前面。
  • 在使用 or 时,将最可能为 True 的条件放在前面。

使用括号提升代码可读性

即使你清楚运算符优先级,也强烈建议在复杂的表达式中使用括号。括号不仅能强制指定运算顺序,更能极大地提高代码的可读性,让你和他人日后更容易理解代码意图。

比较以下两行代码:

# 难以快速理解
if not A or B:
    ...

# 意图清晰明了
if (not A) or B:
    ...

它们逻辑完全等价,但第二行因为括号而一目了然。当然,使用括号时要小心,确保它带来的分组效果符合你的逻辑意图。

本节课中我们一起学习了Python运算符的优先级、如何构建和解析复杂的布尔表达式,以及短路求值这一重要机制及其最佳实践。记住,合理运用括号和短路逻辑,是写出既正确又优雅的代码的关键。编程不仅是逻辑的构建,更是创造力的体现,祝你编码愉快!

031:逻辑运算符与短路求值演示 🧠

在本节课中,我们将学习如何使用逻辑运算符(如 andor)以及理解Python的“短路求值”特性。我们将通过构建一个简单的登录系统来演示这些概念,让你清晰地看到它们在实际编程中如何工作。


概述与准备工作

首先,我们计划创建一个简单的登录界面。为了清晰地组织思路,我们先写下一些注释来规划代码结构。

以下是我们的计划:

  1. 设置我们要检查的正确值(用户名和密码)。
  2. 获取用户输入的用户名和密码。
  3. 检查用户输入的信息是否与正确值匹配。

现在,让我们开始编写代码。


第一步:设置正确值

在第一个注释下,我们将定义程序期望的正确用户名和密码。

# 设置要检查的值
valid_username = "clever_tomato990"
valid_password = "1A2AB3"

这里,valid_usernamevalid_password 是变量,用于存储我们认可的登录凭证。你可以将它们设置为你喜欢的任何值。


第二步:获取用户输入

接下来,我们需要获取用户尝试登录时输入的信息。我们将使用 input() 函数来实现。

# 获取用户输入的信息
username = input("请输入用户名: ")
password = input("请输入密码: ")

注意,在提示字符串的冒号后我们加了一个空格。这是为了在终端中,提示信息和用户输入的内容之间有一个清晰的间隔,使界面更友好。

为了验证代码到目前为止是否按预期工作,我们可以添加打印语句来检查变量的值。

# 打印检查(后续可注释掉)
print(username)
print(password)

运行程序,输入测试值,确认 usernamepassword 变量正确地捕获了我们的输入。验证无误后,可以将这些打印语句注释掉,以保持代码整洁。


第三步:使用 if 语句和 and 运算符验证登录

现在,我们有了正确的凭证和用户的输入。我们需要比较它们。这需要使用 if 语句和逻辑运算符。

一个简单的想法是:仅当用户名密码都正确时,用户才能登录。这正好对应 and 运算符的语义。

# 检查用户信息是否匹配有效值
if username == valid_username and password == valid_password:
    print("欢迎 " + username + "!")
else:
    print("用户名或密码错误。")

代码解释

  • username == valid_username 检查输入的用户名是否匹配。
  • password == valid_password 检查输入的密码是否匹配。
  • and 运算符要求两个条件都必须为真,整个表达式才为真。
  • 如果为真,执行 if 块,打印欢迎信息。
  • 否则,执行 else 块,提示错误。

运行程序,分别测试正确和错误的用户名密码组合,可以看到程序按预期工作。


理解短路求值 🚦

上一节我们使用了 and 运算符。现在,让我们深入理解一个关键概念:短路求值

当我们运行之前的程序并输入一个错误的用户名(但密码正确)时,程序直接输出“用户名或密码错误”。那么,它检查密码了吗?

答案是没有。这就是短路求值在起作用。

对于 and 运算符:

  • Python会从左到右评估条件。
  • 如果第一个条件(例如 username == valid_username)为 False,那么无论第二个条件是什么,整个 and 表达式已经确定是 False
  • 因此,Python会“短路”,跳过对第二个条件的评估,以节省时间。

同理,对于 or 运算符:

  • 如果第一个条件为 True,整个表达式已经确定是 True,Python也会短路,不再检查第二个条件。

功能升级:支持使用邮箱登录

现在,让我们让程序变得更灵活一些。许多网站允许使用用户名邮箱登录。我们如何修改代码来支持这个功能呢?

首先,我们需要一个有效的邮箱地址。

valid_email = "anonymous@email.com"

然后,修改输入提示,让用户知道可以输入邮箱。

username_or_email = input("请输入用户名或邮箱: ")
password = input("请输入密码: ")

最关键的是修改 if 语句的判断逻辑。现在,有效的登录凭证是:(用户名正确 或 邮箱正确) 且 密码正确

if (username_or_email == valid_username or username_or_email == valid_email) and password == valid_password:
    print("欢迎 " + valid_username + "!")
else:
    print("用户名/邮箱或密码错误。")

代码解释

  • 使用括号 () 明确优先级:先计算 or 部分。
  • username_or_email == valid_username or username_or_email == valid_email:只要输入匹配了用户名邮箱中的任意一个,这个子表达式就为 True
  • 然后,这个结果再与 password == valid_password 通过 and 结合。两者都必须为真才能登录成功。

运行程序,使用邮箱和正确密码登录,验证功能是否生效。


提供更具体的错误提示

目前,我们的错误提示比较笼统。出于学习目的,我们尝试告诉用户具体是用户名错误还是密码错误。(注意:在实际产品中,出于安全考虑,通常不提供如此具体的错误信息。)

逻辑如下:

  1. 如果登录成功,显示欢迎信息。
  2. 如果登录失败(if 条件为假),则进入 else 部分。
  3. else 部分中,我们进一步判断:如果密码不匹配,则提示“密码错误”;否则,就假定是用户名/邮箱错误。

我们可以使用 elif(else if)语句来实现。

if (username_or_email == valid_username or username_or_email == valid_email) and password == valid_password:
    print("欢迎 " + valid_username + "!")
elif password != valid_password:
    print("密码错误。")
else:
    print("用户名或邮箱错误。")

代码解释

  • 第一个 if 检查是否完全成功。
  • elif password != valid_password::如果密码不等于正确密码,则执行该块。!= 是“不等于”运算符。
  • 如果既不是成功,也不是密码错误,那么问题就出在用户名/邮箱上,执行最后的 else 块。

!= 运算符可以等价地使用 not 运算符和 == 来替换:
elif not (password == valid_password): 这行代码与 elif password != valid_password: 逻辑完全相同。not 运算符将其后面的布尔值取反。

运行程序,分别测试密码错误和用户名错误的情况,观察不同的提示信息。


总结与练习 🎯

本节课中,我们一起学习了:

  1. 逻辑运算符:使用 andor 来组合多个条件,构建复杂的判断逻辑。
  2. 短路求值:理解了Python为了提高效率,在可能的情况下会跳过不必要的条件评估。
  3. 实践应用:通过构建一个支持用户名/邮箱登录的系统,并逐步增加更具体的错误提示功能,综合运用了变量、输入、输出、条件语句和逻辑运算符。

你可以在此基础上继续发挥创意,例如:

  • 增加一个“管理员密码”,输入该密码可直接登录。
  • 限制用户尝试登录的次数。
  • 处理用户名和密码都错误的情况。

编程之旅充满乐趣,不断尝试和修改代码是最好的学习方式。享受这个过程吧!

032:if/elif/else结构的选择指南

在本节课中,我们将学习如何根据不同的编程需求,在ifif-elif-else和多个独立的if语句之间做出选择。理解何时使用何种结构,是编写清晰、高效条件逻辑的关键。

概述:决策的核心问题

你已经了解了if语句,知道它可以包含elifelse子句。你也知道可以通过编写一个或多个if语句来让程序实现相同的行为。但关键问题是:你何时该使用哪一种结构?

一种决定方法是选择对你而言最直观的方式。当你构思或描述希望程序如何运行时,你写下的条件句是怎样的?它们是否依赖于前一个if条件的真假,还是每个条件都可以独立判断?

然而,如果你对自己的直觉没有把握,该怎么办?这时,一个有效的思考方式是问自己:你希望发生多少件事? 这里特意使用“件事”这个词,因为我不知道你希望程序具体做什么。在本视频的语境中,“一件事”指的是一行或一个代码块,你只希望在特定条件下执行它。这正是这些代码被放在if语句中的原因。

选择逻辑:一件事 vs. 多件事

上一节我们提出了核心问题,本节中我们来看看具体的决策逻辑。

以下是基于你希望发生“一件事”还是“多件事”的决策指南:

  • 如果你只希望发生一件事:那么你很可能需要使用一个单一的if语句,并根据需要包含任意数量的elif子句,也可以包含一个else子句。

    • 原因在于,在一个if-elif-else结构中,一旦找到第一个为真的条件,就只会执行该条件对应的代码块,然后跳过该if语句的其余部分。
    • 如果没有条件为真,并且存在else子句,则会执行else子句的代码。
  • 如果你希望可能发生多件事:那么你很可能需要多个独立的if语句。每个if语句都可以有自己的elif和/或else子句。

示例分析:单件事场景

让我们通过具体例子来加深理解。首先看一个“只希望发生一件事”的场景:根据年龄计算票价

显然,每个人都需要买票,并且基于年龄的票价是固定的。为了确保这一点,你可以使用一个单一的if语句。

# 假设变量 age 已被赋值
if age > 65:
    print("Senior price: $10")
elif age > 18:
    print("Adult price: $15")
elif age > 5:
    print("Child price: $8")
else:
    print("Infant price: Free")

请注意,由于这是一个单一的if-elif-else语句,只会打印一条价格信息。因为一旦某个条件为真(例如age > 18为真),其余部分的代码就会被跳过。此外,由于存在else子句,有且仅有一件事会发生。如果所有ifelif条件都为假,则会执行关于婴儿票价的else子句。

示例分析:多件事场景

现在,让我们看一个“可能希望发生多件事”的例子:在厨房里创建购物清单

你的程序可以询问不同的问题,并根据答案向清单中添加物品。

groceries = []  # 从空购物清单开始

# 第一个独立的 if 语句
if input("Is there enough fruit? (y/n): ") == 'n':
    groceries.append("fruit")

# 第二个独立的 if 语句,无论第一个结果如何都会执行
if input("Is the last jug of milk open? (y/n): ") == 'y':
    groceries.append("milk")

# 其他独立的 if 语句
onions_count = int(input("How many onions are there? "))
if onions_count <= 2:
    groceries.append("onions")

ice_cream_cartons = int(input("How many unopened ice cream cartons? "))
if ice_cream_cartons == 0:
    groceries.append("ice cream")

print("Grocery list:", groceries)

请注意,在决定是否添加当前物品时,购物清单上是否已有其他物品并不相关。每个if语句都是独立判断的。这意味着:

  • 如果所有if条件都为真,所有物品都会被加入清单。
  • 如果所有if条件都为假,清单将为空。
  • 如果部分为真,清单的长度将等于评估为真的if语句的数量。

决策流程图

为了更直观地展示如何选择,可以参考以下流程图:

         [开始]
           |
           v
    [需要发生几件事?]
           |
    /------|------\
   /               \
  v                 v
[一件事]         [多件事]
  |                 |
  v                 v
(使用单一         (使用多个
 if-elif-else)     独立if语句)

菱形代表你需要做出的选择(回答“一件事”还是“多件事”)。矩形块代表你要执行的操作。这种流程图在你想要可视化自己的if语句逻辑时也很有用。

总结与回顾

本节课中我们一起学习了如何为条件逻辑选择正确的结构。总结如下:

如何选择使用何种if结构?
一个要问自己的核心问题是:你试图达到什么结果? 更具体地说,你希望发生多少件事?

  • 如果你只希望发生一件事(例如根据年龄确定客户票价),你很可能只需要一个if-elif-else语句
  • 如果你可能希望发生多件事(例如向购物清单中添加物品),你很可能需要多个独立的if语句

希望本视频对你有所帮助。如果你能想到其他在决定何时使用何种结构时可以问自己的问题,请在讨论区与大家分享。😊

033:生成随机选择 - random模块 🎲

在本节课中,我们将要学习如何在Python程序中引入随机性。我们将了解什么是模块,如何导入并使用它们,并重点学习random模块中的两个核心函数来生成随机数。通过模拟抛硬币等简单例子,你将学会如何让程序的行为变得不可预测,从而增加趣味性和挑战性。

理解随机性的需求

假设你正在玩一个电子游戏,控制一个名叫Terry的角色。Terry需要躲避一只猫,这只猫在向前跳跃前会随机向左或向右轻微转向。你的任务是同时向左或向右移动Terry,以期避开猫。

如果这只猫每次游戏都转向同一个方向,例如总是向右转,那么游戏将毫无乐趣。因为从第二次、第三次开始,你就已经知道猫的行动,只需将Terry移向右边即可轻松躲避。这样的游戏缺乏挑战性。

如果猫的移动变得不可预测,即每次游戏时都随机选择一个方向,游戏就会变得更有趣、更具挑战性。有时猫会扑空,有时则会抓住Terry。

一个更简单的随机性例子是抛硬币,并根据硬币朝上的一面来做决定。假设我们有一枚美国25美分硬币,一面印有美国第一任总统乔治·华盛顿的头像,另一面则印有(例如)新泽西州的埃利斯岛图案。你今天想决定是否去散步,于是你抛起硬币。如果落地时乔治·华盛顿头像朝上,你就去散步;如果埃利斯岛图案朝上,你就不去。这看起来像是头像朝上,所以你决定去散步。

在Python中模拟随机选择

Python本身没有“抛硬币”的功能,但它提供了在指定范围内生成随机数的方法。随机生成数字1或2,本质上就等同于抛硬币。

  • 如果随机生成数字1,则等同于硬币乔治·华盛顿头像朝上,我们去散步。
  • 如果随机生成数字2,则等同于硬币埃利斯岛图案朝上,我们不去散步。

Python通过random模块库来生成随机数。

认识Python模块

在现实世界中,图书馆通常被认为是借阅书籍的地方。你借阅别人写的书,阅读,然后归还。Python库则是一个“借用”代码的地方,这些代码被称为模块

一个模块就像一本书,但实际上它是一个包含大量相关代码的文件。模块内部通常包含你可以在自己程序中使用的函数。使用他人编写好的库模块中的函数,可以节省你编写程序的时间。

如何访问一个库模块呢?你需要使用import语句。使用关键字import,后面跟上模块的名称。import语句应放在程序的顶部。然后,在import语句下方的任何地方,你都可以使用该模块中的任何函数。

那么,如何访问模块中的函数呢?你必须先给出模块的名称,后面跟一个点.,然后是函数名、左括号、函数所需的任何值(我们称之为参数),最后是右括号。

以下是一个例子:

import random
my_number = random.randint(1, 2)
  • 第1行代码导入了random模块。这意味着我们现在可以使用random模块中的任何函数。
  • 第2行代码调用了random模块中的randint函数,以生成一个整数,其值为1或2。
  • 注意格式:首先是模块名random,接着是点.,然后是函数名randint,后面是参数12(表示生成从1到2的整数),最后是右括号。

在更详细地了解random模块之前,我们先来回顾一下Python中的内置函数。

回顾Python内置函数

我们已经使用过一些内置函数。内置函数已经内置于Python中,我们可以直接使用,无需导入任何模块。由于没有导入模块,所以在函数名前没有模块名。

使用函数的另一种说法是调用函数。两者都意味着该函数关联着一些额外的代码,当执行到包含该函数的代码行时,这些额外代码就会运行。

调用函数的格式是:函数名,后跟左括号、0个或多个参数,然后是右括号。就像你的代码有时需要用户输入才能执行一样,大多数函数也需要。你可以将参数视为函数的输入。你可以使用不同的参数来调用函数。

以下是一些熟悉的例子。在查看时,请思考函数名是什么,参数又是什么。

y = str(6934)
  • 函数str函数。该函数接受一个输入值(参数)并将其转换为字符串。
  • 参数是整数6934。在这种情况下,参数是一个常量。
  • str函数会将字符串'6934'赋值给变量y
t = type(y)
  • 函数type函数,用于判断其参数的类型。
  • 参数是变量y。在这种情况下,参数是一个变量。
  • t存储了变量y的类型。
print(t)
  • 函数print函数。
  • 参数是变量t
  • 在这种情况下,打印出的值是<class 'str'>,表示y的类型是字符串。
x = input("How many? ")
  • 函数input函数。
  • 参数是字符串"How many? "
  • 假设用户输入了3
x_int = int(x)
  • 函数int函数。int函数从其参数创建一个整数。
  • 参数是变量x
  • int函数获取x的值(即字符串'3'),将其转换为整数3,并存储在变量x_int中。
print("x times 2 is", x_int * 2)
  • 函数print函数。
  • 参数:在这种情况下有两个参数。print函数允许你打印多个项目,它可以有零个或多个参数。如果参数为零,则打印一个空行。
    • 第一个参数是字符串"x times 2 is"
    • 第二个参数是一个表达式x_int * 2
  • 当参数是表达式时,Python会先计算表达式的值,然后将该值作为参数发送给函数。
  • 在这种情况下,第一个参数是字符串"x times 2 is",第二个参数计算结果为6。然后函数将打印出:x times 2 is 6

我们见过的大多数函数都有一个参数,但print函数可以有零个或多个参数,每个参数都会被打印出来。

观察这些例子,请注意参数可以是常量、变量或表达式。现在,让我们更详细地探讨random模块。

深入探索random模块

模块的名称是random,要使用它,你需要先导入。我们将学习random模块中的两个函数,它们能帮助我们生成随机数。

1. randint 函数

第一个函数名为randint。它有两个参数,名为num1num2。这个函数生成一个介于num1num2之间(包括num1num2)的随机整数。请注意,num1num2都必须是整数。

以下是一个例子:

import random
random_number = random.randint(1, 3)
  • 注意调用函数时,也必须给出它所在的模块名。
  • 在这个例子中,函数调用是random.randint(1, 3)
  • 这个调用会计算为数字123中的一个。
  • 每次运行这段代码,它都会评估这个函数,生成这三个数字中的一个并返回。有时是1,有时是2,有时是3

2. random 函数

这是random模块中的另一个函数,名为random。它生成一个介于0.01.0之间的随机浮点数,包括0.0,但不包括1.0。每次我们的代码调用这个函数,都会生成一个不同的随机数。

请注意,random函数没有参数。这是因为它的设计就是只执行这一个任务,不需要任何输入来改变其行为。

以下是一个例子:

import random
float_number = random.random()
  • 这看起来有点奇怪,因为在这个例子中,模块名和函数名是相同的。
  • 这里,代码调用函数random.random()。它可能计算为0.34,也可能计算为0.987或其他数字。
  • 我们不知道它会计算成什么数字,只知道这个数字将在01之间。
  • 注意这个函数没有参数,它不需要任何输入,只是生成一个0到1之间的数。

总结

本节课中我们一起学习了如何为Python程序引入随机性。我们首先理解了为什么程序需要随机性来增加趣味性,例如在游戏中。然后,我们认识了Python的模块系统,学会了使用import语句来导入random模块。我们回顾了调用函数的基本格式,并重点掌握了random模块中的两个核心函数:用于生成指定范围内随机整数的randint(a, b),以及用于生成0到1之间随机浮点数的random()。通过结合这些知识,你现在已经能够编写程序来模拟像抛硬币这样的随机决策过程了。

034:生成随机选择示例-步行与选择衬衫 👕

在本节课中,我们将学习如何利用Python的random模块生成随机数,并通过两个生动的例子——决定是否散步和选择衬衫——来演示如何将随机数应用于程序决策中。我们将看到如何通过调整概率来影响随机选择的结果。


一个简单的随机选择示例:是否散步 🚶

首先,我们来看一个决定是否去散步的简单程序。这个程序会随机生成数字1或2,并根据结果做出决定。

以下是程序的核心步骤:

  1. 导入random模块,以便使用其中的函数。
  2. 调用random.randint(1, 2)函数,生成一个1到2之间的随机整数。
  3. 将生成的随机数赋值给变量walk
  4. 使用if语句判断walk的值:如果等于1,则去散步;否则(即等于2),不去散步。

让我们逐行分析代码:

import random
walk = random.randint(1, 2)
if walk == 1:
    print("Go for a walk.")
else:
    print("No walk today.")

假设程序生成了数字2。那么变量walk的值就是2。代码检查walk是否等于1,结果为False。因此,程序执行else分支,打印“No walk today.”。执行完if语句后,程序继续执行后续代码。

这个程序的核心是,在运行之前,你无法预知结果是“散步”还是“不散步”,这体现了程序的随机性。


扩展示例:随机选择衬衫 🎨

上一节我们介绍了如何用随机数做二选一的决定。本节中我们来看看一个更复杂的选择:从三件衬衫中随机挑选一件。

假设你有三件衬衫:红色、黄色和蓝色。你想写一个程序来随机决定今天穿哪一件。

思路是生成一个1到3之间的随机整数,然后将每个数字与一种颜色的衬衫关联起来。例如,1对应红色,2对应黄色,3对应蓝色。

以下是实现此功能的代码:

import random
shirt = "?"  # 初始化衬衫变量
choice = random.randint(1, 3)  # 生成1到3的随机整数

if choice == 1:
    shirt = "red"
elif choice == 2:
    shirt = "yellow"
else:
    shirt = "blue"

print(f"Wear {shirt} shirt.")
print(f"(Generated number: {choice})")  # 打印生成的数字以验证

代码执行流程如下:首先生成一个随机整数(例如3)并赋值给choice。然后,if-elif-else语句根据choice的值将对应的颜色字符串赋值给变量shirt。最后,程序打印出要穿的衬衫颜色。

为了验证程序是否正确工作,我们在最后打印出了生成的随机数choice。通过多次运行程序,我们可以确保每种随机情况都按预期工作。


调整选择概率 ⚖️

现在,让我们稍微改变一下问题。假设红色是你最喜欢的衬衫,你希望它被选中的概率是其他衬衫的两倍。

实现方法之一是生成1到4的随机数,并将其中两个数字(例如1和2)分配给红色,黄色和蓝色各分配一个数字(例如3和4)。

以下是修改后的代码:

import random
shirt = "?"
choice = random.randint(1, 4)  # 现在生成1到4的随机数

if choice == 1 or choice == 2:  # 1和2都对应红色
    shirt = "red"
elif choice == 3:
    shirt = "yellow"
else:  # choice == 4
    shirt = "blue"

print(f"Wear {shirt} shirt.")

通过这种方式,红色被选中的概率(2/4 = 50%)确实是黄色或蓝色(各1/4 = 25%)的两倍。


使用随机浮点数实现相同概率 🌊

除了使用整数,我们还可以通过生成0.0到1.0之间的随机浮点数来实现相同的概率分布。我们将0到1的数轴划分为三段:

  • 红色:对应 0.0 ≤ 随机数 < 0.5 (占一半)
  • 黄色:对应 0.5 ≤ 随机数 < 0.75 (占四分之一)
  • 蓝色:对应 0.75 ≤ 随机数 < 1.0 (占四分之一)

这样,红色被选中的概率仍然是其他颜色的两倍。

以下是使用随机浮点数的代码:

import random
shirt = "?"
choice = random.random()  # 生成0.0到1.0的随机浮点数

if choice < 0.5:
    shirt = "red"
elif choice < 0.75:  # 此时 choice 肯定 >= 0.5
    shirt = "yellow"
else:  # 此时 choice 肯定 >= 0.75
    shirt = "blue"

print(f"Wear {shirt} shirt.")

代码的主要变化是:第2行调用random.random()函数生成浮点数;第4行和第6行的条件判断改为与浮点数(0.5和0.75)进行比较。else分支的含义也相应变为处理0.75到1.0之间的数字,对应蓝色衬衫。


本节总结 📚

本节课中我们一起学习了以下核心概念:

  1. 函数与参数:函数可以接收零个或多个参数(即传递给函数的值)。参数可以是常量、变量或表达式。函数利用这些值进行计算或处理。
  2. 导入模块:我们可以像从图书馆借书一样,通过import语句“借用”其他模块中的代码。要使用模块中的函数,必须在函数名前加上模块名和点号,例如 random.randint()
  3. 随机数生成:我们学习了random模块中的两个重要函数:
    • random.randint(a, b):生成一个在 ab 之间(包括a和b)的随机整数
    • random.random():生成一个在 0.01.0 之间(包括0.0,但不包括1.0)的随机浮点数。这个区间内包含非常多的可能值。
  4. 最佳实践:生成随机数时,强烈建议始终将随机值捕获到一个变量中。这样,你既可以用这个变量进行比较判断,也可以打印出它的值来验证和调试你的程序。

掌握在程序中生成随机性的能力,将使你的程序变得更加有趣和强大。

035:生成随机选择示例 - 未来年龄演示 🎲

在本节课中,我们将学习如何使用Python生成随机数,并编写一个程序来预测用户在某个随机年份的年龄。我们将使用Python Tutor来可视化变量变化,并对比其在VS Code中的不同行为。

概述

我们将创建一个程序,首先询问用户的出生年份,然后生成一个介于1900年至2100年之间的随机年份。程序将计算并告知用户在该随机年份的年龄。如果该年份早于用户的出生年份,程序则会提示用户尚未出生。

代码实现步骤

以下是实现该程序的具体步骤。

1. 获取用户输入

首先,我们需要获取用户的出生年份。使用 input() 函数获取输入,由于输入默认为字符串格式,我们需要使用 int() 函数将其转换为整数。

year_born = int(input("What year were you born? "))

2. 导入随机模块并生成随机年份

为了生成随机数,我们需要导入Python的 random 模块。然后,使用 random.randint() 函数在指定范围内生成一个随机整数。

import random
year = random.randint(1900, 2100)

3. 计算并输出年龄

接下来,我们需要判断随机生成的年份是否晚于或等于用户的出生年份。如果是,则计算年龄并输出;否则,提示用户在该年份尚未出生。

if year >= year_born:
    age = year - year_born
    print(f"In year {year}, you will be about {age}.")
else:
    print(f"In year {year}, you were not born yet.")

在Python Tutor中运行程序

将上述代码整合后,在Python Tutor中运行。首次运行时,可能会遇到“NameError: name 'random' is not defined”的错误。这是因为 import 语句必须放在文件顶部。修正后再次运行。

在Python Tutor中,对于相同的随机数范围,程序每次运行都会生成相同的“第一个”随机数。这是Python Tutor为了便于调试而设计的特性。例如,范围设为(1900, 2100)时,首次调用 random.randint(1900, 2100) 可能总是返回1998。

在VS Code中运行程序

将代码复制到VS Code中运行,每次执行都会得到不同的随机年份。这是因为在标准的Python环境中,随机数种子是基于系统时间的,因此每次运行结果都不同。

为了在VS Code中查看结果,我们需要添加 print 语句来输出变量的值。

改进程序:生成有意义的未来年份

我们可以改进程序,使生成的随机年份更有意义。例如,只生成用户出生后120年内的年份,这样可以确保用户已经出生,并且年龄预测在合理范围内。

以下是改进后的代码逻辑:

import random

year_born = int(input("What year were you born? "))
# 生成从出生年份到出生后120年之间的一个随机年份
year = random.randint(year_born, year_born + 120)

age = year - year_born
print(f"In year {year}, you will be about {age}.")

由于现在生成的年份肯定晚于或等于出生年份,因此我们不再需要 if-else 判断语句。

总结

本节课中我们一起学习了如何生成和使用随机数。核心要点包括:

  • 使用 import random 导入随机模块。
  • 使用 random.randint(a, b) 函数在闭区间 [a, b] 内生成随机整数。
  • 在Python Tutor中,对于固定范围,随机数序列是固定的,这有助于调试。
  • 在VS Code或其他标准Python环境中,每次运行都会产生不同的随机数序列。
  • 我们可以根据逻辑需求(如确保用户已出生)来调整随机数的生成范围,使程序更加健壮和实用。

036:生成随机选择示例-选择水果演示 🍎

在本节课中,我们将学习如何使用Python的random模块来编写一个程序,根据设定的概率随机选择晚餐吃哪种水果。我们将探讨多种实现方法,包括使用浮点数和整数来模拟概率。


概述

我们将创建一个程序,根据以下概率随机选择一种水果:

  • 桃子:60%
  • 蓝莓:10%
  • 黑莓:20%
  • 无花果:10%

我们将学习如何生成随机数,并使用if-elif-else语句根据随机数的值来决定最终的选择。


方法一:使用0到1之间的随机浮点数

首先,我们导入random模块,并生成一个0.0到1.0之间的随机浮点数。然后,我们将整个概率区间映射到这个0到1的范围内。

以下是每种水果对应的数字区间:

  • 桃子:0.0 到 0.6
  • 蓝莓:0.6 到 0.7
  • 黑莓:0.7 到 0.9
  • 无花果:0.9 到 1.0

现在,让我们开始编写代码。

import random

# 生成一个0.0到1.0之间的随机浮点数
chance = random.random()
print(f"生成的随机数是: {chance}")

# 根据随机数所在区间选择水果
if chance < 0.6:
    print("今晚吃:桃子")
elif chance < 0.7:
    print("今晚吃:蓝莓")
elif chance < 0.9:
    print("今晚吃:黑莓")
else:
    print("今晚吃:无花果")

代码解释

  1. random.random() 生成一个 [0.0, 1.0) 区间的随机小数。
  2. 第一个if语句检查数字是否小于0.6(即前60%的概率)。
  3. 如果第一个条件不满足,程序会继续检查elif条件。注意,当检查 chance < 0.7 时,我们实际上是在检查数字是否在 [0.6, 0.7) 区间,因为小于0.6的情况已经被排除了。
  4. 依此类推,最后一个else语句处理 [0.9, 1.0) 区间的情况。

在Python Tutor中测试代码

Python Tutor是一个在线代码可视化工具,非常适合初学者理解代码执行流程。但需要注意的是,为了便于调试,Python Tutor在每次运行时为特定范围生成的随机数是固定的。

例如,如果你在Python Tutor中运行上述代码,并且随机数落在0.7到0.9之间,你可能会每次都得到同一个数字(比如0.84),并因此每次都选择黑莓。这是正常现象,并非代码错误。

为了在Python Tutor中测试所有分支,我们可以临时将随机数chance设置为一个固定值进行测试。

import random

chance = random.random()
# 临时注释:用于测试特定分支
# chance = 0.53  # 测试桃子
# chance = 0.65  # 测试蓝莓
# chance = 0.84  # 测试黑莓
# chance = 0.96  # 测试无花果

print(f"生成的随机数是: {chance}")

if chance < 0.6:
    print("今晚吃:桃子")
elif chance < 0.7:
    print("今晚吃:蓝莓")
elif chance < 0.9:
    print("今晚吃:黑莓")
else:
    print("今晚吃:无花果")

在最终的程序中,记得移除或注释掉这些用于测试的固定赋值语句。


方法二:改变判断顺序

上一节我们按照“桃子->蓝莓->黑莓->无花果”的顺序进行判断。实际上,判断的顺序可以任意调整,只要相应地修改每个条件判断的阈值即可。

例如,我们可以按照“无花果->蓝莓->黑莓->桃子”的顺序来判断。这时,我们需要重新分配数字区间:

  • 无花果:0.0 到 0.1
  • 蓝莓:0.1 到 0.2
  • 黑莓:0.2 到 0.4
  • 桃子:0.4 到 1.0

对应的代码如下:

import random

chance = random.random()
print(f"生成的随机数是: {chance}")

if chance < 0.1:
    print("今晚吃:无花果")
elif chance < 0.2:
    print("今晚吃:蓝莓")
elif chance < 0.4:
    print("今晚吃:黑莓")
else: # chance >= 0.4
    print("今晚吃:桃子")

这个版本的程序逻辑与第一个版本完全等价,只是判断顺序不同。这说明了只要概率区间划分正确,代码的逻辑顺序可以灵活安排。


方法三:使用1到10的随机整数

除了使用小数,我们还可以通过生成随机整数来模拟概率。例如,生成一个1到10之间的整数,然后将每个数字映射到一种水果。

以下是映射关系:

  • 数字 1 -> 无花果 (10%)
  • 数字 2 -> 蓝莓 (10%)
  • 数字 3 或 4 -> 黑莓 (20%)
  • 数字 5 到 10 -> 桃子 (60%)

以下是实现代码:

import random

# 生成一个1到10之间(包含1和10)的随机整数
chance = random.randint(1, 10)
print(f"生成的随机数是: {chance}")

if chance == 1:
    print("今晚吃:无花果")
elif chance == 2:
    print("今晚吃:蓝莓")
elif chance == 3 or chance == 4:
    print("今晚吃:黑莓")
else: # chance 在 5 到 10 之间
    print("今晚吃:桃子")

代码解释

  1. random.randint(1, 10) 生成一个 [1, 10] 区间的随机整数。
  2. 使用 == 运算符来检查整数是否等于特定值。
  3. 对于黑莓,我们使用 or 逻辑运算符来组合两个条件(chance == 3chance == 4)。

这种方法对于理解离散概率分布非常直观。


总结

本节课中我们一起学习了如何利用Python的random模块来编写随机选择程序。

我们掌握了以下核心要点:

  • 使用 import random 导入随机模块。
  • 使用 random.random() 生成0到1之间的随机浮点数。
  • 使用 random.randint(a, b) 生成a到b之间的随机整数。
  • 将生成的随机数保存到变量中,以便打印和用于条件判断。
  • 使用 if-elif-else 语句根据随机数的值执行不同的代码分支。
  • 理解并正确划分概率区间是程序逻辑正确的关键。
  • 在Python Tutor等调试环境中,随机数生成可能是确定性的,这是为了便于代码跟踪。

通过“选择晚餐水果”这个生动的例子,我们实践了随机数生成和条件判断的结合使用,这为创建更复杂、更有趣的程序打下了坚实基础。

037:随机数是如何生成的 🔢

在本节课中,我们将要学习计算机如何生成随机数。我们将探讨“伪随机数”的概念,了解其背后的生成算法,并学习如何在Python中通过设置“种子”来控制随机数的生成序列。


概述

计算机无法生成真正的随机数。Python生成的是“伪随机数”。它们看起来是随机的,但实际上是通过一系列确定的步骤(算法)计算出来的。理解这一点是掌握程序中使用随机数的关键。


什么是算法?

上一节我们提到了生成随机数需要遵循一系列步骤。这一系列解决问题的步骤被称为算法

算法就像一个食谱。你必须严格按照步骤执行,否则算法可能无法正确工作。例如,制作蓝莓派需要知道所有配料及其精确用量。步骤的顺序也很重要,如果顺序错误,面粉可能会结块。

有趣的事实:“算法”一词来源于9世纪的波斯数学家穆罕默德·伊本·穆萨·花拉子密。他的拉丁化名字是“Algoritmi”,这便是“algorithm”的由来。


伪随机数生成算法

现在,让我们回到程序运行时如何生成随机数的问题。我们来看一个可能的算法,用于生成一系列伪随机数。

我们的目标是生成一个位于特定范围内的随机数,例如从0到10之间选取一个数。该算法需要一个起始数字,称为种子,并通过运算确保结果保持在指定范围内。

以下是算法的核心步骤:

  1. 选择任意数字作为起始数字或种子。
  2. 决定另外三个数字变量,我们称之为 ABC
  3. 对这些数字进行算术运算(如乘法、加法和取模),生成第一个随机数。

一个常见的算术表达式是:
新数字 = (A * 起始数字 + B) % C

注意:第一次运行算法时,起始数字就是种子。为了生成第二个随机数,我们将第一个生成的随机数作为新的起始数字,并重复相同的算法和变量。以此类推,可以生成任意长度的随机数序列。


算法示例

让我们看一个具体的数字例子。我们将生成一个范围在1到25之间的随机数序列。

首先,为变量赋值:

  • A = 13
  • B = 7
  • C = 25(因为取模运算 % C 的结果范围是0到C-1,即0到24)
  • 选择种子:种子 = 93

使用的算法表达式为:
随机数 = ((A * 起始数字) + B) % C + 1
(加1是为了将范围从0-24偏移到1-25)

计算第一个随机数:

  1. 13 * 93 = 1209
  2. 1209 + 7 = 1216
  3. 1216 % 25 = 16
  4. 16 + 1 = 17
    因此,第一个生成的随机数是 17

计算第二个随机数(使用17作为新的起始数字):

  1. 13 * 17 = 221
  2. 221 + 7 = 228
  3. 228 % 25 = 3
  4. 3 + 1 = 4
    第二个随机数是 4

继续此过程,第三个随机数是10,第四个是13。这就是一种可能的伪随机数生成算法。Python内部使用的算法可能更复杂,但基本原理与此类似。


在Python中设置随机种子

在Python中,你可以设置一个随机种子,使得每次程序运行时生成的随机数序列完全相同。这在程序开发调试阶段非常有用。

要设置随机种子,你需要使用 random 模块。random 模块有一个名为 seed() 的函数,它接受一个数字作为参数。

重要random.seed() 函数与其他函数不同,它不返回任何值。因此,你不能将其结果赋值给一个变量,只需直接调用它。

如果程序不调用 random.seed(),Python会使用类似当前日期和时间的信息自动生成一个种子,这样每次运行程序时种子都不同,从而产生不同的随机数序列。


代码示例

以下是一个决定是否去散步的示例代码,它使用了固定的随机种子。

import random
random.seed(6734)  # 设置随机种子

walk = random.randint(1, 2)  # 生成1或2

if walk == 1:
    print("去散步")
else:
    print("今天不去散步")

代码解释:

  • 第1行:导入 random 模块。
  • 第2行:将随机种子设置为 6734。注意这里没有赋值操作。
  • 第3行:生成一个1或2的随机整数,并赋值给变量 walk
  • 第4-7行:根据 walk 的值打印相应信息。

因为种子固定为 6734random.randint(1, 2)总是生成相同的数字(例如总是1),所以这段代码每次运行都会输出相同的结果(“去散步”)。这有助于在调试时获得可预测的行为。

开发完成后,你可以移除或注释掉 random.seed(6734) 这行代码,这样程序每次运行就会产生不同的随机结果。

请注意:像 Python Tutor 这样的在线学习工具可能会自动设置种子以确保代码演示的一致性。但在 VS Code 等本地环境中运行代码时,如果不设置种子,每次运行都会得到不同的随机数。


相关人物:达娜·莫什科维茨 👩‍🔬

在随机数研究领域,达娜·莫什科维茨 是一位杰出的计算机科学家和数学家。她来自以色列,现任美国德克萨斯大学教授。

她的研究方向正是伪随机数生成,致力于创建算法使生成的数字更加“随机”。她的算法远比我们前面看到的简单示例要复杂和高效。她曾获得多项奖项,包括200年以色列最佳理论计算机科学博士论文奖。


总结

本节课中我们一起学习了:

  1. 伪随机数:计算机通过确定性算法生成的、看似随机的数字序列。
  2. 算法:解决问题的一系列精确步骤,是生成随机数的核心。
  3. 种子:随机数生成算法的起始值。相同的种子会产生完全相同的随机数序列。
  4. random.seed() 函数:在Python中用于设置随机种子。它不返回值,仅用于初始化随机状态。
  5. 种子的用途:在程序调试阶段,使用固定种子可以确保随机行为可重现,便于排查问题。

以下是设置和使用种子的标准代码模式总结:

import random          # 1. 导入模块
random.seed(你的种子)   # 2. 设置种子(可选,调试时使用)

x = random.randint(1, 100)  # 3. 生成随机数
y = random.random()         # 生成另一个随机数

现在,你已经掌握了如何生成随机数,以及如何在需要时控制它们的生成序列。祝你编程愉快!

038:使用随机种子的随机数演示 🎲

在本节课中,我们将学习如何使用Python的random模块生成随机数,并重点探讨如何通过设置随机种子来控制随机数的生成序列。理解随机种子对于调试代码和确保程序行为的可重复性至关重要。

概述

随机数在编程中应用广泛,例如游戏、模拟或随机选择。Python的random模块提供了生成随机数的工具。然而,有时我们需要程序每次运行时产生相同的“随机”序列,以便于调试和测试。这正是随机种子发挥作用的地方。本节将通过一个选择随机颜色的示例,演示设置随机种子如何影响程序输出。

生成随机颜色

首先,我们编写一个基础程序,从四种颜色(蓝、红、绿、黄)中随机选择一种。每种颜色被选中的概率相等。

以下是实现步骤:

  1. 导入random模块。
  2. 生成一个1到4之间的随机整数。
  3. 根据生成的数字,关联并输出对应的颜色。
import random

num = random.randint(1, 4)
print(f"Num is {num}")

if num == 1:
    print("Choose blue.")
elif num == 2:
    print("Choose red.")
elif num == 3:
    print("Choose green.")
else:
    print("Choose yellow.")

运行此程序多次,每次都会得到不同的数字和颜色,因为random.randint在每次调用时都会生成一个新的随机数。

引入随机种子

现在,我们修改程序,在生成随机数之前设置一个随机种子。我们使用random.seed()函数来实现。

import random
random.seed(872)  # 设置随机种子

num = random.randint(1, 4)
print(f"Num is {num}")

if num == 1:
    print("Choose blue.")
elif num == 2:
    print("Choose red.")
elif num == 3:
    print("Choose green.")
else:
    print("Choose yellow.")

设置种子后,无论运行程序多少次,只要种子值(本例中为872)不变,程序输出的随机数序列就完全相同。这对于调试非常有用,因为你可以确保程序在相同输入下产生可预测的输出。

生成多个随机数

为了更清楚地观察序列,我们扩展程序,使其生成并输出三个随机数及对应的颜色。

import random
random.seed(872)  # 设置随机种子

# 第一组
num = random.randint(1, 4)
print(f"Num is {num}")
if num == 1:
    print("Choose blue.")
elif num == 2:
    print("Choose red.")
elif num == 3:
    print("Choose green.")
else:
    print("Choose yellow.")

# 第二组
num = random.randint(1, 4)
print(f"Num is {num}")
if num == 1:
    print("Choose blue.")
elif num == 2:
    print("Choose red.")
elif num == 3:
    print("Choose green.")
else:
    print("Choose yellow.")

# 第三组
num = random.randint(1, 4)
print(f"Num is {num}")
if num == 1:
    print("Choose blue.")
elif num == 2:
    print("Choose red.")
elif num == 3:
    print("Choose green.")
else:
    print("Choose yellow.")

运行此程序,你会看到每次运行都输出完全相同的三个数字和颜色序列(例如 4, 2, 4)。改变种子值(例如改为2023.456),则会开始一个全新的、但自身保持不变的随机序列。

Python Tutor的特殊情况

在线代码可视化工具Python Tutor为了教学目的,会在后台自动为所有使用random模块的代码设置一个固定的随机种子。这意味着,即使你的代码中没有显式调用random.seed(),在Python Tutor中多次运行同一段代码也会得到相同的随机数序列。

不过,你仍然可以在Python Tutor中显式设置自己的种子来覆盖这个默认行为,从而获得不同的、但可重复的序列。

总结

本节课中我们一起学习了:

  1. 使用random.randint(a, b)函数生成指定范围内的随机整数。
  2. 使用random.seed()函数设置随机种子,其作用是初始化伪随机数生成器的起点。
  3. 设置相同的种子可以保证程序每次运行时生成完全相同的随机数序列,这对调试和测试至关重要。
  4. 在线工具Python Tutor会自动设置种子以确保演示结果的一致性,但用户仍可自行设置种子来改变序列。

掌握随机种子的使用,能让你在需要可预测性的场景下更好地控制程序的随机行为。

039:重新执行代码 - while循环与添加收据

在本节课中,我们将学习如何使用 while 循环来重复执行代码块,直到满足特定条件为止。我们将通过一个模拟添加收据金额的示例来理解其工作原理。


想象自己是芭芭拉·利斯科夫

她是20世纪60年代首批获得计算机科学博士学位的女性之一。她的工作领域是编程语言。在60年代,她创造了我们今天仍在使用的编程语言的某些特性。C++、Java和Python等编程语言都采用了她的成果。凭借这些及其他成就,她获得了计算机科学最高荣誉——ACM图灵奖。

让我们回到60年代,当时她是斯坦福大学的计算机科学博士生。那时,她编写了计算机程序来下国际象棋。她如何做到这一点?

她的代码需要做出决策。例如,假设她的程序正在与真人玩家对弈。如果玩家将棋子移动到棋盘上的某个位置,那么她的程序现在应该计算出计算机程序下一步的最佳走法,然后将一个特定的棋子移动到一个特定的位置。通过观察棋盘上所有棋子的位置以及它们所有可能的走法,需要做出大量决策。

我们知道如何使用 if 语句来做决策。事实上,在下棋过程中有如此多的决策,以至于她的博士论文专注于棋局。她没有编写一个完整的下棋程序,而是编写了一个程序来寻找棋盘上只有少数棋子时的解决方案,换句话说,接近游戏尾声时的情况。即使只有少数棋子,仍然可能需要做出很多决策。

她的程序还需要做什么?玩家轮流移动棋子,因此她的代码需要为每一轮重复或重新执行。每当对手走一步棋,她的程序就需要决定走哪一步棋。这会重复多少次?这取决于棋局的进展。可能只有几轮,也可能有很多轮。无论如何,我们需要学习如何编写能够一遍又一遍重新执行代码块的Python代码,直到我们需要停止重复,比如当国际象棋游戏结束时。


Python使用while循环来重新执行代码

以下是 while 循环的格式:

while condition:
    # 代码块

它是关键字 while,后跟一个评估为 TrueFalse 的条件,然后是冒号。下一行是一个缩进的代码块。代码块至少有一行,但可以是多行代码,所有行都缩进以表示它们是 while 循环的主体部分。

这个格式看起来熟悉吗?它应该看起来像一个简单的 if 语句。唯一的区别是每个结构的第一关键字:whileif。它们看起来如此相似,只差一个词,但这两个结构的意义却大不相同。

while 语句代表一个循环,一旦到达内部代码的末尾,执行就会返回到循环的开头。while 循环代码块中的代码可以重新执行多次。让我们将其与 if 语句的含义进行比较。if 语句只发生一次,仅此一次,它不是循环。

以下是 while 循环的工作原理:我们从图的顶部开始向下,直到需要评估 while 条件。如果条件评估为 True,则执行 while 主体中的代码块一次。然后我们跟随箭头回到菱形处,再次评估条件。如果条件再次评估为 True,则再次执行 while 主体中的代码块。然后返回并再次检查条件。只要条件持续评估为 True,这个循环就会一遍又一遍地重复,每次执行代码块,然后评估条件。在某个时刻,再次检查条件,这次条件评估为 False。这意味着你完成了 while 循环的执行。执行继续执行 while 循环之后的任何代码。


让我们看一个例子

假设你有一堆外出就餐的收据,你想写一个程序来输入所有收据金额,并找出你在外出就餐上花了多少钱。如果你外出就餐不多,你可以找出一个月的外出就餐花费。如果你经常外出就餐,这个总数可能只是一周的。让我们来规划这个程序。

你的程序重复读取收据金额并将其添加到总计中,直到输入所有收据。然后你的程序告诉你花了多少钱。我们如何做到这一点?首先,用户输入一个值。只要该值是收据,我们就执行以下操作:将收据添加到目前的总计中。等等,我们需要一个变量来保存总计值。让我们回到程序的顶部。

我们需要创建一个变量。我们称这个变量为 total,并在循环开始前将 total 初始化为 0。现在,回到循环。在我们将收据添加到总计后,我们应该尝试读取下一个值。我们一遍又一遍地重新执行这三行:将收据添加到总计,获取下一个值,确保它是一个有效的收据,重复,重复,重复。最后,一旦我们输入了所有收据,我们就可以打印所有收据的总和。

请注意,在我的计划中,我如何缩进内容以帮助我记住什么属于循环,什么不属于。

现在,你可能在想一些事情。while 循环何时停止?也就是说,我们如何知道何时输入了最后一张收据?我们仍然需要输入一些东西。我们的程序期望一个数字。那么什么不是有效的收据呢?如何用一个负数来表示我们完成了收据输入?我们不太可能得到一张负数的收据。当没有更多收据时,我们输入 -1。然后我们的 while 循环可以设置为只处理非负数,并在我们输入 -1 时停止。


让我们看一个带有while循环的代码示例

该代码将累加一堆收据或数值,直到输入 -1

total = 0.0
print("Enter numbers. Type -1 to stop.")
num = float(input("Enter a number: "))

while num >= 0.0:
    total += num
    num = float(input("Enter a number: "))

print("Total spent:", total)

在第1行,代码创建变量 total 并将其设置为 0.0。在第2行,代码要求用户输入数字,并告知他们需要输入 -1 来停止处理数值。在第3行,代码要求用户输入一个数字。假设用户输入 10.25。变量 num 被创建,10.25 被赋值给 num。在第4行,评估 while 循环以查看数字是否有效,即数字是否大于或等于 0.010.25 大于 0,因此条件评估为 True。第5行和第6行是循环的主体。接下来执行第5行,num 的值被添加到 total。所以 total 现在是 10.25。接下来,在第6行,用户输入另一个数字。假设他们输入 20。然后变量 num 更新为 20。接下来会发生什么?

代码刚刚到达 while 循环主体中的最后一行。这意味着我们返回,下一行要执行的是第4行。代码需要检查 while 的条件,看看 while 循环是否应该继续执行。num 的值 20 大于 0.0。这个条件评估为 Truewhile 循环将再次执行。在第5行,num 的值 20 被添加到 totaltotal 现在是 30.25。接下来是第6行。假设用户输入 5。注意变量 num 更新为 5。接下来会发生什么?

代码刚刚到达 while 循环主体中的最后一行。下一行要执行的是第4行。代码需要检查 while 的条件,看看 while 循环是否应该继续执行。num 的值 5 大于 0.0。这个条件评估为 Truewhile 循环将再次执行。在第5行,num 的值 5 被添加到 totaltotal 现在是 35.25。接下来是第6行,假设用户输入 -1。变量 num 更新为 -1。再次,代码位于 while 循环的底部,因此下一行要执行的代码是第4行。评估 while 条件,这次:-1 大于或等于 0.0 吗?是 Falsewhile 循环现在结束。第7行是 while 循环之后的第一行代码,因此接下来执行第7行,并打印 total 的值。


回顾这段带有while循环的代码中的一些想法

首先,必须有一种方法来停止 while 循环。一种方法是输入一个特殊的数字或数字范围来强制循环停止。在我们的例子中,我们使用了 -1,一个负数。在条件中,只要数字不是负数,while 循环就继续。所以实际上,任何负数都可以。但我们只指定了 -1 以使事情更简单。也就是说,只要条件评估为 True,循环就继续。

另一个想法是,我们需要在循环之前获取一个初始数字,以便 while 循环可以将其进行比较,看它是否有效。然后在 while 循环的底部,我们需要请求下一个数字。


以下是我们可以构建此代码的另一种方式

我们强制 while 循环第一次为 True,因此执行开始执行循环。我们以这样的方式来做,直到我们读入一个数字后,总计才受到影响。

以下是我们如何做到这一点:

total = 0.0
print("Enter numbers. Type -1 to stop.")
num = 0.0

while num >= 0.0:
    total += num
    num = float(input("Enter a number: "))

print("Total spent:", total)

在第3行,我们可以将 num 初始化为 0。这看起来是唯一的区别,但还有另一个区别。我们只请求一次输入,在第6行,而不是两次。这段代码如何工作?

第一次执行第5行时,num 被添加到 total,但 num0,所以这不会改变 total。然后在第6行,输入第一个数字,接着在第4行检查它,如果有效,则在第5行将其添加到 total。以这种方式构建代码,我们得到相同的结果,并且我们只需要在 while 循环的主体中请求一次输入。这段代码仍然将所有的收据累加到变量 total 中。


总结你学到的关于while循环的知识

首先,以下是Python中 while 循环的语法或格式。格式是关键字 while,后跟一个条件,然后在下一行跟一个缩进的代码块。

当你需要重新执行一段代码时,使用 while 循环。while 循环会一遍又一遍地重复其代码体,直到 while 的条件评估为 False

确保你理解 while 循环和 if 语句之间的区别。它们都有一个要评估的条件,但 while 循环代码可以重新执行,而 if 代码只能发生一次。

最后要记住的是,你可以强制 while 循环第一次为 True,以便在代码中只请求一次输入,从而简化你的代码。

希望你在使用 while 循环编写代码时获得乐趣。

040:重新执行代码示例 - 查找最大数 🔍

在本节课中,我们将学习如何使用 while 循环来重复执行代码,并通过一个“查找最大数”的示例来理解其工作原理。我们将重点分析如何跟踪变量状态,并确保循环能够正确终止。


概述

假设我们有一系列数字,目标是找出并打印其中的最大值。我们假设所有考虑的数字都是正数。程序允许用户输入多个数字,当输入一个负数时停止,然后计算并输出已输入数字中的最大值。

例如,用户依次输入 6.39.42.8,最后输入 -1。程序运行后应输出 9.4,即所有值中的最大数。

那么,程序如何计算这个最大数呢?它需要在收集输入的过程中,持续跟踪迄今为止遇到的最大值。为此,程序需要一个变量来存储这个“当前最大值”。


构建程序计划

首先,我们需要一个变量来跟踪最大值,我们将其命名为 largest,并初始化为 0

我们还需要一个变量来存储每次输入的数字,将其命名为 num,并初始化为 0

现在,只要输入的数字有效(即非负数),我们就可以重复以下步骤:

  1. numlargest 进行比较。
  2. 如果 num 大于 largest,则将 largest 的值更新为 num
    • largest 始终代表程序目前所见过的最大数字。
  3. 获取下一个数字并将其存储在 num 中。

这些步骤位于 while 循环的末尾,循环将一遍又一遍地重复执行。

最后,一旦处理完所有输入的数字,程序就可以打印找到的最大值。


代码执行跟踪

让我们逐步跟踪执行此计划的代码。

  1. 第1行:创建并初始化 largest 变量为 0
  2. 第2行:提示用户可以通过输入负数(例如 -1)来停止输入。
  3. 第3行:创建并初始化变量 num0。此变量将用于接收用户的输入。
  4. 第4行:检查 num 的值。由于 0 >= 0 为真,条件成立,因此执行移动到第5行。
  5. 第5行if 条件判断 num > largest,即 0 > 0,结果为假。因此执行跳转到第7行。
  6. 第7行:要求用户输入一个数字。假设用户输入 6.82num 被更新为此值。
  7. 到达 while 循环的末尾,执行返回到第4行。
  8. 第4行:再次评估 while 条件。6.82 >= 0 为真,因此执行移动到第5行。
  9. 第5行if 条件 6.82 > largest (0) 为真,因此执行第6行。
  10. 第6行:将 largest 更新为 6.82,因为我们发现 num 是一个更大的数字。
  11. 第7行:用户输入 8.67num 被更新为 8.67。再次到达循环末尾。
  12. 执行返回到第4行,条件 8.67 >= 0 为真,进入循环。
  13. 第5行if 条件 8.67 > largest (6.82) 为真,执行第6行。
  14. 第6行:将 largest 更新为 8.67
  15. 第7行:用户输入 2.94num 被更新为 2.94
  16. 执行返回到第4行,条件 2.94 >= 0 为真,进入循环。
  17. 第5行if 条件 2.94 > largest (8.67) 为假,因此跳过第6行,直接执行第7行。
  18. 第7行:用户输入 -1num 被更新为 -1
  19. 执行返回到第4行,评估 while 条件 -1 >= 0,结果为假。while 循环结束。
  20. 第8行:打印 largest 的值 8.67

在这个例子中,while 循环总共执行了四次:一次是初始的 0 启动循环,另外三次是针对三个大于 0 的数字。当输入 -1 时,while 条件变为假,循环终止。


理解循环条件与终止

上一节我们通过具体示例跟踪了循环的执行。现在,我们来看看另一个例子,以加深对循环条件的理解。

请看以下代码,请暂停视频思考一下这段代码的作用。特别关注第3行:while 循环在什么情况下会继续执行?又在什么情况下会结束?

import random
num = 0
total = 0
while num != 3:
    num = random.randint(1, 6)
    total = total + 1
print(total)

num 不等于 3 时,while 循环的条件为真。因此,只有当 num 等于 3 时,循环才会结束。

while 循环内部,每次会生成一个 16 之间的随机数,并且变量 total 在每次循环中加 1

这段代码的作用是:打印出在随机生成一个 3 之前,一共生成了多少个随机数。

循环何时会退出?这取决于随机数的生成结果。但最终,总会生成一个 3,循环随之停止。


避免无限循环 ⚠️

那么,while 循环何时停止?当 while 条件变为假时,循环停止。

如果条件永远不会变为假呢? 这意味着你遇到了一个无限循环。代码将永远卡在循环中。

回顾我们刚才看到的例子:

  • 第一个例子的条件是 while num >= 0。如果用户永远不输入负数,就会产生无限循环。
  • 第二个例子的条件是 while num != 3。由于每次循环都会生成一个 16 的随机数,最终总会生成 3,所以这不是无限循环。

在使用 while 循环时,必须确保你的代码设计得当,最终能使 while 循环条件变为假。

确保循环终止的一种方法是明确告知用户如何输入以结束循环(如第一个例子所示)。另一种方法是确保代码逻辑本身能使循环条件最终变为假(例如,编写一个记录网球比赛得分的程序,当有人获得足够分数赢得比赛时,循环停止)。

编程愉快,并请小心无限循环!


总结

本节课中,我们一起学习了:

  1. 如何使用 while 循环来重复执行代码块,以解决“查找一系列输入数字中的最大值”这类问题。
  2. 通过变量(如 largest)来跟踪程序运行过程中的状态。
  3. 逐步跟踪代码执行,理解循环和条件判断如何协作。
  4. 分析了循环的继续与终止条件,并强调了设计循环时必须确保其能够终止,以避免无限循环。
  5. 掌握了确保循环终止的两种常见策略:依赖用户输入或依赖内部逻辑条件。

核心概念可以总结为以下代码结构:

# 初始化变量
largest = 0
num = 0

# 循环条件:当输入数字有效时继续
while num >= 0:
    # 条件判断与状态更新
    if num > largest:
        largest = num
    # 获取下一个输入
    num = float(input("Enter a number: "))

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/duke-prac-py/img/a590d077668bbf8b1579e7d859c1b7c7_4.png)

# 输出结果
print("The largest number is:", largest)

041:使用while循环简化网球计分程序

在本节课中,我们将学习如何使用while循环来编写一个网球比赛的计分程序。我们将模拟一场网球比赛,直到一方选手获胜为止。通过这个例子,你将理解如何利用循环来处理重复性任务,并掌握条件判断在程序流程控制中的应用。


游戏结束的条件

上一节我们介绍了循环的基本概念,本节中我们来看看如何确定循环的停止条件。不同的游戏有不同的结束规则。

  • 一些游戏,如篮球,受时间限制。例如,美国大学篮球比赛总时长限制为40分钟。
  • 棒球则受出局数限制。比赛进行九局,每队每局有三个出局机会。
  • 还有一些游戏,当一方达到特定分数时结束。循环将持续进行,直到达到这个特定分数。

网球计分规则

现在,我们聚焦于网球。网球就是一种达到特定分数即结束的游戏。

  • 网球比赛有两名选手。
  • 选手来回击球,直到一方未能回球或将球击出界外,另一方则得一分。
  • 获胜者是第一个获得至少4分,且领先对手至少2分的选手。

程序设计思路

我们将编写一个程序来记录网球比赛的分数并宣布获胜者。

以下是我们的程序设计步骤:

  1. 初始化变量:我们需要跟踪每位选手的得分,并为每位选手设置一个布尔变量来记录是否获胜。
  2. 游戏循环:使用一个while循环来重复进行比赛。循环条件是“两位选手都尚未获胜”。
  3. 处理得分:在循环内,询问用户哪一位选手赢得了当前这一分。然后更新该选手的分数,并检查其是否满足获胜条件。
  4. 宣布结果:当循环结束后(即一方获胜),打印最终比分和获胜者。

代码实现

现在,让我们根据上述计划来编写代码。我们将在VS Code中完成。

首先,初始化所需的变量。

# 初始化变量
score1 = 0  # 选手1的得分
score2 = 0  # 选手2的得分

player1_won = False  # 选手1是否获胜
player2_won = False  # 选手2是否获胜

接下来,我们设置游戏的主循环。循环将继续,直到其中一位选手获胜。

# 游戏循环:持续进行直到一方获胜
while not player1_won and not player2_won:

在循环内部,我们首先询问哪一位选手赢得了当前这一分。

    # 询问哪一位选手得分
    player = input("哪一位选手得分?输入 1 或 2: ")

然后,我们需要根据用户的输入来更新相应的分数并检查获胜条件。

    # 更新得分选手的分数
    if player == "1":
        score1 += 1  # 选手1得分加1
        # 检查选手1是否获胜:得分>=4 且 领先至少2分
        player1_won = (score1 >= 4) and (score1 - score2 > 1)
    elif player == "2":
        score2 += 1  # 选手2得分加1
        # 检查选手2是否获胜:得分>=4 且 领先至少2分
        player2_won = (score2 >= 4) and (score2 - score1 > 1)
    else:
        # 如果输入不是1或2,提示用户
        print("必须输入 1 或 2 来表示得分的选手。")

为了让玩家能实时看到比分,我们可以在每轮循环结束时打印当前分数。

    # 显示当前比分
    print(f"比分:选手1 {score1} - 选手2 {score2}")

当循环退出时,意味着有一方选手已经获胜。我们来确定获胜者并宣布结果。

# 确定获胜者
if player1_won:
    winner = "选手1"
else:
    winner = "选手2"

# 宣布最终结果
print(f"获胜者是:{winner}")
print(f"最终比分:选手1 {score1} - 选手2 {score2}")

程序运行示例

运行上述程序,你将看到类似以下的交互过程:

哪一位选手得分?输入 1 或 2: 1
比分:选手1 1 - 选手2 0
哪一位选手得分?输入 1 或 2: 1
比分:选手1 2 - 选手2 0
哪一位选手得分?输入 1 或 2: 2
比分:选手1 2 - 选手2 1
...
哪一位选手得分?输入 1 或 2: 1
比分:选手1 6 - 选手2 4
获胜者是:选手1
最终比分:选手1 6 - 选手2 4

总结

本节课中我们一起学习了如何使用while循环来构建一个网球计分模拟程序。我们掌握了如何设定循环条件(while not player1_won and not player2_won),如何在循环内处理用户输入、更新变量状态,以及如何根据特定条件(得分>=4且领先至少2分)来终止循环。这个程序的核心逻辑可以轻松修改以适应其他类似规则的比赛,例如乒乓球(获胜条件改为至少11分且领先至少2分)。通过这个实践,你巩固了循环和条件判断这两个重要的编程概念。

祝你编程愉快!🎾

042:使用while循环模拟随机数 🎲

在本节课中,我们将学习如何使用while循环来模拟一个随机事件——掷骰子。我们将编写一个程序,生成大量随机数,并统计每个数字出现的次数,以此来验证计算机生成的随机数是否“公平”。


概述

我们将模拟一个三面骰子(数字为1、2、3)的投掷过程。通过让程序生成大量随机数并统计每个数字出现的频率,我们可以观察结果是否接近理论上的三分之一概率。这个练习将帮助我们理解循环、条件判断和随机数生成在实际模拟中的应用。


代码实现步骤

以下是构建此模拟程序所需的步骤。

1. 初始化计数器

首先,我们需要创建变量来记录数字1、2和3各自出现的次数。我们将它们初始化为0。

# 初始化计数器
one_count = 0  # 数字1出现的次数
two_count = 0  # 数字2出现的次数
three_count = 0  # 数字3出现的次数

2. 获取用户输入

接下来,我们需要询问用户希望模拟多少次投掷。我们将使用input()函数获取输入,但必须记住将其转换为整数类型,以便后续比较。

# 询问用户模拟次数
num_runs = int(input("How many times to run? "))

3. 导入随机模块

为了生成随机数,我们需要在程序开头导入Python的random模块。

import random

4. 构建while循环

循环的核心逻辑是:只要已生成的随机数总数(即三个计数器的和)小于用户指定的次数,就继续生成新的随机数。

while one_count + two_count + three_count < num_runs:

5. 在循环体内生成并统计随机数

在每次循环中,我们生成一个1到3之间的随机整数。然后,检查这个数字是1、2还是3,并相应地更新对应的计数器。

    # 生成一个1到3的随机整数
    rand_num = random.randint(1, 3)

    # 根据生成的数字更新对应的计数器
    if rand_num == 1:
        one_count += 1
    elif rand_num == 2:
        two_count += 1
    elif rand_num == 3:
        three_count += 1
    else:
        # 理论上不会执行,但保留以应对意外情况
        print("Error: Number is not 1, 2, or 3.")

6. 输出结果

循环结束后,我们打印出每个数字出现的最终次数。

# 循环结束后打印结果
print("Ones:", one_count)
print("Twos:", two_count)
print("Threes:", three_count)

完整代码示例

将以上所有部分组合起来,就得到了完整的模拟程序。

import random

# 初始化计数器
one_count = 0
two_count = 0
three_count = 0

# 询问用户模拟次数
num_runs = int(input("How many times to run? "))

# 循环生成并统计随机数
while one_count + two_count + three_count < num_runs:
    rand_num = random.randint(1, 3)

    if rand_num == 1:
        one_count += 1
    elif rand_num == 2:
        two_count += 1
    elif rand_num == 3:
        three_count += 1
    else:
        print("Error: Number is not 1, 2, or 3.")

# 输出结果
print("Ones:", one_count)
print("Twos:", two_count)
print("Threes:", three_count)

程序测试与调试

在运行程序时,我们可能会遇到一些错误。让我们回顾一下常见的错误及其解决方法。

错误1:类型错误(TypeError)

如果忘记将input()的字符串结果转换为整数,在while循环中比较整数和字符串时就会发生类型错误。

解决方法: 确保使用int()函数进行转换。

num_runs = int(input("How many times to run? "))

错误2:名称错误(NameError)

如果忘记导入random模块,Python将无法识别random.randint函数。

解决方法: 在程序开头添加import random

错误3:逻辑错误(Logic Error)

在更新计数器时,如果错误地增加了错误的值(例如,two_count += 2而不是two_count += 1),会导致统计结果失真。

解决方法: 仔细检查条件判断分支中的代码,确保每个计数器只增加1。


扩展思考

现在我们已经成功模拟了三面骰子,你可以尝试修改程序以实现更复杂的模拟。

  1. 模拟更多面的骰子:如何修改程序来模拟四、五或六面骰子?你需要改变random.randint的范围以及计数器的数量。
  2. 同时投掷多个骰子:如果要模拟同时投掷两个骰子并计算点数之和,程序需要做哪些修改?你可能需要生成两个随机数,然后对它们的和进行统计。

总结

本节课中,我们一起学习了如何使用while循环来构建一个简单的随机数模拟程序。我们掌握了初始化变量、获取用户输入、生成随机数、使用条件语句更新状态以及输出结果的基本流程。通过调试常见的错误,我们也加强了对Python数据类型和逻辑流程的理解。这个程序是计算机模拟的一个基础示例,你可以在此基础上探索更多有趣的概率和统计实验。

043:控制字符串长度 - 使用len函数 📏

在本节课中,我们将要学习如何使用Python内置的len()函数来获取字符串的长度,并利用这个功能来验证用户输入的密码是否符合最低长度要求。


概述

在编程中,我们经常需要检查用户输入的数据是否符合特定要求。一个常见的例子是密码验证。许多应用程序要求密码至少包含8个字符。本节课,我们将学习如何使用len()函数来检查字符串的长度,并编写一个程序,确保用户创建的密码满足最低长度要求。


认识len()函数

len()是Python的一个内置函数,用于返回一个对象的长度或项目数。当应用于字符串时,它会返回字符串中的字符数量。这个函数非常实用,能帮助我们快速获取字符串的长度信息。

以下是len()函数的基本格式:

len(字符串变量、字符串值或字符串表达式)

函数会计算括号内字符串的长度,并返回一个整数值。


len()函数使用示例

为了更好地理解len()函数,让我们看几个具体的例子。

以下是几个使用len()函数的例子:

  1. 直接计算字符串字面量的长度

    x = len("animal")
    # 字符串 "animal" 有6个字符,因此 x 被赋值为 6。
    
  2. 计算字符串表达式的结果长度

    y = len("car" + "32")
    # 首先进行字符串拼接,得到 "car32"。这个字符串有5个字符,因此 y 被赋值为 5。
    
  3. 计算字符串变量的长度

    word = "bike"
    w = len(word)
    # 变量 `word` 存储的字符串是 "bike",它有4个字符,因此 w 被赋值为 4。
    
  4. 计算重复字符串的长度

    z = len(word * 3)
    # `word * 3` 的结果是 "bikebikebike"。由于 "bike" 有4个字母,重复3次后总共有12个字符,因此 z 被赋值为 12。
    

为什么使用len而不是length

你可能会好奇,为什么Python使用缩写len,而不是更直观的length。这主要源于Python的创始人Guido van Rossum的设计偏好。他在1991年创建Python时,倾向于使用简短的名字。lenlength更短,输入起来更快,这在需要将多个函数(如str(), int(), bool())写在同一行代码中时尤其方便。


在循环中使用len()进行密码验证

上一节我们介绍了len()函数的基本用法,本节中我们来看看如何将它应用到实际场景中,比如强制用户输入一个足够长的密码。

我们可以将len()函数与while循环结合,创建一个密码验证程序。这个程序会不断要求用户输入新密码,直到输入的密码长度至少为8个字符。

以下是实现此功能的代码示例:

password = input("Enter a new password: ")

while len(password) < 8:
    print("Password is too short.")
    password = input("Enter a new password: ")

print("Your new password is:", password)

代码运行流程解析

让我们逐步分析一下这段代码是如何工作的:

  1. 程序首先提示用户输入一个新密码。
  2. while循环的条件 len(password) < 8 会检查密码长度是否小于8。
  3. 如果条件为True(密码太短),则执行循环体:
    • 打印提示信息“Password is too short.”。
    • 再次提示用户输入一个新密码。
  4. 第2步和第3步会不断重复,直到用户输入一个长度大于等于8的密码。
  5. 当密码长度符合要求时,while循环的条件变为False,循环结束。
  6. 最后,程序执行循环后的代码,打印出用户设置的新密码。

示例运行过程:

  • 用户输入 cat(3个字符)-> 提示太短,要求重新输入。
  • 用户输入 catbark(7个字符)-> 提示太短,要求重新输入。
  • 用户输入 catmiaomiao(11个字符)-> 长度达标,循环结束,程序确认新密码。

这个例子展示了如何使用循环来“强制”用户执行特定操作,直到满足我们设定的条件为止。


循环终止条件

理解循环何时停止至关重要。在这个例子中,循环继续的条件是“密码长度小于8”。因此,循环停止的条件就是其反面,即“密码长度大于等于8”。一旦用户输入了长度合格的密码,循环条件不再满足,程序便会继续向下执行。


总结

本节课中我们一起学习了以下核心内容:

  • len()函数:用于获取字符串(或其他可迭代对象)的长度。其参数可以是字符串字面量、字符串变量或字符串表达式。
  • 在循环条件中的应用:我们学会了如何在while循环的条件中使用len()函数,以此来控制程序的流程。具体来说,我们使用 while len(password) < 8: 这个条件,确保循环会持续运行,直到用户输入一个长度至少为8个字符的密码。
  • 实际应用:我们构建了一个简单的密码验证程序,它能够友好地引导用户创建符合安全长度要求的密码。

掌握len()函数是处理字符串和用户输入验证的基础,我们将在后续课程中看到更多它与循环结合使用的强大例子。

Happy programming! 🎉

044:检查字符串内部 - 使用 in 关键字 🔍

在本节课中,我们将学习如何使用 in 关键字来检查一个字符串是否包含在另一个字符串中。我们将通过创建一个密码验证程序来实践这一概念,该程序要求密码长度恰好为8个字符,并且其中至少包含一个非字母字符。

概述

在数字世界中,拥有一个安全的密码至关重要。安全的密码可以保护我们的个人信息、财务甚至身份。本节课,我们将讨论如何创建一个满足特定要求的安全密码。具体来说,密码必须恰好为8个字符,并且其中至少有一个字符不是字母。

理解 in 关键字

在深入密码示例之前,我们先学习一个新概念。关键字 in 可以在条件语句中用于检查一个字符串是否是另一个字符串的一部分。

其基本格式如下:

<string1> in <string2>

这个条件检查 string1 是否存在于 string2 内部。string1 中的字母必须在 string2 中连续出现,并且顺序必须完全相同。

如果 string1string2 中,则条件评估为 True,否则为 False。请注意,<string> 可以是一个实际的字符串、一个字符串变量,甚至是一个结果为字符串的表达式。

示例分析

让我们看几个例子来理解其工作原理:

  1. 检查 "rev" 是否在 "university" 中:

    • 虽然 "r""e""v" 这些字母都存在于 "university" 中,但它们不是以 "rev" 的顺序连续出现的(例如,出现了 "ver")。因此,条件为 False
  2. 检查 "to" 是否在 "customize" 中:

    • "to" 确实连续出现在 "customize" 中。因此,条件为 True
  3. 检查 "28" 是否在 "March 28" 中:

    • "28""March 28" 的一部分。因此,条件为 True
  4. 检查 "to" 是否在 "Total" 中:

    • 注意,"to" 是小写,而 "Total" 中的 "T" 是大写。in 关键字是区分大小写的,因此它们不匹配,条件为 False

密码验证程序的设计

现在,让我们回到密码要求:密码长度恰好为8个字符,并且不能全是字母字符。为了使示例简单,我们假设用户输入的所有字母都是小写,并且用户一次输入一个字符。

以下是实现该程序的思路:

  1. 用户在一个循环中逐个字符地构建密码。
  2. 程序可以检查每个字符是否为字母,并在此过程中统计字母的数量。
  3. 由于密码必须恰好为8个字符,当密码长度达到8时,循环停止。因此,while 循环的条件是:只要正在构建的密码长度小于8,就继续循环。
  4. 密码构建完成后,程序根据字母计数判断密码是否合格。如果密码中至少有一个字符不是字母,则密码是合格的。

编写密码验证程序

以下是实现上述逻辑的代码。我们将分两部分讲解。

第一部分:构建密码并统计字母

这部分代码负责接收用户输入,构建密码字符串,并统计其中字母的数量。

# 第1行:初始化一个空字符串,用于构建密码
password = ""

# 第2行:初始化计数器,用于统计字母的数量
count = 0

# 第3-7行:循环,用于构建一个8字符的密码
while len(password) < 8:
    # 第4行:提示用户输入一个字符
    ch = input("Enter a character: ")

    # 第5行:将新字符添加到密码字符串末尾
    password = password + ch

    # 第6行:使用 `in` 关键字检查新字符是否是小写字母
    if ch in "abcdefghijklmnopqrstuvwxyz":
        # 第7行:如果是字母,则计数器加1
        count = count + 1

代码解释

  • while len(password) < 8: 确保循环在密码长度达到8时停止。
  • ch in "abcdefghijklmnopqrstuvwxyz": 是使用 in 关键字的典型例子,它检查变量 ch 中的单个字符是否存在于表示小写字母的字符串中。

第二部分:评估密码质量

密码构建完成后,我们需要根据统计结果告诉用户密码是否合格。

# 第8-11行:根据字母计数判断密码质量
if count == len(password):  # 如果字母数量等于密码总长度,说明全是字母
    print(password, "has all letters. That is not a good password.")
else:  # 否则,说明至少有一个非字母字符
    print("This password is good.")

判断逻辑:如果 count(字母数)等于 len(password)(密码总长度,即8),则意味着所有字符都是字母,密码不合格。否则,密码合格。

程序运行示例

让我们通过两个例子来逐步了解程序如何工作。

示例一:不合格密码(全部为字母)

假设用户逐个输入字符,最终拼出单词 "bookworm"

  1. 程序循环8次,接收 b, o, o, k, w, o, r, m
  2. 循环结束时,password = "bookworm"count = 8(因为每个字符都是字母)。
  3. 评估条件 count == len(password)(即 8 == 8)为 True
  4. 程序输出:bookworm has all letters. That is not a good password.

示例二:合格密码(包含非字母字符)

假设用户输入 "b53kworm"

  1. 程序循环8次,接收 b, 5, 3, k, w, o, r, m
  2. 循环结束时,password = "b53kworm"count = 6(字符 '5''3' 不是字母)。
  3. 评估条件 count == len(password)(即 6 == 8)为 False
  4. 程序输出:This password is good.

in 关键字的其他应用

in 关键字是一个多用途的工具。除了检查字母,你还可以用它来检查许多其他情况。

以下是其他一些应用示例:

  • 检查字符是否为元音字母
    if ch in "aeiou":
        print(ch, "is a vowel.")
    
    如果 ch'e',则条件为 True

  • 检查子串是否存在于密码中

    if "row" in password:
        print("Found 'row' in the password.")
    

    对于 password = "password",虽然字母 'r''o''w' 都存在,但它们不是以 "row" 的顺序连续出现(而是 "wor"),因此条件为 False

  • 检查子串 "or" 是否存在

    if "or" in password:
        print("Found 'or' in the password.")
    

    对于 password = "password""or" 确实连续出现,因此条件为 True

总结

在本节课中,我们一起学习了:

  1. in 关键字:用于在条件语句中检查一个字符串是否包含在另一个字符串中。格式为 <string1> in <string2>,它检查 string1 是否作为子串按顺序完整地存在于 string2 中。
  2. 密码验证程序:我们编写了一个程序,要求用户构建一个恰好8个字符的密码,并使用 in 关键字和计数器来确保密码中至少包含一个非字母字符,从而满足基本的安全要求。

现在,你的工具箱里又多了一个有用的工具——in 关键字。密码可以有很多不同的要求,你可以思考如何使用类似的技术来测试其他要求(例如,必须包含数字、大写字母或特殊符号)。祝你编程愉快!

045:停止条件 - while循环与布尔值 🔄

在本节课中,我们将要学习如何为while循环设置停止条件。我们将探讨如何通过思考“何时停止”来反向推导出循环的继续条件,并利用布尔逻辑来简化表达式。这对于编写清晰、高效的循环代码至关重要。

从停止条件到循环条件

有时,先思考循环的停止条件会更容易。你想让while循环在何时停止?然后,如果你对这个停止条件取反,就得到了循环继续执行的条件。也就是说,循环会一直重复,直到停止条件变为真。

其格式如下:while not (停止条件):。循环会在停止条件为假时重复执行,并在停止条件为真时停止。

让我们看一个例子。假设你希望在构建的密码长度达到8个或更多字符时停止。那么你的while条件是什么?它就是停止条件的否定:while not (len(pw) >= 8):

这有一个更简单的说法,它等价于:while len(pw) < 8:

处理包含逻辑运算符的停止条件

现在,让我们看一个从包含逻辑运算符的停止条件创建while条件的例子。

考虑一个密码的要求:至少8个字符,并且至少包含2个数字字符。这意味着停止条件有两部分:密码长度必须大于等于8,并且数字计数必须大于等于2。假设我们使用变量count来跟踪密码中的数字数量。

那么while条件会是什么?我们只需要对整个停止条件取反,用括号括起来,然后在其前面加上not。代码如下:
while not (len(password) >= 8 and count >= 2):

这个条件等价于:while (not len(password) >= 8) or (not count >= 2):

这两个not表达式都可以简化。整个条件等价于:
while len(password) < 8 or count < 2:
也就是说,如果密码长度太短为真,或者密码还没有至少两个数字为真,那么while循环就应该继续执行,直到这两个条件都为假。

布尔逻辑回顾

为了理解为什么这些条件是等价的,让我们快速回顾一下布尔逻辑。这里有两个表达式A和B。not (A and B)(not A) or (not B) 是等价的。你可以通过真值表来验证这一点。

实践:创建符合要求的密码程序

让我们看一个示例程序,它要求用户创建一个符合特定要求的新密码。在这个例子中,密码必须至少有8个字符,并且其中至少有两个字符是数字。程序会一直运行,直到用户输入的新密码满足这些要求。

以下是程序的工作原理:

  1. 程序需要一个while循环来逐个处理字符。
  2. 程序需要计算有多少字符是数字。
  3. 程序需要使用len()函数检查密码长度。
  4. 程序在密码至少有8个字符且至少包含2个数字时停止。
  5. 循环结束后,程序告知用户密码符合要求。

那么while条件是什么?它是停止条件的否定。停止条件是:至少有8个字符 并且 至少有2个数字。因此,while条件是:while not (至少8个字符 and 至少2个数字):

根据我们学过的布尔逻辑,这等价于:while (not 至少8个字符) or (not 至少2个数字):,可以简化为:while 长度小于8 or 数字少于2:

代码示例与分析

以下是实现上述逻辑的可能代码。在阅读解释前,请先暂停视频尝试理解代码。

pw = ""          # 初始化密码为空字符串
count = 0        # 初始化数字计数为0

while len(pw) < 8 or count < 2:  # 循环条件:长度不足8 或 数字不足2
    ch = input("Enter one character: ")  # 提示用户输入一个字符
    pw = pw + ch                         # 将字符添加到密码中

    if ch.isdigit():                     # 检查字符是否为数字
        count = count + 1                # 如果是数字,计数加1

    if len(pw) >= 8 and count < 2:       # 如果长度已够但数字不足
        print("You need two digits.")    # 提醒用户需要两个数字

print("This password is good.")          # 循环结束,密码符合要求

代码解析:

  • 第1-2行:初始化密码pw为空字符串,数字计数count为0。
  • 第3行while循环条件。只要密码长度小于8 或者 数字计数小于2,循环就会继续。
  • 第4-9行:循环体。
    • 第4行:获取用户输入的一个字符。
    • 第5行:将新字符拼接到密码末尾。
    • 第6-7行:检查字符是否为数字,如果是则增加count
    • 第8-9行:这是一个友好的提示。一旦密码长度达到8位,如果数字还不够,就提醒用户还需要数字。
  • 第10行:循环结束后,打印成功信息。

运行示例:
假设用户依次输入:B, I, R, D, N, E, S, T, 4, 8

  • 输入前8个字母后,pw变为“BIRDNEST”,长度达标(8),但count为0。
  • 此时第8行的if条件为真,程序输出“You need two digits.”。
  • 用户继续输入4count变为1,提示依然存在。
  • 用户输入8count变为2。此时while循环条件len(pw) < 8 or count < 2为假,循环结束。
  • 最终密码为“BIRDNEST48”,程序输出“This password is good.”。

总结

本节课中我们一起学习了while循环的停止条件。

  • 我们了解到,很多时候先思考循环的停止条件比直接思考循环条件更容易。然后通过对停止条件取反(使用not),就可以得到让循环持续执行直到满足停止条件的循环条件,即 while not (停止条件):
  • 我们回顾了布尔逻辑,特别是德摩根定律:not (A and B) 等价于 (not A) or (not B)not (A or B) 等价于 (not A) and (not B)。理解这些定律有助于简化复杂的循环条件。
  • 我们通过一个“创建有效密码”的程序实例,综合应用了这些概念,演示了如何用while循环强制用户输入满足特定条件(长度和数字要求)的数据。

你可以思考其他密码要求(如必须包含大写字母、特殊符号等),并尝试编写相应的程序。编程愉快!

046:字符串索引与字符访问 🔍

在本节课中,我们将学习如何访问字符串中的单个字符,即字符串索引。我们将了解字符串中字符的顺序和位置编号,并学习如何通过索引来获取特定位置的字符。此外,我们还将探讨如何通过循环遍历字符串中的每个字符,以解决实际问题,例如判断一个字符串是否完全由数字组成。


字符串索引的基本概念

字符串是一个有序的字符序列。有序意味着字符串中的字符有固定的顺序:第一个字符、第二个字符,依此类推。每个字符在字符串中都有一个位置编号,这个编号称为索引。

在计算机科学中,索引通常从0开始计数,而不是从1开始。因此,字符串的第一个字符位于索引0,第二个字符位于索引1,以此类推。可以将索引理解为从字符串开头移动到目标字符所需的步数(偏移量)。由于第一个字符不需要移动,所以它的索引是0。

例如,对于字符串 "bridge"

  • 字符 'B' 位于索引0。
  • 字符 'R' 位于索引1。
  • 字符 'I' 位于索引2。
  • 字符 'D' 位于索引3。
  • 字符 'G' 位于索引4。
  • 字符 'E' 位于索引5。

如何访问字符串中的字符

要访问字符串中特定索引位置的字符,可以使用以下格式:

string_variable[index]

其中,string_variable 是字符串变量的名称,index 是一个整数,表示要访问的字符的索引位置。

例如:

x = "bridge"
print(x[1])  # 输出: R
print(x[5])  # 输出: E

需要注意的是,字符串是不可变的。这意味着你可以访问字符串中的字符,但不能修改它们。例如,以下代码会引发错误:

x = "bridge"
x[2] = "U"  # 错误:无法修改字符串中的字符

如果你需要修改字符串,可以创建一个新的字符串,但不能直接修改现有的字符串。


遍历字符串中的字符

为了检查字符串中的每个字符是否都是数字,我们需要遍历字符串中的每个字符。可以使用 while 循环来实现这一点。

以下是遍历字符串并检查每个字符是否为数字的代码示例:

nu = input("Enter an integer: ")
index = 0

while index < len(nu) and nu[index].isdigit():
    index += 1

if index == len(nu):
    print(f"{nu} is an integer.")
else:
    print(f"{nu[index]} is not a digit.")

让我们详细分析这段代码:

  1. 首先,程序要求用户输入一个字符串,并将其存储在变量 nu 中。
  2. 然后,初始化变量 index 为0,用于跟踪当前检查的字符位置。
  3. while 循环中,条件 index < len(nu) and nu[index].isdigit() 确保索引有效且当前字符是数字。如果条件为真,则更新索引以检查下一个字符。
  4. 循环结束后,如果 index 等于字符串的长度,说明所有字符都是数字,字符串表示一个整数。否则,说明字符串中包含非数字字符。

避免程序崩溃的关键点

在遍历字符串时,必须确保索引不会超出字符串的有效范围。如果尝试访问不存在的索引位置,程序会崩溃。为了避免这种情况,我们在 while 循环的条件中添加了索引有效性的检查:

while index < len(nu) and nu[index].isdigit():

这里利用了短路求值的特性:如果 index < len(nu) 为假,则不会执行 nu[index].isdigit(),从而避免了索引越界的错误。


示例分析

让我们通过两个示例来理解代码的执行过程:

示例1:输入字符串 "67A9"

  • 索引0:字符 '6' 是数字,继续。
  • 索引1:字符 '7' 是数字,继续。
  • 索引2:字符 'A' 不是数字,循环结束。
  • 由于索引2不等于字符串长度4,程序输出:A is not a digit.

示例2:输入字符串 "42"

  • 索引0:字符 '4' 是数字,继续。
  • 索引1:字符 '2' 是数字,继续。
  • 索引2:索引等于字符串长度,循环结束。
  • 由于索引2等于字符串长度2,程序输出:42 is an integer.

总结

在本节课中,我们一起学习了字符串索引和字符访问的基本概念。我们了解到字符串是一个有序的字符序列,索引从0开始,表示字符在字符串中的位置。我们学习了如何通过索引访问字符串中的字符,并利用 while 循环遍历字符串中的每个字符。最重要的是,我们学会了在访问字符串字符之前检查索引的有效性,以避免程序崩溃。现在,你已经掌握了如何判断一个字符串是否完全由数字组成,为后续的编程实践打下了坚实的基础。祝你编程愉快! 🎉

047:统计单词中的字符 - 更多索引操作 🔢

在本节课中,我们将学习如何编写一个程序,用于统计一个特定字符在给定单词中出现的次数。我们将使用循环、索引和条件判断来实现这个功能。


概述

假设你想知道一本书中最常见的字母是什么。你需要遍历书中的每个单词,并统计每个字母出现的次数。如果这样做,字母E很可能是最常见的,因为它在很多单词中都出现。

让我们看一个更简单的问题,我们可以用目前学到的所有知识来解决它。我们将获取一个字符和一个单词,然后统计该字符在单词中出现的次数。

以下是一些例子。如果单词是“underrepresented”,字符是“E”,那么我们的程序将告诉我们“underrepresented”中有五个E。如果单词是“butter”,字符是“T”,那么程序将告诉我们“butter”中有两个T。

我们如何编写一个程序来计算这个呢?


程序逻辑分析

为了统计一个字符在单词中出现的次数,我们需要查看单词中的每个字符。我们如何做到这一点?

我们需要一个循环来遍历字符。我们需要一个索引变量来知道当前正在查看哪个字符。我们将从字符串的第一个字符开始,所以索引需要从0开始。

在遍历单词中的字符时,我们需要更新索引。

循环的条件是什么?只要索引小于单词的长度,我们就可以继续遍历字符。

我们还需要统计字符出现的次数。我们如何做到这一点?我们需要一个变量来存储当前看到的字符次数。

我们还需要将单词中的每个字符与我们的字符进行比较,看看是否找到了另一个匹配项。如果是,我们将计数加一。


代码实现

让我们看看解决这个问题的可能代码。让我们从顶部开始。

以下是实现该功能的代码:

word = input("请输入一个单词: ")
character = input("请输入一个字符: ")

index = 0
count = 0

while index < len(word):
    if word[index] == character:
        count += 1
    index += 1

print(f"字符 '{character}' 在单词 '{word}' 中出现了 {count} 次。")

代码逐步解析

第1行和第2行要求用户输入单词和字符。第3行和第4行初始化变量。变量index用于跟踪字符串中的当前字符位置,count用于跟踪我们找到目标字符的次数。

接下来,我们进入循环,我们将查看单词中的每个字符。让我们分解一下。

第5行的条件验证索引是单词中的有效位置。第6行和第7行检查单词中的当前字符是否匹配。如果是,则更新计数以反映这一点。第8行更新索引。注意,在while循环中,每次都必须更新索引,以便继续遍历单词中的字符。

最后,在第9行,我们的程序打印出字符在单词中出现的次数。


程序运行示例

让我们通过一个例子来跟踪程序运行时会发生什么。

程序从第1行开始,要求输入。假设用户输入单词“boot”。然后在第2行,用户输入字符“O”。接下来,在第3行,程序创建并初始化变量index,以跟踪单词中当前字符的位置。注意,索引将从0开始。

在第4行,程序创建并初始化变量count,以跟踪我们将在单词“boot”中看到字母“O”的次数。计数初始化为0。第5行是while循环。单词“boot”的长度是4。0小于4吗?这是真的。程序进入while循环。

对于第6行,程序必须计算位置0处的字符。那是“B”。“B”等于“O”吗?这是假的,所以我们跳过第7行,转到第8行,将索引更新为1。程序到达while循环体的末尾,并移动到第5行继续。

再次检查while条件。这次索引是1。1小于4吗?这是真的。索引位置1处的字符是什么?位置1处的字符是“O”。“O”等于“O”吗?这是真的。在第7行,计数更新为1。我们有了第一个匹配项。

接下来,在第8行,索引增加1,索引现在是2。再次,程序到达while循环体的末尾,并移动到第5行继续。

再次检查while条件。这次索引是2。2小于4吗?这是真的。索引位置2处的字符是什么?位置2处的字符是“O”。“O”等于“O”吗?这是真的。在第7行,计数更新为2。我们有了第二个匹配项。

接下来,在第8行,索引更新,索引现在是3。再次,程序到达while循环体的末尾,并返回到第5行继续。

再次检查while条件。这次索引是3。3小于4吗?这是真的。索引位置3处的字符是什么?位置3处的字符是“T”。“T”等于“O”吗?这是假的,所以我们移动到第8行。索引增加1,索引现在是4。再次,程序到达while循环体的末尾,并移动到第5行继续。

再次检查while条件。这次索引是4。4小于4吗?这是假的。while循环结束。第9行打印出“O”出现了两次。程序结束。


程序总结

为了统计特定字符在单词中出现的次数,程序需要做什么?

程序必须通过循环遍历单词中的字符来查看每个字符。为了访问每个字符,程序需要一个索引,该索引必须在循环中每次更新以遍历单词中的所有字符。程序还必须统计特定字符在单词中出现的次数。为此,需要一个变量,每次找到匹配项时更新该变量。

由于程序有一个循环,我们需要考虑如何停止循环。当单词中没有更多字符可查看时,循环停止。这意味着当索引大于或等于单词的长度时,我们应该已经查看了每个字符。记住,while循环条件是停止条件的反面。while循环条件将是:当索引小于单词的长度时,循环继续处理单词中的字符。

就是这样。记住,这是一个统计字符在单词中出现次数的小程序,但这个想法可以扩展到统计字符在书中出现的次数。我们将学习更多知识以达到那个目标。同时,欢迎你添加或调整这个程序以满足你的需求。编程愉快。😊

048:单词中元音字母翻倍演示

概述

在本节课中,我们将学习如何编写一个Python程序,该程序接收用户输入的一个单词,并生成一个新单词。新单词的规则是:将原单词中的所有元音字母(a, e, i, o, u)重复一次,而辅音字母保持不变。我们将通过使用while循环、字符串索引和条件判断来实现这个功能。


问题分析与计划

首先,我们需要明确任务:用户输入一个单词,程序输出一个新单词,其中元音字母出现两次。

在Python中,字符串是不可变的,这意味着我们不能直接修改原始字符串。因此,我们的策略是创建一个新的空字符串,然后遍历原始单词的每个字母,根据字母类型(元音或辅音)决定向新字符串中添加一个还是两个该字母。

以下是我们的解决计划:

  1. 向用户请求一个单词。
  2. 初始化一个空字符串,用于构建新单词。
  3. 使用循环遍历原始单词的每一个字母。
  4. 对于每个字母,检查它是否为元音。
  5. 如果是元音,则向新字符串添加两个该字母;否则,只添加一个。
  6. 循环结束后,打印出构建好的新单词。

代码实现步骤

第一步:获取用户输入

我们使用input()函数来获取用户输入的单词,并将其存储在一个变量中。

word = input("Enter a word: ")

第二步:初始化变量

我们需要两个关键变量:

  • new_word:用于累积构建新单词,初始为空字符串。
  • index:用于在while循环中追踪当前正在处理哪个字母,初始为0。
new_word = ''
index = 0

第三步:构建循环结构

我们需要一个循环来逐个检查单词中的字母。我们将使用while循环,并在每次迭代后更新index以指向下一个字母。

循环的停止条件是index等于单词的长度(即所有字母都已处理完毕)。因此,循环继续的条件是index < len(word)

while index < len(word):
    # 处理逻辑将放在这里

第四步:在循环内判断并添加字母

在循环体内,我们使用word[index]来获取当前字母。然后判断它是否在元音字符串"aeiou"中。

  • 如果是元音,我们向new_word添加两个该字母。
  • 如果不是元音(即辅音),我们只添加一个该字母。
while index < len(word):
    if word[index] in "aeiou":
        new_word = new_word + word[index] * 2
    else:
        new_word = new_word + word[index]

第五步:更新索引并完成循环

上一节我们介绍了如何在循环内处理字母,但要使循环正确推进,关键的一步是在每次处理完一个字母后,将index的值增加1,以便下次循环检查下一个字母。如果忘记这一步,程序将陷入无限循环。

while index < len(word):
    if word[index] in "aeiou":
        new_word = new_word + word[index] * 2
    else:
        new_word = new_word + word[index]
    index = index + 1  # 关键步骤:移动到下一个字母

第六步:输出结果

循环结束后,new_word已经构建完成,我们将其打印出来。

print("New word is", new_word)

完整代码与测试

将以上所有步骤组合起来,就得到了完整的程序。

# 1. 获取用户输入
word = input("Enter a word: ")

# 2. 初始化变量
new_word = ''
index = 0

# 3. 循环处理每个字母
while index < len(word):
    # 4. 判断是否为元音并相应添加字母
    if word[index] in "aeiou":
        new_word = new_word + word[index] * 2
    else:
        new_word = new_word + word[index]
    # 5. 更新索引,准备处理下一个字母
    index = index + 1

# 6. 输出最终结果
print("New word is", new_word)

现在,让我们测试几个例子:

  • 输入 celebrate,输出 ceeleebbraatee
  • 输入 progress,输出 proogreess
  • 输入 bookmobile,输出 boookmobiilee
  • 输入 shy,输出 shy

总结

本节课中我们一起学习了如何构建一个字符串处理程序。我们掌握了以下核心技能:

  1. 使用while循环和索引变量index来遍历字符串。
  2. 使用in关键字检查一个字符是否存在于某个字符串中(例如判断元音)。
  3. 使用字符串拼接(+)和重复(*)操作来构建新的字符串。
  4. 理解了在循环中更新控制变量(如index)的重要性,以避免无限循环。

这个程序的核心逻辑可以推广到许多其他字符串处理任务中,例如替换特定字符、统计字母出现次数或实现简单的加密解密。你可以尝试修改代码,例如让辅音字母翻倍,或者处理大写字母,来进一步巩固所学知识。

Happy programming! 🎉

049:创建单词缩写演示 🧩

在本节课中,我们将学习如何利用while循环、成员运算符in以及逻辑运算的“短路”特性,来编写一个程序。这个程序的功能是:输入一个单词,然后生成它的缩写。缩写的规则是:提取从单词开头到(但不包括)第一个元音字母之间的所有字母。

问题定义与计划

首先,我们来明确一下问题。缩写规则是:提取单词中从第一个字母开始,到第一个元音字母(不包含该元音) 为止的所有字母。

  • 例如:scrap 的缩写是 SCR(第一个元音是 a)。
  • 例如:water 的缩写是 W(第二个字母 a 就是元音)。

我们还需要考虑一个特殊情况:如果单词以元音开头。对于这种情况,我们简单地使用这个元音作为缩写。

  • 例如:apple 的缩写是 a

现在,让我们规划一下解决这个问题的步骤:

  1. 向用户请求输入一个单词。
  2. 初始化缩写变量为空字符串。
  3. 初始化索引变量为0,用于追踪当前检查的字母位置。
  4. 循环检查单词中的每个字母,直到找到元音或检查完所有字母。
  5. 在循环中,将非元音字母添加到缩写末尾,并更新索引。
  6. 循环结束后,处理特殊情况:如果单词以元音开头,则缩写就是该元音。
  7. 最后,打印出生成的缩写。

代码实现

现在,让我们根据上述计划来编写代码。首先,获取用户输入的单词。

word = input("Enter a word: ")

接下来,初始化缩写变量和索引变量。

abbreviation = ""
index = 0

构建循环条件

循环的停止条件是:当前字母是元音 或者 索引已超出单词范围。因此,while循环的进入条件应该是这两个条件的反面(即“非”)。

我们可以将其写作:
while not (letter is vowel or out of letters)

根据逻辑运算规则(德摩根定律),这等价于:
while (not letter is vowel) and (not out of letters)

现在,让我们将其翻译成具体的Python代码。判断字母是否为元音,可以使用成员运算符in。判断是否超出范围,即检查索引是否小于单词长度。

以下是几种等价的写法,我们将使用最后一种,因为它更清晰:

# 写法一:直接使用德摩根定律后的逻辑
# while (not word[index] in "aeiou") and (index < len(word)):

# 写法二:使用 `not in` 使代码更易读
while index < len(word) and word[index] not in "aeiou":

请注意,我们调整了两个条件的顺序,将index < len(word)放在了前面。这是为了利用逻辑“与”and短路特性:如果第一个条件index < len(word)False(即索引已越界),Python将不会计算第二个条件word[index] not in "aeiou",从而避免引发IndexError错误。

循环体与特殊情况处理

在循环体内,我们将当前字母(已知是非元音)添加到缩写中,并递增索引。

while index < len(word) and word[index] not in "aeiou":
    abbreviation += word[index]
    index += 1

循环结束后,我们需要处理特殊情况:如果单词以元音开头,则缩写应为该元音。由于我们的循环在遇到元音时立即停止,如果第一个字母就是元音,循环体根本不会执行,abbreviation将保持为空字符串。因此,我们需要额外检查:

if word[0] in "aeiou":
    abbreviation = word[0]

最后,我们输出结果。

print("The new word is", abbreviation)

完整代码与测试

将以上所有部分组合起来,就得到了我们的完整程序:

word = input("Enter a word: ")
abbreviation = ""
index = 0

while index < len(word) and word[index] not in "aeiou":
    abbreviation += word[index]
    index += 1

if word[0] in "aeiou":
    abbreviation = word[0]

print("The new word is", abbreviation)

让我们用几个例子来测试一下:

  • 输入 scrap,输出 SCR
  • 输入 water,输出 W
  • 输入 apple,输出 a
  • 输入 shy(没有元音),输出 shy
  • 输入 book,输出 b

程序运行良好!

总结

本节课中,我们一起学习了如何创建一个生成单词缩写的程序。我们首先定义了缩写规则并制定了实现计划。在编码过程中,我们运用了while循环、in成员运算符以及逻辑运算的短路特性来安全地遍历字符串。我们还特别处理了单词以元音开头的边界情况。

你可以尝试修改缩写规则来进一步实验,例如:包含第一个元音,或者只使用单词中不重复的字母。思考一下代码需要如何调整。祝你编码愉快!

050:代码模式-累加器模式 🧮

概述

在本节课中,我们将要学习一种在编程中非常实用的代码模式——累加器模式。这是一种通用的代码模板,能帮助我们通过循环来收集或“累积”信息。我们将通过具体的例子来理解它的三个核心步骤,并学会如何识别和应用它。

什么是代码模式?

当你开始编写更复杂的代码时,将它们组织成更大的概念会很有帮助。在计算机科学中,我们称之为代码模式。代码模式是通用的、可重用的代码模板,能帮助我们实现多种不同的目标。它们也被称为软件设计模式。

这里的“模式”一词,类似于时装设计师为服装创建的缝纫纸样。连衣裙的纸样是测量布料和缝制方法的模板。但使用纸样的人可以选择布料,并决定如何修改或装饰这个纸样,以最好地满足他们的需求。

现在,你可能听说过“算法”这个词。代码模式和算法不是一回事。

  • 算法是一个特定的步骤序列,其目标是每次运行代码时,结果都相同。
  • 模式是一个模板。它是通用的,可以通过多种不同的方式实现,以达到不同的目标或结果。

累加器模式详解

让我们通过一个具体的例子——累加器模式——来深入理解。这是你在使用循环时经常会做的事情。

累加器模式包含三个步骤:

  1. 在循环开始前,初始化一个或多个变量。
  2. 在循环内部,更新这些变量。
  3. 在循环结束后,使用至少一个这些变量。

请注意这个模式的核心要素:至少有一个变量,并且有一个循环。代码在循环前、循环中和循环后都会对这个变量进行操作。

以下是区分这两个部分(发生了什么以及在哪里发生)的表格:

部分 描述 发生位置
初始化 创建并设置变量的初始值。 循环之前
更新 在每次循环迭代中修改变量的值。 循环内部
使用 利用循环后变量的最终值。 循环之后

一开始,你可能很难记住累加器模式的所有部分,这很正常。你可以随时回看这个视频。随着你更多地使用这个模式,你会越来越容易识别何时该使用它,并在阅读更多代码时发现它的身影。

示例一:计算从1到N的和

理论部分讲完了,让我们来看一个明确的例子。

以下代码用于计算从1到n的所有数字之和。我们来逐步分析它。

n = int(input("Enter a number: "))  # 第1行
result = 0  # 第2行
i = 0  # 第3行
while i < n:  # 第4行
    i = i + 1  # 第5行
    result = result + i  # 第6行
print(f"The sum of 1 to {n} is {result}")  # 第7行

代码执行流程:
假设用户输入了数字 3

  1. 第1行:获取用户输入,转换为整数并存入变量 n
  2. 第2行:创建变量 result 并赋值为 0
  3. 第3行:创建变量 i 并赋值为 0
  4. 第4行:进入 while 循环,检查条件 i < n (0 < 3)。条件为真,进入循环体。
  5. 第5行i 的值增加1,变为 1
  6. 第6行:将 i 的值 (1) 加到 result (0) 上,result 变为 1
  7. 循环体结束,回到第4行检查条件 i < n (1 < 3)。条件为真,再次进入循环。
  8. 第5行i 变为 2
  9. 第6行result (1) 加上 i (2),result 变为 3
  10. 回到第4行检查条件 i < n (2 < 3)。条件为真,进入循环。
  11. 第5行i 变为 3
  12. 第6行result (3) 加上 i (3),result 变为 6
  13. 回到第4行检查条件 i < n (3 < 3)。条件为假,跳过循环体
  14. 第7行:执行循环后的代码,打印结果:The sum of 1 to 3 is 6

总结代码功能:

  • 第1行:获取用户输入的 n
  • 第2、3行:初始化变量 resulti
  • 第4至6行:是一个循环。第4行检查条件;循环内部(第5、6行)更新变量 iresult
  • 第7行:打印最终结果。

识别累加器模式的三部分

现在,你能看出累加器模式的三个部分吗?试着将你正在学的内容应用起来,帮助大脑记忆。事实上,如果你答错了,那很好,因为“出错”的感觉会告诉你的大脑:这是需要记住的重要信息。

以下是三个部分以及它们对应的代码行:

  • 初始化部分:第2行和第3行。我们在这里创建并初始化了变量 resulti
  • 更新部分while 循环内的两行,即第5行和第6行。这些行更新了我们初始化的变量。
  • 使用部分:最后一行,第7行。代码在这里打印出了 result 的值。

你可能会想,那第1行呢?这一行是程序的一部分,帮助我们实现计算1到n之和的目标。但它不被视为模式的一部分,因为它没有创建在循环中被更新的变量。虽然我们使用了变量 n 并给它赋值,但在程序的其余部分我们从未改变 n 的值。如果我们包含第1行,那么我们讨论的更像是一个算法——一个计算1到n之和的特定步骤序列。本视频的重点是帮助你关注代码中的常见模式,比如累加器模式。

例如,如何修改这段代码来求从 mn 的和?你仍然会使用累加器模式,但不能再使用我们这里从1到n的完全相同的算法。

为什么叫“累加器”?

既然我们已经看了一个例子,让我们谈谈为什么这个模式使用“累加器”这个词,以及为什么我们要明确地教你这一点。

因为“积累”意味着收集信息。我指的信息不一定是代码始终保留循环遍历的所有内容(比如求1到10所有数字的和)。这种收集的信息也可能意味着:通过检查每个输入,并在一个变量中保留目前最大的数字,从而从用户输入的一系列数字中找到最大值。即使代码只保留了最大的数字,而没有保留任何其他数字,但代码已经积累了“所有其他数字都不是最大的”这一信息。

累加器模式的应用

至于为什么你应该关心这个模式,是因为累加器模式有非常多的用途。

这个模式可以帮助编写代码来:

  • 计算一个字母在字符串中出现的次数。
  • 删除字符串中的所有元音字母。
  • 反转字符串以检查它是否是回文。
  • 以及更多其他任务。

示例二:查找单词中字母表顺序最早的字母

让我们看另一个例子,展示累加器模式更复杂的用法。

以下代码用于查找一个单词中字母表顺序最早的字母。例如,代码会在“cat”中找到“a”,在“zoo”中找到“o”。

word = input("Enter a word: ")  # 第1行
earliest = word[0]  # 第2行
i = 0  # 第3行
while i < len(word):  # 第4行
    if word[i] < earliest:  # 第5行
        earliest = word[i]  # 第6行
    i = i + 1  # 第7行
print(f"The earliest letter is {earliest}")  # 第8行

代码解析:

  • 第1行:获取用户输入的单词。
  • 第2、3行:初始化变量 earliesti。变量 earliest 初始化为字符串 word 中索引0处的字母。计划是让变量 earliest 保存代码目前看到的字母表顺序最早的字母。所以不妨从字符串的第一个字母开始,稍后找到更早的字母时再更改它。变量 i 将用于索引到变量 word 中。
  • 第4至7行:是 while 循环。第4行检查 i 是否小于用户单词的长度。
  • 循环内部有一个 if 语句(第5行)。它检查变量 word 中索引 i 处的字母是否在变量 earliest 中的字母之前(按字母表顺序)。如果新字母更早,代码就将 earliest 的值更新为该字母(第6行),否则什么也不做。
  • if 语句之外,代码将 i 加1(第7行)。
  • 最后,第8行打印出变量 earliest 中的值。

再次识别模式

那么,哪些行属于累加器模式?这些行属于模式的哪个部分?这次,我甚至不会展示各个部分。你能记住它们吗?

以下是所有部分:

  • 第1行不是累加器模式的一部分。
  • 第2行和第3行初始化部分。
  • while 循环中的所有代码(第4至7行)属于累加器模式的更新部分。
  • 最后一行(第8行)再次是模式的使用部分。

请注意,我们的最后一行没有使用变量 i,这没关系。我们不必使用所有初始化的变量。我们需要变量 i 来帮助我们索引到字符串中,以实现我们的目标。

总结

本节课中我们一起学习了代码模式,特别是累加器模式。

  • 代码模式就像一个代码模板,它是通用的,可以在许多不同的情况下重用。
  • 代码模式不是算法,算法是当你运行时每次都能得到相同结果的特定步骤集合。
  • 累加器模式使用循环来收集关于某事物的信息。它包含三个部分:
    1. 在循环之前,初始化一个或多个变量。
    2. 在循环内部,更新这些变量。
    3. 在循环之后,使用至少一个这些变量。

现在,去尝试发现累加器模式吧!实际上,自从我们引入循环以来,我们一直在使用累加器模式。欢迎随时回顾以前的视频,尝试找出它们。

051:循环跟踪 🧐

在本节课中,我们将学习一种称为“循环跟踪”的系统化方法,用于逐步分析循环代码的执行过程。掌握此方法能帮助你理解代码的确切行为、调试错误,并加深对程序逻辑的理解。

概述

循环跟踪是一种通过模拟计算机执行过程,逐行分析循环代码的技术。通过记录变量值的变化,你可以避免依赖记忆,从而更准确地理解代码逻辑。接下来,我们将介绍循环跟踪的三个核心步骤,并通过一个示例进行演示。

循环跟踪步骤

以下是进行循环跟踪的三个系统化步骤。

步骤一:识别变量与表达式

首先,找出在循环中可能发生变化的所有变量和表达式。这通常包括循环变量、循环条件,以及在循环体内被修改的变量或重要的条件判断。

步骤二:创建跟踪表格

其次,创建一个表格来记录你在步骤一中找到的所有项目。表格的第一列应为循环变量,第二列是循环条件,后续各列则用于记录其他需要跟踪的变量和表达式。

步骤三:逐次迭代执行

最后,开始逐步执行循环。在每次迭代开始前,将当前所有变量的值复制到表格的新一行中。然后,逐行执行循环体内的代码,并根据执行结果实时更新表格中对应变量的值。

示例演示

现在,让我们通过一段“神秘代码”来实践上述步骤。

s = input()
i = 0
result = ""
while i < len(s):
    if s[i] not in "AEIOU":
        result = result + s[i]
    i = i + 1
print("result:", result)

步骤一:识别跟踪目标

分析这段代码,我们需要跟踪以下四项内容:

  1. 循环变量 i
  2. 循环条件 i < len(s)
  3. 在循环中被更新的变量 result
  4. 决定是否更新 resultif 条件 s[i] not in "AEIOU"

步骤二:创建跟踪表格

根据识别出的目标,我们创建如下表格:

i (循环变量) i < len(s)? (循环条件) result s[i] not in "AEIOU"? (if条件)

步骤三:逐步执行与记录

假设用户输入字符串 "make"。让我们开始逐步跟踪。

初始化:

  • 执行 s = input()s 被赋值为 "make"
  • 执行 i = 0
  • 执行 result = ""

第一次迭代:

  • 复制变量值:在表格第一行填入 i=0, result=""
  • 检查循环条件0 < len("make") (即 0 < 4) 为 True,进入循环体。
  • 检查if条件s[0]'m''m' not in "AEIOU"True
  • 执行if代码块result = "" + 'm'result 更新为 "m"
  • 更新循环变量i = 0 + 1i 更新为 1
  • 迭代结束,返回循环条件。

此时表格更新为:

i i < len(s)? result s[i] not in "AEIOU"?
0 True "" True
1 "m"

第二次迭代:

  • 复制变量值:在第二行填入 i=1, result="m"
  • 检查循环条件1 < 4True
  • 检查if条件s[1]'a''a' not in "AEIOU"False
  • 跳过if代码块:不执行 result = result + s[i]
  • 更新循环变量i = 1 + 1i 更新为 2

表格更新为:

i i < len(s)? result s[i] not in "AEIOU"?
0 True "" True
1 True "m" False
2 "m"

第三次迭代:

  • 复制变量值:填入 i=2, result="m"
  • 检查循环条件2 < 4True
  • 检查if条件s[2]'k''k' not in "AEIOU"True
  • 执行if代码块result = "m" + 'k'result 更新为 "mk"
  • 更新循环变量i = 2 + 1i 更新为 3

表格更新为:

i i < len(s)? result s[i] not in "AEIOU"?
0 True "" True
1 True "m" False
2 True "m" True
3 "mk"

第四次迭代:

  • 复制变量值:填入 i=3, result="mk"
  • 检查循环条件3 < 4True
  • 检查if条件s[3]'e''e' not in "AEIOU"False
  • 跳过if代码块
  • 更新循环变量i = 3 + 1i 更新为 4

表格更新为:

i i < len(s)? result s[i] not in "AEIOU"?
0 True "" True
1 True "m" False
2 True "m" True
3 True "mk" False
4 "mk"

循环结束:

  • 复制变量值:填入 i=4, result="mk"
  • 检查循环条件4 < 4False。循环终止。
  • 执行 print("result:", result),输出:result: mk

完整跟踪表格如下:

i i < len(s)? result s[i] not in "AEIOU"?
0 True "" True
1 True "m" False
2 True "m" True
3 True "mk" False
4 False "mk"

代码功能解析

通过循环跟踪,我们可以清晰地看出这段代码的功能:它接收一个用户输入的字符串,并打印出该字符串中所有非元音字母(AEIOU)的字符。
例如,对于输入 "Hello World!",代码将输出 result: Hll Wrld!

总结

本节课我们一起学习了循环跟踪的方法。我们首先介绍了其三个核心步骤:识别变量与表达式创建跟踪表格逐次迭代执行。随后,我们通过一个完整的示例演示了如何应用这些步骤来分析一段代码,并最终理解了该代码的功能是过滤字符串中的元音字母。掌握这项技能将极大地帮助你在未来阅读、调试和理解复杂的循环代码。

052:循环跟踪演示

在本节课中,我们将学习如何通过“循环跟踪”的方法来逐步理解代码的执行过程。我们将通过一个具体的例子——寻找一个数字中的最大数位——来演示这一过程。


概述

循环跟踪是一种手动模拟代码执行的方法,尤其适用于理解循环和条件语句。通过创建跟踪表并逐步记录变量的变化,我们可以清晰地看到程序每一步的状态。本节我们将对一个寻找数字中最大数位的代码进行完整的跟踪演示。

代码分析

首先,我们来看待分析的代码。它的目标是找出一个给定数字中的最大数位。

number = 29612
largest_digit = 0
while number != 0:
    digit = number % 10
    if digit > largest_digit:
        largest_digit = digit
    number = number // 10
print(largest_digit)

上一节我们介绍了循环跟踪的概念,本节中我们来看看如何将其应用到这段具体的代码上。

第一步:确定需要跟踪的变量

在开始跟踪之前,我们需要确定在循环执行过程中哪些变量和条件是关键。以下是我们的分析:

  • number: 这是循环的条件变量(while number != 0),并且在循环体内被更新(number = number // 10)。必须跟踪。
  • largest_digit: 这是我们要找的目标值,在if语句中被更新。必须跟踪。
  • 循环条件 (number != 0): 决定循环是否继续。必须跟踪。
  • digit: 在每次循环中计算得出,并用于与largest_digit比较。为了理解比较逻辑,需要跟踪。
  • if条件 (digit > largest_digit): 决定是否更新largest_digit。跟踪其布尔值有助于理解程序分支。

第二步:创建跟踪表

根据第一步的分析,我们创建以下跟踪表。表格的列对应我们需要跟踪的各个项目。

number number != 0? digit digit > largest_digit? largest_digit

第三步:逐步执行与记录

现在,我们开始模拟Python解释器,一行一行地执行代码,并将每一步的状态记录到表中。

初始赋值(循环前)
执行前两行赋值语句后,变量的状态是:

  • number = 29612
  • largest_digit = 0

第一次循环迭代

  1. 进入while循环,检查条件 number != 0。29612不等于0,条件为 True
  2. 执行 digit = number % 1029612 % 10 的结果是 2digit 变为2。
  3. 执行 if 条件判断 digit > largest_digit2 > 0True
  4. 进入if代码块,执行 largest_digit = digitlargest_digit 从0更新为 2
  5. 执行 number = number // 1029612 // 10 的结果是 2961

将本次迭代的结果填入表格第一行:

number number != 0? digit digit > largest_digit? largest_digit
29612 True 2 True 2

第二次循环迭代

  1. 回到循环顶部,检查条件 number != 0。2961不等于0,条件为 True
  2. 执行 digit = number % 102961 % 10 的结果是 1
  3. 执行 if 条件判断 digit > largest_digit1 > 2False
  4. if代码块被跳过。
  5. 执行 number = number // 102961 // 10 的结果是 296

将本次迭代的结果填入表格第二行:

number number != 0? digit digit > largest_digit? largest_digit
2961 True 1 False 2

第三次循环迭代

  1. 条件 296 != 0True
  2. digit = 296 % 10,结果为 6
  3. if 6 > 2True
  4. 执行 largest_digit = 6
  5. number = 296 // 10,结果为 29
number number != 0? digit digit > largest_digit? largest_digit
296 True 6 True 6

第四次循环迭代

  1. 条件 29 != 0True
  2. digit = 29 % 10,结果为 9
  3. if 9 > 6True
  4. 执行 largest_digit = 9
  5. number = 29 // 10,结果为 2
number number != 0? digit digit > largest_digit? largest_digit
29 True 9 True 9

第五次循环迭代

  1. 条件 2 != 0True
  2. digit = 2 % 10,结果为 2
  3. if 2 > 9False
  4. if代码块被跳过。
  5. number = 2 // 10,结果为 0

number number != 0? digit digit > largest_digit? largest_digit
2 True 2 False 9

循环结束

  1. 回到循环顶部,检查条件 number != 00 != 0False
  2. 循环终止,跳转到循环体之后的代码。
  3. 执行 print(largest_digit),输出最终结果 9

总结与建议

本节课中我们一起学习了如何对循环代码进行逐步跟踪。通过创建跟踪表,我们清晰地看到了变量number如何被逐步分解,以及largest_digit如何被更新为最大值9的过程。

最后,提供两点建议:

  1. 个性化跟踪: 在实际操作中,你不需要记录所有细节。可以根据代码逻辑和你自己的理解难点,选择最关键的部分进行跟踪。本次演示展示了完整细节,旨在帮助你找到适合自己的方法。
  2. 使用辅助标记: 在代码旁使用箭头或圆点标记当前执行位置,在表格中高亮刚刚变化的值,这些视觉辅助工具能有效帮助你理清执行步骤和状态变化,方便回溯和检查。

掌握循环跟踪技能,能极大地帮助你理解复杂的代码逻辑,尤其是在调试和阅读他人代码时。

053:错误类型 🐛

在本节课中,我们将学习编程中常见的三种错误类型:语法错误、运行时错误和逻辑错误。理解这些错误是调试代码、提升编程技能的关键一步。


概述

编程过程中遇到错误是普遍且正常的经历。即使是经验丰富的程序员也会犯错。错误信息是计算机向我们提供的宝贵反馈,帮助我们理解代码的问题所在。本节课,我们将通过一个创建聊天机器人“Chip”的示例,学习如何识别和处理不同类型的错误。


语法错误:代码的“语法”问题

上一节我们介绍了错误是学习的机会。本节中,我们来看看第一种错误——语法错误。语法错误类似于写作中的语法或拼写错误,它违反了Python的语法规则,导致代码无法被正确解析和执行。

以下是常见的语法错误原因:

  • 缺少括号、引号或冒号。
  • 错误的缩进。
  • 拼写错误的关键字或变量名。

在代码编辑器中,语法错误通常会用红色波浪线标出。例如,在我们的chip.py代码中,第5行的while循环条件后缺少了一个冒号。

while question != "Q"  # 错误:缺少冒号
    question = input(message)

当我们尝试运行这段有语法错误的代码时,Python解释器会停止并显示错误信息。阅读错误信息时,建议从最后一行开始向上看:

  1. 错误类型:最后一行会明确指出是 SyntaxError
  2. 错误位置:错误信息会指出发生错误的文件名和行号(例如 File "chip.py", line 5)。
  3. 错误提示:在出错的行下方,会有一个 ^ 符号指向可能出错的位置。

修复这个错误很简单,只需在while条件后加上冒号即可。

while question != "Q":  # 正确:添加了冒号
    question = input(message)

运行时错误:执行中的意外

修复语法错误后,代码可以开始运行了。然而,在程序执行过程中,我们可能会遇到第二种错误——运行时错误(也称为异常)。这类错误在代码语法正确,但执行到某一步操作非法时发生。

在我们的示例中,修复语法错误后再次运行程序,输入一个问题后,遇到了一个ValueError

ValueError: empty range for randrange() (1, 1, 0)

这个错误信息看起来有些复杂,但我们可以逐步分析:

  1. 错误类型ValueError,表示使用了无效的值。
  2. 错误描述empty range for randrange() (1, 1, 0),提示randrange()函数的参数范围是空的。
  3. 调用栈:错误信息上方的多行文本显示了函数调用的链条,帮助我们追踪错误源头。我们需要找到指向我们自己代码文件(chip.py)的那一行,它指示了错误可能发生的具体位置(例如第8行)。

通过分析,我们发现错误源于第8行调用random.randrange(1, 0)randrange(start, stop)要求start参数小于stop参数。这里(1, 0)是一个无效的范围。我们应该将其改为(0, 1),以随机生成0或1。

# 错误
random_number = random.randrange(1, 0)
# 正确
random_number = random.randrange(0, 1)

除了ValueError,还有其他常见的运行时错误:

  • IndexError:尝试访问列表、字符串等序列中不存在的索引。
  • NameError:使用了未定义的变量名或函数名。
  • TypeError:操作或函数应用于不适当类型的对象,例如尝试将字符串和整数相加。

逻辑错误:代码“静默”地出错

修复运行时错误后,我们的程序似乎能正常运行了。Chip可以回答问题,程序也不会崩溃。但是,你可能会发现Chip每次都回答“go for it”,永远不会说“maybe later”。这就是第三种错误——逻辑错误。

逻辑错误是最难发现和调试的错误,因为:

  • 程序不会崩溃。
  • 没有任何错误信息。
  • 计算机严格地执行了你写的指令,但它并不知道你原本的意图是什么。

问题出在我们的条件判断逻辑上。代码第9行原本是 if random_number >= 0:,由于random_number只能是0或1,这个条件永远为True,所以永远只会执行第10行的打印语句。

我们需要将条件改为判断random_number是否等于0,这样才能让两个分支有均等的机会执行。

# 错误逻辑:条件永远成立
if random_number >= 0:
    print("Chip says: Go for it!")
else:
    print("Chip says: Maybe later...")

# 正确逻辑:平等判断
if random_number == 0:
    print("Chip says: Go for it!")
else:
    print("Chip says: Maybe later...")

总结

本节课中,我们一起学习了编程中的三种主要错误类型:

  1. 语法错误 🚫:在代码运行前就能发现,由违反Python语法规则导致,编辑器通常会用红色波浪线提示。
  2. 运行时错误 ⚠️:在代码执行过程中发生,程序会停止并抛出异常信息(如ValueError, TypeError)。我们需要仔细阅读错误信息来定位问题。
  3. 逻辑错误 🤔:程序能运行且不报错,但产生的结果与预期不符。这是最难调试的错误,需要仔细检查代码的业务逻辑。

遇到错误时请不要气馁,每一次解读和修复错误的过程,都是你成长为更优秀程序员的阶梯。保持耐心,持续练习,享受编码带来的挑战和乐趣吧!

054:调试策略 🐛

在本节课中,我们将学习如何系统地发现和修复程序中的错误,即“调试”。调试是编程的核心技能之一,掌握有效的策略能帮助你更快地解决问题。

课程概述

你已经编写了一些有趣的程序。你可能运行过这些程序几次,并尝试了不同的输入,然后发现它们并未按预期工作。程序可能表现出意料之外的行为,或者你遇到了错误信息。发现并修复错误不仅是编程的核心部分,对于构建你的编程和调试技能也至关重要。

但你是否想过,为什么我们称这些错误为“Bug”(虫子)?要回答这个问题,让我们先探索一个关于计算机科学先驱——格蕾丝·布鲁斯特·默里·霍珀的精彩故事。

“Bug”一词的起源 🦋

格蕾丝·霍珀是一位杰出的程序员和富有远见的思想家。她的众多贡献之一是创造了Fmatic,这是第一种使用类似英语命令的编程语言。因此,编程对所有人来说都变得更容易、更易接近。

事实上,图中她正拿着Cobal编程语言的说明书,该语言深受Fmatic的影响。为了纪念她,世界上最大的女性计算领域盛会——格蕾丝·霍珀庆典就是以她的名字命名的。

那么,格蕾丝·霍珀与“Bug”这个术语有何关联?她帮助推广了这个词。“Bug”是工程师用来描述问题或故障的术语,但在工程领域之外并不广为人知。

在1945年,格蕾丝·霍珀和其他工程师正在研究哈佛马克一号计算机。他们发现计算机在计算中出现了异常错误。当他们打开计算机检查硬件时,发现里面有一只真正的飞蛾导致了故障。这一事件被记录在日志本中,正如你所见,那只真实的飞蛾被贴在了本子上,旁边还附有一句玩笑话:“发现Bug的第一个实际案例”,尽管“Bug”一词当时已在工程领域使用。所以,这是第一次在计算机中发现一只字面意义上的“虫子”导致了问题。

这个故事广为流传,使得“Bug”成为每个人谈论计算机错误时的常用术语。这个故事凸显了发现和修复错误的挑战性,有时甚至是不可预测性。在本视频中,我们将讨论如何应对这一挑战。

系统性的调试策略

有时,验证你的程序是否确实按预期工作可能很困难。以下是你可以遵循的几个步骤,来测试代码中是否存在隐藏的错误。

第一步:列出主要使用场景

一个在一种情况下能运行的程序,未必在所有情况下都能运行。这一步与你最初编写程序时的活动密切相关,特别是侧重于验证和调试活动。在这些活动中,你可以检查代码是否完成了你最初的计划,以及是否达到了你最初的设计目标。

在考虑不同用途时,请专注于使用程序的每个功能。这有助于验证你的程序计划或实现是否正确。

以下是创建场景列表时需要考虑的方面:

  • 功能测试:例如,如果你正在创建一个计算器程序,一种情况是检查程序是否正确相加数字。另一种情况是测试程序是否遵循正确的运算顺序。
  • 用户多样性:考虑用户将如何使用你的程序,以及它是否符合你的设计目标。确认程序的设计与确认计划同样重要。
  • 可访问性:如果你的程序旨在让残障人士也能使用,请考虑他们可能会如何使用你的程序。例如,考虑指令对于行动不便的人来说是否易于遵循,或者文本对于视力障碍或色盲用户来说是否清晰。
  • 语言与文化:如果你的程序将被非英语使用者或母语非英语的人使用,请考虑程序输出的语言和措辞选择会如何影响这些用户。

创建不同用户的角色档案可能有助于这一步。你可以为每个角色档案设计不同的场景。同时,务必考虑你编写程序的目标,并构思能帮助你确定是否达成目标的场景。例如,如果你的目标是让人微笑,就创建一两个能帮助你判断用户是否微笑的场景。

通过考虑各种场景,包括用户的多样化需求,你的程序将变得更包容、用户友好、有效和正确。

第二步:测试每个场景

现在你有了想要考虑的场景列表,你可以使用列表中每个场景对应的输入来测试你的程序。

这里的测试意味着运行程序,输入你列表中的输入,然后检查输出是否正确。务必同时考虑不正确或意外的输入,以及你希望程序在给定这些输入时如何表现。

回到我们计算器的例子。预期的输入可能是数字,而意外的输入可能是字母。这一步非常重要,因为它帮助你了解你的程序在面对可能遇到的各种输入时的健壮性。我们强烈建议你不要跳过这一步。

第三步:记录错误行为

假设,就像格蕾丝·霍珀和她的团队一样,你在测试程序时遇到了不正确的输出。如果发生这种情况,请记下导致错误行为的输入。

这很像格蕾丝·霍珀的日志本,只不过这次,你的“飞蛾”是虚拟的。同时,务必写下如果程序正确运行,它本应如何表现。这在后续会很有用。

第四步:开始调试

你已经收集了大量信息,现在是时候开始调试了,逐一处理你记录下的每个问题。

以下是调试时可以采用的几种方法:

  • 关注错误信息:调试过程中遇到错误时,请密切关注错误信息。这些信息可以为你提供关于代码中可能存在问题原因的宝贵线索。
  • 比较预期与实际:回顾上一步中发生情况的记录。将实际发生的情况与应该发生的情况进行比较。这种比较是形成关于代码可能出了什么问题的想法的关键。
  • 检查输入:有时代码的输出并不能清楚地指示问题所在。在这种情况下,检查错误发生时使用的输入。问问自己,这个输入是否与导致错误的其他输入不同。通常,问题的原因与输入有关,而不是输出。
  • 使用打印语句:使用print语句会非常有帮助。不要犹豫,打印变量值或任何可能帮助你理解代码在每一步做什么的信息。事实上,print语句是我个人最喜欢的调试策略。
    # 示例:在关键步骤打印变量值
    def calculate_sum(a, b):
        print(f"调试信息:收到 a={a}, b={b}") # 打印输入
        result = a + b
        print(f"调试信息:计算结果 result={result}") # 打印结果
        return result
    
  • 手动追踪代码:如果你仍然不确定错误是什么,尝试手动追踪你的代码。这意味着拿出一张纸和一支笔,一步一步地跟随代码执行。这可能很耗时,但有时是找到错误根源的最佳方法。这样做可能为你节省大量时间和挫败感。同时,这也是一项极好的学习活动,能帮助你掌握这些编程概念。

总结与回顾

本节课中,我们一起学习了系统性的调试策略。让我们回顾一下步骤:

  1. 列出场景:写下用户可能尝试使用你的程序的所有重要场景。
  2. 测试场景:通过使用与每个场景对应的预期和意外输入来运行程序,测试每个场景。
  3. 记录问题:记下你注意到的任何错误行为,以及任何有助于你后续调试代码的有用信息。
  4. 逐一调试:利用手头的所有这些信息,你可以继续进行调试,尝试逐一解决每个问题。

请记住,这些只是建议的步骤。调试可以通过许多不同的方式完成,你可能会有更适合你自己的方法。如果你有任何独特的策略或技巧,我们鼓励你在课程论坛上分享。你的见解可能对课程中的其他人非常有益。

055:调试游戏第一部分演示 🎮

在本节课中,我们将学习如何为一个字母猜词游戏制定调试计划。我们将通过创建一系列“计划场景”和“设计场景”来系统地验证游戏逻辑和设计目标,这是调试复杂程序前的关键准备步骤。

游戏玩法概述

我们首先来了解这个字母猜词游戏的基本玩法。游戏目标是逐一猜出一个秘密单词中的每个字母。当所有字母都被猜出后,游戏结束。

运行游戏时,首先会看到游戏说明:猜出一个四字母单词中的每个字母。接着,程序会提示你猜测秘密单词中的下一个字母。由于尚未猜出任何字母,当前需要猜测的是单词的第一个字母。

例如,如果秘密单词以字母“A”开头,你可以输入“A”。如果猜测错误,程序会提示“再试一次”,并且“当前已猜出的单词”部分不会更新。如果猜测正确,例如输入“B”并猜对了第一个字母,那么“当前已猜出的单词”部分会显示“B”,表示第一个字母已被正确猜出。

随后,游戏会提示猜测第二个字母。如果猜对(例如字母“I”),已猜出部分会更新为“BI”。如果猜错(例如字母“C”),已猜出部分保持不变。继续这个过程,直到猜出所有字母(例如“B”、“I”、“R”、“D”)。当最后一个字母被猜出时,游戏会显示完整的单词并给出成功信息:“你找到了隐藏的单词!”。

创建调试场景

在后续视频中,我们将分析一段试图实现此游戏的代码,但该代码包含许多错误。为了有效地找出并修复这些错误,我们首先需要坐下来创建一些游戏可能出现的“场景”。创建这些场景将极大地帮助调试过程。

以下是创建场景时的一些建议:

  • 建议创建多个场景。场景越多,越容易判断程序是否正确。
  • 建议从简单的场景开始,然后逐渐增加复杂度。
  • 没有唯一正确的方法来编写这些场景,任何有效的方法都可以。我将展示一种特定方式,但你可以自由调整策略。

我将使用文本编辑器来帮助我们创建和记录这些场景。我喜欢为场景编号,这有助于保持条理。

计划场景

计划场景用于验证游戏程序逻辑的正确性。每个场景都遵循相同的模板,包含以下部分:

  • 目标:描述创建该场景的目的,即要验证或测试程序的哪部分功能。
  • 给定:描述场景发生时的背景信息,即已知为真的条件。
  • :描述在场景中发生的具体操作或事件。
  • 然后:描述在这些操作发生后,程序应该做出的正确反应或行为。

请注意,“然后”部分描述的是程序应有的正确行为,而非其实际行为。这个区别在调试时至关重要。

计划场景 1:处理一次正确的猜测

  • 标题:处理一次正确的猜测
  • 目标:测试程序是否能正确处理一次正确的猜测。
  • 给定:用户刚刚开始游戏。
  • :用户被要求输入第一个字母,并输入了正确的字母。
  • 然后:程序应确认猜测正确,显示更新后的已猜出字母列表,并提示用户输入下一个字母。

计划场景 2:处理一次错误的猜测

上一节我们介绍了处理正确猜测的场景,本节中我们来看看当用户猜错时的情况。

  • 标题:处理一次错误的猜测
  • 目标:测试程序是否能正确处理一次错误的猜测。
  • 给定:用户已正确猜出一个字母,现在正尝试猜测秘密单词的第二个字母。
  • :用户被要求输入第二个字母,并输入了一个错误的字母。
  • 然后:程序应指示用户再试一次,并且推进“猜测索引”。(“猜测索引”用于跟踪当前正在猜测的是第几个字母。如果第二个字母猜错,应让玩家继续尝试猜第二个字母,而不是跳到第三个字母。)

计划场景 3:在游戏后期处理一次正确的猜测

接下来,我们考虑一个更复杂的场景,涉及多次猜测。

  • 标题:在游戏后期处理一次正确的猜测
  • 目标:测试程序在游戏后期是否能正确处理一次正确的猜测。
  • 给定:经过一系列(一次或多次)猜测后,用户当前正尝试猜测秘密单词的第二个字母。
  • :用户被要求输入第二个字母,并输入了正确的字母。
  • 然后:程序应确认猜测正确,显示更新后的已猜出字母列表,并提示用户输入下一个字母。

计划场景 4:赢得游戏

最后,我们需要一个场景来测试游戏的胜利条件。

  • 标题:赢得游戏
  • 目标:检查是否有可能赢得游戏。
  • 给定:用户从游戏开始玩起。
  • :通过一系列正确和错误的猜测,用户最终猜出了秘密单词中的所有字母。
  • 然后
    • 每次正确猜测后,用户应看到指示正确的消息。
    • 每次错误猜测后,应出现“再试一次”的消息。
    • 每次猜测后,用户应能看到进度,即显示已正确猜出的字母。
    • 在最后一次正确猜测后,游戏结束,用户应看到消息:“你找到了隐藏的单词!”。

当然,你还可以构思更多计划场景,例如混合正确和错误猜测的更复杂序列,或者玩家始终无法猜出单词的情况。创建自己的场景是非常好的练习,也是调试时非常有效的工具。

设计场景

除了验证程序逻辑的“计划场景”,我们还可以创建“设计场景”来验证游戏的设计初衷和目标是否得以实现。

设计场景 1:非英语母语使用者

  • 标题:非英语母语使用者
  • 目标:测试非英语母语使用者是否能理解规则并完成游戏。
  • 给定:有一位非英语母语使用者即将开始游戏。
  • :用户阅读游戏说明。
  • 然后:游戏说明、反馈信息和秘密单词本身所使用的语言应简单、清晰,并且没有习语表达。这意味着语言应该是非英语母语使用者能够识别和理解的,而不是特定于某种文化的。

设计场景 2:用户体验与趣味性

另一个重要的设计目标是确保游戏具有挑战性且引人入胜。

  • 标题:用户体验与趣味性
  • 目标:确保游戏在具有挑战性的同时,也能让用户感到愉快。
  • 给定:有一组背景(如年龄、技能水平)不同的用户准备玩这个猜词游戏。
  • :这些用户使用不同的秘密单词进行多轮游戏。
  • 然后:当被问及时,用户应报告感到娱乐和投入。

与计划场景一样,你也可以构思更多设计场景,例如测试游戏对不同年龄段的适应性,或者评估其重复可玩性。创建符合你自身目标和期望的场景总是有益的,场景多多益善。

总结

本节课中我们一起学习了为字母猜词游戏制定系统化调试计划的方法。我们首先回顾了游戏的基本玩法,然后重点介绍了如何创建“计划场景”和“设计场景”。计划场景(如处理正确/错误猜测、赢得游戏)帮助我们验证程序逻辑;设计场景(如面向非母语用户、确保趣味性)则帮助我们检验游戏设计目标是否达成。通过预先详细规划这些测试用例,我们能为后续的实际代码调试工作打下坚实基础,更高效地定位和修复程序中的错误。

056:调试游戏第二部分演示 🐛

在本节课中,我们将学习如何调试一个字母猜词游戏。我们将分析一段包含错误的示例代码,通过运行程序、检查错误信息并对照预设的测试场景,来逐步定位和修复代码中的问题。


游戏代码结构与初始状态

上一节我们了解了字母猜词游戏的预期运行逻辑。本节中,我们来看看一段试图实现该游戏但包含错误的示例代码。

代码的第2至4行创建了三个变量并赋予默认值。这三个变量对于维护游戏每一轮的状态至关重要。

以下是这些变量:

  • secret_word: 包含玩家需要猜测的密词。在本例中,密词是 "bird"
  • letters_guessed: 默认值为空字符串。它将包含玩家已正确猜中的密词中的字母。
  • guess_index: 包含玩家当前尝试猜测的字母在密词中的索引位置。游戏开始时,它被赋值为 0,表示玩家正尝试猜测密词的第一个字母(即 "b")。

接着,程序向玩家展示一个长的打印语句,本质上是告诉玩家去猜一个四字母单词中的每个字母。

我们计算了密词的长度。然后是一个 while 循环,其条件是只要 guess_index 小于或等于密词的长度,循环就会继续。

第12行创建了变量 instructions,并为其分配了字符串 "guess the next letter"。因此,instructions 将包含玩家看到的指令。

第13行,我们显示第12行创建的指令,并要求用户输入,将用户输入存储在变量 guess 中。因此,变量 guess 将包含玩家在该轮猜测的字母。

接下来是一个 if-else 语句。if 语句的条件是检查玩家猜测的字母是否确实与密词中位于 guess_index 位置的字母匹配。

如果这两个字母相同,则执行 if 代码块:我们更新 letters_guessed,将玩家正确猜中的字母添加到该变量中,使其成为一个更长的字符串。这样,letters_guessed 将不断包含玩家已猜中的所有正确字母。然后打印 "correct"

如果玩家未能成功猜中该字母,则执行 else 代码块,打印 "Try again"

在该轮结束时,我们打印 "Word so far: ",后跟玩家已正确猜中的字母。然后,我们用一个空的 print() 语句结束该轮,这本质上只是打印一个空行,有助于分隔游戏中的每一轮,使其更易于阅读。

最后,在游戏完成所有轮次的循环后,终端将打印 "You found the hidden word!"


定位并修复语法错误

你可能已经注意到,第10行代码下的红色波浪线暗示可能存在语法错误。也许你已经知道错误在哪里。如果你想尝试发现这个语法错误,可以暂停视频,看看是否能找到它。可能不止一个错误。

让我们运行这个程序,看看我们的怀疑是否正确。它真的是语法错误吗?

我在终端中运行此程序。看起来我们是对的,代码中确实存在语法错误,正如红色波浪线所暗示的那样。

更具体地说,你会注意到这里有一个脱字符号 ^,指向我们 while 循环的开头。因此,语法错误可能与我们的 while 循环条件有关。

错误信息还告诉我们,程序在第10行崩溃,具体是在我们当前正在处理的文件 guessword.py 中。让我们转到第10行,那里似乎有一个 while 循环头,也许语法错误就在那里。

查看第10行,看看是否能发现语法错误。它写的是 guess_index <= len(secret_word),我们有开括号和闭括号,还有冒号。这行代码看起来是正确的,我认为那里没有语法错误。

既然在第10行找不到语法错误,我们可以在之前的代码行中搜索这个语法错误。至少我们知道错误不会在第10行之后,因为程序在第10行就崩溃了。所以错误可能在1到10行之间,至少我们知道这么多。

让我们看看第7行,再往上一行代码,也许语法错误在那里。这里有一个字符串,我们使用字符串拼接将三个不同的字符串组合在一起。这里有 secret_word,它是一个字符串。我们计算长度,它会返回一个整数。然后我们将该整数转换为字符串,这应该可以使用字符串拼接。所以那里不应该有问题。

检查引号:每个开引号都有对应的闭引号吗?看起来是的,所以这不是问题。也许是括号有问题:每个开括号都有对应的闭括号吗?哦,看起来在这个很长的打印语句末尾缺少一个闭括号。

请注意,一旦我添加了那个结束括号,第10行的波浪线就消失了。这通常暗示语法错误现在已经消失了。

让我们再次运行这段代码,看看错误是否已修复。看起来确实消失了,我们不再收到相同的错误消息。

现在我们看到的是:"Guess each letter in a four letter word.""guess the next letter"。我们修复了第一个错误。


使用场景测试游戏逻辑

现在,让我们拿出在之前视频中创建的测试场景,尝试执行我们的第一个场景,看看是否能在这个游戏中找到更多错误。

以下是我们之前视频中使用的场景列表:

  • 场景1: 用户刚开始游戏,第一次猜测,且猜测正确。
  • 场景2: 用户已猜中第一个字母,第二次猜测错误。
  • ...(其他场景)

第一个场景的 Then 部分详细描述了如果程序行为正确,它应该做什么。在这种情况下,程序应确认猜测正确,显示更新后的已猜字母列表,然后要求用户输入下一个字母。

让我们自己来模拟这个场景,看看我们的程序是否真的以这种预期的方式运行。这里我们刚开始游戏,我们将进行第一次猜测。因为密词的第一个字母是 "b",所以我们将输入字母 "b",然后按回车键。我们可以看到程序对我们第一次猜测的输出。

再次查看我们的场景。我们可以检查程序的实际行为是否与 Then 部分详述的预期行为匹配。第一件事是程序应确认猜测正确,它做到了吗?是的,终端显示了字符串 "correct"

然后,它应显示更新后的已猜字母列表,这正是 "Word so far: " 所做的。"Word so far: " 只显示字母 "b",因为这是我们目前唯一正确猜中的字母。

最后,它应该要求用户输入下一个字母,这正是终端底部所做的。所以这个场景实际上没有检测到我们程序中的任何错误行为,这实际上是件好事。

现在,我将在这个场景中写一个新的部分,叫做 Actual。这个部分将详细描述程序实际做了什么。在我们的例子中,程序匹配了预期行为。

为什么我们要在每个场景中写这个部分?因为当你拥有越来越多的场景时,保持条理很重要,跟踪哪些场景你已经验证并在程序中检查其工作正常,哪些场景你尚未处理将变得更加困难。Actual 部分可以帮助你跟踪这些,帮助你保持条理。在调试方面,它也很有用,因为你明确区分了程序实际做了什么与你打算让它做什么,这种区分在调试时极其重要。这就是你希望在场景中包含 Actual 部分的众多原因之一。

让我们继续看第二个场景。第二个场景建立在第一个场景之上。在这个新场景中,你已经猜中了密词的第一个字母(在我们的例子中,是字母 "b")。我们当前正试图猜测第二个字母(在我们的例子中,是字母 "i"),但我们将做出一个错误的猜测。我们将输入字母 "z" 而不是 "i",使其成为一个错误的猜测。

现在让我们输入字母 "z"。查看场景2的 Then 部分。它说程序应指示用户再试一次。它实际做到了吗?我们可以看到终端确实显示了字符串 "Try again",这很好。

它还说 guess_index 不应前进。如果你看 "Word so far: ",它仍然只显示字母 "b"。它没有显示字母 "i" 或密词中的任何其他字母。所以我们仍然在尝试猜测密词的第二个字母。这实际上正是它应该做的。因此,这第二个场景似乎也没有揭示任何错误的程序行为。

所以在这个场景的 Actual 部分,我也会写上 "matched the expected behavior",从而更新我们的笔记。


总结与预告

本节课中,我们一起学习了如何调试一个存在缺陷的字母猜词游戏代码。我们首先修复了一个因缺少括号导致的语法错误。接着,我们利用预设的测试场景(场景1和场景2)来验证游戏在正确猜测和错误猜测两种情况下的基本逻辑,目前程序行为符合预期。

在未来的视频中,我们将继续查看其余的场景,我可以告诉你,这些场景将揭示一些我们目前尚未发现的其他意外行为。我鼓励你尝试运行这段代码,看看是否能发现我们尚未揭示但将在未来视频中揭示的错误。总的来说,享受调试的乐趣吧!

057:调试游戏第三部分演示 🐛

在本节课中,我们将学习如何调试一个字母猜词游戏。我们将通过分析一个具体的场景(玩家正确猜出第二个字母),来定位并修复代码中的逻辑错误。我们将使用打印语句作为调试工具,并学习如何系统地记录和验证程序行为。


在之前的视频中,我们分析了一个字母猜词游戏,玩家需要猜出一个秘密单词中的每个字母。在我们的示例中,秘密单词是“bird”。这段代码包含多个错误。为了验证代码是否能正常工作,我们创建了一系列测试场景。我们已经在前一个视频中查看了场景1和2,现在我们将继续分析场景3。

场景3描述了玩家已经正确猜出秘密单词的第一个字母,现在正在尝试猜第二个字母,并且他们猜对了。

让我们从头开始运行游戏,并尝试重现场景3。程序会要求我们猜一个四字母单词(即“bird”)中的每个字母。我们先猜字母“B”,这是秘密单词的第一个字母。程序提示猜对了,并显示当前已猜出的单词部分。由于我们正确猜出了“B”,程序会逐步揭示秘密单词。当“已猜单词”部分完全显示秘密单词时,游戏正式结束。现在,程序要求我们猜秘密单词的第二个字母,这正是场景3的开始。

场景3的背景是用户正在尝试猜第二个字母,其行为是他们猜对了。如果猜测正确,程序应确认猜测正确,并更新显示当前已猜对的字母列表。因此,“已猜单词”部分不应只显示字母“B”,而应同时显示“B”和“I”。然后,程序应提示用户输入下一个字母,即开始猜第三个字母。

现在,让我们进行第二次猜测,即猜秘密单词的第二个字母。根据场景3的描述,我们猜字母“I”。然而,程序提示“再试一次”,而它本应提示“正确”。看起来程序没有正确处理这次猜测。

我们发现了第一个逻辑错误。现在暂停一下,回顾我们观察到的现象。

实际发生的情况预期应发生的情况存在差异。实际发生的情况是程序没有将此视为正确猜测,因此程序指示用户再试一次,并且游戏进度没有推进。我们仍然在尝试猜第二个字母,因为如果已经进入猜第三个字母,这里应该显示两个字母,但现在只显示一个字母。

预期行为和实际行为似乎不匹配。现在,我们尝试使用一种调试策略来找出原因。我们将使用打印语句。通常,最好将打印语句放在错误行为发生的位置或即将发生之前。

对我们而言,错误可能发生在哪里?第一次猜测(第一个字母)被正确处理了,但第二次猜测没有。因此,错误可能发生在评估猜测是否正确的位置。

在我们的代码中,这可能在哪里?如果你想自己找找看,可以暂停视频。它就在if语句之前。

让我们在if语句之前放置打印语句。我们应该打印什么?我们知道了放置位置,现在需要决定打印内容。通常,最好打印与错误行为相关的所有变量值。

在我们的案例中,我们想弄清楚为什么猜测被错误评估。因此,我们将打印所有与评估猜测相关的变量值,这基本上包括了所有相关变量。

以下是我们要打印的内容:

  • guess:玩家的当前猜测。
  • secret_word:秘密单词。
  • guess_index:存储玩家当前尝试猜测的字母在单词中的索引位置。
  • 玩家当前尝试猜测的字母:即secret_word[guess_index]
  • if语句的条件:即检查guess是否等于secret_word[guess_index]

这些打印语句将帮助我们可视化代码中到底发生了什么。让我们再次运行代码。

我们猜第一个字母“B”。打印信息显示:guess是‘B’,secret_word是‘bird’,guess_index是0(正确,因为我们在猜第一个字母),secret_word[0]是‘B’。if条件评估为True,程序显示“正确”,“已猜单词”显示字母‘B’。一切正常。

现在,猜第二个字母“I”。打印信息显示:guess是‘I’,secret_word是‘bird’,但guess_index仍然是0。这不对!我们现在应该猜第二个字母,其索引位置应该是1,而不是0。如果我们没有详尽地打印这些信息,可能不会发现这个错误。打印语句虽然看起来工作量很大,但它们确实有助于揭示错误行为。我们刚刚发现了一个。

secret_word[guess_index]显示的是‘B’(因为guess_index是0),而它本应是‘I’。因此,if条件评估为False(本应为True),所以程序提示“再试一次”。

那么,为什么guess_index没有更新?我们看到在第一次和第二次猜测时,它打印的都是0。它显然没有被更新。它应该在何时被更新?

一旦我们确定玩家正确猜出了一个字母,我们就知道需要继续猜下一个字母。因此,那将是更新guess_index的好时机。在代码中,我们如何知道玩家正确猜出了一个字母?那就是在if代码块中。如果条件评估为True,则玩家正确猜出了一个字母。

因此,我们应该只在if代码块内部更新guess_index。我们将在if块的末尾将其值增加1(guess_index += 1)。之所以在末尾而不是开头进行,是为了确保先将玩家猜对的字母存入letters_guessed列表,然后再移动到下一个字母。

让我们运行修复后的代码,看看问题是否解决。

我们再次从开头运行。猜第一个字母“B”:guess_index为0,条件为True,显示“正确”,“已猜单词”显示‘B’。很好。

现在猜第二个字母“I”:guess_index现在是1(终于更新了!),secret_word[1]是‘I’,if条件评估为True,显示“正确”,“已猜单词”现在显示了‘B’和‘I’。太棒了!我们修复了第一个逻辑错误。如果没有这些打印语句和指导我们调试过程的场景,发现和修复这个错误会困难得多。

既然这不再是程序的实际行为,我们可以更新我们的笔记,标记场景3的预期行为与实际行为现在匹配了。保持笔记更新、保持条理清晰非常重要。

接下来,让我们继续处理场景4。在这个场景中,玩家最终将赢得游戏。

预期行为是:用户每次正确猜测后应看到“正确”消息,每次错误猜测后应看到“再试一次”消息。用户每次猜测后还应看到他们的进度,显示已猜对的字母。在最后一次正确猜测后,游戏结束,用户应看到“你找到了隐藏的单词”消息。

让我们重现这个场景,看看游戏中是否完全按此发生。

回到我们的游戏,我们进行一些正确和错误的猜测,直到最终赢得游戏。我们首先猜“B”(正确),然后猜“Z”(错误),注意guess_index在更新,“已猜单词”在‘Z’之后没有更新(因为‘Z’错误)。接着猜“I”(正确),“已猜单词”更新了。再猜“R”(正确),guess_index变为2(因为我们刚正确猜出第三个字母),“已猜单词”显示了前三个字母。再猜“C”(错误),“已猜单词”没有更新。最后猜“D”。

有趣的是,此时完整的秘密单词应该已经揭示,游戏本应结束。但如果我再猜一次呢?让我们试试“S”。程序出现了运行时错误。

错误信息是IndexError,这通常意味着某个索引位置过高或过低。错误发生在我们的文件第18行附近(具体位置可能因环境而异)。错误信息提示“字符串索引超出范围”。

除了错误信息,检查在错误消息显示之前成功执行了哪些代码行也很重要。我们看到,在猜测“S”之后,打印语句成功输出了guess(‘S’)、secret_word(‘bird’)和guess_index(4)。然后,在第18行的打印语句处程序崩溃了。

因此,问题很可能就出在第18行。第18行打印的是secret_word[guess_index]。我们刚看到guess_index的值是4。字符串“bird”的长度是4,其有效索引是0到3。索引位置4不存在,这就是问题所在:guess_index的值太大了。

如果没有这些打印语句,要发现这个问题会困难得多。这再次证明了打印语句在调试中是非常有用的工具。

那么,为什么guess_index会更新到一个过大的值呢?查看程序输出,我们注意到在正确猜出整个单词、完整揭示秘密单词后,游戏仍然继续,并要求我们猜测完整秘密单词之后的下一个字母。这意味着游戏没有在应该结束时结束。

决定游戏何时结束的是while循环。看起来while循环可能没有在应该结束时终止。让我们看看while循环的条件:guess_index <= len(secret_word)。问题就在这里!它不应该是“小于或等于”,而应该是“严格小于”。索引位置必须严格小于字符串的长度(即0到长度-1)。所以条件应该是guess_index < len(secret_word)

看起来这就是导致运行时错误的原因。修复之后,让我们结束当前游戏,清除终端,再次运行代码,看看行为是否有差异。

我们进行一些正确和错误的猜测:B(正确),Z(错误),I(正确),R(正确),C(错误),D(正确)。游戏正确结束了!我们成功消除了错误。

现在,既然我们确定不再需要这些打印语句,可以删除它们了。将过时、未使用或已弃用的代码永久保留为注释被认为是糟糕的编程实践。

接下来,我们把秘密单词改成“cat”。这是一个不同长度的单词(三个字母)。但我们不必担心长度不同,因为我们在代码中到处使用len()函数,而不是硬编码数字(如4)。len()函数会自动适应新字符串的长度,这就是使用内置函数的好处。

让我们清除终端,再玩一次。程序现在提示“三字母单词”。输入字母C(正确),Y(错误),A(正确),T(正确)。游戏运行正常,符合所有测试场景。

我们应该更新笔记,标记场景4的预期行为与实际行为现在匹配了。保持笔记更新非常重要。

剩下的就是一些设计相关的场景了。我建议你邀请朋友或家人来玩这个游戏,然后询问他们这些场景试图测试的问题:玩得开心吗?能理解游戏说明吗?如果他们有任何建设性的反馈,可以提出来帮助你改进游戏。你可以自由发挥,为这个游戏或其他程序创建自己的测试场景。

也许你想改编这个游戏以支持其他语言,或者让它变得更难。你可以修改代码,例如从一开始就提供第一个字母,或者随机选择不同的秘密单词,或者显示玩家猜过的所有字母(不仅仅是正确的),或者限制玩家在游戏失败前的猜测次数。有很多有趣的事情可以做,让游戏变得更花哨、更难或更容易。无论如何,最重要的是享受编程的乐趣。编程愉快!


本节课中,我们一起学习了如何通过创建测试场景和使用打印语句来系统地调试一个Python程序。我们定位并修复了guess_index变量未及时更新以及while循环条件错误导致的索引越界问题。我们还强调了在调试过程中保持笔记更新和条理清晰的重要性。通过实践,你掌握了发现、诊断和解决代码逻辑错误的基本方法。

058:制作Python标志饼干 🍪

在本节课中,我们将学习如何制作印有Python标志的饼干。你将能够亲手制作饼干并与朋友分享。整个过程分为准备面团、塑形、烘烤和装饰几个主要步骤。


准备面团材料

上一节我们介绍了课程目标,本节中我们来看看制作饼干所需的具体材料。

以下是制作面团所需的全部材料清单:

  • 一杯黄油(约两条)
  • 一杯半白糖
  • 两个鸡蛋
  • 一茶匙香草精
  • 三杯面粉
  • 半茶匙泡打粉
  • 半茶匙盐

混合面团

准备好材料后,我们开始混合面团。

  1. 将软化(非融化)的黄油与白糖放入碗中,搅拌至质地柔滑。
  2. 加入两个鸡蛋和一茶匙香草精,继续搅拌均匀。
  3. 分次加入三杯面粉、半茶匙泡打粉和半茶匙盐。
  4. 充分搅拌,确保碗底的面粉也完全混合。

冷藏面团

面团混合均匀后,需要将其冷藏以方便后续操作。

  1. 准备三张保鲜膜。
  2. 将面团分成三份,分别揉成球状。
  3. 用保鲜膜分别包裹好面团球。
  4. 将面团放入冰箱冷冻一段时间,可以过夜或仅冷冻至面团变硬。

擀面与切割

面团冷藏好后,我们开始擀开并切割成Python标志的形状。

  1. 在案板和擀面杖上撒一些面粉防粘。
  2. 取出一份面团,用擀面杖擀成薄片。
  3. 将擀好的面片转移到烤盘上。
  4. 使用Python标志的图案作为模板,用刀沿着轮廓切割。也可以使用自制的3D饼干切模。
  5. 使用小型圆形切模(例如数字饼干切模中的小圆点)切出标志的“眼睛”部分。
  6. 预热烤箱至 350华氏度(约177摄氏度)

烘烤饼干

切割好形状后,就可以进行烘烤了。

  1. 将切割好的饼干生坯放入预热好的烤箱。
  2. 烘烤约 15分钟,直至饼干烤熟。
  3. 将烤好的饼干取出,放在蜡纸上冷却。

装饰饼干

饼干冷却后,最后一步是进行装饰。

  1. 准备白色糖霜,并将其分到两个碗中。
  2. 在一个碗中加入金黄色食用色素凝胶,另一个碗中加入皇家蓝色食用色素凝胶,分别调匀。
  3. 用抹刀将糖霜涂抹在冷却的饼干上,制作出金黄色和蓝色的Python标志饼干。
  4. 涂抹好糖霜的饼干需要静置数小时,待糖霜完全干燥。

总结与分享

本节课中我们一起学习了制作Python标志饼干的完整流程,从准备材料、混合面团、塑形、烘烤到最终装饰。

饼干干燥后,你就可以与家人、朋友或同事分享你的作品了。希望你能享受制作和分享的乐趣。

posted @ 2026-03-26 12:26  布客飞龙III  阅读(84)  评论(0)    收藏  举报