Python-项目入门指南-全-

Python 项目入门指南(全)

原文:Python Projects for Beginners

协议:CC BY-NC-SA 4.0

一、入门指南

你好!欢迎迈出成为 Python 开发人员的第一步。令人兴奋不是吗?无论你是刚刚开始学习如何编程,还是已经有了其他语言的经验,本书教授的课程将有助于加速你的目标。作为一名 Python 指导者,我可以向你保证,关键不在于你从哪里开始,而在于你愿意付出多大的努力。

在写这本书的时候,我的日常工作是一名编码训练营教练,在那里我教学生如何在短短十周内从零编程经验到专业开发人员。这本书的设计意图是将基于训练营的方法引入文本。这本书旨在帮助你学习对成为 Python 专业开发者有价值的主题。

接下来的每一章都会有一个概述和对我们那一周所学内容的简要描述。本周我们将涵盖所有必要的基础知识,让我们快速入门。遵循古老的说法,“在你能跑之前,你必须学会走,”在我们开始编码之前,我们必须了解我们的工具是什么以及如何使用它们。

概述

  • 理解这本书为什么和如何工作

  • 安装 Python 和 Anaconda

  • 了解如何使用这些新工具

  • 了解如何使用终端

  • 编写您的第一个 Python 程序

事不宜迟,我们开始吧,好吗?

星期一:介绍

几乎每个程序员都记得“啊哈!”那一刻,一切都为他们所触动。对我来说,那是我学会 Python 的时候。经过多年的计算机科学教育,我发现最好的学习方法之一是构建应用程序并应用你所学的知识。这就是为什么这本书会让你跟着编码,而不是阅读编程背后的理论。Python 使得理解其他语言中难以理解的概念变得简单。这使得它成为进入开发行业的绝佳语言!

你可能已经注意到这本书的结构与大多数不同。我们用星期或天来分隔每个主题,而不是章节。请注意该部分的当前标题。这是基于训练营的方法的一部分,因此你可以设定每天的目标。这本书有两种解读方式:

  1. 在十周的时间里

  2. 在十天的过程中

如果你想遵循 10 周的方法,那么就把每一章当作一个周目标。所有章节被进一步分解为周一至周五的每日部分。前四天,周一到周四,将介绍新的概念来理解。周五,或者更广为人知的项目日,我们将根据一周内学到的经验一起创建一个项目。重点是你每天留出 30-60 分钟来完成每天的任务。

如果你非常渴望尝试训练营的方式,在那里你可以在十天内学会所有的内容,那么就把每一章当成一天。当然,你必须知道,为了在十天内完成这本书,你需要每天投入大约 8 个小时,这对于编码训练营的学生来说是典型的一天。在训练营中(就像我教的那个),我们每天都要复习几个概念,接下来的每一天我们都要重申从之前的课程中学到的主题。这有助于加快学习每个概念的过程。

Python 是什么?

Python 是一种解释的高级通用编程语言。为了理解这些描述的含义,让我们做一些比较:

  • 低级与高级:指我们是否使用机器级别的指令和数据对象编程,或者我们是否使用语言设计者提供的更抽象的操作编程。低级语言(如 C,C++)需要你分配和管理内存,而 Python 为我们管理内存。

  • 通用与针对性:指编程语言的操作是广泛适用还是针对某个领域进行微调。例如,SQL 是一种目标语言,旨在方便从关系数据库中提取信息,但您不会想用它来构建操作系统。

  • 解释过的 vs .编译过的:指程序员编写的指令序列,称为源代码,是直接执行(由解释器)还是先转换(由编译器)成机器级原语操作序列。大多数用 Python 设计的应用程序都是通过解释器运行的,所以错误是在运行时发现的。

Python 还强调代码的可读性,并使用空格来分隔代码片段。我们将在课程中更多地了解 Python 中的空格是如何工作的,但是现在我们只知道 Python 是进入计算机科学行业的第一语言。

为什么是 Python?

我可以继续讲述 Python 为何如此神奇,但简单的谷歌搜索就能帮我做到。Python 是最容易学习的语言之一。注意,我说的是“更容易而不是“容易”……这是因为编程仍然很难,但 Python 比大多数其他语言更接近英语。这是学习 Python 的好处之一,因为你从本书中学到的概念仍然适用于其他语言。Python 也是当今技术行业最受欢迎的技能之一,被谷歌、脸书、IBM 等公司使用。它被用于构建 Instagram、Pinterest、Dropbox 等应用程序。

它也是 2019 年增长最快的语言之一,攀升至未来学习的前 3 种语言。然而它的报酬有多高呢?据 Indeed.com 称,2018 年的平均工资约为11.7 万美元2 垄断的钱多着呢!

然而,学习 Python 的最大原因之一肯定是语言本身的使用。它被用于几个不同的行业:前端开发、后端开发、全栈、测试、数据分析、数据科学、网页设计等。,这使它成为一种有用的语言。

为什么是这本书?

先说想看这本书的主要原因。贯穿本书的教材有一个被证实的记录。我个人使用这种精确的组织方法帮助我的学生在各种行业获得高薪职位。多年来,该课程的结构已经过多次改进,以适应当前的行业趋势。

与竞争对手相比,这本书的下一个优势是概念是如何教授的。我不会用细节来烦你;相反,在本书的整个过程中,我们将一起构建小型和大型应用程序。最好的学习方法往往是边做边学!尤其是在编程方面,我经常告诉学生的一个教训是,试着写代码,如果代码坏了,就修复它。如果你不尝试打破东西,你将无法学习!

最后,这本书不仅会教你如何编程,还会教你如何像程序员一样思考。在每周的开始,我会挑战你,到课程结束时,你将能够理解你需要采取的方法。你总是能区分那些只会编程的人和那些久经考验的开发人员。

这本书是给谁的?

在你开始读这本书之前,先了解你要读的内容总是有好处的。想要读一本书,你首先必须意识到这本书本身是否是为你设计的。如果你对以下任何一个问题的答案是肯定的,那么这本书就是为你而写的:

  • 你是否有过其他编程语言的经验,却想拿起一门高级语言?

  • 你有没有以前没有编程过但是很渴望学习的经历?

  • 你以前学过计算机科学课程吗,但是它们没有帮助你学习如何创建应用程序?

  • 你想转行吗?

  • 你以前有没有试过学语言,但是因为语言的难度而学不下去?

  • 你以前用 Python 编程过,但是想提高自己的能力,学习新的工具吗?

这本书是为各种各样的读者设计的,不管你的背景如何。真正的问题在你身上,“你愿意多努力工作”这本书教授的概念可以让任何愿意学习的人受益。即使你以前用 Python 编程过,这本书仍然可以帮助你成为一个更强的开发者。

你会学到什么

这本书是为教学 Python 而设计的训练营课程而创作的。您可以期望涵盖 Python 开发人员工作中所需的必要信息。这些概念将使你有能力继续你的编程教育。在每章的结尾,我们将使用所涵盖的概念来创建各种现实世界的应用程序。毕竟,我们在这里不仅仅关注 Python,我们试图帮助你成为一名更好的开发人员。

明天,我们将了解如何安装本书使用的必要软件。如果您的机器上已经安装了 Anaconda 和 Python,您可以跳到周三的课程。

星期二:设置 Anaconda 和 Python

今天,我们将进行软件设置。在本书中,我们将使用一个名为 Anaconda 的软件平台,一个名为 Jupyter Notebook集成开发环境(IDE) ,以及 Python 本身的语言。这本书将严格涵盖 Python 3;然而,有时你可能会看到我提到版本 2 和版本 3 之间的细微差别。让我们先下载并安装这些软件,然后我会详细介绍它们各自的功能。

跨平台开发

Python 可以在所有主流操作系统上运行,这使它成为一种跨平台语言。这意味着你可以在一个操作系统上编写代码,并与使用与你完全不同的机器的人一起工作。如果两台机器都安装了 Python,它们应该都能够运行这个程序。

为 Windows 安装 Anaconda 和 Python

大多数 OS X 和 Linux 操作系统已经安装了 Python 但是,您仍然需要下载 Anaconda。对于 Windows 用户,Python 通常不包括在内,但它与 Anaconda 一起安装。使用以下步骤正确安装 Anaconda:

  1. 打开浏览器,输入 www.anaconda.com/distribution/

  2. Click the download button in the header (see Figure 1-1).

    img/481544_1_En_1_Fig1_HTML.jpg

    图 1-1

    Anaconda 下载页面

  3. Once you are on the next page, make sure you select the proper operating system on the header at the top. Click that button (see Figure 1-2).

    img/481544_1_En_1_Fig2_HTML.jpg

    图 1-2

    选择操作系统

  4. Next, click the download button for the Python 3.7 (or greater) section (see Figure 1-3).

    img/481544_1_En_1_Fig3_HTML.jpg

    图 1-3

    下载 Python 3.x 版本

  5. This step is strictly for Windows users… Once the installer fully downloads, go ahead and run it. Use all defaults except for one option. When you get to the page in Figure 1-4, make sure you click the “add to path” option. This will let us access Anaconda through our terminal.

    img/481544_1_En_1_Fig4_HTML.jpg

    图 1-4

    添加到路径

  6. 对于所有选项(除了 Windows 用户的步骤 5),使用默认设置。然后点击“安装”按钮,让 Anaconda 完成安装。

什么是蟒蛇?

Anaconda 是一个 Python 和 R 发行软件。它旨在为 Python " 提供开箱即用的一切。它主要用于数据分析和数据科学;然而,这也是一个极好的学习工具。下载后,它包括

  • 核心 Python 语言和库

  • Jupyter 笔记型电脑

  • Anaconda 自己的包管理器

这些只是 Anaconda 附带的众多特性中的一小部分;然而,这些是我们将在整本书中使用的。这个列表中的第一个特性是 Python 语言和 Python 可以访问的包含包。库是另一个开发人员预先编写的代码,您可以为了自己的利益而使用。下一节将讨论第二个特性。最后,Anaconda 有一种为我们管理环境的方式。这是一个复杂的话题,我们将在接下来的几周深入探讨。

Jupyter 笔记本是什么?

它是一个开源的集成开发环境(IDE),允许你创建和共享包含实时代码、公式、可视化和叙述性文本的文档。对我们来说,它本质上是我们的笔记本,我们将在那里一起编码。如果你不熟悉 ide,它们只是开发人员编写代码的工具。把它们想象成艺术家的画布。它还允许您编写代码片段,而不需要了解很多关于 Python 的知识。我们将在周四的课程中更多地学习朱庇特笔记本。

在今天的课程中,我们安装了 Anaconda、Python 和 Jupyter Notebook。明天,我们将学习为什么以及如何使用终端。

星期三:如何使用终端

根据你的操作系统,你将会使用命令提示符 ( Windows )或者终端 ( Linux 和)。从现在开始,我将把它称为“终端”,所以如果你使用 Windows,请记住这一点。终端是用户能够通过基本文本向计算机发出命令的工具。对于本书的大部分内容,我们将使用终端来测试我们的 Python 代码或运行 Jupyter Notebook。今天我们将学习基本命令以及如何使用 Python shell。首先,让我们打开终端。由于每个操作系统看起来都不同,终端会话将在代码中由“ $ ”定义。您在该符号后看到的任何文本都将是您需要自己写入终端的内容。

更改目录

在终端中,您可能经常想要在文件夹之间移动。这给了你在电脑上导航的能力。了解如何做到这一点很重要,因为这一直是我们启动 Jupyter Notebook 时要做的事情。为了改变目录,你需要输入“ cd ”,然后输入你想去的文件夹名。

$ cd desktop

如果你需要从一个文件夹中返回,那么你需要使用两个点(“..”:

$ cd ..

通常,在本书中,您需要遍历几个目录才能进入一个项目文件夹。当您使用“cd”命令时,您可以选择向前或向后移动多远,您只需要指定您要去的文件夹的正确路径。以下面的代码为例…

$ cd desktop/../desktop

我们将进入桌面目录,但随后返回,只是为了回到它里面。这没什么不好;然而,这只是一个例子,计算机将遵循您指定的路径。通常情况下,我们只需将光盘放入桌面就可以了。

正在检查目录

要检查您当前所在的目录,只需查看您可以书写这些文本行的左侧。对于 Windows 用户,您当前所在的目录将是您所在的结束 URL,以粗体标记如下:

C:\Users\name\desktop>

最后一个文件夹名是“桌面”,这意味着我当前在我的桌面的目录中。如果我要创建任何文件或文件夹,它们会直接在那里创建。要检查您所在的 Linux 目录,它将是“ $ ”左侧的名称:

user@user:~/Desktop$

对于 OS X 用户,它将位于您的用户名(您登录时的用户名)的左侧:

User-Macbook-Pro:Desktop Name$

制作目录

虽然进入您的文件浏览器当然没问题,但是右键单击并选择“创建新文件夹”,了解如何通过终端会话本身创建新文件夹是很好的。确保你在“桌面”目录中,即我们“ cd 之前进入的目录。然后写下下面一行:

$ mkdir python_bootcamp

这将在您的桌面上创建一个名为“ python_bootcamp ”的新文件夹。从现在开始,我们将使用这个文件夹来存储我们的课程,以便保持有序。

创建文件

同样,进入文件资源管理器可以更容易地创建文件。但是,有时我们需要根据文件类型在终端中创建文件。然而,在我们创建新文件之前,让我们将" cd "放到我们创建的" python_bootcamp "文件夹中:

$ cd python_bootcamp

现在,对于 Windows 用户,我们需要键入以下内容:

$ echo.>example.txt

或者如果你使用的是 Linux/OSX :

$ touch example.txt

现在,您应该能够在文件资源管理器中看到 sample.txt 文件。

注意

如果没有看到“”。txt "扩展名,这是因为您没有在文件资源管理器的首选项中选中“扩展名”。

检查版本号

终端总是检查我们下载的某些软件版本号的好方法。因为我们已经下载并安装了 Python,所以让我们运行以下代码:

$ python --version

清除终端输出

有时终端会得到大量无用的输出,或者变得难以阅读。当你想清除输出时,你需要写下面一行(对于 Windows ):

$ cls

对于 Linux/OSX 用户,您需要键入以下内容:

$ clear

使用 Python Shell

Python 是一种需要所谓的“解释器来读取和运行我们创建的代码的语言。当 Python shell 被激活时,它在打开的终端会话中充当本地解释器。当它开放时,我们可以写任何我们想执行的 Python。这通常对于练习小代码片段非常有用,这样您就不必打开 IDE 并运行整个文件。要启动 Python shell,当我们在“ python_bootcamp 目录中时,只需键入“ python ”并点击 enter。将出现以下内容:

$ python
Python 3.7.0 (v3)
Type "help", "copyright", "credits" or "license" for more information
>>>

输出将显示您当前运行的 Python 版本。你会注意到三个箭头( > > > ),这意味着你现在正在 Python 解释器内工作。在 Python shell 中,您编写的所有内容都被解释为 Python 语言。如果出于某种原因,您收到以下响应:

$ python
'python' is not recongized as an internal or external command, operable program or batch file.

这意味着 Anaconda 和 Python 没有正确安装。我建议您回到昨天的课程,按照给出的逐步说明重新安装 Anaconda。您可能还需要重新启动计算机。

编写您的第一行 Python 代码

到目前为止,我们还没有进行任何编程。一般来说,我反对自己不去编码;然而,这些基本的设置说明对于开发人员的入门至关重要。虽然我们还没有学习任何 Python,但是当解释器还在运行的时候,在箭头旁边写下下面的代码并按下回车键:

>>> print("Hello, buddy!")

这就对了。您刚刚编写了第一行 Python 代码,应该会看到以下输出:

>>> print("Hello, buddy!")
Hello, buddy!
>>>

退出 Python Shell

现在,我将在后面的课程中解释您刚刚编写的内容,但现在让我们离开 Python shell,通过编写以下行并按 enter 键来结束今天的课程:

>>> exit( )

今天的课程是关于操作和理解终端的。对于一些开发人员来说,这是一项重要的技能,尤其是那些使用 Linux 操作系统的开发人员。明天我们将讨论如何操作 Jupyter 笔记本!

星期四:使用 Jupyter 笔记本

朱庇特笔记本将会是我们在本书中花费大部分时间的地方。这是数据科学社区中使用的一个强大工具,它使我们更容易学习 Python,因为我们可以专注于编写代码。今天的课程是关于如何使用这个工具,细胞,以及如何打开它。

注意

每节课都会要求你打开 Jupyter 笔记本,所以把这一页放在手边,以防你需要回头再看。

打开 Jupyter 笔记本

Jupyter 笔记本可以通过 Anaconda 程序打开;但是,我希望您开始习惯终端以及如何操作它,所以我们不打算通过 Anaconda 打开它。相反,我们将通过终端来执行此操作。这样做的两个好处是

  • Jupyter 笔记本将在我们的终端所在的目录中打开

  • 作为开发人员,知道如何使用终端将对您有所帮助

如果昨天的终端会话仍然打开,请跳过第一步。

步骤 1:打开终端

我们需要打开终端,将“ cd 放入我们的“ python_bootcamp 目录:

$ cd desktop/python_bootcamp

步骤 2:编写 Jupyter 笔记本命令

通过终端打开 Jupyter Notebook 就像输入工具名称一样简单:

$ jupyter notebook

在键入代码之前,请确保您位于正确的目录中;否则,它将在您的终端目录当前所在的位置打开。通常,这将在用户文件夹中打开 Jupyter Notebook。Jupyter 笔记本将在您的浏览器中打开。

创建 Python 文件

每当我们开始新的一周,我们最终都会创建一个新的工作文件。这样做很简单;Jupyter 笔记本第一次打开时,只需点击屏幕右侧的“新建按钮即可。然后选择“ Python 3 ”(见图 1-5 )。

img/481544_1_En_1_Fig5_HTML.jpg

图 1-5

创建 Python 3 笔记本

一旦你点击了" Python 3 "选项,一个新的标签将作为这个文件打开。点击顶部的名称进行重命名,我们把这个文件命名为“W eek_01 ”(见图 1-6 )。

img/481544_1_En_1_Fig6_HTML.jpg

图 1-6

更改文件名

Jupyter 笔记本电池

现在我们已经打开了 Jupyter Notebook 并创建了一个可以使用的文件,让我们来谈谈细胞。我说的不是生物学;相反,在本笔记本中,您会注意到工具下方的空白白色矩形部分(参见图 1-7 )。这些被称为细胞

img/481544_1_En_1_Fig7_HTML.jpg

图 1-7

以红色突出显示的笔记本单元格

每个单元格都是我们可以写代码的地方,甚至可以使用标记语言。让我们先写一些标记。

  1. 在第一个单元格中单击,周围区域会发出蓝光。

  2. 在工具栏中,您会注意到一个写着“代码”的下拉菜单点击下拉菜单,选择“降价”。

  3. 在单元格内写下以下内容:

# Week 01

注意

编写标记时,一行中 hashtags 的数量与标题的大小有关。比如 HTML 标题标签。

  1. 现在让我们运行单元来执行代码。为此,按住 shift 并按 enter 键(单元格必须被选中)。

  2. 当您使用 shift + enter 时,一个新的单元格将出现在当前单元格的下方。

在这个新创建的单元格中,让我们继续编写一行简单的 Python 代码,看看输出是如何工作的。让我们继续编写以下内容:

# this is python
print("Hello, buddy!")

去查查手机。它将运行单元格内的所有代码并输出结果。同样,不要担心实际的 Python,这一课是关于 Jupyter 笔记本单元如何运行的。

对于本书的其余部分,我们将在 Jupyter 笔记本文件中编写代码。我将使用 markdown 来指定某些部分,所以在继续之前,请确保您已经熟悉了运行单元格、编写 markdown 和创建新的 Jupyter 笔记本文件。

今天我们学习了如何使用 Jupyter 笔记本,以及我们可以用细胞做什么。在明天的课程中,我们将构建我们的第一个 Python 应用程序!

星期五:创建你的第一个程序

每周五将被称为“项目日”,在那里我们将一起构建一个小的应用程序或游戏,它使用了一周中所学的概念。然而,本周我将让您在一个单元格中编写一些代码,这样您就可以看到 Python 的强大功能。因为我们还没有学习任何 Python,所以我希望你们能够体验我们将在接下来的几周里学到的东西。您将要编写的代码将使用第 2、3 和 4 周的概念。在这几周结束时,你将能够完全理解以下代码的每一行,并做出自己的调整,使程序更具挑战性。

我们将从昨天课的 Jupyter 笔记本文件开始学习。如果你在回到这本书后已经关闭了程序,请继续并重新打开文件。

注意

如果您忘记了如何打开 Jupyter 笔记本,请回到昨天的课程,重做这些步骤,除了创建一个文件。

引入的行号

对于较大的项目,有时很难跟着书走。对于这个项目,以及以后的所有课程,我将实现行号。这将使您更容易跟踪并检查您是否正确地编写了代码:

1| ←

行号现在将出现在所有单元格的左侧,因为我们将需要在一个单元格中编写所有这些代码。请务必注意这些数字,因为您可能会看到它们跳过了几行:

1| # this is the first line in the cell
5| # this is the fifth line in the cell

这意味着你应该在第五行上写下所示的第二行。

注意

点按单元格的边后按下“L”来打开线条。

创建程序

我们需要做的第一件事是在文件中的当前单元格下创建一个新的单元格。为此,只需遵循以下步骤:

  1. 单击文件中的最后一个单元格。

  2. 高亮显示时,进入菜单栏中的“插入选项卡,点击下方的“插入单元格”

我们的项目现在有了一个细胞。如果你想创建一个标题为“猜谜游戏的 markdown 单元格,请随意回顾上一课以及我们之前是如何做的。在这个新单元格中,让我们继续编写以下代码:

 1| # guessing game
 2| from random import randint
 3| from IPython.display import clear_output
 5| guessed = False
 6| number = randint(0, 100)
 7| guesses = 0
 9| while not guessed:
10|   ans = input("Try to guess the number I am thinking of!")      # use tab to indent
12|   guesses += 1
14|   clear_output( )
16|   if int(ans) == number:
17|           print("Congrats! You guessed it correctly.")              # use tab twice to indent twice
18|           print( "It took you { } guesses!".format(guesses) )
19|           break
20|   elif int(ans) > number:
21|           print("The number is lower than what you guessed.")
22|   elif int(ans) < number:
23|           print("The number is greater than what you guessed.")

这个程序无论如何都不是完美的,但是尝试猜测计算机正在思考的数字肯定很有趣。我知道现在对你来说这看起来像是一门外语,但是在接下来的几个星期里,每一行都会变得有意义。最终你甚至可以对游戏做出自己的改变和改进!我要你现在做的是运行细胞和玩游戏。开始像开发人员一样思考,并在玩的时候问自己这些问题:

  • 我可以做哪些改进?

  • 是什么让程序崩溃?

  • 我能做得更好吗?

如果出现错误,不要害怕,这是成为开发人员成长的一部分!测试你写的代码的有趣的部分是你试图破坏它。随着我们的深入,我将用一些问题来挑战你,为什么代码中的一行是这样工作的。当这种情况发生时,试着思考几分钟,甚至试着用谷歌搜索答案。作为一名开发者,你会发现你做的很多事情都是在谷歌搜索一个问题。这就是优秀的开发人员与伟大的开发人员之间的区别……独立解决问题的能力。有了这本书的其余课程,没有我的帮助,你也能很好地解决问题。

最终输出

每周的所有源代码都将位于本书的 Github 资源库中。你可以在书的前面找到那个知识库的链接。要找到本周的具体代码,只需从 Github 存储库中打开或下载“ Week_01.ipynb ”文件。如果您在这个过程中遇到了错误,一定要参考您用这个文件中的代码编写的内容,看看哪里出错了。

今天,我们能够看到我们的第一个 Python 程序在运行。虽然你可能不明白发生了什么,但我相信让你见识一下 Python 的威力是至关重要的。随着我们的深入,您可以随时回到该计划,并对其进行自己的改进。毕竟,让你变得更好的唯一方法就是去做!!!

每周总结

我知道这一周有点慢,但这是这个过程中至关重要的一周。我们讲述了如何下载必要的工具,如何使用它们,以及如何使用终端本身。这些主题对于理解接下来的内容非常重要,有助于你走向成功。

在这个周末,我们一起编写了一个有趣的猜谜游戏,我希望你能试着打破这个游戏。作为一名开发人员,想要打破一个程序是很重要的,这样你就可以改进它。在接下来的一周,真正的乐趣开始了。我们将开始学习 Python 的基础知识,并最终一起编写一个小程序。

每周挑战

每周结束时都会有自己的挑战,你一定要试试。完成它们将有助于提高你的编程技能。因为本周主要是关于设置,所以接下来的挑战与编程无关。然而,其他的几周会给你很好的例子来检验你的能力。

  1. 新文件:创建一个名为“第一周-挑战”的新 Jupyter 笔记本文件现在,主工作文件夹中应该有两个文件。

  2. 编写降价:在练习 1 的文件中,创建一个带有降价的单元格,上面写着“挑战 1”尝试几种不同的页眉尺寸。挑一个你最喜欢的。

  3. 探索 Python :你应该习惯于谷歌搜索你感兴趣的问题或话题。尝试搜索您感兴趣的 Python 主题,并在开始学习该语言时记住它们。

  4. 激励自己:每个程序员都是白手起家。每个人都通过推动自己学习他们感兴趣的语言而成为伟大的程序员。找出是什么促使你想成为一名开发人员,并把它写下来。当你开始奋斗时,请记住这一点。

二、Python 基础

无论你想到哪个著名的程序员,像比尔·盖茨或吉多·范·罗苏姆,他们在人生的某个阶段也是从基础做起的。这些基本概念是构建学习任何编程语言的基础所必需的。毕竟,你不会从屋顶开始盖房子,你需要有一个基础来工作。这就是本周发挥作用的地方。

本周的重点将是数据类型和变量的。这些几乎是任何编程语言的核心概念。学习一门语言的美妙之处在于,它让你可以很容易地学会其他语言。这部分是因为所有语言都遵循相同的核心概念。到本周末,你将能够理解如何自己编写简单的程序。一个程序,比如我们将一起构建的程序,我们将在一个格式良好的收据中向用户打印信息。

本周我还将介绍您的第一个挑战问题。这些问题是为了确保你开始“像开发人员一样思考。”有些问题可能没有明确的答案,但它们会促使你创造解决方案并解决问题。重要的是,你花一些时间思考每个问题,这样你就可以开始训练你解决问题的技巧。毕竟,这是每个开发行业最受欢迎的技能。

概述

  • 了解数据类型

  • 如何使用变量

  • 看看你能用绳子做什么

  • 如何操作字符串

  • 编写打印收据的程序

挑战问题

在编程中,我们有一个概念叫做“算法”一个算法仅仅是一组步骤。不管你知不知道,你一生都在使用算法。一个常见的算法是一个食谱,你按照它来制作食物。

要像开发人员一样思考,您必须开始理解计算机如何读取代码。一台计算机只和它应该执行的程序一样聪明。这意味着如果步骤不正确,即使最聪明的计算机也可能失败。例如,让我们用一个食谱来烤一个蛋糕。如果我们错过了一个步骤,或者把蛋糕放在烤箱里太久,那么我们就失败了,就像电脑错过了一个关键步骤一样。

现在,我希望你们思考一下制作花生酱和果冻三明治的步骤。在一张纸上写下你的步骤。当你把它们写出来的时候,试着像电脑一样思考,并且明白你需要尽可能的精确。答案会在本章末尾。

星期一:注释和基本数据类型

今天是你学习 Python 语言的第一课!今天教授的两个概念将有助于建立我们正在努力的基础。为了继续今天的内容,让我们从“ python_bootcamp ”文件夹中打开 Jupyter 笔记本。如果需要,请回到上周关于如何打开 Jupyter 笔记本的课程。打开后,创建一个新文件,并将其重命名为“ Week_02 接下来,用下面的代码进行第一个单元格的降价:

# Comments & Basic Data Types

什么是注释,为什么要使用注释?

评论就像你留下的笔记,可以给自己看,也可以给别人看。它们不被解释器读入,意味着你可以写任何你想写的东西,计算机会忽略它。一个好的评论应该简短、易读、切中要点。在每一行放一个注释是很乏味的,但是不放任何注释是不好的做法。当你编程时,你会开始理解快乐的媒介是什么样子的。

当你开始写更大的程序时,你会想给自己留些笔记。我经常创建一个程序,停止工作三个星期,当我回来时,我忘记了我在做什么。留下评论不仅对你自己有好处,对其他阅读你代码的人也有好处。把评论想象成面包屑,帮助你理解正在发生的事情。

写评论

在 Python 中,我们可以使用散列 (#)符号来编写注释。此符号后面的任何文本都将被注释掉。在减价标题下方的单元格中,让我们写下第一条评论:

# this is a comment

让我们继续运行单元。请注意,什么也没有发生。这是因为计算机完全忽略任何评论。在大多数情况下,我们将在他们自己的行上写评论;但是,在某些情况下,您可能会看到与代码一致的注释。在与上一条注释相同的单元格中,我们添加下面一行:

print("Hello")       # this is also a comment

这一行的第一部分将运行并输出【Hello】****,但是第二部分将被忽略,因为有散列符号。

**### 注意

Markdown 使用散列字符作为头,就像 Python 注释一样。确保您知道您的单元格设置为“降价/单元格”的类型

要编写多行注释,以便为更大部分的代码编写更具描述性的段落,我们需要使用三个左右双引号:

" " "
       This is a multi-Line comment
" " "
print("Hello")       # this is also a comment

去查查手机。请注意,多行注释中的文本会被忽略。这些类型的注释对于添加关于代码的描述性段落非常有用。但是,一定不要过度使用它们,因为使用太多它们肯定会把程序搞得一团糟。

什么是数据类型?

几乎所有的语言都使用数据类型,它们对每个程序都是必不可少的。数据类型是我们定义值的方式,比如单词或数字。如果我问你一个句子是由什么组成的,你可能会用“单词或字符”来回答在编程中,我们称之为字符串。就像我们把数字称为它们自己的数据类型一样。数据类型定义了我们可以做什么,以及这些值如何存储在计算机的内存中。在表 2-1 中,你会发现每一行都显示了一个数据类型、一个样本值以及对每一个的描述。阅读每一部分,了解每种类型的详细解释。您可以在表格中找到我们本周讨论的四种基本类型。

表 2-1

数据类型示例

|

数据类型

|

样本值

|

描述

|
| --- | --- | --- |
| 整数 | five | 整数 |
| 浮动 | Five point seven | 十进制数字 |
| 布尔代数学体系的 | 真实的 | 真值或假值 |
| 线 | “你好” | 引号中的字符 |

打印声明

在我们进一步讨论之前,我只想简单介绍一下打印声明。几乎在每一种语言中,你都需要向用户输出信息的能力;在 Python 中,我们可以通过 print 语句做到这一点。现在我不想太深入,但是打印语句是我们在 Python 中称之为的函数。我们将在整个第五周讨论各种功能。但是现在,我们只需要知道 print 语句允许我们向用户输出信息。它的工作方式是通过写下关键字“打印”,然后加上括号。括号内的内容将输出给用户查看。

整数

这些数据类型通常被称为整数整数。它们是正数或负数整数,没有小数点。整数的使用有各种各样的原因,在数学计算和索引(,我们将在后面进入);它们是任何语言中的主要数据类型。让我们在文件的下一个单元格中打印几个例子:

# the following are all integers
print(2)
print(10)

去查查手机。结果输出应该是一系列数字 210

漂浮物

每当一个数字上有小数点时,它们就被称为浮点数据类型。不管它是 1 位数还是 20 位数,它仍然是一个浮点数。浮点数的主要用途是在数学计算中,尽管它们也有其他用途。让我们来看一个例子:

# the following are all floats
print(10.953)
print(8.0)       # even this number is a float

去查查手机。输出应该是一系列数字 10.9538.0

注意

数字“8.0”被认为是一个浮点数,因为它包含一个小数点。

布尔运算

布尔数据类型为真或假值。你可以把它想象成一个开关,要么关闭,要么打开。除了 True 或 False,它不能被赋予任何其他值。布尔是一种关键的数据类型,因为它们提供了多种用途。其中最常见的是跟踪是否发生了什么。例如,如果你玩一个视频游戏,想知道玩家是否还活着,当玩家开始繁殖时,你可以设置一个布尔值“”。当玩家失去了他们所有的生命,你会设置布尔为“”。这样你可以简单地检查布尔值,看看玩家是否还活着。这使得程序更快,而不是每次都计算生命。让我们继续运行以下代码:

# the following are booleans
print(True)
print(False)

去查查那个手机。输出应该分别是字

用线串

也被称为"字符串文字 "这些数据类型是我们今天要讨论的四种数据类型中最复杂的。字符串的实际定义是

Python 中的字符串是代表 unicode 字符的字节数组。

对于大多数初学者来说,这听起来就像一堆废话,所以让我们把它分解成一些我们可以理解的简单的东西。字符串无非是一组 字符、符号、数字、空格,甚至是两组 引号之间的空格。在 Python 中,我们可以使用单引号或双引号来创建字符串。大多数时候这是个人偏好,除非你想在一个字符串中包含引号(见下一个块中的第 3 行)。引号内的内容将被视为字符串,即使它是一个数字。让我们在下一个单元格中为字符串编写一些示例:

# the following are strings
print(" ")
print("There's a snake in my boot!")
print('True')

输出将在顶部包含一个空行,因为我们在第一条语句中不打印任何内容。

周一练习

  1. 输出:打印出你的名字。

  2. 类型检查:尝试使用 Type()方法检查值的类型。这将始终打印出您正在检查的数据类型。当您不确定时,这对于检查数据类型很有用。举个例子:

    >>> type(int) # will output <class 'int'>

今天,我们关注 Python 中的四种基本数据类型。理解两者之间的差异是我们前进的关键。在明天的课程中,我们将开始了解如何保存这些数据类型,以便在程序的后面使用。

星期二:变数

变量是编程中最重要的初级概念之一。它们允许我们使用指定的名称将值保存到内存中。这让我们可以在程序的后面使用这些值。昨天的课程讲述了不同的数据类型,但是如果您想保存其中一种数据类型以备后用,该怎么办呢?这就像我们如何在大脑中存储信息一样,变量存储在计算机内存中,我们可以在以后通过引用我们使用的名称来访问它们。我不会深入 Python 如何存储信息背后的理论,因为我们更关注编程的应用,但值得注意的是 Python 自动为我们处理内存存储和垃圾收集

为了跟上这一课,让我们从之前的笔记本文件“ Week_02 ”继续,简单地在底部添加一个标有“变量”的 markdown 单元格。

它们是如何工作的

我们在等于运算符(“=”)的左边声明了一个名称,在右边,我们为想要保存以备后用的值赋值。举个例子(不用写这个):

>>> first_name = "John"

当你创建一个变量时,你赋值的那一行是一个叫做声明的步骤。我们刚刚声明了一个名为“ first_name 的变量,并为它分配了字符串数据类型“ John 的值。这个字符串现在存储在内存中,我们可以通过调用变量名“ first_name ”来访问它。

注意

变量名只能包含字母、下划线和数字。但是,它们不能以数字开头。

处理命名错误

所有的程序员都会犯错,所以如果你遇到错误也不成问题。这是工作带来的。让我们来看看变量(不需要写这个)的一个常见错误:

>>> Sport = 'baseball'    # capital 'S'
>>> print(sport)          # lowercase 'S'

如果我们尝试运行这段代码,我们将得到以下错误/输出:

NameError: name 'sport' is not defined

这是因为名字完全不同。我们引用了一个带有小写字母“s”的变量,但是声明了一个带有大写字母“s”的变量。

整数和浮点变量

为了在变量中存储一个整数或浮点数,我们在操作符的左边给一个名字,在右边写一个数字。在下一个单元格中,让我们继续编写以下代码:

num1 = 5           # storing an integer into a variable
num2 = 8.4         # storing a float into a variable
print(num1, num2)  # you can print multiple items using commas

去查查那个手机。注意输出是 5 和 8.4 ,即使我们打印出了“ num1 和“ num2”。“我们打印出存储在这些变量中的值。

布尔变量

请记住,布尔值是值,因此存储它们就像键入这两个单词中的一个一样简单。让我们写以下内容:

# storing a boolean into a variable
switch = True
print(switch)

去查查那个手机。结果输出为“”。注意,在 Jupyter Notebook 中,True 或 False 的值会发绿光。如果我们写得正确,这是一个很好的指示。

字符串变量

字符串和前面三种数据类型一样容易存储。请记住,使用单引号或双引号很重要。让我们继续在新的单元格中编写以下代码:

# storing strings into a variable
name = 'John Smith'
fav_number = '9'
print(name, fav_number)            # will print 9 next to the name

继续运行它。记住字符串“ 9 ”和整数 9 是不一样的。这两种数据类型表现不同,即使输出看起来相似。

使用多个变量

几乎在你编写的任何程序中,你都需要对变量进行一些计算或操作。在下面的代码中,我们从先前声明的变量中访问值,并将它们相加以创建一个和。在运行此单元之前,请确保已经运行了之前的单元。让我们将它放入一个新的单元格中:

# using two variables to create another variable
result = num1 + num2
print(result)

运行这个单元后,你会注意到它将 58.4 加在一起输出 13.4

注意

如果您得到一个错误消息,说某个变量不存在,请尝试运行首先声明该变量的单元格。

对数值变量使用运算符

把 Python 想象成一个计算器,我们可以随意改变任何变量。在下面的代码中,我们改变了先前定义的"结果"变量:

# adding, deleting, multiplying, dividing from a variable
result += 1   # same as saying result = result + 1
print(result)
result *= num1   # same as saying result = result * num1
print(result)

去查查手机。在第一行中,我们给结果加 1,然后我们把它乘以" num1,"的值,也就是 5。与此同时,计算机保存了结果变量,所以我们可以继续编辑它。然后我们打印结果,结果显示为 72.0

覆盖以前创建的变量

Python 让我们很容易改变一个变量的值,只需要简单地重新声明它。在某些语言中,您必须定义数据类型,但是 Python 为我们处理了所有这些。我们已经在前面的结果变量中看到了这种情况,但是在它自己的单元格中值得注意:

# defining a variable and overwriting it's value
name = 'John'
print(name)
name = 'Sam'
print(name)

继续在新的单元格中运行。您会注意到输出显示了“约翰”“山姆”。访问或重新声明变量的位置很重要;记住这一点。

空白

空白仅仅意味着用于间隔的字符,并且有一个"空的"表示。在 python 的上下文中,它意味着制表符和空格。例如:

>>> name = 'John Smith'

等号运算符的左右两边都有空格。这不是必需的,但它使阅读代码更容易。计算机在编译代码时会忽略空白。然而,在字符串中,空格是而不是空格,这只是一个“空格”字符。

星期二练习

  1. 变量输出:将值 3 存储在一个名为“x”的变量中,将值 10 存储在一个名为“y”的变量中。将 x * y 的结果保存到一个名为“结果”的单独变量中。最后,输出信息,如下所示:

    >>> 3 + 10 = 13
    
    
  2. 面积计算:计算一个 245.54”x 13.66”的长方形的面积。打印出结果。提示:面积是宽度乘以高度。

变量无处不在,Python 让我们很容易将它们合并。能够存储信息是任何程序的关键部分。明天我们将看看如何操作字符串。

星期三:使用字符串

理解可以对字符串数据类型做什么很重要。接下来的两天包括一起操作字符串,这样我们可以在周末建立一个收据打印程序。我们不会担心接受用户输入,而是担心如何格式化字符串,什么是字符串索引,等等。

为了继续学习这一课,让我们从之前的笔记本文件“ Week_02 ”继续,并在底部简单地添加一个 markdown 单元格,上面写着“使用字符串”。

串并置

当我们谈论连接字符串时,我的意思是我们希望将一个字符串添加到另一个字符串的末尾。这个概念只是将字符串变量加在一起以完成一个更大的字符串的许多方法之一。对于第一个示例,让我们将三个单独的字符串加在一起:

# using the addition operator without variables
name = "John" + " " + "Smith"
print(name)

继续运行 markdown 单元格下面的单元格。我们得到的输出是“约翰·史密斯”。我们最后添加了两个字符串,这两个字符串是名称,并使用一个内部带有空格的字符串来分隔它们。让我们先尝试将这两个名字存储到变量中:

# using the addition operator with variables
first_name = "John"
last_name = "Smith"
full_name = first_name + " " + last_name
print(full_name)

去查查那个手机。我们得到与前一个细胞完全相同的输出;然而,这次我们使用了变量来存储信息。

格式化字符串

之前,我们通过将多个字符串相加来创建一个更大的字符串,从而创建了一个全名。虽然这完全可以使用,但对于较大的字符串来说,就很难阅读了。想象一下,你必须创造一个使用 10 个变量的句子。将所有十个变量添加到一个句子中很难跟踪,更不用说读取了。我们需要使用一个叫做字符串格式的概念。这将允许我们编写一个完整的字符串,并在适当的位置注入我们想要使用的变量。

。格式( )

format 方法的工作原理是直接在结束字符串引号后加一个句点,后跟关键字“ format ”。关键字后的括号内是将注入字符串的变量。不管是什么数据类型,它都会把它插入到字符串中合适的位置,这就带来了一个问题,它怎么知道把它放在哪里?这就是花括号发挥作用的地方。花括号的顺序与格式括号中变量的顺序相同。要在一个格式字符串中包含多个变量,只需用逗号分隔每个变量。让我们来看看一些例子:

# injecting variables using the format method
name = "John"
print( "Hello { }".format(name) )
print( "Hello { }, you are { } years old!".format(name, 28) )

去查查那个手机。我们会看到第一行的输出是“你好约翰”,第二行是“你好约翰,你 28 岁了”。请记住,format 函数会注入变量,甚至是数据类型本身。在这个例子中,我们注入了整数值 28。

f 字符串(Python 3.6 中的新特性)

在 Python 中将变量注入字符串的新方法是使用我们称之为 f strings 的方法。通过将字母" f "放在一个字符串的前面,你可以直接在行中将一个变量注入到一个字符串中。这一点很重要,因为当字符串变长时,它使字符串更容易阅读,这是格式化字符串的首选方法。只要记住你需要 Python 3.6 来使用它;否则你会收到一个错误。要在一个字符串中注入一个变量,只需在变量名周围加上花括号。让我们看一个例子:

# using the new f strings
name = "John"
print( f"Hello {name}" )

去查查手机。我们得到了与相同的输出。format() 方法;但是,这次读代码就容易多了。

注意

在本书中,我们将使用。format()方法。

Python 2 中的格式

Python 2 不包括。format()方法;取而代之的是,你可以使用百分比运算符来标记被注入变量的位置。下面是将变量“ name 注入“ %s ”位置的例子。百分比运算符后的字母表示数据类型。对于整数,你可以用“ %d ”来表示数字。在字符串结束后,您可以放置一个百分比运算符,后跟您想要使用的变量。让我们看一个例子:

# one major difference between versions 2 & 3
name = 'John'
print('Hello, %s' % name)

去查查那个手机。您会注意到,我们得到了与前面方法相同的输出。如果您想在 Python 2 中用多个变量格式化一个字符串,那么您需要编写以下代码:

# python 2 multiple variable formatting
first_name = "John"
last_name = "Smith"
print( "Hello, %s %s" % (first_name, last_name) )        # surround the variables in parenthesis

去查查手机。我们将得到输出“你好,约翰·史密斯”。当传递多个变量时,需要用括号将变量名括起来,并用逗号分隔。注意,字符串中还有两个符号,从左到右依次表示每个变量的位置。

字符串索引

关于字符串,我们需要理解的另一个关键概念是它们是如何存储的。当计算机将一个字符串保存到内存中时,字符串中的每个字符都被赋予一个我们称之为“索引 的符号。“索引本质上是内存中的一个位置。把索引想象成你在商场排队等候时的位置。如果你在队伍的最前面,你的索引号将会是零。你后面的人将被赋予索引位置 1。他们后面的人将被给予索引位置 2,以此类推。

注意

包括 Python 在内的大多数语言的索引都是从 0 开始,而不是从 1 开始。

Python 字符串也是如此。如果我们取一个类似“Hello”的字符串,分解它们的索引(见图 2-1 ,我们可以看到字母“ H ”位于索引零处。让我们试一个例子:

# using indexes to print each element
word = "Hello"
print( word[ 0 ] )            # will output 'H'
print( word[ 1 ] )            # will output 'e'
print( word[ -1 ] )           # will output 'o'

为了索引一个特定的元素,您可以在变量名的右边使用方括号。在那些方括号中,您放入您希望访问的索引位置。在前面的例子中,我们正在访问存储在变量“单词”中的字符串“你好”中的前两个元素。最后一行访问最后一个位置的元素。使用负索引号将导致试图从后面访问信息,例如-4 将导致字母“e”的输出。

img/481544_1_En_2_Fig1_HTML.jpg

图 2-1

字符串的索引位置

使用索引时要非常小心。索引是内存中的一个特定位置。如果你试图访问一个超出范围的位置,你会使你的程序崩溃,因为它试图访问一个不存在的内存位置。例如,如果我们试图访问“Hello”上的索引 5。

字符串切片

我想简单介绍一下切片这个话题。切片主要用于 Python 列表;但是,您也可以在字符串上使用它。切片本质上是当你只想要变量的一部分时,这样,如果我只想要单词“hello”中的“He ”,我们将编写如下:

print( word[ 0 : 2 ] )            # will output 'He'

括号中的第一个数字是起始索引;第二个是停止指数。我们将在下一周触及这个概念;然而,你可以随意摆弄切片。不过,在今天结束之前,我想快速介绍一下切片时的开始、停止和步进参数。切片的语法总是

>>> variable_name[ start : stop : step ]

在上一个单元格中,我们只包括开始和停止,因为步骤是可选的,默认情况下每次递增 1。然而,如果我们想打印每隔一个字母:

print( word[ 0 : 5 : 2 ] )         # will output 'Hlo'

去查查手机。通过将步骤作为数字 2 传递,它每次将索引增加 2,而不是 1。我们将在后面的章节中更深入地讨论这个问题;现在,让这成为对所有三个参数的切片的介绍。

周三练习

  1. 变量注入:创建一个打印语句,将整数、浮点、布尔和字符串全部注入一行。输出应该看起来像“ 23 4.5 假约翰”。

  2. 填空:使用 format 方法,通过将你的名字和喜欢的活动赋值到变量中来填写以下空格:

    "{ }'s favorite sports is { }."

    "{ } is working on { } programming!"

我们今天讨论了使用字符串、格式化和索引时的一些关键概念。明天我们将使用其他方法来帮助我们操作字符串。

周四:字符串操作

在您将要构建的许多程序中,您会希望以某种方式改变字符串。字符串操作仅仅意味着我们想要改变当前的字符串。幸运的是,Python 有大量的方法,我们可以用它们来改变字符串数据类型。

接下来,让我们从之前的笔记本文件“ Week_02 ”继续,简单地在底部添加一个 markdown 单元格,上面写着:“操纵字符串。

。标题( )

通常,你会遇到不大写的单词,这些单词通常应该是名字。title 方法将字符串中每个单词的首字母全部大写。尝试以下方法:

# using the title method to capitalize a string
name = "john smith"
print( name.title( ) )

去查查那个手机。我们得到的输出是一个" John Smith ",每个单词都有大写字母。这个方法对于正确格式化名字非常有用。

注意

尝试使用 name.lower()和 name.upper()看看会发生什么。

。替换( )

replace 方法的工作方式类似于查找和替换工具。它在括号中接受两个值,一个是它要搜索的值,另一个是它用以下内容替换搜索到的值:

# replacing an exclamation point with a period
words = "Hello there!"
print( words.replace( "!", "." ) )

去查查那个手机。这将导致“ Hello there ”的输出。

注意

为了在以后正确存储替换,我们必须重新声明我们的 words 变量: words = words.replace('!', '.').

。查找( )

find 方法将搜索我们要求它搜索的任何字符串。在本例中,我们尝试搜索整个单词,但我们可以搜索包括字符或完整句子在内的任何内容:

# finding the starting index of our searched term
s = "Look over that way"
print( s.find("over") )

去查查那个手机。你会注意到我们得到了 5 的输出。Find 返回匹配的起始索引位置。如果你计算单词“ over 开始的位置,那么“ o 在索引位置 5。当您想要访问搜索中的特定索引时,这一点非常重要。

。条状( )

如果你想去掉字符串左右两边的某个字符,你可以使用 strip 方法。默认情况下,它会删除空格。让我们尝试运行以下代码:

# removing white space with strip
name = "   john   "
print( name.strip( ) )

输出将产生“ john ”,因为我们已经移除了左侧和右侧的所有空格。

注意

试试看。lstrip()和。rstrip()看看会发生什么。

。拆分( )

我不会对 split 进行过多的描述,因为它返回的是一个列表,我们还没有涉及到这些;然而,我想让你看看如何使用这种方法。它的作用是将句子中的单词分成一组单词,存储在一个列表中。现在先不要担心列表,我们会有的。现在,让我们看看这个方法是如何工作的:

# converting a string into a list of words
s = "These words are separated by spaces"
print( s.split(" ") )

去查查手机。输出结果是单词列表“ ['这些','单词','是','分隔','分隔','空格'] ”。我们将回到这个方法以及它为什么重要。

周四练习

  1. 大写:尝试操作字符串“uppercase ”,使它打印出来的都是大写字母。你需要寻找一种新方法。

  2. 剥去符号:剥去这个字符串“$$John Smith”左边的所有美元符号。试试看。lstrip()和。剥离()。要查看有关如何进一步使用 strip 方法的描述,请尝试使用 Python 中的帮助功能,方法是键入以下内容:

    >>> help(" ".strip)
    
    

今天你学了一些操作方法,但还有更多。试着用你在网上找到的其他人做实验。

星期五:创建收据打印程序

欢迎来到你的第一个项目!我们将创建一个非常基本的收据打印程序。本周,我们已经学习了变量、操作符和字符串操作,我们将使用这些技能来创建这个程序。

接下来,让我们从我们的“ Week_02 ”笔记本继续,简单地在底部添加一个减价单元格,上面写着:“星期五项目:打印收据。

完工图纸

描绘出你想要建造的东西的设计总是好的。对于较大的项目,你会想要创建一个流程图或某种设计文档,这将使你走上正轨。这样你就不会偏离预期的结果。对我们来说,我们将用我们所学的概念构建一个小的收据打印程序,其中的输出将如图 2-2 所示。

img/481544_1_En_2_Fig2_HTML.jpg

图 2-2

星期五项目的最终结果

我们开始吧,好吗!

启发过程

无论何时开始一个项目,你都必须知道从哪里开始。无论项目大小,都有一定的依赖性。就像盖房子一样,你必须先有地基,然后才能盖屋顶。现在,这个程序大约有 50 行,几乎没有依赖关系,所以我们将从顶部边框开始,一路向下到底部。

定义我们的变量

在减价标题下方的单元格中,让我们开始定义我们将在整个计划中使用的变量:

1| # create a product and price for three items
2| p1_name, p1_price = "Books", 49.95
3| p2_name, p2_price = "Computer", 579.99
4| p3_name, p3_price = "Monitor", 124.89

我总是喜欢在构建这些周五项目时引入新概念,因为实现好的编码技术是有好处的。该块中引入的技术是在同一行声明多个变量的能力。为此,我们只需用逗号分隔变量名及其相关值。看前面声明的两个变量,“ Books 的值会保存到变量名“ p1_name ”,值“ 49.95 ”会保存到变量名“ p1_price ”。我们已经把程序减少了一半,而不是写六行。我们用的线越少越好(多数时候)。像 x 和 y 这样的变量,或者在我们的例子中,一个名字和价格,是在一行中声明相关变量的好例子。

接下来,让我们在收据的顶部定义公司将使用的变量。这个项目的所有代码可以在单个单元格中完成,或者您可以将单元格分开。这取决于你。我提供了行号,以防您跟踪单个单元格:

6| # create a company name and information
7| company_name = "coding temple, inc."
8| company_address = "283 Franklin St."
9| company_city = "Boston, MA"

例如,我们将公司名称全部小写,这样我们可以使用字符串操作方法来解决这个问题。

最后,让我们在收据的底部声明我们将输出给用户的消息:

11| # declare ending message
12| message = "Thanks for shopping with us today!"

去查查手机。现在我们已经定义了所有的变量,我们可以继续了。

创建顶部边框

正如我们可以从这个项目开始时的设计中看到的,我们需要在顶部和底部打印出一个边框。让我们从顶部边框开始:

14| # create a top border
15| print( "*" * 50 )

去查查手机。这里应用了一个新概念,我们写 "*" * 50 。我们所要做的就是打印出 50 个星星作为上边框,而不是打印 50 条语句,我们可以简单地将字符串乘以我们想要的数字。这样我们得到了顶部边框,同时保持代码简洁易读。代码的可读性始终是关键。

显示公司信息

我们已经在前面几行中为公司定义了变量,所以让我们显示它们:

17| # print company information first, using format
18| print( "\t\t{ }".format( company_name.title( ) )
19| print( "\t\t{ }".format(company_address) )
20| print( "\t\t{ }".format(company_city) )

去查查手机。起初,这些打印语句可能看起来有点难以理解;不过,我给你介绍一个逃兵角色。转义字符由定义反斜杠 "" 字符读入。反斜杠后面的内容是计算机将解释的内容。在三个打印语句中,我们使用 "\t" 作为制表符缩进。你可能会看到的另一个流行的转义字符是 "\n" ,它的意思是换行,就像你按下回车键一样。我们在一行中使用两个转义字符,使其在输出中居中。让我们创建一个分割线:

22| # print a line between sections
23| print( "=" * 50 )

去查查手机。就像我们打印上边框一样,我们将等号乘以 50 来创建相同宽度的线条。这将给出单独部分的外观。

显示产品信息

查看我们的原始设计,我们希望在列出每个产品的名称和价格之前创建一个标题。这可以通过使用我们的转义字符进行缩进来实现:

25| # print out header for section of items
26| print("\tProduct Name\tProduct Price")

去查查手机。由于标题名称的大小,我们只需要在每个标题前使用一个制表符。现在,我们可以为每个产品的信息输出一行:

28| # create a print statement for each product
29| print( "\t{ }\t\t${ }".format(p1_name.title( ), p1_price) )
30| print( "\t{ }\t\t${ }".format(p2_name.title( ), p2_price) )
31| print( "\t{ }\t\t${ }".format(p3_name.title( ), p3_price) )

去查查手机。我们使用与之前的打印声明相似的样式,以便将每个产品的标题和价格放在各自标题的中心。尽量不要被打印字符串中的所有符号搞糊涂;您可以简单地将它们分解成一个制表符,然后是格式化为字符串的第一个变量,接着是两个制表符,接着是美元符号(以使价格看起来像货币),然后是格式化为字符串的第二个变量。这就完成了我们产品的部分,所以让我们放入另一个部分分隔线:

33| # print a line between sections
34| print('=' * 50)

去查查手机。这将为我们的下一部分设置显示总数。

显示总数

像 products 部分一样,我们希望为我们的合计创建一个标题,但是我们还希望它位于 products 部分的 price 列的中心。为此,我们将使用三个选项卡:

36| # print out header for section of total
37| print("\t\t\tTotal")

去查查手机。现在我们已经将合计标题与 products 中的 price 列对齐,我们可以在下一行输出合计。然而,在我们可以打印出总数之前,我们必须首先计算总数,它是我们所有产品的总和。让我们定义一个名为 total 的变量,然后将其打印出来:

39| # calculate total price and print out
40| total = p1_price + p2_price + p3_price
41| print( "\t\t\t${ }".format(total) )

去查查手机。同样,我们已经添加了三个选项卡,加上一个美元符号,使总值显示为货币。现在让我们添加一个部分边界:

43| # print a line between sections
44| print( "=" * 50)

继续运行单元格,以确保它看起来像目前为止期望的输出。

显示结束消息

为了显示最后的感谢信息,我们的设计使它的间距比任何其他部分都稍大,所以我们需要添加几个换行符来给它一些额外的间距:

46| # output thank you message
47| print( "\n\t{ }\n".format(message) )

去查查手机。我们的信息现在集中了,我们准备继续前进。

显示底部边框

为了完成这个简单的打印程序,为了美观,我们需要添加一个底部边框:

49| # create a bottom border
50| print( "*" * 50 )

继续最后一次检查手机。

恭喜你!尽管可能很简单,但这是一个巨大的里程碑。在学习了更多的材料之后,试着回到这里来改进它。

每周总结

本周,我们复习了变量编程和字符串处理中一些非常重要的基础概念。您必须始终记住,变量需要在使用之前声明,并且相关的名称与等号运算符右侧的值一起保存在内存中。Python 中的字符串很容易处理,因为这种语言有各种各样的方法,我们可以调用这些方法来完成这项工作。在周末,我们能够构建一个简单的收据打印程序。试试破解程序!我总是鼓励学生尝试破坏程序,因为这将教会你如何修复它。

挑战问题解决方案

做 PB&J 三明治没有一个确定的解决方案,但是我希望你回去看看你是否还不够具体。计算机的智能取决于我们对它们的编程,所以如果你说把花生酱放在面包上,它可能会理解为把整个罐子放在面包上。作为一名开发人员,你需要对你的描述进行具体化。甚至尝试用改进的步骤重写一个新算法。

每周挑战

要测试你的技能,请尝试以下挑战:

  1. 侧边框:在周五的项目中,我们最终在打印出来的信息的上方和下方创建了边框。现在试着在边上加一个星形边框。

  2. 研究方法:我们已经讨论了一些广泛使用的字符串操作方法;然而,还有很多;试着查找一些并实现它们。

  3. 反转:声明一个等于“Hello”的变量。使用切片反转字符串。纠结的话试着查一下。

小费

切片时,可以定义开始、停止和步进。**

三、用户输入和条件

欢迎来到第 3 周!本周我们将介绍如何在我们的程序中处理用户输入和做出决策。这些决定被称为分支语句或条件。如果你每天都在思考你的生活,你在不知道的情况下根据具体情况做出决定,比如早上什么时候起床,午餐吃什么,什么时候吃饭等等。这些被称为分支语句。这同样适用于编程,我们需要让计算机做决定。

概述

  • 使用用户输入

  • 如何使用“如果”语句来做决策

  • 如何使用“elif”语句做出多个决策

  • 无论如何,如何使用“else”语句来做决定

  • 构建具有决策和用户输入的计算器

挑战问题

本周的挑战是测试你阅读代码的能力。我希望你阅读代码块,并考虑它是否会工作。如果你认为它不起作用,我希望你记下它为什么不起作用。既能读又能写很重要:

>>> print('{} is my favorite sport'.format(element))
>>> element = 'Football'

写下答案后,继续在单元格内运行代码。如果你的答案是不正确的,试着分析你为什么。答案会在本章末尾。

星期一:用户输入和类型转换

在今天的课程中,我们将介绍与用户交互的能力和一个叫做类型转换的概念。这些对于理解如何在周末构建计算器是必要的。

为了继续今天的内容,让我们从“ python_bootcamp ”文件夹中打开 Jupyter 笔记本。打开后,创建一个新文件,并将其重命名为“ Week_03。“接下来,对第一个标题为:“用户输入&类型转换”的单元格进行降价。“我们将开始在那个牢房下面工作。

接受用户输入

在我们将要创建的许多程序中,你需要接受用户输入。为此,我们需要使用 input() 函数。像 print 函数一样,input 将打印括号内的字符串,但它也会创建一个框供用户输入信息。让我们看一个例子:

# accepting and outputting user input
print( input("What is your name? ") )

去查查那个手机。您会注意到,无论您在框中写什么,单元格都会输出。当解释器遇到输入功能时,它会暂停,直到按下 enter 键。

注意

输入的信息以字符串形式输入程序。

存储用户输入

在前一个单元格中,我们只是打印出用户输入的内容。但是,为了处理他们输入的数据,我们需要将其存储到一个变量中:

# saving what the user inputs
ans = input("What is your name? ")
print("Hello { }!".format(ans) )

去查查那个手机。存储用户在程序中输入的信息就像存储到变量中一样简单。这样我们就可以随时处理他们输入的数据。

什么是类型转换?

Python 定义了类型转换函数,直接将一种数据类型转换成另一种数据类型,这在日常和竞争性编程中非常有用。在某些情况下,您正在处理的数据可能不是正确的类型。最明显的例子是用户输入,因为无论用户输入什么,输入都被当作字符串。如果您希望输入一个数字,您需要将输入转换为整数数据类型,以便能够处理它。

检查类型

在我们讨论如何进行类型转换之前,我想先介绍一下 Python 的一个重要函数,它允许我们检查任何给定变量的类型:

# how to check the data type of a variable
num = 5
print( type(num) )

去查查那个手机。这里的输出将是“ <类‘int’>”。不要担心这里的课程,我们会在下一周上课。重点放在第二部分,它将类型输出为整数。这允许我们检查使用什么数据类型。

转换数据类型

Python 让我们能够通过简单地将类型包装在变量周围,轻松地将类型从一种类型转换为另一种类型。让我们来看一个将字符串转换成整数的例子:

# converting a variable from one data type to another
num = "9"
num = int(num)     # re-declaring num to store an integer
print( type(num) )   # checking type to make sure conversion worked

去查查那个手机。我们刚刚将字符串 "9" 转换为一个整数。现在我们可以在任何计算中使用变量 num。为了正确地进行转换,我们使用了 int() 类型转换。放在括号内的任何数据类型都被转换为 int。查看表 3-1 了解如何从一种数据类型转换到另一种数据类型。

表 3-1

转换数据类型

|

当前类型

|

数据值

|

转换为

|

适当的代码

|

输出

|
| --- | --- | --- | --- | --- |
| 整数 | nine | 线 | 字符串(9) | '9' |
| 整数 | five | 浮动 | 浮子(5) | Five |
| 浮动 | Five point six | 整数 | int(5.6) | five |
| 线 | ‘9’ | 整数 | int('9 ') | nine |
| 线 | “真的” | 布尔代数学体系的 | 布尔("真") | 真实的 |
| 布尔代数学体系的 | 真实的 | 整数 | int(True) | one |

正如你所看到的,有几种方法可以将类型转换为;您只需要为每个定义的数据类型使用关键字。布尔类型 True 转换为整数 1,因为 True 和 False 值分别表示 1 和 0。此外,将浮点数转换为整数只会截断小数,以及小数右边的任何数字。

注意

并非所有数据类型都可以正确转换。这是有限度的。

转换用户输入

让我们尝试处理用户的输入,以便向他们键入的任何内容添加 100:

# working with user input to perform calculations
ans = input("Type a number to add: ")
print( type(ans) )    # default type is string, must convert
result = 100 + int(ans)
print( "100 + { } = { }".format(ans, result) )

去查查那个手机。输入数字“ 9 ”会给我们一个合适的结果;然而,这种转换不能很好地与单词“九”一起工作,因为输入的默认返回类型是一个字符串,如该单元格中的第一个 print 语句所示。

处理错误

在最后一个单元格中,我们将用户输入转换为整数;然而,如果他们用一个词来代替呢?程序会马上中断。作为一名开发人员,我们必须假设用户不会提供我们期望他们提供的正确信息。为了解决这个问题,我们将引入 try 和 except 块。Try 和 except 用于捕捉错误。它的工作原理是尝试运行 try 块中的内容;如果它没有产生错误,那么它会继续运行,不会碰到 except 块;但是,如果发生错误,except 块中的代码将运行。这是为了确保当出现错误时,你的程序不会停止运行。这是处理错误的通用方法;还有许多其他方法,比如使用函数 isalpha()isalnum() 。让我们看一个使用 try 和 except 块的例子:

# using the try and except blocks, use tab to indent where necessary
try:
      ans = float( input("Type a number to add: ") )
      print( "100 + { } = { }".format(ans, 100 + ans) )
except:
      print("You did not put in a valid number!")
# without try/except print statement would not get hit if error occurs
print("The program did not break!")

去查查那个手机。尝试输入不同的答案,包括非数字。您会注意到,如果您不输入数字,我们的无效打印语句将会输出。如果我们没有 try 和 except,程序就会中断,最后一个 print 语句也不会出现。

代码块和缩进

在大多数其他编程语言中,缩进只是用来帮助代码看起来漂亮。但是对于 Python 来说,它是指示代码块所必需的。让我们从“处理错误一节中取出之前的代码。try 语句后的两行是缩进的,称为代码块。这些行属于 try 语句,因为它们直接在语句后缩进。except 块中的另一个 print 语句也是如此。这就是为什么我们的无效打印语句只在 except 块运行时才运行。所有代码块都需要连接到一个语句;不能随意缩进一节。

注意

缩进必须一致。它并不总是需要四个空格;然而,一个制表符有四个空格,所以用制表符缩进通常更容易。

周一练习

  1. 转换:试着将一串“ True 转换成一个布尔值,然后输出它的类型以确保正确转换。

  2. 输入总和:创建两条输入语句,要求用户输入两个数字。把这些数字的总和打印出来。

  3. 汽车信息:要求用户输入汽车的年份、品牌、型号和颜色,并打印一份格式良好的声明,如“2018 蓝色雪佛兰 Silverado”

今天是涵盖用户输入、如何从一种数据类型转换到另一种数据类型以及如何处理错误的重要一步。

周二:If 声明

今天我们将学习如何在代码中做决定。这将使我们能够让我们的程序根据用户的输入、计算等来决定运行哪一行代码。这是本周最重要的一课。一定要花大量的时间来学习今天的课程。

为了跟上这一课,让我们从之前的笔记本文件“ Week_03 ”继续,并在底部添加一个标有“ If 语句”的单元格。

它们是如何工作的

每天你都要做上百个决定。这些决定决定了你一天要做什么。在编程中,这些被称为分支语句“if 语句”if 语句的工作方式与做出决策的方式相同。您检查一个条件,如果该条件为真,则执行该任务,如果不为真,则不执行该任务继续前进:

“我饿了吗?”

是的,所以我应该做些食物

继续烹饪食物

使用 if 语句可以在编程中实现相同的决策过程。

编写第一条 If 语句

所有分支语句都以同样的方式开始,用关键字“ if ”。关键字后面是所谓的条件。最后,在语句的末尾总会有一个结束冒号。 if 语句检查给定条件是还是。如果条件为真,则代码块运行。如果为 False,则程序继续运行,不运行任何直接缩进在 If 语句后面的代码。让我们试一试:

# using an if statement to only run code if the condition is met
x, y = 5, 10
if x < y:
      print("x is less than y")

去查查那个手机。注意这里的输出是“x 小于 y”。这是因为我们最初声明 x 等于 5,y 等于 10,然后使用 if 语句来检查 x 是否小于 y,它确实小于 y。如果 x 等于 15,那么缩进在“if”之后的打印语句将永远不会运行,因为条件将为假。

比较运算符

在我们继续分支语句之前,我们需要回顾一下比较运算符。到目前为止,我们已经使用了算术运算符来加减值,使用赋值运算符来声明变量,随着“if 语句”的引入,我们现在已经看到了比较运算符。你可以做几个比较。然而,您将使用的大多数比较运算符如表 3-2 所示。

表 3-2

比较运算符

|

操作员

|

情况

|

功能

|

例子

|
| --- | --- | --- | --- |
| == | 平等 | 如果 x == y: | 如果 x 等于 y … |
| != | 不平等 | 如果 x!= y: | 如果 x 不等于 y… |
| > | 大于 | 如果 x > y: | 如果 x 大于 y… |
| < | 不到 | 如果 x < y: | 如果 x 小于 y… |
| >= | 大于或等于 | 如果 x >= y: | 如果 x 大于或等于 y… |
| <= | 小于或等于 | 如果 x <= y: | 如果 x 小于或等于 y… |

注意

w3 Schools 1 提供了大量的参考资料,可以了解许多不同类型的运营商的更多信息。

检查用户输入

我们新学到的条件语句的一个很大的用途是检查用户输入。让我们试试:

# checking user input
ans = int( input("What is 5 + 5? ") )
if ans == 10:
      print("You got it right!")

去查查那个手机。我们的条件语句检查用户的输入是否等于整数 10。如果是,那么缩进的 print 语句将运行。注意第二行,我们要求用户输入,并立即将他们的答案转换成整数。由于除了之外,我们没有使用 try,输入非数字会导致错误。

逻辑运算符

逻辑运算符用于组合条件语句。您可以在一个“if 语句”中写尽可能多的条件。根据所使用的逻辑运算符,if 语句可能会运行,也可能不会运行。让我们看看我们可以使用的三个逻辑运算符。

逻辑运算符“与”

逻辑运算符是为了确保当你检查多个条件时,条件的两边都为真。这意味着如果左边或右边的条件为假,那么代码将不会运行该代码块。让我们试一个例子:

# using the keyword 'and' in an 'if statement'
x, y, z = 5, 10, 5
if x < y and x == z:
      print("Both statements were true")

去查查那个手机。输出将导致“两个语句都为真”,因为 x 小于 y,并且与 z 的值相同。

注意

您可以在一行中包含任意多的条件。

逻辑运算符“或”

逻辑运算符用于检查的一个或两个条件是否为真。使得如果左边的条件为假,而右边的条件为真,则代码块仍将运行,因为至少有一个条件为真。只有当两个条件都为假时,“if 块”才不会使用运算符运行。让我们来看一个例子:

# using the keyword 'or' in an 'if statement'
x, y, z = 5, 10, 5
if x < y or x != z:
      print("One or both statements were true")

去查查那个手机。请注意,我们得到了输出“一个或两个陈述为真”。即使我们的第二个条件为假,这仍然有效,因为 x 等于 z,我们检查它是否不等于它;但是,由于左边的条件为真,所以它运行。

逻辑运算符“非”

在某些情况下,您可能希望检查值的反义词。“not”操作符就是为此而使用的。它本质上返回与当前值相反的值。让我们试一试:

# using the keyword 'not' within an 'if statement'
flag = False
if not flag:                  # same as saying if not true
      print("Flag is False")

去查查那个手机。您会注意到结果输出是“Flag is False”。这是由于“非”操作符,它取 False 的相反值,并使条件返回 True。

注意

如果我们写“ if flag == False: ”,我们会得到相同的结果。

成员运算符

成员运算符用于测试序列是否出现在对象中。有两个关键字可以用来检查一个值是否存在于一个对象中。我们去看看。

成员运算符“in”

当你想检查一个给定的对象是否有一个值出现在其中,你可以使用操作符中的。最好的用例是检查字符串中的某个值。让我们来看一个例子:

# using the keyword 'in' within an 'if statement'
word = "Baseball"
if "b" in word:
      print( "{ } contains the character b".format(word) )

去查查那个手机。结果输出是“棒球包含字符 b”。这是区分大小写的,但幸运的是,单词 Baseball 有一个小写字母和一个大写字母 b。

成员运算符“不在”

同样,如果你想检查一个对象是否不包含一个特定的值,你可以使用 "not in" 操作符。本质上,这只是检查与“操作符中的“??”相反的操作。让我们看看:

# using the keyword 'not in' within an 'if statement'
word = "Baseball"
if "x" not in word:
      print( "{ } does not contain the character x".format(word) )

去查查那个手机。得到的输出是“棒球不包含字符 x”。它只是检查字符 x 是否不包含在我们的 word 变量的字符串值中。

星期二练习

  1. 检查包含性-第一部分:要求用户输入,并检查他们所写的内容是否包含“ es ”。

  2. 检查包含性-第二部分:要求用户输入,并检查他们所写的内容是否在结尾有一个“ ing ”。提示:使用切片。

  3. 检查相等:让用户输入两个单词,写一个条件语句检查两个单词是否相同。不区分大小写,这样大写字母就不重要了。

  4. 返回指数:要求用户输入一个数,如果小于 10,返回该数的平方。提示:研究指数的算术表达式。

今天是关于条件语句的重要一课。对任何程序来说,让计算机做出决定并根据决定采取行动的能力都是非常重要的。

星期三:Elif 声明

条件语句给了我们在程序中做决定的权力,但是到目前为止,我们只看到了它们的一瞥。今天,我们将学习所有关于 elif 语句的知识。它们让我们能够根据情况运行单独的代码块。它们也被称为 else if 语句。

为了继续本课,让我们从之前的笔记本文件“ Week_03 ”继续,并在底部添加一个标有“ Elif 语句”的降价单元格。

它们是如何工作的

正如我们在上一课中看到的,条件语句赋予我们在程序中做决定的能力;然而,你如何处理多重决策?在 Python 中,我们使用 elif 语句来声明基于给定条件的另一个决策。 Elif 语句必须与一个 if 语句相关联,这意味着没有 if 就不能创建一个 elif 。Python 按照从上到下的顺序工作,所以它检查第一个 if 语句;如果该语句为假,则继续第一个 elif 语句并检查该条件。如果该条件也返回 False,它将继续执行下一个条件语句,直到不再需要检查。但是,一旦单个条件语句返回 True,所有其他条件都会被跳过,即使它们为 True。它的工作原理是,第一个返回 True 的条件是唯一运行的代码块。

撰写您的第一份 Elif 陈述

创建一个 elif 语句与一个 if 语句是相同的,除了一点不同,您使用了 elif 关键字。你也可以为每个 elif 设置多个条件。让我们来试试:

# using the elif conditional statement
x, y = 5, 10
if x > y:
      print("x is greater")
elif x < y:
      print("x is less")

去查查那个手机。注意,输出是“x 较小”。它检查了初始的 if 语句,但是由于返回 False,它继续执行 elif 条件语句。该语句返回 True,其中的代码块运行。

检查多个 Elif 条件

基于单个变量编写多个决策的能力是必要的,这也是构建 elif 语句的原因。以下面的代码为例:

# checking more than one elif conditional statement
x, y = 5, 10
if x > y:
      print("x is greater")
elif (x + 10) < y:                    # checking if 15 is less than 10
      print("x is less")
elif (x + 5) == y:                    # checking if 10 is equal to 10
      print("equal")

去查查那个手机。得到的输出是“等于”。第一个 ifelif 语句都返回 False,但是第二个 elif 语句返回 True,这就是该代码块运行的原因。您可以拥有任意多的elif,但是它们必须与一个 if 语句相关联。

注意

在条件中,我们执行加法,但是我们把它放在括号中,这样它首先执行数学运算。

条件句中的条件句

我们已经了解了 Python 如何使用缩进来分隔代码块。到目前为止,我们只看到了一个缩进层次,但是如果我们在一个 if 语句中添加一个 if 语句会怎么样呢?

# writing multiple conditionals within each other - multiple block levels
x, y, z = 5, 10, 5
if x > y:
      print("greater")
elif x <= y:
      if x == z:
              print("x is equal to z")       # resulting output
      elif x != z:
              print("x is not equal to z")   # won't get hit

去查查那个手机。输出结果在“x 等于 z”。分解一下,最初的 if 语句返回 False,下一个 elif 语句返回 True,所以它运行那个块。现在,该块内部是另一个条件语句,所以它检查返回 True 的第一个 if 语句,并运行其中的代码块。

If 语句与 Elif 语句

您需要理解的一个主要区别是使用 elif 语句而不是使用多个 if 语句。所有的 elif 语句都连接到一个原始的 if 语句,这样一旦一个条件为真,其余的就不会运行。让我们看一个例子:

# testing output of two if statements in a row that are both true
x, y, z = 5, 10, 5
if x < y:
      print("x is less")
if x == z:
      print("x is equal")

去查查那个手机。注意,这里产生的输出都是打印语句。这部分是因为有两个 if 语句。这些 if 语句互不相关;它们是独立的条件语句,而一个 elif 总是连接到一个 if

# testing output of an if and elif statement that are both true
x, y, z = 5, 10, 5
if x < y:
      print("x is less")
elif x == z:
      print("x is equal to z")

去查查那个手机。注意,这里的输出只有“x 小于”,不包括第二个打印语句。那是因为一个 elif 附加在一个 if 语句上,一旦其中一个条件返回 True,那么其他所有条件即使本身为 True 也不会被检查。

周三练习

  1. 升高/降低:要求用户输入一个数字。键入 convert 这个数字,并使用 if/elif 语句打印它是大于还是小于 100。

  2. 寻找解决方案:给定下面的代码,修复任何/所有错误,使其输出“更低”:

x, y = 5, 10
if x > y:
      print("greater")
try x < y:
      print("lower")

今天是创建一个为我们做决定的程序的第二步,不是一个决定,而是多个决定。

星期四:Else 语句

任何好的决策的第三也是最后一部分是默认情况下做什么。在 Python 中,我们知道它们是 else 语句。今天的课很短,但对进一步理解条件语句是必要的。

为了继续本课,让我们从之前的笔记本文件“ Week_03 ”继续,并在底部添加一个标有“ Else 语句”的降价单元格。

它们是如何工作的

Else 条件语句是end all beif 语句的 all 。有时你无法为你想做的每个决定创造条件,所以这就是 else 语句有用的地方。 else 语句将涵盖所有未涵盖的其他可能性,并且如果程序到达该处,将始终运行该代码。这意味着如果一个 elif 或 if 语句返回 True,那么它将永远不会运行else;然而,如果它们都返回 False,那么无论什么时候,else 子句都会运行。同样,在代码中更容易看到它;我们试试吧!

编写第一条 Else 语句

像一个 elif 语句一样, else 子句需要总是与一个原始的 if 语句相关联。 else 子句涵盖了所有其他可能性,所以根本不需要写条件;您只需要提供关键字“else”,后跟一个结束冒号。请记住,如果程序到达语句,else 子句将运行其中的代码。尝试以下方法:

# using an else statement
name = "John"
if name == "Jacob":
      print("Hello Jacob!")
else:
      print("Hello { }!".format(name) )

去查查那个手机。注意这里的输出是“你好 John”。第一个 if 语句返回 False,所以一旦它到达 else 子句,就会运行其中的打印语句。

完整的条件语句

现在我们已经讨论了条件语句的所有三个部分,让我们继续尝试在一个语句中同时使用这三个部分:

# writing a full conditional statement with if, elif, else
name = "John"
if name[0] == "A":
      print("Name starts with an A")
elif name[0] == "B":
      print("Name starts with a B")
elif name[0] == "J":
      print("Name starts with a J")
else:                                 # covers all other possibilities
      print( "Name starts with a { }".format( name[0] ) )

去查查那个手机。得到的输出是 "Name 以 J" 开头,由第二个 elif 语句输出。第一个 if 和 elif 语句返回 False,所以它们的代码块没有运行。一旦第二个 elif 语句返回 True 并运行它自己的代码,那么 else 语句将被跳过而不运行。记住索引0 开始,所以通过在名称变量后使用括号符号访问字符串内的第一个元素。

注意

如果您在理解括号符号方面有困难,请务必回头查看关于字符串索引的部分。

周四练习

  1. 修复错误:给定以下代码,修复任何/所有错误,使其正确输出“你好约翰”:

    >>> name = "John"
    >>> if name == "Jack":
    >>>           print("Hello Jack")
    >>> elif:
    >>>>          print("Hello John")
    
    
  2. 用户输入:要求用户输入不带冒号的军用时间(1100 =上午 11:00)。编写一个条件语句,使其输出以下内容:

    1. 早上好如果不到 1200

    2. "下午好"如果在 1200 和 1700 之间

    3. "晚上好"如果等于或高于 1700

今天我们学习了所有关于 else 语句的内容。现在,您可以构建能够在给定条件下生成代码的程序。

星期五:创建计算器

上周我们一起开发了一个收据打印程序。根据本周的经验,我们将构建一个简单的计算器,它接受用户输入并输出正确的结果。

为了跟上这一课,让我们从之前的笔记本文件“ Week_03 ”继续,并在底部添加一个减价单元格,内容为“星期五项目:创建一个计算器”。

完工图纸

每个星期,我们总是想设计出最终的设计。由于本周的主题是逻辑而非外观,我们将列出构建计算器的必要步骤:

  1. 询问用户他们想要执行的计算。

  2. 向用户询问他们想要运行操作的号码。

  3. 为数学运算设置 try/except 子句。

    1. 将数字输入转换成浮点数。

    2. 执行操作并打印结果。

    3. 如果遇到异常,打印错误。

步骤#1:要求用户进行计算

对于其中的每一步,让我们将代码放在单独的单元格中。这将允许我们对项目的具体步骤进行分段,使得测试每个步骤变得更加容易。第一步是要求用户输入要执行的数学运算(加、减等。):

# step 1: ask user for calculation to be performed
operation = input("Would you like to add/subtract/multiply/divide? ").lower( )
print( "You chose { }.".format(operation) )   # for testing purposes

去查查那个手机。根据用户输入的内容,您的输出将打印他们选择的内容。您会注意到,在我们接受输入的那一行,我们也立即将其转换为小写。这是为了避免以后出现区分大小写的问题。我们的打印声明仅用于对该单元进行测试,稍后将被删除。

第二步:询问数字,提醒订单问题

在步骤#1 下面的单元格中,我们需要创建逻辑的下一步。这里,我们要求用户输入几个数字,并输出这些数字以供测试之用:

# step 2: ask for numbers, alert order matters for subtracting and dividing
if operation == "subtract" or operation == "divide":
      print( "You chose { }.".format(operation) )
      print("Please keep in mind that the order of your numbers matter.")
num1 = input("What is the first number? ")
num2 = input("What is the second number? ")
print( "First Number: { }".format(num1) )   # for testing purposes
print( "Second Number: { }".format(num2) )   # for testing purposes

去查查那个手机。请注意,我们在 print 语句中警告用户,如果他们选择减法或除法,数字的顺序很重要。这很重要,因为 num1 总是在操作符的左边(在我们的程序中是 ??),这有很大的不同。

注意

如果出现未定义的错误,请重新运行上一个单元格。

步骤 3:设置数学运算的 Try/Except

第三步,也是最后一步,是尝试执行操作。这里设置 try/except 块的原因是因为我们必须将用户的输入转换成浮点数据类型。我们必须假设他们可能没有输入正确的输入。让我们看看这个细胞是如何工作的:

# step 3: setup try/except for mathematical operation
try:
       # step 3a: immediately try to convert numbers input to floats
       num1, num2 = float(num1), float(num2)
       # step 3b: perform operation and print result
       if operation == "add":
               result = num1 + num2
               print( "{ } + { } = { }".format(num1, num2, result) )
       elif operation == "subtract":
               result = num1 - num2
               print( "{ } - { } = { }".format(num1, num2, result) )
       elif operation == "multiply":
               result = num1 * num2
               print( "{ } * { } = { }".format(num1, num2, result) )
       elif operation == "divide":
               result = num1 / num2

               print( "{ } / { } = { }".format(num1, num2, result) )
       else:
               # else will be hit if they didn't chose an option correctly
               print("Sorry, but '{ }' is not an option.".format(operation) )
except:
       # steb 3c: print error
       print("Error: Improper numbers used. Please try again.")

去查查那个手机。这里发生了很多事情,所以让我们从头开始。我们设置了一个 try 块,并立即将用户的输入转换成浮点数。如果这导致一个错误,那么 except 子句将被命中并输出一个错误发生而不是程序中断。如果输入可以转换,那么我们设置一个 if/elif/else 语句来执行计算并输出正确的结果。如果他们没有输入正确的操作,我们会让他们知道。此单元格依赖于前两个。如果出现错误,请重新运行以前的单元格。

最终输出

既然我们已经在三个独立的单元中为我们的程序创建了逻辑,现在我们可以把它们放在一个单元中。让我们删除所有的测试打印语句。实际上,您可以将三个单元格中的所有代码粘贴到一个单元格中,结果如下:

# step 1: ask user for calculation to be performed
operation = input("Would you like to add/subtract/multiply/divide? ").lower( )
# step 2: ask for numbers, alert order matters for subtracting and dividing
if operation == "subtract" or operation == "divide":
      print( "You chose { }.".format(operation) )
      print("Please keep in mind that the order of your numbers matter.")
num1 = input("What is the first number? ")
num2 = input("What is the second number? ")
# step 3: setup try/except for mathematical operation
try:
      # step 3a: immediately try to convert numbers input to floats
      num1, num2 = float(num1), float(num2)
      # step 3b: perform operation and print result
      if operation == "add":
            result = num1 + num2
            print( "{ } + { } = { }".format(num1, num2, result) )
      elif operation == "subtract":
            result = num1 - num2
            print( "{ } - { } = { }".format(num1, num2, result) )
      elif operation == "multiply":
            result = num1 * num2

            print( "{ } * { } = { }".format(num1, num2, result) )
      elif operation == "divide":
            result = num1 / num2
            print( "{ } / { } = { }".format(num1, num2, result) )
      else:
            # else will be hit if they didn't chose an option correctly
            print("Sorry, but '{ }' is not an option.".format(operation) )
except:
      # steb 3c: print error
      print("Error: Improper numbers used. Please try again.")

去查查那个手机。现在,您可以运行单个单元来让我们的程序从头到尾正常工作。它并不完美,但它给了你进行简单计算的能力。一如既往的尝试打破程序,改一句绕一句,变成自己的。

祝贺你完成了另一个项目!尽管这个计算器可能很简单,但我们已经展示了使用逻辑、接受用户输入并转换它以及检查错误的能力。

每周总结

多好的一周啊!我们刚刚看到了如何与用户进行交互,以及如何执行分支语句。这将允许我们用逻辑构建项目,它将根据程序使用的信息执行特定的代码。这里要记住的最重要的概念是我们的条件语句和 try/except 块。了解捕捉错误和导致程序崩溃的错误之间的区别很重要。我们总是想尽可能地捕捉错误,以确保我们的程序。下周我们将学习循环,以及如何一遍又一遍地连续运行代码块,直到我们不想再运行为止。

挑战问题解决方案

如果您要运行质询问题的代码块,您会发现它会产生一个错误。这是因为我们试图在声明“元素”变量之前访问它。如果你要颠倒这两条线,程序将按预期工作。

每周挑战

要测试你的技能,请尝试以下挑战:

  1. 反转数字:改变计算器项目,使数字的顺序无关紧要。有几种方法可以得到相同的结果;一种方法是询问用户是否愿意颠倒数字的位置。

  2. 年龄组:要求用户输入自己的年龄。根据他们的输入,输出以下组之一:

    1. 介于 0 和 12 之间= "Kid "

    2. 13 至 19 岁之间=“青少年”

    3. 20 至 30 岁之间=“年轻人”

    4. 31 至 64 岁之间=“成人”

    5. 65 岁或以上=“高级”

  3. 基于文本的 RPG :这是一个开放式的练习。创建一个基于文本的 RPG,并有一个故事线。你接受用户的输入,给他们几个选择,根据他们的选择,他们可以选择不同的道路。根据故事的长度,你将使用几个分支语句。

四、列表和循环

在这一周里,我将介绍一种叫做“列表的新数据类型和一种叫做“循环的新概念列表将赋予我们存储大型数据集的能力,而循环将允许我们重新运行部分代码。

这两个主题是一起介绍的,因为列表很适合循环。尽管列表是 Python 中最重要的数据类型之一,但在介绍它们之前,我们需要理解数据类型和分支语句的基础。到这个周末,我们将有必要的工具来建立一个小规模的刽子手游戏。我们将使用我们在前几周和本周学到的所有概念。

通过应用和重复,每次介绍一个概念时,你都能进一步理解它。如果你还没有一个概念,重要的是要坚持下去,不要停留在单一的课程上。

概述

  • 了解列表数据类型

  • 如何以及为什么使用 for 循环

  • 如何以及为什么使用 while 循环

  • 了解如何使用列表

  • 一起创造刽子手

挑战问题

想象你是一个大城市的市长。对于这个例子,让我们假设主要城市是马萨诸塞州的波士顿。你刚刚收到警报,你需要撤离这座城市。你首先做什么?

星期一:列表

今天我们将介绍 Python 中最重要的数据类型之一,列表。在其他语言中,它们也被称为“数组,具有类似的特征。这是你学习的第一个数据收集。在接下来的几周里,我们将看到其他数据收集类型。

为了继续今天的内容,让我们从“ python_bootcamp ”文件夹中打开 Jupyter 笔记本。打开后,创建一个新文件,并将其重命名为“ Week_04 接下来,制作第一个标题为:“列出了的单元格我们将开始在那间牢房下面工作。

什么是列表?

一个列表是 Python 中的一个数据结构,是元素的可变有序序列。可变意味着你可以改变里面的项目,而有序序列是指索引位置。列表中的第一个元素总是位于索引 0 。列表中的每个元素或值被称为一个项目。正如字符串被定义为引号之间的字符一样,列表是通过在方括号 [ ] 之间使用不同的数据类型来定义的。同样,像字符串一样,列表中的每一项都被分配了一个索引或位置,用于该项在内存中的保存位置。列表也被称为数据集合。数据集合只是可以存储多项的数据类型。在后面的章节中,我们会看到其他的数据集合,比如字典和元组。

声明数字列表

对于我们的第一个列表,我们将创建一个只填充数字的列表。定义一个列表就像任何其他数据类型一样;运算符的左边是变量的名称,右边是值。这里的区别在于,值是一组在方括号中声明的项。这对于存储类似的信息很有用,因为您可以轻松地传递一个存储多个元素的变量名。为了分隔列表中的每一项,我们简单地使用逗号。让我们试试:

# declaring a list of numbers
nums = [5, 10, 15.2, 20]
print(nums)

去查查那个手机。你会得到【5,10,15.2,20】的输出。当输出一个列表时,它包括括号。这个当前列表由三个整数和一个浮点数组成。

访问列表中的元素

现在我们知道了如何定义一个列表,我们需要采取下一步,了解如何访问列表中的条目。为了访问列表中的特定元素,您使用了一个索引。当我们声明列表变量时,每一项都有一个索引。请记住,Python 中的索引从零开始,用括号括起来。第 2 周的星期三也包括索引:

# accessing elements within a list
print( nums[1] )          # will output the value at index 1 = 10
num = nums[2]             # saves index value 2 into num
print(num)                # prints value assigned to num

去查查那个手机。这里我们将得到两个值输出, 1015.2 。输出第一个值是因为我们正在访问我们的 nums 列表中的索引位置 1,其中存储了一个整数 10。在我们创建了一个名为 num 的新变量之后,第二个值被打印出来,这个变量被设置为存储在我们的 nums 列表中索引 2 处的值。

声明混合数据类型列表

列表可以保存任何数据类型,甚至其他列表。让我们来看看几种数据类型的例子:

# declaring a list of mixed data types
num = 4.3
data = [num, "word", True]         # the power of data collection
print(data)

去查查那个手机。这会输出【4.3,'字',真】。它输出 4.3 作为第一项,因为当定义列表时,它存储的是 num 的值,而不是变量本身。

列表中的列表

让我们变得复杂一点,看看列表如何存储在另一个列表中:

# understanding lists within lists
data = [5, "book", [ 34, "hello" ], True]     # lists can hold any type
print(data)
print( data[2] )

去查查那个手机。这将输出 [5,'书',[34,'你好'],True][34,'你好'] 。第一个输出是整个数据变量的值,它存储了一个整数、一个字符串、一个列表和一个布尔数据类型。第二个输出是存储在我们的数据变量中的列表,它位于索引 2 处,包含一个整数和字符串数据类型。

访问列表中的列表

在最后一个单元格中,我们看到了如何输出存储在数据变量中的列表。现在,我们将看看如何访问内部列表中的项目。要正常访问列表中的条目,我们只需使用括号符号和索引位置。当该项目是另一个列表时,只需在第一组括号后添加第二组括号。让我们先来看一个例子,然后再回过头来看:

# using double bracket notation to access lists within lists
print( data[2][0] )       # will output 34
inner_list = data[2]      # inner list will equal [34, 'hello']
print( inner_list[1] )    # will output 'hello'

去查查那个手机。第一个输出将是 34 。这是因为我们的第一个索引位置正在访问数据中的第二个索引,这是一个列表。然后,指定的第二个索引位置正在访问位置 0 处的那个列表中的值,这导致整数 34。第二个输出是“你好”。我们得到这个结果是因为我们声明了一个变量来存储我们的数据变量的索引 2 处的值,这个变量恰好是一个列表。我们的 inner_list 变量现在等于【34,' hello '】,我们访问索引 1 处的值,它是字符串“hello”。为了更好地理解多重索引的工作原理,请查看表格 4-1 。

表 4-1

多重索引值

|

索引位置

|

位置值

|

数据类型

|

可以再次索引

|
| --- | --- | --- | --- |
| Zero | five | 整数 | 不 |
| one | “书” | 线 | 是 |
| Two | [34,'你好'] | 目录 | 是 |
| three | 真实的 | 布尔代数学体系的 | 不 |

请注意,字符串还可以进一步索引。如果您只想打印出“book”中的“b”,您只需简单地写下以下内容:

>>> print( data[ 1 ][ 0 ] )    # will output 'b'

更改列表中的值

当您使用列表时,您需要能够改变列表中项目的值。这就像将一个普通变量重新声明为一个不同的值,除了您首先访问索引:

# changing values in a list through index
data = [5, 10, 15, 20]
print(data)
data[0] = 100            # change the value at index 0 - (5 to 100)
print(data)

去查查那个手机。在我们改变索引 0 处的值之前,它输出【5,10,15,20】。一旦我们访问了零索引并将其值更改为 100 ,然而,列表最终更改为【100,10,15,20】

可变存储

当变量被声明时,赋值被放入内存中的一个位置。这些位置有一个特定的参考 ID。您不经常需要检查变量的 ID,但是出于教育目的,了解存储是如何工作的是有好处的。我们将使用 id() 函数来检查变量在内存中的存储位置:

>>> a = [ 5, 10 ]
>>> print( id(a) )     # large number represents location in memory

当一个列表存储在内存中时,每个条目都有自己的位置。使用索引符号更改该值将会更改存储在该内存块中的值。现在,如果一个变量的值是另一个变量,就像这样:

>>> a = [5, 10]
>>> b = a

更改特定索引处的值将会更改这两个列表的值。让我们看一个例子:

# understanding how lists are stored
a = [5, 10]
b = a
print( "a: { }\t b: { }".format(a, b) )
print( "Location a[0]: { }\t Location b[0]: { }".format( id(a[0]), id(b[0]) ) )
a[0] = 20                # re-declaring the value of a[0] also changes b[0]
print( "a: { }\t b: { }".format(a, b) )

去查查那个手机。我们会得到几个输出。第一个是打印出两个列表变量的值,以显示它们具有相同的值。第二个 print 语句将输出每个列表的第一项在内存中的位置。最后,在我们改变了列表“a”中第一项的值之后,列表“b”中的值也会改变。这是因为它们共享相同的内存位置。

复制列表

那么,如何在不改变原始列表的情况下创建一个类似的列表呢?你抄!让我们看看如何:

# using [:] to copy a list
data = [5, 10, 15, 20]
data_copy = data[ : ]       # a single colon copies the list
data[0] = 50
print( "data: { }\t data_copy: { }".format(data, data_copy) )

去查查那个手机。这次的输出将导致只有我们的数据变量的第一项被设置为 50 。由于 data_copy 仅仅是列表的一个副本,现在如果我们需要再次使用它,我们可以一直保持原始列表的完整性。

注意

您也可以使用方法。复制()。

周一练习

  1. Sports:定义一个字符串列表,其中每个字符串都是一项运动。然后用下面的一行输出每项运动“我喜欢玩{ }”…

  2. 第一个字符:对于下面的列表,打印出每个项目的第一个字母。(输出应为‘J’,‘A’,‘S’,‘K’)

    names = ['约翰','亚伯拉罕','山姆','凯利']

今天是关于我们的第一个数据收集类型,列表。有很多内容要介绍,但是理解如何定义、更改值和复制列表是很重要的。

星期二:循环

今天我们将讨论编程中的一个重要概念,循环。在大多数应用程序中,您需要能够多次运行相同的代码。我们使用循环,而不是多次编写相同的代码行。在 Python 中有两种类型的循环,今天的课程是关于循环的

**为了跟上这一课,让我们从之前的笔记本文件“ Week_04 ”继续,简单地在底部添加一个标有“ For Loops ”的降价单元格

循环如何工作

循环是程序员多次重新运行同一行代码的方式。循环将一直运行,直到满足某个条件。以一个第一人称射击游戏为例,游戏将继续运行,直到要么你赢了,要么你的生命值为零。一旦出现任何一种情况,游戏就结束了。

注意

将你的代码压缩到尽可能少的行总是很重要的,因为这对程序来说更有效率。

不管你是否知道,循环在生活中无处不在。每天我们醒来,去工作,去睡觉,我们知道这是例行公事,但这只是一个循环。我们每天重复同样的过程,直到周末。同样的概念也适用于我们程序中的循环。

编写 For 循环

For 循环主要用于循环一定的次数。以图 4-1 为例,这个语法表明循环将运行五次。让我们进一步分析一下。每个 for 循环都以关键字“for”开始。然后定义一个临时变量,有时称为计数器索引。接下来是“关键字中的”,后面是 range 函数(后面会解释)。最后,我们用冒号来结束语句。所有 for 循环都将遵循关键字、变量、关键字、函数和冒号的精确结构。

img/481544_1_En_4_Fig1_HTML.jpg

图 4-1

For 循环语法

现在我们已经讨论了编写 for 循环的结构,让我们来编写一个:

# writing your first for loop using range
for num in range(5):
      print( "Value: { }".format(num) )

去查查那个手机。这将为我们的值输出“ 0,1,2,3,4 ”。这个循环实际上是数到五并打印出每个数字。那么它是如何打印出每个数字的呢?创建 for 循环时,默认情况下,range 函数从零开始,并将零值赋给我们的临时变量 num 。每次循环都是我们所谓的迭代。对于每一次迭代,一旦块中的所有代码都运行了,当前的迭代就结束了,循环从顶部重新开始。除了这次,它增加了 num 的值,默认为 1 。我们的临时变量被赋值为 1 的值,并继续运行 for 循环中的代码行,这只是打印出 num 的值。它将继续这样做,直到我们达到数字 5。为了给你一个每次迭代赋值的概念,参考表 4-2 。

表 4-2

使用 range()为每次迭代分配的值

|

循环迭代

|

数字的值

|

输出

|
| --- | --- | --- |
| one | Zero | 值:0 |
| Two | one | 值:1 |
| three | Two | 值:2 |
| four | three | 值:3 |
| five | four | 值:4 |

注意

不输出值 5,因为 range()计数到但不包括

范围( )

范围允许我们从一个数字计数到另一个数字,同时能够定义从哪里开始开始结束,以及我们增加减少多少。也就是说,如果我们愿意,我们可以每隔一个数字或者每隔五个数字计算一次。当与 for 循环一起使用时,它使我们能够循环一定的次数。在前面的例子中,我们看到范围 5 打印出五个数字。这是因为范围默认从 0 开始,每次递增 1。让我们看另一个例子:

# providing the start, stop, and step for the range function
for num in range(2, 10, 2):
      print( "Value: { }".format(num) )  # will print all evens between 2 and 10

去查查那个手机。这一次,我们指定程序从值 2 开始循环,计数到 10,但增量为 2。我们的值的输出变为“ 2,4,6,8 ”。

按元素循环

当处理可迭代的数据类型时,意味着它们有一个可以循环的元素集合,我们可以用不同的方式编写 for 循环:

# printing all characters in a name using the 'in' keyword
name = "John Smith"
for letter in name:
      print( "Value: { }".format(letter) )

去查查那个手机。输出将是每次打印出一个字母。记住,字符串可以被索引,并且是字符或符号的集合,这使得它们是可迭代的。这个 for 循环将遍历每个字符,并在包含该字符/符号的块中运行代码。表 4-3 回顾了该循环的前几次迭代。

表 4-3

在具有范围的字符串上循环的迭代值

|

循环迭代

|

信的价值

|

输出

|
| --- | --- | --- |
| one | J | 值:J |
| Two | o | 值:o |
| three | h | 值:h |
| four | n | 值:n |
| five | 空格符号 | 价值: |
| six | S | 值:S |

连续语句

现在我们已经看到了循环是如何工作的,让我们来讨论几个可以用于循环的重要语句。第一个是继续语句。一旦 continue 语句被命中,当前迭代将停止并返回到循环的顶部。让我们看一个例子:

# using the continue statement within a foor loop
for num in range(5):
      if num == 3:
              continue
      print(num)

去查查那个手机。输出将导致 0,1,2,4 ,因为只有当 num 等于 3 的值时,才会读取 continue 语句。一旦命中该语句,它将停止当前的迭代,并返回到顶部,继续在下一次迭代中循环。这完全阻止了 continue 下面的代码被解释,所以它不会命中 print 语句。

break 语句

我们可以使用的最重要的语句之一是 break 语句。它允许我们在任何时间点打破循环。让我们看一个例子:

# breaking out of a loop using the 'break' keyword
for num in range(5):
      if num == 3:
              break
      print(num)

去查查那个手机。输出将导致“ 0,1,2 ”,因为当 num 等于 3 时,我们完全打破了循环。一旦断点被读取,循环完全停止,循环内不再运行代码。这对于在满足条件时停止循环非常有用。

注意

如果使用双循环,break 语句将只从该语句所在的循环中断开。也就是说,如果在内部循环中使用 break 语句,它将不会同时跳出两个循环。

Pass 语句

这三个语句的最后一个是 pass。pass 语句只是一个占位符,这样程序就不会中断。让我们看一个例子:

# setting a placeholder using the 'pass' keyword
for i in range(5):
      # TODO: add code to print number
      pass

去查查那个手机。什么都没发生,但这是件好事。如果你完全去掉 pass 语句,程序将会崩溃,因为块中需要某种代码。

它的存在只是为了让我们不必在循环中写代码。这对设计一个程序很有用。

注意

使用“ TODO 是设置提醒的一般惯例。

星期二练习

  1. 可被三整除:编写一个 for 循环,打印出从 1 到 100 的所有可被三整除的数字。

  2. Only 元音字母:要求用户输入,编写一个 for 循环,输出循环中的所有元音字母。例如:

    >>> "Hello" ➔ "eo"
    
    

今天我们学习了所有关于 for 循环及其工作原理的知识。循环允许我们多次运行相同的代码行。

星期三:While 循环

我们今天将讨论另一种循环,即 while 循环。昨天我们看到了循环是如何工作的,以及为什么我们要使用 for 循环。当您需要基于条件而不是计数进行循环时,通常会使用 while 循环。今天我们将讨论基于条件的循环。

为了跟上这一课,让我们从之前的笔记本文件“ Week_04 ”继续,并简单地在底部添加一个 markdown 单元格,表示“ While 循环

编写 While 循环

像 for 循环一样,while 循环以关键字“while”开始。接下来,我们有一个条件,就像我们用来写一个 if 语句。让我们看一个例子:

# writing your first while loop
health = 10
while health > 0:
      print(health)
      health -= 1     # forgetting this line will result in infinite loop

去查查那个手机。这将继续打印出健康的值,直到满足条件。在这种情况下,一旦健康不再大于零,循环就停止运行。在最后一行,我们将的生命值减 1,因此每次迭代都会将的生命值减少到接近零。如果我们没有在任何时间点减少健康,这将成为一个无限循环(这是不好的)。

While 与 For

我已经解释了几次为什么我们要使用每个循环;然而,重申概念总是好的。For 循环通常在需要对元素集合进行计数或迭代时使用。While 循环通常用于基于条件的循环。当使用 while 循环时,通常会使用布尔变量。每个循环都有它们的用例;在大多数情况下,这是个人偏好,但一般的经验法则是用 for 循环计数,用 while 循环计数。

注意

对于 while 循环,pass、break 和 continue 语句的工作方式都是一样的。

无限循环

在之前的一个单元格中,我提到过无限循环是不好的。无限循环将继续运行,直到程序中断、计算机关闭或时间停止。了解了这一点,就不要制造无限循环了。下面是一个无限循环的例子:

>>> game_over = False
>>> while not game_over:
>>>           print(game_over)

如果您要在一个单元中运行它,最终您将不得不关闭 Jupyter Notebook 并重启它(或者至少是内核)。这是因为 game_over 变量永远不会变为 True ,并且该条件一直运行到 game_over 变为 True 为止。始终确保你有办法退出循环,不管是通过中断还是通过一个条件。

嵌套循环

循环中循环的概念就是我们所说的嵌套循环。循环的概念仍然适用。当使用嵌套循环时,内部循环必须总是在外部循环继续之前完成运行。让我们看一个例子:

# using two or more loops together is called a nested loop
for i in range(2):    # outside loop
      for j in range(3):     # inside loop
            print( i, j )

去查查那个手机。起初,这似乎有点令人困惑,因为这里发生了很多事情。让我们用表 4-4 分解输出。

表 4-4

跟踪嵌套循环值

|

循环

|

I 的值

|

j 的值

|

内部循环计数

|

外部循环计数

|
| --- | --- | --- | --- | --- |
| one | Zero | Zero | one | one |
| Two | Zero | one | Two | one |
| three | Zero | Two | three | one |
| four | one | Zero | four | Two |
| five | one | one | five | Two |
| six | one | Two | six | Two |

总的来说,我们可以看到内部循环运行了六次,外部循环运行了两次。只有当外循环运行时, i 的值才会增加,直到内循环结束时才会增加。内循环每次都必须从 0 数到 3,才能在外循环上运行下一次迭代。

周三练习

  1. 用户输入:编写一个 while 循环,继续要求用户输入,一直运行到用户输入“quit”为止。

  2. 双循环:在 while 循环中写一个 for 循环,从 0 数到 5,但是当它达到 3 时,它将 game_over 变量设置为 True 并且从循环中断开。while 循环应该继续循环,直到 game_overTrue 为止。输出应该只有 0,1,2

今天是一个有点短的一天,因为循环的概念是相同的,无论是一会儿还是一会儿。记住 while 循环用于条件循环,而我们使用 for 循环用于计数/迭代。

星期四:使用列表

既然我们已经学习了什么是列表以及如何使用循环,我们今天就来复习一下如何使用列表。列表对于 Python 中的任何程序来说都是一把重要的钥匙,所以我们在使用它们时需要了解我们的能力。

为了跟上这一课,让我们从之前的笔记本文件“ Week_04 ”继续,并在底部简单地添加一个 markdown 单元格,表示“使用列表

检查长度

通常,我们需要知道一个列表中有多少项。为此,我们使用了 len() 函数:

# checking the number of items within a list
nums = [5, 10, 15]
length = len(nums)        # len() returns an integer
print(length)

去查查那个手机。这将输出 3 。我们将 length 函数用于多种用途,无论是检查空列表还是在 range 函数中使用它来循环列表。

切片列表

几周前,我们讨论了切割字符串。列表以同样的方式工作,以便您能够访问特定的项目。切片遵循与范围功能开始、停止、步进相同的参数:

# accessing specific items of a list with slices
print( nums[ 1 : 3 ] )       # will output items in index 1 and 2
print( nums[ : 2 ] )         # will output items in index 0 and 1
print( nums[ : : 2 ] )       # will print every other index - 0, 2, 4, etc.
print( nums[ -2 : ] )        # will output the last two items in list

去查查那个手机。输出显示在每个语句旁边的注释中。我们使用括号符号,就好像我们在访问一个索引;然而,我们通过冒号分隔其他值。顺序永远是【开始:停止:步进】。默认情况下,start 为零,step 为一。如果您想保留默认值,可以选择不使用这些值。对步长位置使用负数将导致向后切片。如果在开始或停止位置使用负数,切片将从后面开始或停止。也就是说,如果您将-5 作为停止位置,它将从列表的开头一直切片到列表结束之前的五个元素。

添加项目

当需要向列表中添加项目时,Python 有两种不同的方法。

。追加( )

Append 总是将括号中的值添加到列表的后面。让我们看看:

# adding an item to the back of a list using append
nums = [10, 20]
nums.append(5)
print(nums)           # outputs [10, 20, 5]

去查查那个手机。我们声明了一个包含两个条目的列表,然后将整数值 5 添加到列表的后面。

。插入( )

向列表添加条目的第二种方法是使用 insert。此方法需要一个索引来将值插入到特定位置。让我们看一个例子:

# adding a value to the beginning of the list
words = [ "ball", "base" ]
nums.insert(0, "glove")   # first number is the index, second is the value

去查查那个手机。输出将导致 ['手套','球',' basex'] 。Glove 现在位于零索引中,因为我们在 insert 方法中指定了该索引。

移除项目

从列表中删除项目有几种方法,下面是主要的两种方法。

。流行( )

默认情况下, pop 方法删除列表中的最后一项;但是,您也可以指定要删除的索引。这种方法也广泛用于保存被删除的项目。当使用 pop 时,它不仅移除项目,还会返回项目。这允许我们将该值保存到一个变量中,供以后使用:

# using pop to remove items and saving to a variable to use later
items = [5, "ball", True]
items.pop( )                   # by default removes the last item
removed_item = items.pop(0)    # removes 5 and saves it into the variable
print(removed_item, "\n", items)

去查查那个手机。使用 pop ,我们可以看到它先移除了 True 项,然后是索引零中的元素,恰好是整数 5 。在将它从列表中弹出时,我们将它保存到一个变量中,稍后我们将它与新列表一起输出。

。移除( )

remove 方法允许我们根据给定的值从列表中删除项目:

# using the remove method with a try and except
sports = [ "baseball", "soccer", "football", "hockey" ]
try:
      sports.remove("soccer")
except:
      print("That item does not exist in the list")
print(sports)

去查查那个手机。这里我们将看到输出是我们的体育列表,没有足球,因为我们能够正确地删除它。现在我们之所以使用一个 try,并把去掉,是因为如果“足球”不在列表中,那么程序就会崩溃。

使用数字列表数据

Python 为我们提供了一些函数来处理数字数据列表,比如 min、max 和 sum。还有几个我们可以使用的,尽管这些是最常用的:

# using min, max, and sum
nums = [5, 3, 9]
print( min(nums) )   # will find the lowest number in the list
print( max(nums) )   # will find the highest number in the list
print( sum(nums) )   # will add all numbers in the list and return the sum

去查查那个手机。输出将导致 3、9 和 17 。正如他们的名字所说,他们会找到最小和最大的数字。 sum 函数将简单地把所有的数字相加。

对列表进行排序

通常,你需要使用一个排序列表。有几种方法可以做到这一点,但它们非常不同。一个将改变原始列表,而另一个返回一个副本。

已排序( )

排序的函数可以在数字或字母列表上工作,但不能在混合列表上工作。Sorted 也返回列表的一个副本,所以它不会改变原来的列表。通常如果你需要保持原件完好无损,一定要使用这个功能:

# using sorted on lists for numerical and alphabetical data
nums = [5, 8, 0, 2]
sorted_nums = sorted(nums)     # save to a new variable to use later
print(nums, sorted_nums)       # the original list is in tact

去查查那个手机。您会注意到我们的 nums 列表的输出仍然是我们声明它时的原始顺序。要使用新的排序列表,我们只需将它保存到一个新变量中。

。排序( )

sort 方法的用途与我们前面的 sort 函数的用途相同;但是,它会直接改变原来的列表:

# sorting a list with .sort() in-place
nums = [5, 0, 8, 3]
nums.sort( )          # alters the original variable directly
print(nums)

去查查那个手机。结果输出将是一个正确排序的列表。请记住,变量 nums 现在已经被更改为。sort() 直接改变数值。

条件和列表

使用列表时,通常需要检查值是否存在。现在我们将介绍如何在列表上运行条件语句。在列表上运行条件有许多原因;这只是几个例子。

使用“在”和“不在”关键字

当我们上周讨论条件语句时,我们已经看到了这些关键字的用法。使用列表时,它们有助于快速查找列表中的值:

# using conditional statements on a list
names = [ "Jack", "Robert", "Mary" ]
if "Mary" in names:
      print("found")          # will run since Mary is in the list
if "Jimmy" not in names:
      print("not found")      # will run since Jimmy is not in the list

去查查那个手机。输出结果为“发现”“未发现”。在第一条语句中,我们试图查看列表中是否存在【玛丽】,它确实存在。第二个条件语句检查是否“Jimmy”不存在,这也是真的,所以它也运行。

检查空列表

有太多的理由需要检查空列表。这通常是为了确保您不会在程序中导致任何错误,所以让我们看看如何检查:

# using conditionals to see if a list is empty
nums = [ ]
if not nums:            # could also say 'if nums == []'
      print("empty")

去查查那个手机。这将输出“空”。评论中提到了,但是我们也可以检查它是否等于空括号。在这里,我想告诉你如何使用【非】关键字。要检查包含项目的列表,您需要编写以下代码:

>>> if nums:

循环和列表

您可以使用 for 和 while 循环来遍历列表中的项。

使用 For 循环

当用 for 循环遍历一个列表时,语法看起来像我们以前使用 range 函数时一样;然而,这一次我们使用了一个临时变量、in 关键字和列表名称。对于每次迭代,临时变量被赋予该项的值。让我们试一试:

# using a for loop to print all items in a list
sports = [ "Baseball", "Hockey", "Football", "Basketball" ]
for sport in sports:
      print(sport)

去查查那个手机。这里我们可以看到这个单元格将输出列表中的每一项。在第一次迭代中,临时变量“sport”被赋值为“棒球,一旦打印出来,它就移动到下一项。

使用 While 循环

While 循环总是用于条件循环。列表 while 循环的一个很好的用例是删除一个项目。有很多用途,这只是其中之一:

# using the while loop to remove a certain value
names = [ "Bob", "Jack", "Rob", "Bob", "Robert" ]
while "Bob" in names:
      names.remove("Bob")    # removes all instances of 'Bob'
print(names)

去查查那个手机。输出将是我们的姓名列表,列表中没有“Bob”。我们使用 while 循环和一个条件的组合来检查列表中的“鲍勃”值,然后继续删除它,直到我们的条件不再为真。

周四练习

  1. 删除重复项:从下面的列表中删除所有重复项。提示:使用计数()方法。输出应该是 ['鲍勃','肯尼','阿曼达']

    >>> names = ['Bob', 'Kenny', 'Amanda', 'Bob', 'Kenny']
    
    
  2. 用户输入:使用 while 循环不断要求用户输入一个单词,直到他们键入“退出”。一旦他们输入一个单词,就把它添加到列表中。一旦他们退出循环,使用 for 循环输出列表中的所有项目。

今天很重要,这样我们就能理解如何使用列表,不管是条件语句还是循环。列表可以使用很多方法;在本书的其余部分,我们将更多地讨论它们。

星期五:创造刽子手

随着时间的推移,项目通常会变得更长。今天我们将利用过去四周学到的所有概念来建造 Hangman。像往常一样,随着我们编码的进行,新的概念将被引入。今天的目标是有一个功能齐全的刽子手游戏,我们可以猜测,失去一条生命,赢得或失去游戏。我们不会添加图形,虽然在我们一起完成这个项目后,你可以自己随意添加。

为了跟上这一课,让我们从之前的笔记本文件“ Week_04 ”继续,并在底部添加一个减价单元格,内容为“星期五项目:创建刽子手

完工图纸

像往常一样,我们希望在开始编码之前就设计好最终的设计。本周不会像上周一样以图形为基础,所以我们将把重点放在运行程序的逻辑和必要步骤上。幸运的是,逻辑本质上就是玩这个游戏所需的步骤:

  1. 选择一个单词来玩。

  2. 要求用户输入。

  3. 检查猜测是否正确。

    1. 如果是,请在适当的位置显示该字母。

    2. 如果不是,赔一条命。

  4. 继续步骤 2 和 3,直到出现以下情况之一:

    1. 用户猜对了单词。

    2. 用户失去了他们所有的生命。

这是主要的游戏功能。在实际运行游戏之前,我们还需要执行其他几个步骤,比如声明游戏变量;然而,这是我们在开始编码之前需要布局的主要功能。了解这种结构将使我们能够继续我们的计划。

先前引入的线符号

就像我们在第一周如何添加行号一样,我们将为这个项目和所有其他项目引入线符号的概念。由于需要编辑以前编写的线,甚至在项目中间添加代码,我们将引入线符号的概念。这些符号将通过使用三个空方块来显示,并将代表先前编写的代码。你可以在这里看到一个例子:

1| if num > 1:   ◻◻◻
3|            # new code will go here
5|            print(   ◻◻◻

当我们在之前编写的代码之间添加行时,我将使用这三个方块来表示哪一行应该在我们正在编写的代码的上面和下面。这也意味着你应该保持这条线不变。当我们需要覆盖之前的一行时,我会让你知道。当你看到这三个方块时,一定要注意行号,因为这将有助于让你知道你是否错过了一行。

注意

点按单元格的边后按下“L”来打开线条

添加导入

我们将在一个单元格中编写这个程序,它大约有 50 行长。第一步是导入一些我们需要的附加函数:

1| # import additional functions
2| from random import choice
3| from IPython.display import clear_output

第二行是导入一个名为“choice”的函数,它将从列表中选择一个随机项目。我们将用它来随机选择单词。第三行是导入一个 Jupyter Notebook 特定的函数,该函数清除输出。当使用循环时,如果我们不清除输出,它将继续在彼此之上输出。

声明游戏变量

下一步是理解运行游戏需要哪些变量,并声明它们。如果你想到 Hangman 和我们需要跟踪的东西,我们需要跟踪用户的生活,他们试图猜测的单词,可供选择的单词列表,以及游戏是否结束:

5| # declare game variables
6| words = [ "tree", "basket", "chair", "paper", "python" ]
7| word = choice(words)     # randomly chooses a word from words list
8| guessed, lives, game_over = [ ], 7, False   # multi variable assignment

第七行声明了一个名为单词的变量,它将从我们的单词列表中选择一个随机项目。第八行是我们一起声明三个变量的地方;猜测的将被赋予一个空列表生命将被设置为 7game_over 将被声明为

注意

在我们编码的过程中,可以随意编写打印语句来检查每个变量的值。这有助于了解我们所声明的内容。

生成隐藏单词

在游戏中,我们希望用户能够看到单词中有多少个字母。为此,我们可以创建一个字符串列表,其中每个字符串都是一个下划线。列表中的项目数将被设置为与所选单词的长度相同:

10| # create a list of underscores to the length of the word
11| guesses = [ "_ " ] * len(word)

在第 11 行,我们声明了一个名为guess的变量,它被设置为一个下划线列表。我们通过将列表乘以单词的长度得到合适的长度。

创建游戏循环

无论程序大小,每个游戏都有一个主循环。我们的主循环将执行我们在最终设计部分定义的逻辑。与其一次写完,不如让我们一步一步来。第一步是能够接受用户输入并停止玩游戏:

13| # create main game loop
14| while not game_over:
15|           ans = input("Type quit or guess a letter: ").lower( )
17|           if ans == "quit":
18|           print("Thanks for playing.")
19|           game_over = True

去查查手机。如果您输入“退出”,程序应该会停止,因为我们正在循环,直到 game_over 被设置为 True ,这仅在我们输入“退出”时发生。

注意

在继续之前,务必确保单元运行完毕。

输出游戏信息

下一步是开始向用户输出信息。让我们用一个格式良好的语句输出他们的生活和他们试图猜测的单词:

14| while not game_over:   ◻◻◻
15|           # output game information
16|           hidden_word = "".join(guesses)
17|           print( "Word to guess: { }".format(hidden_word) )
18|           print( "Lives: { }".format(lives) )
20|           ans = input(   ◻◻◻

去查查手机。根据选择的单词,您将得到不同的输出。如果选择的单词是四个字母,我们将得到“单词猜:_ _ _ _ _ _ _”和“生命:7 ”)的输出。格式没什么新意,但是第 16 行呢?我们之所以能够在第 17 行创建一个下划线字符串输出,是因为 join 方法。它声明我们希望将猜测列表中的所有项目连接在一起,中间没有空格。例如:

>>> chars = ['h', 'e', 'l', 'l', 'o']
>>> print('-'.join(chars))

前面两行将输出“ h-e-l-l-o ”。这是一个将列表显示为字符串的简单方法。

核实猜测

下一步是检查用户的输入是否是正确的猜测。我们暂时不会更改任何字母,因为我们首先要确保我们可以识别正确的猜测,并输出他们正确的猜测或删除一个生命:

24|            game_over = True   ◻◻◻
25|            elif ans in word:       # check if letter in word
26|            print("You guessed correctly!")
27|            else:                   # otherwise lose life
28|            lives -= 1
29|            print("Incorrect, you lost a life.")

去查查手机。如果你继续错误地猜测,你会注意到生命会降到零度以下。一定要猜出一个正确的字母和一个不正确的字母,这样才知道这是有效的。

清除输出

现在我们的程序进行得更深入了,我们可以看到这个循环不断地输出低于先前输出的信息。让我们开始清除输出:

20|        ans = input(   ◻◻◻
22|        clear_output( )          # clear all previous output
24|        if ans == 'quit':   ◻◻◻

去查查手机。你会注意到,无论我们玩多长时间,它都会正确地清除之前显示的信息。这是 Jupyter 笔记本特有的功能。

创造失败的条件

下一个逻辑操作将是创造一种失败的方式,因为我们的生活可以降到零度以下:

31|         print('Incorrect,  ◻◻◻
33|         if lives <= 0:
34|         print("You lost all your lives, you lost!")
35|         game_over = True

去查查手机。现在如果你输了所有的生命,游戏会停止运行,告诉你输了。请记住,循环只运行到 game_over,这是一旦生命的变量下降到时我们设置的值。

处理正确的猜测

既然我们可能会输,我们需要看到正确猜测的能力。为了理解如何改变显示的字母,我们首先需要记住输出的是什么。我们的猜测列表被转换成一个字符串并输出。这意味着当用户猜对了,我们需要改变我们的猜测列表中的条目到它们相应的位置。列表的长度与我们在单元格开头选择的单词的长度相同,因此每个下划线代表一个字母位置。如果单词是“运动”,那么“ _ _ _ _ ”中的第一个下划线将代表“ s ”。我们只需要用猜测的字母替换列表中正确的下划线。我们可以通过使用 for 循环并跟踪索引来做到这一点:

28|              print('You guessed correctly!')   ◻◻◻
30|              # create a loop to change underscore to proper letter
31|              for i in range( len(word) ):
32|                              if word[ i ] == ans:      # comapares values at indexes
33|                                     guesses[ i ] = ans
34|              else:   ◻◻◻

去查查那个手机。现在当猜中一个正确的字母时,它会输出变化。for 循环循环到单词的长度,我们使用变量" i "来跟踪索引。然后我们检查每个字符是否等于猜测的字母。如果是,那么我们将该项从下划线改为在该索引处猜测的字母。查看表 4-5 中的流程示例。让我们用【流行】来表示单词,用 p 来表示猜测。

表 4-5

取索引值检查猜测

|

ans 的值

|

I 的值

|

单词[i]的值

|

条件值

|

变化后的猜测值

|
| --- | --- | --- | --- | --- |
| p ' | Zero | p ' | 真实的 | ['p ',' _ ',' _ '] |
| p ' | one | 的 | 错误的 | ['p ',' _ ',' _ '] |
| p ' | Two | p ' | 真实的 | ['p ',' _ ',' p'] |

创造成功的条件

完成这个项目的最后一步是建立成功的条件。为了获胜,用户需要猜出随机选择的单词中的所有字母。我们已经跟踪了他们正确猜测的单词,所以我们只需要对照随机单词进行检查:

40|        game_over = True   ◻◻◻
41|        elif word == "".join(guesses):
42|        print("Congratulations, you guessed it correctly!")
43|        game_over = True

去查查手机。现在,如果用户猜对了所有字母,就可以正式获胜。我们使用与前面相同的连接方法,将我们的列表转换成一个字符串,这样,如果列表中还有下划线,连接的字符串就不等于随机单词。然后,我们打印一份祝贺,并将我们的 game_over 变量改为 True,以结束循环。

输出猜测的字母

虽然我们的游戏现在已经完成,我们可以赢或输,我们应该添加一个关键的功能,这是处理以前猜测的字母。每当用户猜出了之前的字母,他们不应该因此受到惩罚,但是他们也应该能够看到之前的猜测。在这个项目开始的时候,我们创建了一个名为 guessed 的变量,到现在都没有用过。到目前为止,这个变量仍然是一个空列表,所以让我们来实现它。在我们添加到列表中之前,让我们确保可以正确地打印出信息:

16|        hidden_word =   ◻◻◻
17|        print("Your guessed letters: { }".format(guessed) )
18|        print("Word to guess   ◻◻◻

去查查手机。在我们输出信息的顶部,我们现在打印出猜测字母的完整列表。以列表的形式保留它是完全没问题的。即使您猜测,它仍然会显示一个空列表,因为我们还没有添加该功能。

添加猜测的字母

现在让我们添加将用户的猜测添加到我们的猜测列表的功能:

37|         print("Incorrect,   ◻◻◻
39|         if ans not in guessed:
40||        guessed.append(ans)       # add ans to guessed list
42|         if lives <= 0:   ◻◻◻

去查查手机。现在猜测列表会随着用户玩游戏而更新。

处理先前的猜测

最后一项任务是确保当他们再次猜中同一封信时,他们不会被夺走一条生命,而是提醒他们已经被猜中了。我们需要重写整个条件语句来检查字母是否在单词 though 中:

27||          game_over = True   ◻◻◻
28|           elif ans in word and ans not in guessed:
29||          print("You guessed correctly!")   ◻◻◻
34|                                  guesses[ i ] = ans   ◻◻◻
35|           elif ans in guessed:
36||          print("You already guessed that. Try again.")
37|           else:   ◻◻◻

去查查手机。我们不得不更改第 28 行的 elif 语句,因为我们还需要检查该字母是否尚未添加到猜测列表中。在第 35 行,我们添加了第二个 elif 语句,该语句将检查该字母是否在猜测的列表中。请记住,一旦 if/elif 语句运行,下面的语句将不会运行。如果那两个条件句都不是,那么说明他们还没猜到字母,而且不在随机词里。游戏现在已经完成了全部功能。

最终输出

祝贺您完成这个项目!由于项目的规模,完整的代码将不会写在这里。相反,你可以在 Github 上找到本书资源文件所在的完整版本的代码。你可以在书的前面找到链接。每周的所有资源文件都位于该链接中。要找到这个项目的具体代码,只需打开或下载“ Week_04.ipynb ”文件。如果您在这个过程中遇到了错误,请确保将您的代码与该文件中的代码进行交叉引用,看看您可能在哪里出错了。所有未来项目的最终代码输出也可以在同一个位置找到,所以一定要将这个页面加入书签。

多好的一天啊!我们能够使用循环的概念,以及列表的力量来创建一个有趣的游戏。试着添加你自己的信号弹,或者打破它,以进一步了解什么可能或可能不工作。

每周总结

这无疑是漫长的一周,充满了大量的信息。一定要花些时间练习这些概念,或者自己练习,或者完成周末练习。我们讨论了为什么列表在 Python 中如此重要,以及如何在我们的程序中使用它们。还介绍了 Python 提供的两个循环,for 循环和 while 循环。使用循环,我们可以根据需要多次重新运行代码,或者迭代列表之类的数据集合。如果你对所有的信息感到不知所措,请放心,我们做的每件事都使用循环和列表。这会给你大量的练习和重复。

挑战问题解决方案

尽管我们在寻找一个特定的答案,但这是一个有点棘手的问题。如果你的第一个动作是转身问:“出了什么问题?"或"我为什么要撤离这座城市?”,那你回答对了。之所以需要先问这个问题,是因为不同的问题需要不同的解决方案。如果你开始写一个需要使用汽车的疏散计划,如果问题是所有的街道都被淹没了呢?建议人们开车出城是不明智的。有时候问题的答案就是问题本身。另一个教训是,你应该总是后退一步,思考每个问题。永远不要假设你马上就知道解决方案;问问题没问题。

每周挑战

要测试你的技能,请尝试以下挑战:

  1. 金字塔:使用 for 循环建立 x 的金字塔。它应该是模块化的,这样,如果您循环到 5 或 50,它仍然创建均匀间隔的行。提示 : 将字符串“x”乘以行。例如,如果您循环到范围 4,它应该产生以下结果:

    >>>     x
    >>>    x x
    >>>   x x x
    
    
  2. Output Names :编写一个循环,遍历一个项目列表,只输出字符串中有字母的项目。以下面的列表为例,应该只输出【约翰】【阿曼达】:

    >>> names = ['John', '  ', 'Amanda', 5]
    
    
  3. Convert Celsius :给定一个以摄氏度为单位的温度列表,编写一个循环来遍历该列表,并输出转换为华氏温度的温度。提示:换算为“F =(9/5)÷C+32”:

    >>> temps = [32, 12, 44, 29]
    Output would be [89.6, 53.6, 111.2, 84.2]
    
    ```**
    
    

五、函数

本周开始函数的话题。除了循环,函数可能是最难理解的主题之一。出于这个原因,这一整周都致力于只涵盖函数。这也是编程中比较重要的课题之一。知道如何使用函数将极大地提高你的编程技能。

函数给了我们能力,使我们的程序更加强大和干净,同时也节省了我们的时间。我们将在第一天回顾它们是如何工作的,但是我们使用函数的原因是因为它具有编写一次并重复调用的能力。

我们已经构建的许多程序都可以从函数的使用中受益,尤其是像 Hangman 这样的游戏。在周末,我们将构建一个类似购物车列表的程序。我们将了解为什么将添加、删除和显示等任务分离到不同的函数中很重要。

概述

  • 如何使用函数以及它们是什么

  • 使用参数传递数据

  • 从函数返回数据

  • 理解范围及其重要性

  • 创建购物车程序

挑战问题

请记住,算法只不过是一组循序渐进的指令。如果我们要写一个换灯泡的算法,会是什么样子?有哪些需要考虑的问题?需要几个步骤?最有效的方法是什么?使用下面的算法,可能会出现什么问题?

  1. 取出备用灯泡。

  2. 关闭给当前灯泡供电的开关。

  3. 拧下电流灯泡。

  4. 拧入备用灯泡。

  5. 打开给新灯泡供电的开关。

  6. 如果备用灯泡没有打开,重复步骤 1 至 5。

星期一:创建和调用函数

今天的课程是关于理解什么是函数,函数的阶段,以及如何编写函数。我们将发现为什么它们在程序中如此重要,以及它们将如何使我们的生活变得更容易。

为了继续今天的内容,让我们从“ python_bootcamp ”文件夹中打开 Jupyter 笔记本。一旦它打开,创建一个新文件,并将其重命名为“周 _05。"接下来,制作第一个标题为:"的单元格 markdown,创建&调用函数。“我们将开始在那个牢房下面工作。

什么是函数?

编程最好的参考资料之一是 w3schools。 1 他们甚至还有 Python 教程。他们的官方文档对函数的描述如下:

函数是一段代码,只有在被调用时才会运行。

您可以将数据(称为参数)传递到函数中。

函数可以返回数据结果。 2

程序经常需要重复运行相同的代码,尽管循环在这方面有所帮助,但我们不希望在整个程序中多次编写相同的循环。这个问题的解决方案是使用一个函数。它们本质上存储了只在被调用时才运行的代码。

所有函数通常都与单一任务或程序相关联。这使得我们更容易将程序分解成函数。如果您构建一个需要重复打印五行信息的程序,并且您需要在五个不同的地方输出它,您将需要编写 25 行代码。使用函数时,您可以将这五行存储在一个块中,并在需要时调用该函数,结果是五行输出信息,五行调用函数,总共十行。这导致了一个更有效的程序。

函数语法

像循环一样,函数遵循一个精确的模式来创建每一个函数。它们都是以关键字“ def ”开头,后面是函数的。该名称是任意的,可以是除 Python 关键字和先前定义的函数之外的任何名称。名字后面紧跟着的是括号,括号内是参数。我们明天才讨论参数,所以要知道参数是可选的,但是括号是必需的。最后,像任何其他 Python 语句一样,我们需要一个结尾冒号。示例见图 5-1 。

img/481544_1_En_5_Fig1_HTML.jpg

图 5-1

函数语法

编写您的第一个函数

现在我们知道了语法结构的样子,让我们继续写我们自己的:

# writing your first function
def printInfo( ):               # defines what the function does when called
      print("Name: John Smith")
      print("Age: 45")
printInfo( )                    # calls the function to run
printInfo( )                    # calls the function again

去查查手机。我们定义了一个名为 printInfo 的函数,它在每次被调用时打印两行信息。下面我们调用函数两次,输出信息两次。这似乎不是一个更有效的程序,但是想象一下你需要在一个程序中输出 20 次同样的信息。简洁高效。

函数阶段

在 Python 中,每个函数有两个阶段。第一阶段是函数定义。您可以在这里定义函数的名称、它应该接受的任何参数,以及它应该在与之相关的代码块中做什么。见图 5-2 。

img/481544_1_En_5_Fig2_HTML.jpg

图 5-2

函数生命周期的两个步骤(定义和调用)

第二阶段被称为函数调用。函数在被调用之前是不会运行的,所以你可以定义尽可能多的函数,但是如果你从来不调用其中的一个,那么什么都不会发生。当您调用一个函数时,它将运行定义中的代码块。

UDF 与内置

在不知不觉中,你一直在使用函数。范围、打印、镜头等函数。,都被称为“内置函数。它们被包含在 Python 中,因为它们有特定的用途来帮助构建我们的应用程序。既然我们正在学习函数,我们可以开始创建我们自己的所谓的UDF用户定义函数

执行计算

让我们再看一个基本函数的例子,但是这次不仅仅是打印代码块内部:

# performing a calculation in a function
def calc( ):
      x, y = 5, 10
      print(x + y)
calc( )              # will run the block of code within calc and output 15

去查查手机。每次调用 calc 函数时,我们都会得到一个输出值 15

周一练习

  1. 打印名字:定义一个名为 myName 的函数,让它在被调用时打印出你的名字。

  2. Pizza Toppings :定义一个函数,打印出所有你喜欢的 Pizza Toppings。调用该函数三次。

虽然今天没有太多的编码,但是理解函数的价值是很重要的。现在我们可以将代码分成块,这将使程序更容易阅读和运行。

星期二:参数

我们使用函数的主要原因之一是为了使我们的代码模块化。今天的主题是了解如何在函数中使用参数以及它们是什么。

为了继续本课,让我们从之前的笔记本文件“ Week_05 ”继续,并在底部添加一个标有“参数”的降价单元格。

什么是参数?

参数是在函数定义中声明的临时变量。虽然我们到目前为止编写的函数执行特定的任务,但是它们不是模块化的,因为它们总是为每个调用输出相同的响应。当要调用不同值的函数时,需要使用参数。在函数定义的括号内是您要声明参数名称的地方。这是一个任意变量名,用于引用函数块中的值;但是,您通常希望它与您正在处理的数据相关。当调用函数时,您将传入运行代码块所需的值。取图 5-3 。

img/481544_1_En_5_Fig3_HTML.jpg

图 5-3

接受函数的参数

注意

参数是传递给函数调用的值。在上图中,第 3 行将参数“John”传递给 printName 函数,该值将被传递给参数名

该函数由括号内的参数“ name 定义。同样,它可以被称为任何东西,但是我们期望传入一个人的名字。该代码块在执行时将使用格式化打印语句中该参数的值。第 3 行的调用是我们将值传递给函数的地方,称为参数。在这个例子中,我们将得到“你好约翰”的输出。我们现在可以调用这个函数,传入任何我们想要的字符串值,它会打印出来。这个函数现在是模块化的。

传递单个参数

让我们使用图 5-3 中的例子来创建我们的第一个接受参数的函数:

# passing a single parameter into a function
def printName(full_name):
      print( "Your name is: { }".format(full_name) )
printName("John Smith")
printName("Amanda")

去查查手机。我们会得到两个不同的输出,它们使用相同的函数。参数允许我们为每个调用传递不同的信息。

多参数

前面的示例将字符串数据类型传递给函数,因此让我们看看如何传递数字并创建一个格式良好的打印语句:

# passing multiple parameters into a function
def addNums(num1, num2):
      result = num1 + num2
      print( "{ } + { } = { }".format(num1, num2, result) )
addNums(5, 8)      # will output 13
addNums(3.5, 5.5)    # will output 9.0

去查查那个手机。我们的函数定义期望两个数字被传入参数 num1num2 。在函数块中,我们通过参数名来引用这些值。

传递列表

当大量数据存储在列表中时,传递它们通常是最容易的。因此,函数非常擅长在列表上执行重复的任务。让我们看一个例子:

# using a function to square all information
numbers1 = [ 2, 4, 5, 10 ]
numbers2 = [ 1, 3, 6 ]
def squares(nums):
      for num in nums:
              print(num**2)
squares(numbers1)
squares(numbers2)

去查查手机。你可以看到它会输出所有的平方数。这比为每个列表编写两次 for 循环要高效得多。这就是函数和传入参数的美妙之处。

注意

记住 nums 是一个任意的名字,是我们在函数块中引用的变量。

默认参数

在许多情况下,参数可以与默认值相关联。以 pi 的值为例;它将始终是 3.14,因此我们可以将一个名为 pi 的参数设置为该精确值。这允许我们调用已经为 pi 定义了值的函数。如果你想让 pi 有一个更简洁的值,你可以,但是一般来说 3.14 就足够了:

# setting default parameter values
def calcArea(r, pi=3.14):
      area = pi * (r**2)
      print( "Area: { }".format(area) )
calcArea(2)     # assuming radius is the value of 2

去查查手机。现在我们可以运行这个函数,而不需要为 pi 传递一个值。默认参数必须始终跟随非默认参数。在这个例子中,半径必须首先声明,然后是 pi

使参数可选

有时,您需要创建带有可选参数的函数。最好的例子总是中间名;有些人有,有些人没有。如果我们想写一个在两种情况下都能正确输出的函数,我们需要把中间的名字作为可选参数。我们通过将空字符串值指定为默认值来实现这一点:

# setting default parameter values
def printName(first, last, middle=""):
      if middle:
              print( "{ } { } { }".format(first, middle, last) )
      else:
              print( "{ } { }".format(first, last) )
printName("John", "Smith")
printName("John", "Smith", "Paul")     # will output with middle name

去查查手机。无论您是否传入中间名,该函数都将高效运行。请记住我们参数的顺序!根据函数定义,参数必须从左到右排列。如果在第二次调用中【保罗】被作为第二个值放在【约翰】之后,那么我们的函数会将【保罗】赋给参数【最后一个

命名参数赋值

在函数调用期间,您可以显式地将值赋给参数名。当您不想混淆传入值的顺序时,这很有用,因为默认情况下它们是从左到右工作的。如果您愿意,可以使用参数名为每个参数赋值,但大多数情况下这是不必要的。让我们来看一个例子:

# explicity assigning values to parameters by referencing the name
def addNums(num1, num2):
      print(num2)
      print(num1)
addNums(5, num2 = 2.5)

去查查手机。这里,我们使用一个关键字参数在调用中显式地分配了 num2 的值。

*参数

使用* args 允许您向函数传递可变数量的参数。这允许您使函数更加模块化。神奇的不是这里的关键字“args;实际上是一元运算符()允许我们执行这个特性。理论上,你可以用任何人替换 args 这个词,比如“ 数据,它仍然可以工作。然而,args 是整个行业的默认和通用标准。让我们看看如何在函数调用中使用参数:

# using args parameter to take in a tuple of arbitrary values
def outputData(name, *args):
      print( type(args) )
      for arg in args:
              print(arg)
outputData("John Smith", 5, True, "Jess")

去查查手机。您会注意到 args 参数将调用中未赋值的所有值作为一个元组,作为第一个 print 语句的输出。然后,我们输出元组中的每个参数。当访问块中的 args 参数时,不需要包含一元运算符。注意"约翰·史密斯"没有被打印出来。那是因为我们在函数定义中有两个参数, name 和* args 。函数调用中的第一个参数被映射到 name 参数,其余的被插入到 args 元组中。当您不确定会有多少个参数时,这是一个有用的机制。

**夸格斯

像 args 一样, kwargs 允许我们在函数中接受任意数量的值;然而,它作为一个带有关键字参数的字典。关键字参数是通过键传递的值,这使得我们可以在函数块中轻松地访问它们。同样,这里的神奇之处在于两个一元操作符(**)而不是 kwargs 的关键字。让我们来看看:

# using kwargs parameter to take in a dictionary of arbitrary values
def outputData(**kwargs):
      print( type(kwargs) )
      print( kwargs[ "name" ] )
      print( kwargs[ "num" ] )
outputData(name = "John Smith", num = 5, b = True)

去查查手机。这一次,我们可以看到该类型是一个字典,我们能够像处理任何其他字典一样输出 kwargs 参数中的每个键值对。这个单元格中的关键字参数在函数调用中,在这里我们专门声明了一个要传递给函数的键和值。

星期二练习

  1. 用户输入:要求用户输入一个单词,并将该单词传递给一个函数,该函数检查该单词是否以大写字母开头。如果确实输出“,否则“”。

  2. 没有名字:定义一个函数,它接受两个参数,名字姓氏,并且这两个参数都是可选的。如果没有值传入参数,它应该输出“没有名字传入”;否则,它应该打印出名称。

今天讲的都是函数参数以及如何使用它们。参数的使用使我们的函数在程序中模块化,这样我们就可以成功地减少编写的代码行。

周三:返回声明

到目前为止,我们已经打印出了函数所改变的数据,但是如果以后需要访问这些信息,该怎么办呢?这就是使用 return 语句的地方。函数可以操作数据,然后将数据发送回函数调用发生的地方,以保存信息供以后使用。今天我们将学习如何做到这一点,以及它为什么有用。

为了跟上这一课,让我们从笔记本文件“ Week_05 ”继续,只需在底部添加一个标有“退货单”的降价单元格

它是如何工作的

图 5-4 描绘了如何先计算传入函数的两个参数,然后返回到调用的原始位置存储到变量中。这个变量现在可以在程序的后面与那个值一起使用。

img/481544_1_En_5_Fig4_HTML.jpg

图 5-4

返回信息并存储到变量中

您可以返回任何数据类型,但只能返回一个变量。当您需要返回多条数据时,您将返回一组数据:

>>> def returnMultiple():
>>>           a = 5
>>>           b = 10
>>>           return [a, b]     # one data type holding multiple items

使用返回

return 语句用于将信息发送回函数调用发生的地方。到目前为止,我们已经使用了 print 语句来输出信息,但是如果我们需要在程序的后面访问该值,这就行不通了。相反,我们可以返回值并保存到一个变量中,以便以后使用。让我们来看几个例子:

# using return keyword to return the sum of two numbers
def addNums(num1, num2):
      return num1 + num2
num = addNums(5.5, 4.5)    # saves returned value into num
print(num)
print( addNums(10, 10) )     # doesn't save returned value

去查查手机。我们将得到 1020 作为输出。当我们第一次调用 addNums 时,它用 5.54.5 运行函数并返回总和。然后,它将返回值存储在 num 中。第二次调用该函数时,我们简单地就地打印它。从这里开始,我们可以重用存储在 num 中的值,但不能重用第二次调用返回的值。

三元运算符

三元运算符是一种简化的 Python 分支语句。这些运算符可用于将值赋给变量,或者在这种情况下,决定从函数返回什么:

# shorthand syntax using a ternary operator
def searchList(aList, el):
      return True if el in aList else False
result = searchList( [ "one", 2, "three" ], 2)    # result = True
print(result)

去查查手机。三元运算符返回 True,因为满足给定的条件。通常写出的相同代码如下所示:

>>> if el in aList:
>>>         return True
>>> else:
>>>         return False

如果可以的话,尽量少写是一个好习惯,但这不是必须的。

周三练习

  1. 全名:创建一个函数,接受名和姓,并返回两个名字的组合。

  2. 用户输入:在一个函数中,请求用户输入。让这个函数返回要存储在函数外部变量中的输入。然后打印出输入内容。

今天我们学习了如何从函数中获取信息。这将允许我们保存它处理的数据以备后用。

星期四:范围

今天我们要讨论一个叫做范围的重要概念。这个概念处理程序中声明的变量的可访问性。我们将讨论不同类型的作用域以及如何处理它们。

为了跟上这一课,让我们从之前的笔记本文件“ Week_05 ”继续,并简单地在底部添加一个标有“ Scope ”的单元格

范围的类型

在 Python 中,作用域有三种类型:全局、函数、类。我们还没有复习类,所以我们将在下一章讨论类的范围。在不知道的情况下,我们使用了另外两种类型的作用域。全局作用域是指你声明一个变量可以被整个文件或应用程序访问。到目前为止,我们声明的大多数变量都是全局变量;然而,在你编写的大多数程序中,你会想要避免全局变量。不过目前在 Jupyter 笔记本上还可以。函数作用域是指被声明的变量,只能在函数内部访问。在函数内部声明的变量不能在函数外部访问,因为一旦函数终止,在函数内部声明的变量也会终止。

全局范围访问

定义了全局属性后,文件的其余部分就可以访问它们了。但是,我们必须记住函数作用域是如何工作的。即使您声明了一个整个文件都可以访问的变量,它在函数中也是不可访问的。让我们看一个例子:

# where global variables can be accessed
number = 5
def scopeTest( ):
       number += 1      # not accessible due to function level scope
scopeTest( )

去查查手机。我们最终会收到一个错误,因为该函数仅限于在其中声明或传入的变量。

注意

传入时,它只传递值,不传递变量。

处理函数范围

当处理函数中声明的变量时,通常不需要在函数外部访问它。但是,为了访问该值,最佳做法是返回它:

# accessing variables defined in a function
def scopeTest( ):
      word = "function"
      return word
value = scopeTest( )
print(value)

去查查手机。现在我们可以访问函数中定义的单词,我们只需将返回值赋给另一个变量就可以了。

原地算法

当传递变量到一个函数中时,你只是传递那个变量的值,而不是变量本身。使得以下内容不会改变变量 num :

>>> num = 5
>>> def changeNum(n):
>>>       n += 5
>>>       print(num)

但是,当通过索引改变信息时,这是不同的。由于索引的工作方式,通过内存位置而不是引用,通过索引位置改变列表中的元素将改变原始变量。让我们来看一个例子:

# changing list item values by index
sports = [ "baseball", "football", "hockey", "basketball" ]
def change(aList):
      aList[ 0 ] = "soccer"
print("Before Altering: { }".format(sports) )
change(sports)
print( "After Altering: { }".format(sports) )

去查查手机。注意当函数被调用时, sports 列表中的第一项是如何变化的。这是由于在传入列表时索引本身的值发生了变化。这些被称为就地算法,因为无论你在哪里改变信息,它都会直接改变内存位置的值。

周四练习

  1. Names :创建一个函数,在给定的索引处用 name 参数改变传入的列表。这样,如果我传入【比尔】和索引 1,,它将把【富人】变成【比尔】在下面使用列表和函数定义:

    >>> names = ['Bob', 'Rich', 'Amanda']
    >>> def changeValue(aList, name, index):
    
    

今天对于理解可变可达性是如何工作的很重要。知道这些信息会保证变量的安全。

星期五:创建购物车

对于今天的项目,我们将构建一个在列表中存储产品的应用程序。我们将能够添加、删除、清除和显示购物车中的产品。将使用过去几周教授的所有概念。

为了继续本课,让我们从之前的笔记本文件“ Week_05 ”继续,并在底部添加一个 markdown 单元格,内容为“星期五项目:创建购物车

完工图纸

正如我们本周介绍的函数一样,最终的设计将基于我们程序动作的逻辑。函数执行特定的任务,通常是一个动作。对于我们的购物车程序,我们需要考虑的操作是添加、删除、清除和显示购物车中的商品。逻辑设计将如图 5-5 所示。

img/481544_1_En_5_Fig5_HTML.jpg

图 5-5

购物车程序逻辑

我们将确保有一个包含循环和处理用户输入的主函数。

初始设置

像上周的项目一样,我们将在单个单元格中创建程序,所以请确保您熟悉我们在该项目中使用的概念。首先,让我们从 Jupyter Notebook 导入清算函数,并声明一个要使用的全局变量:

1| # import necessary functions
2| from IPython.display import clear_output
4| # global list variable
5| cart = [ ]

我们想要声明一个全局变量 cart ,以便在整个程序中使用。我们将使用一个列表,因为我们需要存储几个条目。使用列表还允许我们直接编辑变量,而不必因为项目分配的工作方式而传递变量。

添加项目

正如初始设计中所述,我们首先要创建我们的函数。我们将从向我们的购物车变量添加商品的函数开始:

 7| # create function to add items to cart
 8| def addItem(item):
 9|          clear_output( )
10|          cart.append(item)
11|          print( "{ } has been added.".format(item) )

我们在创建主循环之前不会调用这个函数。当被调用时,该函数将清除输出,追加传入参数的,并输出给用户。

移除项目

接下来,我们将创建从我们的购物车变量中移除商品的函数:

13| # create function to remove items from cart
14| def removeItem(item):
15|           clear_output( )
16|           try:
17|           cart.remove(item)
18|           print( "{ } has been removed.".format(item) )
19|           except:
20|           print("Sorry we could not remove that item.")

我们希望确保在 remove 语句周围包含一个 try 和 except 子句,因为当删除一个不存在的项目时,程序会崩溃。这可以防止这种情况发生,并且可以正确地删除该项目,或者向用户输出该项目不起作用。

展示购物车

我们希望用户能够随时查看购物车,这使用了一个简单的循环:

22| # create a function to show items in cart
23| def showCart( ):
24|           clear_output( )
25|            if cart:
26|            print("Here is your cart:")
27|            for item in cart:
28|                           print( "- { }".format(item) )
29|            else:
30|            print("Your cart is empty.")

在该函数中,我们首先清除输出,然后检查购物车中是否有商品。如果它是空的,我们让用户知道;否则,我们将循环遍历这些项目,每行输出一个。

清理购物车

我们需要的最后一个函数是清理购物车的能力:

32| # create function to clear items from cart
33| def clearCart( ):
34|           clear_output( )
35|           cart.clear( )
36|           print("Your cart is empty.")

使用内置的 clear 方法,我们清除购物车中的所有商品,并让用户知道。

创建主循环

到目前为止,我们已经创建了处理用户动作的函数。现在我们需要设置程序的主函数,它将包含主循环和结束函数:

38| # create main function that loops until the user quits
39| def main( ):
40|           done = False
42|           while not done:
43|           ans = input("quit/add/remove/show/clear: ").lower( )
45|           # base case
46|           if ans == "quit":
47|                           print("Thanks for using our program.")
48|                           showCart( )
49|                           done = True
51| main( )    # run the program

去查查手机。你现在应该可以输入“quit”并退出程序;否则,它将继续运行。除了退出,我们还没有设置要做什么;然而,我们已经确保我们的基本情况设置正确,以免产生无限循环。我们还使用布尔变量 done 来跟踪主循环是否完成。

处理用户输入

这个程序的最后一步是添加我们之前创建的处理用户输入的函数:

49|              done = True   ◽◽◽
50|      elif ans == "add":
51|                      item = input("What would you like to add? ").title( )
52|                      addItem(item)
53|      elif ans == "remove":
54|                      showCart( )
55|                      item = input("What item would you like to remove? ").title( )
56|                      removeItem(item)
57|      elif ans == "show":
58|                      showCart( )
59|      elif ans == "clear":
60|                      clearCart( )
61|      else:
62|                      print("Sorry that was not an option.")
64| main( )    # run the program

去查查手机。我们已经包含了几个 elif 语句来处理用户的输入。现在,根据他们的选择,我们将能够调用必要的函数。在第 5155 行,我们接受用户的第二次输入,键入他们想要添加或删除的项目,但为了区分大小写,我们确保将其更改为标题大小写。如果他们没有选择合适的任务来执行,我们确保通过 else 子句让他们知道。

最终输出

祝贺您完成这个项目!由于项目的规模,你可以在 Github 上找到完整版本的代码。要找到这个项目的具体代码,只需打开或下载“ Week_05.ipynb ”文件。如果您在这个过程中遇到了错误,请确保将您的代码与该文件中的代码进行交叉引用,看看您可能在哪里出错了。

今天,我们能够使用函数构建一个完整的购物车程序。我们可以看到,我们的主循环是干净的,易于阅读。即使是这个小程序,也能看出函数的威力。

每周总结

本周是我们提高编程技能的一大步。我们了解到函数在减少代码行数方面非常有用。它们有助于使我们的程序更高效、更易读。它们可以使用参数变得模块化,甚至可以使用 return 关键字返回特定的数据。我们讨论的最后一个概念是如何处理项目中的范围,以及它如何处理变量的可访问性。在周末,我们一起构建了购物车程序,以展示在程序中使用函数的能力。下周我们将继续学习称为数据集合的高级变量类型的知识。

挑战问题解决方案

这个挑战的目的是让你开始思考所安排的步骤中可能出现的错误。在你开始编程算法之前,你需要理解你设计的步骤可能会出错,因为计算机只有在你编程后才会变得聪明。这个算法有几个问题。最明显的是在第二步和第三步之间,我们试图更换灯泡。你检查过灯泡是否太热而不能触摸了吗?在这种情况下,我们没有,所以任何人直接遵循这个算法可能会烧伤。作为人类,基本本能占据了主导地位,我们会停止触摸它,但计算机会继续执行它们被告知的任务。其他突出的问题包括检查替换灯泡的类型是否正确,以及如何处理我们刚刚替换的灯泡。算法没有指定一个步骤来适当地处理它,那么我们就永远把它留在我们手中吗?这些是我们在更换灯泡时需要考虑的步骤。当你开始构建自己的算法时,你不仅需要确保算法有效,还需要考虑如何处理容易出错的情况。

每周挑战

要测试你的技能,请尝试以下挑战:

  1. 重构 Hangman :这是一个很大的任务,所以要轻装上阵,但是要尝试重构上周的 Hangman 项目来使用函数。想想 Hangman 需要什么动作,把那些任务变成函数。

  2. 通过索引移除:在购物车程序中,设置移除函数,这样你也可以通过索引移除。设置列表,使其以编号列表的形式打印出来,当被要求删除一个项目时,用户也可以在列表项目旁边键入一个数字。例如,使用下面的代码,你可以输入 "1" 来删除 "Grapes" :

    >>> 1) Grapes
    >>> What would you like to remove? 1
    
    

六、数据收集和文件

Python 中有几种数据结构。本周我们将讨论字典集合元组冷冻集合,以增加我们的集合知识。每一种都有特定的用途,我们将看到它们之间的区别。

知道如何处理任何语言的文件都很重要。为了处理数据,我们需要知道如何读写几种类型的文件。我们将介绍如何处理文本文件和 ?? 文件。

概述

  • 理解词典

  • 使用字典

  • 学习其他重要的数据收集

  • 使用文件

  • 使用文件创建示例数据库

挑战问题

本周的挑战是写一个函数来检查一个单词是否是回文。该函数应该接受一个参数并返回 True 或 False。先试着把函数写在纸上,再试着编程!

星期一:字典

今天,我们将学习字典中有价值的数据集合。它们使用键存储信息,比 Python 列表高效得多。

为了继续今天的内容,让我们从“ python_bootcamp ”文件夹中打开 Jupyter 笔记本。打开后,创建一个新文件,并将其重命名为“ Week_06。“接下来,对标题为:“字典”的第一个单元格进行降价。“我们将开始在那个牢房下面工作。

什么是字典?

字典是无序数据的集合,存储在 - 对中。“无序的意思是它在内存中的存储方式。它不能通过索引访问,而是通过键访问。列表被称为有序数据集合,因为每个项目都被分配了一个特定的位置。字典的工作方式类似于现实生活中的字典,其中关键字是单词,值是定义。字典对于处理大型数据、映射数据、CSV 文件、API、发送或接收数据等非常有用。

声明字典

和其他变量一样,变量名放在等号运算符的左边,右边是字典。所有的字典都是通过使用左花括号和右花括号创建的。在花括号之间,我们定义了我们的键值对。只能用字符串或数字来声明密钥。有一个冒号分隔键和值。冒号后面是值,它可以是任何数据类型,包括其他数据集合,甚至是另一个字典:

# declaring a dictionary variable
empty = { }   # empty dictionary
person = { "name": "John Smith" }    # dictionary with one key/value pair
customer = {
      "name": "Morty",
      "age": 26
}                      # dictionary with two key/value pairs
print(customer)

去查查手机。这里我们可以看到我们声明了三个不同的字典,一个是空的,一个只有一个键值对,另一个有多个键值对。所有键值对必须用逗号分隔。接下来我们将看到如何访问这些数据。

注意

您也可以使用 dict( ) 来声明一个空字典。

访问词典信息

存储在字典中的所有数据都是通过与您试图访问的值相关联的键来访问的。我们简单地写下字典的名字,后面加上方括号。方括号内是密钥。这将检索存储在该项中的值:

# accessing dictionary information through keys
person = { "name": 'John" }
print( person[ "name" ] )       # access information through the key

去查查手机。这将输出“ John ,因为这是存储在“ name 键中的内容。

使用 Get 方法

另一种检索信息的方法是使用 get() 方法。使用这种方法和以前访问值的方法的主要区别在于 get 方法不会抛出键错误。如果键不存在,它将简单地返回“ None ”。您还可以在调用中添加第二个参数,以便让程序返回更具体的数据类型。让我们试试:

# using the get method to access dictionary information
person = { "name": 'John" }
print( person.get("name") )       # retrieves value of name key as before
print( person.get("age", "Age is not available.") )     # get is a secure way to retrieve information

去查查手机。在第二个打印语句中,我们将收到“年龄不可用”消息,因为关键字“年龄不存在。这给了我们一种更安全的检索信息的方法。

带列表的词典

当您开始将数据集合作为值使用时,字典变得非常强大:

# storing a list within a dictionary and accessing it
data = { "sports": [ "baseball", "football", "hockey", "soccer" ] }
print( data["sports"][0] )  # first access the key, then the index

去查查手机。为了访问列表,我们必须首先访问“运动键。之后,我们可以通过索引像访问任何其他列表一样访问项目。这会输出“棒球”。请记住,如果不先附加一个键,我们就无法创建存储列表的字典:

# improperly storing a list within a dictionary
sports = [ "baseball", "football", "hockey", "soccer" ]
sports_dict = dict( sports )     # will produce error, no key

去查查手机。这将产生一个错误,因为没有与 sports 变量相关联的键。要正确存储该列表,您应该编写以下内容:

>>> sports_dict = dict( { "sports" : sports } )

带词典的列表

当试图找出如何访问信息时,字典中的列表组合和反之亦然会变得令人困惑。永远记住列表是有索引的,字典使用关键字。根据存储数据的顺序,您需要先做其中一项。当一个列表存储一个字典时,您需要首先通过索引来访问该字典。之后,您可以访问字典中的键值对。让我们看一个例子:

# storing a dictionary within a list and accessing it
data = [ "John", "Dennis", { "name": "Kirsten" } ]
print( data[2] )   # the dictionary is in index 2
print( data[2]["name"] )   # first access the index, then access the key

去查查手机。首先,我们访问第二个索引中的条目,这是我们的字典。然后我们访问存储在“ name 键中的值,它是“ Kirsten 的输出。

注意

使用数字作为键时要非常小心。

带字典的字典

由于它们在内存中的存储方式,字典非常强大和高效。通常,您会希望使用字典作为键值对的值。让我们看一个例子:

# storing a dictionary within a dictionary and accessing it
data = {
      "team": "Boston Red Sox",
      "wins": { "2018": 108, "2017": 93 }
}
print( data["wins"] )   # will output the dictionary within the wins key
print( data["wins"]["2018"] )   # first access the wins key, then the next key

去查查手机。这将在第二条语句中输出“ 108 ”。我们可以通过访问“”的第一个键,然后访问“ 2018 的第二个键来访问这些信息。

周一练习

  1. 用户输入:询问用户的姓名和年龄,然后用这些键值对创建一个字典。创建后输出词典。

  2. 访问配料:使用 for 循环在“配料”键内输出以下列表中的所有配料:

    >>> pizza = {
    >>>     'ingredients': ['cheese', 'sausage', 'peppers']
    >>> }
    
    

数据集合允许我们处理大数据,因为它们存储在键值对中。请记住,数据是通过键访问的。

星期二:使用字典

今天的课程将涵盖如何添加数据、操作数据、移除键值对以及遍历字典。

为了跟上这一课,让我们从我们以前的笔记本文件“ Week_06 ”继续,并在底部简单地添加一个标有“使用字典”的单元格。

添加新信息

在声明一个字典之后,您通常需要添加新的键值对。让我们看看如何:

# adding new key/value pairs to a dictionary
car = { "year": 2018 }
car["color"] = "Blue"
print( "Year: { } \t Color: { }".format( car["year"], car["color"] ) )

去查查手机。要添加新的对,在 equals 操作符的左侧,您需要提供字典名,后跟括号内的新键。右边是你想要的值。这将输出一个带有我们汽车信息的格式良好的字符串。

注意

从 Python 开始,3.7 的字典是默认排序的。在 Python 的旧版本中,键值对并不总是保持它们的顺序。您可能需要使用一个 OrderedDict()。

改变信息

改变键值对就像添加一个新的键值对一样。如果键存在,它简单地覆盖以前的值;但是,如果它不存在,它将为您创建一个新的键-值对:

# updating a value for a key/value pair that already exists
car = { "year": 2018, "color": "Blue" }
car["color"] = "Red"
print( "Year: { } \t Color: { }".format( car["year"], car["color"] ) )

去查查手机。就像我们之前声明一个新的键-值对一样,因为键" color "已经存在于字典中,它只是覆盖了先前的值。

删除信息

有时你需要去掉某一对。为此,您需要使用 del 函数:

# deleting a key/value pair from a dictionary
car = { "year": 2018 }
try:
      del car["year"]
      print(car)
except:
      print("That key does not exist")

去查查手机。删除键值对时要非常小心。如果你试图删除的密钥不存在,它会使程序崩溃。为了避免这个问题,我们使用了 try/except。

循环字典

字典和列表一样是可重复的。然而,他们有三种不同的方法。可以同时迭代键和值,也可以只迭代键或值。

仅循环键

要在只访问键的情况下遍历字典,您将使用键()【方法:

# looping over a dictionary via the keys
person = { "name": "John", "age": 26 }
for key in person.keys( ):
      print(key)
      print( person[key] )  # will output the value at the current key

去查查手机。当我们迭代 person 时, key 的临时变量将等于每个键名。这仍然使我们能够通过使用我们的变量来访问每个值。

仅循环值

当不需要访问按键时,使用。values( ) 方法最好:

# looping over a dictionary via the values
person = { "name": "John", "age": 26 }
for value in person.values( ):
      print(value)

去查查手机。我们不能访问键名,但是对于这个方法,我们只是试图获取值。当我们遍历 person 时,我们的临时变量 value 将存储键值对中的每个值。

循环键值对

如果您需要访问键和值的能力,那么您将希望使用。items()方法。这种方法将分配两个临时变量,而不是一个:

# looping over a dictionary via the key/value pair
person = { "name": "John", "age": 26 }
for key, value in person.items( ):
      print( "{ }: { }".format(key, value) )

去查查手机。当我们迭代 person 时,键-值对被分配给它们各自的临时变量 key 和值。我们现在很容易接触到这两者。

注意

临时变量名通常称为“ k ”和“ v.

星期二练习

  1. 用户输入:声明一个空字典。询问用户的姓名、地址和电话号码。将该信息添加到字典中,并遍历它以显示给用户。

  2. 解题:下面这段代码有什么问题:

    >>> person = { 'name', 'John Smith' }
    >>> print(person['name'])
    
    

今天对于理解如何使用字典很重要。记住,添加和改变键值对是相同的语法。

星期三:元组、集、冷冻集

Python 包括几个其他数据集合,它们都有自己的特性。今天,我们将看看另外三个有时有用的方法。

为了跟上这一课,让我们从笔记本文件“ Week_06 ”继续,简单地在底部添加一个 markdown 单元格,表示“元组、集、冷冻集。

什么是元组?

一个元组与一个列表完全相同,除了它是不可变的。当一个东西是不可变的,这意味着它一旦被声明就不能被改变。元组对于存储您不想更改的信息很有用。它们像列表一样排序,所以你可以使用索引遍历它们。

声明元组

要声明一个元组,需要使用逗号来分隔两个或多个条目。列表用外面的方括号表示,而元组可以用可选的括号声明。它们更有可能用括号声明,因为这样更容易阅读。让我们看一个例子:

# declaring a tuple
t1 = ("hello", 2, "hello")    # with parens
t2 = True, 1                  # without parens
print( type(t1), type(t2) )     # both are tuples
t1[0] = 1     # will crash, tuples are immutable once declared

去查查手机。你可以看到我们输出变量的类型,它们都输出“ tuple ”。如上所述,元组的声明有括号和无括号。该单元格中的最后一行将产生一个错误,因为元组的项一旦声明就不能更改。覆盖元组中数据的唯一方法是重新声明整个元组。

什么是集合?

集合共享列表字典的相同特征。一个集合是一个类似列表的信息集合;然而,就像字典中的关键字一样,集合只能包含唯一值。它们也是一个无序集合。这意味着它们不能通过索引访问,而是像字典键一样通过值本身访问。但是它们可以被迭代,就像字典键如何循环一样。在存储独特物品的情况下,器械包很实用。

声明集合

有两种方法可以声明一个集合。第一种方法是使用关键字“ set ”,后跟括号和方括号。第二种方式更实用,看起来像是使用一组花括号声明的字典。让我们来看看:

# declaring a set
s1 = set( [1, 2, 3, 1] )      # uses the set keyword and square brackets
s2 = {4, 4, 5}            # uses curly brackets, like dictionary
print( type(s1), type(s2) )
s1.add(5)      # using the add method to add new items to a set
s1.remove(1)   # using the remove method to get rid of the value 1
print(s1)     # notice when printed it removed the second "1" at the end

去查查手机。我们将看到它将两个变量的类型输出为“ sets ”。当我们输出我们的 s1 变量的值时,它最终只输出“ 1,2,3 ”。请记住,集合是唯一的项目,因此它会删除第二个 "1" 值。集合有各种方法,允许我们添加、删除和更改其中的信息,如添加/删除行所示。

什么是冰冻人?

Frozensets 本质上是一个集合和一个元组的组合。分别是不可变无序唯一。这些对于像银行账号这样的敏感信息来说是完美的,因为你不想改变它们。它们可以被迭代,但不能被索引。

声明一个冷冻集

要声明一个 frozenset,可以使用关键字“ frozenset ”,后跟括号和方括号。这是声明 frozenset 的唯一方法。让我们来看一个例子:

# declaring a frozenset
fset = frozenset( [1, 2, 3, 4] )
print( type(fset) )

去查查手机。在本书中,我们不会经常使用 frozensets,但是所有这些数据集合在 Python 语言中都有特定的用途。

数据收集差异

表 6-1 显示了每个集合之间的差异汇总。

表 6-1

收藏异同

|

数据收集

|

整齐的

|

可重复的

|

独一无二的

|

不变的

|

易变的

|
| --- | --- | --- | --- | --- | --- |
| 目录 | 是 | 是 | 不 | 不 | 是 |
| 词典 | 不 | 是 | 仅钥匙 | 仅钥匙 | 仅值 |
| 元组 | 是 | 是 | 不 | 是 | 不 |
| 一组 | 不 | 是 | 是 | 不 | 是 |
| 冻结集合 | 不 | 是 | 是 | 是 | 不 |

周三练习

  1. 用户输入:要求用户输入任意多的银行账号,并将其存储在一个列表中。一旦用户输入完信息,就将列表转换成 frozenset 并打印出来。

  2. 转换:将下面的列表转换成一组唯一的值。打印出来后,检查有没有重复:

    >>> nums = [3, 4, 3, 7, 10]
    
    

今天,我们能够查看其他三个数据集合。每一个都有一个目的,即使我们大部分时间都在使用字典和列表。

星期四:读写文件

根据你编写的程序类型,你需要保存或访问信息。为此,你需要了解如何处理文件,无论是创建,还是

为了继续本课,让我们从之前的笔记本文件“ Week_06 ”继续,只需在底部添加一个 markdown 单元格,显示“阅读&编写文件”。

使用文本文件

默认情况下,Python 附带了一个 open() 函数,允许我们创建或修改文件。这个函数接受两个参数,文件名,和模式。如果文件名存在,那么它将简单地打开文件进行修改;否则,它将为您创建文件。该模式参考 Python 如何打开和处理文件。例如,如果你只是需要从文件中获取信息,你可以打开它阅读。这将允许您在不意外更改文件的情况下使用它。让我们看看如何打开、写入和读取文本文件:

1| # opening/creating and writing to a text file
2| f = open("test.txt", "w+")   # open file in writing and reading mode
3| f.write("this is a test")
4| f.close( )
5| # reading from a text file
6| f = open("test.txt", "r")
7| data = f.read( )
8| f.close( )
9| print(data)

去查查手机。让我们一行一行地走一遍。我们以读写模式打开文件进行完全编辑,并将值赋给变量 f 。在第 3 行,我们使用 write() 方法将我们的句子写入文件。然后我们关闭文件。任何时候你打开一个文件,你都必须关闭它。在我们创建并写入测试文件之后,我们以只读模式打开它。在第 7 行,我们使用 read() 方法将文件的所有内容读入一个字符串,该字符串被分配给我们的数据变量。然后我们输出信息。

注意

模式“w”将覆盖整个文件。使用“a”进行追加。

写入 CSV 文件

CSV 文件通过在每个单元格之间用逗号分隔来处理数据。这就是所谓的表格数据结构。为了开始使用它们,Python 有一个名为“csv”的默认库。为了与他们合作,我们需要导入它。导入这个库之后,我们将使用第二种打开文件的方法,使用带有关键字的。这个概念就像一个 while 循环,所以当文件打开时,我们可以使用它,一旦代码块运行完毕,它会自动为我们关闭文件。让我们来看看这个例子:

1| # opening/creating and writing to a csv file
2| import csv
3| with open("test.csv", mode="w", newline="") as f:
4|    writer = csv.writer(f, delimiter=",")
5|    writer.writerow( ["Name", "City"] )
6|    writer.writerow( ["Craig Lou", "Taiwan"] )

去查查手机。让我们一行一行地走一遍。我们在第 2 行导入 CSV 库。然后我们以写模式打开文件,作为变量 f 。我们还将 newline 参数设置为空字符串,这样就不会在行之间创建空行。在第 4 行,我们创建了一个 writer 变量,允许我们写入 CSV 文件。最后两行将几行数据写入 CSV 文件。一旦块完成,文件自动关闭,我们就完成了。继续检查文件;您将看到新的数据输出。请记住,写入模式将始终覆盖文件中先前的任何数据。

从 CSV 文件中读取

为了从我们刚刚创建的 CSV 文件中读取数据,我们可以简单地将模式设置为 read:

1| # reading from csv files
2| with open("test.csv", mode="r") as f:
3|    reader = csv.reader(f, delimiter=",")
4|    for row in reader:
5|            print(row)

去查查手机。您会注意到它将每一行输出为一个列表,其中包含两个条目。我们以读取模式打开文件,作为变量 f 。然后我们通过为我们读取文件内容的 CSV 库创建一个阅读器对象。然后我们循环遍历 reader 变量并打印出每一段数据。

注意

对象将在下一周讨论。

Python 中的文件模式

表 6-2 显示了一些你可以在 Python 中使用的文件模式。

表 6-2

文件模式

|

方式

|

描述

|
| --- | --- |
| r′ | 这是默认模式。它以只读方式打开文件。 |
| w ' | 打开文件进行写入。如果文件不存在,它会创建一个。 |
| x ' | 创建新文件。如果文件存在,操作将失败。 |
| a′ | 以追加模式打开。如果文件不存在,它会创建一个。 |
| ' b ' | 以二进制模式打开。 |
| '+' | 将打开一个文件进行读写。利于更新。 |

周四练习

  1. 用户输入:询问用户最喜欢的号码,并保存到文本文件中。

  2. 数据转储:使用以下数据的字典,将信息保存到一个 csv 文件中,以关键字为头,以值为数据行:

    >>> data = {
    'name' : ['Dave', 'Dennis', 'Peter', 'Jess'],
    'language': ['Python', 'C', 'Java', 'Python']
    }
    
    

今天我们学习了如何处理文本和 CSV 文件。有两种处理文件的方法,每种方法都有自己的用途,但通常使用 with 语句更容易。

星期五:用 CSV 文件创建用户数据库

对于本周的项目,我们将使用 CSV 文件构建一个用户数据库的副本。我们将能够接受输入,并允许用户登录/注销/注册。

为了继续本课,让我们从之前的笔记本文件“ Week_06 ”继续,并在底部添加一个 markdown 单元格,内容为“星期五项目:使用 CSV 文件创建用户数据库”。

完工图纸

本周的项目都是关于逻辑的。我们需要了解如何设置用户登录和注销的逐步过程。这个程序有三个主要部分,注册用户、用户登录和运行程序的主循环。知道了前两个是任务,我们就可以把它们做成函数,必要时在主循环中调用它们。让我们继续为这个程序设计逻辑流程:

  1. 检查用户是否登录。
    1. 如果已登录,询问他们是否愿意注销/退出。

      1. 退出或注销用户,然后重新启动。
    2. 否则,询问他们是否愿意登录/注册/退出。

      1. 如果登录,要求用户输入电子邮件/密码。

        1. 如果正确,请用户登录并重新启动。

        2. 否则,显示错误并重启。

      2. 如果注册,要求提供电子邮件/密码/口令 2。

        1. 如果密码匹配,保存用户并重启。

        2. 否则,显示错误并重启。

      3. 如果退出,说谢谢并退出程序。

这是我们主循环的程序流程图。既然您已经确切地知道了程序应该如何运行,我建议您在继续之前尝试自己构建它。通过这样做,你将能够引用我的代码,看到你可能在哪里犯了错误,等等。循环将继续运行,直到用户退出并允许他们注册或登录。登录后,您只能注销或退出。这很简单,但将提供一些关于如何处理菜单系统的见解。

设置必要的导入

首先,让我们从导入运行程序所需的文件和函数开始:

1| # import all necessary packages to be used
2| import csv
3| from IPython.display import clear_output

我们将在一个单元中编写所有的代码,所以现在不需要运行这个单元。我们已经导入了 CSV 库,以便能够处理 CSV 文件,还导入了 clear output 函数,允许我们从单元格中清除笔记本语句。

处理用户注册

接下来,我们将设计注册用户的功能。让我们来看看这个功能:

 5| # handle user registration and writing to csv
 6| def registerUser( ):
 7|   with open("users.csv", mode="a", newline="") as f:
 8|           writer = csv.writer(f, delimiter=",")
10|           print("To register, please enter your info:")
11|           email = input("E-mail: ")
12|           password = input("Password: ")
13|           password2 = input("Re-type password: ")
15|           clear_output( )
17|           if password == password2:
18|                   writer.writerow( [email, password] )
19|                   print("You are now registered!")
20|           else:
21|                   print("Something went wrong. Try again.")

我们首先定义函数并打开一个名为“ user.csv ”的 CSV 文件。这将是我们存储数据的文件。我们用该文件创建一个 writer 对象,它将允许我们追加附加数据。在询问用户的信息后,我们检查输入的两个密码是否相同,然后或者用我们创建的 writer 对象添加用户,或者让用户知道出现了错误。您可以随意调用这个函数并试用它。第一次尝试后,您应该会看到文件被创建。

处理用户登录

我们需要设计的第二个任务是让用户登录的能力。让我们看看如何做到这一点:

23| # ask for user info and return true to login or false if incorrect info
24| def loginUser( ):
25|   print("To login, please enter your info:")
26|   email = input("E-mail: ")
27|   password = input("Password: ")
29|   clear_output( )
31|   with open("users.csv", mode="r") as f:
32|           reader = csv.reader(f, delimiter=",")
34|           for row in reader:
35|                   if row == [email, password]:
36|                           print("You are now logged in!")
37|                           return True
39|   print("Something went wrong, try again.")
40|   return False

在用户登录功能中,我们要求用户输入他们的信息。然后,我们以只读模式打开存储用户信息的文件。使用 CSV 库创建一个 reader 对象,我们在第 34 行逐行遍历数据。我们读取的每一行都是包含两项的列表形式。第一项总是电子邮件,第二项是密码。在第 35 行,我们根据一个填充了用户输入信息的临时列表检查行信息。如果数据匹配,我们登录并返回 True 否则,我们告诉他们出错了,并返回 False。注册后尝试调用此函数。

注意

该文件存储在与笔记本文件相同的目录中。

创建主循环

神奇的事情就发生在这里。到目前为止,我们已经创建了程序的两个主要功能,注册和登录用户。这个主循环将处理菜单系统,以及基于用户是否登录来显示什么。让我们继续完成这个程序:

42| # variables for main loop
43| active = True
44| logged_in = False
46| # main loop
47| while active:
48|   if logged_in:
49|           print("1\. Logout\n2\. Quit")
50|   else:
51|           print("1\. Login\n2\. Register\n3\. Quit")
53|   choice = input("What would you like to do? ").lower( )
55|   clear_output( )
57|   if choice == "register" and logged_in == False:
58|           registerUser( )
59|   elif choice == "login" and logged_in == False:
60|           logged_in = loginUser( )
61|   elif choice == "quit":
62|           active = False
63|           print("Thanks for using our software!")
64|   elif choice == "logout" and logged_in == True:
65|           logged_in = False
66|           print("You are now logged out.")
67|   else:
68|           print("Sorry, please try again!")

去查查手机。在循环开始之前,我们为程序定义了几个变量。这些变量将跟踪登录的用户以及程序是否应该继续运行。然后,我们进入主循环,并根据登录的用户显示适当的菜单。由于程序启动时用户从未登录,将显示第二个菜单。然后我们询问用户他们想使用 input() 方法做什么。下一部分是我们菜单系统的逻辑发生的地方。根据用户的选择,我们执行特定的操作。我们已经使用户只能登录或注册,如果他们还没有登录。同样,他们只有在登录后才能注销。如果他们选择登录或注册,我们调用各自的函数来执行他们的操作。为了让用户登录,记住该函数返回,然后我们将 logged_in 变量设置为。如果用户决定退出,我们将我们的活动变量设置为并退出程序。在此之前,程序将根据登录的用户不断显示正确的菜单。如果他们选择了包含的选项之外的任何选项,我们将显示错误消息。

今天,我们能够理解使用 CSV 文件的用户注册过程背后的逻辑。我们将在本书后面使用类似的概念来存储数据。

每周总结

本周我们学习了更重要的数据收集之一,字典。它们在处理数据时很重要,因为它们允许我们分配键值对并高速检索信息。我们还讨论了一些在特定情况下有用的其他数据收集。在理解了集合之后,我们能够学习使用文件。读写文件让我们能够给程序添加额外的功能,正如我们在周五项目中创建用户注册应用时看到的那样。我们将能够把这些知识应用到我们在本书后面创建的程序中。

挑战问题解决方案

如果你不知道什么是回文,希望你查一下。这是一个单词向前和向后拼写相同的地方,就像“赛车”有几种不同的方法可以得到这个问题的答案。以下是一个简单明了的问题解决方案示例:

>>> def palindrome(word):
>>> return True if word == word[::-1] else False

记得我们在前一章中讨论过三元运算符,它允许我们写一行条件语句。如果您写出了整个 if else 语句,但能够获得相同的结果,那么干得好!展望未来,你应该开始尝试理解如何进一步压缩你的代码,以得到适当的优化。

每周挑战

要测试你的技能,请尝试以下挑战:

|

最喜欢的食物?

|

票数

|
| --- | --- |
| 土耳其 | 5 |
| 沙拉 | 3 |

  1. 更改密码:从周五开始在项目中增加一个名为 changePassword 的功能,允许用户在登录时更改密码。

  2. 最喜欢的食物:写一个新程序,询问用户他们最喜欢的食物是什么。将答案保存到一个名为“ favorite_food.csv 的 CSV 文件中。回答后,显示一个记录结果的表格。表格示例:

七、面向对象编程

许多语言被称为面向对象编程(OOP) 语言。Python、JavaScript、Java 和 C++只是使用 OOP 的几个名字。在这一周里,我们将开始理解 OOP 是什么,为什么它如此有用,以及如何在程序中实现它。

在 Python (以及大多数语言)中,我们通过自己构建的来创建对象。您可以将类视为如何创建对象的蓝图。以第一人称射击游戏为例。所有的玩家、交通工具和武器都是物体。两个团队中可能各有五个人,但是这些人中的每一个都是从相同的蓝图中创建的。他们都有相似的特征,如体重、身高、发色等。你不用为十个不同的人写同样的代码,而是写一个蓝图,然后根据这个蓝图创造每个人。这压缩了代码,使程序更容易管理和维护。在周末,我们将一起构建一个完整的 21 点游戏,看看 Python 类的威力!

概述

  • 理解面向对象编程的基础

  • 什么和如何使用属性(类中的变量)

  • 什么和如何使用方法(类中的函数)

  • 理解继承的基础(父类或基类)

  • 用类创建 21 点

挑战问题

以下代码的结果是什么?

      >>> values = { 4:4, 8:8, "Q":10, "ACE":11 }
      >>> card = ("Q", "Hearts")
      >>> print("{ }".format(values[ card[ 0 ] ] ) )

星期一:创建和实例化一个类

Python 中的所有对象都是从中创建的。OOP 的要点是重用相同的代码,同时灵活地创建每个具有自己特性的对象。今天,我们将学习 OOP 的术语和阶段,以及如何编写我们的第一个类。

为了继续今天的内容,让我们从“ python_bootcamp ”文件夹中打开 Jupyter 笔记本。打开后,创建一个新文件,并将其重命名为“ Week_07。"接下来,制作第一个标题为:"创建&实例化一个类的单元格。“我们将开始在那个牢房下面工作。

什么是对象?

看看你的周围,你看到了什么?可能有沙发、椅子、电视、书等。就在你身边。在编程中,所有这些都将作为对象被引用。甚至人也会被当作对象来引用。这是因为所有对象都来自特定的蓝图。在 Python 中,这些蓝图被称为类。让我们以汽车为例。所有的汽车都有相似的特征,可以用一个模板组装。每辆车通常都有车轮、颜色、品牌、型号、年份、车辆识别号等。类允许我们做的是构建一个包含所有这些特性的蓝图,并从中创建不同的汽车。这将减少我们必须编写的代码,并使我们能够为我们创建的任何汽车赋予特定于该对象的个人特征。图 7-1 说明了从同一个类创建多个对象的概念。

img/481544_1_En_7_Fig1_HTML.jpg

图 7-1

从相同的类蓝图创建三个相似的汽车

OOP 阶段

使用类时有两个阶段。第一阶段是类定义。像函数定义一样,这个阶段是您编写蓝图以供调用时使用的地方。第二阶段称为实例化。它是从类定义创建对象的过程。在一个对象被实例化后,它被称为一个实例。一个类定义中可能有多个实例。让我们开始看看如何定义一个类并创建一个实例!

创建一个类

使用类的第一步是创建类定义或"蓝图。创建一个新的类,语法类似于函数,但是你使用了关键字而不是 def 。在这个类块的缩进中,我们将为我们的类属性方法编写蓝图。不过现在不要担心这些;我们将在周二和周三复习这些内容。现在,我们将只使用关键字传递。让我们来看一个例子:

# creating your first class
class Car( ):
      pass      # simply using as a placeholder until we add more code tomorrow

去查查手机。什么也不会发生,但这很好,因为这意味着它的工作!所有的类都是用相同的结构创建的,除了不用写 pass ,我们将用赋予对象特性的代码填充这个块。

注意

在 Python 中,数据类型的基础也是类。打印出整数的类型导致

创建实例

现在我们知道了如何创建类定义,我们可以开始理解如何创建一个对象的实例。像将数据类型存储到变量名中一样,我们使用类似的语法,除了在类名后面,我们使用括号。我们将在明天的课中复习这些括号的用途。让我们来看看:

# instantiating an object from a class
class Car( ):        # parens are optional here
      pass
ford = Car( )    # creates an instance of the Car class and stores into the variable ford
print(ford)

去查查手机。您将得到类似于 " < main 的输出。0x0332DB 处的汽车对象>。这描述了从“汽车、构建实例的类,以及类本身在内存中存储的位置“ 0x0332DB。“我们已经成功地创建了汽车对象的一个实例,并将其存储到变量“ ford ”中。

创建多个实例

请记住,您可以从每个类中创建任意数量的实例;但是,您将它们存储在单独的变量或数据集合中。让我们从我们的类中创建两个实例:

# instantiating multiple objects from the same class
class Car( ):
      pass
ford = Car( )
subaru = Car( )    # creates another object from the car class
print( hash(ford) )
print( hash(subaru) )    # hash outputs a numerical representation of the location in memory for the variable

去查查手机。当我们输出变量的哈希值时,我们得到两个不同的数字。这些数字是变量在内存中位置的数字表示。这意味着尽管这两个变量是从同一个源创建的,但它们是作为独立的实体存储在程序中的。这就是物品的美,因为每个实例都有自己的特点。

周一练习

  1. Animals :创建一个名为“Animals”的类,并从中创建两个实例。使用两个名为“狮子”和“老虎”的变量

  2. 解题:下面这段代码有什么问题?

    >>> class Bus:
    >>>     pass
    >>> school_bus = Bus( )
    
    

今天是进入面向对象编程世界的第一步。为了在 Python 中构建对象,我们必须首先创建类定义,也称为蓝图。从那里,我们可以从该类创建单个或多个实例。这个过程被称为实例化。明天我们将看到如何给每个实例赋予特性。

星期二:属性

昨天我们看到了如何创建一个类定义。今天,我们将开始了解如何给类和它们的实例赋予个性化的特性,即所谓的属性。属性只是定义在类中的变量,仅此而已。如果你听到有人在谈论属性,你会马上知道他们在谈论类。属性是我们如何存储每个对象实例的个人信息。将属性视为对象的信息来源。对于汽车,属性可以是颜色、车轮数量、座位数量、引擎大小等。

为了继续本课,让我们从之前的笔记本文件“ Week_07 ”继续,并在底部添加一个标有“属性”的 markdown 单元格。

声明和访问属性

像变量一样,我们用名称和值来声明属性;但是,它们是在类内部声明的。我们在上周已经讨论过范围。属性只在它们被定义的类中可用,所以为了访问一个属性,你必须创建一个实例:

# how to define a class attribute
class Car( ):
      sound = "beep"      # all car objects will have this sound attribute and its' value
      color = "red"           # all car objects will have this color attribute and its' value
ford = Car( )
print(ford.color)     # known as 'dot syntax'

去吧,运行细胞。输出结果为“红色”。当我们从汽车类中实例化福特变量时,它是用两个属性创建的。这些属性是在类定义中自动设置的,因此从 Car 类创建的每个实例都将发出声音哔哔声,颜色为红色。“我们以后再看看如何改变这种情况。为了访问对象的属性,使用点语法。您可以从写实例的名称开始,后跟一个点和您想要访问的属性。所有类都使用类似的点语法来访问属性和方法(更多关于方法的内容参见)。

更改实例属性

不是所有你创建的对象都有相同的特征,所以你需要有能力改变属性值。为此,您需要使用点语法:

# changing the value of an attribute
class Car( ):
     sound = "beep"
     color = "red"
ford = Car( )
print(ford.sound)     # will output 'beep'
ford.sound = "honk"       # from now on the value of fords sound is honk, this does not affect other instances
print(ford.sound)    # will output 'honk'

去查查手机。您会注意到,我们将在修改之前和之后输出福特实例的声音属性。使用点语法,我们能够给声音属性分配一个新值。这与改变变量的值没有什么不同。福特物体的声音属性现在将是“嘎”直到我们决定改变它。

使用 init()方法

到目前为止,我们一直在以非常基本的形式创建类。当你想实例化一个具有特定属性的对象时,需要使用初始化 ( init )方法。每当创建一个实例,就会立即调用 init 方法。您可以使用此方法在创建时实例化具有不同属性值的对象。这使我们能够轻松地创建具有个性化属性的类实例。现在,我们明天将讨论方法,所以不要太担心语法,但更要理解如何使用这个方法。这个方法的声明在单词 init 前后有两个下划线。它还将括号内的 "self" 关键字(在下一节中对此有更多介绍)作为强制参数。对于本例,我们将创建一个实例,并在实例化时定义颜色。让我们继续尝试一下:

1| # using the init method to give instances personalized attributes upon creation
3| class Car( ):
4|    def __init__(self, color):
5|            self.color = color       # sets the attribute color to the value passed in
7| ford = Car("blue")      # instantiating a Car class with the color blue
9| print(ford.color)

去查查手机。我们将得到一个结果输出“蓝色”。当我们创建福特实例时,它被初始化为属性颜色设置为蓝色。所有这些都发生在第 5 行。当我们声明要实例化的 ford 变量时,它立即将参数“blue”传递给初始化方法。self 参数被忽略,并且“蓝色”被传入颜色参数。在 init 方法中,我们将 color 属性设置为刚刚传入的参数。因此值为“蓝色”请记住,该方法的参数与函数的工作方式相同,并且需要按照正确的顺序。

“自我”关键词

self 关键字是对该类的当前实例的引用,用于访问与该实例相关联的变量和方法。想象一个你从未见过的足球队。你如何区分每个玩家和下一个玩家?你可能会用他们球衣背面的号码。即使每个玩家都有不同的特征,你也可以很容易地根据他们的号码挑选出他们中的任何一个。在 Python 中,本质上就是如何识别从同一来源创建的对象。在前一个单元格中,我们打印出了来自福特实例的属性颜色。Python 之所以知道在哪里访问这个值,特别是针对 ford ,是因为我们使用了 self 关键字。对于基本类,我们不需要它,因为这些属性是全局可访问的,这将在今天晚些时候讨论。现在,只要知道当您想要实例化一个具有个性化属性的对象时,您需要声明 init 方法,并使用 self 关键字来保存每个属性值。

init()实例化多个对象

为了真正理解 init 方法是如何工作的,让我们用两个不同值的属性实例化两个实例:

# defining different values for multiple instances
class Car( ):
      def __init__(self, color, year):
              self.color = color       # sets the attribute color to the value passed in
              self.year = year
ford = Car("blue", 2016)       # create a car object with the color blue and year 2016
subaru = Car("red", 2018)    # create a car object with the color red and year 2018
print(ford.color, ford.year)
print(subaru.color, subaru.year)

去查查手机。底部的两条 print 语句将输出每个实例的属性。当我们实例化福特斯巴鲁对象时,我们为它们各自的属性赋予了不同的值。这就是 OOP 的妙处。我们能够用两行代码从同一个源构建两个不同的对象。即使类本身有几千行长,创建十个不同的实例也只需要十行代码。

全局属性与实例属性

不知不觉中,你已经使用了全局可访问的属性和实例可访问的属性。全局属性可以被类及其所有实例直接引用,而实例属性(,在 init 方法中定义)只能被类实例访问。如果一个属性是在一个类中声明的,而不是在 init 方法中声明的,那么它就是一个全局属性。使用 self 关键字在 init 方法中声明的任何属性都是实例属性。让我们看一个例子:

 1| # using and accessing global class attributes
 3| class Car( ):
 4|       sound = "beep"   # global attribute, accessible through the class itself
 6|       def __init__(self, color):
 7|               self.color = "blue"     # instance specific attribute, not accessible through the class itself
 9| print(Car.sound)
11| # print(Car.color)   won't work, as color is only available to instances of the Car class, not the class itself
13| ford = Car("blue")
15| print(ford.sound, ford.color)   # color will work as this is an instance

去查查手机。在第 6 行,我们打印出声音“beep”,通过使用点语法的类蓝图直接访问它。您可以通过使用类名而不是实例名来实现这一点。我们能够这样做是因为声音属性被设置为一个全局可访问的属性。整个第 7 行被注释掉,因为它会产生一个错误,因为颜色属性是在 init 方法中声明的,并且只能被实例访问,而不能被类本身访问。最后,在第 9 行,在我们实例化了 ford 实例之后,我们打印出了声音颜色属性。所有的类实例都可以访问全局和实例级别的属性,这就是为什么我们能够输出声音。然而,您必须记住的是,我们无法为 ford 实例的 sound 属性赋予个性化的值。只有在 init 方法中声明属性时,我们才能在实例化时赋予实例个人值。目前,为了给福特声音属性赋予不同的值,我们必须在实例化后对其进行更改。

星期二练习

  1. Dogs :创建一个Dogs类,它有一个全局属性和两个实例级属性。全局属性应该是“物种,值为“犬科动物”。"这两个实例属性应该是"名称和"品种。然后实例化两个狗对象,一个哈士奇名为萨米和一个巧克力实验室名为凯西

  2. 用户输入:创建一个 Person 类,该类有一个名为的实例级属性。"要求用户输入他们的名字,并用他们输入的名字创建一个 Person 类的实例。然后打印出他们的名字。

今天我们学习了所有关于属性的知识,以及如何给类赋予个性化的变量。初始化方法和 self 关键字的使用允许我们在实例化时声明属性。最后,全局和实例级属性之间的差异是关键。初始化方法中的那些属性不能通过类直接访问,而是通过类的实例访问。

星期三:方法

当您想到对象时,您会将某些功能和动作与它们联系起来。以汽车为例。它们有颜色和轮子等属性,还有动作,如停止、加速、转弯等。在类中,这些动作被称为方法方法本质上是类内的函数。如果你听到有人谈论方法,你会立刻知道他们在谈论 OOP。今天,我们将看到如何为我们的类声明方法,如何调用它们,以及它们为什么有用。

为了跟上这一课,让我们从笔记本文件“ Week_07 ”继续,并在底部添加一个标有“方法”的 markdown 单元格。

定义和调用方法

定义一个方法和定义一个函数是一样的;但是,您只需将代码放在类缩进块中。当声明一个想要通过实例访问的方法时,必须在定义中使用 self 参数。没有 self 关键字,方法只能由类本身访问。为了调用一个方法,你使用点语法。因为方法只是函数,所以调用它们时必须在实例名后加上括号:

# defining and calling our first class method
class Dog( ):
      def makeSound(self):
              print("bark")
sam = Dog( )
sam.makeSound( )

去查查手机。我们将得到“树皮作为我们的输出。当我们创建类定义时,它在蓝图中包含了方法 makeSound 。一旦我们创建了 Dog 类的实例,我们就能够通过使用点语法调用它来访问该方法。在一个类中,你可以有任意多的方法。

在方法中访问类属性

在您创建的方法中,您经常需要访问类中定义的属性。为此,您需要使用 self 关键字来访问该属性。记住 self 引用的是访问该类的实例。当我们创建多个实例时, self 允许程序理解返回哪个声音属性。即使对于全局属性也是如此。让我们看一个例子:

# using the self keyword to access attributes within class methods
class Dog( ):
      sound = "bark"
      def makeSound(self):
              print(self.sound)    # self required to access attributes defined in the class
sam = Dog( )
sam.makeSound( )

去查查手机。我们将再次得到“ bark 的输出,除了这一次,这是因为我们访问了在类中声明的声音属性。每当您需要使用 self 引用属性时,您必须在方法参数中包含 self

方法范围

像全局属性一样,您可能有可以通过类本身而不是类的实例访问的方法。这些也可以称为 。它们不能由类的实例访问。根据您构建的类,拥有一个只能通过类而不能通过实例访问的方法可能会有所帮助。让我们看一个例子:

 1| # understanding which methods are accessible via the class itself and class instances
 3| class Dog( ):
 4|   sound = "bark"
 6|   def makeSound(self):
 7|           print(self.sound)
 9|   def printInfo( ):
10|           print("I am a dog.")
12| Dog.printInfo( )       # able to run printInfo method because it does not include self parameter
14| # Dog.makeSound( )    would produce error, self is in reference to instances only
16| sam = Dog( )
18| sam.makeSound( )     # able to access, self can reference the instance of sam
20| # sam.printInfo( )     will produce error, instances require the self parameter to access methods

去查查手机。这次我们在我们的 Dog 类中定义了两个方法。一个方法在参数中有 self ,而另一个没有。没有自身参数的方法可以通过类本身访问,这也是第 8 行输出“我是狗”的原因。”。第 9 行被注释掉是因为 makeSound 只能被我们的 Dog 类的实例访问,而不是类本身。最后,我们可以看到第 12 行也被注释掉了,因为没有用 self 作为参数定义的方法不能被类的实例访问。否则,我们会产生错误。这就是自我关键词的重要性。

将参数传递给方法

方法的工作方式与函数相同,可以将参数传递给要使用的方法。当传入这些参数时,不需要使用 self 参数引用它们,因为它们不是属性,而是该方法可以使用的临时变量:

# writing methods that accept parameters
class Dog( ):
      def showAge(self, age):
              print(age)    # does not need self, age is referencing the parameter not an attribute
sam = Dog( )
sam.showAge( 6 )    # passing the integer 6 as an argument to the showAge method

去查查手机。我们会得到一个 6 的输出。在定义了一个的实例后,我们调用了方法 showAge ,并将整数 6 的参数传递给该方法。该方法然后能够打印出年龄。我们不需要说“ self.age ”,因为 self 引用的是类属性,而不是参数。

使用 Setters 和 Getters

在编程中有一个概念叫做设置器获取器。它们是您创建来重新声明属性值并返回属性值的方法。我们已经看到了如何通过直接访问属性值来改变它们;然而,这有时会导致问题或意外改变值。好的做法是创建一个方法来改变属性值,并在需要设置新值时调用该方法。当您想要访问给定的属性值时,情况也是如此;不是直接访问它,而是调用一个将返回值的方法。这为我们提供了一种更安全的方法来访问实例属性。让我们看看如何:

 1| # using methods to set or return attribute values, proper programming practice
 3| class Dog( ):
 4|  name = ' '     # would normally use init method to declare, this is for testing purposes
 6|  def setName(self, new_name):
 7|          self.name = new_name      # declares the new value for the name attribute
 9|  def getName(self):
10|          return self.name       # returns the value of the name attribute
11| sam = Dog( )
13| sam.setName("Sammi")
15| print( sam.getName( ) )       # prints the returned value of self.name

去查查手机。我们已经创建了两个方法,一个 setter 和一个 getter 。这些方法一般会有各自的关键字“ set ”和“ get ”开头的方法名。在第 4 行,我们定义了一个 setter 来接受一个 new_name 的参数,并将属性名更改为传入的值。这是更改属性值的更好的做法。在第 6 行,我们创建了一个 getter 方法,它简单地返回了 name 属性的值。这是检索属性值的更好的做法。第 9 行和第 10 行调用了这两个方法,以便修改并打印出返回值。

用方法增加属性

像 setters 一样,当您想通过增加或减少属性值而不是完全改变它时,最好的方法是创建一个方法来完成任务:

# incrementing/decrementing attribute values with methods, best programming practice
class Dog( ):
      age = 5
      def happyBirthday(self):
              self.age += 1
sam = Dog( )
sam.happyBirthday( )    # calls method to increment value by one
print(sam.age)    # better practice use getters, this is for testing purposes

去查查手机。对于这个例子,我们创建了一个名为 happyBirthday 的方法,它将在每次被调用时将狗的年龄加 1。这只是更好的实践,但不是更改类属性值的必需方法。

方法调用方法

当从另一个方法调用一个方法时,需要使用 self 参数。让我们创建一个 getter 方法和一个根据值打印出狗的信息的方法:

 1| # calling a class method from another method
 3| class Dog( ):
 4|  age = 6
 6|  def getAge(self):
 7|          return self.age
 9|  def printInfo(self):
10|          if self.getAge( ) < 10:     # need self to call other method for an instance
11|                  print("Puppy!")
13| sam = Dog( )
15| sam.printInfo( )

去查查手机。我们将在这里得到“ Puppy 的输出。我们可以从我们的 getter 中获得返回值,因为我们在 printInfo 方法中引用了 getAge 方法。它使用了 self 关键字和点语法。该条件被证明为真,因为返回值是 6 ,所以它继续在块内运行 print 语句。

神奇的方法

虽然它们有一个有趣的名字,魔法方法是 Python 中类的基础。不知不觉中,你已经使用了一个,初始化方法。所有魔法方法的名字前后都有两个下划线。当你打印任何东西的时候,你正在访问一个叫做 str 的神奇方法。当你使用操作符 (+,-,/,∑,==等。你正在使用魔法方法。它们本质上是决定 Python 中的操作符和其他任务执行什么的函数。不要太沉迷于它们,因为我们不会太多地使用它们,但我想向你介绍它们。如前所述,使用 print 函数时调用 str magic 方法;它代表一个类的字符串表示。让我们改变打印出我们自己定义的类时打印出的内容:

# using magic methods
class Dog( ):
      def __str__(self):
              return "This is a dog class"
sam = Dog( )
print(sam)     # will print the return of the string magic method

去查查手机。以前当我们打印一个类时,它会输出类蓝图的名称和内存位置。现在,由于我们改变了 str 魔法方法,我们能够输出完全不同的打印语句。请记住, str 魔法方法期望返回一个字符串,而不是打印出来。所有神奇的方法都需要特定的参数和返回值。请随意查找更多的例子,并修改其他例子,看看它们是如何工作的!

周三练习

  1. Animals :创建一个动物的类定义,该动物具有一个物种属性以及一个 setter 和 getter 来改变或访问属性值。创建一个名为“狮子、的实例,并使用参数“猫”调用 setter 方法。”然后通过调用 getter 方法打印出物种

  2. 用户输入:创建一个类 Person ,它在实例化时接受一个名字,但是将年龄设置为 0。在类定义设置中,setter 和 getter 将要求用户输入他们的年龄,并将 age 属性设置为输入值。然后将格式化字符串中的信息输出为“你已经 64 岁了。“假设用户输入 64 作为他们的年龄。

今天,我们学习了方法以及它们在类中的基本功能。为了访问其他方法,我们需要使用 self 参数。方法为类提供了额外的功能,并且几乎在我们创建的每个类中都要用到。这将给给定类的所有实例相同的功能。

周四:继承

有时你会创建具有相似属性或方法的类。以一个关于狗和猫的课程为例。两者都有几乎相同的代码、属性和方法。我们使用一个叫做继承的概念,而不是写同样的代码两次。

为了跟上这一课,让我们从之前的笔记本文件“ Week_07 ”继续,并简单地在底部添加一个标有“继承”的单元格

什么是继承?

继承是允许类在编程中具有代码可重用性的概念之一。当你有两个或更多使用相似代码的类时,你通常想要建立一个所谓的“超类。“继承超类中所有代码的两个类被称为子类。“想到传承的一个好办法就是父母和他们的孩子。父母将基因传递给他们的孩子,这些基因是遗传的,并有助于确定孩子出生时的特征。继承以同样的方式工作,其中子类继承超类中的所有属性和方法。我们可以继承一个类,只需要编写一次代码,而不是为两个类编写两次相同的属性和方法。

继承一个类

要继承一个类,我们需要将我们继承的类的名称放在子类名称后面的括号中。让我们来试试:

 1| # inheriting a class and accessing the inherited method
 3| class Animal( ):
 4|  def makeSound(self):
 5|          print("roar")
 7| class Dog(Animal):         # inheriting Animal class
 8|  species = "Canine"
10| sam = Dog( )
12| sam.makeSound( )     # accessible through inheritance
14| lion = Animal( )
16| # lion.species      not accessible, inheritance does not work backwards

去查查手机。在第 5 行,我们将动物类继承到我们的类中。这使得能够访问 makeSound 方法,这就是为什么在第 8 行,我们能够使用点语法来访问 makeSound 。但是请记住,继承不能向后工作,所以 Animal 不能访问 Dog 类中定义的属性和方法。因此,第 10 行被注释掉,因为物种属性在动物中不存在,试图访问它会产生错误。

使用 super()方法

使用继承时, super 方法用于创建向前兼容性。当声明超类中需要的属性时, super 用于初始化它的值。 super 的语法是关键字 super、括号、点号、初始化方法以及 init 调用括号内的任何属性。让我们看一个例子:

 1| # using the super( ) method to declare inherited attributes
 3| class Animal( ):
 4|  def __init__(self, species):
 5|          self.species = species
 7| class Dog(Animal):
 8|  def __init__(self, species, name):
 9|          self.name = name
10|          super( ).__init__(species)   # using super to declare the species attribute defined in Animal
12| sam = Dog("Canine", "Sammi")
14| print(sam.species)

去查查手机。在第 6 行,我们声明 name 属性等于传入的参数,因为这个属性只在 Dog 类中定义。第 7 行是调用超级方法来初始化物种属性的地方,因为它是在超类动物内部声明的。这里使用 super 有助于减少代码行,这在超类需要几个属性时更明显。一旦调用了超级方法,我们的物种属性值被设置为传入的参数,我们现在可以通过我们的实例访问它,这就是为什么我们能够在第 9 行输出物种。

方法覆盖

有时当使用继承时,您希望子类能够在调用相同的方法时执行不同的操作。从之前创建的动物类中获取我们的 makeSound 方法。它打印出“咆哮”,但这不是你创建类时想要狗发出的声音。相反,我们使用方法覆盖的概念来改变方法的功能。在子类中,我们重新定义了方法(同名的)来执行不同的任务。Python 总是首先使用子类中定义的方法,如果不存在,它将检查超类。让我们使用方法覆盖来改变 makeSound 方法,并为我们的 Dog 类打印正确的语句:

 1| # overriding methods defined in the superclass
 3| class Animal( ):
 4|  def makeSound(self):
 5|          print("roar")
 7| class Dog(Animal):
 8|  def makeSound(self):
 9|          print("bark")
11| sam, lion = Dog( ), Animal( )     # declaring multiple variables on a single line
13| sam.makeSound( )     # overriding will call the makeSound method in Dog
15| lion.makeSound( )    # no overriding occurs as Animal does not inherit anything

去查查手机。在第 8 行,我们声明了两个实例山姆狮子。下一行是我们从我们的狗实例 sam 中调用 makeSound 方法的地方。由于方法覆盖,输出导致“吠声”。由于该方法被继承,但随后在 Dog 类中被重新定义,它打印 bark。在第 10 行,我们用我们的动物实例狮子调用同样的方法。这个输出是“ roar ,因为 lionAnimal 类的一个实例。请记住,继承不能反向工作。子类不能给超类任何特性。

继承多个类

到目前为止,我们已经看到了如何从单个超类继承。现在我们将尝试从多个类中继承。主要的区别是你如何超属性。不使用 super 方法,而是直接调用类名并传入带有属性的 self 参数。让我们看看如何:

 1| # how to inherit multiple classes
 3| class Physics( ):
 4|  gravity = 9.8
 6| class Automobile( ):
 7|  def __init__(self, make, model, year):
 8|          self.make, self.model, self.year = make, model, year    # declaring all attributes on one line
10| class Ford(Physics, Automobile):      # able to access Physics and Automobile attributes and methods
11|  def __init__(self, model, year):
12|          Automobile.__init__(self, "Ford", model, year)   # super does not work with multiple
14| truck = Ford("F-150", 2018)
16| print(truck.gravity, truck.make)   # output both attributes

去查查手机。我们会得到 9.8福特的产量。在第 7 行,你会注意到我们继承了括号中的两个类福特类。第 9 行是这次奇迹发生的地方。我们不使用 super,而是通过直接调用继承类的名称来初始化变量。使用 init 方法,我们传递自身参数以及汽车需要的所有属性。Python 知道使用哪个超类,因为行首有名字。在最后一行,我们可以看到我们可以访问在 PhysicsAutomobile 中声明的两个属性,我们从这里继承。

周四练习

  1. 好人/坏人:创建三个类,一个名为“角色的超类,将使用以下属性和方法进行定义:
    1. 属性:姓名、队伍、身高、体重

    2. 方法:sayHello

      The sayHello method should output the statement “Hello, my name is Max and I’m on the good guys”. The team attribute should be declared to a string of either “good” or “bad.” The other two classes, which will be subclasses, will be “GoodPlayers” and “BadPlayers.” Both classes will inherit “Characters” and super all the attributes that the superclass requires. The subclasses do not need any other methods or attributes. Instantiate one player on each team, and call the sayHello method for each. The output should result in the following:

            >>> "Hello, my name is Max and I'm on the good guys"
            >>> "Hello, my name is Tony and I'm on the bad guys"
      
      

今天的主题是 OOP 中的继承。使用继承,我们可以减少我们在相似的类之间写的重复的行。继承的类称为超类,而执行继承的类称为子类。此外,重写继承方法的能力被称为方法重写,并为子类提供类定制。

星期五:创造 21 点

在这一周里,我们学习了如何使用 Python 中的类来改进我们的程序。今天,我们将把所有的知识放在一起,一起构建流行的游戏 21 点。我们将在整个程序中使用类,你将会看到我们如何用 Python 构建一个成熟的面向对象的游戏。假设你知道如何玩 21 点。如果没有,请随意查找规则和如何玩的步骤。

为了跟上这一课,让我们从之前的笔记本文件“ Week_07 ”继续,并在底部添加一个减价单元格,内容为“星期五项目:创造 21 点”。

完工图纸

和之前所有周五的项目一样,我们需要创建一个我们可以遵循的最终设计。本周有点不同,因为我们也需要首先设计我们的类。这将有助于我们在开始编程之前找出我们的类需要哪些属性和方法。坚持这个蓝图将会改进编程过程。首先,让我们想想我们需要什么类。在 21 点游戏中,你有特定的游戏规则、游戏动作和这副牌本身。然后我们还需要考虑有一个玩家和一个庄家在玩游戏。似乎我们需要创建两个类,一个用于游戏本身,一个用于两个玩家。你可能会争辩说,你需要一个单独的庄家和玩家类别;然而,我们保持这个游戏设计简单一点。我们先想想游戏类需要什么:

  • 游戏属性

    卡片组——容纳游戏中使用的所有 52 张卡片

    花色–用于创建所有四种花色的牌组

    值——用于创建一副牌,所有牌值的元组

  • 游戏方式

    制作一副牌——当被调用时创建新的 52 副牌

    pull card——从一副牌中弹出任意一张牌并返还给它

游戏类主要是记录我们玩的牌。我们当然也可以将所有与游戏相关的方法放在这个类中;然而,我想让这些类简单一些,便于你理解。如果你想事后重构游戏,请随意。像 checkWinner,checkBust,handleTurn 等方法。,都可能是游戏类的一部分。在这一课中,我们不会担心将这些方法添加到游戏中。知道游戏类将要处理什么将帮助我们理解我们的玩家类需要什么。现在让我们继续计划这个类的属性和方法:

  • 玩家属性

    手牌——将牌储存在玩家手中

    name–存储玩家或庄家名字的字符串变量

  • 玩家方法

    calcHand–返回计算所得的总点数

    show hand——以格式优美的语句打印出玩家的手牌

    add card——取一张牌并把它加到玩家的手上

正如我们所见,玩家类将记录每个玩家的手牌以及任何与改变手牌相关的方法。通常,您总是希望将改变属性的方法放在存储属性的同一个类中。现在我们已经对每个类所需的属性和方法有了一个很好的想法,我们将遵循这个指导方针来为游戏编程。

设置导入

让我们通过导入我们将使用的必要函数来开始编写这个程序:

1| # importing necessary functions
2| from random import randint          # allows us to get a random number
3| from IPython.display import clear_output

随意测试一下 randint 函数。它接受两个参数,a 最小值最大值,并将在这两个参数之间返回一个随机数。我们需要的另一个输入是清除笔记本单元输出的能力。

创建游戏类

接下来,我们将开始编写我们的主游戏类,我们称之为21 点。查看我们之前创建的设计,我们需要用属性牌组花色初始化该类:

 5| # create the blackjack class, which will hold all game methods and attributes
 6| class Blackjack( ):
 7|  def __init__(self):
 8|          self.deck = [ ]      # set to an empty list
 9|          self.suits = ("Spades", "Hearts", "Diamonds", "Clubs")
10|          self.values = (2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K", "A")

我们将 deck 属性设置为空列表,因为我们将创建一个为我们创建 deck 的方法。另外两个属性被创建为元组,这样我们可以在不改变条目的情况下迭代它们。我们将使用它们来制作我们的卡片。

生成甲板

使用在二十一点类中定义的花色,我们将构建我们的牌组:

12|     # create a method that creates a deck of 52 cards, each card should be a tuple with a value and suit
13|     def makeDeck(self):
14|          for suit in self.suits:
15|                  for value in self.values:
16|                          self.deck.append( (value, suit) )    # ex: (7, "Hearts")
18| game = Blackjack( )
19| game.makeDeck( )
20| print(game.deck)         # remove this line after it prints out correctly

去查查手机。我们的 makeDeck 方法已经生成了 52 个元组的完整组,每个元组的值在 0 索引中,一个组在 1 索引中。我们将每张卡存储为一个元组,因为我们不想意外地改变值。在最后三行中,我们创建游戏的一个实例,调用 makeDeck 方法,并输出 deck 属性的值。完成后一定要删除最后一行,因为 print 语句仅用于调试目的。

从这副牌中抽出一张牌

现在我们已经创建了这副牌,我们可以创建一个方法来从这副牌中抽取一张牌。我们将使用 pop 方法,这样我们可以同时获得一个项目并将其从卡片组中移除:

16|                          self.deck.append( (value, suit) )     #   ex: (7, "Hearts")  ◽◽◽
18|  # method to pop a card from deck using a random index value
19|  def pullCard(self):
20|          return self.deck.pop( randint(0, len(self.deck) – 1) )
22| game = Blackjack( )
23| game.makeDeck( )
25| print( game.pullCard( ), len(game.deck) )     # remove this line after it prints out correctly

去查查手机。您应该得到类似于“ (7,' Hearts') 51 ”的输出。元组是我们打印出来的牌,而 51 正在向我们证明它正在从这副牌中移除一张牌。我们设置了 pullCard 方法,这样它可以从一副牌中随机弹出一张牌。它随机选择,因为我们传递给 randint 的参数。我们允许的最大数量总是比卡片组的大小小一,因为索引是从零开始的。如果这副牌还有 45 张,我们希望随机整数在 0 到 44 之间。然后,它弹出随机索引中的项目,将其从卡片组中移除,并将其返回到调用该方法的位置。目前,我们只是将它打印出来,但稍后我们会将它添加到玩家的手中。完成后一定要删除最后一行,因为 print 语句仅用于调试目的。

创建玩家类

随着游戏类的正常运行,我们将注意力转向玩家类。让我们首先创建类定义来接受一个名称,并将指针设置为一个空列表:

20|            return self.deck.pop( randint(0, len(self.deck) – 1) )  ◽◽◽
22| # create a class for the dealer and player objects
23| class Player( ):
24|  def __init__(self, name):
25|          self.name = name
26|          self.hand = [ ]
28| game = Blackjack( )
29| game.makeDeck( )
31| name = input("What is your name?")
32| player = Player(name)
33| dealer = Player("Dealer")
34| print(player.name, dealer.name)    # remove after working correctly

去查查手机。我们将得到输入的名称的打印声明,以及“经销商”。我们用属性来定义要初始化的玩家类。 name 属性作为参数被接受,而 hand 被直接设置在类内部。在我们实例化了游戏对象后,我们要求用户输入他们的名字,并用他们的输入创建一个玩家类的实例。 dealer 对象将始终被称为“ Dealer ”,这就是为什么我们在实例化过程中使用传入的值来创建实例。

将牌添加到玩家手中

一旦我们正确实例化了 player 对象,我们就可以开始处理 Player 类所需的方法了。在看先用哪个方法编程的时候,总是需要思考哪些方法依赖于其他方法。对于这个类,卡尔昌德showHand 方法依赖于手中的牌。出于这个原因,我们将致力于 addCard 方法,然后关注另外两个:

26|          self.hand = [ ]  ◽◽◽
28|  # take in a tuple and append it to the hand
29|  def addCard(self, card):
30|         self.hand.append(card)
32| game = Blackjack( )   ◽◽◽
37| dealer = Player("Dealer")   ◽◽◽
39| # add two cards to the dealer and player hand
40| for i in range(2):
41| player.addCard( game.pullCard( ) )
42| dealer.addCard( game.pullCard( ) )
44| print( "Player Hand: { } \nDealer Hand: { }".format(player.hand, dealer.hand) )   # remove after

去查查手机。我们将在玩家的每手牌中随机获得两张牌的输出。 addCard 方法只是接受一个表示一张牌的元组,并将其附加到玩家的手上。在第 40 行,我们开始一个循环的,这将为每手牌增加两张牌。它通过使用游戏实例方法 pullCard 来拉一张卡。该方法返回一个元组,然后该元组被传递给 addCard 方法,该方法随后被附加到相应玩家的手牌。这个循环将足以作为游戏的开始,其中所有玩家从他们手中的两张牌开始。确保删除最后一行,因为它用于调试。

展示玩家的手牌

在前面的部分中,我们打印出了每个玩家的整手牌。然而,在实际的 21 点中,你只显示发给庄家的第二张牌。直接引用属性也是不好的做法,所以我们需要创建 showHand 方法来解决这两个问题。我们将使用格式良好的打印声明来显示牌,但更重要的是,我们将确保如果仍轮到玩家,那么您只能看到庄家的一张牌:

30|          self.hand.append(card)   ◽◽◽
32|  # if not dealer's turn then only show one of his cards, otherwise show all
33|  def showHand(self, dealer_start = True):
34|          print( "\n{ }".format(self.name) )
35|          print("===========")
37|          for i in range( len(self.hand) ):
38|                  if self.name == "Dealer" and i == 0 and dealer_start:
39|                          print("- of –")  # hide first card
40|                  else:
41|                          card = self.hand[ i ]
42|                          print( "{ } of { }".format( card[0], card[1] ) )
44| game = Blackjack( )   ◽◽◽
54|    dealer.addCard( game.pullCard( ) )   ◽◽◽
56| # show both hands using method
57| player.showHand( )
58| dealer.showHand( )

去查查手机。输出的结果是玩家手里显示两张牌,而庄家只显示一张。让我们一步一步来。在第 33 行,我们用参数 dealer_start 声明了 showHand 方法。这个参数将是一个布尔值,它跟踪我们是否隐藏发牌者拿到的第一张牌。我们将默认值设置为 True ,这样我们唯一需要将参数 False 传入该方法的时间是在最后我们想要显示庄家的牌的时候。第 37 行的 for 循环允许我们打印出玩家对象手中的每张牌。第 38 行是我们检查两件事的地方:

  1. 调用这个方法的实例是经销商。

  2. 还没轮到庄家 (dealer_start == True)。

如果两者都为真,那么我们隐藏第一张牌;否则,我们将显示玩家庄家的所有牌。声明卡片变量是为了在读取代码时易于使用,因为我们将它设置为我们手中的一个项目,它代表一张卡片。然后,我们打印带有元组值的格式化语句。这是通过访问代表每张卡的元组的 01 索引来完成的。在单元格的底部,我们为每个玩家对象调用这些方法。

计算手总数

既然我们能够调用一个方法来正确显示玩家的每手牌,我们需要计算这手牌中的牌的总数。然而,这种方法变得有点棘手,因为我们需要记住一些检查:

  1. ace 可以值 11 或 1 分。如果总数超过 21,它们就值 1 分。

  2. 如果庄家只出示一张牌,那么即使他手里有两张牌,他手牌的价值也应该只代表这一张牌的价值。

  3. 所有的面牌(J,Q,K)都值 10 分。

有几种方法可以处理这个方法。我们一起编程只是众多方式中的一种。当考虑如何计算 ace 时,我们需要在计算完所有其他牌的总数后检查它们的值。我们会先记录我们有多少个 a,然后再合计。为了确保我们正确地返回庄家的总数,我们将像在 showHand 方法中那样跟踪是否轮到他。最后,为了计算牌面的值,我们将创建一个值字典来提取:

42|                      print( "{ } of { }".format( card[0], card[1] ) )   ◽◽◽
43|                  print( "Total = { }".format( self.calcHand(dealer_start) ) )
45|     # if not dealer's turn then only give back total of second card
46|     def calcHand(self, dealer_start = True):
47|          total = 0
48|          aces = 0    # calculate aces afterwards
49|          card_values = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 10:10, "J":10, "Q":10, "K":10, "A":11}
51|          if self.name == "Dealer" and dealer_start:
52|                  card = self.hand[ 1 ]
53|                  return card_values[ card[ 0 ] ]
55|          for card in self.hand:
56|                  if card[ 0 ] == "A":
57|                          aces += 1
58|                  else:
59|                          total += card_values[ card[ 0 ] ]
61|          for i in range(aces):
62|                  if total + 11 > 21:
63|                          total += 1
64|                  else:
65|                          total += 11
67|          return total
69| game = Blackjack( )   ◽◽◽

去查查手机。从第 46 行开始,我们用参数 dealer_start 声明我们的 calcHand 方法。我们将这个参数设置为默认值 True ,这样它默认只显示庄家一张牌的总数。第 47 行是我们声明变量来跟踪总数的地方。第 48 行是我们声明变量的地方,用来记录我们手中有多少个 a。在第 49 行,我们声明了一个表示卡值的键值对字典。第 51 行的条件语句检查经销商实例是否是调用该方法的对象,以及经销商 _ 开始参数是否为。如果它们都为真,那么我们将简单地返回庄家手中第二张牌的价值。这是第二张牌,因为我们将变量设置为等于手牌中的第二个项目,即第二张牌。然后我们用索引 0 中的卡片变量项引用 card_values 字典。这个条目将成为一个键,然后字典将返回这个键-值对的值。如果索引 0 处的项目是“ J ”,字典将返回值 10。从第 55 行开始的 for 循环将遍历相应玩家手中的每张牌,参考字典中的牌值,并将该牌值添加到当前总数中。如果这张牌是 a,它只会在我们的a 变量上加 1,而不会在总数上加任何东西。第 61 行的下一个 for 循环将循环玩家手中的 a 数。对于每张 ace,我们将根据总点数增加 1 点或 11 点。如果给这手牌加 11 分使总数大于 21,我们就简单地加一分。在方法结束时,我们返回总数。最后,第 43 行是我们在 showHand 方法中调用 calcHand 的地方。我们传递 dealer_start 变量,以防我们试图在庄家的回合中亮出这手牌。稍后,在庄家的回合中,我们将传递的参数,然后它将计算所有庄家的牌的总数,而不仅仅是一张。

处理玩家的回合

类定义现在已经完成了。我们可以开始关注主要的游戏流程。首先,我们将处理玩家的回合。他们应该有能力击中或者停留。如果他们留下,他们的回合就结束了。如果他们打中了,那么我们需要从一副牌中抽出一张牌,加到他们手上。卡片添加后,我们必须检查玩家是否超过 21 岁。如果他们这样做,他们就输了,我们需要跟踪这一点,以确定以后的输出:

83| dealer.showHand( )   ◽◽◽
85| player_bust = False     # variable to keep track of player going over 21
87| while input("Would you like to stay or hit?").lower( ) != "stay":
88|   clear_output( )
90|   # pull card and put into player's hand
91|   player.addCard( game.pullCard( ) )
93|   # show both hands using method
94|   player.showHand( )
95|   dealer.showHand( )
97|   # check if over 21
98|   if player.calcHand( ) > 21:
99|           player_bust = True       # player busted, keep track for later
100|          print("You lose!")      # remove after running correctly
101|          break   # break out of the player's loop

去查查手机。现在,试着打到 21 岁以上。这将导致输出"你输了!”。如果你不超过 21 岁,什么都不会发生,因为我们还没有解决这个问题,但是我们会到达那里的。在第 85 行,我们声明了一个变量来跟踪超过 21 岁的玩家。然后我们开始 while 循环,询问用户是想打还是留下。如果他们选择留下,那么这个循环将会继续。在循环中,我们将清除输出,向玩家的手牌中添加一张牌,展示这手牌,然后检查他们是否失败。这个循环有两种结束方式,要么破产,要么选择留下。

处理庄家的回合

庄家的回合将与玩家的回合非常相似,但是我们不需要问庄家是否愿意打。庄家在 17 岁以下自动击球。我们还需要追踪经销商是否破产:

100|           break   # break out of the player's loop   ◽◽◽
102| # handling the dealer's turn, only run if player didn't bust
103| dealer_bust = False
105| if not player_bust:
106|    while dealer.calcHand(False) < 17:            # pass False to calculate all cards
107|          # pull card and put into player's hand
108|          dealer.addCard( game.pullCard( ) )
110|          # check if over 21
111|          if dealer.calcHand(False) > 21:         # pass False to calculate all cards
112|                  dealer_bust = True
113|                  print("You win!")    # remove after running correctly
114|                  break      # break out of the dealer's loop

去查查手机。尝试运行单元格,直到庄家超过 21,导致打印语句运行。我们首先在第 103 行声明一个变量来跟踪破产的经销商。在第 105 行,我们检查玩家是否已经被终结,因为这一轮已经结束,庄家不需要抽任何牌。第 106 行是我们循环的起点,它将向庄家的手牌中添加一张牌,并检查他是否失败。循环将继续,直到庄家超过 16 点,或者他超过 21 点。当我们这次为庄家调用 calcHand 方法时,我们传递了 False 的参数。这样一来,该方法将计算整手牌的总数,而不仅仅是第二张牌,就像我们之前所做的那样。

计算赢家

这个游戏的最后一步是计算谁是赢家。到目前为止,我们已经做了一些检查,看看玩家是否已经输了 21 分。我们将首先检查玩家是否破产,然后是庄家。如果两个玩家都没有失败,那么我们需要看看谁的总得分更高。如果他们打成平手,那么这就是所谓的推,没有人会赢:

113|                  break      # break out of the dealer's loop   ◽◽◽
115| clear_output( )
117| # show both hands using method
118| player.showHand( )
119| dealer.showHand(False)     # pass False to calculate and show all cards, even when there are 2
121| # calculate a winner
122| if player_bust:
123|    print("You busted, better luck next time!")
124| elif dealer_bust:
125|    print("The dealer busted, you win!")
126| elif dealer.calcHand(False) > player.calcHand( ):
127|    print("Dealer has higher cards, you lose!")
128| elif dealer.calcHand(False) < player.calcHand( ):
129|    print("You beat the dealer! Congrats!")
130| else:
131|    print("You pushed, no one wins!")

去查查手机。我们现在有一个功能齐全的 21 点游戏!首先,我们清除输出并显示玩家的双手。不过,主要的区别在第 119 行。我们将参数 False 传递给庄家的 showHand 方法。这是为了显示所有庄家的牌,以及完整的总数。记得我们在 showHand 中调用了 calcHand 方法,并传递了 dealer_start 的值,我们通过这个方法调用将该值设置为 False 。之后,我们设置一些条件,根据给定的条件输出正确的结果。

最终输出

祝贺您完成这个项目!由于项目的规模,你可以在 Github 上找到完整版本的代码。要找到这个项目的具体代码,只需打开或下载“ Week_07.ipynb 文件。如果您在这个过程中遇到了错误,请确保将您的代码与该文件中的代码进行交叉引用,看看您可能在哪里出错了。

尽管今天的项目很长,但我们还是看到了一些面向对象编程的好例子。使用类给了我们重用几行代码的能力,就像我们对玩家和庄家对象所做的那样。这个程序当然可以重构,在 21 点类中有更多的方法;然而,我希望您能够更容易地阅读代码。出于这个原因,我保持了较短的类和独立的主要游戏功能。如果你愿意的话,一定要测试这个游戏并加入你自己的特色。

每周总结

在这一周里,我们讨论了面向对象编程的概念,以及为什么它们在编程世界中如此重要。在 Python 中,我们称它们为类。它们允许我们重用代码并从一个对象创建多个实例。当在类中存储变量或创建函数时,它们被称为属性和方法。我们可以使用点语法和 self 参数来引用它们。如果没有类,我们将需要为程序中的所有对象硬编码每一行。这在大型项目中变得尤为明显。为了增加代码的可重用性,我们可以使用继承。这允许子类从超类继承属性和方法,就像父类和子类一样。在这个周末,我们能够创建一个面向对象的 21 点游戏。这展示了 OOP 的能力,因为我们能够创建 player 对象的多个实例。向前看,一定要把你周围的世界当成物体。它将帮助你适应面向对象的世界,理解对象的属性和方法。

挑战问题解决方案

挑战题的解答是 10 。这个输出背后的原因是字典的工作方式。记住,当从字典中访问信息时,您可以访问键-值对。当从字典中访问一个键时,您会得到那个键-值对的值。下面一行正在访问 card 变量中第一项的值:

     >>> card[0]

这将导致“Q”,因为它是分配给卡的元组中的第一个项目。当我们访问字典时,我们正在访问“Q”键的值。最后一行应该是这样的:

    >>> print("{ }".format(values["Q"]))

这将输出“Q:10”键-值对的值,即 10

每周挑战

要测试你的技能,请尝试以下挑战:

  1. 游戏循环:使用我们周五项目中的代码,创建一个游戏循环,这样你就可以不断地玩新的一手牌,直到玩家决定退出。只有当玩家输入“退出”时,电池才会停止运行;否则,你应该继续玩新牌。

  2. 添加货币:使用我们周五项目中的代码,在游戏中添加下注货币的功能。确保跟踪玩家类中的货币,因为属性应该属于那个对象。在每手牌之前,询问用户他们想下多少注;如果他们赢了,将该金额加入他们的货币;如果他们输了,从他们现在拥有的中减去这个数额;如果他们打成平手,什么也不会发生。

八、高级主题 I:效率

现在我们有了一个坚实的基础,我们可以开始深入更高级的主题。在接下来的两周里,我们将讨论一些概念,这些概念有助于减少您需要编写的代码量。这些概念中的许多将帮助我们为第 10 周的数据分析做准备。

本周,我们将讨论使用列表理解匿名函数的一行程序。这将有助于通过在一行中压缩相同的功能来减少代码行。然后,我们将介绍一些内置的 Python 函数,这些函数使数据处理变得更加容易。我们讨论的最后一个概念是当函数调用自己时,称为递归函数。通常,这些类型的函数缺乏效率,所以我们将介绍如何使用一个叫做记忆化的缓存概念。由于本周都是关于高级主题,我们将深入到编程中更重要的算法之一… 二分搜索法!我们将看到如何一行一行地编写这个算法,并理解搜索算法如何有效地工作。

概述

  • 使用理解在一行中构建列表

  • 理解一行匿名函数

  • 使用 Python 的内置函数进行列表变更

  • 理解递归函数以及如何改进它们

  • 为二分搜索法编写算法

挑战问题

在本周的挑战中,我想让你们编写一个程序,让一个用户输入一个数字,然后告诉这个用户他们输入的数字是不是一个质数。记住,质数只能被 1 和它本身整除,并且必须在数字 2 之上。创建一个名为“ isPrime 的函数,将输入传递给它,并返回一个值。在对该功能进行编程时,请务必牢记效率。

周一:列表理解

列表理解允许我们创建一个在单行中填充数据的列表。我们不用创建一个空列表,遍历一些数据,并在单独的行上把它附加到列表中,我们可以使用理解一次执行所有这些步骤。它不会提高性能,但是它更干净,有助于减少程序中的代码行。有了理解,我们可以把两行或更多行简化成一行。另外,一般来说写起来更快。

为了继续今天的内容,让我们从“ python_bootcamp ”文件夹中打开 Jupyter 笔记本。打开后,创建一个新文件,并将其重命名为“ Week_08。“接下来,对标题为:“列出理解”的第一个单元格进行降价。“我们将开始在那个牢房下面工作。

列表理解语法

使用列表理解的语法取决于你要写的内容。列表理解的一般语法结构如下所示:

>>> *result* = [   *transform*    *iteration*    *filter*   ]

例如,当您想要填充列表时,语法应该具有以下结构:

>>> name_of_list = [ item_to_append for item in list ]

但是,当您想要包含 if 语句时,理解应该如下所示:

>>> name_of_list = [ item_to_append for item in list if condition ]

只有满足条件,项目才会被追加到新列表中;否则不会收录。最后,如果您想包含一个 else 条件,它将如下所示:

>>> name_of_list = [ item_to_append if condition else item_to_append for item in list ]

当在列表理解中使用 else 条件时,只有当 if 语句证明为真时,第一项才会被追加到列表中。如果为 False,则 else 语句后面的项将被追加到列表中。

生成数字列表

让我们尝试使用列表理解生成一个从 0 到 100 的数字列表:

# create a list of ten numbers using list comprehension
nums = [ x for x in range(100) ]      # generates a list from 0 up to 100
print(nums)

去查查手机。您会注意到我们输出了一个包含 100 个数字的列表。列表理解允许我们在一行中构建这个列表,而不是在单独的行中写出 for 循环和 append 语句。来自前面单元格的理解是以下代码的精确表示:

>>> nums = [ ]
>>> for x in range(100):
>>>       nums.append(x)

如你所见,我们使用理解将三行减少到一行。这不会提高性能,但会减少代码中的行数。这在更大的程序中变得更加明显,我强烈建议你尽可能地使用理解。接下来,我们将开始在建立列表时使用列表理解。

If 语句

前面,我们讨论了在你的理解中包含一个 if 语句时,语法是如何变化的。让我们试一个例子,只列出偶数:

# using if statements within list comprehesion
nums = [ x for x in range(10) if x % 2 == 0 ]     # generates a list of even numbers up to 10
print(nums)

去查查手机。出于这种理解,只有当条件证明为真时,变量 x 才会被追加到列表中。在我们的例子中,当 x 的当前值被 2 整除时,条件为真。在下面的内容中,您会发现不使用理解时所需的相同代码:

>>> nums = [ ]
>>> for x in range(10):
>>>       if x % 2 == 0:
>>>               nums.append(x)

这一次,我们能够将四行代码减少到一行。这通常可以提高代码的可读性。

If-Else 语句

现在让我们更进一步,添加一个 else 语句。这一次,当数字被 2 整除时,我们将追加字符串“偶数”;否则,我们将追加字符串“奇数”:

# using if/else statements within list comprehension
nums = [ "Even" if x % 2 == 0 else "Odd" for x in range(10) ]   # generates a list of even/odd strings
print(nums)

去查查手机。这将输出一个表示奇数或偶数的字符串列表。这里,当 if 条件为真时,我们追加字符串“ Even ”;否则,else 语句将被命中并追加字符串“奇数”。没有理解的代码的相同表示可以在下面找到:

>>> nums = [ ]
>>> for x in range(10):
>>>       if x % 2 == 0:
>>>               nums.append("Even")
>>>         else:
>>>               nums.append("Odd")

我们已经将代码行从六行减少到一行。理解对于快速生成数据非常重要;但是条件大了就变得更难了。理解不允许使用 elif 语句,只允许使用 if/else 语句。

用变量列表理解

Comprehension 对于从其他列表中生成数据也非常有用。让我们利用理解力,得到一个数字列表,并生成这些数字的平方的单独列表:

# creating a list of squared numbers from another list of numbers using list comprehension
nums = [2, 4, 6, 8]
squared_nums = [ num**2 for num in nums ]      # creates a new list of squared numbers based on nums
print(nums)

去查查手机。我们会得到【4,16,36,64】的输出。对于这个例子,我们能够通过添加表达式“num∫2”来生成平方数。没有理解的代码的相同表示如下所示:

>>> squared_nums = [ ]
>>> for num in nums:
>>>      squared_nums.append(num**2)

在本例中,我们能够将所需的线路从三条减少到一条。

词典理解

不仅可以在列表上使用理解力,还可以在 Python 字典上使用理解力。语法结构完全相同,只是需要包含一个键-值对而不是一个数字来插入到字典中。让我们创建一个偶数作为键的字典,其中值是键的平方:

# creating a dictionary of even numbers and square values using comprehension
numbers = [ x for x in range(10) ]
squares = { num : num**2 for num in numbers if num % 2 == 0 }
print(squares)

去查查手机。我们会得到如下结果:“ {0: 0,2: 4,4: 16,6: 36,8: 64} ”。我们能够使用 comprehension 添加每个键值对,同时使用条件语句检查它们是否是偶数。

周一练习

  1. 度转换:利用列表理解,将下面的列表转换为华氏。目前,温度单位是摄氏度。换算公式为“ (9/5) * C + 32 ”。你的输出应该是【53.6,69.8,59,89.6】。

    >>> degrees = [ 12, 21, 15, 32 ]
    
    
  2. 用户输入:要求用户输入一个 100 以内的整数。使用列表理解生成一个能被 100 整除的数字列表。例如,如果输入了数字 25,那么输出应该是【25,50,75,100】

今天的重点是使用一个叫做列表理解的概念来生成列表。根据需要的表达式,您将使用特定的语法结构。理解并不能提高表现;相反,它减少了执行相同任务所需的代码行数。它还可以提高可读性。

星期二:λ函数

Lambda 函数,也称为匿名函数,是 Python 中的单行函数。像列表理解一样,lambda 函数允许我们减少程序中需要编写的代码行。它不适用于复杂的函数,但有助于提高较小函数的可读性。

为了跟上这一课,让我们从之前的笔记本文件“ Week_08 ”继续,并简单地在底部添加一个标有“ Lambda 函数”的 markdown 单元格。

Lambda 函数语法

lambda 函数的语法通常保持不变,不像开始添加条件语句时的列表理解。首先,让我们看看基本结构:

>>> lambda arguments : expression

Lambdas 总是以关键字 lambda 开头。接下来你会发现任何被传入的参数。在冒号的右边,我们将看到要执行并返回的表达式。Lambdas 默认返回表达式,所以我们不需要使用关键字:

>>> lambda arguments : value_to_return if condition else value_to_return

像列表理解一样,条件语句放在最后。这是 lambda 函数最复杂的部分。超过这一点就需要完整地写出函数。

注意

Lambdas 基本上在冒号右边使用三元运算符。

使用λ

当使用 lambdas 而不将它们存储到变量中时,需要用圆括号将函数和任何传入的参数括起来。让我们从小处着手,编写一个 lambda 函数,它将返回参数平方的结果:

# using a lambda to square a number
( lambda x : x**2 )( 4 )     # takes in 4 and returns the number squared

去查查手机。我们将得到一个 16 的输出。第一组括号包含 lambda 函数。第二组保存传入的参数。在这种情况下,整数 4 被传入 x,并执行表达式x∫2并返回结果。它们被称为匿名函数,因为它们没有名字。在下面的内容中,您将找到为执行相同执行的普通函数编写的代码:

>>> def square(x):
>>>       return x**2
>>> square(4)

我们把三条线变成了一条。一旦你习惯了阅读 lambda 语法,用这些函数来读写程序就变得更容易了。

传递多个参数

Lambdas 可以接受任意数量的参数,比如函数。让我们这次尝试传入两个参数,并将它们相乘:

# passing multiple arguments into a lambda
( lambda x, y : x * y )( 10, 5 )      # x = 10, y = 5 and returns the result of 5 * 10

去查查手机。我们将得到 50 的产量。这一次 lambda 函数在冒号的左边接受了两个参数 x 和 y。在冒号的右边,它能够执行将这两个参数相乘并返回结果的表达式。在下面,你会发现相同的代码,就像我们写了一个普通的函数一样:

>>> def multiply(x, y):
>>>      return x * y
>>> multiply(10, 5)

和以前一样,我们能够保存几行代码来获得相同的结果。

保存 Lambda 函数

Lambdas 之所以命名为匿名函数,是因为它们没有名称来引用或调用。lambda 函数一旦被使用,除非保存到变量中,否则不能再次使用。让我们像以前一样使用同一个 lambda 函数,只是这次将它保存到一个名为" square 的变量中,即使在 lambda 函数被读取后也可以引用该变量:

# saving a lambda function into a variable
square = lambda x, y : x * y
print(square)
result = square(10, 5)      # calls the lambda function stored in the square variable and returns 5 * 10
print(result)

去查查手机。我们将得到和以前一样的输出,除了这次我们通过调用函数 square 得到它。当函数存储在变量内部时,变量名充当函数调用。当我们在 square 变量中存储一个 lambda 时,我们能够通过调用 square 并传递参数来调用 lambda 函数。

注意

即使是正常定义的函数也可以保存到变量中,并通过变量名引用。

条件语句

一旦开始向 lambda 函数中添加条件语句,它们的行为方式与三元运算符相同。唯一的区别是您必须同时提供 if 和 else 语句。你不能只使用 if 语句;这将导致语法错误,因为它总是需要一个表达式来返回。让我们创建一个 lambda,它将返回传入的两个参数之间的较大值:

# using if/else statements within a lambda to return the greater number
greater = lambda x, y : x if x > y else y
result = greater(5, 10)
print(result)

去查查手机。我们将得到一个 10 的输出,因为它是较高的值。当你需要一个可以执行简单条件的函数时,Lambdas 非常有用。与普通函数相同的代码如下所示:

>>> def greater(x, y):
>>>       if x > y:
>>>               return x
>>>       else:
>>>               return y
>>> result = greater(5, 10)

当使用条件语句时,很容易看到 lambda 函数的威力。在这种情况下,我们能够将五行代码变成一行。

返回 Lambda

lambda 函数的亮点在于它们能够使其他函数更加模块化。假设我们有一个函数,它接受一个参数,我们希望这个参数在程序的后面与一个未知数相乘。我们可以简单地创建一个变量来存储返回的 lambda 函数,同时传递一个参数。让我们试几个例子:

# returning a lambda function from another function
def my_func(n):
      return lambda x : x * n
doubler = my_func(2)          # returns equivalent of lambda x : x * 2
print( doubler(5) )   # will output 10
tripler = my_func(3)          # returns equivalent of lambda x : x * 3
print( tripler(5) )     # will output 15

去查查手机。我们将得到一个输出 1015 。当我们定义我们的 doubler 变量时,我们调用 my_func ,同时传入整数值 2 。该值在 lambda 函数中使用,然后返回 lambda。然而,lambda 并不是以"lambda x:x∫n"的形式返回;现在返回的是整数 2 而不是 n 。每当调用 doubler 时,它实际上是被调用的 lambda 函数。这就是为什么当我们将值 5 传入倍增器时,我们会得到 10 的输出。这同样适用于我们的变量三倍器。由于返回的 lambda 函数,我们能够修改 my_func 的结果。

星期二练习

  1. 填空:为下面的代码填空,使其接受一个参数“x”,如果大于 50 则返回“True”;否则,它应该返回“False”:

    >>> ____ x _ True if x _ 50 ____ False
    
    
  2. 度数转换:编写一个 lambda 函数,它接受摄氏度的度数值,并返回转换成华氏度的度数。

今天,我们能够理解普通函数和匿名函数之间的区别,或者称为 lambda 函数。它们有助于提高可读性,并能够精简您的代码。它们最强大的特性之一是能够通过从函数返回来赋予函数更多的功能。

星期三:映射、过滤和减少

当处理数据时,通常需要能够从数据中修改、过滤或计算表达式。这就是这些重要的内置函数发挥作用的地方。 map 函数用于迭代数据集合并修改它。过滤器函数用于迭代一个数据集合,你猜对了…过滤掉不符合条件的数据。最后, reduce 函数获取一个数据集合并将其压缩成一个结果,就像列表的 sum 函数一样。

为了跟上这一课,让我们从笔记本文件“ Week_08 ”继续,简单地在底部添加一个 markdown 单元格,表示“映射、减少和过滤”。

不带 Lambdas 的地图

当您需要改变一个可迭代数据集合中的所有项目时,使用 map 函数。它接受两个参数,应用于每个元素的函数和可迭代数据。使用 map 时,返回一个 map 对象,它是一个迭代器。现在不要担心这些是为了什么;只要知道我们可以把它们转换成我们可以处理的数据类型,比如列表。让我们尝试获取一个摄氏温度列表,并将其全部转换为华氏温度:

1| # using the map function without lambdas
2| def convertDeg(C):
3|     return (9/5) * C + 32
4| temps = [ 12.5, 13.6, 15, 9.2 ]
5| converted_temps = map(convertDeg, temps)      # returns map object
6| print(converted_temps)
7| converted_temps = list(converted_temps)      # type convert map object into list of converted temps
8| print(converted_temps)

去查查手机。第一个 print 语句将在 0x 0dc 3d 3>输出“ < map object”或类似的内容。这是因为 map 函数返回的是 map 对象,而不是转换后的数据集合。在第 7 行,我们能够将地图对象转换成一个列表,结果是输出“【54.5,56.48,59,48.56】”。当映射被调用时,该函数开始迭代传入的 temps 列表。在迭代过程中,它将单个项目传递给 convertDeg 函数,直到传递完所有项目。该过程的等效过程如下:

>>> for item in temps:
>>>       convertDeg(item)

转换后,它会将数据追加到地图对象中。直到我们转换了地图对象,我们才能看到转换后的温度。

带有 Lambdas 的地图

既然我们已经看到了如何将 map 与一个正常定义的函数一起使用,那么这次让我们尝试一下 lambda 函数。由于 map 需要一个函数作为第一个参数,我们可以简单地用 lambda 代替已定义函数的名称。我们也可以在同一行键入 convert:

# using a map function with lambdas
temps = [ 12.5, 13.6, 15, 9.2 ]
converted_temps = list( map( lambda C : (9/5) * C + 32, temps) )    # type convert the map object right away
print(converted_temps)

去查查手机。我们将得到和以前一样的输出,但是代码行要少得多。这就是结合这两个概念的妙处。当 map 函数遍历 temps 列表并返回转换后的值时,lambda 函数接受每个项目。我们正在执行的相同过程可以在下面的代码行中找到:

>>> def convertDeg(degrees):
>>>       converted = [ ]
>>>       for degree in degrees:
>>>              result = (9/5) * degree + 32
>>>              converted.append(result)
>>>       return converted
>>> temps = [ 12.5, 13.6, 15, 9.2 ]
>>> converted_temps = convertDeg(temps)
>>> print(converted_temps)

如您所见,lambda 函数和 map 的使用有助于在我们需要更改数据时减少代码行数。

不带 Lambdas 的过滤器

过滤器功能对于收集数据和删除任何不需要的信息非常有用。像映射函数一样,它接受一个函数和一个可迭代的数据类型,并返回一个过滤器对象。这个对象可以被转换成一个工作列表,就像我们对 map 对象所做的那样。让我们使用相同的数据,过滤掉不超过 55 华氏度的温度:

# using the filter function without lambda functions, filter out temps below 55F
def filterTemps(C):
     converted = (9/5) * C + 32
     return True if converted > 55 else False          # use ternary operator
temps = [ 12.5, 13.6, 15, 9.2 ]
filtered_temps = filter(filterTemps, temps)      # returns filter object
print(filtered_temps)
filtered_temps = list(filtered_temps)     # convert filter object to list of filtered data
print(filtered_temps)

去查查手机。第一次输出的结果是“ <过滤器对象在 0x 0dc 3d 3>”,就像我们的地图对象输出。第二条语句的结果是“【56.48,59】”的输出。当我们使用过滤器并传入 temps 时,它一次遍历列表中的一项。然后,它会将每个项目传递给 filterTemps 函数,无论返回的结果是还是假,它都会将项目添加到过滤器对象中。直到我们把对象转换成一个列表,我们才能输出数据。使用 lambda 函数可以进一步减少所需的代码行。

使用 Lambdas 进行过滤

让我们执行与前面相同的步骤,只是这次我们将使用 lambda 函数:

# using the filter function with lambda functions, filter out temps below 55F
temps = [ 12.5, 13.6, 15, 9.2 ]
filtered_temps = list( filter( lambda C : True if (9/5) * C + 32 > 55 else False, temps) )  # type convert the filter
print(filtered_temps)

去查查手机。我们将得到与前面相同的输出,只是这次我们能够减少 lambda 函数使用的行数。我们正在执行的相同过程可以在下面的代码行中找到:

>>> def convertDeg(degrees):
>>>       filtered = [ ]
>>>       for degree in degrees:
>>>              result = (9/5) * degree + 32
>>>              if result > 55:
>>>                        filtered.append(degree)
>>>      return filtered
>>> temps = [ 12.5, 13.6, 15, 9.2 ]
>>> filtered_temps = convertDeg(temps)
>>> print(filtered_temps)

就像使用 lambdas 的 map 函数一样,将 filter 函数与 lambda 结合起来可以大大减少代码。

Reduce 的问题是

尽管我将向您展示如何使用 reduce 函数,但您应该明白,有一种比使用实际函数更好的方法。据 Python 的创造者自己说:

所以现在减少()。这实际上是我最讨厌的一个,因为除了几个涉及+或的例子之外,几乎每次我看到带有重要函数参数的 reduce()调用,我都需要拿起笔和纸,在我理解 reduce()应该做什么之前,画出实际输入到该函数中的内容。因此,在我看来,reduce()的适用性仅限于关联操作符,在所有其他情况下,最好显式写出累加循环。* 1

用他自己的话来说,他是说 reduce 只服务于几个目的,但除此之外,它是没有用的,所以对循环使用一个简单的更有意义。让我们看看这两个例子。

注意

Reduce 是 Python 2 中的一个内置函数,从那以后,它被移到了 functools 库中。

使用 Reduce

reduce 函数接受两个参数,执行的函数和迭代的数据集合。但是,与过滤器和贴图不同,reduce 一次迭代两个项目,而不是一个项目。reduce 的结果是总是返回一个结果。在下面的例子中,我们想把所有的数字相乘。让我们用 reduce 来执行这个例子:

# for informational purposes this is how you use the reduce function
from functools import reduce
nums = [ 1, 2, 3, 4 ]
result = reduce( lambda a, b : a * b, nums )     # result is 24
print(result)

去查查手机。输出将是 24。由于 reduce 函数接受两个参数,它将 nums 列表压缩为一个返回值。在下文中,您将看到执行相同过程的建议方式:

>>> total = 0
>>> for n in nums:
>>>         total = total * n

在很大程度上,很容易理解为什么 Rossum 如此坚持建议用 for loops 来代替,因为当你尝试更复杂的数据集合,如列表中的列表时, reduce 会变得难以理解。

周三练习

  1. 映射名字:使用 lambda 和 map 函数映射下面的名字列表,产生下面的结果" [ "Ryan "," Paul "," Kevin Connors" ]

    >>> names = [ "   ryan", "PAUL", "kevin connors     " ]
    
    
  2. Filter Names :使用 lambda and filter 函数,过滤掉所有以字母“a”开头的名字,使其不区分大小写,因此它会过滤掉名字的大小写。下面列表的输出应该是 ["弗兰克","里帕尔"]

    >>> names = [ "Amanda", "Frank", "abby", "Ripal", "Adam" ]
    
    

今天,我们学习了一些重要的内置函数,在 Python 中处理数据时可以使用这些函数。将映射过滤器与 lambdas 结合起来有助于提高我们的代码可读性,并缩短所需的代码行。最后,减少在一些情况下会有帮助;然而,for 循环通常更具可读性。

星期四:递归函数和记忆

递归是编程中的一个概念,函数在它的块中调用自己一次或多次。然而,由于函数不断地调用自己,这些类型的函数经常会遇到速度问题。记忆通过存储已经计算过的值以备后用来帮助这个过程。我们先来多了解一下递归函数。

为了跟上这一课,让我们从之前的笔记本文件“ Week_08 ”继续,并在底部简单地添加一个标记单元格,上面写着“递归函数和记忆化”。

理解递归函数

所有的递归函数都有一个所谓的基本情况,或者一个停止点。像循环一样,您需要一种方法来中断递归调用。如果没有一个,你会创建一个最终会崩溃的无限循环。例如,假设我们为以下问题设定了一个基础案例 1 :

  1. 你能计算出 5 的总数吗?

  2. 你能算出 5∫4 的和吗?

  3. 你能计算出 5÷4÷3 的和吗?

  4. 你能算出 5∫4∫3∫2 的和吗?

  5. 你能算出 5∫4∫3∫2∫1 的和吗?

  6. 是的,我们达到了我们的基本情况;结果是 120。

在这个例子中,我们从 5 开始递归调用,希望在计算总数之前达到我们的基本情况。在每次新的调用中,我们在表达式中增加一个数字,这个数字是前一个数字减一。这是一个阶乘函数执行递归调用的例子。根据任务的不同,函数可以同时执行两次递归调用。最明显的例子就是斐波那契数列。我们一起编程。

你可能会问自己,这些有什么用?通常,您可以编写一个循环来执行与递归调用相同的任务。那么为什么要使用它们呢?在某些情况下,递归函数比编写循环更容易理解。因为会出现重复的任务,所以经常在搜索和排序算法中使用它们。假设您需要搜索一个四维数组,也就是所谓的列表中的列表中的列表。您可以编写一个递归函数,在每次发现新的维度时调用自己,而不是编写一堆 for 循环来遍历每个列表。代码将产生更少的行,并且更容易阅读。让我们来看看一些例子!

编写阶乘函数

阶乘是递归的一个比较简单的例子,因为它们是给定数字乘以所有先前数字直到达到零的结果。让我们试着给它编程:

# writing a factorial using recursive functions
def factorial(n):
      # set your base case!
      if n <= 1:
              return 1
      else:
              return factorial( n – 1 ) * n
print( factorial(5) )      # the result of 5 * 4 * 3 * 2 * 1

去查查手机。从前面的例子中我们知道,我们将得到一个输出 120 。递归调用发生在 else 块中。return 语句在自身内部调用 factorial 函数,因为为了得到 factorial(5),的结果,它必须计算“factorial(4)÷5”。然后它必须计算"阶乘(3)÷4"才能得到阶乘(4) 的结果,如下所示:

  1. 阶乘(5) =阶乘(4)÷5

  2. 阶乘(4) =阶乘(3)÷4

  3. 阶乘(3) =阶乘(2)÷3

  4. 阶乘(2) =阶乘(1)÷2

  5. 阶乘(1) = 1

这种情况一直发生,直到在 factorial(1) 达到基本情况,该情况没有递归调用,并返回值 1 。一旦达到基本情况,它就可以开始将所有计算值返回到原始调用,如下所示:

  1. 阶乘(1) = 1

  2. 系数(2)= 1 ' 2 = 2

  3. 系数(3)= 3 ' 3 = 6

  4. 系数(4)= 9 ' 4 = 24

  5. 系数(5)= 24’5 = 120

递归函数向下工作,直到达到基本情况。一旦返回一个值,它就可以返回到以前的调用并返回一个结果。

斐波那契数列

斐波那契数列是数学中最著名的公式之一。它也是编程中最著名的递归函数之一。序列中的每个数字都是前面两个数字的和,因此 fib(5) = fib(4) + fib(3) 。斐波纳契数列的基本情况是 01 ,因为 fib(2) 的结果是“ fib(2) = fib(1) + fib(0) ”。为了创建递归序列,我们需要返回两个值以下的值:

# writing the recursive fibonacci sequence
def fib(n):
      if n <= 1:
              return n
      else:
              return fib( n – 1 ) + fib( n – 2 )
print( fib(5) )    # results in 5

去查查手机。我们得到 5 作为输出。记住这不是 3 + 4 的结果,而是 fib(3) + fib(4) 的结果。斐波那契数列在一次返回中使用了两次递归调用,这使得它比我们的阶乘函数复杂得多。为了计算 fib(5)fib(1) 必须计算五次。这是因为两部分递归调用。当这些递归调用发生时,它们基本上分解成一个金字塔状的结构。例如,让我们看看图 8-1 。

img/481544_1_En_8_Fig1_HTML.jpg

图 8-1

斐波那契数列递归序列树

该图表示为了计算 fib(5) 的结果而需要发生的所有递归调用。随着传入的数量增加,递归调用的结构和数量也在增加。它是指数级的,可以显著降低程序的速度。即使试图执行 fib(40) 也需要几分钟,而 fib(100) 通常会因为最大递归深度问题而中断。这就引出了我们的下一个话题,如何解决这个问题… 记忆

理解记忆

当你第一次访问一个网页时,你的浏览器需要一点时间来加载该网页所需的图像和文件。当你第二次进入完全相同的页面时,它通常会加载得更快。这是因为你的浏览器使用了一种叫做“缓存的技术当您第一次加载页面时,它会将图像和文件保存在本地。第二次访问该网页时,它没有重新下载所有的图像和文件,而是直接从缓存中加载。这改善了我们的网络体验。

在计算中,记忆化是一种优化技术,主要用于通过存储先前调用函数的结果并在尝试计算相同序列时返回保存的结果来加速计算机程序。这就是所谓的“??”缓存,“??”,前面的段落是内存化如何提高性能的真实例子。让我们看一些记忆化如何改进递归函数的例子。

使用记忆

为了将记忆应用到斐波纳契数列,我们必须了解缓存值的最佳方法是什么。在 Python 中,字典让我们能够基于给定的键存储值。它们也是基于常数时间,用大 O 符号表示。我们将在下周讨论这个话题。现在,只要明白字典在返回信息方面比大多数其他数据集合要快得多。由于字典的速度和独特的键结构,我们可以用它们来存储每个斐波那契数列的值。这样,一旦已经计算了像 fib(3) 这样的单个序列,就不需要再次计算。它被简单地存储在缓存中,并在需要时被检索。让我们试一试:

 1| # using memoization with the fibonacci sequence
 3| cache = { }        # used to cache values to be used later
 5| def fib(n):
 6|     if n in cache:
 7|          return cache[ n ]       # return value stored in dictionary
 9|     result = 0
11|     # base case
12|     if n < = 1:
13|          result = n
14|     else:
15|          result = fib( n – 1 ) + fib( n -2 )
17|     cache[ n ] = result      # save result into dictionary with n as the key
19|     return result
21| print( fib(50) )    # calculates almost instantly

去查查手机。请注意,这次它几乎可以立即计算出 fib(50) 。如果我们在不缓存值的情况下运行,执行同样的计算可能需要几个小时或几天。这就是记忆在工作中的美妙之处。这个过程从将参数传递到 fib 开始。然后程序检查参数是否作为关键字出现在缓存中。如果有,它只是返回值。然而,如果不是,它需要通过使用递归来计算正确的结果,直到达到基本情况。一旦达到基数,值就开始作为键值对保存在缓存中。当递归调用开始沿着结构向上返回时,它们只是从字典中取出值。由于记忆化,它只需计算一次,而不必计算上千次。

注意

记忆不是完美的;单个缓存中可以存储的数据量是有限制的。

使用@lru_cache

既然我们已经知道如何自己创建一个缓存系统,那么让我们使用 Python 的内置方法来实现内存化。它被称为" lru_cache最近最少使用的缓存。它执行的方式和我们之前的记忆技术是一样的;然而,它会用更少的代码行来完成,因为我们把它作为一个装饰器来应用。让我们来看看:

# using @lru_cache, Python’s default moization/caching technique
from functools import lru_cache
@lru_cache( )       # python’s built-in memoization/caching system
def fib(n):
      if n <= 1:
              return n
      else:
              return fib( n – 1 ) + fib( n – 2 )
fib(50)       # calculates almost instantly

去查查手机。我们将获得与前一个单元格相同的输出,但这次使用的行数更少。它执行完全相同的技术,除了它作为装饰器而不是直接在函数中应用。就性能而言,没有更好的方法,但是使用 lru_cache 看起来更舒服。

周四练习

  1. Factorial Caching :将 lru_cache 内置装饰器应用于我们之前创建的 Factorial 函数,或者建立自己的缓存系统。

  2. 搜索数据:创建一个接受两个参数的函数,一个数据列表和一个要搜索的项目。搜索传入的数据列表,如果要搜索的项目出现,则返回 True,否则,返回 False。如果其中一项是另一个列表,创建一个递归调用,这样就不需要创建另一个循环。使用下面的示例调用作为预期数据的参考:

    >>> searchList( [ 2, 3, [ 18, 22 ], 6 ], 22 )
    
    

今天,我们学习了所有的递归函数,以及如何用记忆的概念来改进它们。我们能够使用简单的缓存技术来存储以前计算的值。递归函数在有意义的时候会很有用,但是在大多数情况下,一个简单的 for 循环就足够了,因为递归函数会随着时间变得很慢。

星期五:写二分搜索法

本周的项目是关于理解编程中最有效的算法之一… 二分搜索法。当你需要搜索一个充满数据的列表时,你需要高效地完成它。为十个条目的列表创建一个算法可能没有意义,但是想象一下如果它是一百万个条目。你不希望在列表中一项一项地搜索,试图找到你要找的东西。相反,我们使用像二分搜索法这样的算法来执行这些任务。

为了跟上这一课,让我们从之前的笔记本文件“ Week_08 ”继续,并在底部添加一个减价单元格,上面写着:“星期五项目:写一个二分搜索法。

完工图纸

尽管程序本身相对较小,但我们必须理解二分搜索法算法是如何工作的。对于本周的设计概念,我们将列出需要遵循的步骤。记住算法只不过是一组步骤。二分搜索法也不例外。该算法的每个步骤如下:

  1. 对列表进行排序。

  2. 找到中间的索引。

  3. 检查中间索引处的值;如果它是我们要寻找的值,则返回 True。

  4. 检查中间索引处的值;如果它比我们要找的值大,就切掉列表的右半部分。

  5. 检查中间索引处的值;如果它小于我们所要寻找的值,就切掉列表的左半部分。

  6. 重复步骤 2 到 6,直到列表为空。

  7. 如果 while 循环结束,意味着没有剩余的项目,所以返回 False。

让我们通过一个例子和下面的参数: [ 14,0,6,32,8 ],,我们将寻找数字 14 。参见表 8-1 进行逐步走查。

表 8-1

二分搜索法示例描述

|

步骤

|

变量的值

|

描述

|

密码

|
| --- | --- | --- | --- |
| one | 列表:[0,6,8,14,32] | 立即对列表进行排序 | list.sort( ) |
| Two | 中期:2 | 找到中间,5 / 2,向下舍入 | len(list) // 2 |
| three | 值:8 | 不要还真,8 不是 14 | 列表【2】 |
| four | 条件:假 | 8 小于 14 不跑挡 | 如果列表【2】>14 |
| five | 列表:[14,32] | 运行程序块,删除列表的前半部分 | list = list[mid + 1 : ] |
| Two | mid: 1 | 中间索引是 1,因为 2 / 2 | len(list) // 2 |
| three | 数值:32 | 不要还真,32 不是 14 | 列表【1】 |
| four | 列表:[14] | 运行块,删除列表的后半部分 | list = list[ : mid - 1] |
| Two | mid: 0 | 找到中间,1 / 2,向下舍入 | len(list) // 2 |
| three | 返回 True | 中间索引的值为 14,返回 True | 返回真值 |

线性搜索需要我们逐项搜索列表,看看我们要找的号码是否在列表中。当考虑效率和搜索需要多长时间来完成任务时,它将基于列表的长度。随着列表长度的增长,找到我们要找的数字所需的时间也在增长。然而,使用二分搜索法,即使列表中有一百万个数字,在列表中查找一个数字所需的时间也只需要很少的步骤。例如,当你搜索一个有一百万个数字的列表时,线性搜索可能要尝试一百万次才能找到这个数字,但是二分搜索法可以在 20 次猜测内找到它。当它搜索时,它将列表切成两半。在 10 次猜测之内,你已经在处理一个不到 2000 个条目的列表了。这就是高效算法的妙处。让我们一起走过每一步,了解算法是如何编程的。

程序设置

在我们开始编写算法之前,我们需要建立一种方法来生成一个随机的数字列表。让我们导入 random 模块并使用 list comprehension 生成一些数据:

1| # setting up imports and generating a list of random numbers to work with
2| import random
4| nums = [ random.randint(0, 20) for i in range(10) ]    # create a list of ten numbers between 0 and 20
6| print( sorted(nums) )      # for debugging purposes

去查查手机。我们导入了 random 模块,以便用我们的列表理解生成一个 20 个随机数的列表。出于调试目的,我们在第 6 行输出了一个经过排序的版本 nums ,以便查看我们将要处理的数据。

第一步:给列表排序

算法的第一步是对列表进行排序。一般来说,在传入列表之前要对其进行排序,但是我们希望采取所有的预防措施,确保该算法甚至可以处理未排序的列表。让我们首先定义函数定义,并对传入的列表进行排序:

 4| nums = [ random.randint(0, 20) for i in range(10) ]   # create a ...   ◽◽◽
 6| def binarySearch(aList, num):
 7|  # step 1: sort the list
 8|  aList.sort( )
10| print( sorted(nums) )      # for debugging purposes
12| print( binarySearch(nums, 3) )

我们已经在底部添加了函数调用,并将打印返回值,但是现在当您运行单元格时,什么也不会发生。让我们进入第二步。

第二步:找到中间的索引

在这一步,我们需要找到中间的索引。我说的不是列表中间项目的值,而是实际的索引号。如果我们正在搜索一个包含一百万个条目的列表,中间的索引将是 500,000。该索引处的值可以是任何数字,但同样,这不是本步骤的目的。让我们写出第二步:

 8|  aList.sort( )    ◽◽◽
10|  # step 2: find the middle index
11|  mid = len(aList) // 2       # two slashes means floor division – round down to the nearest whole num
13|  print(mid)     # remove once working
15| print( sorted(nums) )     # for debugging purposes    ◽◽◽

去查查手机。为了找到中间的索引,我们需要将列表的长度除以 2,然后向下舍入到最接近的整数。我们需要使用整数,因为一个索引只能是一个整数。你永远无法访问索引 1.5 。此外,我们向下舍入,因为向上舍入会导致指数超出范围的错误。例如,如果列表中有一个项目,那么 1 / 2 = 0.5 并且向上舍入到 1 将导致错误,因为列表中的单个项目在索引 0 处。输出结果将是 5 ,因为我们正在处理一个包含十个数字的列表。完成后,继续删除第 13 行的 print 语句。

步骤 3:检查中间索引处的值

现在我们有了中间的索引,我们想看看给定索引处的值是否是我们要寻找的数字。如果是,那么我们要返回真值:

11|  mid = len(aList) // 2      # two slashes ...    ◽◽◽
13|  # step 3: check the value at middle index, if it is equal to num return True
14|  if aList[mid] == num:
15|          return True
17| print( sorted(nums) )     # for debugging purposes    ◽◽◽

去查查手机。根据为你随机生成的列表,你将得到一个输出,或者是或者是。如果数字 3 出现在索引 5 处,那么您的输出将为,因为我们在第 14 行的条件为真,并将运行返回语句。

步骤 4:检查值是否更大

如果我们要找的数字不在中间的索引处,那么我们需要找出要删除列表的哪一半。让我们首先检查中间索引处的值是否大于我们要搜索的数字。如果是,我们需要删除列表的右半部分:

15|          return True    ◽◽◽
17|  # step 4: check if value is greater, if so, cut off right half of list using slicing
18|  elif aList[mid] > num:
19|          aList = aList[ : mid ]
21|          print(aList)   # remove after working properly
23| print( sorted(nums) )     # for debugging purposes    ◽◽◽

去查查手机。在第 18 行,我们检查列表中间索引处的值是否大于我们在函数调用过程中传递的参数。不过,第 19 行是二分搜索法的神奇之处。使用切片,我们能够将列表的值重新声明到列表的前半部分。

注意

请记住,切片允许您输入开始、停止和步进。如果你没有像前面那样输入一个数字,这意味着你在使用默认值。默认值为 start = 0、stop = len(list)和 step = 1。

我们的意思是,我们希望保留从索引 0 到中间索引的所有项目。完成后删除第 21 行,因为它将简单地输出我们新的列表的结果。

步骤 5:检查值是否小于

这一步与第 4 步完全相同,但条件相反。如果中间索引的值小于我们要寻找的数字,我们要删除左半部分:

19|          aList = aList[ : mid ]     ◽◽◽
21|  # step 5: check if value is less, if so, cut off left half of list using slicing
21|  elif aList[mid] < num:
22|          aList = aList[ mid + 1 : ]
23|  print(aList)    # remove after working properly
25| print( sorted(nums) )     # for debugging purposes    ◽◽◽

去查查手机。在第 22 行,我们执行与步骤 4 相反的切片。这次我们声明" mid + 1 "因为我们不想包含中间的索引,因为它已经被检查过了。这个逻辑现在已经在我们的二分搜索法上实现了。剩下的就是建立一个循环来重复步骤 2 到 5,如果我们没有找到我们想要的东西,就返回 False

步骤 6:建立一个循环来重复步骤

我们需要循环,直到找到参数,或者直到列表为空。这听起来像是一个 while 循环的好例子。在创建了 while 语句之后,我们需要确保在循环中执行步骤 2 到 5 的代码:

 8|  aList.sort( )    ◽◽◽
10|  # step 6: setup a loop to repeat steps 2 through 6 until list is empty
11|  while aList:
12|          mid = len(aList) // 2
14|          if aList[mid] == num:
15|                  return True
16|          elif aList[mid] > num:
17|                  aList = aList[ : mid ]
18|          elif aList[mid] < num:
19|                  aList = aList[ mid + 1 : ]
21|          print(aList)    # remove after working properly
21| print( sorted(nums) )     # for debugging purposes    ◽◽◽

去查查手机。我们的二分搜索法现在正在执行所有必要的步骤,要么在找到参数时返回 True ,要么创建一个空列表,在这种情况下,循环将结束。请记住,我们前面的 while 语句与“while len(list)>0:”相同。如果循环结束,剩下的就是返回 False ,因为这意味着列表中不包含我们的号码。

步骤 7:否则返回 False

为了完成我们的二分搜索法,我们只需在之后返回 False ,同时循环结束:

19|                   aList = aList[ mid + 1 : ]     ◽◽◽
21|   # step 7: return False, if it makes it to this line it means the list was empty and num wasn’t found
22|   return False
24| print( sorted(nums) )     # for debugging purposes    ◽◽◽

去查查手机。我们现在已经完成了二分搜索法算法!现在,当你运行这个单元时,你将得到一个输出,或者是真的 ?? 或者是假的 ?? 的 ??。您可以在 while 循环中随意打印列表,这样您就可以看到列表在每一步是如何被截断的。

最终输出

你可以在 Github 资源库中找到本周以及这个项目的所有代码。下面的最终输出将不包括我们在前面的块中添加的任何注释,因此您可以清楚地看到完整的版本:

 1| # full output of binary search without comments
 2| import random
 4| nums = [ random.randint(0, 20) for i in range(10) ]
 6| def binarySearch(aList, num):
 7|  aList.sort( )
 9|  while aList:
10|          mid = len(aList) // 2
12|          if aList[mid] == num:
13|                  return True
14|          elif aList[mid] > num:
15|                  aList = aList[ : mid ]
16|          elif aList[mid] < num:
17|                  aList = aList[ mid + 1 : ]
19|   return False
21| print( sorted(nums) )
22| print( binarySearch(nums, 3) )

去查查手机。如果遇到任何问题,请务必参考这段代码。尝试增加传入列表中的项目数,看看它能多快找到您的编号。即使在大的列表中,这个算法也会以极快的速度执行。

今天不仅对理解二分搜索法如何工作很重要,而且对理解我们如何从一系列逐步指令中编写算法也很重要。算法可能很容易理解,但很难翻译成代码。使用这种算法,我们可以开始理解搜索是如何高效的,即使有大量数据需要筛选。

每周总结

在这一周中,我们讨论了 Python 中一些更高级的主题。当您开始构建您的编程体验时,您应该始终考虑效率。首先,我们需要确保我们的程序在执行时是正确的,但是我们也需要知道它们的速度。如果一个算法或程序能给你一只股票精确的价格,但要花十年时间去执行,那它就一文不值了。这就是一个伟大算法的重要性。除了效率,我们还想记住代码的可读性。尽管 sing list comprehension、lambdas 和递归函数不能提高我们程序的速度,但它有助于提高我们阅读正在发生的事情的能力。在下周的课程中,我们将讨论算法的复杂性和使用某些数据类型时性能的重要性。

挑战问题解决方案

接下来,你可以找到本周挑战问题的答案:

 1| # ask user for input, return whether it is prime or not
 3| def isPrime(num):
 4|  for i in range( 2, int(num**0.5) + 1 ):
 5|          if num % i == 0:
 6|                  return False
 7|  else:
 8|          return True
10| n = int( input("Type a number: ") )
12| if isPrime(n):
13|  print("That is a prime number.")
14| else:
15|  print("That is not a prime number")

这个程序最重要的部分在第 4 行。虽然你可能已经得到了正确的,我们想创建这个程序,以便它是有效的。第 4 行的语句也可能如下所示:

>>> for i in range(2, num):

然而,这条生产线的问题是效率不高。当你试图计算一个数是否是质数时,这个数的平方根是你需要的最大值。如果一个数不能被 2 和它自身的平方根整除,那么这意味着它是一个质数。如果我们不计算传入数字的平方根来计算质数,那么我们将不得不一直循环到质数本身。让我们以数字 97 为例,它是一个质数。使用第二个 for 循环语句,我们将总共循环 96 次迭代。然而,将语句写在代码块中,我们将只循环总共九次迭代。随着传入的数字变大,迭代次数也变多。因此,在编程时牢记效率总是很重要的。

每周挑战

要测试你的技能,请尝试以下挑战:

  1. 递归二分搜索法:把我们一起创建的二分搜索法算法变成一个递归函数。与其使用 while 循环,不如调用自己来减少列表并最终返回

  2. 高效算法:看看我们写的二分搜索法,你怎么可能让它更高效呢?

  3. 区分大小写的搜索:重写二分搜索法,使其能够处理包含数字和字母的列表。它应该区分大小写。使用下面的函数调用来理解传入的参数。提示:【22】<‘a’将返回 True

    >>> binarySearch( [ 'a', 22, '3', 'hello', 1022, 4, 'e' ] , 'hello')  # returns True
    
    

九、高级主题 II:复杂性

本周是高级 python 概念的继续,将涵盖开发人员在工作中必须了解的更多主题。

本周开始,我们将介绍一个你一直在使用的概念,生成器和迭代器。在接下来的几天里,我们将讨论装饰者模块,它们将帮助我们构建更大规模的应用程序。这些概念将有助于理解如何使用框架,比如 FlaskDjango

虽然我不喜欢在这本书里谈论理论,但理解时间复杂性如何与算法一起工作是很重要的。周四,我们将深入大 O 符号并进一步理解算法。本书中的所有课程都引导你能够继续深造,成为一名 Python 开发人员。这一切将我们带入周五的项目,即面试准备。因为这本书是作为改善或改变你职业生涯的工具而设立的,所以其中很重要的一部分就是面试过程。会有关于这个过程的信息,会有什么期待,以及如何处理一些你可能会被问到的面试问题。

概述

  • 理解生成器和迭代器对象

  • 使用和应用装饰器

  • 创建和导入模块

  • 什么是时间复杂度和大 O 记数法?

  • 知道如何处理面试、问题等等

挑战问题

作为一名程序员,你必须考虑执行一个程序所花费的时间。即使是一个会给你 100%准确答案的程序,如果没有及时把答案给你,也可能毫无用处。不用查,你认为当需要检索和存储信息时,列表或字典更有效吗?

星期一:生成器和迭代器

在本书的前几节,你可能已经看到了提到的单词生成器迭代器。在不知情的情况下,你一直在使用它们。今天,我们将深入了解这些概念是什么以及如何使用它们。

为了继续今天的内容,让我们从“ python_bootcamp ”文件夹中打开 Jupyter 笔记本。打开后,创建一个新文件,并将其重命名为“ Week_09。“接下来,制作第一个标题为:“生成器和迭代器”的单元格。“我们将开始在那个牢房下面工作。

迭代器与可迭代对象

一个迭代器是一个包含可以被迭代的条目的对象,这意味着你可以遍历所有的值。一个可迭代是一个集合,比如列表、字典、元组和集合。主要的区别是可迭代的不是迭代器;相反,它们是数据的容器。在 Python 中,迭代器对象实现了神奇的方法 iternext ,允许你遍历它的值。

创建基本迭代器

我们可以很容易地从可迭代器中创建迭代器。您可以简单地使用 iter() 函数来实现:

 1| # creating a basic iterator from an iterable
 3| sports = [ "baseball", "soccer", "football", "hockey", "basketball" ]
 5| my_iter = iter(sports)
 7| print( next(my_iter) )     # outputs first item
 8| print( next(my_iter) )     # outputs second item
10| for item in my_iter:
11|   print(item)
13| print( next(my_iter) )     # will produce error

去查查手机。迭代器将永远记住它们返回的最后一项,这就是为什么我们在第 13 行得到一个错误。使用 next() 方法,我们能够输出迭代器中的下一项。然而,一旦迭代器中的所有项都被使用了,我们就不能再遍历迭代器了,因为没有剩余的项了。迭代器也非常适合循环,像列表和字典一样,我们可以简单地使用 in 关键字(见第 10 行)。你仍然可以像我们通常做的那样循环遍历列表,它总是从索引 0 开始,但是一旦我们的迭代器没有条目了,我们就不能再使用它了。

创建我们自己的迭代器

现在我们已经看到了如何从 Python iterable 创建迭代器,让我们创建自己的迭代器类,它将输出字母表中的每个字母。要创建一个迭代器,我们需要实现神奇的方法 iter()next():

 1| # creating our own iterator
 3| class Alphabet( ):
 4|   def __iter__(self):
 5|           self.letters = "abcdefghijklmnopqrstuvwxyz"
 6|           self.index = 0
 7|           return self
 9|   def __next__(self):
10|           if self.index <= 25:
11|                   char = self.letters[ self.index ]
12|                   self.index += 1
13|                   return char
14|           else:
15|                   raise StopIteration
17| for char in Alphabet( ):
18|   print(char)

去查查手机。输出结果是一次打印一个字母的整个字母表。我们首先创建一个名为" Alphabet 的迭代器。然后我们使用 iter 方法来声明与这个迭代器相关的属性。将 iter 方法视为迭代器的初始化方法。在 iter 方法结束时,必须始终返回 self 。声明了 next 方法,这样当被调用时,迭代器可以返回字母串中的下一个字符。我们存储了一个名为 index 的属性,以便跟踪接下来应该返回哪个条目。最后,我们在第 14 行添加了一个条件,这样如果已经输出了所有的字母,它就会产生一个 StopIteration 错误。当您需要以特定的方式遍历 Python 集合时,迭代器非常有用。

什么是发电机?

生成器是产生反馈信息以产生一系列结果而不是单个值的函数。它们是简化迭代器创建的一种方式。通常,当函数完成任务并返回信息时,函数内部声明的变量将被删除。然而,对于生成器,它们使用“ yield ”关键字将信息发送回被调用的位置,而不会终止函数。生成器并不总是必须返回整数,尽管您可以生成您想要的任何信息。让我们看几个既有数字又有单个字符的例子。

注意

生成器是简化的迭代器。

创建范围生成器

虽然 range 函数不是一个生成器,但是我们可以使用 yield 关键字从生成器创建我们自己的版本。让我们试一试:

 1| # creating our own range generator with start, stop, and step parameters
 3| def myRange(stop, start=0, step=1):
 4|  while start < stop:
 5|          print( "Generator Start Value: { }".format(start) )
 6|          yield start
 7|          start += step       # increment start, otherwise infinite loop
 9| for x in myRange(5):
10|  print( "For Loop X Value: { }".format(x) )

去查查手机。这两个 print 语句用于显示发生器 myRange 何时被访问,与循环的何时输出结果相比较。我们能够调用 myRange 就像我们调用一个普通的 Range 函数一样,因为发电机的工作方式。在第 3 行,我们像声明其他函数一样声明该函数,接受与 range 相同的参数。我们在第 4 行的函数中开始一个 while 循环,这个循环将使返回到 start 值。一旦信息被返回给 for 循环,它就能够将该值用于当前的迭代。一旦 for 循环完成了它的代码块,它就返回到生成器,因为 while 循环条件没有得到满足。通常,一旦函数返回了信息,就不会再被调用;但是,生成器会继续返回并存储信息,直到它们的条件得到满足。如果我们没有用步骤增加开始值,我们将创建一个无限循环。与迭代器一样,当您需要特定的序列进行迭代时,生成器会很有用。当您需要知道内存时,生成器非常有用。尽管它们在性能方面没有那么高效,但在存储信息时它们是内存高效的。它们在需要创建数据管道的情况下很有用,这是指需要对数据片段执行一组执行。**

周一练习

  1. 反向迭代:创建一个接受列表的迭代器,当迭代结束时,它以相反的顺序返回信息。提示:在迭代器中接受参数时,需要使用 init 方法,以及 iternext 。下面的调用应该产生“5,4,3,2,1”

    >>> for i in RevIter( [ 1, 2, 3, 4, 5 ] ):
    
    
  2. Squares :创建一个类似 range 函数的生成器,除了它每次都产生一个平方数。下面这个调用的结果应该是“0,1,4,16”

    >>> for i in range(4):
    
    

今天,我们能够理解如何构建我们自己的 range 函数,以及如何迭代数据集合。生成器是迭代器的简化版本,但是使用 yield 关键字返回信息。迭代器必须总是通过使用 iter 和 next 方法来创建,并且对于创建我们自己的迭代序列非常有用。

星期二:装修工

如果你想学习框架,或者了解如何改进 Python 中的功能,那么你需要理解什么是装饰器以及它是如何工作的。这将有助于简化我们的代码,同时减少改进程序所需的代码行。

为了跟上这一课,让我们从之前的笔记本文件“ Week_09 ”继续,简单地在底部添加一个标有“ Decorators”的 markdown 单元格。

什么是装修工?

装饰器,也被称为包装器,是在不显式修改其他函数的情况下赋予它们额外能力的函数。它们由函数名前面的“@”符号表示,该符号写在函数声明的上方,如下所示:

>>> @decorator
>>> def normalFunc( ):

当您希望在函数执行之前或之后执行某些功能时,Decorators 非常有用。例如,让我们假设您想要基于用户登录来限制对某个功能的访问。您可以将代码放入装饰器中,并将装饰器应用于所有函数,而不是为您创建的每个函数编写相同的条件语句。现在,无论何时调用一个函数,条件语句仍然会运行,但是您可以节省几行代码。这是 Flask 框架的一个真实例子,它使用 decorators 基于用户认证限制对某些页面的访问。今天晚些时候我们会看到一个最小的例子。

高阶函数

一个高阶函数是一个对其他函数进行操作的函数,要么把一个函数作为它的自变量,要么返回一个函数。在上周的课程中,我们看到了用 lambdas,map,filter 和 reduce 完成的。装饰器是高阶函数,因为它们接受一个函数并返回一个函数。

创建和应用装饰器

为了创建装饰器,我们需要声明一个接受另一个函数作为参数的函数。在这个装饰器中,我们可以定义另一个要返回的函数,它将运行作为参数传入的函数。我们来看看这是怎么写的:

 1| # creating and applying our own decorator using the @ symbol
 3| def decorator(func):
 4|   def wrap( ):
 5|          print("======")
 6|          func( )
 7|          print("======")
 8|   return wrap
10| @decorator
11| def printName( ):
12|   print("John!")
14| printName( )

去查查手机。我们将得到一个" John!"以名字上下的等号作为边框。在第 10 行,我们将装饰器附加到了 printName 函数上。每当调用 printName 函数时,装饰器就会运行,并且 printName 会作为“ func 的参数传入。在装饰器中,我们声明了一个名为包装的函数。这个 wrap 函数会打印一个边框,然后调用 func 参数,再打印另一个边框。记住装饰者必须返回一个函数才能运行。我们声明的装饰器可以附加到我们编写的任何函数上。使用这个装饰器的所有函数将简单地运行,它们的上下都有一个边框。

带参数的装饰器

虽然 decorators 只是简单地给函数增加了额外的功能,但是它们也可以像其他函数一样有参数。让我们以下面的例子为例,我们想要运行函数 x 次:

 1| # creating a decorator that takes in parameters
 3| def run_times(num):
 4|   def wrap(func):
 5|           for i in range(num):
 6|                   func( )
 7|   return wrap
 9| @run_times(4)
10| def sayHello( ):
11|   print("Hello!")

去查查手机。这个单元格会输出" Hello!“四次。当装饰器接受一个参数时,语法会改变。我们的装饰器这次接受了一个参数 num ,而 wrap 函数这次接受了该函数作为参数。在我们的 wrap 函数中,我们创建了一个 for 循环,它将运行附加到装饰器的函数,运行次数与第 9 行装饰器上声明的参数一样多。

注意

当向装饰器传递参数时,函数会自动运行,所以在这个实例中我们不需要调用 sayHello。

带有装饰符和参数的函数

当您需要一个函数接受参数,同时还附加了一个装饰器时, wrap 函数必须接受与原始函数完全相同的参数。让我们来试试:

 1| # creating a decorator for a function that accepts parameters
 3| def birthday(func):
 4|  def wrap(name, age):
 5|           func(name, age + 1)
 6|  return wrap
 8| @birthday
 9| def celebrate(name, age):
10|  print( "Happy birthday { }, you are now { }.".format(name, age) )
12| celebrate("Paul", 43)

去查查手机。这将输出一个格式良好的字符串,其中包含第 12 行传入的信息。当我们调用庆祝时,装饰器接受庆祝作为 func 的参数,两个参数“保罗和“ 43 ”被传递到包装。当我们在包装中调用我们的函数时,我们将相同的参数传递给函数调用;然而,我们将年龄参数增加 1。

限制功能访问

你可能想知道装饰者如何服务于一个目的,因为最后几个单元格似乎没有意义。对于它们中的每一个,我们可以简单地在原始函数中添加这些行。不过这只是为了语法理解。Decorators 在框架中经常使用,它可以帮助您在框架中编写的许多函数增加功能。一个例子是能够基于用户登录凭证来限制对页面或功能的访问。让我们创建一个装饰器,如果密码不匹配,它将有助于限制访问:

 1| # real world sim, restricting function access
 3| def login_required(func):
 4| def wrap(user):
 5|         password = input("What is the password?")
 6|         if password == user["password"]:
 7|                 func(user)
 8|         else:
 9|                 print("Access Denied")
10|   return wrap
12| @login_required
13| def restrictedFunc(user):
14|   print( "Access granted, welcome { }".format(user[ "name" ]) )
16| user = { "name" : "Jess", "password" : "ilywpf" }
18| restrictedFunc(user)

去查查手机。在第 13 行,我们声明了一个普通的函数,它接受一个用户,并输出一个包含他们的名字和可访问性的语句。我们的装饰器附在第 12 行,这样当我们调用 restrictedFunc 并传入我们创建的用户时,它将通过装饰器运行。在 wrap 函数中,我们要求用户输入密码,并在第 6 行检查密码是否正确。如果他们输入正确的密码,那么我们允许他们访问该功能并打印出“访问授权”。然而,如果密码不正确,那么我们输出“拒绝访问”,并且从不运行 restrictedFunc 。这是一个简单的例子,说明了 Flask 如何处理页面的用户限制,但是它证明了装饰者的重要性。我们现在可以将 login_required 附加到我们认为应该只由用户访问的任何功能上。

星期二练习

  1. 用户输入:创建一个装饰器,要求用户输入一个数字,只有当数字小于 100 时,才运行这个函数。该功能应该简单地输出“小于 100 ”。在下面使用函数声明:

    >>> @decorator
    >>> def numbers( ):
    >>>          print("Less than 100")
    
    
  2. 创建路由:创建一个装饰器,它接受一个字符串作为参数,一个包装函数接受 func 。让 wrap 函数打印出字符串,并运行传入的函数。传入的函数不需要做任何事情。在 Flask 中,您可以通过使用接受 URL 字符串的 decorators 来创建页面。使用下面的函数声明来启动:

    >>> @route("/index")
    >>> def index( ):
    >>>        print("This is how web pages are made in Flask")
    
    

今天是为使用 Python 的其他技术(比如框架)做准备的重要一课。装饰器有助于改善函数的执行,可以附加到任何必要的函数上。这有助于减少代码并提供改进的功能。

星期三:模块

大多数程序倾向于包含许多行代码,以至于你不能把它们都存储在一个文件中。相反,您将代码分成几个文件,这有助于保持项目的组织性。这些文件中的每一个都称为模块。这些模块中有变量、函数、类等。,您可以将其导入到项目中。幸运的是,Python 拥有一大批开发人员,他们创建模块供我们使用,以增强我们自己的项目。今天,我们将看看 Python 中包含的一些模块,如何导入它们,如何使用它们,以及如何编写我们自己的模块在 Jupyter Notebook 中使用。

为了跟上这一课,让我们从笔记本文件“ Week_09 ”继续,只需在底部添加一个标有“模块”的降价单元格。

导入模块

在接下来的几个例子中,我们将使用 math 模块,这是 Python 的内置模块之一。这个特定的模块有函数和变量来帮助我们解决任何与数学相关的问题,无论是舍入、计算圆周率还是许多其他与数学相关的任务。对于第一个单元格,我们将导入整个数学模块及其内容:

# import the entire math module
import math
print( math.floor(2.5) )     # rounds down
print( math.ceil(2.5) )        # rounds up
print(math.pi)

去查查手机。我们会得到“ 2 ”、“ 3 ”和“ 3.14 的输出。当我们导入 math 时,我们能够访问 math 的所有函数、变量和类。在这个例子中,我们调用了存储在 math 模块中的两个函数和一个变量。为了导入整个模块及其内容,只需在模块名称前加上关键字 import。无论何时你想访问它的任何内容,你都需要使用点语法。现在我们可以使用任何数学的代码。

仅导入变量和函数

当您知道您不需要使用整个模块,而是需要使用几个函数或变量时,您可以直接导入它们。您应该始终确保只导入您需要的东西。在前一个单元格中,我们导入了整个 math 模块;然而,我们并不真的需要,因为我们只使用了其中的两个函数和一个变量。要导入特定的内容,您需要包括来自关键字的和您想要导入的内容的名称:

# importing only variables and functions rather than an entire module, better efficiency
from math import floor, pi
print( floor(2.5) )
# print( ceil(2.5) )     will cause error because we only imported floor and pi, not ceil and not all of math
print(pi)

去查查手机。我们将得到“ 2 ”和“ 3.14 ”的输出。导入模块的特定部分时,import 语句会稍有变化。要从单个模块中分离多个导入,可以使用逗号。我们注释掉了 ceil 的打印语句,因为它不起作用。我们只直接导入了 floorpi ,没有导入 ceil 函数。注意,我们也不需要在名称前引用带有点语法的 math 模块。这是因为我们直接导入了 floor 函数和 pi 变量,所以现在可以不用点语法引用它们了。记住只导入你需要的东西。

注意

您可以像前面一样从模块中导入类;简单地使用类名。

使用化名

通常,您想要导入的内容的名称可能很长。您可以在导入时给出一个“别名或昵称,而不必每次都写出完整的名称:

# using the 'as' keyword to create an alias for imports
from math import floor as f
print( f(2.5) )

去查查手机。我们将得到与前两个单元格相同的输出,除了这次我们能够将 floor 函数引用为字母“ f ”。这是因为我们使用“作为关键字来编写我们的进口声明。您可以给导入的任何内容重新命名,尽管通常最好只给较大的名称重新命名。

创建我们自己的模块

现在我们知道了如何导入和调用一个模块,让我们创建自己的模块。继续打开你电脑上的任何文本编辑器,如记事本文本编辑。在文件中编写以下代码,保存在您的“ Week_09 ”文件所在的同一个文件夹中,文件名为“ test.py ”。如果这两个文件不在同一个目录中,它会产生一个错误:

# creating our own module in a text editor
# variables to import later
length = 5
width = 10
# functions to import later
def printInfo(name, age):
      print( "{ } is { } years old.".format(name, age) )

参见图 9-1 中文本编辑器中的代码示例。

img/481544_1_En_9_Fig1_HTML.jpg

图 9-1

文本编辑器(notepad++)中带有代码的 test.py 模块

您刚刚编写了第一个模块!请记住,模块只不过是在其他文件中编写的代码,我们可以将它们导入到我们的任何项目中。现在让我们看看如何使用它们。

使用我们在 jupiter 笔记本中的模块

在任何其他情况下,您都可以用来自关键字的导入来导入我们在 test.py 中编写的变量和函数。然而,Jupyter Notebook 在使用您创建的模块时会有所不同。我们将使用“ run 命令来加载我们创建的整个模块。运行文件后,我们可以使用我们在模块中编写的变量和函数。让我们来看看如何做到这一点:

# using the run command with Jupyter Notebook to access our own modules
%run test.py
print(length, width)
printInfo("John Smith", 37)       # able to call from the module because we ran the file in Jupyter above

去查查手机。您会注意到我们能够输出在 test.py 模块中声明的变量和函数打印语句。请记住,run 命令将文件当作单个单元格来运行。模块中的任何函数调用或打印语句都会立即运行。要测试这一点,请尝试在模块底部放置一个打印语句。当您在开发环境中工作时( IDE ,您将像平常一样编写导入,如下所示:

>>> from test import length, width, printInfo

这就是 Jupyter Notebook 处理我们创建的文件的方式。

注意

您可以将在 Python 文件夹中创建的任何模块放在硬盘上。一旦文件存在,就可以正常访问它们,而不用使用 run 命令。

周三练习

  1. 时间模块:导入时间模块,调用睡眠函数。让电池休眠 5 秒,然后打印“时间模块导入”。虽然我们还没有介绍这个模块,但是这个练习将为你提供一个很好的练习,让你自己尝试使用一个模块。请随意使用谷歌、Quora 等。

  2. 计算区域:创建一个名为“ calculation.py 的模块,该模块内部有一个单一的函数。该函数应该接受两个参数,并返回它们的乘积。我们可以想象我们正在试图计算一个矩形的面积,它需要接受长度和宽度属性。在 Jupyter Notebook 中运行该模块,并在单元格中使用以下函数调用:

    >>> calcArea(15, 30)
    
    

今天的重点是关于模块,如何导入它们,如何使用它们,如何创建我们自己的模块,以及如何在 Jupyter Notebook 中调用我们自己的模块。理解模块是如何工作的会让你有能力使用 Python 中的框架。例如,Flask 使用了许多不同的模块,因为每个模块都有特定的用途。当你需要组织你的项目时,模块就是答案。

周四:理解算法复杂性

在这本书里,我们一直在边做边学。在开始时,我说过我们不会深入理论,而是通过一起构建项目和编码来学习。今天的重点主要是编程和算法的理论。如果编程中有一个理论你应该懂,那应该是大 O 记法

为了跟上这一课,让我们从之前的笔记本文件“ Week_09 ”继续,并在底部简单地添加一个标记单元格,写着“理解算法复杂性”。

什么是大 O 记数法?

作为一名软件工程师,你经常需要估计一个程序的执行时间。为了给出一个合适的估计,你必须知道程序的时间复杂度。这就是算法复杂性发挥作用的地方,也称为大 O 符号。它是描述一个算法或程序执行需要多长时间的概念。以一个列表为例。随着列表中项目数量的增加,遍历列表所需的时间也在增加。这就是所谓的 O(n) ,其中 n 代表运算次数。之所以叫大 O 记法,是因为你在运算次数前面加了一个“大 O ”。

大 O 建立了一个最坏情况的运行时。即使你搜索了 100 个条目的列表,并且第一次就找到了你想要的,这仍然会被认为是 O(100) ,因为它可能需要多达 100 次操作。

最有效率的大 O 记数法是 O(1) ,也称为常数时间。这意味着无论需要多少项目或步骤,都将花费相同的时间,并且通常是即时发生的。如果我们采用相同的 100 个条目的列表并直接访问一个索引,这将被称为 O(1) 。我们将立即检索该索引中的值,而不需要遍历列表。

效率最低的时间复杂度之一是O(n∫2)。这是一个双循环的表示。我们编写的冒泡排序算法使用了双 for 循环,被认为是编程中效率较低的排序算法之一;然而,它很容易理解,因此是对算法的一个很好的介绍。我们将在今天晚些时候看到冒泡排序与另一种设计得更高效的算法相比是如何的。

当你将遍历列表中每个元素的简单搜索与像二分搜索法这样的高效算法进行比较时,你会发现它们不会随着时间以相同的速度增长。以表格 9-1 为例,该表格显示了搜索给定物品所需的时间。

表 9-1

大 O 批注增长率对比 1

|

元素数量

|

简单搜索

|

二进位检索

|
| --- | --- | --- |
| 运行时用大 O 表示法 | O(n) | O(对数 n) |
| Ten | 10 毫秒 | 3 毫秒 |
| One hundred | 100 毫秒 | 7 毫秒 |
| Ten thousand | 10 秒 | 14 毫秒 |
| One billion | 11 天 | 32 毫秒 |

我们可以清楚地看到,有效的算法可以帮助提高我们的程序速度。因此,在编写代码时,记住效率和时间复杂性是很重要的。图 9-2 中的图片描绘了操作数量相对于元素数量的复杂性。

img/481544_1_En_9_Fig2_HTML.jpg

图 9-2

大 O 符号复杂度随时间变化图

这里没有涵盖所有的大 O 符号,所以如果你想进一步理解这些概念,一定要做进一步的研究。这只是对什么是大 O 以及为什么它在编写我们的程序时很重要的一个介绍。

散列表

当我们最初讨论字典时,我们非常简要地回顾了一下哈希。既然我们已经介绍了大 O 符号,理解散列表以及它们为什么重要就容易多了。字典可以以 O(1) 的复杂度被访问,因为它们是如何存储在内存中的。它们使用哈希表来存储键值对。在讨论哈希表之前,让我们快速回顾一下哈希函数以及如何使用它:

>>> a, c = 'bo', "bob"
>>> b = a
>>> print(hash(a), hash(b), hash(c))

从前面的代码中,我们将为 ab 获得相同的值,并为 c 的散列获得单独的值。哈希函数用于创建给定值的整数表示。在这种情况下,字符串“ bo 的整数和变量 ab 是相同的;但是,“ bobc 变量完全不同,因为它们的值不同。

当字典将键值对存储到内存中时,它们使用了这个概念。哈希表用于存储哈希、键和值。当您需要通过键检索给定值时,会用到存储的散列。以表 9-2 为例。有三个键值对,都有不同的哈希值。当您想要访问名称的值时,您应该写:

>>> person[ "name" ]

所发生的是 Python 散列字符串" name "并寻找散列值而不是密钥本身。您可以把这想象成通过索引来检索列表中的项目。这是非常有效的,因为您可以在 O(1) 时间几乎立即检索基于散列的值。

表 9-2

Python 哈希表的逻辑表示

|

混杂

|

钥匙

|

价值

|
| --- | --- | --- |
| Two billion eight hundred and thirty-nine million seven hundred and two thousand five hundred and seventy-two | 名字 | 约翰·史密斯 |
| Eight billion two hundred and sixty-seven million three hundred and forty-eight thousand seven hundred and twelve | 年龄 | Thirty-two |
| -2398350273 | 语言 | 计算机编程语言 |

字典是有用的数据集合,不仅可以保持信息的联系,还可以提高效率。当你试图回答编程问题或让程序运行得更快时,请记住这一点。像关于大 O 符号的信息一样,这只是对散列表的介绍。如果你想了解更多,一定要使用谷歌、Quora 等搜索。

字典与列表

为了理解哈希表和 Python 字典的真正威力,让我们将其与列表进行比较。我们将编写一个条件语句,让 Python 检查字典和列表中的给定项,并计算每一项需要多长时间。我们要把代码分成两个单元。第一个单元格将生成包含 1000 万个条目的字典和列表:

# creating data collections to test for time complexity
import time
d = { }        # generate fake dictionary
for i in range(10000000):
      d[ i ] = "value"
big_list = [ x for x in range(10000000) ]       # generate fake list

去查查手机。什么都不会发生。我们只是在这个单元格中创建了变量,所以我们不必重新创建它们,因为根据您的计算机,这需要几秒钟的时间。在下面的单元格中,我们将为每次数据收集寻找最后一个元素所花费的时间设置一个计时器。我们将使用时间模块来跟踪开始和结束时间:

 1| # retrieving information and tracking time to see which is faster
 3| start_time = time.time( )       # tracking time for dictionary
 5| if 9999999 in d:
 6|  print("Found in dictionary")
 8| end_time = time.time( ) – start_time
10| print( "Elapsed time for dictionary: { }".format(end_time) )
12| start_time = time.time( )        # tracking time for list
14| if 9999999 in big_list:
15|   print("Found in list")
17| end_time = time.time( ) – start_time
19| print( "Elapsed time for list: { }".format(end_time) )

去查查手机。在第 3 行和第 12 行,我们以 UTC 格式访问当前时间。检查完我们的条件后,我们再次获得 UTC 格式的当前时间;然而,我们从中减去开始时间,得到整个执行所用的秒数。你会注意到这两个时间有很大的不同。这个列表通常需要花费 11.5 秒,而字典几乎每次都是即时的。现在这看起来没有太大的区别,但是如果你需要搜索 1000 个项目呢?使用列表现在成了一个问题,因为字典会继续立即做,但是列表会花更长的时间。

注意

除非另有说明,时间模块以 UTC ( 通用时间)获取时间。UTC 始于 1970 年 1 月 1 日。输出 time.time()时看到的数字是从那天上午 12:00 开始的秒数。

算法之战

测试时间复杂度的一个最明显的方法是运行两个算法。这将让我们真正看到高效算法背后的力量。我们将测试冒泡排序和另一种叫做插入排序的排序算法。虽然插入排序不是排序时最有效的算法,但我们会发现它仍然比冒泡排序强大得多。让我们继续写下第一个单元格中的两个排序算法:

 1| # testing bubble sort vs. insertion sort
 3| def bubbleSort(aList):
 4|  for i in range( len(aList) ):
 5|          switched = False
 6|          for j in range( len(aList) – 1 ):
 7|                  if aList[ j ] > aList[ j + 1 ]:
 8|                          aList[ j ], aList[ j + 1 ] = aList[ j + 1 ], aList[ j ]
 9|                          switched = True
10|          if switched == False:
11|                  break
12|   return aList
14| def insertionSort(aList):
15|   for i in range( 1, len(aList) ):
16|           if aList[ i ] < aList[ i – 1 ]:
17|                   for j in range( i, 0, -1 ):
18|                           if aList[ j ] < aList[ j – 1 ]:
19|                                   aList[ j ], aList[ j + 1 ] = aList[ j + 1 ], aList[ j ]
20|                   else:
21|                           break
22|   return aList

去查查手机。现在我们已经定义了需要调用的两个函数,让我们设置一些要排序的随机数据,并像在上一节中一样设置一个计时器:

 1| # calling bubble sort and insertino sort

to test time complexity
 2| from random import randint
 4| nums = [ randint(0, 100) for x in range(5000) ]
 6| start_time = time.time( )     # tracking time bubble sort
 7| bubbleSort(nums)
 8| end_time = time.time( ) – start_time
 9| print( "Elapsed time for Bubble Sort: { }".format(end_time) )
11| start_time = time.time( )     # tracking time insertion sort
12| insertionSort(nums)
13| end_time = time.time( ) – start_time
14| print( "Elapsed time for Insertion Sort: { }".format(end_time) )

去查查手机。这甚至不是一场比赛。插入排序是一种比它的对应物更有效的算法。尽管两者都使用了双 for 循环的概念,但冒泡排序的步骤效率要低得多,因为它每次都是从列表的前面开始。在设计你的程序和算法时,记住时间复杂性总是很重要的。如果你不确定什么是最好的,试着像我们这里一样测试一下。

周四练习

  1. 归并排序:做一些研究,尝试找出归并排序算法的“大 O”表示。

  2. 二分搜索法:二分搜索法要在一千万个数字中找到一个数字,最多可以猜多少次?

虽然今天更多的是关于理论,而不是这本书的其他部分,但它是编程最重要的方面之一。大 O 符号帮助我们理解程序和算法的效率。理解我们为什么使用像字典或列表这样的数据集合总是很重要的。当效率很重要时,可以实现字典来改进程序。这是我们使用字典进行缓存的另一个原因。

周五:面试准备

如果你正在寻找一份新的职业或工作,比如 Python 开发人员,那么如果你不能通过面试,那么所有这些课程都是徒劳的。本周五,我们将讲述一个通用软件开发面试的过程。我们将涵盖每个阶段,面试前后要做什么,白板,回答一般性和技术性问题,以及如何勾画你的简历和个人资料。这一课对于那些在面试过程中挣扎的人或者那些从未参加过正式软件开发面试的人来说是有帮助的。如果你对这一部分不感兴趣,并希望继续下去,就把今天作为本书时间表的一个休息。

开发人员面试流程

开发人员角色的面试过程可以分为许多不同的阶段。在下文中,你会发现业内许多公司实践的主要阶段。请记住,这是一个一般的面试过程,并不是每个公司都会严格遵循这些流程。请将本节更多地用作可能发生的情况的指南:

  • 第一阶段
    • 关于你自己和过去工作经验的基本问题。第一步通常是打电话给第三方招聘人员、内部招聘人员、人力资源或公司的人才招聘人员。在面试过程的第一步,面试官试图判断你是否适合这个职位。他们希望你提到“流行语”,同时提供为什么你非常适合这个职位的信息。你想把自己和这个职位联系起来。一定要谈论你使用他们寻找的语言和技术的经历。面试官在找你满足一半的要求,让自己很匹配。没有人会知道所有的事情,但是向他们展示你所知道的和你学习的意愿是很好的。

注意

流行语是该职位寻找的关键词。例如,使用 Python 的后端职位会期望听到类似于 API、JSON、Python、Flask、Django、Jinja、关系数据库、PostgreSQL 等词。

  • 第二阶段

    • 如果你通过了电话面试,你通常会被要求参加一次面对面的面试。这个阶段通常是你遇到目前在公司工作的其他开发人员的地方。虽然他们会问你一些面试问题,但这个阶段通常是让员工看看他们是否愿意和你一起工作,并在更私人的层面上了解你。一般来说,你会一次面试一小部分员工。您将有两到五次这样的会议,每次持续 10 到 15 分钟。在雇用一个人之前,这些小组通常会聚在一起讨论下一阶段的潜在候选人。在这个阶段,一定要适当地介绍自己,和每个人握手。了解每一位员工,试着在个人层面上与他们建立联系。
  • 第三阶段

    • 这是技术回合。在这一阶段,将提出问题来评估开发人员的技能和能力。通常,会有一个白板问题、几个纸上技术问题和一个脑筋急转弯。这一阶段通常由招聘经理或你将共事的团队经理来进行。当被问到一个问题时,确保你清楚地理解它。非常欢迎您在回答问题之前,根据需要提出尽可能多的问题,以便清楚地了解问题。如果你不知道问题的答案,让面试官知道你没有用过这个概念或者没有看到问题。在这个阶段,面试官会知道你是否不知道自己在说什么,所以不要试图编造一些东西。他们会对你的诚实留下更深刻的印象,并试图引导你解决问题。在这个阶段,他们不在乎你是对是错。他们更感兴趣的是你如何思考,你解决问题的能力如何。
  • 第四阶段

    • 此时,你通常和招聘经理或人力资源人员坐在一起。在这个阶段,你可以问关于公司的问题,以及工作角色。如果你已经做到了这一步,公司已经看到了你作为一名潜在员工的价值。通常,这是合同谈判和薪资谈判的地方。在面试结束时,准备好要问的问题,而且要问很多。如果你没有问题,这通常是没有准备好或懒惰的表现。

面试前要做什么

几乎在你生活中做的每一件事上,你都要做好充分的准备。面试也是一样。以下是你在面试前应该做的一些提示:

  • 研究

    • 一定要研究你面试的公司。不要仅仅了解他们创造了什么产品,或者提供了什么服务,还要知道他们支持什么慈善机构,他们合作的公司等等。这表明你参与并关心公司的利益。一点点就够了。
  • 做好准备

    • 准备一个文件夹或文件夹,里面包括你的简历、一叠面试笔记纸、工作实例等。
  • 简历

    • 总是用高质量的纸打印简历。

    • 让你的简历符合你要面试的工作。比如后端角色,提到 Python,SQL,数据库相关技术等。

    • 保持你的简历在一页以内。

    • 不要添油加醋。

    • 用经验、技能和教育等部分来组织。

    • 把你的简历想象成 30 秒的电梯推销。

    • 通常,让设计师忽略你的简历会有所帮助。一些网站会收取很少的费用,但是会让你的简历看起来更专业,更有条理。

  • 组合网 S ite

    • 不是所有的开发者都有个人网站,但是如果你没有的话,看起来肯定很糟糕。想象一下去看一个没有牙齿的牙医。把你自己看作是你试图卖给公司的产品,你应该有一个网站来展示你的技能,并让别人联系你。
  • Github

    • 几乎每个招聘机构和公司都会通过你的 Github 来查看你参与过的项目。

    • 最好在你的投资组合中也有完整的项目。一个大项目总会比 10 个小项目更突出。

    • 在你的简历、作品集网站和电子邮件中包含你的 Github 账户。

  • LinkedIn

    • 大多数招聘人员和公司登录 LinkedIn 只有一个原因,那就是寻找潜在的求职者。

    • 确保你的个人资料是最新的,包含所有相关信息和你参与过的项目。

    • 你的个人资料图片应该是专业的。你不需要穿西装打领带,但是最好不要有你在沙滩上的照片。

    • 把这个网站看作是你的专业网络服务。

    • 经常发布你想从事的领域的信息。你发布的内容越多,招聘人员就越容易认出你。

  • 社交媒体

    • 保密或保持干净。你最好相信公司会看你的帖子,以了解你是谁,如果他们不喜欢他们看到的,你就不会得到回复。
  • 直接申请

    • 直接向公司提交申请看起来总是更专业。通常,你会在 Indeed 或 ZipRecruiter 上找到一份自己喜欢的工作;然而,这些公司每天都被这些网站上的申请淹没,他们通常有算法来淘汰大多数候选人。发送一封直接的电子邮件表明你投入了时间和精力去直接联系公司。

一般问题

下面是一个非技术性问题的列表,后面是一个好答案的例子。选择这些问题是因为它们通常被不恰当地提问和回答:

  • 你期望的薪水是多少?

    • “我现在没有确切的数字。我想对其他公司提供的类似职位做一些调查。这个职位你给员工的平均工资是多少?”

    • 当他们询问时,千万不要说出一个数字,这在任何谈判过程中都会给他们提供筹码。

    • 用另一个问题来反驳他们的问题。

    • 如果他们继续问你一个数字,简单地陈述同样的回答。

  • 你认为自己五年后会怎样?

    • “未来五年,我会更加关注自己的技能。我知道,专注于继续我的教育和自我提升将引领我到达我需要的地方。”

    • 专注于提高你的技能表现出同情心。

  • 你为什么想成为一名软件开发人员?

    • “我一直对能够从无到有创造出一些东西很感兴趣,我也一直喜欢挑战。当你能够解决问题并构建应用程序时,那是一种美妙的感觉。”

    • 展示你作为开发人员的激情;这将永远是一种优势。

    • 永远不要提是钱的问题,即使是。

  • 你为什么要转行?

    • “我觉得我在以前的职业生涯中没有受到足够的挑战,我一直对编程感兴趣,并对构建改善人们生活的应用程序感到兴奋。”

    • 像上一个问题一样,展示你对这个职业的热情和动力。

    • 解释你喜欢接受挑战,这表明你并不懒惰。

    • 永远不要提是钱的问题,即使是。

  • 你为什么想在这里工作?

    • “你们在这里构建的应用程序帮助了世界各地如此多的用户,我很乐意成为其中的一员。”

    • 谈论公司合作的应用程序或慈善机构。这表明你有激情,在团队中工作出色,而且你有动力。

    • 提及公司文化也是一个很好的答案。

    • 不提工资,福利,甚至更惨…没有答案。

  • 告诉我一个棘手的软件问题,以及你是如何解决的。

    • “我当时正在做一个项目,我被指派在应用程序中实现 Steam API。不幸的是,API 无法正确连接。使用调试器,我在导入和函数调用位置设置了断点。在意识到他们根本没有被击中后,我想这一定是连接的问题。在尝试了几种导入方式并通读了文档之后,我决定将应用程序设置为在函数被点击时关闭。当我第二次运行这个程序时,它立刻就关闭了。意识到函数正在被调用,但是应用程序没有正常运行,我认为这一定是一个导入问题。直到我在一个更新的应用程序中测试该 API 时,才发现问题是由于代码是在 2.2 版本中编写的,而该 API 需要 3.6 版本。为了连接 API,我必须通过 mapper 函数手动导入库,该函数可以在版本之间翻译代码。在意识到映射器工作后,我能够实现 Steam API 包含在其 SDK 中的库。”

    • 尽可能深入地研究这个问题。他们想知道导致问题的每一个细节,你是如何解决问题的,以及你试图解决问题的所有想法。虽然前面的答案可能对你来说没有太大的意义,但它显示了问题,我做了什么来试图找到问题,以及一旦我找到问题,我是如何想出解决方案的。

白板和技术问题

这一部分列出了在白板和技术问题的面试流程的第三阶段您应该考虑使用的技巧:

  • 慢慢来

    • 绝对不要急着解决问题。在回答问题之前,先考虑一个合适的解决方案。通常,给定时间后,你会想到两三种不同的解决方案。
  • 大声说出

    • 总是谈论你的思考过程。这会让面试官感觉更舒服,这样你们就不会坐在一个安静的房间里思考了。

    • 这向面试官展示了你解决问题的能力。

    • 即使你没有给出正确的答案,他们至少能明白你错在哪里,并提供一些指导。

  • 步骤>语法

    • 写白板时,你需要当着面试官的面在白板上写下一个功能或几行代码。要记住的最重要的事情是,你的思维过程比你的实际代码更重要。

    • 你可以在白板上有语法错误,但仍然可以通过面试;然而,不正确的算法或步骤会导致失败。

  • 提问

    • 如果你不确定,问问题。当试图解决一个问题时,提出问题是完全可以的。

    • 记住你问的问题很重要。问一个排序方法是做什么的,和你想让我用什么类型的排序方法相比,有很大的不同。

  • 算法复杂度

    • 永远记住算法的复杂性。在你写完代码后,你通常会被问到是否有办法进一步提高代码的性能。

    • 知道你刚写的算法的大 O 符号范畴。

    • 考虑什么样的数据类型或集合最适合您的场景。

  • 要诚实

    • 如果你不知道一个答案,绝对不要试图通过你的方式说话。这个阶段的面试官是一个专业的开发人员,可以挑出任何没有意义的东西。

    • 诚实地说你不确定,但愿意学习这些材料,这将被证明是回答你不知道如何解决的问题的更好方法。

面试问题结束

你绝不会希望在面试结束时,当他们问你是否有任何问题时,你两手空空。在面试中做笔记,写下你想到的问题通常是一个好习惯。在下文中,你会发现一系列你应该考虑问的问题:

  • 通勤情况如何?

  • 停车是免费的吗?

  • 你会举办社交活动吗?

  • 如果我想进一步发展我的职业技能,你们会提供服务或学费补偿吗?

  • 你们提供什么样的福利?

  • 公司文化是什么样的?

  • 有多少人将和我一起在团队中工作?

  • 会有辅导吗?

  • 你能告诉我更多关于这个职位的日常职责吗?

  • 在这家公司工作,你最喜欢的是什么?

  • 对于这个职位的人来说,在这个公司里典型的职业道路是什么?

  • 面试过程的下一步是什么?

  • 在典型的一天里,我会期待些什么?

  • 这家公司支持哪些慈善机构?

  • 有什么公司活动吗,比如运动队?

面试后做什么

即使你通过了前三个阶段,如果你没有在面试后执行正确的步骤,你仍然会悲惨地失败。在下文中,你会发现一些例子,告诉你在面试过程结束后应该做些什么:

  • 跟进

    • 总是,总是,总是立即给面试官发一封电子邮件,感谢他们抽出时间。这表示尊重,也是一种礼貌的姿态。
  • 自我批判

    • 理解自己的错误。不要把它当成个人问题;你能变得更好的唯一方法是理解和自我反省。
  • 继续建造

    • 总是致力于项目,努力改善你的投资组合。

    • 与最新的库、语言和技术保持同步。

    • 经常更新你的简历和作品集。

  • 冒险外出

    • 出去参加当地的社交活动。在这里你会遇到很多熟人。当你认识在公司工作的人时,总是更容易找到工作。

    • 像 code alongs 或 hackathons 这样的活动是结识其他希望一起工作的开发人员的好方法。

  • 拒绝

    • 这种情况时有发生,你不会总是得到这份工作。如果真的发生了,一定要礼貌地问面试官为什么你没有得到这份工作。不要往心里去;相反,利用这些信息成为更好的开发人员并进行改进。

今天的主题是了解面试过程以及如何提高面试技巧。即使是最伟大的程序员也可能是糟糕的面试官。找到一份合适的工作需要大量的努力和专注,即使这样,也可能不会成功。最好的建议是继续提高你的技能,并与其他软件开发人员建立联系。

每周总结

本周是更高级的 Python 概念的第二部分。本周教授的许多课程不仅对面试很重要,而且对提高你的项目表现也很重要。迭代器和生成器是一种可以用来创建更好的循环结构和算法的对象。能够使用 decorators 将有助于提高函数能力,并且在 Flask 或 Django 这样的框架中被广泛使用。模块允许我们通过将函数或整个文件导入我们的程序来使用其他开发者的代码。能够编写我们自己的模块允许我们减少每个文件中的代码量。您通常希望尽可能保持组织有序,因为这使得项目更容易阅读、维护和修复。然而,如果这周你需要理解一个主题,那就是大 O 符号。了解大 O 是如何工作的,可以在求职面试中有所帮助,知道如何提高申请的速度。还有更多关于 Python 和编程的高级主题,但这最后两周将为您提供足够的时间来开始构建自己的项目,甚至继续学习框架和使用数据库的大型应用程序。

挑战问题解决方案

在周四的课程中,我们复习了这个问题的确切答案。显而易见,字典显然是存储和检索数据的更有效的方式。在处理大型数据集时,记住要使用的正确数据结构总是很重要的。可以肯定的是,在面试过程中也会问到类似的问题。

每周挑战

要测试你的技能,请尝试以下挑战:

  1. 了解市场:上 Indeed 或 Monster 这样的求职网站,寻找你感兴趣的潜在工作。记下他们寻找的资格和技术。看了几个职位描述,排名前三的技术是什么?这些应该是你前进的重点。

  2. 购物车模块:从我们几周前编写的购物车程序中取出代码,并将其放入一个模块中。在 Jupyter 笔记本上,运行模块,让程序正常工作。

  3. 增强的购物车:在程序中增加一个新功能,允许用户保存购物车。在运行程序时,应该加载保存的购物车。该方法应该在模块中编写。提示:使用 CSV 或文本文件。

  4. 代码大战:在 www.codewars.com 上算账,想办法解决一些问题。代码战争已被用于面试练习问题,提高你的算法和解决问题的技能,等等。这将有助于提高本书教授的技能。试着每天解决一个问题,你会发现你的 Python 编程技能会提高。

十、数据分析介绍

到目前为止,我们已经介绍了足够多的 Python 基础和编程概念,可以向更大更好的东西前进了。本周将全面介绍 Python 必须提供的数据分析库。我们不会像其他专注于这个主题的书那样深入探讨;相反,我们将涵盖足够多的内容,帮助您顺利地分析和解析信息。

我们将了解 Pandas 库以及如何使用表格数据结构,如何使用 BeautifulSoup 进行网页抓取,如何解析数据,以及像 matplotlib 这样的数据可视化库。在周末,我们将一起使用所有这些库来创建一个抓取和分析网站的小项目。

概述

  • 使用 Anaconda 环境和发送请求

  • 学习如何用 Pandas 分析表格数据结构

  • 了解如何使用 matplotlib 显示数据

  • 使用 BeautifulSoup 库从网上搜集数据

  • 创建网站分析工具

挑战问题

假设你是一名数据分析师,你刚刚得到一组数据,显示了所有司机的事故数量、年龄和发动机的大小。你需要想出一种方法来显示这些信息,让它讲述一个故事。通常你会用 x,y,z 坐标创建一个图表;然而,这可能会变得复杂,你没有时间去做。你如何渲染信息,使它仍然被认为是三维的,但你只能使用 x 和 y 轴?

星期一:虚拟环境和请求模块

今天我们将学习所有关于虚拟环境的知识,为什么我们需要它们以及如何使用它们。它们对于我们本周要做的事情是必要的,我们需要下载和导入一些库来使用。我们还将进入请求模块,并简要介绍API

今天的课,我们不会从朱庇特笔记本开始;相反,如果您还没有打开终端,请将光盘放入“python_bootcamp”文件夹。如果你有运行 Jupyter Notebook 的终端,一定要停止它,因为我们需要在终端中写一些命令。

什么是虚拟环境?

Python 虚拟环境本质上是一种工具,允许您将项目依赖关系保存在与其他项目分离的空间中。Python 中的大多数项目需要使用 Python 中默认不包含的模块。现在,您可以简单地将模块(或库)下载到 Python 文件夹中使用;然而,这可能会导致一些问题。假设你正在进行两个独立的项目,其中第一个项目使用 Python 版本 2.7 ,第二个项目使用 Python 版本 3.5 。如果您尝试对两者使用相同的语法,您将会遇到几个问题。相反,您将创建两个独立的虚拟环境,每个项目一个。这样,由于个性化的虚拟环境,两个项目都可以使用正确的依赖项正常运行。

注意

创建虚拟环境时,会出现一个名为“ venv 的文件夹。这是保存您下载的所有库的地方。简单地说,虚拟环境只不过是一个存储其他文件的文件夹。

作为理解虚拟环境的类比,首先想象一下我们自己的星球。现在把它想象成一个充满了草、太阳、云、空气等的环境。在编程的情况下,Python 就像是地球,草地、太阳、云和空气就像是你需要包含在环境中的库。由于 Python 没有包含在它们中,我们将创建一个虚拟环境来存储这些库,以便在需要时可以将它们导入到我们的项目中。如果你想到火星,这将是另一个项目,有一个单独的虚拟环境专门为该计划。

对于第一次看到虚拟环境的人来说,虚拟环境通常是一个难以理解的概念,所以这里有另一个类比。假设你计划了两次假期,一次去海滩,另一次去滑雪。你决定打包两个独立的行李箱,而不是用同一个行李箱装不同的衣服。沙滩装包括泳衣、太阳镜和人字拖。另一个手提箱包括一件夹克、一双滑雪鞋和一双靴子。在下文中,您可以找到此类比中的关系:

  • 度假➤项目

  • 行李箱➤虚拟环境

  • 服装和配饰➤项目依赖/文件

注意

记住从第一章开始,当在终端中工作时,你会在我们输入的命令旁边看到 $ 。在接下来的几节中,我们将在终端内部工作。

皮普是什么?

Pip 是 Python 的标准包管理器。任何时候你需要下载、卸载或者管理一个库或者模块来在你的项目中使用,你可以使用 pip 。从 v3.4 开始,它就包含在 Python 的所有安装中。要检查您的 pip 版本,请在终端中编写以下内容:

$ pip --version

请随意访问 Python 包索引 ( PyPI )来查看您能够下载的所有可能的库。您可以在未来的项目中使用它们中的任何一个。今天,我们将学习如何安装和使用请求模块,但首先,让我们创建并激活一个虚拟环境。

创建虚拟环境

Anaconda 之所以是如此出色的工具,其中一个重要原因是它能够为我们组织虚拟环境。我们将用它来创建我们的第一个虚拟环境。在终端中,键入以下命令:

$ conda create --name data_analysis python=3.7

继续运行该命令。然后,它会问你是否想通过键入“ y 或“ n ”来继续,只需键入“ y ”表示是,然后按回车键。在我们的程序文件中,将在 Anaconda 目录下创建一个文件夹。该文件夹将被命名为" data_analysis。“我们刚刚使用 Python 版本 3.7 创建了我们自己的虚拟环境。为了使用它,我们必须激活它。如果您想使用 Python 的默认虚拟环境系统,可以使用关键字“virtualvenv”如果你感兴趣的话,一定要去看看。为了便于使用,我们将在本章中使用 Conda 的环境。

注意

你可以从任何地方创造康达环境;你不需要被 cd 到一个特定的文件夹。

激活虚拟环境

使用虚拟环境的第二步是激活它。激活一个环境允许计算机从一个单独的可执行文件中执行我们的脚本。默认情况下,我们使用存储在程序目录中的 Python 可执行文件。我们可以通过在终端中输入以下命令来查看可执行文件的路径:

我们需要首先激活 Python shell:

$ python

现在,我们可以通过键入以下几行来查看路径:

>>> import os
>>> import sys
>>> os.path.dirname(sys.executable)

您会注意到路径是 Python 最初安装的默认文件夹。完成后,继续并退出 Python shell。我们将在本节的最后回到这些相同的命令,看看一旦环境被激活,路径是如何改变的。

一旦创造了环境,就不需要再创造了;您可以在需要使用时随时激活它。在您能够将库下载到环境中之前,您必须首先激活它。根据您的操作系统,在终端中编写以下命令:

对于窗口:

$ activate data_analysis

激活环境后,您将看到名称出现在终端左侧的括号中。它将显示如下:

    >>> (data_analysis) C:\Users...

对于 Mac/Linux:

$ source activate data_analysis

与 Windows 一样,激活环境后,您将在目录左侧看到名称:

    >>> (data_analysis)  <User>~/Desktop...

如果你能看到旁边的名字,你已经成功地激活了环境。在我们继续之前,让我们看看我们的可执行文件现在在哪里,方法是在 Python shell 中运行与本节开头相同的命令,以查看可执行文件的路径:

>>> import os
>>> import sys
>>> os.path.dirname(sys.executable)

在运行这些相同的行之后,您会注意到输出了一个不同的路径。这是我们的 Conda 环境的可执行文件,将运行我们的脚本。现在我们可以开始安装我们可能需要使用的任何库或包。

安装软件包

为了将包安装到虚拟环境中,我们将使用 pip 。安装任何软件包的语法都是一样的。它的关键字是 pip 安装,后跟软件包名称。在我们的例子中,我们今天将使用请求包。让我们编写以下命令:

$ pip install requests

继续运行该命令。我们刚刚在我们的环境中安装了请求模块。要确保安装正确,请编写以下命令:

$ conda list

该命令列出了安装在该环境中的所有软件包。您将能够看到我们刚刚下载的 requests 包,以及我们创建环境时最初下载的其他包。

API 和请求模块

请求模块允许我们使用 Python 发出 HTTP 请求。它是进行 API 调用和从外部资源请求信息的标准库。

注意

如果你对 HTTP 请求不熟悉,我建议查看一下 w3schools 1 资源以获取更多信息,因为这本书不是为涵盖网络而设计的。

一个应用程序编程接口 ( API )是一组允许应用程序访问操作系统、应用程序或其他服务的特征或数据的功能和过程。简单来说,API允许我们与其他开发者设计的网页和软件进行交互。假设你需要一些关于房价的数据。你可以利用像 Zillow 和 Trulia 这样的大公司汇集的资源,而不是自己收集所有的信息。为了访问这些信息,您需要调用他们的 API ,这将返回您需要的数据。API让开发人员的生活更加轻松,因为我们可以在我们的项目中使用其他公司创建的数据或工具。

使用请求模块

现在我们已经创建并激活了我们的环境,并且安装了我们将在今天余下的时间里使用的包,我们可以打开 Jupyter Notebook 了。

注意

如果您没有激活环境或者没有安装请求模块,那么您将会收到错误。确保激活环境,并检查是否安装了请求模块。

要了解本课程剩余部分的内容,请从“终端”中的“ python_bootcamp ”文件夹打开 Jupyter 笔记本。打开后,创建一个新文件,并将其重命名为“ Week_10。“接下来,对第一个标题为:“虚拟环境和请求模块”的单元格进行降价我们将开始在那间牢房下面工作。

发送请求

在这一课中,我们将从由 Github 创建的 API 中请求信息。通常,API需要一个密钥来使用它们的服务;然而,我们将使用一个不需要 API 键的函数。首先,我们必须向一个特定的 URL 发送一个请求,它将向我们发送一个响应。该响应将包含我们能够解析的数据。写下以下内容:

1| # sending a request and logging the response code
3| import requests
5| r = requests.get("https://api.github.com/users/Connor-SM")
7| print( r )
8| print( type( r ))

去查查手机。为了使用请求,您必须导入它,这就是我们在第 3 行所做的。接下来,我们在请求对象中使用 get() 方法,以便从我们传入的给定 URL 中请求信息。我们期望得到的数据将是我的 Github 账户的资料信息。请随意将 URL 中的“ Connor-SM ”替换为您自己的个人资料用户名。第一个 print 语句将输出一个响应代码。你该回去了<回应【200】>;如果没有,请务必检查您的互联网连接。这个输出让我们知道我们成功地从 Github URL 请求了信息。有关响应代码列表及其含义,请务必访问w3schools2资源。第二个 print 语句将输出变量的类型,这是一个请求对象。所有请求对象都预装了我们可以访问的默认方法和属性。这将允许我们处理收到的数据。

访问响应内容

为了访问我们在响应中得到的数据,我们需要访问请求对象中的内容属性:

# accessing the content that we requested from the URL
data = r.content
print(data)

去查查手机。我们将得到一个字节字符串输出,它带有大量的括号和难以阅读的信息。来自API的响应通常以字符串格式发送,因为字符串是比对象轻得多的数据类型。我们得到的实际响应是以 JSON 格式显示的。JavaScript 对象符号( JSON )格式相当于 Python 字典,是通过请求发送数据的默认格式。下一步是将数据从一个 JSON 格式的字符串转换成一个我们可以解析的字典。

转换响应

幸运的是,requests 对象附带了一个名为 json() 的内置 JSON 转换方法。在我们将响应转换成字典之后,让我们输出所有的键值对:

# converting data from JSON into a Python dictionary and outputting all key-value pairs
data = r.json( )      # converting the data from a string to a dictionary
for k, v in data.items( ):
      print("Key: { } \t Value: { }".format(k, v))
print(data["name"])     # accessing data directly

去查查手机。通过 for 循环实现和简单的 print 语句,现在所有的信息都易于阅读和访问。

传递参数

您执行的大多数 API 调用都需要额外的信息,比如参数或头文件。这些信息由 API 接收,并用于执行特定的任务。让我们这次执行一个调用,同时在 URL 中传递参数,以在 Github 上搜索特定于 Python 的存储库:

# outputting specific key-value pairs from data
r = requests. get("https://api.github.com/search/repositories?q=language:python")
data = r.json( )
print(data["total_count"])    # output the total number of repositories that use python

去查查手机。有几种不同的方法可以通过请求发送参数。在这种情况下,我们将它们直接写入 URL 字符串本身。您也可以在 get 方法中定义它们,如下所示:

>>> requests.get("https://api.github.com/search/repositories",
>>>           params = { 'q' = 'language:python' } )

当通过 URL 发送参数时,用问号分隔 URL 和参数。问号的右边是一组键值对,表示正在传递的参数。对于我们的例子,被传递的参数有一个键“ q ”和一个值“请求+语言:python ”。 Github 上的 API 将获取这些信息,并将使用 Python 的存储库数据返回给我们,因为这是我们在参数中要求的。然而,并不是所有的API都需要参数,就像我们在本课前面的第一个调用一样。为了弄清楚调用一个 API 时需要什么,总是要阅读文档。好的 API 文档非常重要,可以让你的开发者生活更加轻松。

注意

要停止运行虚拟环境,只需在终端中写入" deactivate。“本周每节课前会要求你激活环境。

周一练习

  1. 测试环境:创建一个新的虚拟环境,名为测试。“创建时安装 Python 版本 2.7 而不是当前版本。完成后,通过检查列表,确保它安装了正确的 Python 版本。

  2. JavaScript 库:使用我们上一课中的请求模块和 Github API 链接,计算出 Github 上有多少库使用 JavaScript。

今天是数据分析的一个重要开端。我们不仅讲述了如何使用虚拟环境及其原因,而且还简要介绍了 API 的请求模块。当在一周的剩余时间里使用任何图书馆时,我们都需要激活我们的数据分析虚拟环境。在本周末,我们将讨论网页抓取,这需要我们使用请求模块。

星期二:熊猫

当你需要处理数据时, Pandas 是终极工具。本质上是类固醇的优势。如果你熟悉 SQL 语言,这对你来说会更容易,因为 Pandas 是 Python 和 SQL 的混合体。最终,您将能够以比其他传统方法更有效的方式分析和处理表格数据。

就像昨天的课是如何开始的一样,我们需要将熊猫图书馆安装到我们的虚拟环境中。为了继续今天的课程,请将 cd 放入“ python_bootcamp ”文件夹,并激活环境。我们今天将从候机厅开始。

注意

如果你不记得如何激活环境,回到昨天的课程。

熊猫是什么?

Pandas 是一个在 C 语言中构建的灵活的数据分析库,非常适合处理表格数据。这是目前基于 Python 的数据分析事实上的标准,流利地使用 Pandas 将会对你的生产力和你的简历产生奇迹。这是从零开始回答的最快方法之一。由于是用 C 语言编写的,它在执行计算时提高了速度。Pandas 模块是一个高性能、高效率、高水平的数据分析库。它允许我们处理大量的数据,称为数据帧。

注意

NumPy 是 Python 中科学计算的基础包。它由 C 语言构建,使用多维数组,可以高速执行计算。

熊猫图书馆在很多方面都很有用,你可以做以下任何事情,甚至更多:

  • 计算统计数据并回答关于数据的问题,如每列的平均值、中值、最大值和最小值

  • 查找列之间的相关性

  • 跟踪一个或多个列的分布

  • 在 matplotlib 的帮助下,使用柱状图、柱状图等可视化数据。

  • 清理和过滤数据,无论是缺失的还是不完整的,只需应用用户定义的函数( UDF )或内置函数

  • 将表格数据转换为 Python 以便使用

  • 将数据导出到 CSV、其他文件或数据库中

  • 对可应用于您的分析的新列进行功能设计

无论您需要对数据做什么,Pandas 都是您的终极分析库。

关键术语

以下是我们将在本节中使用的关键术语。请务必仔细阅读并在必要时参考它们:

  • 系列➤一维标记数组,能够保存任何类型的数据

  • 数据框架➤电子表格

  • 轴➤列或行,轴= 0 乘行;轴= 1(按列)

  • 记录➤一行

  • 数据帧或系列对象的➤数据类型

  • 时间序列➤序列使用时间间隔的对象,如按小时跟踪天气

安装熊猫

要安装 Pandas,首先确保您的虚拟环境被激活,然后将以下命令写入终端:

$ pip install pandas

运行该命令后,它应该会安装 Pandas 需要的几个包。如果您想检查并确保您下载了正确的库,只需写出 list 命令。

进口熊猫

为了继续本课的剩余部分,让我们打开并继续我们之前的笔记本文件“ Week_10 ”,并在底部添加一个标有“熊猫”的降价单元格

进口熊猫很简单;但是,在导入库时有一个行业标准:

# importing the pandas library
import pandas as pd          # industry standard name of pd when importing

去查查手机。我们将熊猫作为 pd 导入,因为它更短,更容易引用。

创建数据帧

Pandas 研究的中心对象是 DataFrame,这是一种表格数据结构,包含行和列,就像 Excel 电子表格一样。您可以从 Python 字典或包含表格数据的文件(如 CSV 文件)创建数据框架。让我们从字典中创建我们自己的字典:

 1| # using the from_dict method to convert a dictionary into a Pandas DataFrame
 2| import random
 4| random.seed(3)     # generate same random numbers every time, number used doesn't matter
 6| names = [ "Jess", "Jordan", "Sandy", "Ted", "Barney", "Tyler", "Rebecca" ]
 7| ages = [ random.randint(18, 35) for x in range( len(names) )]
 9| people = { "names" : names, "ages" : ages }
11| df = pd.DataFrame.from_dict(people)
12| print(df)

去查查手机。我们导入了 random 模块,这样我们就可以为第 7 行的人创建随机年龄。在第 4 行使用种子方法会给我们两个相同的随机数。您可以将任何数字作为参数传递给 seed 然而,如果你使用一个不是 3 的数字,你会得到一个不同于这本书的输出。

注意

随机数不是真正的随机;它们遵循特定的算法返回一个数字。

在我们为每个人生成一个姓名和随机年龄的列表后,我们创建了一个名为“ people”的字典。“神奇的事情真的发生在第 11 行,我们用熊猫来创建我们将要使用的数据帧。当它被创建时,它使用键作为列名,值与相应的索引相匹配,这样的名字[0]的年龄[0] 将成为一条记录。您应该输出一个类似于表 10-1 的表格。

表 10-1

从虚假数据创建的数据框架

|   |

年龄

|

名称

|
| --- | --- | --- |
| Zero | Twenty-five | 系以脚带 |
| one | Thirty-five | 约旦 |
| Two | Twenty-two | 桑迪 |
| three | Twenty-nine | 泰德 |
| four | Thirty-three | 大吵大闹 |
| five | Twenty | 泰勒(男子名) |
| six | Eighteen | 丽贝卡(女子名ˌ寓意迷人的美) |

访问数据

有几种不同的方法可以访问数据帧中的数据。您可以选择按列或按记录进行选择。让我们来看看如何做到这两者。

按列索引

按列访问数据与用键从字典中访问数据是一样的。在第一组括号中,输入您想要访问的列名。如果您想要访问该列中的特定记录,您可以使用第二组带索引的括号:

1| # directly selecting a column in Pandas
2| print( df["ages"] )
3| print( df["ages"][3] )     # select the value of "ages" in the fourth row (0-index based)
5| # print( df[4] )   doesn't work, 4 is not a column name

去查查手机。在第 2 行,我们输出数据的整个年龄列。第二条语句允许我们访问特定单元格中的值。但是要小心,将索引号放在第一组括号中会产生错误,因为第一组括号仅用于列名,而“ 4 不是列。

记录索引

当你需要访问整个记录时,你必须使用 loc 。这允许我们通过索引来指定记录位置。让我们访问整个第一条记录,然后访问该记录中的名称:

# directly selecting a record in Pandas using .loc
print( df.loc[0] )
print( df.loc[0]["names"] )     # selecting the value at record 0 in the "names" column

去查查手机。我们可以看到我们能够输出整个记录。在使用 loc 的情况下,必须首先指定记录索引位置,然后指定列名。

分割数据帧

当您想要访问特定数量的记录时,您必须对数据帧进行切片。分割 Pandas 的工作方式与 Python 列表完全相同,在一组括号内使用开始停止步骤。让我们访问从索引 2 到索引 5 的记录:

# slicing a DataFrame to grab specific records
print( df[2:5] )

去查查手机。这将输出索引 234 处的记录。同样,切片时要小心,因为去掉冒号会导致试图访问列名。

内置方法

当使用熊猫时,这些方法经常被用来使你的生活更容易。可能要花整整一周的时间,仅仅探索熊猫中 DataFrames 支持的内置函数。然而,我们将简单地突出几个有用的,给你一个熊猫开箱即用的想法。

头部( )

当您处理大型数据集时,您通常会希望查看几条记录来了解您正在查看的内容。要查看数据帧中最上面的记录以及列名,可以使用 head() 方法:

# accessing the top 5 records using .head( )
df.head(5)

去查查手机。这将输出前五条记录。传递给该方法的参数是任意的,将从顶部显示您想要的任意多的记录。

尾部( )

要从底部查看给定数量的记录,可以使用 tail() 方法:

# accessing the bottom 3 records using .tail( )
df.tail(3)

去查查手机。这将输出底部的三条记录供我们查看。

按键( )

有时您需要列名。无论您是在制作模块化脚本还是分析您正在处理的数据,使用 keys( ) 方法都将有所帮助:

# accessing the column headers (keys) using the .keys( ) method
headers = df.keys( )
print(headers)

去查查手机。这将在我们的数据帧中输出一个标题名称列表。

。形状

数据帧的形状通过列数来描述记录的数量。检查形状以确保使用适当数量的数据始终很重要:

# checking the shape, which is the number of records and columns
print( df.shape )

去查查手机。我们将得到一个返回的 (7,2) 元组,表示记录和列。

描述( )

描述方法将为您提供所有数字数据的基本分析。您可以查看最小值、最大值、25%、50%、平均值等。,只需在 DataFrame 上调用此方法。这些信息有助于您开始分析,但通常不会回答您正在寻找的那些问题。相反,我们可以使用这种方法作为从哪里开始的指南:

# checking the general statistics of the DataFrame using .describe( ), only works on numerical columns
df.describe( )

去查查手机。请记住,它只会返回数字列类型的信息,这就是为什么我们只看到 ages 列的输出。

排序值( )

当需要根据列信息对数据帧进行排序时,可以使用这种方法。您可以传入一列或多列作为排序依据。当传递多个时,必须将它们作为列名列表传递,其中第一个名称优先:

# sort based on a given column, but keep the DataFrame in tact using sort_values( )
df = df.sort_values("ages")
df.head(5)

去查查手机。在这个单元格中,我们将 df 变量的值重新声明为新排序的 DataFrame。这样我们可以查看所有按年龄排序的人。您也可以传入一个参数来按降序排序。

过滤

让我们来看看如何过滤数据帧以获取满足特定条件的信息。

条件式

我们可以创建一个布尔数据类型的列来表示我们正在检查的条件,而不是过滤掉信息。让我们用当前的数据框架写一个条件,显示 21 岁或以上可以喝酒的人:

# using a conditional to create a true/false column to work with
can_drink = df["ages"] > 21
print(can_drink)

去查查手机。当您想要创建基于布尔数据类型的列时,您需要写出基于整个列的条件。在这里,我们创建了一个 can_drink 变量来存储整个 ages 列的值。它们是真-假值,因为我们创造了我们的条件。我们可以用它来创建另一个列。

系统增强

当您需要过滤掉记录但保留数据帧中的信息时,您需要使用一个称为子集的概念。我们将使用与前面相同的条件,只是这次我们将使用它来过滤掉记录,而不是创建一个真假表示:

# using subsetting to filter out records and keep DataFrame intact
df[ df["ages"] > 21 ]

去查查手机。输出只产生年龄等于或大于 21 岁的记录。我们从上面获取条件,并在访问 df 变量时将它放在括号内。尽管看起来很奇怪,但语法表示如下:

>>> dataframe_variable [ conditional statement to filter records with ]

您也可以编写以下代码来获得相同的结果:

>>> df[ can_drink ]

请记住,can_drink 是 true-false 值的表示,这意味着前面的语句将过滤掉所有值为 false 的记录。

列转换

从 CSV 或数据库导入的原始数据帧中的列很少会是您分析所需的列。您将花费大量的时间,使用一般的计算操作不断地转换列或列组,以产生作为旧列的函数的新列。熊猫对此全力支持,并且做得很有效率。

生成包含数据的新列

要在 DataFrame 中创建一个新列,可以使用与向字典中添加新的键值对相同的语法。让我们创建一列假数据,代表我们数据框架中的人成为我们公司的客户有多长时间了:

1| # generating a new column of fake data for each record in the DataFrame to represent customer tenure
2| random.seed(321)
4| tenure = [ random.randint(0, 10) for x in range( len(df) )]
6| df["tenure"] = tenure         # same as adding a new key-value pair in a dictionary
7| df.head( )

去查查手机。输出将产生一个新的列,该列是为他们的任期创建的随机数。我们能够在第 6 行添加列及其值。在表 10-2 中,你会发现更新的数据帧,按年龄排序。

表 10-2

向数据框架添加新列

|   |

年龄

|

名称

|

任期

|
| --- | --- | --- | --- |
| six | Eighteen | 丽贝卡(女子名ˌ寓意迷人的美) | four |
| five | Twenty | 泰勒(男子名) | six |
| Two | Twenty-two | 桑迪 | Two |
| Zero | Twenty-five | 系以脚带 | five |
| three | Twenty-nine | 泰德 | eight |
| four | Thirty-three | 大吵大闹 | seven |
| one | Thirty-five | 约旦 | five |

应用( )

基于当前数据添加新列被称为“特征工程”这是数据分析师工作的一大部分。通常,你无法从你收集的数据中回答你的问题。相反,您需要创建对回答问题有用的自己的数据。对于这个例子,让我们试着回答以下问题:“每个顾客属于哪个年龄段?”。你可以看这些人的年龄,并推测他们的年龄组;然而,我们想让它变得更简单。为了容易地回答这个问题,我们将需要一个代表每个客户年龄组的新列。我们可以通过在数据帧上使用应用方法来做到这一点。apply 方法接收每条记录,应用传递的函数,并将返回值设置为新的列数据。让我们来看看:

# feature engineering a new column from known data using a UDF
def ageGroup(age):
      return "Teenager" if age < 21 else "Adult"
df["age_group"] = df["ages"].apply(ageGroup)
df.head(10)

去查查手机。使用 apply 方法,我们能够创建一个新列来轻松回答我们的问题。当添加新的年龄组列时,我们基于年龄组列中的值应用了年龄组函数。然后,它遍历 DataFrame 中的每条记录,并将"青少年或"成人"的返回值设置为新的 age_group 列的值。apply 方法使我们可以很容易地用自己的 UDF 添加新数据。看一下表 10-3 。

表 10-3

特征工程年龄组列

|   |

年龄

|

名称

|

任期

|

年龄组

|
| --- | --- | --- | --- | --- |
| six | Eighteen | 丽贝卡(女子名ˌ寓意迷人的美) | four | 十几岁的青少年 |
| five | Twenty | 泰勒(男子名) | six | 十几岁的青少年 |
| Two | Twenty-two | 桑迪 | Two | 成人 |
| Zero | Twenty-five | 系以脚带 | five | 成人 |
| three | Twenty-nine | 泰德 | eight | 成人 |
| four | Thirty-three | 大吵大闹 | seven | 成人 |
| one | Thirty-five | 约旦 | five | 成人 |

注意

当需要基于多列应用值时,必须设置 axis = 1。

聚集

原始数据加上转换通常只是故事的一半。您的目标是从数据中提取实际的见解和可操作的结论,这意味着通过聚合函数将数据从潜在的数十亿行缩减为统计数据的摘要。本节假设您对 SQL 和 groupby 函数有所了解。如果你不熟悉 groupby 在 SQL 中的工作方式,请访问 w3schools3获取参考资料。

groupby(群件)

为了将信息浓缩成统计数据的摘要,我们需要使用 Pandas 的 groupby 方法。无论何时将信息分组,都需要使用聚合函数让程序知道如何将信息分组。现在,让我们计算一下在我们的数据框架中每个年龄组有多少记录:

# grouping the records together to count how many records in each group
df.groupby("age_group", as_index=False).count( ).head( )

去查查手机。当使用计数方法将信息分组在一起时,程序将简单地累加属于每个类别的记录数。我们将有两个类别:有五项记录的成人和有两项记录的青少年。我们的 groupby 方法的第一个参数是我们想要分组的列,第二个参数是确保我们不会将索引重置为年龄组列。如果设置为 True,,那么产生的数据帧将使用 age_group 作为每条记录的唯一标识符。

平均值( )

我们不用计算每个类别中有多少条记录,而是使用 mean 方法求出每一列的平均值。我们将基于同一列进行分组:

# grouping the data to see averages of all columns
df.groupby("age_group", as_index=False).mean( ).head( )

去查查手机。使用平均值方法,我们将能够得到所有数值列的平均值。输出结果应该是类似于表 10-4 的数据帧。

表 10-4

按年龄组分组并平均数据

|   |

年龄组

|

年龄

|

任期

|
| --- | --- | --- | --- |
| Zero | 成人 | Twenty-eight point eight | Five point four |
| one | 十几岁的青少年 | Nineteen | Five |

仅仅通过平均信息,我们可以看到成年人倾向于拥有更长的任期。请注意,names 列已被删除。这是因为 groupby 只保存数字数据,因为它不能计算字符串的平均值。

具有多列的 groupby()

当需要按多列分组时,参数必须作为列表传入。列表中的第一项将是数据帧分组所依据的主列。在我们的例子中,让我们看看有多少成年人的任期是五年:

# grouping information by their age group, then by their tenure
df.groupby( [ "age_group", "tenure" ], as_index=False).count( ).head(10)

去查查手机。为了回答这个问题,我们需要首先按照年龄组进行分组,以便将信息浓缩为成年人和青少年。接下来,我们需要根据任期进一步对数据进行分组。这可以让我们看到每个任期内有多少成年人。由于我们没有太多的数据,答案只有两个。我们得出这个结论是因为我们在分组时使用了计数方法。每个年龄组的所有其他任期只有一个客户。

添加记录

要将记录添加到数据帧中,需要访问下一个索引,并以列表结构的形式赋值。在我们的例子中,下一个索引将是 7 。让我们添加一个已经存在于 DataFrame 中的相同行,这样我们就可以看到如何删除下一个单元格中的重复信息:

# adding a record to the bottom of the DataFrame
df.loc[7] = [ 25, "Jess", 2, "Adult" ]    # add a record
df.head(10)

去查查手机。这将在底部添加一条新记录,其数据与索引 0 中的记录相同。您不需要太频繁地添加新记录,但是当时机到来时知道如何做是有帮助的。

drop_duplicates()

您经常会看到带有重复信息的数据,或者只是重复的 id。您必须删除所有重复的记录,因为这会扭曲您的数据,导致不正确的答案。您可以根据单个列或整个记录是否相同来删除重复记录。在我们的例子中,让我们删除基于相似名称的重复项,这将删除我们刚刚添加到数据帧中的记录:

# removing duplicates based on same names
df = df.drop_duplicates( subset="names" )
df.head(10)

去查查手机。这将删除名为“ Jess ”的第二条记录通过将列名传递给 subset 参数,我们可以删除所有同名的重复项。

注意

省略 subset 参数将只删除所有列中具有相同值的重复记录。

熊猫加入

通常,您将不得不组合来自多个不同来源的数据,以获得您的探索或建模所需的实际数据集。Pandas 在设计连接时大量使用 SQL。本节假设对 SQLSQL 连接有所了解。如果您不熟悉 SQL 中的连接的工作方式,请访问 w3schools 4 获取参考资料。

创建第二数据帧

让我们创建一个二级数据框架来代表客户对我们公司的评价。我们将为三个用户创建评级,这样我们就可以看到内部连接和外部连接:

# creating another fake DataFrame to work with, having same names and a new ratings column
ratings = {
       "names" : [ "Jess", "Tyler", "Ted" ],
       "ratings" : [ 10, 9, 6 ]
}
ratings = df.from_dict(ratings)
ratings.head( )

去查查手机。现在我们已经创建了第二个数据帧,我们可以将这两个数据帧连接在一起,就像在 SQL 中将两个表连接在一起一样。

内部连接

每当执行联接时,都需要一个唯一的列来联接数据。在我们的例子中,我们可以使用 names 列将 ratings 数据帧与我们的原始数据帧连接起来。让我们对这两个数据集执行内部连接,以便将用户与其评级联系起来:

# performing an inner join with our df and ratings DataFrames based on names, get data that matches
matched_ratings = df.merge(ratings, on="names", how="inner")
matched_ratings.head( )

去查查手机。我们将得到类似于表 10-5 的输出:

表 10-5

连接数据框架以一起查看客户评级和年龄

|   |

年龄

|

名称

|

任期

|

年龄组

|

等级

|
| --- | --- | --- | --- | --- | --- |
| Zero | Twenty | 泰勒(男子名) | six | 十几岁的青少年 | nine |
| one | Twenty-five | 系以脚带 | five | 成人 | Ten |
| Two | Twenty-nine | 泰德 | eight | 成人 | six |

使用 merge 方法,我们能够执行一个连接。通过将 how 参数指定为“ inner ,我们能够返回一个数据帧,其中只包含那些发布评级的记录。与以前相比,我们现在可以利用这些数据做更多的事情。我们可以计算给我们评级的客户的平均年龄,每个年龄组的平均评级等。连接总是有助于将独立的数据帧连接在一起,这在处理数据库时尤其有用。

外部连接

如果我们想要返回所有的记录,但是要连接给出一个记录的人的评分,我们需要执行一个外部连接。这将允许我们保留原始数据帧中的所有记录,同时添加评级列。我们需要指定如何将参数改为外层:

# performing an outer join with our df and ratings DataFrames based on names, get all data
all_ratings = df.merge(ratings, on="names", how="outer")
all_ratings.head( )

去查查手机。这次我们将得到所有七个记录的数据帧;然而,那些没有给出评级的人会被给一个 NaN 作为一个值。这代表的不是一个数字。“一旦我们将这些信息结合起来,我们就可以找出那些给出评价和没有给出评价的人的平均年龄。从营销的角度来看,这将有助于了解谁是目标人群。

数据集管道

数据集管道是一个特定的过程,在这个过程中,我们获取数据,并为我们的模型清理数据,从而能够进行预测。如果您使用的数据集不干净,这可能是一个漫长的过程。不干净的数据集将具有重复记录、到处都是空值或导致不正确预测的未过滤信息。一般流程如下:

  1. 进行探索性分析

    • 在这一步中,您希望非常了解您的数据。记下您一眼看到的内容或您可能想要清理或添加的内容。本质上,您希望对您的数据所能提供的东西有所了解。记下列数、数据类型、异常值、空值和不必要的列。这通常是当您想要绘制出每列数据并推测相关性、非信息特征等的时候。
  2. 数据清理

    • 不适当的清理会导致不良预测和不良数据集。在这里,您需要移除不需要的观察值(如重复值),修复结构错误(如同名但输入错误的列),处理缺失数据,并过滤异常值信息。这是下一步的关键。
  3. 特色工程

    • 创建数据集没有描述的新信息非常重要。如果你对这个主题有所了解,你可以利用自己的专业知识,你可以隔离数据,让你的算法更专注于重要的观察。在这里,您可以将特征工程列归入一个组,添加虚拟变量,删除未使用的特征等。如果您认为数据缺失或者可以根据数据集中的信息创建数据,那么您可以在这里用自己的知识扩展数据集。

现在您已经知道了清理数据集的过程,这将在一天结束时的第一个练习中派上用场。

星期二练习

  1. 加载数据集:进入 www.Kaggle.com ,点击顶栏菜单中的数据集。选择一个你喜欢的数据集,下载到“ python_bootcamp ”文件夹。然后,使用 read_csv 方法将数据集加载到 Pandas DataFrame 中,并显示前五条记录。

  2. 数据集分析:这是一个开放式的练习。对您在练习#1 中选择的数据集进行一些分析。尝试回答这样的问题:

    1. 有多少项记录?

    2. 每一列的数据类型是什么?

    3. 是否有重复的记录或列?

    4. 是否有数据缺失?

    5. 两列或多列之间是否存在相关性?

今天的重点是学习非常重要的 Pandas 库以及如何使用数据框架。我们用了一些小的现实生活中的例子,但在大多数情况下,今天只是了解你可以在熊猫身上做什么。对于周五的项目,我们将使用熊猫来帮助我们分析体育统计数据。

周三:数据可视化

数据可视化是分析师拥有的最强大的工具之一,主要有两个原因。首先,它在指导分析师决定“下一步看什么”方面的能力是无与伦比的。通常,直观显示的是数据中的模式,这些模式不容易通过只看数据帧来辨别。其次,他们是分析师最伟大的沟通工具。专业分析师需要向负责根据数据采取行动的团队展示他们的结果。视觉效果比原始数据更能讲述你的故事。

就像昨天的课程是如何开始的一样,我们需要在我们的虚拟环境中安装一个库。为了继续今天的课程,请将 cd 放入“ python_bootcamp ”文件夹并激活环境。我们今天将从候机厅开始。

图表的类型

知道使用哪种图表对于正确显示数据非常重要。我们今天会看几个图表。但是,以下是一些您想知道的常见图表:

  • 折线图:随时间探索数据

  • 条形图:比较数据类别,跟踪随时间的变化

  • 饼图:探索整体的部分,即分数

  • 散点图:像折线图一样,追踪两个类别之间的相关性

  • 直方图:与条形图无关,显示变量分布

  • 蜡烛图:在金融领域用的很多,就是可以比较一只股票在一段时间内的走势

  • 箱线图:看起来与烛台图表一样,比较最小值、第一个四分位数、中间值、第三个四分位数和最大值

根据您在概念化数据时需要完成的任务,您将能够选择特定类型的图表来描绘您的数据。

安装 Matplotlib

要安装 matplotlib ,首先确保您的虚拟环境被激活,然后将以下命令写入终端:

$ pip install matplotlib

运行该命令后,它应该安装几个 matplotlib 需要的包。如果您想检查并确保您下载了正确的库,只需写出 list 命令。

导入 Matplotlib

为了跟上本课的其余部分,让我们打开并继续我们之前的笔记本文件" Week_10 "并在底部添加一个标有" Matplotlib "的 markdown 单元格

与 Pandas 一样,matplotlib 在您导入库时也有一个行业标准名称:

# importing the matplotlib library from matplotlib import
pyplot as plt          # industry standard name of plt when importing

去查查手机。我们将 pyplot 作为 plt 导入,这样我们就可以引用 matplotlib 提供的许多图表。

线形图

让我们从我们可以创建的最基本的图表开始,折线图:

 1| # creating a line plot using x and y coords
 3| x, y = [ 1600, 1700, 1800, 1900, 2000 ] , [ 0.2, 0.5, 1.1, 2.2, 7.7 ]
 5| plt.plot(x, y)      # creates the line
 7| plt.title("World Population Over Time")
 8| plt.xlabel("Year")
 9| plt.ylabel("Population (billions)")
11| plt.show( )

去查查手机。首先,我们创建我们的 xy 坐标用于绘图。 plot() 方法允许我们绘制一条单线;它只需要传入坐标。第 7、8 和 9 行都是用来定制图表及其外观的。最后,我们使用 show() 方法来呈现图表。你应该输出一个类似图 10-1 的图表。

img/481544_1_En_10_Fig1_HTML.jpg

图 10-1

人口数据单线图

当您想要在图表中添加更多的线条时,只需根据需要应用任意多的 plot()方法。这一次,让我们为每条情节主线添加更多定制:

 1| # creating a line plot with multiple lines
 3| x1, y1 = [ 1600, 1700, 1800, 1900, 2000 ] , [ 0.2, 0.5, 1.1, 2.2, 7.7 ]
 4| x2, y2 = [ 1600, 1700, 1800, 1900, 2000 ] , [ 1, 1, 2, 3, 4 ]
 6| plt.plot(x1, y1, "rx-", label="Actual")      # create a red solid line with x dots
 7| plt.plot(x2, y2, "bo--", label="Fake")      # create a blue dashed line with circle dots
 9| plt.title("World Population Over Time")
10| plt.xlabel("Year")
11| plt.ylabel("Population (billions)")
12| plt.legend( )    # shows labels in best corner
14| plt.show( )

去查查手机。通过添加第二组坐标,我们能够在第 7 行使用 plot() 方法绘制第二条线。我们还使用速记语法指定了这些行应该如何呈现。对于 plot 方法中的第三个参数,我们可以传递一个表示颜色、点符号和线条样式的字符串。最后,我们为每一行添加了一个标签,以便于阅读多行图表,并且我们能够通过调用 legend() 方法来显示它。输出应该如图 10-2 所示。

img/481544_1_En_10_Fig2_HTML.jpg

图 10-2

人口数据的多线图

条形图

当您需要绘制分类数据时,条形图是更好的选择。让我们为选择他们最喜欢的电影类别的人数创建一些假数据,并绘制出来:

 1| # creating a bar plot using x and y coords
 3| num_people, categories = [ 4, 8, 3, 6, 2 ] , [ "Comedy", "Action", "Thriller", "Romance", "Horror" ]
 5| plt.bar(categories, num_people)
 7| plt.title("Favorite Movie Category", fontsize=24)
 8| plt.xlabel("Category", fontsize=16)
 9| plt.ylabel("# of People", fontsize=16)
10| plt.xticks(fontname="Fantasy")
11| plt.yticks(fontname="Fantasy")
13| plt.show( )

去查查手机。创建了要处理的数据后,我们在第 5 行创建了我们的图。使用 bar() 方法,我们能够创建条形图。数字数据必须总是设置在 y 轴上,这就是为什么我们的类别在 x 轴上。我们还向图表添加了几个新的定制。我们可以调整字体大小、要显示的字体,甚至可以调整刻度线显示的大小。你应该渲染一个类似图 10-3 的图表。

img/481544_1_En_10_Fig3_HTML.jpg

图 10-3

电影类别数据的条形图

箱形图

在需要比较一段时间内或不同类别的单个统计数据的情况下,箱线图非常有用。它们的设计类似于烛台图表,您可以在其中查看最小值、最大值、25%四分位数、75%四分位数和中值,这对于显示一段时间内的数据非常有用。对于股票,货币是 y 轴数据,时间是 x 轴数据。对于我们的示例,让我们创建两个单独的组并显示每个组的高度:

 1| # creating a box plot – showing height data for male-female
 3| males, females = [ 72, 68, 65, 77, 73, 71, 69 ] , [ 60, 65, 68, 61, 63, 64 ]
 4| heights = [ males, females ]
 6| plt.figure(figsize=(15, 8))     # makes chart bigger
 7| plt.boxplot(heights)           # takes in list of data, each box is its' own array, heights contains two lists
 9| plt.xticks( [ 1, 2 ] , [ "Male" , "Female " ] )         # sets number of ticks and labels on x-axis
10| plt.title("Height by Gender", fontsize=22)
11| plt.ylabel("Height (inches)", fontsize=14)
12| plt.xlabel("Gender", fontsize=14)
14| plt.show( )

去查查手机。为了在单独的类别中绘制数据,我们需要一个列表列表。在第 4 行,我们声明我们的数据,它包含男性和女性的身高列表。当我们绘制数据时,它会将每个列表放入自己的框中。你会注意到这个数字比平常大得多;我们在第 6 行声明一个新的图形大小。为了呈现图表,我们在第 7 行使用了 boxplot() 方法,并将 heights 作为我们的数据传入。然而,更重要的一行是第 9 行,在这里我们定义了在 x 轴上出现的类别的数量。我们将它们排序为“”然后“”,因为这是它们在第 4 行声明的顺序。图表应呈现如图 10-4 所示。**

**img/481544_1_En_10_Fig4_HTML.jpg

图 10-4

高度数据的箱线图

散点图

如果你熟悉聚类,那么你就会知道散点图的重要性。这些类型的图通过为每组数据绘制一个点来帮助区分不同的组。使用两个特征,像花的高度和宽度,我们可以分类花属于哪个物种。让我们创建一些假数据,并绘制点:

 1| # creating a scatter plot to represent height-weight distribution
 2| from random import randint
 3| random.seed(2)
 5| height = [ randint(58, 78) for x in range(20) ]        # 20 records between 4'10" and 6'6"
 6| weight = [ randint(90, 250) for x in range(20) ]     # 20 records between 90lbs. and 250lbs.
 8| plt.scatter(weight, height)
10| plt.title("Height-Weight Distribution")
11| plt.xlabel("Weight (lbs)")
12| plt.ylabel("Height (inches)")
14| plt.show( )

去查查手机。为了创建一些假数据,我们使用了来自 random 模块的 randint 方法。在这里,我们能够为身高体重列表创建 20 条记录。为了绘制数据,我们使用 scatter() 方法,并向图中添加一些特征。您应该得到如图 10-5 所示的输出。

img/481544_1_En_10_Fig5_HTML.jpg

图 10-5

身高体重数据散点图

柱状图

虽然折线图对于可视化时间序列数据的趋势非常有用,但直方图是可视化分布的王者。通常,您感兴趣的是变量的分布,可视化比一组汇总统计数据提供了更多的信息。首先,让我们看看如何创建直方图:

 1| # creating a histogram to show age data for a fake population
 2| import numpy as np         # import the numpy module to generate data
 3| np.random.seed(5)
 5| ages = [ np.random.normal(loc=40, scale=10) for x in range(1000) ]    # ages distributed around 40
 7| plt.hist(ages, bins=45)         # bins is the number of bars
 9| plt.title("Ages per Population")
10| plt.xlabel("Age")
11| plt.ylabel("# of People")
13| plt.show( )

去查查手机。我们之前提到过 NumPy 模块。在数据科学中,它被用来进行极快的数值计算。熊猫的数据帧建立在 NumPy 阵列之上。然而,对于这个单元格,您只需要知道我们用它来创建围绕给定数字的随机数。我们指定的数字被传递到第 5 行的 loc 参数中。 scale 参数是我们希望随机数相隔多远。当然,它仍然会创建这个范围之外的数字,但它主要是创建 1000 个集中在 40 岁左右的随机数。

为了创建直方图,我们使用了 hist() 方法并传入适当的数据。直方图让我们可以看到一个特定的数据出现了多少次。在我们的例子中,40 岁出现了 60 多次。y 轴代表 x 轴值的频率。参数指定了您在图表上看到多少个条形。你可能在想:垃圾箱越多越好,对吗?错,太多和太少之间总是有一条细微的界线;通常你只需要测试出正确的数字。我们通过添加定制来完成此图表。结果应该如图 10-6 所示。

img/481544_1_En_10_Fig6_HTML.jpg

图 10-6

集中分布的年龄数据直方图

虽然数据是假的,但我们可以从图表中推断出很多信息。我们可以看到可能存在的异常值,大致的年龄范围,等等。

直方图分布的重要性

要了解直方图对理解中心分布如此重要的原因,我们需要创建更多的假数据。然后,我们将绘制两个数据集,看看它们是如何叠加的:

# showing the importance of histogram's display central distribution
florida = [ np.random.normal(loc=60, scale=15) for x in range(1000) ]        # assume numpy is imported
california = [ np.random.normal(loc=35, scale=5) for x in range(1000) ]
# chart 1
plt.hist(florida, bins=45, color="r", alpha=0.4)           # alpha is opacity, making it see through
plt.show( )
# chart 2
plt.hist(california, bins=45, color="b", alpha=0.4)     # alpha is opacity, making it see through
plt.show( )
# chart 3
plt.hist(florida, bins=45, color="r", alpha=0.4)           # alpha is opacity, making it see through
plt.hist(california, bins=45, color="b", alpha=0.4)     # alpha is opacity, making it see through
plt.show( )

去查查手机。因为调用了三个 show 方法,所以我们能够在这个单元格中输出三个不同的直方图。当你看前两个直方图时,它们看起来是一样的。如果不深入研究图表,很难看出数据完全不同。因此,为了正确查看数据,我们输出第三个直方图,两个数据集重叠,如图 10-7 所示。我们现在能够清楚地看到每个数据集中心分布的差异。这在分析数据时很重要。我们将 alpha 设置为 0.4 ,因为它允许我们设置不透明度。数字越大,数据就越可靠。

img/481544_1_En_10_Fig7_HTML.jpg

图 10-7

直方图分布图重要性

注意

当呈现几个图表时, matplotlib 知道如何在运行 s how 方法后通过将图表重置为空来分隔每个绘图,直到那时所有正在绘制的信息都将包含在一个图表中。

保存图表

能够渲染这些图表是美妙的;但是,有时您需要在演示文稿中使用它们。幸运的是, matplotlib 提供了一种方法,可以将我们创建的图表保存到文件中。方法支持许多不同的文件扩展名;最常见的。jpg" 就是我们要用的。让我们将一个简单的绘图折线图呈现到本地文件夹中:

 1| # using savefig method to save the chart as a jpg to the local folder
 3| x, y = [ 1600, 1700, 1800, 1900, 2000 ] , [ 0.2, 0.5, 1.1, 2.2, 7.7 ]
 5| plt.plot(x, y, "bo-")       # creates a blue solid line with circle dots
 7| plt.title("World Population Over Time")
 8| plt.xlabel("Year")
 9| plt.ylabel("Population (billions)")
11| plt.savefig("population.jpg")

去查查手机。你会注意到在“ python_bootcamp ”文件夹中有一个名为“【population.jpg】的新图像文件。如果不指定 URL 路径,它会将图像保存在 Jupyter 笔记本文件所在的本地文件夹中。

注意

您可以将图表保存为 PDF 或 PNG 等其他格式。

扁平化多维数据

通常,在数据分析中,您希望尽可能避免 3D 绘图。这并不是因为你想要传达的信息没有包含在结果中,而是有时用其他方式表达一个观点更容易。表现第三维的最好方法之一是使用颜色而不是深度。

例如,假设您有三个数据集需要绘制:身高、体重和年龄。你可以渲染一个 3D 模型,但是那太过分了。相反,你可以像我们之前在散点图上一样渲染身高和体重,并给每个点着色来代表年龄。颜色的第三维现在很容易阅读,而不是试图用 z 轴(深度)来描绘数据。让我们在下面一起创建这个确切的散点图:

 1| # creating a scatter plot to represent height-weight distribution
 2| from random import randint
 3| random.seed(2)
 5| height = [ randint(58, 78) for x in range(20) ]
 6| weight = [ randint(90, 250) for x in range(20)
 7| age = [ randint(18, 65) for x in range(20) ]              # 20 records between 18 and 65 years old
 9| plt.scatter(weight, height, c=age)             # sets the age list to be shown by color
11| plt.title("Height-Weight Distribution")
12| plt.xlabel("Weight (lbs)")
13| plt.ylabel("Height (inches)")
14| plt.colorbar(label="Age")         # adds color bar to right side
16| plt.show( )

去查查手机。通过将代表颜色的参数 c 添加到散点图中,我们可以很容易地以 2D 方式表示三个数据集,如图 10-8 所示。右边的颜色条是通过第 14 行创建的,我们也为它创建了标签。在某些情况下,您确实需要使用 z 轴,比如表示空间数据。然而,在可能的情况下,简单地使用颜色作为第三维度不仅更容易创作,也更容易阅读。

img/481544_1_En_10_Fig8_HTML.jpg

图 10-8

使用颜色作为第三维渲染 3D 绘图

周三练习

  1. 三线图:创建三个随机数据列表,包含 20 个 1 到 10 之间的数字。然后创建一个三行的线图,每个列表一行。给每条线赋予自己的颜色、点符号和线型。

  2. 用户信息:创建一个程序,要求任意数量的用户给出 1 到 5 颗星之间的评级,并在没有更多用户愿意回答时绘制数据的条形图。使用下面的文本作为提问的例子

今天我们学习了数据可视化的重要性,以及如何创建自定义图表来恰当地展示我们的数据。使用 matplotlib 时,有大量的绘图可供选择,每种绘图都有自己的优缺点,在选择绘图类型时,您需要考虑这些优缺点。最后,如果您不能正确地向业务决策者展示数据,那么您收集的所有数据都将被浪费。

周四:网络抓取

你可能以前听说过“网页抓取”这个术语。在像 Python 这样的大多数语言中,web 抓取由两部分组成:发出请求和解析数据。我们需要为第一部分使用请求模块,为第二部分使用一个名为的库。简而言之,您编写的请求数据并解析数据的脚本被称为" scraper。“在今天的课程中,我们将使用这两个库来收集一些数据。

像昨天的课一样,我们需要在虚拟环境中安装一个库。为了继续今天的课程,请将 cd 放入“ python_bootcamp ”文件夹,并激活环境。我们今天将从候机厅开始。

安装美丽的汤

要安装美汤,首先要确保你的虚拟环境被激活,然后将以下命令写入终端:

$ pip install bs4

运行命令后,应该会安装几个美汤需要的包。

进口美丽的汤

为了跟上本课的其余部分,让我们打开并继续我们之前的笔记本文件“ Week_10 ”,并在底部添加一个标有“ Web 抓取”的 markdown 单元格。

我们需要导入请求bs4 库中的 BeautifulSoup 类:

# importing the beautiful soup and requests library
from bs4 import BeautifulSoup
import requests

去查查手机。我们将使用请求模块向给定的 URL 发出请求。当 URL 端点不是一个返回正确格式数据的 API,而是一个呈现 HTML 和 CSS 的网页时,我们得到的响应是该网页的代码。为了解析这段代码,我们将它传递到 BeautifulSoup 对象中,这使得操作和遍历代码变得容易。

请求页面内容

要开始收集数据,让我们向一个只包含一首诗的简单网页发送一个请求:

# performing a request and outputting the status code
page = requests.get("http://www.arthurleej.com/e-love.html")
print(page)

去查查手机。我们会得到一个“ <响应【200】>”的输出。这让我们知道对网页的请求是成功的。为了查看我们收到的响应,我们需要访问页面变量的内容属性:

# outputting the request response content
print(page.content)

去查查手机。这将输出一大串用于编写该网页的所有代码,包括标签、样式、脚本等。正如本书前面提到的,这个 URL 呈现一个网页,所以我们得到的响应是所有代码的字符串。下一步是将响应转换成我们可以处理和解析数据的对象。

用漂亮的汤解析响应

漂亮的 Soup 库附带了许多属性和方法,使我们更容易解析代码。使用这个库,我们可以使代码易于查看、抓取和遍历。我们需要创建一个 BeautifulSoup 对象,将页面内容以及我们想要使用的解析器类型传递给它。在我们的例子中,我们正在处理 HTML 代码,所以我们需要使用 HTML 解析器:

# turning the response into a BeautifulSoup object to extract data
soup = BeautifulSoup(page.content, "html.parser")
print( soup.prettify( ) )

去查查手机。方法将创建一个格式良好的输出供我们查看。这使得我们更容易看到实际编写的代码。由于我们指定的解析器, soup 对象知道如何正确地解析内容。Beautiful Soup 适用于其他语言,但我们将在本书中使用 HTML。既然我们已经将内容转化为可以使用的对象,让我们学习如何从代码中提取数据。

抓取数据

利用美汤提取数据的方法有很多。下面几节将介绍这样做的一些主要方法。本节假定了基本的 HTML 知识。

。查找( )

为了找到代码中的特定元素,我们可以使用 find() 方法。我们传递的参数是我们想要搜索的标签,但是它只会找到第一个实例并返回它。这意味着,如果我们的代码中有四个粗体元素标签,并且我们使用这个方法来查找一个粗体标签,它将只返回找到的第一个粗体元素标签。让我们试一试:

# using the find method to scrape the text within the first bold tag
title = soup.find("b")
print(title)
print( title.get_text( ) )         # extracts all text within element

去查查手机。如果您使用 web 浏览器控制台工具中的 inspector 选项卡查看代码,您将能够看到代码中的第一个粗体标签是这首诗的标题。第一个 print 语句的结果是“ < b > Love < /b > ”,第二个语句只是元素中的文本。我们能够通过使用 get_text() 方法提取文本。

。查找全部( )

为了找到给定元素的所有实例,我们使用了 find_all() 方法。这将返回代码中所有标签的列表。让我们找到代码中所有粗体标签并提取文本:

# get all text within the bold element tag then output each
poem_text = soup.find_all("b")
for text in poem_text:
      print( text.get_text( ) )

去查查手机。如果您使用检查器工具来查看代码,您会注意到所有文本都在粗体标签内。结果是整首诗的输出。

通过属性查找元素

所有的 HTML 元素都有与之相关的属性,无论是样式、id、类等等。,您可以使用 Beautiful Soup 来查找具有特定属性值的元素。让我们从我的个人 Github 页面请求响应,并找到显示我的用户名的元素:

1| # finding an element by specific attribute key-values
3| page = requests.get("https://github.com/Connor-SM")
4| soup = BeautifulSoup(page.content, "html.parser")
6| username = soup.find( "span", attrs={ "class" : "vcard-username" } )       # find first span with this class
8| print(username)       # will show that element has class of vcard-username among others
9| print( username.get_text( ) )

去查查手机。我们向 Github 发送一个请求,并将内容解析成一个 BeautifulSoup 对象进行处理。在第 6 行,我们搜索一个 span 标记元素,它的属性为 class,值为" vcard-username "这将在第 8 行输出整个 span 标记,包括文本、属性和语法。最后,我们提取第 9 行的文本,输出与该页面相关的用户名。

注意

通过属性查找元素也适用于 find_all 方法。您还可以在属性参数中包含多个键值对。

DOM 遍历

本节将介绍如何通过遍历 DOM 层次结构来提取信息。DOM 是文档对象模型的缩写,是网页设计中的一个概念,它描述了浏览器中元素之间的关系和结构。网页上的所有元素都属于三种关系之一:

  1. 亲子

  2. 兄弟姐妹

  3. 祖孙

当您进行 web 抓取时,理解这个概念很重要,因为您可能需要访问特定元素的子元素。子元素引用另一个元素中的所有元素。以下面的 HTML 代码为例:

<body>
     <div>
         <h1>Title</h1>
         <h3>Sub-title</h3>
         <p>Text</p>
     </div>
</body>

在本例中, < div > 元素是 h1h3p 元素的父元素。这三个元素被称为子元素。如果我们想从这个 < div > 元素中提取所有文本,我们可以访问子元素。

注意

在前面的示例中,h1、h3 和 p 元素都是同级元素。body 是 div 元素的父元素,也是 h1、h3 和 p 元素的祖父元素。

因为 DOM 是一个 web 设计概念,所以在本书中会简单介绍一下。如果你想了解更多关于这个主题或 HTML 基础知识的信息,一定要访问 w3schools 5 资源。

访问子属性

幸运的是,当 Beautiful Soup 将页面内容转换为对象时,它会跟踪所有元素的子元素。这允许我们遍历 DOM,并按照我们认为合适的方式解析数据。让我们从前面的诗中抓取并将响应转换成一个 BeautifulSoup 对象:

# traversing through the DOM using Beautiful Soup – using the children attribute
page = requests.get("http://www.arthurleej.com/e-love.html")
soup = BeautifulSoup(page.content, "html.parser")
print(soup.children)       # outputs an iterator object

去查查手机。soup 对象中的子元素存储在一个迭代器中。在下面的练习中,让我们从网页中提取出 title 元素。

了解孩子的类型

在开始之前,我们首先需要了解 BeautifulSoup 对象中的子类型。让我们将迭代器转换成可以循环的元素列表:

# understanding the children within the soup object
for child in list( soup.children ):
      print( type( child ) )

去查查手机。结果,我们会有四个孩子,但只有三种不同的类型:

  • <类‘bs4 . element . doctype’>

    • 一个 Doctype 对象引用了定义所用 HTML 版本的 Docstring。
  • <类‘bs4 . element . navigable string’>

    • 一个字符串对应于一个标签中的一段文本。Beautiful Soup 使用 NavigableString 类来包含这些文本。到目前为止,我们已经使用了 get_text() 方法来提取文本;但是,您也可以使用以下方法提取数据:

      >>> tag.string
      
      

      这产生了可导航字符串类型。

  • <类‘bs4 . element . tag’>

    • 一个标签对象对应于原始文档中的一个 XML 或 HTML 标签。当我们访问元素和它们的文本时,我们将访问原始的标签来这样做。

如果您要输出这些对象中的每一个,您会发现除了 Doctype 之外的所有代码都出现在标签对象中。

访问标签对象

如果我们想访问 title 标签中的文本,我们需要首先遍历它的父标签,这恰好是 head 标签。既然我们知道我们要寻找的元素存在于标签对象中,我们需要将该对象保存到一个变量中,并输出其中的部分:

# accessing the .Tag object which holds the html – trying to access the title tag
html = list( soup.children )[2]
for section in html:
     print("\n\n Start of new section")
     print(section)

去查查手机。当您输出 HTML 变量中的每个部分时,您会发现在第一个索引处有一个空的部分,在 head 元素的位置之前。我们为每个新部分输出 print 语句,以防空字符串占用索引。

访问 Head 元素标签

现在我们知道 head 元素位于 HTML 子元素的索引 1 处,我们可以执行相同的执行来访问 head 中的每个子元素:

# accessing the head element using the children attribute
head = list( html.children )[1]
for item in head:
      print("\n\n New Tag")
      print(item)

去查查手机。当你输出中的每个标签时,你会注意到我们一直在寻找的标题标签位于索引 1

注意

记住,存储在这些变量中的每个对象都是一个迭代器,可以被类型转换成列表。

抓取标题文本

最后一步是从标题标签中提取文本:

# scraping the title text
title = list( head )[1]
print(title.string)    # .string is used to extract text as well
print( type(title.string) )      # results in NavigableString
print( title.get_text( ) )

去查查手机。我们刚刚遍历了 DOM ,以便从我们的 title 元素中抓取文本。

注意

访问对象的子元素的能力允许我们创建模块化或自动化的 web scrapers,它可以执行各种任务。由于大多数网站在其网页上遵循相似的风格,如果我们知道正确的模式,创建一个在单个页面上提取信息的脚本将允许我们在许多其他页面上这样做。例如,名为 baseball-reference 的棒球在线统计数据库保存了 MLB 历史上所有棒球运动员的数据。每个玩家在网站的 URL 上都有一个唯一的标识符。如果您编写了一个解析脚本来提取一个球员的信息,那么您将能够编写一个循环来提取数据库中所有球员的信息。

周四练习

  1. 字数统计:写一个程序,统计以下环节有多少字: www.york.ac.uk/teaching/cws/wws/webpage1.html 。使用 requests 模块和 Beautiful Soup 库提取所有文本。

  2. 问题#2 :使用以下链接,从表格中提取每个体育场名称: https://en.wikipedia.org/wiki/List_of_current_National_Football_League_stadiums 。总共应该有 32 个名字。

今天我们学习了如何通过网络搜集信息。使用请求模块,我们可以接收呈现给定网页的代码响应。然后,我们可以将这个响应转换成一个对象,通过漂亮的 Soup 库轻松解析和提取数据。在明天的课程中,我们将使用这周学过的所有库来分析我们从网上搜集的信息。

星期五:网站分析

今天的项目将包括请求模块、美汤matplotlib 库。这个项目的目标是创建一个脚本,该脚本将接受一个网站来抓取和显示网站中使用的热门词汇。我们将在一个格式良好的条形图中绘制结果,使那些查看数据的人更容易理解。

为了继续今天的课程,请将 cd 放入“ python_bootcamp ”文件夹,并激活环境。我们将从之前的笔记本文件“ Week_10 ”继续,并在底部添加一个减价单元格,内容为“周五项目:网站分析

完工图纸

正如我们每周所做的,我们需要设计出最终的程序应该是什么样子,以及它应该如何运行。出于测试目的,我们将使用微软的主页。最终,我们希望最终的输出看起来如图 10-9 所示。

img/481544_1_En_10_Fig9_HTML.jpg

图 10-9

分析微软主页上最常用的词

我们将让程序不断地询问用户是否想抓取一个网站,然后接受用户对他们想分析的网站的输入。之后,我们可以执行我们的网站;过滤掉所有无用的信息,如冠词、换行符等。;最终能够创建条形图。程序输出应该如下所示:

>>> Would you like to scrape a website (y/n)? y
>>> Enter a website to analyze: https://www.microsoft.com/en-us/
>>> The top word is: Microsoft
>>> *** show bar plot ***
>>> Would you like to scrape a website (y/n)? n
>>> Thanks for analyzing! Come back again!

为了让输出正常工作,我们需要创建一个程序所需步骤的大纲。请花点时间试着自己写出来。我们创建的程序需要执行以下步骤:

  1. 继续问用户他们是否愿意抓取一个网站,直到他们说不。

  2. 询问用户是否愿意对网站进行网络抓取。

    • 如果用户说是

      1. 接受用户关于他们想要抓取的站点的输入。

      2. 向网站发送请求。

      3. 解析请求响应中页面内容的所有文本。

      4. 过滤掉所有非文本元素,如脚本、注释等。

      5. 过滤掉所有的文章单词和无用的字符,如换行符和制表符。

      6. 循环所有剩余的文本,并计算每个单词的频率。

      7. 保留前七个单词,显示最常用的单词。

      8. 创建前七个单词的条形图。

    • 如果用户说不

      1. 退出程序并显示感谢信息。

导入库

我们需要从导入我们将在整个项目中使用的所有库开始。让我们将所有导入放在它们自己的单元格中,这样我们只需要运行一次导入,而不是每次运行程序代码时都导入它们:

1| # import all necessary libraries
2| import requests
3| import matplotlib.pyplot as plt
4| from bs4 import BeautifulSoup
5| from bs4.element import Comment
6| from IPython.display import clear_output

去查查手机。唯一没有见过的新导入是第 5 行的导入。为了只分析页面上出现的单词,我们需要过滤掉程序中某个地方的注释中的所有文本。稍后使用 Comment 类将允许我们识别文本是否在注释中,以便我们可以正确地过滤掉它。

创建主循环

让我们在下一个单元格中编写以下所有代码,这样我们就不必重新运行所有的导入。我们需要创建一个主循环,这样我们就可以继续询问用户是否愿意浏览网站。当他们这样做时,我们将简单地打印他们现在进入的站点:

 1| # main loop should ask if user wants to scrape, then what site to scrape
 2| while input("Would you like to scrape a website (y/n)? ") == "y":
 3|  try:
 4|          clear_output( )
 6|          site = input("Enter a website to analyze: ")
 8|          print(site)       # remove after runs properly
 9|  except:
10|          print("Something went wrong, please try again.")
12| print("Thanks for analyzing! Come back again!")

去查查手机。这给了我们基本的循环结构,要求用户输入他们想要抓取的站点。如果他们选择不刮,那么我们输出一个感谢消息。我们希望将这个循环的主要部分包装在一个 try-except 子句中,因为我们不能期望用户总是输入有效的 URL。如果用户没有输入有效的 URL,就会出错。现在,我们不必担心错误,程序会不断地询问用户是否愿意抓取另一个网站。

注意

每当您重新启动笔记本时,您都需要再次运行导入单元格。

抓取网站

现在我们已经接受了用户的输入,我们需要抓取网站。最好将这段代码从主循环中分离出来,所以让我们将它放在自己的函数中:

 1| # request site and return top 7 most used words
 2| def scrape(site):
 3|  page = requests.get(site)
 5|  soup = BeautifulSoup(page.content, "html.parser")
 7|  print( soup.prettify( ) )    # remove after runs properly
 9| # main loop should...    ◽◽◽
14|           site = input("Enter a website...      ◽◽◽
16|           scrape(site)
17|  except:     ◽◽◽

去查查手机。在我们要求用户输入一个网站后,我们在第 16 行调用 scrape 函数,用站点变量作为参数。然后,该程序将从网站请求内容,并用 BeautifulSoup 解析它。出于测试目的,我们使用pretify()方法来输出我们得到的响应。如果仔细查看输出,您会注意到元素标记中有许多文本没有在网站上显示出来。像 scriptscomments 这样的标签包含了我们不希望包含在分析中的文本,所以我们最终需要将它们过滤掉。一旦它们被过滤掉,我们就只剩下出现在网站主页上的实际文本了。一旦单元正常运行,删除第 7 行的代码。

注意

要阅读这本书,请使用微软网址: www.microsoft.com/en-us/

删除所有文本

现在我们收到了响应,我们可以开始解析页面内容中的所有文本:

 1| # request site and return top 7 most used words
 2| def scrape(site):
 3|  page = requests.get(site)
 5|  soup = BeautifulSoup(page.content, "html.parser")
 7|  text = soup.find_all(text=True)     # will get all text within the document
 9|  print(text)     # remove after runs properly
11| # main loop should...    ◽◽◽

去查查手机。我们使用 BeautifulSoup 对象中的 find_all 方法来获取页面中包含的每一段文本。注意,这返回给我们一个列表,其中包含换行符、制表符、脚本、注释以及我们在适当的文本元素中需要的实际文本,如 h1p 等。下一步是过滤掉那些不必要的元素。一旦电池正常运行,移除线 9;这仅用于测试目的。

过滤元件

尽管我们正在从页面内容中解析文本,但是大部分文本包含在我们不想包含在分析中的元素中。让我们以脚本标签为例。脚本标签用于在网页中编写 JavaScript。如果我们在分析中包括这一点,就会导致不恰当的结果。HTML 注释也是如此,如下所示:

<!-- this is an HTML comment -->

网页上看不到 HTML 注释中的任何文本。这和 Python 注释的概念是一样的。它们是供程序员使用的,不被编译器读取。知道我们只想对页面上出现的单词进行分析,我们必须过滤掉这些不必要的元素:

 1| # filter out all elements that do not contain text that appears on site
 2| def filterTags(element):
 3|  if element.parent.name in [ "style", "script", "head", "title", "meta", "[document]" ]:
 4|          return False
 6|  if isinstance(element, Comment):
 7|          return False
 9|  return True
17|  text = soup.find_all(text=True)...     ◽◽◽
19|  visible_text = filter(filterTags, text)
21|  for text in visible_text:
22|          print(text)       # remove after runs properly
24| # main loop should...    ◽◽◽

去查查手机。在我们从第 17 行的页面内容中解析出所有文本后,我们在第 19 行过滤掉不必要的元素。filter 方法用于循环遍历我们的文本变量中的每一项,并应用 filterTags 函数来确定该项是否应该被包含。如果项目不是不应该包含的注释或元素标签,我们基本上想要返回 True 。第 3 行是我们检查文本是否在我们不想包含的元素中的地方。第 3 行列表中包含的所有字符串都是 HTML 元素。但是注释略有不同,因为它们不是元素。要知道一个条目是否是评论,我们需要使用 Beautiful Soup 的评论对象。

注意

当 BeautifulSoup 解析页面内容时,它将 HTML 识别为四个对象之一:Tag、NavigableString、Beautiful Soup 和 Comment。

在第 6 行,我们检查该项是否是注释对象的实例。如果是,我们返回 False ,这样我们就可以过滤掉它。如果该项不是注释或者它的父元素是有效元素,那么我们返回 True 。然后,我们对变量进行循环,在第 21 行输出每一项。我们现在只剩下出现在网页上的单词。你会注意到每个单词之间有很多空格,这是下一步。在电池正常运行后,移除线 22,因为它仅用于测试目的。

过滤废物

下一步是过滤掉任何转义字符(换行符,制表符);冠词,如 a、an、the 等。;以及其他我们认为无用的词。当我们对一个网站进行分析时,我们希望看到最上面的描述性文字。知道一个网站的关键词是“,并不能描述这个网站的任何信息。例如,当抓取一个新闻网站时,我们会期望看到关于当天头条新闻的关键词。为了执行这个过滤,我们需要创建另一个函数来处理删除我们所谓的“浪费”:

 1| filter article words and hidden characters
 2| def filterWaste(word):
 3|  bad_words = ( "the", "a", "in", "of", "to", "you", "\xa0", "and", "at", "on", "for", "from", "is", "that", "his",
 4|                 "are", "be", "-", "as", "&", "they", "with", "how", "was", "her", "him", "i", "has", "|" )
 6|  if word.lower( ) in bad_words:
 7|          return False
 8|  else:
 9|          return True
11| # filter out all elements that do not...    ◽◽◽
31|  for text in visible_text:     ◽◽◽
32|          words = text.replace("\n", "").replace("\t", "").split(" ")       # replace all hidden chars
34|          words = list( filter(filterWaste, words) )
36|          for word in words:
37|                  print(word, end=" ")      # remove after runs properly
39| # main loop should...    ◽◽◽

去查查手机。我们从第 31 行开始这个过程,遍历 visible_text 中的每一项,用空字符串替换所有换行符和制表符。然后,我们用我们的 filterWaste 函数对那个项目运行一个过滤器,看看我们是否需要将它从第 34 行的列表中删除。在 filterWaste 函数中,我们定义了一组我们想要过滤掉的单词或字符,称为 bad_words 。将项目转换成小写后,我们检查它是否存在于 bad_words 中;如果是,我们返回False;否则,我们返回 True 来保持它在列表中。在第 37 行,我们在执行过滤后输出每个单词。这个输出中包含的单词具有足够的描述性和信息性,可以告诉我们网站主要在谈论什么。一旦电池正常运行,移除线 37,因为这仅用于测试目的。

注意

如果愿意,您可以向 bad_words 数据集合中添加更多单词或字符。这只是为了让我们暂时过得去。有一个名为 NLTK 的库,它有一个很大的文章单词和字符列表,必要时你可以在更大的项目中使用。

统计词频

在我们过滤掉所有的浪费和元素后,我们只剩下适当的词来进行分析。下一步是计算一个给定单词出现的次数。字典将是记录每个单词的计数的最佳实践,因为我们可以使用单词作为键,使用频率作为值:

29|   visible_text = filter(filterTags, text)...     ◽◽◽
31|   word_count = { }
33|   for text in visible_text:     ◽◽◽
38|          for word in words:     ◽◽◽
39|                  if word != "":         # if it doesn't equal an empty string
40|                          if word in word_count:
41|                                  word_count[ word ] += 1
42|                          else:
43|                                  word_count[ word ] = 1
45|   print(word_count)      # remove after runs properly
53| # main loop should...    ◽◽◽

去查查手机。在第 31 行,我们创建了字典来记录字数。当我们遍历我们的单词列表中的每个单词时,我们首先检查它是否是一个空字符串,因为我们将所有转义字符都转换为空字符串,当然不想将它们包括在计数中。在第 40 行,我们检查这个单词是否已经被添加到字典中,在这种情况下,我们将把值加 1(第 41 行)。如果它还没有被添加到字典中,那么我们只需添加该单词的一个键和一个值 1 。我们在第 45 行输出结果,以查看单词及其频率。现在我们可以查看所有的单词和它们出现的时间;然而,我们想要标出最热门的单词,所以接下来我们需要对字典进行排序。电池正常运行后,移除管线 45。

按词频对词典排序

为了输出或返回前七个单词,我们需要按值对字典进行排序:

43|                                 word_count[ word ] = 1       ◽◽◽
45|   word_count = sorted(word_count.items( ), key=lambda kv: kv[1], reverse=True)   # sort on value
47|   print( word_count[ :7 ] )     # remove after runs properly
49| # main loop should...    ◽◽◽

去查查手机。为了理解这里发生的事情,首先我们需要澄清的输出是什么。 项()】变成了:

>>> d = { "word" : 1, "hello" : 2 }
>>> result = d.items( )
>>> print(result)
dict_items( [ ("word", 1), ("count", 2) ] )

结果是一个列表中的一对元组。通常,在字典上使用排序的函数会产生一个按键排序的列表;然而,当我们使用 lambda 函数通过改变 key 参数来基于值进行排序时,它实际上是接受这些元组中的每一个,并基于索引 1 进行排序,索引 1 是代表单词频率的值。记住,排序的函数返回一个列表。当我们运行第 45 行时,由于参数“ reverse=True ”,它产生了一个从最高值到最低值排序的元组列表。最后,我们通过切片输出前七个单词。电池正常运行后,移除线 47。

显示顶部的单词

现在我们得到了前七个单词,让我们输出最常用的单词来衡量:

45|   word_count = sorted...     ◽◽◽
47|   return word_count[ :7 ]
49| # main loop should...    ◽◽◽
54|            site = input("Enter a website...      ◽◽◽
56|            top_words = scrape(site)
58|            top_word = top_words[0]    # tuple of (word, count)
60|            print("The top word is: { }".format( top_word[0] ))        # don't remove
61|   except:      ◽◽◽

去查查手机。我们首先从 scrape 函数返回前七个单词,而不是打印出来。这将把元组列表返回到我们的主循环的第 56 行,并将它们保存到 top_words 变量中。之后,我们将第一个元组赋给我们的 top_word 变量,因为它代表页面上使用最频繁的单词。最后,我们通过访问包含单词和频率计数的元组的零索引来输出第 60 行的顶部单词。

绘制结果图表

我们程序中需要执行的最后一步是在柱状图中绘制结果:

 1| # graph results of top 7 words
 2| def displayResults(words, site):
 3|  count = [ item[ 1 ] for item in words ][ : : -1 ]      # reverses order
 4|  word = [ item[ 0 ] for item in words ][ : : -1 ]       # gets word out of reverses order
 6|  plt.figure( figsize=(20, 10) )     # define how large the figure appears
 8|  plt.bar(word, count)
10|  plt.title("Analyzing Top Words from: { }...".format( site[ :50 ] ), fontname="Sans Serif", fontsize=24)
11|  plt.xlabel("Words", fontsize=24)
12|  plt.ylabel("# of Appearances", fontsize=24)
13|  plt.xticks(fontname="Sans Serif", fontsize=20)
14|  plt.yticks(fontname="Sans Serif", fontsize=20)
16|  plt.show( )
77|          print("The top word is...      ◽◽◽
79|          displayResults(top_words, site)     # call to graph
80|  except:      ◽◽◽

去查查手机。我们会得到最后的输出,这是我们在这节课中编程的目标。该图将通过调用第 79 行的 displayResults 函数,在一个格式良好的条形图中显示前七个单词及其频率。我们传入顶词站点的参数,以便为图表提供数据和标题。在第 3 行和第 4 行,我们使用 comprehension 将值和单词分成各自的列表。然后我们用最后的切片来反转它们。否则,它将从最高到最低显示图形。通过将该数据传递给 bar 方法,在第 8 行绘制条形图。最后,我们添加一个标题、标签和一些样式。

最终输出

该程序现在已经完成,可以用来分析任何网站的热门词汇。请注意,一些站点可以并且确实会阻止请求,在这种情况下,将会执行异常。你可以在 Github 资源库中找到本周的所有代码,以及这个项目。

今天我们学习了如何创建一个程序来抓取用户输入的任何网站内容。重要的是看看我们如何一起使用这些数据分析库来创建一个有用的工具。现在,我们可以使用这个 web 工具来分析新闻网站并查看趋势信息。当然,这是一个简单的网页刮刀,但经过适当的修改后,可以成为一个更有用的工具。

每周总结

有许多 Python 库对数据分析很有用。在这一周里,我们讨论了一些业界最广泛使用的模块和库。本周你已经准备好开始学习更多关于分析的知识,以及如何实现这些库来进一步提高你的技能。学习了虚拟环境,你将知道如何使用 Python 库和管理你的包。使用请求模块,我们能够调用 API 并解析页面内容。这个模块允许我们的程序与其他软件通信,以改善用户体验。然而,本周最重要的图书馆之一是熊猫数据分析图书馆。几乎每个领域的数据分析师和科学家都在使用它。它赋予你同时使用 Python 和 SQL 的能力;它的效率极高,使得处理数据库和文件更加容易。这真的是所有的结束,所有的分析库。然而,没有可视化,数据分析是不完整的。使用 matplotlib,我们能够覆盖我们可以使用的各种图表,以及如何有效地展示我们的数据。重要的是要记住,没有适当可视化的数据永远不会产生高质量的结果。我们介绍的最后一个库是用 Beautiful Soup 进行 web 抓取的,这是一个重要的库,有助于理解 Python 中的其他语言,在这里我们能够解析来自页面请求的信息和文本。最后,我们将这四课中的三课结合到一个程序中,创建了一个 web 抓取分析工具。为了进一步学习这个主题,您可以使用 www.elitedatascience.com 或了解数据科学库,例如 NLTKSK-Learn

挑战问题解决方案

正如我们在周三的课程中了解到的,尝试和实现数据的 3D 可视化既困难又耗时。本周的问题是我们如何在表达一个三维图形的同时简化它。在周三课程快结束时,我们已经找到了答案,我们发现我们可以使用颜色作为第三维。这允许我们在二维空间内保存一个图形,但它是三维的。对于那些基于可视化做出决策的人来说,在试图简化数据时,记住这一点很重要。

每周挑战

要测试你的技能,请尝试以下挑战:

  1. 用户输入:正如我们在周五的项目中看到的,有许多文章单词或字符我们想要过滤掉。不幸的是,我们无法跟踪每个站点的所有信息。对于这个挑战,实现一个代码块,询问用户他们想要过滤掉哪些额外的单词或字符,以便他们可以改变显示的单词。

  2. 保存图:实现一个代码块,询问用户是否想要保存文件。如果他们这样做了,一定要问用户他们想给图像起什么名字,并用那个名字保存它。

  3. Pandas 实现:在我们的项目中,不是使用字典来跟踪来自网站的单词,而是在代码中实现 Pandas 来跟踪信息。您应该能够执行 head 或 tail 功能来查看顶部或底部最常用的单词。

  4. 保存数据:实现 Pandas 保存唯一词及其频率后,将信息输出到每个站点的 CSV 文件中。文件名应该代表网站名称,例如,“Microsoft _ frequency _ words . CSV”。

**

posted @ 2024-08-09 17:43  绝不原创的飞龙  阅读(5)  评论(0编辑  收藏  举报