密西根大学创造性编程笔记-全-

密西根大学创造性编程笔记(全)

00:遇见Barb Ericson博士 👩‍🏫

在本节课中,我们将通过Barb Ericson博士的自我介绍,了解她的学术背景、职业经历以及她为计算机科学教育所做的贡献。


让我介绍一下自己。我是信息学院的助理教授。我担任助理教授只有三年时间,但我从事相关工作的时间要长得多。

那么,我的背景是怎样的?高中时期,我的职业选择要么是成为一名兽医,因为我热爱动物;要么是成为一名律师,因为我在高中参加过辩论。这些是我当时考虑的方向。

然而,我获得了底特律市中心韦恩州立大学的奖学金,但该校没有兽医学院。因此我想,或许我可以成为一名律师。我主修了政治学,但这门专业只有去法学院深造才有用。于是我决定攻读双学位。

我应该双修什么专业呢?我在高中时上过一门使用BASIC语言的编程课,当时我们使用的是电传打字机(图中可见)。我觉得这很有趣。因此,我决定双修政治学和计算机科学。

最终,我发现自己更喜欢计算机科学。我做了几次实习,发现自己非常享受这个过程。于是我决定坚持走计算机科学的道路,并于1983年从韦恩州立大学获得了理学学士学位。

毕业后,我首先在通用汽车研究实验室工作。这是一张当时的照片,我们最初在计算机房里工作。后来,我的工作是协助支持人们使用3D图形软件包。

之后,我转到贝尔通信研究所,从事了大量数据库方面的工作。他们资助我在密歇根大学攻读硕士学位。接着,我做了许多关于双音多频信号输入和语音合成输出的工作,以帮助电话维修人员。为此,我曾与维修人员一同乘车,观察他们如何使用我们的软件。

我后来又回到了通用汽车研究实验室,并开发了软件,帮助他们从油泥模型中创建3D数学模型。汽车设计师们先用油泥雕塑出汽车模型。我的工作涉及用户界面设计和3D图形。

接下来,我去了造纸科学与技术研究所,同样从事3D图形和流体动力学方面的工作。之后在克拉克亚特兰大大学,我们参与了一个有趣的项目,旨在利用人工智能帮助医生在X光片中检测肿块等异常。

这引导我后来在NCR公司从事基于案例的推理研究,针对中风和脑肿瘤,尝试利用人工智能辅助医生进行诊断。

随后,我在佐治亚理工学院工作了很长时间,担任讲师和计算推广主任,并最终在那里获得了博士学位。

我从事的工作之一是对教师进行推广培训。我举办了大量研讨会,帮助教师学习如何教授计算科学。我为学生组织了许多夏令营,并协助修订了佐治亚州的课程。

特别值得一提的是,我与我的丈夫Mark Guzdial博士合作编写了关于“媒体计算”的书籍,这让我有了一些知名度。这是一种通过编写程序来操作媒体(如图片中的像素、声音中的样本、视频中的帧)来教授计算的方法。

当然,作为推广人员,我还与学生,尤其是年轻学生,做了许多事情。我们举办了许多计算夏令营、与青年服务组织的研讨会、小学周末项目。我们还运营了一个名为“Rise Up for CS”或“Sisters Rise Up for CS”的项目,旨在帮助来自代表性不足群体的学生在高中AP计算机科学课程中取得成功。

近年来,在获得博士学位后,我一直专注于交互式电子书的研究,你们将在本课程中使用到它们。这些电子书基于教育心理学的原理设计。

核心理念是:通过“示例+练习”的模式,学习效果会更好。因此,我们设计了这些带有子目标标签的交互式示例和练习题。子目标标签是一种试图抽象出更高层次逻辑的注释。

我们提供了大量不同类型、能即时反馈的练习题,这对学习很有帮助。我特别研究过的一种题型是“代码排序题”,我们会给出解决一个问题的正确代码块,但顺序被打乱,你需要将它们拖拽到正确的顺序。

我一直在创建多本交互式电子书,你们将在本课程中使用其中一本。以上就是关于我的一些介绍。

那么,为什么不在论坛上也分享一下关于你自己的信息呢?


本节课中,我们一起了解了Barb Ericson博士从学生时代到成为计算机科学教育者的丰富历程,包括她的学术选择、多样的职业路径以及在教育技术,特别是“媒体计算”教学法和交互式电子书开发方面所做的突出贡献。

01:本课程的独特之处 🎨

在本节课中,我们将要了解这门课程与其他在线课程的不同之处,以及它如何通过实践、协作和创意项目来帮助你学习编程。

课程概述

欢迎来到这门课程。这门课程与许多在线课程不同,它不会包含大量讲座,而是更侧重于动手实践学习。

实践与协作学习

上一节我们介绍了课程的实践导向,本节中我们来看看协作的重要性。我们鼓励你与他人合作,以小组形式学习,因为这将提升你的学习效果和动力。人类是社会性生物,我们可以通过观察他人和与他人讨论来学习。

互动练习与自适应学习

课程提供了大量互动练习。如果你需要复习,可以回顾前面的章节或完成更多练习题。以下是练习的一些特点:

  • 部分练习题是自适应的,这意味着它们会帮助你逐步找到正确的解决方案。

创意项目与开放性任务

接下来,我们将探讨课程的核心——创意项目。与许多编程入门课程中常见的练习(如计算销售税或转换摄氏与华氏温度)不同,你将进行绘画创作或编写个性化故事。

以下是课程任务的特点:

  • 课程包含开放性编辑创意性任务
  • 这些任务旨在更有趣,更能激发你的学习动力。

总结

本节课中,我们一起学习了这门课程的独特之处:它强调动手实践、鼓励协作学习、提供自适应互动练习,并最终通过开放性的创意项目来应用所学知识,使学习过程更加有趣和富有动力。

03:Python是什么 🐍

在本节课中,我们将要学习Python编程语言的基本概念,了解它的起源、特点以及为什么它在当今编程世界中如此流行。

概述

大家好,我是Barbar Erickson,信息学院的助理教授。我很高兴能带领大家开启学习Python的旅程。那么,Python究竟是什么呢?

Python的起源与命名

首先,Python并不是一条巨蟒。它的名字来源于20世纪70年代BBC的喜剧系列节目《Monty Python's Flying Circus》(巨蟒剧团之飞翔的马戏团)。如果你还没看过,可以去了解一下。😊

Python的特点与流行原因

上一节我们了解了Python名字的趣味由来,本节中我们来看看它作为一门编程语言的特点。

Python是一门非常流行的编程语言,许多公司都在使用它。它的流行部分归功于其强大的功能。

以下是Python流行的几个关键原因:

  • 功能强大且库丰富:拥有大量库,可以简化开发工作。
  • 应用领域广泛:被广泛应用于数据科学和机器学习等领域。
  • 易于学习:相比许多其他文本编程语言,Python学起来稍微容易一些。
  • 支持面向对象编程:这是当今最常见的程序开发方式。这意味着Python支持(对事物的分类)和对象(在程序中实际执行工作的实体)。面向对象编程的核心概念可以用一个简单的类定义来理解:
    class Dog: # 定义一个“狗”类
        def __init__(self, name):
            self.name = name # 属性:名字
    
        def bark(self): # 方法:叫
            print(f"{self.name} says: Woof!")
    

总结

本节课中我们一起学习了Python是什么。我们了解到它是一门以喜剧节目命名的、功能强大且易于学习的编程语言,因其丰富的库和对面向对象编程的支持,在数据科学等多个现代技术领域备受青睐。

现在,让我们开始Python的学习之旅吧。😊

04:为何分组协作 👥

在本节课中,我们将探讨在编程学习过程中进行分组协作的重要性。我们将了解团队合作如何提升学习效果、增强动力,并培养对职业生涯至关重要的软技能。


上一节我们介绍了课程的整体框架,本节中我们来看看为何要特别强调分组协作。

研究表明,在小组中工作既能提高学习动力,也能改善学习效果。部分原因在于,当你需要向他人解释自己的想法时,这个过程本身就能帮助你更好地理解概念。

通常,与他人合作也更有趣,并且能帮助你避免陷入困境,尤其是在编程时。总的来说,研究表明,多样化的团队表现优于个人。团队通常能解决个人无法解决的问题。如果你曾参与过密室逃脱游戏,你可能会发现,在他人帮助下成功逃脱的可能性远大于独自一人。

同样重要的是,团队协作能力、与他人沟通的能力、在团队中工作、自我管理及管理他人的能力,对雇主而言非常重要。因此,掌握这类技能在工作场所也极具价值。这就是我认为你应该进行分组协作的原因。


以下是分组协作的几个核心优势:

  • 提升理解与学习效果:向他人解释想法的过程能深化自己的理解。
  • 增加动力与乐趣:与他人合作通常更有趣,能维持学习热情。
  • 突破困境与解决问题:团队能提供不同视角,帮助解决个人难以攻克的问题,其能力模式可表示为:团队解决问题的能力 > 个体解决问题的能力之和
  • 培养关键职场技能:沟通、协作与管理能力是雇主高度重视的素质。

本节课中我们一起学习了分组协作在编程学习中的多重益处。它不仅能让学习过程更高效、更有趣,还能帮助我们培养未来职业生涯中不可或缺的团队合作与沟通技能。在接下来的实践中,请积极拥抱团队合作。

05:POGIL角色解析 🧩

在本节课中,我们将学习一种名为POGIL的小组协作方法。我们将重点解析POGIL活动中的四个核心角色,了解每个角色的职责,以便在团队合作中更高效地学习和解决问题。


什么是POGIL? 🤔

POGIL是“过程导向的引导式探究学习”的缩写。在这种学习模式中,学生以小组形式合作,但关键在于,小组中的每个成员都被赋予一个明确的、定义好的角色。

上一节我们介绍了POGIL的基本概念,本节中我们来看看这些具体的角色分别是什么,以及它们如何共同推动团队前进。

POGIL的四个核心角色

在典型的POGIL活动中,通常包含以下四个角色:

以下是四个角色的具体职责:

  1. 管理者

    • 负责为小组成员分配任务。
    • 帮助确保整个团队按计划、按时完成任务。
    • 如果团队在某个问题上卡住,管理者应鼓励大家继续前进。
  2. 记录员

    • 负责写下讨论中的要点。
    • 记录团队所学到的知识或达成的结论。
  3. 汇报者

    • 当活动说明中有不清晰的地方时,负责向指导者(如老师或助教)提问。
    • 负责向全班或指导者呈现小组的最终成果或发现。
  4. 反思者

    • 确保小组内所有成员的声音都被听到,避免只有一两个人主导讨论。
    • 可以观察并反馈团队的动态,例如团队合作是否顺畅,有哪些方面可以改进。


本节课中,我们一起学习了POGIL协作学习法中的四个关键角色:管理者记录员汇报者反思者。每个角色都有其独特的职责,共同保障团队探究活动能够有序、高效且包容地进行。理解并实践这些角色,将极大地提升你在团队项目中的协作效率和贡献价值。

06:电子书中编写代码的方法 📖

在本节课中,我们将学习如何在电子书中直接编写、运行和调试Python代码。我们将了解电子书提供的交互式编程环境,包括编写函数、测试代码、查看历史版本以及使用辅助工具。


电子书的交互式编程功能

使用电子书的一个优点是,你可以直接在书中编写代码。让我们看看具体如何操作。

以下是一个示例:创建一个名为 area_of_rectangle 的函数,它接收宽度和长度作为参数,并返回矩形的面积。例如,area_of_rectangle(5, 4) 应返回 20,即 5 * 4 的结果。

编写并测试代码

首先,我们尝试编写函数。初始想法是计算面积,但可能会忘记返回结果。

def area_of_rectangle(width, length):
    area = width * length

运行上述代码会发现测试未通过,因为函数没有返回任何值。我们需要添加 return 语句。

def area_of_rectangle(width, length):
    area = width * length
    return area

现在运行代码,测试用例应该会通过。你可以在代码透镜中查看结果,如果正在团队中工作,还可以与他人分享这段代码。

使用辅助工具进行调试

电子书界面右上角有一个铅笔图标,点击它可以打开一个窗口,允许你直接输入并运行Python代码。如果你不确定乘法操作是否正确,可以在此进行测试。

print(5 * 4)

测试完成后,可以关闭该窗口。这个工具对于快速验证想法或调试非常有帮助。

查看代码历史版本

另一个重要功能是代码历史记录。请确保在使用电子书时已登录账户。登录后,你会看到一个滑块控件,允许你回溯查看代码的不同版本。

通过滑动滑块,你可以看到代码最初的样子、第二次修改后的状态,以及后续的所有更改。这样,你可以轻松追踪代码的演变过程,回顾之前的解决方案。


本节课中,我们一起学习了如何在电子书中直接编写和运行Python代码。我们了解了如何创建函数、使用内置工具进行测试和调试,以及如何利用历史记录功能回顾代码的修改过程。这些功能使得学习和编程过程更加直观和高效。

07:重载代码历史记录 🔄

在本节课中,我们将学习如何在编程环境中找回之前保存的代码。如果你曾经离开后又返回,可能会发现代码编辑器是空的。但只要你登录过,你的代码其实是被保存下来的。本节将指导你如何通过“加载历史记录”功能恢复你的工作成果。


上一节我们介绍了编程环境的基本操作,本节中我们来看看如何管理你的代码历史。

如果你在编程时登录了账户,然后离开了书本或页面,当你再次返回时,可能会惊讶地发现代码编辑器似乎是空的。请不要担心,你的代码实际上已经被自动保存了。关键在于你需要手动加载这些保存的历史记录。

以下是找回代码的具体步骤:

  1. 首先,确保你已登录到之前的同一个账户。
  2. 在代码编辑器区域附近,寻找一个名为 加载历史记录 的按钮。
  3. 点击这个按钮。
  4. 系统将加载你上次运行代码时的所有历史版本。

成功加载后,你将看到上次编写和运行的完整代码。这个功能更强大之处在于,你还可以查看更早的历史版本,追溯整个编程过程的变化。

所以,请记住这个核心要点:只要你登录了账户,你的所有操作都会被保存。 但每次重新进入时,务必记得点击 加载历史记录 按钮来恢复你的工作现场。


本节课中我们一起学习了“加载历史记录”功能的重要性与使用方法。我们明白了在登录状态下,代码会被自动保存,而通过点击特定的按钮,我们可以轻松找回任何历史版本的代码,这能有效防止工作丢失并方便进行版本回溯。

08:欢迎进入第一周 🎉

在本节课中,我们将要学习第一周的整体安排,了解本周的学习目标,并熟悉课程所需的核心工具与Python基础知识。

概述

本周的目标是让大家熟悉电子书平台,掌握Python编程的基础知识,并运用这些知识创建一个个性化的故事。

熟悉电子书平台

首先,你需要注册并使用课程指定的电子书。登录后,你在书中编写的所有代码和答案都会被自动保存。这样,你可以随时回顾自己的学习进度。

接下来,我们将熟悉书中提供的多种练习形式。以下是书中包含的主要练习类型:

  • 填空题:在代码片段中填入缺失的部分。
  • 拖放题:通过拖拽代码块来构建正确的程序。
  • 代码排序题:将打乱的代码行重新排列成正确的顺序。

此外,你还会接触到代码可视化工具。这个工具允许你逐行执行代码,直观地观察每一步中变量的变化和程序的执行流程,这对于理解代码逻辑和调试非常有帮助。

学习Python基础

上一节我们介绍了学习工具,本节中我们来看看本周要学习的核心Python知识。这些是构建更复杂程序的基础。

你将学习以下核心概念:

  • 打印输出:使用 print() 函数在屏幕上显示信息。
    print("Hello, World!")
    
  • 数学运算符:进行基本的数学计算,如加(+)、减(-)、乘(*)、除(/)。
    result = 5 + 3 * 2  # result 的值为 11
    
  • 变量:创建变量来存储数据,以便在程序中重复使用。
    my_name = "Alex"
    age = 25
    
  • 字符串:创建和处理由字符组成的文本序列。
    greeting = "Welcome to Creative Coding!"
    
  • 字符串切片:从字符串中获取一部分内容(子串)。
    word = "Python"
    sub = word[0:2]  # sub 的值为 "Py"
    
  • 自定义函数:创建你自己的函数来封装可重复使用的代码块。
    def say_hello(name):
        print(f"Hello, {name}!")
    
  • 内置函数:使用Python语言中已经提供好的常用函数,例如 len() 用于获取长度,input() 用于获取用户输入。

实践项目:创建个性化故事

掌握了以上基础知识后,你将综合运用它们来完成本周的实践项目:创建一个个性化的故事。你将编写一个程序,可能通过询问用户的名字、喜好等信息,然后生成一个包含这些信息的独特故事。

总结

本节课中我们一起学习了第一周的学习路线。我们明确了本周的两个主要任务:熟悉电子书平台及其各种交互式练习工具,以及学习Python编程的基础语法,包括打印、运算、变量、字符串和函数。最后,我们将运用这些知识完成一个有趣的编程项目。学习过程中请大胆尝试,不必害怕犯错,因为调试和解决问题是学习编程的重要环节。祝你学习愉快!😊

09:混合代码帕森斯问题 🧩

在本节课中,我们将学习一种名为“帕森斯问题”或“混合代码问题”的编程练习方法。这种方法旨在通过将正确的代码打乱成块,让你通过拖拽排序来构建解决方案,从而降低编程学习的难度。


什么是帕森斯问题? 🤔

帕森斯问题是一种编程练习形式。我们为你提供解决某个问题所需的全部正确代码,但会将这些代码分解成多个块并打乱顺序。你的任务是将左侧的代码块拖拽到右侧,并按正确的逻辑顺序排列,从而构建出完整的解决方案。

如何使用帕森斯问题界面? 🖱️

以下是使用帕森斯问题界面的基本步骤:

  1. 查看左侧被打乱的代码块。
  2. 将你认为正确的代码块拖拽到右侧的工作区。
  3. 注意,有时会存在成对的代码块(例如“1A”和“1B”),其中一个是正确的,另一个是错误的。你只需选择其中一个放入你的解决方案。
  4. 排列好所有代码块后,点击“检查”按钮验证你的方案。

遇到困难怎么办?求助按钮来帮忙! 🆘

如果你在解题时遇到困难,界面底部提供了一个“求助”按钮。当你提交了至少三个错误的解决方案后,就可以点击这个按钮寻求帮助。

求助功能主要通过两种方式简化问题:

  • 移除干扰项:它会将解决方案中不需要的错误代码块移回左侧并变为灰色。
  • 合并代码块:它会将两个本应在一起的代码块合并成一个。

系统会持续提供帮助,直到你的问题被简化到只剩下三个代码块。此时,它会提示你应该能够独立完成排序了。

让我们来看一个实例 👀

这是一个帕森斯问题的界面。左侧是混合的代码块,其中包含一些正确和错误配对的选项。

我首先选择一系列我认为正确的代码块拖到右侧,并记得在成对选项中只选一个。当我构建出一个完整的方案后,我点击“检查”按钮。

如果顺序有误,系统会高亮显示错误。我可以尝试调整顺序并再次检查。在提交了三次错误答案后,系统提示我可以使用“求助”按钮。

点击“求助”按钮后,首先一个错误的代码块被移除并动画式地回到了左侧。有趣的是,它甚至与正确的代码块成对出现,这本身就是一个提示:“嘿,应该用旁边这个才对!”。

我可以选择那个正确的代码块。如果还有其他错误代码块,继续点击“求助”按钮也会将它们移除。

之后,求助功能开始合并代码块:一个块移动并与另一个合并,重新绘制成一个新块。每次点击“求助”,它都会继续合并块,直到最终只剩下三个代码块。这时,系统会鼓励我:“你应该能自己排列好这三个块了。”


总结 📝

本节课我们一起学习了帕森斯问题(混合代码问题)这种编程练习方法。我们了解了它的基本形式:通过拖拽排序打乱的代码块来构建解决方案。更重要的是,我们掌握了如何使用“求助”功能在遇到困难时简化问题,例如移除干扰项或合并代码块。请记住,当你被这类问题卡住时,善用“求助”按钮是推进学习的好方法。

10:函数入门指南 🧩

在本节课中,我们将学习Python中函数的基础知识。函数是命名一组语句的方式,当你调用该函数时,这些语句就会被执行。我们将通过一个示例来了解函数的定义、调用以及参数传递。

函数定义与结构

上一节我们了解了函数的基本概念,本节中我们来看看函数的具体结构。一个函数定义包含几个关键部分。

以下是一个包含两个函数定义的示例:

def calc_per(a, b=4):
    # 函数体语句
    result = (a + b) * 2
    return result

def main():
    # 函数体语句
    val1 = calc_per(3)
    val2 = calc_per(a=8, b=10)
    print(val1, val2)

# 调用main函数
main()
  • 函数头:定义的第一行,以冒号结束。例如 def calc_per(a, b=4):
  • 函数名:跟在 def 关键字之后、参数列表之前的部分。def 关键字表明这是一个函数定义。
  • 参数列表:括号内的一组名称,用于接收传递给函数的值。可以为其指定默认值,如 b=4

函数调用与参数传递

理解了如何定义函数后,我们来看看如何调用它们。调用函数意味着执行其内部的语句。

以下是函数调用的方式:

  • 使用函数名,后跟参数列表。例如 calc_per(3)
  • 当调用带有默认参数的函数时,可以只提供部分参数。例如 calc_per(3) 中,b 将使用默认值 4
  • 也可以通过命名参数的方式明确指定每个参数的值。例如 calc_per(a=8, b=10)

在Python中,调用函数本身也是一个表达式。注意,函数可以不接收任何参数,也可以使用 return 关键字返回一个值。函数也可以不返回任何内容,例如示例中的 main 函数。

总结

本节课中我们一起学习了Python函数的基础知识。我们了解了如何定义函数,包括函数头、函数名和参数列表的写法。我们也探讨了如何调用函数,以及如何通过位置或命名的方式传递参数,包括使用默认参数。最后,我们知道了函数可以选择是否通过 return 语句返回一个值。掌握函数是组织代码和实现复用的关键一步。

11:CodeLens代码可视化 🧐

在本节课中,我们将学习一个名为CodeLens的强大工具,它可以帮助我们可视化代码的执行过程,从而更好地理解程序内部发生了什么。

概述

理解代码的执行流程是编程学习中的关键一步。有时,仅仅阅读代码可能不足以让我们清楚地知道变量如何变化、函数如何被调用。为了解决这个问题,我们可以使用一种名为CodeLens的代码可视化工具。它本质上是一个集成在电子书中的Python Tutor版本,允许我们逐步执行代码,并清晰地看到每一步的执行结果和变量状态。

CodeLens是什么?

上一节我们提到了理解代码的重要性,本节中我们来看看具体的工具。CodeLens是一个代码可视化功能。当你点击电子书中许多活动代码旁的CodeLens按钮时,就会在代码下方打开一个可视化界面。这个界面会逐步展示代码的执行过程,并显示每个变量的当前值,就像给代码的执行过程拍了一张张“快照”。

如何使用CodeLens?

让我们通过一个具体的例子来了解如何使用CodeLens。假设我们有以下一段简单的代码:

# 这是一个测试函数
def test(a, b=2):
    return a * b

def main():
    print("hello")
    result = test(3)
    print(result)

main()

当我们点击这段代码旁的“Show Code Lens”按钮后,可视化工具会出现在代码下方。

逐步执行示例

以下是使用CodeLens逐步执行上述代码时,我们会观察到的关键步骤:

  1. 准备执行:工具打开后,一条红线会指示即将执行的代码行。它会自动跳过注释行,因为计算机不执行注释。

  2. 定义函数:点击“Next”按钮,工具会执行第2行,定义 test 函数。然后再次点击,它会跳到下一个定义,即定义 main 函数。

  3. 调用函数:定义完函数后,红线会指向第16行 main() 这个函数调用。点击“Next”,我们将进入 main 函数内部开始执行。

  4. 执行主函数

    • 首先执行 print("hello"),我们会在输出区域看到打印出的“hello”。
    • 接着,要执行 result = test(3),这意味着我们需要调用 test 函数。
  5. 进入test函数:当调用 test(3) 时,工具会跳转到 test 函数。此时,我们可以清晰地看到参数 a 的值是3。同时,由于函数定义中为参数 b 设置了默认值 b=2,所以即使调用时只传了一个参数,b 的值也被自动赋为2。

通过这个逐步过程,我们就能直观地看到 test(3) 是如何计算出结果6,并最终被打印出来的。

总结

本节课我们一起学习了CodeLens代码可视化工具。它是一个极其有用的调试和理解工具,允许你像放电影一样一步步查看代码的执行过程,并实时观察每个变量的值。当你对某段代码的运行逻辑感到困惑时,不妨打开CodeLens,让它带你清晰地走一遍代码的旅程。

12:整除、浮除与取模运算

📘 概述

在本节课中,我们将学习Python中三种基本的除法运算:浮点除法、整数除法和取模运算。理解这些运算的区别对于编写正确的程序至关重要。

🔍 代码示例分析

我们将通过一个具体的代码示例来逐步分析这三种运算的行为。首先,我们定义一个函数并执行它。

def test(a, b=2):
    print("Welcome!")
    print(a / b)   # 浮点除法
    print(a // b)  # 整数除法
    print(a % b)   # 取模运算

def main():
    print("Hello")
    test(5)

main()

🧮 三种除法运算详解

以下是三种除法运算的核心概念和区别。

1. 浮点除法(Single Slash /

浮点除法使用单个斜杠 /。它执行标准的除法运算,结果总是一个浮点数(即小数),即使两个数可以整除。

公式result = a / b

在上面的例子中,当 a = 5b = 2 时:

print(5 / 2)  # 输出:2.5

运算 5 / 2 的结果是 2.5

2. 整数除法(Double Slash //

整数除法使用双斜杠 //。它执行除法运算,但会丢弃结果的小数部分(即向下取整),只返回整数商。

公式result = a // b (商向下取整)

在上面的例子中:

print(5 // 2)  # 输出:2

运算 5 // 2 的结果是 22 * 2 = 4,余数 1 被丢弃。

3. 取模运算(Percent Sign %

取模运算使用百分号 %。它不返回商,而是返回两个数相除后的余数。

公式remainder = a % b

在上面的例子中:

print(5 % 2)  # 输出:1

运算 5 % 2 的结果是 1。因为 2 乘以 2 等于 45 减去 41

💡 编程中的应用场景

理解这些运算的区别在编程中非常实用。以下是它们的一些常见用途:

  • 判断奇偶性:使用取模运算 % 可以轻松判断一个数是奇数还是偶数。如果一个数 n % 2 的结果是 0,那么它是偶数;如果是 1,那么它是奇数。
  • 循环与分组:取模运算常用于创建循环模式或将项目分组。例如,在列表中每隔N个元素执行一次操作。
  • 数据分割:整数除法可以用于将总数分割成等份,例如计算一箱能装多少件物品。

📝 总结

本节课我们一起学习了Python中三种关键的算术运算:

  1. 浮点除法 /:执行标准除法,结果总是浮点数。
  2. 整数除法 //:执行除法并丢弃小数部分,只返回整数商。
  3. 取模运算 %:返回两个数相除后的余数。

记住:单斜杠是浮除,双斜杠是整除,百分号是求余。掌握这些运算将帮助你更有效地处理数值计算和逻辑判断。

13:字符串综合解析

概述

在本节课中,我们将要学习Python中字符串的基本概念和操作。字符串是编程中处理文本数据的基础,理解其工作原理对于后续的编程学习至关重要。


字符串基础

计算机可以处理多种不同类型的数据,它们当然可以处理数字,也可以处理字符或字符串。字符串就是一个字符序列,并且在Python或大多数编程语言中,会保持这些字符的顺序。在Python中,你可以用成对的单引号、双引号,甚至三引号来包裹字符串。

请看以下代码。你认为当我打印它时会发生什么?

fruit = 'banana'

这里,单引号包裹着字符串“banana”。接下来,我将打印这个变量fruit的类型。

print(type(fruit))

然后,我将执行letter = fruit[1],并打印这个letter变量。

letter = fruit[1]
print(letter)

你认为会打印出哪个字母?这段代码首先会打印出<class 'str'>,其中“str”代表字符串。然后打印出的字母是“a”。

这是因为字符串的索引从0开始,而不是1。所以在这个字符串“banana”中,索引1对应的字符是“a”。

如果我将索引改为0并重新运行,现在会看到字母“B”。

letter = fruit[0]
print(letter)  # 输出:B

那么,如果我尝试索引6会发生什么?因为这个字符串有6个字符。

letter = fruit[6]  # 这将导致错误

实际上,这样做会导致“索引超出范围”的问题,因为最后一个有效的索引是5,即字符串长度减1。


字符串索引规则

上一节我们介绍了字符串的基本访问,本节中我们来看看字符串索引的具体规则。

记住,在处理字符串时,字符串的第一个元素位于索引0,最后一个元素位于索引字符串长度 - 1

以下是关于字符串索引需要记住的几个关键点:

  • 索引从0开始计数。
  • 最后一个字符的索引是 len(字符串) - 1
  • 尝试访问不存在的索引(如本例中的索引6)会导致 IndexError

总结

本节课中我们一起学习了Python字符串的基础知识。我们了解到字符串是字符的有序序列,可以使用单引号、双引号或三引号定义。最重要的是,我们掌握了字符串的索引机制,即索引从0开始,访问元素时必须确保索引值在有效范围内(0 到 长度-1)。理解这些概念是进行更复杂文本处理的第一步。

14:字符串切片技术 🧩

在本节课中,我们将要学习Python中一个非常实用的技术——字符串切片。通过切片,我们可以轻松地从一个字符串中获取指定的部分,而无需修改原始字符串。

概述

字符串切片允许我们获取字符串的特定部分或子串。在Python中,这被称为“切片”。请记住,切片操作不会改变原始字符串,它总是返回一个新的字符串。

切片基础

上一节我们介绍了切片的基本概念,本节中我们来看看切片的具体语法和用法。

切片的基本语法是 string[start:end]。其中:

  • start 是切片开始的索引(包含该索引处的字符)。
  • end 是切片结束的索引(不包含该索引处的字符)。

如果省略 start,则默认从索引0开始。如果省略 end,则默认到字符串末尾。

以下是几个关键点:

  • 字符串索引从0开始。
  • 切片 [0:2] 会获取索引0和1的字符,即前两个字符。
  • 可以使用负数索引从字符串末尾开始计数,例如 -1 表示最后一个字符。
  • 切片 [-2:] 会获取从倒数第二个字符开始到字符串末尾的所有字符。

代码示例解析

让我们通过一个具体的代码示例来理解切片是如何工作的。

def get_short_name(first, last):
    return first[0:2] + last[-2:]

def main():
    first_name = "Christopher"
    last_name = "Columbus"
    short_name = get_short_name(first_name, last_name)
    print(short_name)  # 输出会是什么?

if __name__ == "__main__":
    main()

在这段代码中,get_short_name 函数接收名和姓,然后通过切片组合成一个短名称。

  • first[0:2] 获取 first 字符串的前两个字符(索引0和1)。对于 "Christopher",结果是 "Ch"
  • last[-2:] 获取 last 字符串的最后两个字符。对于 "Columbus",结果是 "us"
  • 最后,将这两个切片结果用 + 运算符连接起来,得到 "Ch" + "us" = "Chus"

因此,运行这段代码将打印出:Chus

总结

本节课中我们一起学习了Python的字符串切片技术。我们了解到切片操作 string[start:end] 可以高效地提取字符串的子串,并且它总是返回一个新的字符串,不会修改原字符串。通过使用正数索引和负数索引,我们可以灵活地从字符串的开头或末尾进行切片。这是一个在数据处理和字符串操作中非常基础且强大的工具。

15:字符清理方法 🧹

在本节课中,我们将学习Python中用于清理字符串的strip方法。这些方法能帮助我们移除字符串开头或结尾处不需要的字符,例如多余的空格、制表符或换行符。

概述

处理字符串时,我们经常会遇到字符串前后带有额外空格或其他空白字符的情况。这些多余字符会影响字符串比较、显示或进一步处理。Python提供了strip()rstrip()lstrip()三个方法来处理这类问题。本节我们将通过实例详细讲解它们的功能与区别。

核心方法解析

以下是三种字符串清理方法的基本说明:

  • strip(): 移除字符串开头和结尾的所有空白字符(包括空格、换行符\n、制表符\t等)。
  • rstrip(): 仅移除字符串结尾(右侧) 的空白字符。
  • lstrip(): 仅移除字符串开头(左侧) 的空白字符。

代码示例与逐步分析

为了直观地理解这些方法,我们通过一个具体的代码示例来逐步分析。假设我们有一个字符串 " help! ",其前后各有两个空格。

def test(input_string):
    print(f"原始字符串长度: {len(input_string)}")
    print(f"原始字符串: '{input_string}'")

    stripped = input_string.strip()
    print(f"\n应用 strip() 后: '{stripped}'")
    print(f"strip() 后长度: {len(stripped)}")

    r_stripped = input_string.rstrip()
    print(f"\n应用 rstrip() 后: '{r_stripped}'")
    print(f"rstrip() 后长度: {len(r_stripped)}")

    l_stripped = input_string.lstrip()
    print(f"\n应用 lstrip() 后: '{l_stripped}'")
    print(f"lstrip() 后长度: {len(l_stripped)}")

def main():
    my_string = "  help!  "
    test(my_string)

if __name__ == "__main__":
    main()

现在,让我们模拟代码执行过程,观察每一步的变化:

  1. 首先,定义test函数和main函数,然后调用main()
  2. main函数中,变量my_string被赋值为 " help! "(注意前后的空格)。
  3. 调用test函数,并将my_string作为参数传入。
  4. test函数内部,首先打印原始字符串及其长度。原始字符串 " help! " 的长度是 9(2个前空格 + 5个字符help! + 2个后空格)。
  5. 接着,对字符串应用strip()方法。该方法会移除字符串开头和结尾的所有空白字符。因此,结果变为 "help!",其长度变为 5
  6. 然后,对原始字符串应用rstrip()方法。该方法仅移除字符串结尾(右侧) 的空白字符。因此,结果变为 " help!",保留了开头的两个空格,其长度为 7
  7. 最后,对原始字符串应用lstrip()方法。该方法仅移除字符串开头(左侧) 的空白字符。因此,结果变为 "help! ",保留了结尾的两个空格,其长度同样为 7

总结

本节课我们一起学习了Python中用于清理字符串边缘空白字符的三个方法。strip()方法最为常用,它能一次性清理字符串两端的空白。而rstrip()lstrip()则提供了更精细的控制,分别用于单独清理字符串右侧或左侧的空白。掌握这些方法能让你在处理用户输入、文件读取或数据清洗时,更高效地确保字符串数据的整洁性。

16:类型转换实践 🧮

在本节课中,我们将要学习编程中一个非常基础且重要的概念:类型转换。计算机会以不同的方式存储不同类型的数据,例如数字和字符。为了让程序正确运行,我们有时需要将数据从一种类型转换为另一种类型。

数据类型与转换的必要性

正如我们之前讨论过的,计算机中有不同类型的数据,它们以不同的方式被表示。例如,数字在计算机内部以一种方式存储,而像字母A、B、C这样的字符则以另一种方式存储。计算机需要明确知道我们正在处理的是字符串还是数字。

当我们将字符串拼接在一起时,比如在打印变量值时,必须牢记一个原则:不能将两种不同类型的值直接组合在一起。如果我有一个字符串,想加上一个数字,就必须先将数字转换为字符串。反之,如果我想对字符串进行数学计算,也必须先将它转换为数字。

代码示例解析

让我们通过一个具体的代码示例来理解这个过程。以下是示例代码的逐步解析:

import datetime

def get_name():
    print("Hey input, what's your first name?")
    first_name = input()
    print("Hello " + first_name)
    print("What's your age?")
    age = input()
    print("What's today's date?")
    today = datetime.datetime.now()
    age_int = int(age)
    birth_year = today.year - age_int
    print("You were born in either " + str(birth_year) + " or " + str(birth_year - 1))

以下是代码执行流程的详细步骤:

  1. 获取姓名:程序首先打印提示,要求用户输入名字。用户输入(例如“Barb”)后,程序会拼接字符串并问候用户。
  2. 获取年龄:接着,程序询问用户的年龄。用户输入(例如“59”)后,这个值会被存储在变量 age 中。请注意,此时 age 是一个字符串,因为它来自 input() 函数。
  3. 类型转换与计算:程序获取当前日期。为了计算出生年份,它需要将字符串 age 转换为整数。这是通过 int(age) 函数完成的。转换后,age_int 的值是整数 59
  4. 执行计算:程序用当前年份减去整数年龄,得到出生年份(例如1962)。
  5. 结果输出:最后,程序需要将计算出的整数年份(birth_year)与字符串拼接起来输出。因此,它再次使用 str(birth_year) 将整数转换回字符串。

核心要点总结

本节课中我们一起学习了类型转换的核心概念和实践方法。记住以下两个关键点:

  • 输入默认为字符串:使用 input() 函数获取的任何用户输入,初始类型都是字符串。
  • 转换函数
    • 要将字符串转换为整数以进行数学计算,请使用 int() 函数。
    • 要将数字(或其他类型)转换为字符串以进行拼接或输出,请使用 str() 函数。

理解并正确应用类型转换,是确保程序逻辑正确、避免常见错误的基础。

17:条件语句与列表入门 🚀

在本节课中,我们将要学习第二周的核心内容,重点掌握条件语句列表这两个编程基础概念。通过它们,你可以让程序根据不同的情况做出决策,并有序地管理多个数据项。

条件语句:让程序学会选择 🤔

上一节我们介绍了本周的学习目标,本节中我们来看看条件语句。条件语句允许你根据某个条件是还是来决定是否执行特定的代码块。其核心逻辑可以用一个简单的公式来描述:

如果 条件 为真,则执行 代码块

在Python中,最基本的条件语句使用 if 关键字。以下是其基本结构:

if 条件:
    # 条件为真时执行的代码

你将通过条件语句完成以下学习任务:

  • 使用简单的条件语句:掌握 if 语句的基本用法。
  • 测试代码中的所有可能结果:确保程序在各种情况下都能正确运行。
  • 使用复杂的条件语句:学习 elifelse 来缩短代码,处理多种情况。
  • 预测条件语句的输出:在运行代码前,推断程序会输出什么结果。
  • 结合条件语句与列表编写代码:将决策逻辑应用到数据集合中。

列表:有序的数据集合 📋

学会了如何做决策,我们还需要一种结构来有效地存储和管理数据。这就是列表。列表可以按顺序存放多个项目(元素)。

在Python中,列表用方括号 [] 表示,元素之间用逗号分隔。例如,一个存储水果的列表可以这样定义:

fruits = [“苹果”, “香蕉”, “橙子”]

你将通过列表完成以下学习任务:

  • 使用索引修改和处理列表:学习如何通过位置(索引)访问和更改列表中的元素。索引从0开始。
  • 使用常见的列表方法:例如,使用 append() 在列表末尾添加元素,或使用 pop() 移除元素。
  • 预测包含列表的代码输出:理解列表操作如何改变数据,并预测最终结果。
  • 编写使用列表的代码:在实际编程任务中应用列表。

本周任务预告 🎯

在本周结束时,你的作业将是使用条件语句创建一个属于你自己的文字冒险故事。你将运用所学的条件判断,为玩家设计不同的故事分支和结局。


本节课中我们一起学习了第二周的两个核心主题:条件语句列表。你了解了条件语句如何让程序根据条件执行不同代码,也认识了列表作为有序集合的基本用法。掌握这些是构建更复杂、更智能程序的重要一步。

18:条件语句解析 🧠

在本节课中,我们将要学习编程中一个非常强大的概念:条件语句。它允许程序根据特定条件是否为真,来决定执行哪一部分代码。我们将通过一个简单的例子来理解其工作原理,并学习如何在Python中实现它。


从顺序执行到条件执行

之前我们接触的代码大多是顺序执行的,即一行接一行地运行。而条件语句赋予了程序选择性执行的能力,即只有当某个条件为真(或为假)时,才执行特定的代码块。

为了理解这个概念,让我们设想一个场景:有三个人想出去吃饭,但无法决定去哪家餐厅,因为他们各自有最喜欢的三个不同餐厅。如何随机地为他们做出选择呢?


一个决策流程图

我们可以编写一个Python程序来解决这个问题。思路是:随机生成一个0到2之间的整数(对应三个选项),然后根据这个数字决定餐厅。

在分析代码执行路径时,我们常用流程图来可视化。在流程图中,我们用菱形来表示条件判断。

以下是上述餐厅选择问题的流程图逻辑:

  1. 生成一个随机数 x(0, 1, 2)。
  2. 检查条件 x == 0 是否为真。
    • 如果为真,选择餐厅 A。
  3. 如果为假,则检查下一个条件 x == 1 是否为真。
    • 如果为真,选择餐厅 B。
  4. 如果以上条件均为假,则默认选择餐厅 C。

在Python中实现条件语句

现在,让我们将上述逻辑转化为实际的Python代码。首先,我们需要导入 random 模块来生成随机数。

以下是实现代码:

import random

# 生成一个0到2之间(包含0和2)的随机整数
x = random.randint(0, 2)

# 根据x的值决定输出
if x == 0:
    print("A")
elif x == 1:
    print("B")
else:
    print("C")

代码解析:

  • import random: 导入随机数模块。
  • x = random.randint(0, 2): 生成一个随机整数并赋值给变量 x,其值可能是0、1或2。
  • if x == 0:: 第一个条件判断。如果 x 等于0,则执行下方缩进的代码块,打印“A”。
  • elif x == 1:: elif 是“否则如果”的缩写。如果第一个条件不满足(即 x 不是0),则检查这个条件。如果 x 等于1,则打印“B”。
  • else:: 如果以上所有条件都不满足,则执行 else 下方的代码块,打印“C”。

运行这段程序,每次的输出可能会是“A”、“B”或“C”,实现了随机选择的效果。


总结

本节课中我们一起学习了条件语句。我们了解到,通过使用 ifelifelse 关键字,程序可以根据不同的条件做出决策,从而执行不同的代码路径。这是让程序变得“智能”和灵活的基础工具之一。从简单的餐厅选择到复杂的游戏逻辑,条件语句都是构建程序决策能力的核心。

19:全条件测试实践 🧪

在本节课中,我们将学习如何对条件语句进行全面的测试。我们将通过一个具体的例子,了解如何确保测试覆盖条件语句的每一个可能分支,并学习使用代码调试工具来观察程序的执行流程。

概述

条件语句是编程中控制程序流程的重要结构。为了确保代码在各种情况下都能正确运行,测试每一个条件分支至关重要。本节将通过一个温度判断函数的例子,演示如何进行全面的条件测试。

条件分支测试

上一节我们介绍了条件语句的基本概念,本节中我们来看看如何系统地测试一个包含多个分支的条件语句。

以下是一个根据温度给出建议的函数示例:

def temperature_advice(temp):
    if temp < 32:
        return "baby it's cold outside"
    elif temp < 70:
        return "wear a coat"
    elif temp < 80:
        return "feels great"
    else:
        return "too hot to handle"

这个函数包含四个可能的分支。为了全面测试,我们需要为每个分支提供相应的输入值。

以下是测试每个分支所需的输入值列表:

  • 分支一 (temp < 32):输入 20,预期输出 "baby it's cold outside"
  • 分支二 (temp < 70):输入 32,预期输出 "wear a coat"
  • 分支三 (temp < 80):输入 79,预期输出 "feels great"
  • 分支四 (else):输入 85,预期输出 "too hot to handle"

特别需要注意的是,测试边界条件(例如刚好等于或略高于/低于临界值的数字)非常重要,这有助于发现潜在的错误。

使用代码调试工具

除了编写测试用例,我们还可以利用代码调试工具(如代码透镜)来逐步执行程序,直观地观察每个条件分支是如何被评估和执行的。

以下是使用调试工具观察程序执行流程的步骤:

  1. 首先,工具会定义 temperature_advice 函数和 main 函数。
  2. 调用 main 函数后,程序开始执行。
  3. 当调用 temperature_advice(20) 时,调试器进入函数内部。它检查条件 temp < 32,结果为真,因此返回 "baby it's cold outside",随后该结果被打印出来。
  4. 接着调用 temperature_advice(32)。条件 temp < 32 为假,于是检查下一个条件 temp < 70,结果为真,因此返回 "wear a coat" 并打印。
  5. 调用 temperature_advice(79) 时,前两个条件均为假,检查第三个条件 temp < 80 为真,返回 "feels great" 并打印。
  6. 最后调用 temperature_advice(85),所有 ifelif 条件均为假,程序执行最后的 else 分支,返回 "too hot to handle" 并打印。

通过这种方式,你可以清晰地看到程序在每个测试用例下的执行路径,确保所有逻辑都按预期工作。

总结

本节课中我们一起学习了如何对条件语句进行全面的测试。关键点包括:为条件语句的每一个可能分支设计测试用例,特别注意测试边界条件,以及利用代码调试工具逐步跟踪程序的执行过程,以验证每个分支的逻辑是否正确。记住,充分的测试是编写健壮、可靠代码的重要环节。

20:编写get_middle函数 🧮

在本节课中,我们将学习如何编写一个名为 get_middle 的函数。这个函数的核心任务是接收一个字符串,并根据其长度返回中间的字符。我们将通过分析字符串的长度和奇偶性,来设计相应的逻辑。

概述

get_middle 函数的目标是:给定一个字符串 s,返回其“中间”的字符。具体规则如下:

  • 如果字符串长度小于3,则直接返回原字符串。
  • 如果字符串长度为奇数,则返回正中间的一个字符。
  • 如果字符串长度为偶数,则返回中间的两个字符。

例如,get_middle(“ABC”) 应返回 ”B”,而 get_middle(“ABCD”) 应返回 ”BC”

实现步骤

首先,我们需要获取输入字符串的长度,这是后续所有判断的基础。

以下是实现 get_middle 函数的具体步骤:

第一步:获取字符串长度
我们使用Python内置的 len() 函数来获取字符串的长度,并将其存储在一个变量中。

length_str = len(s)

第二步:计算中间索引
我们需要找到字符串的“中间”位置。由于字符串索引必须是整数,我们使用整数除法 // 来计算。

mid = length_str // 2

第三步:根据条件返回结果
现在,我们可以根据字符串的长度和奇偶性,使用条件语句来决定返回什么。

  • 条件一:长度小于3
    如果字符串长度小于3,直接返回原字符串。

    if length_str < 3:
        return s
    
  • 条件二:长度为奇数
    如何判断一个数是奇数?我们可以使用取模运算符 %。如果一个数除以2的余数为1,那么它就是奇数。

    elif length_str % 2 == 1:
        return s[mid]
    
  • 条件三:长度为偶数
    如果长度是偶数,我们需要返回中间的两个字符。对于长度为4的字符串 ”ABCD”mid 的值是2(索引为0,1,2,3)。我们需要的两个字符是索引1和2(即 ”BC”)。因此,我们使用字符串切片 s[mid-1:mid+1] 来获取它们。

    else:
        return s[mid-1:mid+1]
    

完整代码示例

将以上步骤组合起来,就得到了 get_middle 函数的完整实现。

def get_middle(s):
    length_str = len(s)
    mid = length_str // 2

    if length_str < 3:
        return s
    elif length_str % 2 == 1:
        return s[mid]
    else:
        return s[mid-1:mid+1]

总结

本节课我们一起学习了如何编写 get_middle 函数。我们掌握了几个关键点:使用 len() 获取长度,使用 // 进行整数除法以计算中间索引,使用 % 判断奇偶性,以及根据不同的条件使用返回语句或字符串切片来得到最终结果。这个练习很好地融合了字符串操作和条件逻辑的应用。

21:实现闹钟功能代码 🕐

在本节课中,我们将学习如何实现一个简单的闹钟功能。这个功能会根据给定的星期几(用数字表示)以及你是否在度假,来决定闹钟应该设置为“关闭”、“10点”还是“7点”响起。

概述

我们将编写一个函数,它接收三个参数:day(星期几,0代表周日,6代表周六),vacation(是否在度假,布尔值)。根据规则,我们需要返回对应的闹钟设置。规则如下:

  • 如果你在度假且是周末,闹钟应为“关闭”。
  • 如果你在度假且是工作日,闹钟应为“10点”。
  • 如果你不在度假且是周末,闹钟应为“10点”。
  • 如果你不在度假且是工作日,闹钟应为“7点”。

上一节我们介绍了问题背景,本节中我们来看看具体的代码实现。

方法一:使用列表判断周末

首先,我们可以创建一个列表来定义哪些天是周末。然后使用Python的in操作符来判断给定的day是否在周末列表中。

以下是使用此方法的代码实现:

def alarm_clock(day, vacation):
    # 定义周末列表,0代表周日,6代表周六
    weekend = [0, 6]

    if vacation:
        # 如果在度假
        if day in weekend:
            return "off"
        else:
            return "10:00"
    else:
        # 如果不在度假
        if day in weekend:
            return "10:00"
        else:
            return "7:00"

运行这段代码,它将通过所有测试用例,证明其正确性。

方法二:使用逻辑运算符判断周末

除了使用列表,我们也可以直接使用逻辑运算符来检查day是否为周末(即等于0或6)。

以下是使用此方法的代码实现:

def alarm_clock(day, vacation):
    if vacation:
        # 如果在度假
        if day == 0 or day == 6:  # 检查是否为周日或周六
            return "off"
        else:
            return "10:00"
    else:
        # 如果不在度假
        if day == 0 or day == 6:  # 检查是否为周日或周六
            return "10:00"
        else:
            return "7:00"

同样,运行这段代码也能通过所有测试。这为我们提供了至少两种解决闹钟问题的方法。

总结

本节课中我们一起学习了如何实现一个基础的闹钟逻辑函数。我们探讨了两种方法:第一种利用列表in操作符来判断日期属性;第二种直接使用逻辑比较==or)。两种方法都有效,你可以根据个人偏好或具体场景选择使用。核心在于理解问题规则,并将其清晰地转化为条件判断语句。

22:布尔表达式返回值技巧 🎯

在本节课中,我们将学习一个简化代码的技巧:如何直接返回布尔表达式的结果,从而避免冗长的 if-else 条件判断。

概述

我们经常会遇到只有两种可能结果的条件判断。例如,抛一枚硬币,结果要么是正面,要么是反面。在编程中,我们可能会编写一个函数来检查某个变量是否等于特定值,如果相等则返回 True,否则返回 False。本节课将介绍一种更简洁的写法。

初始代码示例

让我们先看一段代码。这里有一个名为 get_result 的函数,它接收一个变量 x 和一个值,然后检查 x 是否等于该值。如果相等,函数返回 True;否则返回 False

def get_result(x, value):
    if x == value:
        return True
    else:
        return False

x = "heads"
print(get_result(x, "heads"))  # 应输出 True
print(get_result(x, "tails"))  # 应输出 False

运行这段代码,我们首先会得到 True,然后是 False。注意,函数内部的 x 和外部的 x 虽然同名,但在 Python 中属于不同的命名空间,因此可以重复使用变量名。

简化技巧

上一节我们介绍了使用 if-else 语句进行条件判断的常规方法。本节中我们来看看如何简化它。

仔细观察,我们的逻辑是:如果测试条件为真,则返回 True;否则返回 False。实际上,我们可以直接返回这个测试条件本身的结果。

以下是简化后的代码:

def get_result(x, value):
    return x == value

x = "heads"
print(get_result(x, "heads"))  # 输出 True
print(get_result(x, "tails"))  # 输出 False

通过直接返回表达式 x == value,我们删除了三行代码,但得到了完全相同的结果。表达式 x == value 本身就会计算出一个布尔值(TrueFalse)。

核心要点总结

本节课中我们一起学习了布尔表达式的返回值技巧。

  • 当你发现自己的代码模式是“如果某条件为真,则返回 True,否则返回 False”时,你可以直接返回该条件表达式本身。
  • 这个技巧能让你的代码更简短、更清晰。
  • 其核心公式是:用 return <condition> 替代 if <condition>: return True else: return False

记住这个技巧,它是在处理简单布尔逻辑时让代码更优雅的有效方法。

23:复杂条件语句应用 🧩

在本节课中,我们将学习如何使用复杂的条件语句来简化代码。通过将嵌套的 if 语句合并为单个复杂的布尔表达式,我们可以使代码更简洁、更易读。


概述

编写代码时,我们经常会遇到需要检查多个条件的情况。一种常见的做法是使用嵌套的 if 语句,但这可能导致代码冗长且难以维护。本节将介绍如何利用逻辑运算符(andornot)创建复杂的条件语句,从而简化嵌套结构。

从嵌套条件到复杂条件

上一节我们介绍了嵌套 if 语句的基本用法。本节中,我们来看看如何将它们转换为更简洁的复杂条件语句。

当你发现自己在编写嵌套的 if 语句(即一个 if 语句内部包含另一个 if 语句)时,可以考虑使用逻辑运算符将它们合并。逻辑运算符 andornot 可以组合布尔表达式,形成复杂的条件。

以下是一个使用闹钟程序的示例。原始代码使用了嵌套条件:

if vacation:
    if day in weekend:
        return "off"
    else:
        return "10:00"
else:
    if day in weekend:
        return "10:00"
    else:
        return "7:00"

我们可以将其重写为使用复杂条件语句的形式:

if vacation and day in weekend:
    return "off"
elif vacation and day not in weekend:
    return "10:00"
elif not vacation and day in weekend:
    return "10:00"
else:
    return "7:00"

运行修改后的代码,结果与原始版本相同,但代码行数更少,结构更清晰。

关于代码可读性的注意事项

虽然复杂条件语句可以使代码更短,但人们常常在处理它们时遇到困难。更短的代码并不总是更好,因为它可能更难理解。因此,在追求简洁的同时,也应确保代码易于他人阅读和维护。


总结

本节课中我们一起学习了如何应用复杂的条件语句来简化嵌套的 if 结构。通过使用逻辑运算符 andornot,我们可以将多个条件合并为单个表达式,从而使代码更加简洁。记住,在优化代码长度时,始终要权衡可读性和维护性。

24:列表综合解析

在本节课中,我们将要学习Python中一个非常重要的数据结构——列表。我们将了解列表的基本概念、如何创建和操作列表,以及如何使用列表来组织和处理数据。

列表的基本概念

上一节我们介绍了数据组织的基本思想,本节中我们来看看Python中实现这一思想的具体工具——列表。

列表是一种按顺序存储项目的数据结构。这类似于一个生日愿望清单,你可以在清单上添加你感兴趣的物品,也可以移除物品。你可以询问清单上的第一项是什么,第二项是什么。总之,列表的核心概念是按顺序保存项目

在Python中创建和访问列表

了解了列表的概念后,我们来看看在Python中如何具体使用列表。

在Python中,我们使用方括号 [] 来创建一个列表,并用逗号分隔其中的值。例如,创建一个数字列表:

x = [3, 4, 5, 6]
print(type(x))

与字符串类似,我们可以使用方括号和索引来获取列表中特定位置的值。索引从0开始计数。

print(x[2])  # 获取索引为2的元素,即第三个元素

我们还可以使用切片操作符来获取列表的一个子集。切片语法为 [start:end],它包含起始索引,但不包含结束索引。

print(x[1:3])  # 获取从索引1到索引3(不包含3)的子列表

列表的操作与方法

列表在Python中是一种被称为“列表类”的对象。这个类内置了许多方法,可以对列表进行各种操作。

以下是列表的一些常用操作:

  • 添加元素:使用 append() 方法可以在列表的末尾添加一个新元素。
  • 获取长度:使用内置函数 len() 可以获取列表中元素的数量。
  • 计算总和:如果列表中的元素都是数字,可以使用内置函数 sum() 计算所有元素的总和。

让我们通过代码来演示这些操作:

x = [3, 4, 5, 6]
print(x)          # 打印原始列表

x.append(7)       # 在列表末尾添加数字7
print(x)          # 打印添加元素后的列表

print(len(x))     # 打印列表的长度
print(sum(x))     # 打印列表中所有数字的总和

运行这段代码,你会看到:

  1. 原始列表是 [3, 4, 5, 6]
  2. 使用 append(7) 后,列表变为 [3, 4, 5, 6, 7]
  3. len(x) 返回 5,因为列表中有5个元素。
  4. sum(x) 返回 25,即 3+4+5+6+7 的结果。

需要特别注意的是,切片操作(如 x[1:3])会返回列表的一个副本,而不会修改原始列表。

总结

本节课中我们一起学习了Python列表的基础知识。我们了解到列表是一种有序的数据集合,使用方括号创建。我们学会了如何通过索引访问元素、使用切片获取子列表,以及如何使用 append()len()sum() 等方法来修改和查询列表。列表是Python编程中组织数据的核心工具之一,熟练掌握它对于后续的学习至关重要。

25:解决 first_half 问题 🧩

在本节课中,我们将学习如何编写一个名为 first_half 的函数。这个函数的目标是接收一个列表作为参数,并返回该列表的前半部分元素组成的新列表。我们将使用Python的切片操作符来实现这个功能,并确保它能正确处理包含偶数个和奇数个元素的列表。

函数目标与思路

上一节我们介绍了列表切片的基本概念。本节中,我们来看看如何应用切片来解决一个具体问题:获取列表的前半部分。

first_half 函数需要接收一个列表,并返回一个新列表,该新列表包含原列表从开头到中间位置的元素。例如:

  • 输入 [1, 2, 3, 4],应返回 [1, 2]
  • 输入 [7, 8, 9],应返回 [7]

为了实现这个功能,我们需要找到列表的“中点”索引。核心步骤是计算列表长度,然后通过整数除法找到中间位置。

实现步骤详解

以下是实现 first_half 函数的具体步骤。

首先,我们需要计算列表的长度以确定其中点。我们可以使用内置的 len() 函数来获取列表的长度。

length = len(input_list)

接下来,我们需要找到切片的终点索引,即列表的“中点”。我们使用整数除法运算符 // 来计算。整数除法会丢弃结果的小数部分,只保留整数,这正好符合切片索引必须是整数的要求。

mid = length // 2

现在,我们可以使用切片操作符来获取列表的前半部分。切片语法 list[start:end] 会返回从索引 startend-1 的元素。由于我们需要从列表开头(索引0)切到中点 mid,所以可以这样写:

return input_list[:mid]

这里,起始索引 0 被省略了,这是Python切片的默认行为。

代码验证与测试

让我们用之前的例子来验证这个逻辑是否正确。

对于列表 [1, 2, 3, 4]

  • length 为 4。
  • mid4 // 2 = 2
  • 切片 [:2] 返回索引 0 和 1 的元素,即 [1, 2]。✅ 正确。

对于列表 [7, 8, 9]

  • length 为 3。
  • mid3 // 2 = 1
  • 切片 [:1] 返回索引 0 的元素,即 [7]。✅ 正确。

因此,完整的函数实现如下:

def first_half(input_list):
    mid = len(input_list) // 2
    return input_list[:mid]

运行包含测试用例的程序后,这个函数能够通过所有测试。

总结

本节课中我们一起学习了如何编写 first_half 函数。我们回顾了使用 len() 函数获取列表长度,并利用整数除法 // 计算中点索引。最关键的是,我们应用了列表切片操作 list[:mid] 来优雅地提取列表的前半部分元素。记住,合理利用这些内置函数和操作符可以让代码更简洁、高效。

26:循环与列表入门 🚀

在本节课中,我们将要学习编程中的两个核心概念:循环列表。你将学会如何预测包含循环和列表的代码输出,掌握多种循环的使用方法,并了解如何高效地处理和筛选数据。


循环与列表的核心概念 🔄

上一节我们介绍了本周的学习目标,本节中我们来看看具体要掌握哪些技能。

你将能够:

  • 预测包含循环列表的代码的输出。
  • 在遍历对象时使用 range 函数。
  • 使用 for each 循环来遍历字符串或列表。
  • 使用带 rangefor 循环或 while 循环。
  • 在遍历列表时,比较两个相邻的值。

高效编程技巧 ⚡

在掌握了基础循环后,我们可以进一步提升代码效率。

我将向你展示如何使用列表推导式,这是一种能让代码更简洁、并能过滤数据的方法。

同时,你也需要学会在面对具体问题时,如何判断并选择使用哪种类型的循环。


本周实践任务 🎨

理论需要结合实践。为了巩固所学知识,本周的作业是创作ASCII艺术


总结

本节课中我们一起学习了第三周的主要内容:循环列表的各种操作,包括for循环、while循环、range函数以及高效的列表推导式。最后,我们知道了本周将通过创作ASCII艺术来应用这些新技能。

希望你本周学习愉快!

27:循环结构总览 🔄

在本节课中,我们将要学习Python编程中一个强大的功能:循环。循环允许我们重复执行一段代码,这对于处理数据集合或重复任务至关重要。我们将探讨三种主要的循环方式,并通过简单的示例理解它们的工作原理。


循环的基本概念

编程的强大特性之一是能够迭代或重复执行一条语句或一个语句块。在Python中,有多种方式可以实现循环。让我们通过一些代码来了解它们。


1. For Each 循环

首先,我们来看 for each 循环。这种循环会逐个遍历列表中的每个元素。

以下是其工作原理的代码示例:

numbers = [1, 2, 3]
for num in numbers:
    print(num)

在这个例子中,变量 num 会依次取列表 numbers 中的每一个值(1, 2, 3),然后打印出来。这是一种直接遍历集合元素的方法。


2. 带 Range 的 For 循环

上一节我们介绍了直接遍历元素的循环,本节中我们来看看如何使用索引进行循环。带 range 的 for 循环 允许我们从某个起始索引开始,到某个结束索引为止进行循环。

以下是其工作原理的代码示例:

numbers = [1, 2, 3]
for i in range(len(numbers)):
    print(numbers[i])

range(len(numbers)) 会生成一个从 0 到 2(因为列表长度为3)的迭代器。变量 i 依次成为这些索引值(0, 1, 2),我们通过 numbers[i] 来访问列表中的对应元素并打印。

当你不想遍历整个列表,或者只想遍历列表的一部分时,这种方法非常有用。你也可以直接使用 range 函数指定一个固定的范围。

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

这段代码会打印数字 1, 2, 3。range(1, 4) 表示从 1 开始(包含),到 4 结束(不包含)。


3. While 循环

除了基于集合或固定次数的循环,我们还可以使用 while 循环。这种循环会在某个布尔表达式为 True 时持续执行。

以下是其工作原理的代码示例:

numbers = [1, 2, 3]
i = 0
while i < len(numbers):
    print(numbers[i])
    i = i + 1

我们首先设置一个计数器 i 为 0。只要 i 小于列表长度(3),循环就会继续。在循环体内,我们打印 numbers[i] 的值,然后将 i 增加 1。这个过程会一直持续,直到 i 等于 3,此时条件 i < 3 不再为真,循环结束。


总结

本节课中我们一起学习了Python中三种主要的循环结构:

  1. For Each 循环:用于直接遍历序列(如列表)中的每个元素。
  2. 带 Range 的 For 循环:通过索引或固定范围来控制循环次数。
  3. While 循环:在满足特定条件时重复执行代码块。

理解这些循环结构是掌握编程逻辑和高效处理重复任务的关键。你可以根据不同的场景选择最合适的循环方式。

28:单步调试count_odd函数 🐛

在本节课中,我们将学习如何使用代码透镜(Code Lens)工具来单步调试一个名为 count_odd 的函数。通过逐行执行代码,我们可以清晰地观察程序的状态变化,这对于理解和调试程序逻辑至关重要。

概述

我们将分析一个计算列表中奇数个数的函数。通过单步调试,我们将观察变量如何被赋值、循环如何执行以及条件判断如何影响程序流程。

函数解析

首先,我们来看一下 count_odd 函数的定义。它的作用是遍历一个数字列表,并统计其中奇数的数量。

def count_odd(num_list):
    count = 0
    for num in num_list:
        if num % 2 == 1:
            count += 1
    return count

代码解释

  • count = 0:初始化计数器。
  • for num in num_list::遍历列表中的每一个数字。
  • if num % 2 == 1::使用取模运算符 % 判断数字是否为奇数。公式 num % 2 == 1 成立时,num 为奇数。
  • count += 1:如果数字是奇数,计数器加一。
  • return count:返回最终的计数结果。

使用代码透镜进行单步调试

上一节我们介绍了函数的基本逻辑,本节中我们来看看如何使用代码透镜工具来可视化执行过程。

点击“代码透镜”按钮后,工具会加载并显示代码。一条红色高亮线会指示即将执行的代码行。执行后,该行会变为绿色,表示已执行完毕。

以下是使用代码透镜调试的步骤:

  1. 定义函数:首先,工具会高亮并执行函数定义部分,在内存中创建 count_odd 函数。
  2. 执行主程序:接着,执行跳到主函数(main)的调用处。
  3. 创建列表:执行 list1 = [2, 8, 9],工具会可视化地展示这个列表及其索引。
  4. 调用函数:执行 print(count_odd(list1)),程序进入 count_odd 函数内部。

深入函数内部执行过程

现在,我们进入 count_odd 函数,观察其内部的逐步执行。

  1. 初始化计数器:首先执行 count = 0,变量 count 被创建并赋值为0。
  2. 开始循环for num in num_list: 开始执行。第一次循环,列表的第一个元素 2 被赋值给变量 num
  3. 条件判断:检查条件 if num % 2 == 1。计算 2 % 2,结果为0,不等于1。因此条件为假,不执行 count += 1
  4. 继续循环:回到循环开始,取下一个元素 8 赋值给 num。判断 8 % 2 == 1,结果为假,计数器仍未增加。
  5. 最后一次循环:取最后一个元素 9 赋值给 num。判断 9 % 2 == 1,结果为真,因为9是奇数。于是执行 count += 1,计数器 count 的值从0变为1。
  6. 返回结果:循环结束,执行 return count,将结果 1 返回给主调函数。
  7. 打印输出:主函数接收到返回值 1,并通过 print 语句输出。

测试更多用例

单步调试完第一次调用后,我们可以继续观察函数如何处理不同的输入。

以下是后续的测试用例和结果:

  • list1 被重新赋值为 [1, 3, 5] 并调用 count_odd 时,函数返回 3,因为所有数字都是奇数。
  • 当列表是 [0, 2, 4] 时,函数返回 0,因为其中没有奇数(0不被视为奇数)。

调试完成后,可以隐藏代码透镜视图,直接保存并运行代码以查看所有输出。

总结

本节课中我们一起学习了如何使用代码透镜进行单步调试。我们详细跟踪了 count_odd 函数的执行过程,观察了变量状态在循环和条件判断中的变化。记住,代码透镜(Code Lens) 是一个强大的调试工具,它可以帮助你直观地理解代码执行流程,从而更有效地发现和修复程序中的错误。

29:修复sum67函数 🔧

在本节课中,我们将学习如何修复一个名为 sum67 的函数。这个函数的目标是计算列表中所有数字的总和,但需要忽略所有介于数字6和7(包括6和7本身)之间的数字。我们将通过分析代码、识别错误并逐步修正来完成任务。


概述与问题定位

首先,我们来看一下需要修复的 sum67 函数。你可以随时运行代码来查看错误信息。错误提示指出第5行有“bad input”。如果你之前使用过其他编程语言,可能会遇到一个常见问题:Python使用关键字 and 进行逻辑“与”操作,而不是像Java或C++那样使用 && 符号。这是第一个需要修正的问题。

此外,第7行应该使用双等号 == 来测试相等性,而不是单等号。单等号在Python中是赋值操作符。

修正了语法错误后,代码可以编译,但测试仍然失败。这表明逻辑上还存在问题。


分析函数逻辑

sum67 函数接收一个数字列表 nums,并返回列表中所有项目的总和,但需要忽略所有介于数字6和7(包括6和7本身)之间的数字。例如:

  • sum67([1, 2, 3]) 应返回 6,因为列表中没有6或7。
  • sum67([2, 6, 8, 7, 2]) 应返回 4,因为需要忽略从6到7之间的所有数字(即 6, 8, 7)。

函数定义如下,它接收一个数字列表。我们初始化总和 total 为0,并设置一个标志 found6 来追踪是否遇到了一个尚未配对的数字6。

def sum67(nums):
    total = 0
    found6 = True  # 初始值可能有问题
    for num in nums:
        if found6 and num == 7:
            found6 = False
        elif num == 6:
            found6 = True
            continue
        if found6:
            continue
        else:
            total += num
    return total

逐步调试与修正

上一节我们介绍了函数的基本逻辑,本节中我们来看看具体的错误点并进行修正。

1. 初始化标志 found6
代码中 found6 的初始值被设置为 True。这不符合逻辑,因为在开始遍历列表时,我们还没有找到任何数字6。因此,我们应该将其初始值改为 False

found6 = False  # 修正:初始状态应为未找到6

2. 调整条件判断顺序
当前代码的逻辑是:如果 found6 为真且当前数字是7,则将标志置为 False。否则,如果数字是6,则将标志置为 True 并跳过后续操作。这个逻辑基本正确,但需要确保在遇到6时,6本身不被计入总和。

3. 修复过早返回
原代码中,return total 语句错误地缩进在了 for 循环内部。这会导致循环在第一次迭代后就提前返回,无法遍历整个列表。我们需要将其移到循环体外。

def sum67(nums):
    total = 0
    found6 = False  # 修正1:初始化未找到6
    for num in nums:
        if found6 and num == 7:
            found6 = False
        elif num == 6:
            found6 = True
        elif not found6:  # 如果不在6和7之间,则累加
            total += num
    return total  # 修正2:在循环结束后返回

4. 逻辑梳理
修正后的逻辑流程如下:

  • 如果 found6 为真且当前数字是 7:关闭忽略区间标志(found6 = False)。
  • 否则,如果当前数字是 6:开启忽略区间标志(found6 = True)。
  • 否则,如果 found6 为假(即不在忽略区间内):将当前数字加入总和 total

修正后的完整代码

以下是经过所有修正后的 sum67 函数代码:

def sum67(nums):
    total = 0
    found6 = False
    for num in nums:
        if found6 and num == 7:
            found6 = False
        elif num == 6:
            found6 = True
        elif not found6:
            total += num
    return total

总结

本节课中我们一起学习了如何修复 sum67 函数。我们主要完成了以下工作:

  1. 识别并修正了Python语法上的常见错误,如使用 and 而非 &&,以及使用 == 进行相等比较。
  2. 修正了逻辑标志 found6 的错误初始值。
  3. 确保了函数在遍历完整个列表后才返回结果,而不是提前返回。
  4. 梳理了函数的核心逻辑:使用一个布尔标志来跟踪是否处于应被忽略的“6到7”区间内,并据此决定是否将数字累加到总和中。

通过这个练习,你不仅修复了一个具体的函数,也加强了对条件逻辑、循环控制和调试技巧的理解。

30:循环进阶技巧 🔄

在本节课中,我们将学习如何改变循环的控制流程,具体介绍 continuebreak 这两个关键语句的用法。它们能让我们更灵活地控制循环的执行路径。

理解循环控制流

上一节我们介绍了循环的基本结构。本节中,我们来看看如何通过特定语句来干预循环的执行过程。

循环通常从检查条件开始。只要条件为真,就会执行循环体。然而,有时我们希望在循环体执行到一半时,根据特定情况跳过剩余部分或直接终止整个循环。

使用 continue 语句

continue 语句用于跳过当前循环迭代中剩余的代码,并立即返回到循环条件检查处。

其工作流程可以概括为以下伪代码:

while 条件:
    # 执行部分代码...
    if 某个特定条件:
        continue  # 跳过后面的代码,回到 `while 条件:` 处
    # 如果执行了continue,这部分代码将被跳过...

当在循环体内遇到 continue 时,程序会忽略 continue 之后的所有语句,直接跳转回循环的开头,重新评估循环条件。如果条件仍然为真,则开始下一次迭代。

使用 break 语句

break 语句用于立即终止当前所在的整个循环。

其工作流程可以概括为以下伪代码:

while 条件:
    # 执行部分代码...
    if 某个特定条件:
        break  # 立即退出整个循环
    # 如果执行了break,循环结束,这部分代码不会执行...

当在循环体内遇到 break 时,无论循环条件当前是否为真,程序都会立即退出该循环,继续执行循环之后的代码。一个常见的用法是配合 while True: 创建“无限循环”,然后在满足某个条件时用 break 安全退出。

continuebreak 的作用范围

以下是关于这两个语句作用范围的重要说明:

  • continuebreak 只影响它们所在的、最内层的那个循环。
  • 如果循环是嵌套的(即一个循环 inside 另一个循环),break 只会退出它所在的那一层循环,外层的循环会继续执行。

总结

本节课中我们一起学习了循环的两个进阶控制语句。continue 让我们能够跳过当前迭代的剩余部分,直接开始下一次循环。而 break 则允许我们在满足特定条件时,立即终止整个循环。合理运用这两个工具,可以让你编写的循环逻辑更加清晰和强大。

30:为猜数游戏添加尝试计数 🎮

在本节课中,我们将学习如何在一个简单的猜数字游戏中,使用 while 循环来追踪玩家的尝试次数。我们将通过修改代码,添加一个计数器来实现这一功能。


概述

while 循环的一个绝佳应用场景是当你不知道需要多少次迭代时。例如,在一个游戏中,你无法预知玩家何时会获胜。我们将以一个简单的猜数字游戏为例,玩家需要猜测一个1到10之间的数字,并会收到“正确”、“过低”或“过高”的反馈。我们将使用 while 循环来持续游戏,直到玩家猜中为止,并在此过程中记录猜测的次数。

代码解析

首先,我们来看一下基础的游戏代码结构。

import random

def check_value(target, actual):
    if actual == target:
        return "correct"
    elif actual < target:
        return "too low"
    else:
        return "too high"

def main():
    target = random.randint(1, 10)
    guess = int(input("Enter a number between 1 and 10 inclusive: "))
    result = check_value(target, guess)

    while result != "correct":
        print(result)
        guess = int(input("Enter another number: "))
        result = check_value(target, guess)

    print("You guessed it! It was", target)

if __name__ == "__main__":
    main()

这段代码首先导入 random 模块来生成随机目标数字。check_value 函数用于比较猜测值与目标值,并返回相应的提示字符串。在 main 函数中,程序会持续循环,直到玩家猜中数字为止。

添加尝试计数器

上一节我们介绍了游戏的基本循环逻辑。本节中,我们来看看如何修改代码以追踪玩家的猜测次数。

我们需要在 main 函数中初始化一个计数器变量,并在每次玩家进行错误猜测时递增它。以下是具体的修改步骤:

  1. 初始化计数器:在获取第一次猜测之前,我们将计数器初始化为 1。这是因为玩家在进入循环前已经进行了一次猜测。
  2. 递增计数器:在 while 循环内部,每次玩家猜错并需要再次尝试时,我们将计数器加一。
  3. 显示结果:在游戏结束时,我们不仅打印出目标数字,还打印出玩家总共尝试的次数。

以下是修改后的 main 函数代码:

def main():
    target = random.randint(1, 10)
    num_guesses = 1  # 初始化计数器为1
    guess = int(input("Enter a number between 1 and 10 inclusive: "))
    result = check_value(target, guess)

    while result != "correct":
        print(result)
        num_guesses += 1  # 每次错误猜测后递增计数器
        guess = int(input("Enter another number: "))
        result = check_value(target, guess)

    print("You guessed it! It was", target, "in", num_guesses, "tries.")

代码运行示例

让我们运行修改后的代码,看看它是如何工作的。

假设程序随机选择了数字 3

  • 玩家第一次猜测 5,程序提示“too high”,计数器仍为1(因为这是第一次猜测,尚未在循环内递增)。
  • 玩家第二次猜测 3,程序提示“correct”,循环结束。
  • 程序最终输出:You guessed it! It was 3 in 2 tries.

这准确地反映了玩家进行了两次尝试。

总结

本节课中我们一起学习了如何为猜数字游戏添加尝试计数功能。我们通过引入一个计数器变量 num_guesses,并在 while 循环中巧妙地管理它的初始化和递增,实现了对玩家游戏过程的追踪。这个例子很好地展示了 while 循环在处理未知迭代次数任务时的实用性,以及如何通过简单的变量操作来增强程序的功能。

32:修复倒计时程序 🔧

在本节课中,我们将学习如何诊断并修复一个存在问题的倒计时程序。我们将重点关注无限循环的成因、如何正确使用while循环,以及如何确保程序按预期执行。


分析倒计时函数

首先,我们来看一下原始的倒计时函数。以下是其代码:

def countdown(start):
    value = start
    while value > 0:
        print(value)
        print(blast off)

这个函数接收一个起始值start,并将其赋值给变量value。只要value大于0,循环就会继续执行,打印当前值并试图打印“blast off”。

识别问题

上一节我们分析了代码结构,本节中我们来看看运行它时会出现什么问题。

如果尝试运行这段代码,会发现程序似乎没有任何输出。这主要有两个原因:

  1. 我们定义了两个函数(countdownmain),但从未调用它们。
  2. 即使调用了函数,while循环的条件value > 0会永远为真,因为循环内部没有修改value的值,这导致了无限循环

无限循环是使用while循环时一个非常常见的错误,它会使程序卡住,无法继续执行后续代码。

修复步骤

以下是修复这个倒计时程序需要进行的几个关键步骤:

  1. 确保函数被调用:在脚本末尾添加对main()函数的调用。
  2. 修改循环变量:在while循环内部,每次迭代后需要减少value的值,否则循环永远不会结束。
  3. 修正字符串语法print(blast off)中的blast off是一个字符串,需要用引号包裹。
  4. 调整循环条件:为了能从起始值倒数到0,循环条件应改为value >= 0

让我们应用这些修复。修改后的代码如下:

def countdown(start):
    value = start
    while value >= 0:  # 修改条件,包含0
        print(value)
        value = value - 1  # 每次循环减少value的值
    print("blast off")  # 修正字符串语法

def main():
    countdown(5)

# 调用main函数,启动程序
main()

运行修复后的代码,程序将能够顺利地从5倒数到0,然后打印出“blast off”。


本节课中我们一起学习了如何诊断和修复一个存在无限循环和语法错误的倒计时程序。核心要点包括:理解while循环的执行逻辑、避免无限循环的关键在于在循环体内修改条件变量、以及确保正确的函数调用和语法。掌握这些调试技巧对编程至关重要。

33:列表相邻值比较方法

在本节课中,我们将要学习如何在遍历列表时,比较相邻的两个元素。这是一个常见的编程任务,例如检查列表中是否存在连续重复的元素。我们将通过一个具体的例子来掌握这种方法。

概述

遍历列表时,有时我们需要比较当前元素与其相邻的下一个元素。直接使用 for item in list 这种循环方式无法方便地获取当前元素的索引,从而难以访问其相邻元素。因此,我们需要一种能够获取索引的遍历方法。

上一节我们介绍了基本的列表遍历,本节中我们来看看如何通过索引来比较相邻元素。

核心方法:使用索引遍历

为了比较列表中的相邻元素,我们可以使用 for 循环配合 range() 函数来遍历列表的索引。这样,在每次循环中,我们不仅知道当前元素 nums[i],还能通过 i+1 访问到它的下一个元素 nums[i+1]

以下是实现这一思路的关键步骤:

  1. 使用 range(len(nums)) 生成从 0len(nums)-1 的索引序列。
  2. 在循环体内,通过 nums[i]nums[i+1] 访问相邻元素并进行比较。

注意:当我们访问 nums[i+1] 时,必须确保 i+1 是一个有效的索引,不会超出列表范围。这意味着我们的循环不能一直进行到最后一个索引,而应该在倒数第二个索引处停止。

因此,正确的循环范围是 range(len(nums) - 1)。这样,i 的最大值就是 len(nums)-2,而 i+1 就是最后一个元素的索引 len(nums)-1,从而避免了 IndexError

实践案例:检查相邻的2

让我们通过一个具体的函数来实践这个方法。函数 has22(nums) 的目标是:如果列表 nums 中至少有一对相邻的元素都等于 2,则返回 True;否则返回 False

例如:

  • has22([1, 2, 2]) 应返回 True,因为索引1和2的元素都是2。
  • has22([2, 1, 2]) 应返回 False,因为两个2并不相邻。

以下是该函数的实现代码:

def has22(nums):
    for i in range(len(nums) - 1):
        if nums[i] == 2 and nums[i + 1] == 2:
            return True
    return False

代码解释:

  1. for i in range(len(nums) - 1): 这行代码确保我们遍历从0到倒数第二个索引的所有位置。
  2. if nums[i] == 2 and nums[i + 1] == 2: 在循环中,检查当前元素和下一个元素是否都等于2。
  3. 如果找到这样一对相邻的2,函数立即返回 True
  4. 如果循环结束都没有找到,则执行最后的 return False 语句。

注意:函数末尾的 return False 前面不需要加 else。因为如果条件满足,函数会在循环内部的 return True 处提前返回。只有当循环完整执行完毕都未返回时,才会执行到最后的 return False

总结

本节课中我们一起学习了如何在Python中比较列表的相邻元素。核心要点是使用索引进行遍历,并特别注意循环的边界条件,防止访问越界。这种方法适用于任何需要比较列表中连续元素对的场景,是处理序列数据的一项基础且重要的技能。

34:相邻值比较替代方案 🔄

在本节课中,我们将学习解决“检查列表中是否存在连续两个相同数字”问题的另一种方法。我们将使用一个布尔标志变量,通过简单的 for 循环来追踪状态,而不必直接操作列表索引。

上一节我们介绍了通过索引遍历列表来比较相邻元素的方法。本节中我们来看看如何不依赖索引,仅通过一个标志变量和 for 循环来实现相同的功能。

核心思路概述 💡

该方法的核心是使用一个名为 found_two 的布尔变量作为“状态记录器”。它的初始值为 False,表示尚未遇到数字 2。当我们遍历列表时,根据当前数字和 found_two 的状态来更新这个标志,并判断是否找到了连续的两个 2

算法步骤详解 🛠️

以下是使用布尔标志变量实现 has22 函数的具体步骤:

  1. 初始化标志变量:创建一个布尔变量 found_two 并将其初始值设为 False。在 Python 中,布尔值 TrueFalse 的首字母必须大写。

    found_two = False
    
  2. 遍历列表元素:使用 for 循环遍历列表中的每一个数字。

    for num in nums:
    
  3. 判断与状态更新:在循环内部,根据当前数字 numfound_two 的状态进行条件判断:

    • 情况一:如果当前数字是 2 并且 found_two 已经是 True,这意味着我们刚刚遇到过一个 2,现在又遇到了一个 2,即找到了连续的两个 2。此时函数可以直接返回 True
      if num == 2 and found_two == True:
          return True
      
    • 情况二:如果当前数字是 2 但是 found_twoFalse,这意味着我们遇到了一个 2,但前一个数字不是 2。此时我们将 found_two 设置为 True,记录“刚刚遇到一个 2”这个状态。
      elif num == 2 and found_two == False:
          found_two = True
      
    • 情况三:如果当前数字不是 2,则无论之前的状态如何,都需要将 found_two 重置为 False,因为连续状态已被打断。
      else:
          found_two = False
      
  4. 循环结束处理:如果完整遍历了整个列表,都没有触发返回 True 的条件,则说明列表中不存在连续的两个 2,函数最终返回 False

    return False
    

方法总结 📝

本节课中我们一起学习了解决“检查连续两个相同数字”问题的替代方案。这种方法利用一个布尔变量来记忆前一个元素是否为目标值(如 2),从而在遍历过程中动态判断连续性。它避免了直接使用索引,逻辑清晰,是处理此类“状态依赖”型问题的有效模式。

通过对比两种方法,你可以根据实际情况选择使用索引遍历,或是使用这种基于状态标志的循环策略。

35:循环数据过滤技术 🔄

在本节课中,我们将学习一种处理列表的实用技术:遍历列表并过滤出符合特定条件的元素。具体来说,我们将通过一个示例,学习如何从一个原始列表中筛选出满足条件的元素,并创建一个包含这些元素的新列表。


上一节我们介绍了列表的基本操作,本节中我们来看看如何通过循环来过滤列表中的数据。

列表的一个常见用途是遍历其中的元素,并根据特定条件筛选出部分元素,从而创建一个新的列表。让我们通过一个例子来理解这个过程。

我们将完成一个名为 filter_strings 的函数。这个函数接收一个名为 string_list 的字符串列表,并返回一个新的列表。新列表将按原始顺序包含所有长度大于3的字符串。

例如,用列表 ["run", "she", "said"] 调用 filter_strings 函数,应该只返回 ["said"],因为只有它的长度大于3。

以下是实现这个函数的步骤:

  1. 首先,创建一个新的空列表,用于存放结果。
  2. 然后,使用 for 循环遍历原始列表中的每一个字符串。
  3. 在循环内部,检查当前字符串的长度是否大于3。
  4. 如果条件满足,就将这个字符串添加到新列表中。
  5. 循环结束后,返回这个新列表。

让我们尝试实现它。我们可以这样开始:

def filter_strings(string_list):
    new_list = []  # 创建一个新的空列表
    for s in string_list:  # 遍历原始列表中的每个字符串
        if len(s) > 3:  # 检查字符串长度是否大于3
            new_list.append(s)  # 如果条件满足,将其添加到新列表末尾
    return new_list  # 返回新列表

请注意,必须确保函数最后返回了 new_list,否则函数将不会输出任何结果。

这就是过滤列表的一种方法:遍历列表中的每个元素,如果它符合你的筛选条件,就将其添加到一个新列表中,最后返回这个新列表。


本节课中我们一起学习了如何使用循环来过滤列表数据。我们创建了一个函数,它遍历输入的字符串列表,检查每个元素的长度,并将长度大于3的字符串收集到一个新列表中。这是一种基础但强大的数据处理技术,在后续处理集合数据时会经常用到。

36:列表推导式解析 📝

在本节课中,我们将要学习 Python 中一个非常有趣且强大的特性——列表推导式。这是一种创建或修改列表的简洁方法,能让代码更易读、更高效。

什么是列表推导式? 🤔

列表推导式提供了一种优雅的方式来生成新列表。其核心思想是,通过对一个可迭代对象中的每个元素应用一个表达式,来构建一个新的列表。

上一节我们介绍了列表的基本操作,本节中我们来看看如何使用列表推导式来简化代码。

列表推导式的语法 📖

列表推导式的基本语法结构如下:

new_list = [expression for member in iterable]

其中:

  • new_list 是生成的新列表的名称。
  • expression 是一个表达式,用于计算新列表中每个元素的值。
  • member 是每次迭代时,从 iterable 中取出的一个元素。
  • iterable 是一个可迭代对象,例如列表、元组、字符串或 range 对象。

可迭代对象指的是任何你可以遍历或循环处理的对象。例如,range(10) 会返回一个可以迭代的对象,你可以从中依次获取 0 到 9 这些值。

创建新列表的示例 🔢

让我们通过一个具体的例子来理解。假设我们想创建一个包含 0 到 9 的平方数的列表。

以下是使用列表推导式的方法:

squares = [i * i for i in range(10)]
print(squares)

代码执行过程如下:

  1. range(10) 生成一个可迭代对象,包含数字 0 到 9。
  2. 对于这个范围中的每个值 i(先是 0,然后是 1,依此类推,直到 9),计算表达式 i * i
  3. 将每次计算的结果(0, 1, 4, 9, ...)依次放入新列表 squares 中。
  4. 最后打印这个列表,输出结果为 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

基于现有列表生成新列表 🔄

列表推导式不仅可以创建全新的列表,还可以基于现有列表生成一个经过转换的新列表。请注意,原始列表本身不会被修改。

以下是基于现有列表生成新列表的步骤:

假设我们有一个列表 L1 = [3, 6, 7],我们想创建一个新列表,其中的每个元素都是 L1 中对应元素的一半。

L1 = [3, 6, 7]
R1 = [i / 2 for i in L1]
print(R1)
print(L1)

代码执行过程如下:

  1. 遍历列表 L1 中的每个元素 i(先是 3,然后是 6,最后是 7)。
  2. 对每个 i 计算表达式 i / 2(得到 1.5, 3.0, 3.5)。
  3. 将这些结果组成新列表 R1
  4. 打印 R1 会输出 [1.5, 3.0, 3.5]
  5. 打印原始的 L1,会发现它仍然是 [3, 6, 7],没有被改变。这清楚地表明 R1L1 是两个不同的列表对象。

总结 📚

本节课中我们一起学习了 Python 的列表推导式。我们了解到:

  • 列表推导式是一种通过 [expression for member in iterable] 语法快速创建或转换列表的方法。
  • 它可以用于从零开始生成新列表(如计算平方数)。
  • 它也可以用于基于现有列表生成一个元素经过处理的新列表,而不会改变原列表。
  • 掌握列表推导式能让你的代码更加简洁和 Pythonic。

37:列表推导式数据过滤 📋

在本节课中,我们将学习如何使用列表推导式来简化数据过滤的代码。我们将从一个传统的循环方法开始,然后将其转换为更简洁、更高效的列表推导式。

概述

上一节我们介绍了使用 for 循环和条件判断来过滤列表中的字符串。本节中,我们来看看如何使用列表推导式实现相同的功能,并大幅减少代码量。

传统过滤方法回顾

我们之前解决字符串过滤问题的方法是:创建一个新的空列表,然后使用 for 循环遍历传入的字符串列表中的每一个字符串。如果该字符串的长度大于三,我们就将其追加到新列表中,最后返回这个新列表。

以下是该方法的代码表示:

def filter_strings(str_list):
    new_list = []
    for s in str_list:
        if len(s) > 3:
            new_list.append(s)
    return new_list

引入列表推导式

列表推导式提供了一种更简洁的方式来创建列表。其核心思想是将循环和条件判断整合在一行代码中。

以下是使用列表推导式实现相同过滤功能的代码:

def filter_strings(str_list):
    new_list = [s for s in str_list if len(s) > 3]
    return new_list

在这个列表推导式中:

  • s for s in str_list 是一个循环,它遍历 str_list 中的每个元素。
  • if len(s) > 3 是一个条件,只有满足该条件的元素才会被包含在新列表中。

列表推导式详解

列表推导式的基本结构可以概括为以下公式:
新列表 = [表达式 for 变量 in 可迭代对象 if 条件]

  • 表达式:通常是对循环变量的操作或直接使用变量本身。
  • for 变量 in 可迭代对象:循环部分,用于遍历原始数据。
  • if 条件:可选部分,用于过滤元素。

在我们的例子中:

  • 表达式s(直接使用字符串本身)。
  • 循环for s in str_list
  • 条件if len(s) > 3

优势总结

使用列表推导式的主要优势在于:

  • 代码简洁:将多行循环和条件判断压缩为一行。
  • 可读性高:对于熟悉该语法的开发者,意图一目了然。
  • 效率相当:在大多数情况下,其执行效率与传统的循环方法相同。

总结

本节课中我们一起学习了列表推导式在数据过滤中的应用。我们首先回顾了使用传统 for 循环和 append 方法进行过滤的步骤,然后引入了列表推导式这一更简洁的语法。通过对比,我们了解到列表推导式如何将循环、条件判断和列表创建合并为一行清晰的代码,从而让程序更加简洁易读。

38:列表推导式进阶应用 🚀

在本节课中,我们将深入学习列表推导式。列表推导式是一种简洁的创建列表的方法,它允许你通过一个表达式和一个可迭代对象来生成新列表。我们将通过几个例子来理解其基本结构和高级用法。

列表推导式基础

上一节我们介绍了列表推导式的概念,本节中我们来看看它的具体语法。列表推导式的基本结构是:[表达式 for 变量 in 可迭代对象]。它会遍历可迭代对象中的每个元素,对每个元素执行表达式,并将结果收集到一个新列表中。

让我们看一个例子。

out = [i * 2 for i in range(11)]
print(out)

这段代码会输出一个列表:[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]range(11) 生成从 0 到 10 的数字序列。对于序列中的每个数字 i,表达式 i * 2 会计算其两倍的值,并放入新列表。

使用列表作为可迭代对象

除了 range,我们也可以直接使用一个列表作为可迭代对象。

以下是另一个例子,它使用字符串列表作为输入。

out = [i * 2 for i in [“A”, “B”, “C”]]
print(out)

这段代码会输出:[‘AA’, ‘BB’, ‘CC’]。这里,对于列表中的每个字符串 i,表达式 i * 2 会将该字符串重复一次。

在列表推导式中使用条件过滤

列表推导式的一个强大功能是可以在其中加入条件语句进行过滤。

以下是如何在列表推导式中使用 if 条件。

strings = [“apple”, “bat”, “car”, “dragon”]
out = [string for string in strings if len(string) > 3]
print(out)

这段代码会输出:[‘apple’, ‘dragon’]。它遍历 strings 列表中的每个元素,但只有当字符串的长度大于 3 时,才会将该字符串包含在新列表 out 中。

总结

本节课中我们一起学习了列表推导式的进阶应用。我们回顾了其基础语法 [表达式 for 变量 in 可迭代对象],并学习了如何使用实际的列表作为输入源,以及如何通过添加 if 条件来过滤元素,从而生成更符合需求的新列表。列表推导式是编写简洁、高效Python代码的重要工具。

39:Python基础数据结构入门 🐍

在本节课中,我们将要学习Python中的三种基础数据结构:列表、元组和字典。这些结构是组织和处理数据的基础,对于数据科学和数据分析至关重要。


第四周课程概述 📋

本周是本课程的最后一周。我们将专注于Python中的基础数据结构,即列表、元组和字典。

上一节我们介绍了本周的学习主题,本节中我们来看看本周的具体学习目标。

以下是本周你将学习的内容:

  • 学习如何预测列表、元组和字典的输出。
  • 学习如何在不同数据类型之间进行转换。
  • 学习如何过滤数据,这是数据科学或数据分析中数据清洗的一部分。
  • 学习使用字典来统计事物出现的次数。
  • 学习如何进行排序。在数据科学或数据分析中,我们常常需要找出排名靠前或靠后的项目,排序是解决此类问题的好方法。

核心学习内容详解 🔍

预测输出与类型转换

我们将讨论如何预测来自列表、元组和字典的输出,以及如何将数据从一种类型转换为另一种类型。

数据过滤

数据过滤是数据清洗的关键环节。我们将学习如何从数据集中筛选出需要的信息。

使用字典进行计数

字典是进行频率统计的强大工具。我们将学习如何使用字典来统计事物出现的次数。

排序操作

为了找出“最佳”或“最差”的项目,排序功能必不可少。我们将学习两种排序方法:

  • 使用列表内置的 sort() 方法。
  • 使用内置的 sorted() 函数,该函数也适用于元组和字典。你可以从字典的键值对(通过 .items() 获取)中得到一个元组列表,然后对其进行排序。

本周作业:消息加密与解密 🔐

在你的作业中,你将运用所学的字典知识,完成一个消息的加密和解密任务。


课程总结 🎯

本节课中我们一起学习了Python的三大基础数据结构:列表、元组和字典。我们探讨了如何预测它们的输出、进行类型转换、过滤数据、利用字典计数以及执行排序操作。这些技能是进行更复杂数据操作和分析的基石。请享受编程的乐趣,祝你学习愉快!

40:元组综合解析 📚

在本节课中,我们将要学习一种与列表非常相似的数据结构——元组。我们将探讨元组的基本概念、它与列表的相似之处以及关键区别,并通过代码示例来加深理解。

元组的基本概念

上一节我们介绍了列表,它是一种有序存储元素的数据结构。本节中我们来看看元组。元组同样用于有序地存储元素,它通常是由逗号分隔的一系列元素组成的集合。

以下是元组的基本创建方式:

# 声明一个只包含一个元素的元组
tuple_one = (1,)
print(type(tuple_one))  # 输出: <class 'tuple'>

# 不使用括号创建元组
tuple_two = 1,
print(type(tuple_two))  # 输出: <class 'tuple'>

# 创建包含多个元素的元组
tuple_three = (2, 5, 7)
print(tuple_three)  # 输出: (2, 5, 7)

请注意,即使元组只包含一个元素,也必须在其后加上逗号。括号是可选的,但逗号是必需的。

元组与列表的相似之处

元组与列表在许多操作上非常相似。例如,它们都支持索引访问和切片操作。

以下是元组的一些常见操作:

# 索引访问(索引从0开始)
print(tuple_three[1])  # 输出: 5

# 切片操作(创建新元组,不修改原元组)
print(tuple_three[1:])  # 输出: (5, 7)

# 遍历元组中的所有元素
for item in tuple_three:
    print(item)
# 输出:
# 2
# 5
# 7

元组与列表的关键区别

既然元组和列表如此相似,为什么我们需要另一种数据结构呢?主要区别在于元组是不可变的,而列表是可变的。

以下是列表的可变性示例:

# 列表是可变的
list_one = [2, 5, 7]
list_one[0] = 3
print(list_one[0])  # 输出: 3

然而,尝试修改元组会导致错误:

# 尝试修改元组(会导致错误)
tuple_three[0] = 3  # 这会引发 TypeError

元组就像字符串一样是不可变的。对字符串进行的任何修改(如转换为大写或小写)实际上都会创建一个新的字符串。而元组则完全不允许修改,也不能进行追加等操作。它们是与列表不同的数据结构。

总结

本节课中我们一起学习了元组的基本概念和操作。我们了解到元组是一种有序的、不可变的数据结构,与列表在索引访问、切片和遍历等方面相似,但关键区别在于其不可变性。理解元组的这一特性对于在编程中选择合适的数据结构非常重要。

41:元组使用场景 🧩

在本节课中,我们将要学习元组(Tuple)的核心使用场景。元组与列表(List)非常相似,但它是不可变的。我们将探讨为何以及何时应该选择使用元组,而不是列表。

元组与列表的核心区别

上一节我们介绍了元组的基本概念,本节中我们来看看元组存在的意义。既然元组很像列表,但不可更改,那么它的用途是什么?

关键在于元组的效率更高。当你需要从函数返回多个值时,Python有一个很好的特性,这是许多其他语言不具备的:可以返回一个包含多个项目的元组。这样,你就不必只返回一个值。

元组的主要使用场景

以下是元组的几个典型应用场景:

  1. 函数返回多个值
    因为你不希望任何人修改或改变返回的数据,所以它不必是一个列表,可以只是一个元组。这使得操作更高效。

  2. 从字典中获取键值对
    当我们处理字典并希望获取键值对时,同样不希望数据被随意改动,因为它是从字典中获取的。使用元组可以确保数据的完整性。

  3. 元组解包赋值
    另一个需要注意的特性是,你可以在赋值语句的左侧使用元组,右侧对应多个值。如果一个函数返回多个值,你可以在左侧用一个包含多个变量的元组来接收它们。这样,每个变量都会获得正确的值。这是一个非常方便的特性。

元组解包示例

当然,元组不一定需要用括号括起来。请看以下示例:

# 示例 1:基本解包
x, y = (4, "Fred")
print(y)  # 输出:Fred
print(x)  # 输出:4

# 示例 2:省略括号的解包
a, b = 99, 98
print(a)  # 输出:99
print(b)  # 输出:98

在第一个例子中,元组 (4, "Fred") 被解包并赋值给变量 xy。在第二个例子中,我们甚至省略了括号,直接进行赋值,这同样是有效的元组解包操作。

总结

本节课中我们一起学习了元组的关键使用场景。我们了解到,元组因其不可变性而比列表更高效,常用于函数返回多个值保护从字典等数据结构中获取的数据,以及利用元组解包特性来简化多变量赋值操作。记住,在不需要修改数据集合时,使用元组是一个更优的选择。

42:字典基础教程 📚

在本节课中,我们将要学习Python中一个非常强大且灵活的数据结构——字典。我们将了解字典是什么,它与之前学过的列表和元组有何不同,以及如何创建和使用字典。

概述

我们已经讨论了列表,它按顺序存储项目并且是可变的。我们也讨论了元组,它同样按顺序存储项目,但它是不可变的,无法更改。本节中,我们将介绍另一种主要的数据结构——字典。这是一种相当不同的数据结构。

什么是字典? 🗂️

列表是一种集合,它允许我们将多个事物用一个名字来指代,并按顺序存储这些项目。字典也是一种集合,同样允许我们用一个名字指代大量事物。但它的核心机制是存储键值对

你可以将字典想象成一个带标签的储物柜。给定一个“钥匙”(键),你可以往对应的“柜子”里放入一样东西(值),也可以根据这个“钥匙”取出里面的东西。这个“东西”本身也可以是一个集合(如另一个列表或字典),这使得字典成为Python中最强大的数据集合类型。

字典在其他编程语言中也有类似的概念,例如映射、哈希映射或C#中的属性包。

创建与访问字典

以下是创建和使用字典的基本方法。

创建一个空字典

你可以使用花括号 {}dict() 函数来创建一个空字典。

purse = dict()
# 或
purse = {}

为字典添加键值对

创建字典后,你可以通过指定键来设置对应的值。

purse['money'] = 24.50
purse['candy'] = 3
purse['tissues'] = 5

执行 print(purse) 会输出类似 {'money': 24.50, 'candy': 3, 'tissues': 5} 的内容。请注意,字典使用花括号 {} 包围,这与列表的方括号 [] 和元组的圆括号 () 不同。

更新字典中的值

如果你已经拥有某个键,你可以直接更新它对应的值。

purse['candy'] = purse['candy'] - 1
# 执行后,'candy' 对应的值会从 3 变为 2。

总结

本节课中,我们一起学习了字典的基础知识。我们了解到字典是一种存储键值对的集合,它使用花括号 {} 表示,并且通过键来访问和修改对应的值。与列表和元组不同,字典不强调顺序,而是强调键与值之间的映射关系,这使其在处理需要快速查找的数据时非常高效。在接下来的课程中,我们将进一步探索字典的更多高级用法。

43:字典进阶操作 📚

在本节课中,我们将深入学习Python字典的更多高级操作。我们将探讨字典的常见用途,例如计数和映射,并学习如何遍历字典、检查键是否存在以及获取字典的各种视图。


字典的常见用途

上一节我们介绍了字典的基本概念。本节中我们来看看字典的两个核心用途:计数映射

  • 计数:我们经常使用字典来统计某个键出现的次数。对于特定的键,我们记录它被看到的次数。
  • 映射:字典可以用来将一个事物映射到另一个事物。例如,你可以使用字典来查找给定一个西班牙语单词,其对应的英语单词是什么。

以下是一个映射的示例:

D = {'uno': 'one', 'dos': 'two', 'tres': 'three'}

在这个字典中,键是西班牙语单词,值是对应的英语单词。例如,'uno' 映射到 'one'


遍历与操作字典

让我们通过Python可视化工具来逐步分析以下代码,了解如何操作字典。

D = {'uno': 'one', 'dos': 'two', 'tres': 'three'}
print(D)
for key in D:
    print(D[key])
print('uno' in D)
print('seis' in D)
print(len(D))
print(list(D.keys()))
print(list(D.values()))
print(list(D.items()))

代码逐步解析:

  1. 首先,字典 D 被创建,其中包含了键到值的映射。
  2. 打印整个字典。注意输出被花括号 {} 包围,这是识别字典的一个标志。
  3. 使用 for key in D: 循环遍历字典。对于字典,这种迭代会返回一个键的列表。循环会依次处理每个键。
  4. 在循环内,打印当前键对应的值。因此,程序会依次打印 'one''two''three'
  5. 使用 in 操作符检查键 'uno' 是否在字典中,结果为 True
  6. 检查键 'seis' 是否在字典中,结果为 False,因为它不在字典中。
  7. 打印字典的长度,即它包含的键值对数量。
  8. 打印仅包含键的列表。注意输出是列表格式,使用方括号 []
  9. 打印仅包含值的列表。
  10. 打印包含键值对的元组列表。

以下是你可以对字典执行的一些操作总结:

  • 使用 in 操作符检查一个键是否存在于字典中。
  • 使用 len() 函数获取字典中键值对的数量。
  • 使用 .keys() 方法获取所有键的视图。
  • 使用 .values() 方法获取所有值的视图。
  • 使用 .items() 方法获取所有键值对(以元组形式)的视图。

总结

本节课中我们一起学习了字典的进阶操作。我们了解了字典在计数和映射方面的实际应用,并通过示例代码掌握了如何遍历字典、检查成员资格、获取字典长度以及提取键、值或键值对列表。这些操作是有效使用Python字典进行数据组织和处理的基础。

44:zip函数功能解析 🧵

在本节课中,我们将要学习Python中一个非常实用的内置函数——zip。这个函数能够将多个可迭代对象(如列表)的元素“配对”组合起来,形成一个新的迭代器。理解zip函数对于处理并行数据、简化循环逻辑非常有帮助。

什么是zip函数? 🤔

zip函数的作用是将多个可迭代对象中对应的元素打包成一个个元组,然后返回由这些元组组成的迭代器。我们可以形象地将其理解为“拉链”,就像将两片布料用拉链连接在一起。

以下是zip函数的基本用法示例:

list1 = [1, 2]
list2 = ['a', 'b']
result = zip(list1, list2)

在这个例子中,我们有两个列表,每个列表包含两个元素。zip函数会将这两个列表“拉”在一起,创建一个新的迭代器。

zip函数如何工作? ⚙️

上一节我们介绍了zip函数的基本概念,本节中我们来看看它的具体工作过程。

当我们运行上述代码并查看结果时,会发现result是一个由元组组成的列表。第一个元组包含第一个列表的第一个元素和第二个列表的第一个元素,第二个元组则包含两个列表的第二个元素。

# 运行结果类似于:
# [(1, 'a'), (2, 'b')]

这个过程就是依次从每个可迭代对象中取出一个元素,组合成一个元组。

zip对象的类型与迭代器 🔄

zip函数返回的并不是一个直接的列表,而是一个zip对象。为了理解我们实际得到的是什么,我们可以使用type()函数来查看其类型。

print(type(result))  # 输出: <class 'zip'>

zip类是一种迭代器。迭代器是一种可以逐个返回元素的对象,我们可以使用for循环来遍历它。

以下是遍历zip对象的示例:

for item in result:
    print(item)

将zip对象转换为列表 📋

由于zip函数返回的是一个迭代器,如果我们想直接查看或使用一个完整的列表,需要将其转换为列表。

我们可以使用list()函数来完成这个转换:

list_result = list(zip(list1, list2))
print(list_result)  # 输出: [(1, 'a'), (2, 'b')]

这与处理range对象类似。range函数也返回一个迭代器(range对象),我们可以通过list()函数将其转换为列表。

r = range(1, 10)
print(type(r))      # 输出: <class 'range'>
list_r = list(r)
print(list_r)       # 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

字符串、列表和range对象都是我们可以迭代遍历的对象。

总结 📝

本节课中我们一起学习了Python的zip函数。我们了解到zip函数可以将多个可迭代对象的对应元素组合成元组,并返回一个zip迭代器。我们学习了如何查看其类型、使用for循环进行遍历,以及如何通过list()函数将其转换为列表以便直接查看。zip函数是处理并行数据的强大工具,能够使代码更加简洁高效。

45:字典计数优化方法

在本节课中,我们将学习如何优化使用字典进行字符计数的代码。我们将重点介绍一种更简洁、更安全的方法来避免访问字典中不存在的键时可能引发的错误,并学习如何使用 .get() 方法来实现这一目标。


字典访问的潜在问题

在使用字典时,需要特别注意的一个问题是,当你尝试访问一个字典中不存在的键时,程序会引发一个 KeyError 错误。这可能导致程序意外终止。

让我们先看一段基础的字符计数代码:

def count_characters_in_string(s):
    letter_dictionary = {}
    for char in s:
        if char in letter_dictionary:
            letter_dictionary[char] = letter_dictionary[char] + 1
        else:
            letter_dictionary[char] = 1
    return letter_dictionary

这段代码的逻辑是:创建一个空字典,遍历字符串中的每个字符。如果字符已经是字典的键,则将其对应的值加一;否则,将该字符作为新键加入字典,并将其值初始化为1。


使用 .get() 方法进行优化

上一节我们介绍了基础但略显冗长的字典计数方法。本节中,我们来看看如何使用字典的 .get() 方法,将上述四行逻辑合并为一行,实现相同的功能,同时避免潜在的 KeyError 错误。

.get() 方法的语法是:字典.get(键, 默认值)。它的作用是尝试获取指定键的值。如果键存在,则返回其值;如果键不存在,则返回你提供的默认值,而不会引发错误。

我们可以将循环体内的代码优化如下:

def count_characters_in_string_optimized(s):
    letter_dictionary = {}
    for char in s:
        letter_dictionary[char] = letter_dictionary.get(char, 0) + 1
    return letter_dictionary

以下是这行代码的工作原理:

  • letter_dictionary.get(char, 0) 会检查字符 char 是否是字典的键。
  • 如果是,则返回该键当前的值(即该字符已出现的次数)。
  • 如果不是,则返回我们指定的默认值 0
  • 然后,无论哪种情况,我们都对这个结果加 1,并将新值赋回给 letter_dictionary[char]

这种方法与原始的四行代码效果完全相同,但更加简洁和高效。


本节课中,我们一起学习了字典计数的一种优化方法。通过使用 .get(键, 默认值) 方法,我们可以用一行代码安全地实现键值的递增操作,这比传统的“检查-判断”模式更简洁,并能有效避免因访问不存在的键而导致的程序错误。

46:列表、元组、字典排序技巧 📊

在本节课中,我们将要学习如何对Python中的列表、元组和字典进行排序。排序是数据处理中的常见需求,例如,在列表中查找元素时,有序列表能提高搜索效率;在字典中,我们可能需要根据值来排序,以找出出现频率最高或最低的项目。本节将详细介绍各种数据结构的排序方法。


列表排序

首先,我们来看如何对列表进行排序。列表本身提供了一个 sort 方法,它会直接修改原列表。

以下是列表排序的示例代码:

my_list = [3, 1, 4, 1, 5, 9, 2, 6]
my_list.sort()
print(my_list)  # 输出:[1, 1, 2, 3, 4, 5, 6, 9]

使用 sort 方法后,列表 my_list 本身被改变,元素按升序排列。需要注意的是,sort 方法仅适用于列表,因为列表是可变的。


元组排序

上一节我们介绍了列表的排序,本节中我们来看看元组。元组是不可变的,因此不能使用 sort 方法直接修改它。但我们可以使用内置的 sorted 函数,它会返回一个新的已排序列表。

以下是元组排序的示例:

my_tuple = (3, 1, 4, 1, 5, 9, 2, 6)
sorted_list = sorted(my_tuple)
print(sorted_list)  # 输出:[1, 1, 2, 3, 4, 5, 6, 9]
print(my_tuple)     # 输出原元组:(3, 1, 4, 1, 5, 9, 2, 6)

sorted 函数不会改变原始元组,而是返回一个新的列表。如果你需要元组形式的结果,可以将其转换回元组:sorted_tuple = tuple(sorted(my_tuple))


字典排序

现在,我们来看看字典的排序。字典由键值对组成,排序时可能需要根据键或值进行。

按键排序

直接对字典使用 sorted 函数,默认会返回一个按键排序的键列表。

以下是按键排序的示例:

my_dict = {'banana': 3, 'apple': 4, 'pear': 1, 'orange': 2}
sorted_keys = sorted(my_dict)
print(sorted_keys)  # 输出:['apple', 'banana', 'orange', 'pear']

这返回的是排序后的键列表,而不是完整的键值对。

获取键值对列表并排序

为了获取排序后的键值对,我们可以先使用 items 方法获取键值对列表,然后对其进行排序。

以下是获取并排序键值对列表的示例:

items_list = list(my_dict.items())
print(items_list)  # 输出:[('banana', 3), ('apple', 4), ('pear', 1), ('orange', 2)]

sorted_items = sorted(items_list)
print(sorted_items)  # 输出:[('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)]

默认情况下,排序依据是每个元组的第一个元素,即键。

按值排序

如果我们希望根据字典的值进行排序,可以使用 sorted 函数的 key 参数,并配合 lambda 函数。

以下是按值排序的示例:

sorted_by_value = sorted(my_dict.items(), key=lambda x: x[1])
print(sorted_by_value)  # 输出:[('pear', 1), ('orange', 2), ('banana', 3), ('apple', 4)]

这里,lambda x: x[1] 是一个匿名函数,它指定排序依据为每个元组的第二个元素,即值。

降序排序

如果需要降序排列,可以添加 reverse=True 参数。

以下是降序排序的示例:

sorted_desc = sorted(my_dict.items(), key=lambda x: x[1], reverse=True)
print(sorted_desc)  # 输出:[('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)]

这样,值最大的项会排在前面。


总结

本节课中我们一起学习了Python中列表、元组和字典的排序技巧。

  • 列表,可以使用 list.sort() 方法进行原地排序。
  • 元组,需使用 sorted() 函数,它会返回一个新的排序列表。
  • 字典sorted() 默认按键排序。要按值排序或获取排序后的键值对,需结合 items() 方法和 key=lambda 参数。

记住这些方法,你就能灵活地根据需求对各种数据结构进行排序了。

47:使用lambda函数进行排序 🔢

在本节课中,我们将深入探讨在使用sorted函数时,如何结合匿名函数(即lambda函数)来对数据进行排序。我们将通过一个具体的字典排序示例,详细解析lambda函数的工作原理和排序过程。


概述

我们将学习如何使用Python的sorted函数配合lambda表达式,对一个字典的键值对进行排序。核心在于理解lambda函数如何作为key参数,临时定义排序规则,从而影响最终的排序结果。

详细解析

上一节我们介绍了sorted函数的基本用法,本节中我们来看看如何结合lambda函数实现更灵活的排序。

我们首先创建一个字典:

d = {'red': 3, 'blue': 2, 'white': 1}

接下来,我们使用sorted函数对这个字典的项(即键值对组成的元组列表)进行排序。代码如下:

ds = sorted(d.items(), key=lambda x: x[1])

在这行代码中,key=lambda x: x[1]是关键部分。让我们分解它的工作流程:

  1. d.items()返回一个视图对象,包含('red', 3), ('blue', 2), ('white', 1)这样的元组。
  2. sorted()函数开始排序时,会依次将每个元组传递给key参数指定的lambda函数。
  3. lambda函数lambda x: x[1]是一个匿名函数。参数x代表每次传入的元组。
  4. 该函数体x[1]表示返回元组中索引为1的元素,即字典的值。

以下是lambda函数处理每个元组的具体过程:

  • 第一个元组('red', 3)传入,x[1]返回3
  • 第二个元组('blue', 2)传入,x[1]返回2
  • 第三个元组('white', 1)传入,x[1]返回1

sorted函数根据这些返回值3, 2, 1进行升序排序。排序后的顺序基于值1, 2, 3。因此,最终的排序结果是元组列表按照字典的值升序排列:

[('white', 1), ('blue', 2), ('red', 3)]

可以看到,键'white', 'blue', 'red'被重新组织,以匹配其对应值1, 2, 3的升序顺序。

进行降序排序

如果我们希望按照值进行降序排序,只需在sorted函数中添加reverse=True参数。

代码如下:

ds_desc = sorted(d.items(), key=lambda x: x[1], reverse=True)

其过程与升序排序类似:

  1. lambda函数依然依次处理每个元组,并返回值。
  2. 对于('red', 3),返回3;对于('blue', 2),返回2;对于('white', 1),返回1
  3. 由于设置了reverse=Truesorted函数会按照返回值3, 2, 1进行降序排序。

因此,得到的结果是:

[('red', 3), ('blue', 2), ('white', 1)]

此时,列表按照字典值的降序(3, 2, 1)排列。


总结

本节课中我们一起学习了lambda函数在排序中的应用。我们了解到,通过key=lambda x: x[1],可以指示sorted函数根据字典的值进行排序。通过控制reverse参数,可以轻松实现升序或降序排列。这种方法简洁高效,是处理自定义排序规则的常用技巧。

0:遇见Barb Ericson博士 👩‍🏫

在本课程中,我们将学习如何利用面向对象编程的力量进行创造性表达。我们将从理解对象和类的基本概念开始,学习如何编写和使用它们,并最终创建自己的模拟程序和游戏。

面向对象编程是自计算早期以来最重要的变革之一。理解什么是对象、什么是类,以及如何在代码中使用它们,对于现代编程至关重要。本课程将聚焦于此,帮助你掌握这些核心技能。

什么是面向对象编程?🤔

上一节我们介绍了课程的整体目标,本节中我们来看看面向对象编程的核心概念。

面向对象编程是一种编程范式,它将数据和操作数据的方法组织成“对象”。每个对象都是某个“类”的实例。类定义了对象的蓝图或模板,而对象则是根据这个模板创建的具体实体。

以下是理解面向对象编程的几个关键点:

  • 对象:是拥有状态(属性)和行为(方法)的实体。例如,一个“乌龟”对象可能有“位置”和“颜色”属性,以及“前进”和“转向”等方法。
  • :是创建对象的蓝图。它定义了对象将拥有哪些属性和方法。代码 class Turtle: 就是定义一个乌龟类。
  • 实例化:根据类创建具体对象的过程。代码 my_turtle = Turtle() 就是创建了一个乌龟类的实例。

本课程你将学到什么?🎯

理解了基本概念后,本节中我们来看看在本课程中你将具体实践哪些内容。

你将运用面向对象编程的力量进行创造性表达。例如,使用一个像小机器人一样的乌龟对象(它内部有一支笔)来绘制一幅场景,或者创建一个文字冒险游戏。

学生们将创建模拟现实世界对象的程序。例如,一个算命程序。这正是面向对象编程的强大之处之一——你可以模拟现实世界的对象。

学生们将调试包含多个类的面向对象程序。大多数真实的程序都会包含多个类。

学生们将使用继承来利用现有的类。继承允许你在现有事物的基础上进行构建和扩展。

总结 📝

本节课中我们一起学习了面向对象编程的引入。我们了解到,本课程将引导你掌握对象和类的概念,并利用它们进行创造性编码,例如绘制图形和创建游戏。你还将学习如何模拟现实对象、调试多类程序以及通过继承复用代码。希望你能享受这段学习旅程。

1:本课程的独特之处 🎨

在本节课中,我们将要了解这门《创造性编程》课程与其他在线课程的不同之处,以及它如何通过实践、协作和创意项目来帮助你学习。

欢迎来到这门课程。它与许多在线课程不同,不会包含大量讲座,而是更侧重于动手实践学习。

我们将鼓励你与他人合作,以小组形式学习,因为这能提升你的学习效果和动力。人类是社会性生物,我们可以通过观察他人和与他人讨论来学习。

互动练习与自适应学习 📚

上一节我们提到了课程的实践性,本节中我们来看看具体的练习形式。

电子书中包含大量互动练习。如果你需要复习,可以随时返回前面的章节或完成更多练习题。

以下是练习的一些特点:

  • 部分练习题是自适应的,这意味着它们会帮助你逐步找到正确的解决方案。

开放、编辑与创意任务 🎭

除了常规练习,课程的核心在于创意实践。

课程还包含开放编辑和创意任务。与许多编程入门课程中常见的练习(例如计算销售税或转换摄氏与华氏温度)不同,你将进行绘画创作或编写个性化故事。

希望这些内容对你来说会更有趣,也更能激发你的学习动力。

本节课中我们一起学习了这门《创造性编程》课程的独特之处:它强调动手实践、鼓励小组协作、提供互动与自适应的练习,并最终通过开放式的创意项目(如绘画和故事创作)来应用所学知识,让编程学习过程变得更具吸引力和动力。

2:Python是什么 🐍

在本节课中,我们将要学习Python编程语言的基本概念,了解它的起源、特点以及为什么它在当今编程世界中如此流行。

大家好,我是Barbar Erickson。我是信息学院的助理教授。我很高兴能带领大家踏上学习Python的旅程。那么,Python到底是什么呢?它是一条巨蟒吗?不,Python是以《蒙提·派森的飞行马戏团》命名的,这是一部20世纪70年代的BBC喜剧系列。如果你没看过,可以去看看。😊

它是一种非常流行的编程语言,许多公司都在使用它。它之所以流行,部分原因在于它非常强大。有大量的库可以让事情变得更简单,并且它被广泛应用于数据科学和机器学习领域。与许多其他文本型语言相比,它也相对容易学习一些。它还支持面向对象编程,这是当今人们开发程序最常用的方式。这意味着它支持(事物的分类)和实际执行程序工作的对象

上一节我们介绍了Python的起源和基本定位,本节中我们来看看Python的一些核心特性。

以下是Python的几个关键特点:

  • 强大且功能丰富:拥有庞大的第三方库生态系统,例如用于数据处理的pandas库和用于机器学习的scikit-learn库。
  • 易于学习:语法清晰简洁,接近自然语言,降低了初学者的入门门槛。
  • 支持面向对象编程(OOP):允许使用class关键字定义类,并通过创建对象来组织和管理代码。

例如,一个简单的Python类和对象创建代码如下:

# 定义一个‘Dog’类
class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        return "Woof!"

# 创建一个Dog类的对象
my_dog = Dog("Buddy")
print(my_dog.bark())  # 输出:Woof!

此外,Python在数据科学和人工智能等前沿领域扮演着重要角色,这使得掌握它不仅有助于一般性编程,还能打开通往热门技术领域的大门。

本节课中我们一起学习了Python是什么。我们了解到Python是一种以喜剧节目命名的、强大且易于学习的编程语言。它因其丰富的库和对面向对象编程的支持而广受欢迎,是进入编程世界以及数据科学等领域的优秀选择。现在,让我们开始动手实践吧!😊

3:为何分组协作 👥

在本节课中,我们将探讨在编程学习与实践中采用分组协作模式的原因与价值。我们将分析协作如何提升学习效果、增加动力,并培养对职业生涯至关重要的软技能。


上一节我们介绍了课程的基本框架,本节中我们来看看为何鼓励大家进行分组协作。研究表明,小组协作学习既能提升学习动力,也能深化对知识的理解。部分原因在于,当你向他人解释自己的想法时,这个过程本身就能帮助你更好地厘清概念。

此外,与他人协作通常也更有趣,并能帮助你在遇到困难时——尤其是在编程过程中——避免陷入僵局。总体而言,研究显示,多样化的团队表现往往优于个人。团队常常能解决个人无法独立应对的问题。

以下是小组协作带来的具体益处:

  • 提升理解与学习效果:向他人解释你的思路能巩固自身知识。
  • 增加动力与乐趣:与他人合作能让学习过程更愉快。
  • 避免陷入困境:在遇到编程难题时,同伴能提供不同视角和帮助。
  • 解决更复杂问题:团队集合多元智慧,能应对个人难以独立完成的挑战。例如,在密室逃脱游戏中,与他人的协作通常是成功的关键。

同样重要的是,团队协作能力——包括沟通、团队合作、自我管理与协调他人——对雇主而言至关重要。因此,掌握这类技能在未来的职场中也极具价值。


本节课中我们一起学习了分组协作在编程学习中的多重优势。从深化个人理解、提升学习乐趣,到培养解决复杂问题的能力及职场必备的软技能,协作都是推动个人与团队共同成长的有效方式。

4:POGIL角色解析 🧩

在本节课中,我们将学习一种名为POGIL的协作学习方法,并详细解析其团队中的四个核心角色。掌握这些角色有助于提升团队协作效率,确保每个成员都能有效参与并贡献。


概述

POGIL代表“过程导向的引导式探究学习”。在这种学习模式中,团队成员以小组形式协作,但每个成员都被赋予一个明确的角色。这些角色共同确保团队任务能够高效、有序地完成,并促进所有成员的参与。

POGIL的四个核心角色

上一节我们介绍了POGIL的基本概念,本节中我们来看看其具体的四个角色。以下是每个角色的定义和职责:

  • 管理者

    • 为团队成员分配任务。
    • 确保整个团队按计划推进,并按时完成任务。
    • 在团队遇到困难时,鼓励成员继续前进或调整策略。
  • 记录员

    • 负责记录讨论中的要点和关键发现。
    • 写下团队学习到的内容或达成的共识。
  • 汇报者

    • 当任务说明不够清晰时,负责向指导者提问。
    • 负责向全班或指导者展示团队的最终成果或结论。
  • 反思者

    • 确保团队中每个人的声音都被听到,避免只有少数人主导讨论。
    • 观察并反馈团队的协作动态,评估团队合作是否良好,并提出可以改进的地方。

总结

本节课中我们一起学习了POGIL协作学习法中的四个关键角色:管理者记录员汇报者反思者。每个角色都有其独特的职责,共同构成了一个高效、平衡的团队协作框架。理解并实践这些角色,将帮助你在未来的小组项目中更好地与他人合作。

5:电子书中编写代码的方法 📖

在本节课中,我们将学习如何在电子书中直接编写、运行和调试Python代码。这是一种便捷的学习方式,允许你即时实践并查看结果。


概述

电子书的一个显著优点是允许你直接在书中编写代码。我们将通过一个计算矩形面积的函数示例,来演示如何完成这一过程。

编写与运行代码

上一节我们了解了电子书的交互特性,本节中我们来看看如何具体编写代码。

假设我们需要创建一个名为 area_of_rectangle 的函数,它接收宽度和长度作为参数,并返回矩形的面积。例如,area_of_rectangle(5, 4) 应返回 20

以下是创建此函数的步骤:

  1. 首先,在电子书提供的代码编辑区域,定义一个函数并计算面积。
    def area_of_rectangle(width, length):
        area = width * length
    
  2. 运行这段初始代码后,你可能会发现测试用例未通过,因为函数没有返回任何值。
  3. 因此,我们需要修改代码,使其返回计算出的面积。
    def area_of_rectangle(width, length):
        area = width * length
        return area
    
  4. 再次运行代码,现在测试用例应该通过了。你可以通过“代码透镜”功能查看运行结果。

使用辅助工具

除了直接编辑,电子书还提供了其他有用的工具来辅助编程。

如果你不确定乘法运算符 * 是否正确工作,可以点击右上角的铅笔图标,打开一个Python代码输入窗口进行测试。

例如,你可以输入 print(5 * 4) 来验证结果是否为 20。测试完成后,可以关闭这个窗口。

查看代码历史

另一个重要的功能是代码历史记录。请确保你在学习时已登录账户,这样你就能使用界面上的滑块工具。

通过拖动滑块,你可以回溯查看代码的各个历史版本,例如最初的样子、第二次修改后的状态等。这有助于你回顾自己的解题思路和修改过程。


总结

本节课中我们一起学习了在电子书中编写代码的方法。我们实践了如何定义一个函数、运行代码并通过测试,还了解了如何使用临时代码窗口进行测试,以及如何利用历史记录功能回顾代码的演变过程。这些工具能有效提升你的编程学习效率。

6:重载代码历史记录 🔄

在本节课中,我们将学习如何在编程环境中找回之前保存的代码。这对于中断工作后返回项目,或希望回顾之前版本代码的情况非常有用。

如果你在编程环境中工作过,然后离开并再次返回,可能会惊讶地发现代码编辑器似乎是空的。但请放心,只要你之前是登录状态,你的代码就已经被保存了。接下来,我将向你展示如何找回这些代码。

如何找回已保存的代码

当你重新进入编程环境并发现之前的代码不见了时,无需惊慌。请按照以下步骤操作:

以下是找回代码的具体步骤:

  1. 在编程环境的界面上,找到一个名为 “Load History”(加载历史记录)的按钮。
  2. 点击这个按钮。
  3. 系统会自动加载你上次登录并运行代码时的所有历史记录。

点击“Load History”按钮后,你将能看到上次运行的确切代码。此外,你还可以查看更早的版本,回顾之前的修改过程。

重要注意事项

请记住一个关键点:只有在你登录状态下进行的操作才会被自动保存。因此,为了确保你的工作进度不会丢失,请务必在开始编程前登录你的账户。

本节课中我们一起学习了如何使用“Load History”功能找回已保存的代码历史。关键在于两点:首先,确保在编程时处于登录状态;其次,返回项目后记得点击“加载历史记录”按钮。这样,你的所有工作成果都能得到妥善保存和恢复。

7:课程概述 🎨

在本节课中,我们将要学习第一周课程的核心内容。本周我们将重点探索如何通过导入模块来扩展Python的功能,并初步接触面向对象编程中“对象”与“类”的核心概念。最后,我们将运用所学知识,使用turtle对象绘制一个图形场景。

导入模块 📦

Python语言本身相当精炼,但其强大之处在于拥有大量可导入并使用的模块或库。学习如何导入模块是本周你将掌握的首要技能。

对象与类初探 🧱

上一节我们介绍了模块的导入,本节中我们来看看编程中一个非常重要的思想:对象与类。我们将学习如何将代码组织成对象和类。

以下是本周关于对象与类的具体学习目标:

  • 学习如何创建对象并使用对象的方法。
  • 学习如何定义对象和类。
  • 理解对象和类之间的区别。
  • 学习如何定义对象的方法、属性(或特性)和行为。

函数与对象结合应用 ⚙️

在掌握了对象的基本概念后,你将进一步学习编写以对象作为参数的函数,这能让你的代码更加灵活和强大。

实践项目:使用Turtle绘图 🐢

在本周的最后,你将综合运用所学的关于可重用函数和对象的知识,使用turtle图形对象绘制一个场景。这是一个有趣的实践,能帮助你巩固本周的所有概念。

本节课中我们一起学习了第一周的课程安排,包括导入Python模块、理解面向对象编程中对象与类的基础知识,以及如何编写操作对象的函数。最终,我们将通过turtle绘图项目来实践这些概念。祝你学习愉快!

8:编程核心概念解析 🧠

在本节课中,我们将要学习编程中的一个强大思想:面向对象编程。我们将通过一个生动的蚂蚁觅食模拟程序来理解其核心概念,包括类、对象以及它们如何协同工作。

面向对象编程简介

面向对象编程是一种编程范式,其核心思想是:你可以编写程序来定义事物的或分类,然后从这些类中创建对象,最后由这些对象在程序中执行具体的工作。

蚂蚁觅食模拟程序示例

为了具体说明,让我们来看一个蚂蚁觅食的模拟程序。当我运行这个程序时,会出现一个场景:其中有蚁丘,红色的小蚂蚁从蚁丘中出来。它们相当随机地四处游荡,寻找食物。当它们找到食物时,会留下信息素,然后返回蚁丘。其他蚂蚁可以跟随这些信息素找到食物。

这是一个很好的多对象示例。程序中定义了多个类,例如 WorldActorAntHillCalendarCreatureAnt。因此,这是一个面向对象的程序。它将程序分解为多个类,然后我们创建这些类的对象,由这些对象在程序中实际执行工作。

核心概念解析

上一节我们通过示例看到了面向对象程序的实际运行效果。本节中,我们来详细看看构成这个程序的核心概念。

以下是构成面向对象程序的关键元素:

  • :类是对象的蓝图或模板。它定义了某一类事物共有的属性(数据)和方法(行为)。例如,Ant 类定义了所有蚂蚁共有的属性(如位置、携带的食物量)和方法(如移动、寻找食物、返回蚁巢)。
    • 公式/代码表示class Ant:
  • 对象:对象是类的具体实例。根据同一个类可以创建出多个独立的对象。例如,程序运行时,会根据 Ant 类创建出成百上千个独立的蚂蚁对象,每个对象都有自己的状态(位置、食物量)。
    • 公式/代码表示ant1 = Ant()
  • 属性:属性是对象内部存储的数据,用于描述对象的状态。例如,一个蚂蚁对象可能有 x_positiony_positionfood_carried 等属性。
    • 公式/代码表示self.x_position = 100
  • 方法:方法是定义在类中的函数,用于描述对象可以执行的操作或行为。例如,Ant 类可能有 move()search_food()return_to_hill() 等方法。
    • 公式/代码表示def move(self):

程序结构分析

理解了基本概念后,我们来看看这些概念如何组织成一个完整的程序。

在这个蚂蚁模拟程序中,不同的类承担了不同的职责:

  • World 类可能负责管理整个模拟环境,如画布大小、食物位置。
  • ActorCreature 类可能是一个更基础的类,定义了所有生物共有的特性(如位置、移动),Ant 类则继承自它。
  • AntHill 类代表蚁丘,管理从这里出生的蚂蚁。
  • Calendar 类可能用于控制模拟的时间步长或周期。
  • Ant 类则具体定义了蚂蚁的觅食行为逻辑。

程序运行时,首先会创建这些类的对象(例如,一个 World 对象,几个 AntHill 对象)。然后,AntHill 对象会不断地创建新的 Ant 对象。每个 Ant 对象根据其内部方法(如 move, search_food)自主行动,并通过修改自身属性(如位置)和与环境(其他对象)交互,共同呈现出复杂的群体觅食行为。

总结

本节课中,我们一起学习了面向对象编程的核心概念。我们了解到,面向对象编程通过来定义事物的抽象蓝图,通过创建类的对象实例来构建程序。对象拥有属性来存储状态,并通过方法来定义行为。我们通过一个蚂蚁觅食的模拟程序,直观地看到了多个类(如 World, Ant, AntHill)如何被定义,它们的对象又如何被创建并相互作用,从而完成复杂的程序任务。掌握这些概念是理解和构建更大型、更模块化软件的基础。

9:类如同工厂 🏭

在本节课中,我们将学习面向对象编程中的一个核心概念——。我们将通过一个生动的比喻来理解类是什么,以及它如何帮助我们创建具有特定属性和行为的对象。


概述

类可以被看作是一个工厂。就像汽车工厂可以生产出许多辆汽车一样,一个类可以用来创建多个具有相同基本结构,但具体属性可能不同的对象。这些对象不仅拥有数据(属性),还能执行特定的操作(行为)。理解类与对象的关系,是掌握面向对象编程的关键第一步。


类如同工厂

上一节我们介绍了编程中的基本概念,本节中我们来看看“类”这个强大的工具。

你可以把一个类想象成一个工厂。通过这个工厂,你可以创建许多属于同一类型的对象。例如,一个汽车工厂可以生产出许多辆汽车,这些汽车都属于“汽车”这个类别,但它们各自拥有不同的属性。

以下是这些汽车对象可能具有的不同属性:

  • 它们有不同的颜色。
  • 它们可能配备了不同的选项(例如,天窗、真皮座椅)。

当你向工厂订购一辆汽车时,你需要指定你想要的汽车类型、颜色以及其他各种属性。这个过程,就类似于在代码中实例化一个类并传入初始参数。


对象的属性与行为

当我们得到一辆汽车时,我们不仅关心它的外观(属性),更期望它能执行特定的功能(行为)。你会感到不满,如果你得到的汽车不能前进、后退、左转或右转。

这些“能做的事情”就是我们期望汽车具备的行为

因此,当你定义一个类时,你需要明确两件事:

  1. 属性:我们需要知道的关于这个对象的数据。在代码中,这通常由类的 __init__ 方法中的变量来定义。
    def __init__(self, color, has_sunroof):
        self.color = color          # 属性:颜色
        self.has_sunroof = has_sunroof # 属性:是否有天窗
    
  2. 行为:这个对象可以执行的操作。在代码中,这由类内部定义的方法来实现。
    def drive_forward(self):
        print(f"The {self.color} car drives forward.") # 行为:前进
    
    def turn_left(self):
        print("The car turns left.") # 行为:左转
    


总结

本节课中我们一起学习了“类”的概念。我们通过“工厂”的比喻,理解了类是一个用于创建对象的蓝图或模板。每个由类创建的对象(实例)都拥有类所定义的属性(数据)和行为(方法),但各自的属性值可以不同。掌握类的定义和使用,是构建复杂、模块化程序的基础。

10:Turtle绘图入门 🐢

在本节课中,我们将学习如何使用Python的Turtle库进行绘图。Turtle是一个历史悠久的编程教学工具,它通过控制一个虚拟的“海龟”在屏幕上移动并留下轨迹,来帮助我们直观地理解编程概念,特别是对象和类。

概述:Turtle的历史与概念

Turtle绘图的概念可以追溯到20世纪60年代。麻省理工学院的教授西摩·派珀特和他的团队创造了Logo编程语言。他们使用一个装有笔的实体机器人海龟,通过编程指令控制它前进、左转和右转,从而绘制图形。派珀特将这种海龟称为“思考的对象”,因为它能帮助学习者利用自身的身体运动知识(如前进、转向)来解决问题。如今,包括Python在内的许多编程语言都支持Turtle库。

第一个Turtle程序

上一节我们了解了Turtle的背景,本节中我们来看看如何在Python中编写第一个Turtle程序。

以下是一个简单的Python代码示例。运行这段代码,你将看到一只名为Alex的海龟在屏幕上移动并绘制线条。

import turtle

# 创建一个绘图窗口
space = turtle.Screen()
# 创建一个海龟对象,命名为Alex
alex = turtle.Turtle()

# 控制Alex移动
alex.forward(150)  # 向前移动150像素
alex.left(90)      # 向左转90度
alex.forward(75)   # 再向前移动75像素

运行这段代码后,海龟Alex会先向前移动150个单位,然后左转90度,最后再向前移动75个单位,在屏幕上画出一个“L”形的折线。

理解代码结构

现在,让我们分解一下上面的代码,理解每一部分的作用。

以下是代码中涉及的核心概念和步骤:

  1. 导入库:Python本身功能有限,但可以通过import语句引入额外的库来扩展功能。这里我们导入了turtle库。
  2. 创建画布turtle.Screen()创建了一个窗口对象,我们将其赋值给变量space,作为海龟绘图的画布。
  3. 创建海龟turtle.Turtle()创建了一个海龟对象,我们将其赋值给变量alex。你可以把alex想象成一只真实的、可以接受命令的海龟。
  4. 发送指令:我们使用点号(.) 来向海龟对象发送指令。这被称为点表示法。例如:
    • alex.forward(150):命令Alex向前移动150像素。
    • alex.left(90):命令Alex向左转90度。

修改与实验

理解了基本结构后,我们可以通过修改参数来观察不同的绘图效果。

例如,如果我们只让Alex向前移动50像素,代码如下:

alex.forward(50)
alex.left(90)
alex.forward(75)

运行修改后的代码,你会发现Alex在移动较短距离后就进行了转弯,从而绘制出形状不同的折线。通过改变forward()函数中的数字(距离)和left()right()函数中的数字(角度),你可以创造出各种图形。

总结

本节课中我们一起学习了Turtle绘图的基础知识。我们了解了Turtle作为“思考对象”的起源,掌握了在Python中导入turtle库、创建屏幕和海龟对象的方法,并学会了使用点表示法(如对象.方法(参数))向海龟发送前进和转向的指令。通过修改指令中的参数,你可以开始探索和创造属于自己的简单图形。

11:模块导入方法详解 🐢

在本节课中,我们将学习Python中模块导入的不同方法。模块是包含Python代码的文件,通过导入模块,我们可以使用其中定义的函数、类和变量。理解不同的导入方式对于编写清晰、高效的代码至关重要。

导入模块的基本方法

上一节我们介绍了模块的概念,本节中我们来看看具体的导入方法。Python提供了多种导入模块的方式,每种方式都有其适用场景。

方法一:直接导入模块名

使用import关键字后跟模块名,可以直接导入整个模块。这种方式允许你使用模块中的所有功能,但使用时需要在函数或变量名前加上模块名作为前缀。

例如,导入math模块并使用其中的cosine函数:

import math
result = math.cos(0)

方法二:使用别名导入模块

有时模块名较长,可以使用as关键字为模块设置一个简短的别名。这可以使代码更简洁,同时保持命名空间的清晰。

以下是使用别名导入math模块的示例:

import math as m
result = m.cos(0)

方法三:从模块中导入特定内容

如果你只需要模块中的特定函数或变量,可以使用from ... import ...语句。这样可以避免使用模块名前缀,直接使用导入的名称。

以下是从math模块中导入cosine函数和pi常量的示例:

from math import cos, pi
result = cos(pi)

方法四:导入模块中的所有内容

使用from ... import *可以导入模块中的所有名称。这种方式允许你直接使用模块中的函数和变量,无需模块名前缀。然而,这种方式可能导致命名冲突,因此不推荐在大型项目中使用。

以下是导入turtle模块中所有内容的示例:

from turtle import *
forward(100)

不同导入方法的比较

以下是各种导入方法的优缺点总结:

  • import module_name:保持命名空间清晰,避免命名冲突,但代码稍显冗长。
  • import module_name as alias:代码简洁,同时保持命名空间清晰,适合模块名较长的情况。
  • from module_name import name1, name2:直接使用特定名称,代码简洁,但可能引起命名冲突。
  • from module_name import *:使用最方便,但极易引起命名冲突,不利于代码维护。

总结

本节课中我们一起学习了Python中模块导入的四种主要方法。理解这些方法有助于你根据实际需求选择最合适的导入方式,从而编写出既清晰又高效的代码。对于初学者,建议优先使用import module_nameimport module_name as alias的方式,以养成良好的编程习惯。

12:Turtle基础操作 🐢

在本节课中,我们将学习Python中Turtle图形库的基础操作。我们将从导入库开始,逐步了解如何创建画布、控制海龟移动以及理解其坐标系和方向系统。


上一节我们介绍了课程概述,本节中我们来看看Turtle模块的基本使用方法。

首先,你需要导入Python的turtle库。这个库定义了海龟类及其所有功能。

import turtle

导入库后,你需要为海龟创建一个绘图屏幕或空间。

screen = turtle.Screen()

接着,你需要创建一个海龟对象,并为其命名。创建后,海龟默认位于屏幕中央,面朝东方(即屏幕右侧)。

alex = turtle.Turtle()

创建好海龟后,你可以使用点号(.)调用其方法来控制它。例如,让名为alex的海龟前进或转向。

以下是控制海龟的基本命令示例:

alex.forward(100)  # 海龟向前移动100个单位
alex.left(90)      # 海龟向左转90度
alex.right(90)     # 海龟向右转90度

上一节我们学习了如何创建和控制海龟,本节中我们来理解海龟绘图所在的坐标系。

Turtle屏幕的坐标系定义如下:X轴从左边的-160到右边的+160,Y轴从底部的-160到顶部的+160。海龟的初始位置(0,0)位于这个坐标系的中心。

理解海龟的方向至关重要。每个海龟对象都记录着自己当前面对的方向。初始状态下,海龟面朝东方(X轴正方向)。

当你命令海龟左转或右转时,旋转是基于它当前面对的方向进行的。例如,如果海龟面朝东,执行left(90)会使它面朝北;执行right(90)会使它面朝南。

请记住:海龟的每一次移动和转向,都依赖于它当前的方向。


本节课中我们一起学习了Python Turtle图形库的基础知识。我们了解了如何导入库、创建屏幕和海龟对象,并使用forward()left()right()等基本命令控制海龟移动。同时,我们明确了Turtle的坐标系以及基于当前方向进行转向的核心概念。掌握这些是进行更复杂创意编程绘图的第一步。

13:上下文问题查看 🧭

在本节课中,我们将学习如何通过“在上下文中查看问题”的功能来更好地理解和完成课程作业。这个功能能帮助你看到问题在原始页面中的位置和相关的代码,从而更准确地找到答案。


如何利用上下文功能

上一节我们介绍了作业中可能遇到的问题。本节中我们来看看一个具体的解决方法。

当你遇到一个难以理解的问题时,直接看问题本身可能不够。作业中的问题通常是基于其上方或周围的代码情境提出的。

以下是操作步骤:

  1. 在作业界面中找到你感到困惑的问题。
  2. 点击该问题旁边的 “在上下文中查看” 按钮或链接。
  3. 页面将跳转,并显示该问题在原始学习材料(如课程页面或代码示例)中出现的位置。

功能应用示例

为了让你更清楚地理解,我们来看一个具体的例子。

假设你遇到的问题是:“一个Turtle对象在首次创建时面向哪个方向?

如果只看这个问题,你可能需要回忆或猜测。但通过使用“在上下文中查看”功能,你可以看到提出这个问题时所在的完整代码块:

import turtle

# 创建一只新的海龟
my_turtle = turtle.Turtle()
# 让海龟向前移动
my_turtle.forward(100)

通过运行或阅读这段上下文代码,你可以直观地观察到海龟被创建后的初始状态和动作,从而更容易推断出正确答案(例如,初始方向通常是0度,即正东方向)。

因此,如果你在回答问题时有困难,请记住使用“在上下文中查看”功能。


本节课中我们一起学习了如何利用“在上下文中查看”功能来辅助完成编程作业。关键在于不要孤立地看待问题,而是要回到它出现的原始情境中,结合相关的代码和说明来寻找线索。这个方法能帮助你更有效地理解和解决学习过程中遇到的挑战。

14:修复Turtle错误1 🐢

在本节课中,我们将学习如何修复一段涉及海龟绘图(Turtle)的代码。我们将通过识别并修正一系列常见的编程错误,包括命名规范、语法错误和拼写错误,最终让代码正确运行。

概述

我们有一段存在错误的代码,目标是使其能够正常运行并绘制出一个大写的“L”形状。我们将逐步分析错误信息,定位问题所在,并进行修正。

错误分析与修复

上一节我们概述了任务,本节中我们来看看具体的错误和修复步骤。

首先运行代码时,我们在第二行遇到了第一个错误:“named screen is not defined”。

问题根源:在编程惯例中,类名应以大写字母开头。这标志着它是一个重要的类别,而不仅仅是某个事物的名称。这与英语习惯相反:在英语中,我们不会将“turtle”这种类别名称大写,但会将“Alicia”这样的人名大写。在编程中,我们大写类名,而不大写单个对象名。

修复方法:将 screen 改为 Screen

# 错误示例
screen = turtle.Screen()
# 正确示例
Screen = turtle.Screen()

修正后,我们在第四行遇到了第二个错误:“missing one argument”。

问题根源:第四行代码 le do right 90 看起来似乎正确。此时,我们应该检查它上面的那一行。仔细观察会发现,第三行缺少了闭合括号。

修复方法:为第三行代码加上括号。

# 错误示例
turtle = turtle.Turtle
# 正确示例
turtle = turtle.Turtle()

现在代码可以编译运行了,但并未产生预期的图形。它本应右转90度,前进,再左转90度。

问题根源:前进命令 forward 后面缺少了前进距离参数 150。同时,在图片下方的错误信息中,第七行提示“There‘s no forward”。我们发现第七行的 forward 被错误地写成了大写字母开头的 Forward

修复方法:为 forward 命令添加参数 150,并将 Forward 改为全小写的 forward。在Python中,除了类名以及 NoneTrueFalse 等关键字外,其他都应使用小写。

# 错误示例
turtle.forward
turtle.Forward(75)
# 正确示例
turtle.forward(150)
turtle.forward(75)

最终效果

完成所有修正后,再次运行代码。海龟初始方向朝东,执行 right(90) 后转向南,然后前进150个单位,接着 left(90) 转向东,最后前进75个单位,成功绘制出一个大写的“L”形状。

总结

本节课中我们一起学习了如何调试海龟绘图代码。我们修复了类名大小写错误、缺少括号的语法错误、函数参数缺失以及函数名拼写错误。关键要点在于:遵循类名大写的惯例,注意语法细节(如括号),并确保函数调用时参数完整且名称拼写正确。掌握这些调试技巧对编程至关重要。

15:修复Turtle错误2 🐢

在本节课中,我们将学习如何诊断和修复一段存在多处错误的Python Turtle代码。我们将通过逐行分析错误信息,理解常见的语法和逻辑问题,并最终使程序成功运行。


上一节我们介绍了调试的基本思路,本节中我们来看看一个具体的、包含多个错误的Turtle绘图程序案例。

首先,我们尝试保存并运行这段有问题的代码。程序立即报错,提示在第二行有“bad input”。

以下是错误分析的第一步:

  • 问题在于第二行的 Import 使用了首字母大写。在Python中,除了 NoneTrueFalse 等少数特例外,所有关键字都应使用小写。正确的写法是 import

修正这个错误后,我们再次运行程序。此时,错误信息变为:第5行缺少一个必需的参数 angle

然而,观察第5行代码 turtle.left(90)angle 参数 90 明明存在。这是一个重要的调试技巧:当错误指向某一行,而该行本身看起来没有问题时,请检查它前面的代码

于是我们检查第4行,发现 turtle.forward(100 缺少了闭合的右括号 )。这导致Python解释器在解析语法时产生了混乱,误报了第5行的错误。修正后,代码变为 turtle.forward(100)

再次运行程序,我们遇到了第三个错误:第7行的 turn 未被识别。

turn 并不是Turtle模块的有效命令。如果我们想让海龟左转,应该使用关键字 left。因此,我们将第7行的 turn 修改为 left

程序现在可以启动了,但绘图没有完全结束。这是因为最后一条 forward 指令没有指定前进的距离。根据图形推断,我们需要前进75个单位。

所以,我们将最后一行修改为 turtle.forward(75)

完成所有修正后,程序终于成功运行,绘制出了预期的正方形图案。


本节课中我们一起学习了如何系统地修复一个多错误的程序。我们回顾一下关键步骤:首先,注意Python关键字的大小写规则;其次,当错误行本身无误时,要检查其上方代码的语法完整性(如括号匹配);然后,确认所使用的函数或方法名称是否正确;最后,检查函数调用是否提供了所有必需的参数。通过耐心地逐一排查这些常见问题点,就能有效地完成调试工作。

16:解决绘制字母F问题 🐢

在本节课中,我们将学习如何使用Python的Turtle库来绘制一个字母“F”。我们将通过分解问题、逐步编写代码并调试,最终完成图形的绘制。


概述

我们将通过一个具体的编程问题——绘制字母“F”——来学习如何将复杂任务分解为简单的步骤。我们将使用Turtle图形库,它允许我们通过控制一个“海龟”光标来绘制图形。课程将涵盖初始化环境、控制海龟移动、抬笔落笔操作以及调试代码。


导入库与创建海龟

首先,我们需要导入必要的库并设置绘图环境。以下是第一步的代码:

import turtle

导入Turtle库后,我们创建一个绘图窗口和一只海龟,并为其命名。

# 创建绘图窗口和海龟
screen = turtle.Screen()
t = turtle.Turtle()

现在,我们有了一个名为 t 的海龟,它默认面向东方(屏幕右侧)。


绘制F的第一笔(长竖线)

上一节我们创建了海龟,本节中我们来看看如何绘制字母“F”的第一笔,即长竖线。

海龟默认朝东。要向上(北)画线,需要先左转90度。然后,我们让海龟向前移动一段距离,例如100像素,以画出长竖线。

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

t.left(90)      # 向左转90度,使海龟朝北
t.forward(100)  # 向前移动100像素,画出长竖线

执行后,海龟将位于画布顶部,面朝北方。


绘制F的顶部横线

绘制完竖线后,海龟位于顶部且朝北。要绘制顶部的短横线,需要让海龟向右转90度,使其朝东,然后向前移动一段较短的距离,例如50像素。

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

t.right(90)     # 向右转90度,使海龟朝东
t.forward(50)   # 向前移动50像素,画出顶部横线

现在,字母“F”的顶部结构已经完成。


移动海龟并绘制中间横线

接下来,我们需要在不画线的情况下,将海龟移动到竖线的中间位置,以绘制中间的横线。这需要用到“抬笔”和“落笔”操作。

首先,我们抬起笔,这样移动时就不会留下痕迹。然后,将海龟移动到目标坐标 (0, 60)。最后,落下笔并向前移动,画出中间的横线。

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

t.penup()           # 抬起笔,移动时不画线
t.goto(0, 60)       # 移动到坐标 (0, 60)
t.pendown()         # 落下笔,准备画线
t.forward(50)       # 向前移动50像素,画出中间横线

注意:在Python的Turtle库中,所有方法名都是小写,例如 penup()pendown()


代码整合与检查

我们已经完成了各个步骤的代码。现在,将它们整合在一起,并检查是否有错误。

以下是完整的代码:

import turtle

# 创建海龟
t = turtle.Turtle()

# 绘制长竖线
t.left(90)
t.forward(100)

# 绘制顶部横线
t.right(90)
t.forward(50)

# 移动并绘制中间横线
t.penup()
t.goto(0, 60)
t.pendown()
t.forward(50)

# 保持窗口打开
turtle.done()

运行此代码,你应该能看到一个完整的字母“F”被绘制出来。


总结

本节课中,我们一起学习了如何使用Python的Turtle库绘制字母“F”。我们分解了绘制过程,逐步实现了初始化、方向控制、移动和抬笔/落笔操作。记住,编程中常有多种方法解决同一问题,关键是理解每一步的逻辑。通过这个练习,你掌握了将图形任务转化为代码的基本技能。

17:多Turtle协同 🐢

在本节课中,我们将学习如何创建和使用多个Turtle对象。通过理解每个Turtle如何独立地管理自己的属性和行为,你将能够编写更复杂、更有趣的图形程序。

概述

当我们从类创建对象时,可以根据需要创建任意数量的对象。每个对象都会维护自己的数据(即属性),并拥有相同的行为能力。例如,所有的Turtle对象都知道如何前进、左转和右转。接下来,我们将深入探讨这一概念。

深入理解Turtle对象

使用Turtle类,你可以创建任意数量的Turtle。每个Turtle在创建时都位于画布中心,面朝东方。它会跟踪自己的数据,例如颜色、朝向和位置。所有Turtle都能执行相同的动作,如前进、左转、右转、抬笔和落笔。每个Turtle的初始状态都是在窗口中心面朝东方,默认颜色为黑色。

让我们通过一个例子来具体看看。在以下代码中,我们创建了一个名为Zri的Turtle,并让它执行一系列动作:

# 创建Turtle对象Zri
Zri = Turtle()
# 设置朝向为90度(面朝北)
Zri.setheading(90)
# 前进100个单位
Zri.forward(100)
# 右转120度
Zri.right(120)
Zri.forward(100)
Zri.right(120)
Zri.forward(100)
Zri.right(120)

这段代码让Zri面朝北,前进100个单位,然后右转120度并重复三次,最终回到初始朝向。

使用多个Turtle

现在,如果我们创建两个不同的Turtle对象,例如Zri和Chad,情况会有所不同。Zri会执行上述动作,但当我们创建Chad时,它同样会在画布中心被创建,面朝东方。因此,Chad会执行另一套指令。

以下是使用两个Turtle的示例:

# 创建第一个Turtle对象Zri
Zri = Turtle()
Zri.setheading(90)
Zri.forward(100)
Zri.right(120)
Zri.forward(100)
Zri.right(120)
Zri.forward(100)
Zri.right(120)

# 创建第二个Turtle对象Chad
Chad = Turtle()
# Chad从中心开始,面朝东方,执行不同的动作
Chad.left(45)
Chad.forward(150)

在这个例子中,Zri绘制了一个三角形,而Chad则从中心出发,左转45度后前进150个单位。

注意事项

在使用多个Turtle时,请记住每个Turtle都有自己的朝向和位置。这意味着你需要分别管理每个Turtle的状态,以确保它们按照预期的方式行动。

总结

本节课中,我们一起学习了如何创建和使用多个Turtle对象。我们了解到,每个Turtle都是独立的,拥有自己的属性和行为。通过分别控制它们,我们可以创建出更复杂和有趣的图形。记住,关键在于理解每个Turtle的独立性和如何管理它们的状态。

18:创建实用函数 🛠️

在本节课中,我们将学习如何将一段绘制等边三角形的代码封装成一个可复用的函数。我们将从分析现有代码开始,逐步将其转换为一个函数,并探讨如何设置参数、默认值以及如何调用它。

分析现有代码

首先,我们来看一段绘制等边三角形的代码。这段代码使用 turtle 图形库,绘制一个底边水平的等边三角形。这种形状非常适合后续用来构建像房子屋顶这样的图形。

import turtle

# 初始化turtle
alicia = turtle.Turtle()

# 绘制等边三角形的代码
for _ in range(3):
    alicia.forward(100)  # 假设边长为100
    alicia.left(120)

如果保存并运行这段代码,屏幕上将显示一个每条边长度相同、底边水平的等边三角形。

将代码转换为函数

上一节我们分析了绘制三角形的代码,本节中我们来看看如何将其封装成一个函数。函数化的目的是提高代码的复用性。

以下是创建 triangle 函数的关键步骤:

  1. 定义函数:使用 def 关键字定义函数,并为其命名。
  2. 设置参数:确定函数需要哪些输入。这里我们需要 turtle 对象和三角形的 length
  3. 设置默认值:为参数设置默认值,使函数调用更灵活。
  4. 替换局部变量:将函数内部对特定 turtle 对象(如 alicia)的引用,替换为参数名。
  5. 调用函数:定义函数后,必须调用它才能执行。

让我们开始实践。首先,在导入语句后定义函数。

import turtle

def triangle(turtle_obj, length=100):
    """
    绘制一个等边三角形。
    :param turtle_obj: turtle对象,用于绘图
    :param length: 三角形的边长,默认为100
    """
    for _ in range(3):
        turtle_obj.forward(length)  # 使用传入的length参数
        turtle_obj.left(120)

注意,我们将函数内的 alicia 全部替换为参数名 turtle_obj,并且 forward 的距离使用了参数 length

调用函数并调试

函数定义完成后,我们需要创建 turtle 对象并调用这个函数。

# 创建turtle对象
alicia = turtle.Turtle()

# 调用函数,传入turtle对象和边长
triangle(alicia, length=20)

如果运行代码后三角形大小没有变化,请检查是否在函数内部正确使用了 length 参数,而不是一个固定的数值。

扩展应用:绘制多个三角形

现在我们已经有了一个可用的三角形绘制函数,可以轻松地扩展代码来绘制多个三角形。例如,我们可以在每个三角形之间抬起画笔,移动位置,然后绘制下一个。

以下是绘制一系列三角形的示例:

import turtle

def triangle(turtle_obj, length=100):
    for _ in range(3):
        turtle_obj.forward(length)
        turtle_obj.left(120)

alicia = turtle.Turtle()

# 绘制第一个三角形
triangle(alicia, 50)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-crt-prog/img/b61c129b2c397e392517fe46027ff59b_3.png)

# 抬起画笔,移动到新位置
alicia.penup()
alicia.forward(80)
alicia.pendown()

# 绘制第二个三角形
triangle(alicia, 30)

通过这种方式,我们可以用同一个函数高效地创建复杂的图案。

总结

本节课中我们一起学习了如何将具体的绘图代码抽象成一个通用的函数。我们掌握了定义函数、设置参数与默认值、在函数内部使用参数以及正确调用函数的方法。将代码函数化是编程中的一项核心技能,它能极大地提升代码的清晰度、可维护性和复用性。

19:图形颜色填充技巧 🎨

在本节课中,我们将学习如何在Python的Turtle图形库中为绘制的形状填充颜色。我们将了解如何设置填充颜色、画笔颜色,以及如何正确地开始和结束填充操作。


上一节我们介绍了如何绘制基本图形,本节中我们来看看如何为这些图形填充颜色。

填充形状的基本方法

要为形状填充颜色,我们需要在绘制形状之前开始填充,并在绘制完成后结束填充。以下是实现这一过程的基本步骤。

以下是填充一个圆形的基本代码示例:

import turtle

# 创建屏幕和乌龟对象
space = turtle.Screen()
t1 = turtle.Turtle()

# 设置颜色(同时设置画笔和填充色)
t1.color("red")

# 开始填充
t1.begin_fill()
# 绘制一个半径为50的圆
t1.circle(50)
# 结束填充
t1.end_fill()

space.mainloop()

运行此代码将创建一个填充为红色的圆形。

单独设置填充颜色

我们也可以只设置填充颜色,而不改变画笔颜色。此时,图形的轮廓将使用默认的画笔颜色(黑色)绘制。

以下是仅设置填充颜色的代码示例:

import turtle

space = turtle.Screen()
t1 = turtle.Turtle()

# 仅设置填充颜色
t1.fillcolor("blue")

t1.begin_fill()
t1.circle(50)
t1.end_fill()

space.mainloop()

运行此代码将创建一个填充为蓝色、轮廓为黑色的圆形。

同时设置画笔和填充颜色

color 方法可以接受两个参数,分别用于设置画笔颜色和填充颜色。第一个颜色是画笔颜色,第二个颜色是填充颜色。

以下是同时设置两种颜色的代码示例:

import turtle

space = turtle.Screen()
t1 = turtle.Turtle()

# 同时设置画笔颜色和填充颜色
# 一个参数是画笔颜色,第二个是填充颜色
t1.color("green", "yellow")

t1.begin_fill()
t1.circle(50)
t1.end_fill()

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-crt-prog/img/bcdae144263a93306b77aa9eefa94cc7_3.png)

space.mainloop()

运行此代码将创建一个轮廓为绿色、填充为黄色的圆形。


本节课中我们一起学习了在Turtle图形中填充颜色的技巧。我们掌握了三种方法:使用 color() 同时设置画笔和填充色,使用 fillcolor() 仅设置填充色,以及通过 color() 的双参数形式分别指定两种颜色。关键在于使用 begin_fill()end_fill() 来包裹需要填充的绘图指令。

20:欢迎进入第二周 🎉

在本节课中,我们将要学习如何编写简单的类。我们将了解类的各个组成部分,识别并修复创建类时常见的错误,并最终编写一个模拟现实世界对象的简单类。

本周学习目标

上一节我们介绍了本周的总体安排,本节中我们来看看具体的学习目标。以下是本周我们将要掌握的内容:

  • 学习如何编写简单的类。
  • 学习如何识别类的所有组成部分,包括:类的定义、构造函数(__init__方法)、__str__方法、属性和方法。
  • 修复在开始创建类时常见的代码错误,例如在将self作为参数传递时忘记写入。
  • 预测对象创建、方法调用和对象打印时的执行流程。
  • 解释对象和类之间的区别。
  • 编写一个模拟现实世界对象的简单类,例如一个可以摇晃并获取答案的“神奇八号球”。

关于实践项目

上一节我们明确了学习目标,本节中我们来看看具体的实践项目。我们将编写一个类似“神奇八号球”的类。

但与通常给出“是”、“否”或“也许”等答案的神奇八号球不同,我们编写的类可以告诉你一个“运势”,例如“你将在考试中取得优异成绩”。

祝你好运。😊


本节课中我们一起学习了第二周的学习目标和实践项目。我们明确了本周将聚焦于理解并编写简单的类,识别其结构,并最终完成一个模拟“神奇八号球”的编程实践。

21:Python类编写指南 🚗

在本节课中,我们将学习如何在Python中定义自己的类。类是面向对象编程的核心,它允许我们将数据(属性)和功能(方法)捆绑在一起,创建自定义的数据类型。我们将从创建一个简单的Car类开始,逐步为其添加属性和方法。

定义类的基本结构

在Python中,我们使用class关键字来定义一个类。类名通常以大写字母开头。

class Car:
    pass

pass是一个占位符关键字,它允许代码在结构不完整的情况下通过编译。这在你需要先搭建框架,稍后再填充具体实现时非常有用。

使用这个类,我们可以创建一个对象:

c = Car()
print(type(c))

运行上述代码,输出会显示<class '__main__.Car'>,这表示我们已经成功定义了一个类并创建了它的一个实例对象。不过,这个对象目前还没有任何实际信息。

初始化对象属性:__init__方法

上一节我们创建了一个空的类。为了让对象包含有意义的数据,我们需要一个特殊的方法来初始化对象的属性。这个方法叫做__init__

__init__方法在创建新对象时自动调用。它的第一个参数必须是self,它代表当前正在创建的对象实例。我们可以通过self来设置对象的属性。

以下是定义Car类并初始化其make(品牌)、model(型号)和color(颜色)属性的方法:

class Car:
    def __init__(self, make, model, color):
        self.make = make
        self.model = model
        self.color = color

请注意,self.makeself.modelself.color是对象的属性,而makemodelcolor是传递给方法的局部变量。虽然它们名称相同,但代表的是两个不同的事物。

现在,我们可以这样创建并打印一个Car对象:

c = Car("Ford", "Volt", "blue")
print(c)

然而,直接打印对象c仍然只会输出类似<__main__.Car object at 0x...>的信息,这并不直观。

美化对象输出:__str__方法

为了让打印对象时显示更有用的信息,我们需要定义另一个特殊方法:__str__。当使用print()函数打印一个对象时,Python会自动调用这个对象的__str__方法,并打印其返回的字符串。

这个过程被称为“方法重写”。Car类默认继承了一个基础的__str__方法,它只返回类名。我们需要重写它以提供自定义信息。

以下是添加__str__方法的Car类:

class Car:
    def __init__(self, make, model, color):
        self.make = make
        self.model = model
        self.color = color

    def __str__(self):
        return "Make: " + self.make + ", Model: " + self.model + ", Color: " + self.color

现在,当我们创建并打印对象时:

c1 = Car("Ford", "Volt", "blue")
print(c1)  # 输出:Make: Ford, Model: Volt, Color: blue

c2 = Car("Toyota", "Camry", "red")
print(c2)  # 输出:Make: Toyota, Model: Camry, Color: red

这样,我们就得到了清晰、易读的对象信息。

核心要点总结

本节课中我们一起学习了Python类的基础编写指南。以下是创建类时需要掌握的核心步骤:

  1. 定义类:使用class关键字,类名首字母大写。
  2. 初始化属性:定义__init__(self, ...)方法来设置对象的初始状态。记住,所有实例方法的第一个参数都必须是self
  3. 定义字符串表示:重写__str__(self)方法,返回一个描述对象的字符串,以便使用print()时能输出友好的信息。

掌握__init____str__这两个方法是开始创建自定义类的关键。它们让你的对象既能存储数据,又能清晰地展示自己。

22:使用CodeLens调试对象创建过程 🐛

在本节课中,我们将学习如何使用CodeLens工具,一步步地观察和理解在Python中创建一个类对象并打印它时,程序内部究竟发生了什么。

上一节我们介绍了类和对象的基本概念,本节中我们来看看对象创建和打印的具体执行流程。

代码示例与执行流程

我们有一个Book类,当我们运行程序时,它会打印出类似“title: a wrinkle in time, author: Madeleine L'Engle”的信息。这个过程是如何发生的呢?

以下是使用CodeLens逐步调试的步骤:

  1. 定义类:程序首先会定义Book类。
  2. 定义主方法:接着,它会定义main函数。
  3. 执行主方法:然后,程序跳转到第11行开始执行main函数。
  4. 创建对象:在第12行,我们创建Book对象并初始化其属性(或变量)。这一步会自动调用__init__函数。
  5. 调用__init____init__方法接收self(指向当前对象实例的引用)、titleauthor参数,并将这些信息设置到对象中。
  6. 打印对象:当我们尝试打印这个对象时,Python会自动寻找并调用该对象的__str__方法。
  7. 调用__str____str__方法返回一个字符串。
  8. 输出结果:最后,这个返回的字符串被打印到屏幕上。

核心概念解析

在创建对象时,有两个特殊方法被自动调用:

  • __init__方法:这是对象的构造器或初始化器。它的作用是初始化对象的属性。
    def __init__(self, title, author):
        self.title = title
        self.author = author
    
  • __str__方法:当使用print()函数打印对象时,Python会调用此方法来获取对象的字符串表示形式。
    def __str__(self):
        return f"title: {self.title}, author: {self.author}"
    

通过使用CodeLens逐步执行代码,我们可以清晰地看到程序控制流如何跳转,以及这些特殊方法是如何在对象生命周期中被调用的。这有助于我们精确理解从类定义到对象创建和使用的完整过程。

本节课中我们一起学习了如何使用CodeLens调试工具来跟踪对象创建和打印的详细步骤。我们看到了__init____str__这两个特殊方法在何时以及如何被自动调用,从而加深了对Python面向对象编程内部机制的理解。

23:为类添加方法教程 🧩

在本节课中,我们将学习如何为Python类添加新的方法。我们将通过一个具体的例子,演示如何为一个表示“人”的类添加一个用于生成姓名首字母缩写的方法。


上一节我们介绍了类的初始化方法和字符串表示方法。本节中我们来看看如何为类添加一个全新的、具有特定功能的方法。

假设我们有一个Person类,它包含first_name(名)和last_name(姓)两个属性。现在,我们希望为这个类添加一个方法,用于生成并返回此人姓名的小写首字母缩写。

为类添加方法的过程非常简单。就像定义 __init____str__ 方法一样,我们在类内部定义一个函数即可。这个新方法必须将 self 作为第一个参数,以便在方法内部访问当前对象的属性。

以下是添加 initials 方法的步骤:

  1. 在类定义内部,使用 def 关键字定义新方法。
  2. 方法名可以自定义,这里我们使用 initials
  3. 方法的第一个参数必须是 self
  4. 在方法体内,通过 self.first_nameself.last_name 访问对象的属性。
  5. 使用字符串索引 [0] 获取名字和姓氏的第一个字符。
  6. 使用 .lower() 方法将字符转换为小写。
  7. 将两个小写首字母拼接起来,并使用 return 语句返回结果。

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

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

    # 这是我们新添加的方法
    def initials(self):
        # 获取名字的首字母并转为小写
        first_initial = self.first_name[0].lower()
        # 获取姓氏的首字母并转为小写
        last_initial = self.last_name[0].lower()
        # 返回拼接后的首字母缩写
        return first_initial + last_initial

# 创建Person对象并测试新方法
person1 = Person("John", "Doe")
print(person1.initials())  # 输出:jd

运行上述代码,将会成功打印出 “jd”。这表明我们成功地为 Person 类添加并调用了一个新方法。

在编写类方法时,有两个关键点必须牢记:

  • 方法定义时,第一个参数必须是 self
  • 在方法内部访问对象的属性时,必须通过 self.属性名 的形式(例如 self.last_name)。如果忘记添加 self. 前缀,Python解释器将无法识别该变量,并会报错提示“未定义”。


本节课中我们一起学习了如何为Python类添加新的方法。我们了解了定义方法的语法,明确了 self 参数的关键作用,并通过一个生成姓名首字母的实例掌握了从访问属性到返回结果的完整流程。为类添加方法是扩展其功能、封装相关操作的基本方式。

24:类设计方法论 🎯

在本节课中,我们将学习如何为一个简单的猜数字游戏设计一个类。我们将从分析游戏需求开始,识别出类所需的属性和方法,并最终用代码实现它。


从需求到设计

上一节我们讨论了类的基本概念。本节中,我们来看看如何为一个具体的问题设计类。以猜数字游戏为例:游戏主持人心中想一个1到100之间的随机数,玩家进行猜测,主持人会告知猜测是“太高”、“太低”还是“正确”。

为了将这个游戏转化为代码,我们需要思考这个类需要什么。

识别类的属性

设计类时,首先要考虑的是属性,即这个类的对象需要跟踪哪些数据。

以下是猜数字游戏对象需要跟踪的数据:

  • number:主持人心中所想的随机数。
  • guess:玩家当前猜测的数字。

定义类的方法

接下来,我们需要定义类的方法,即对象能够执行的行为。对于这个游戏,核心行为就是“玩游戏”。

因此,我们需要一个 play 方法。这个方法将包含一个 while 循环,直到玩家猜对为止。在每次循环中,如果猜测不正确,程序会要求用户输入一个数字,并告知其猜测结果是太高、太低还是正确。


代码实现

现在,让我们尝试为这个设计编写代码。

import random

class GuessingGame:
    def __init__(self):
        # 属性:随机数和当前猜测
        self.number = random.randint(1, 100)
        self.guess = None

    def play(self):
        # 方法:执行游戏逻辑
        while self.guess != self.number:
            # 获取用户输入
            self.guess = int(input("请猜一个1到100之间的数字:"))

            # 判断猜测结果
            if self.guess < self.number:
                print("太低了!")
            elif self.guess > self.number:
                print("太高了!")
            else:
                print("恭喜你,猜对了!")

# 创建游戏对象并开始游戏
game = GuessingGame()
game.play()


本节课中我们一起学习了面向对象编程中的类设计方法论。我们通过一个猜数字游戏的例子,实践了如何从需求中识别出类的属性(如 numberguess)和方法(如 play),并最终将其转化为可运行的代码。掌握这种“需求分析 -> 识别属性与方法 -> 代码实现”的设计流程,是进行创造性编程和构建复杂程序的基础。

25:用户输入获取 🎮

在本节课中,我们将要学习如何在Python程序中获取用户的输入。这是创建交互式程序(如游戏或工具)的基础。我们将了解input()函数的工作原理,以及如何处理用户输入的数据类型。

概述

当你在玩游戏时,通常需要从用户那里获取输入。Python提供了一个内置函数input()来实现这个功能。本节将介绍如何使用input()函数,并重点说明其返回的数据类型始终是字符串。如果你需要对输入进行数学运算,必须将其转换为整数或其他数字类型。

input()函数的基本用法

input()函数用于从用户那里获取输入。你可以包含一条消息,作为提示显示给用户。该函数会返回用户输入的内容,你需要将其存储在一个变量中以便后续使用。

以下是使用input()函数的基本语法:

user_input = input("提示信息:")

例如,你可以询问用户的名字并打印出来:

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

运行这段代码时,会弹出一个窗口询问“What is your name?”,用户输入名字后,程序会打印问候语。

处理数字输入

input()函数总是返回字符串。如果你期望用户输入一个数字并打算对其进行算术运算,就必须先将字符串转换为整数。

转换需要使用内置的int()函数:

number_string = input("Enter a number: ")
number_int = int(number_string)
result = number_int + 10
print("Your number plus 10 is:", result)

如果你忘记转换而直接进行数学运算,Python会报错。例如,尝试将字符串与整数相加会导致类型错误。

# 这会导致错误
age = input("How old are you? ")
next_year_age = age + 1  # 错误:无法将字符串与整数相加

正确的做法是先转换:

age = input("How old are you? ")
age_int = int(age)
next_year_age = age_int + 1
print("Next year you will be:", next_year_age)

关键要点总结

本节课我们一起学习了如何获取和处理用户输入。

  • input()函数用于获取用户输入,并可以附带提示信息。
  • 该函数总是返回一个字符串
  • 如果你计划对输入进行数学运算,必须使用int()函数将其转换为整数。
  • 忽略类型转换会导致程序错误。

记住这些要点,你就能在程序中有效地与用户进行交互了。

26:随机数生成实践 🎲

在本节课中,我们将学习如何在Python中生成随机数,这是开发猜数字游戏等程序的关键步骤。我们将重点介绍random模块的两个核心函数。

概述

我们将探索Python内置的random模块,学习如何生成随机浮点数和指定范围内的随机整数。这些技能对于创建需要随机元素的程序至关重要。

生成随机浮点数

首先,我们需要导入random模块。该模块包含一个名为random()的函数,它可以生成一个介于0.0(包含)和1.0(不包含)之间的随机浮点数。

以下是使用random()函数生成10个随机数的示例代码:

import random
for i in range(10):
    print(random.random())

运行这段代码,你会看到类似0.94这样的输出。这些数字非常接近0,但永远不会正好是1。这个函数适用于需要小数随机值的情况。

生成指定范围的随机整数

上一节我们介绍了生成随机小数的方法,但在猜数字游戏中,我们通常需要的是整数。本节中我们来看看如何生成指定范围内的随机整数。

random模块提供了randint()函数来解决这个问题。它接受两个参数:一个下限值和一个上限值,并返回一个介于这两者之间(包含上下限)的随机整数。

以下是使用randint()函数生成10个1到10之间随机整数的示例:

import random
for i in range(10):
    print(random.randint(1, 10))

运行这段代码,你可能会看到1,也可能看到10。多次运行可以验证,randint(1, 10)确实会等概率地生成1到10之间的所有整数,包括1和10。这个函数非常适合用于我们的猜数字游戏。

总结

本节课中我们一起学习了Python中生成随机数的基本方法。我们首先使用random.random()生成0到1之间的随机小数,然后使用random.randint(low, high)生成指定范围内的随机整数。掌握这两个函数,你就能为各种程序(如猜数字游戏)注入随机性了。

27:创建NumGuess类 🎲

在本节课中,我们将学习如何创建一个名为 NumGuess 的类,用于实现一个简单的猜数字游戏。我们将整合之前学过的用户输入和随机数生成知识,通过面向对象编程的方式构建一个结构清晰、可复用的游戏程序。


导入模块与类定义

首先,我们需要导入 random 模块,以便在游戏中生成随机数字。接着,我们将定义一个类。在Python中,定义类使用 class 关键字,而不是 def。类名通常以大写字母开头,如果包含多个单词,则采用驼峰命名法。我们将这个类命名为 NumGuess

import random

class NumGuess:

初始化方法(构造函数)

上一节我们定义了类,本节中我们来看看如何初始化它。我们将定义 __init__ 方法,也就是构造函数。这个方法除了必需的 self 参数外,不需要其他参数。在构造函数中,我们将初始化两个属性:游戏随机选择的数字和玩家的当前猜测。

    def __init__(self):
        self.num = random.randint(1, 100)
        self.guess = -1
  • self.num:使用 random.randint(1, 100) 生成一个1到100之间的随机整数。
  • self.guess:初始化为 -1。因为我们的有效猜测范围是1到100,所以 -1 可以表示玩家尚未进行有效猜测。

定义游戏主循环方法

现在,我们来定义游戏的核心逻辑——play 方法。这个方法也是一个对象方法,因此第一个参数必须是 self。游戏将在一个循环中运行,直到玩家猜中数字为止。

以下是 play 方法的基本结构:

    def play(self):
        while self.num != self.guess:
            # 获取并处理用户输入
            # 判断猜测结果并给出提示

获取与处理用户输入

在循环内部,我们需要获取玩家的输入。使用 input 函数提示玩家输入一个数字,但请注意,input 返回的是字符串类型,我们需要将其转换为整数。

            guess_str = input("Pick a number from 1 to 100: ")
            self.guess = int(guess_str)

判断猜测结果并给出提示

获取玩家的猜测后,我们需要将其与随机数 self.num 进行比较,并给出相应的提示:猜高了、猜低了还是猜对了。

以下是判断逻辑的代码:

            if self.guess > self.num:
                print("Too high.")
            elif self.guess < self.num:
                print("Too low.")
            else:
                print("Correct.")

创建对象并运行游戏

类定义完成后,我们需要实际创建它的一个对象(实例)来运行游戏。创建对象的方法是使用类名后跟括号 (),这会自动调用类的 __init__ 方法。然后,我们调用该对象的 play 方法来启动游戏。

my_game = NumGuess()
my_game.play()

代码运行示例与调试

让我们运行一下代码。首先,导入模块并定义类。然后,创建游戏对象 my_game,这会触发 __init__ 方法,生成随机数(例如50)并将初始猜测设为-1。接着,调用 play 方法。

游戏进入循环,提示用户输入。如果输入40,程序会判断为“Too low.”并再次提示输入。如果下一次输入50,程序会判断为“Correct.”。此时,循环条件 self.num != self.guess 不再成立,循环结束,游戏完成。

在开发过程中,如果遇到变量名拼写错误(例如将 guess_str 误写为 gu_str),程序会报错。这时需要检查并修正代码,然后重新运行。


本节课中我们一起学习了如何创建一个完整的 NumGuess 猜数字游戏类。我们涵盖了类的定义、构造函数的初始化、核心游戏循环的逻辑、用户输入的处理、条件判断以及对象的创建与使用。这是将基本编程概念整合到面向对象框架中的一个典型示例。

28:课程概述与目标 🎮

在本节课中,我们将要学习第三周的核心内容,重点是多类交互、图像处理以及面向对象编程的深入应用。

欢迎来到第三周。本周,你将学习如何处理交互的类,即多个类之间的协作。你将特别专注于处理图像和像素,进行图片或图像的操作。你还将学习如何分析问题领域,以识别其中的类、属性和方法。你将能够描述对象属性和类属性之间的区别,并能够预测包含多个类的代码的输出结果。此外,你将能够修复和编写包含多个类的代码。在本周结束时,你将创建一个文本冒险游戏。


第三周:核心学习目标 📋

上一节我们介绍了本周的总体方向,本节中我们来看看具体的学习目标。

以下是本周你需要掌握的核心技能列表:

  • 处理交互的类:学习如何让多个类协同工作。
  • 图像与像素操作:使用代码处理图像,进行各种变换。
  • 领域分析与类设计:从实际问题中识别并设计出合适的类、属性和方法。
  • 区分对象与类属性:理解 self.attribute(对象属性)与 ClassName.attribute(类属性)的不同。
  • 代码预测与调试:阅读和理解多类代码,预测其行为,并修复其中的错误。
  • 项目实践:综合运用所学知识,开发一个完整的文本冒险游戏。

第三周:实践项目 🏁

在掌握了上述核心概念后,我们将通过一个综合项目来应用它们。

本周的最终实践任务是创建一个文本冒险游戏。这个项目将要求你设计多个类(例如 PlayerRoomItem 等),并让它们之间进行丰富的交互,从而巩固你对面向对象编程和多类协作的理解。


本节课中我们一起学习了第三周的课程概览。我们明确了本周将聚焦于多类交互、图像处理以及面向对象设计的深化,并最终通过构建一个文本冒险游戏来整合所有技能。希望你能在本周的学习中取得丰硕的成果。

29:图像与像素基础 🖼️

在本节课中,我们将学习如何使用Python处理图像。我们将从导入图像库开始,然后学习如何加载图像、访问其像素、修改像素颜色,并最终显示处理后的结果。通过一个简单的例子,你将看到如何通过减少红色通道的值来模拟一个绿色滤镜效果。

导入图像库

首先,我们需要导入一个图像处理库。这个库提供了创建和处理图像所需的类和方法。

import image

创建图像对象

导入库之后,我们可以从一个图像文件创建一个图像对象。这里我们以名为“arch.JPG”的文件为例。

img = image.Image("arch.JPG")

获取与遍历像素列表

图像由像素组成。我们可以从图像对象中获取一个包含所有像素的列表,然后遍历这个列表来访问每一个像素。

pixel_list = img.getPixels()
for p in pixel_list:
    # 在这里对每个像素进行操作

修改像素颜色

在遍历像素时,我们可以修改每个像素的颜色值。例如,下面的代码将每个像素的红色分量设置为0。

    p.setRed(0)

更新图像并显示结果

修改像素后,我们需要更新图像对象以反映这些更改。最后,我们创建一个窗口并在其中绘制处理后的图像。

    img.updatePixel(p)
win = image.ImageWin()
img.draw(win)

由于我们将红色分量设为了0,图像会呈现出一种蓝绿色调,类似于透过绿色滤镜观看的效果。这是因为水会吸收红光,所以水体看起来是蓝绿色的。


本节课中,我们一起学习了图像处理的基础知识。我们了解了如何导入图像库、加载图像、访问和修改像素,以及显示最终结果。通过将每个像素的红色分量设置为零,我们模拟了一个绿色滤镜效果,使图像呈现出蓝绿色调。

30:创建负片图像技术 🖼️

在本节课中,我们将学习如何通过编程将一张彩色图像转换为其负片效果。负片效果是传统胶片摄影中常见的现象,它反转了图像中的所有颜色。我们将通过操作图像中每个像素的红、绿、蓝(RGB)颜色通道值来实现这一效果。


负片效果的原理

在传统胶片摄影中,冲洗出来的底片(负片)颜色与实际照片的颜色是相反的。例如,实际照片中的黑色区域在负片上会显示为白色,红色会显示为青色。这种效果可以通过反转每个颜色通道的亮度值来实现。

具体来说,对于一个8位颜色通道(值范围从0到255),反转操作就是将当前值从255中减去。其核心公式可以表示为:

新颜色值 = 255 - 原始颜色值

这个公式适用于红、绿、蓝每一个独立的颜色通道。


实现负片效果的步骤

上一节我们介绍了负片效果的原理,本节中我们来看看如何在代码中具体实现它。我们将逐个像素地处理图像,并应用上述公式。

以下是实现负片效果的具体步骤:

  1. 遍历图像的每一个像素
  2. 获取该像素原始的红色(R)、绿色(G)、蓝色(B)值
  3. 对每个颜色通道应用反转公式
    • 新红色值 = 255 - 原始红色值
    • 新绿色值 = 255 - 原始绿色值
    • 新蓝色值 = 255 - 原始蓝色值
  4. 将计算出的新RGB值设置回当前像素

代码示例

假设我们使用一个支持像素操作的图像处理库(如PIL或OpenCV),核心代码逻辑如下所示:

# 假设 image 是一个可以读取和修改像素的图像对象
for x in range(image.width):
    for y in range(image.height):
        # 获取原始像素的RGB值
        original_red, original_green, original_blue = image.getpixel((x, y))
        
        # 应用负片转换公式
        new_red = 255 - original_red
        new_green = 255 - original_green
        new_blue = 255 - original_blue
        
        # 将新颜色设置回像素
        image.putpixel((x, y), (new_red, new_green, new_blue))

运行这段代码后,原本的图像就会转变为它的负片。例如,原本偏红的区域会变为青色,原本明亮的区域会变暗,从而产生一种“幽灵般”的视觉效果,这正是原图像的色彩负片。


总结

本节课中我们一起学习了如何创建图像的负片效果。我们首先理解了负片是颜色通道值的反转,然后掌握了实现这一效果的核心公式 新值 = 255 - 原始值,最后通过遍历像素并应用该公式的代码实现了完整的转换过程。这是一种基础但强大的图像处理技术,是理解更复杂色彩操作的良好起点。

31:卡牌游戏分析 🃏

在本节课中,我们将学习如何分析一个简单的卡牌匹配游戏,并以此为例,探讨如何设计面向对象的程序。我们将识别游戏中的核心“事物”,并思考如何将它们转化为具有属性和行为的类。


游戏简介 🎮

上一节我们介绍了面向对象编程的基本概念。本节中,我们来看看一个具体的例子:一个名为“Aces and Twos”的卡牌匹配游戏。

游戏的目标是点击翻开卡牌,并找到所有花色和点数都匹配的卡牌对。当两张卡牌的点数和花色都相同时,它们就会保持翻开状态。

识别类与对象 🔍

在思考如何创建类来表示这个游戏时,我们首先要问:游戏中我们正在处理哪些“事物”?

这些“事物”被称为卡牌。一张卡牌具有花色和点数两个属性。例如,“Ace of Spades”的点数是“Ace”,花色是“Spades”。花色可能是黑桃、梅花、红心或方块。

这些是类的一个对象(一张具体的卡牌)应该知道的关于自身的数据。如果“我”是一张卡牌,“我”应该知道自己的花色和点数。在比较卡牌时,我们应该能够判断它们是否具有相同的花色和点数。

定义类的属性与方法 ⚙️

这同时也意味着对象需要具备行为。为了玩这个游戏,我们必须能够匹配卡牌,判断它们是否相等、是否是同一种卡牌。此外,我们还需要有选择或翻转卡牌的能力,点击后卡牌会翻开,如果匹配成功则保持翻开状态。

以下是我们在设计类时需要考虑的核心要素:

  • 属性:一张卡牌对象所知道的数据。
    • 点数
    • 花色
  • 方法:一张卡牌对象可以执行的行为。
    • 判断是否与另一张卡牌相等
    • 被选中时翻转(改变状态)

因此,我们开始思考类的命名、这些事物的分类、类的对象拥有哪些数据或属性,以及它们具有哪些行为或方法。


总结 📝

本节课中,我们一起学习了如何通过分析一个卡牌游戏来实践面向对象的设计思维。我们识别出游戏中的核心实体——“卡牌”,并定义了它的关键属性(点数和花色)以及必要的行为(匹配判断和状态翻转)。这个过程是构建任何面向对象程序的第一步:将问题域中的事物抽象为具有数据和行为的类。

32:UML类图入门 🧩

在本节课中,我们将学习如何使用UML类图来设计和记录面向对象程序中的类。我们将以之前玩过的“Aces and Twos”纸牌游戏为例,分析其中的Card类和Deck类,并了解如何用标准化的图表来表示它们的属性与方法。


什么是UML类图? 📊

UML类图是一种用于可视化软件系统中类、其属性(数据)和方法(行为)以及它们之间关系的标准图表。它帮助开发者在编写代码前清晰地规划程序结构。

上一节我们介绍了面向对象编程的基本概念,本节中我们来看看如何用UML类图来具体描述这些类。

分析“Aces and Twos”游戏中的类

在“Aces and Twos”游戏中,我们主要处理两种对象:单张的Card(纸牌)和整副的Deck(牌堆)。让我们逐一分析。

Card类详解

Card类代表一张单独的纸牌。根据游戏逻辑,每张牌都有其特定的属性和可以执行的操作。

以下是Card类的关键组成部分:

  • 属性

    • suit:表示花色,例如红心(Hearts)、黑桃(Spades)、梅花(Clubs)、方块(Diamonds)。
    • rank:表示牌面值,例如从A(Ace)到K(King)。
  • 方法

    • select():在游戏中点击牌时,执行选择牌的操作。
    • turn():将牌翻面。这个方法可以控制牌是保持翻开状态还是翻回背面。
    • __eq__():这是一个特殊方法,用于比较两张牌是否相同。在Python中,__eq__方法定义了使用==运算符时的行为。对于纸牌,我们通过检查两张牌的suitrank属性是否都相等来判断它们是否匹配。

代码示例:__eq__方法

def __eq__(self, other_card):
    return self.suit == other_card.suit and self.rank == other_card.rank

Deck类详解

Deck类代表一副完整的纸牌,它本质上是Card对象的一个有序集合。

以下是Deck类的关键组成部分:

  • 属性

    • cards:一个包含多张Card对象的列表。通常,一副标准的扑克牌包含52张牌,即4种花色,每种花色从A到K。
  • 方法

    • shuffle():洗牌,即随机打乱cards列表中纸牌的顺序。

如何阅读UML类图 📖

现在,让我们将上面的分析对应到UML类图的表示方法上。

在UML类图中,每个类都用一个矩形框表示。这个框被水平线分为三个部分:

  1. 顶部区域:填写类名(例如 CardDeck)。
  2. 中部区域:列出类的属性(例如 suitrankcards)。
  3. 底部区域:列出类的方法(例如 select()turn()shuffle())。

属性与方法之间通常用一条横线分隔,以增强可读性。


总结 🎯

本节课中我们一起学习了UML类图的基础知识。我们了解到UML类图是设计和记录类结构的强大工具,它通过一个标准的矩形框,清晰地展示了类的名称、属性(数据)和方法(行为)。我们以纸牌游戏为例,具体分析了CardDeck两个类,并学会了如何将它们的组成部分对应到UML类图的相应区域。掌握阅读和绘制简单的UML类图,将有助于你在未来的编程项目中更好地进行规划和协作。

33:卡牌对象匹配实践 🃏

在本节课中,我们将学习如何用Python实现一个卡牌类,并重点探讨如何让两个内容相同但内存地址不同的卡牌对象能够被正确地判断为“相等”。我们将通过定义类的特殊方法来实现这一功能。


卡牌类的实现

上一节我们讨论了卡牌类的概念,本节中我们来看看如何在Python中具体实现它。

我们首先定义一个名为 Card 的类。它有一个初始化方法 __init__,用于接收花色和点数(或称为值),并将它们赋值给对象的属性。

class Card:
    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank

接下来,我们可以定义一个 __str__ 方法,用于返回卡牌的字符串表示。这里我们使用F-string,它允许我们在字符串中直接嵌入变量值,无需手动转换类型。

    def __str__(self):
        return f"{self.rank} of {self.suit}"

此外,我们还可以创建“获取器”方法,用于返回卡牌的花色和点数。

    def get_suit(self):
        return self.suit

    def get_rank(self):
        return self.rank

对象相等性的问题

现在,我们创建两个内容相同的卡牌对象,比如都是“红心2”。

c1 = Card("Hearts", 2)
c2 = Card("Hearts", 2)

如果我们使用 == 运算符来检查它们是否相等,结果会是 False。这是因为在默认情况下,Python的 == 运算符比较的是两个对象在内存中是否是同一个实例,而不是它们的内容是否相同。

例如,c1 == c1 会返回 True,但 c1 == c2 会返回 False。这在卡牌游戏中是不符合逻辑的,我们希望两张点数花色相同的牌被视为相等。


重写 __eq__ 方法

为了解决这个问题,我们需要在 Card 类中重写 __eq__ 方法。这个特殊方法定义了当使用 == 运算符时,对象应该如何进行比较。

以下是 __eq__ 方法的实现:

    def __eq__(self, other):
        return self.suit == other.suit and self.rank == other.rank

这个方法接收另一个对象 other 作为参数,然后比较当前对象(self)和 other 的花色与点数是否都相同。如果都相同,则返回 True,否则返回 False

通过重写这个方法,我们“覆盖”了从父类继承来的默认相等性检查逻辑。现在,当我们执行 c1 == c2 时,Python会调用我们自定义的 __eq__ 方法,并正确地返回 True


总结

本节课中我们一起学习了如何构建一个功能完整的卡牌类。我们实现了初始化方法、字符串表示方法以及获取器方法。更重要的是,我们通过重写 __eq__ 特殊方法,解决了对象内容相等但实例不同时,== 运算符判断错误的问题。这使得我们的 Card 类更符合卡牌游戏的逻辑需求。

34:Card类单步调试 🃏

在本节课中,我们将学习如何通过单步调试来理解Card类和Deck类的协同工作方式。我们将看到如何创建一副包含52张牌的扑克牌,并观察每个对象在内存中的创建过程。


类与对象的关系

上一节我们介绍了Card类的基本结构。本节中我们来看看Card类如何与Deck类结合,共同构建一副完整的扑克牌。

在Python中,一副牌(Deck)是52张Card对象的集合,包含了从A到K的所有点数以及四种花色。

以下是Card类和Deck类的核心代码结构:

class Card:
    rank_names = ['Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King']
    suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']

    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        return f"{self.rank_names[self.rank]} of {self.suit_names[self.suit]}"

class Deck:
    def __init__(self):
        self.card_list = []
        for suit in range(len(Card.suit_names)):
            for rank in range(len(Card.rank_names)):
                self.card_list.append(Card(suit, rank))

    def __str__(self):
        return '\n'.join(str(card) for card in self.card_list)

单步调试过程

现在,我们通过Python Tutor工具来单步执行代码,观察对象的创建过程。

  1. 首先,定义了Card类。它包含__init____str__方法。
  2. rank_namessuit_names类属性,而非对象属性。这意味着所有Card对象共享这两份列表,节省了内存空间。
  3. 接着,定义了Deck类。
  4. 执行主函数main(),其中创建了一个Deck对象。
  5. 创建Deck对象时,会自动调用其__init__方法:
    • card_list初始化为空列表。
    • 使用嵌套循环遍历所有花色和点数。
    • 在循环内部,为每个花色和点数的组合创建一个Card对象。
  6. 创建Card对象时,会调用Card类的__init__方法,为该对象设置suitrank属性。
  7. 创建的第一张牌是“梅花A”(Ace of Clubs),随后是“梅花2”(Two of Clubs),依此类推。
  8. 代码会持续循环,直到创建完梅花花色的所有牌(从A到K),然后继续处理下一个花色(方块、红心、黑桃)。

核心机制总结

本节课中我们一起学习了Card类和Deck类如何协同工作来构建一副扑克牌。关键点在于:

  • 类属性rank_namessuit_names被定义为类属性,由所有实例共享,提高了效率。
  • 对象创建Deck__init__方法通过嵌套循环系统地创建所有52张Card对象。
  • 组合关系Deck对象包含一个Card对象列表,这是面向对象编程中“组合”关系的典型例子。

通过单步调试,我们清晰地看到了从类定义到对象创建,再到最终组成完整牌堆的每一步过程。

35:类之间的关系概述 🧩

在本节课中,我们将要学习面向对象编程中两个核心的类关系:继承与关联。理解这些关系是构建复杂、模块化程序的关键。


类之间的两种基本关系

上一节我们介绍了本周的学习主题。本节中我们来看看类之间存在的两种基本关系。

  • 继承:这种关系可以描述为“是一个”或“是一种类型”。例如,“狗”是一种“动物”。
  • 关联:这种关系可以描述为“有一个”。例如,“汽车”有一个“发动机”。

本周学习目标

以下是本周课程结束后,你将能够掌握的核心技能:

  • 理解何时使用继承,何时使用关联。
  • 能够预测包含继承和关联关系的代码的输出结果。
  • 创建一个子类,并重写继承的方法(这也称为特化)。
  • 创建一个父类,从两个或多个其他类中抽象出通用特性(这称为泛化),然后让子类继承这个父类。
  • 分析问题领域,识别类之间的关系。
  • 完成一项作业:创建一个Image类的子类,并实现三种不同的图像特效。

总结

本节课中我们一起学习了面向对象编程中类之间的两种核心关系:继承(“是一个”)和关联(“有一个”)。我们明确了本周的学习目标,包括创建子类、重写方法、进行泛化与特化,以及最终应用这些知识来创建图像处理特效。希望你能在本周的学习中有所收获。

36:UML类图继承机制 🧩

在本节课中,我们将学习UML类图中两种基本的类关系:关联关系和继承关系。理解这些关系对于设计面向对象的程序至关重要。

关联关系

上一节我们介绍了UML类图的基本概念,本节中我们来看看第一种关系:关联关系。关联关系描述了一个类“拥有”另一个类,通常用一条直线连接两个类来表示。

以下是关联关系的几个例子:

  • 一个航班(Flight)拥有一个机场(Airport)。实际上,它关联了两个机场:出发机场和到达机场。
  • 一场电影放映(MovieShowing)关联一部电影(Movie)。电影放映指的是在特定日期和时间播放某部电影。
  • 在示例UML图中,客户(Customer)拥有姓名和送货地址,并且通过一条直线与订单(Order)关联。这意味着一个客户拥有一个订单,一个订单也关联一个客户。
  • 一个订单可能还拥有订单号、接收日期,以及一个订单项(Item)列表。每个订单项可能包含名称、ID或价格等信息。

继承关系

了解了类之间的“拥有”关系后,我们来看看另一种重要的关系:继承关系。继承关系通过一个空心三角形箭头表示,它指明一个类是另一个类的父类。

以下是继承关系的具体说明:

  • 在图中,客户(Customer)是一个父类,它有两种子类:企业客户(Corporate Customer)和个人客户(Personal Customer)。
  • 企业客户和个人客户可能享有不同的折扣,个人客户可能还有奖励计划。
  • 图中的另一个继承关系是组合套餐(Combo)。当你去快餐店时,可能会点“套餐1”,它包含一个三明治、一杯饮料和薯条等。组合套餐是一种可以订购的订单项(Item),但它本身是多个物品的组合。
  • 组合套餐的价格通过将其包含的每个物品的价格相加,再乘以某个折扣系数(例如0.9或0.8)来计算。其价格计算方式可以用以下公式表示:
    combo_price = discount_factor * (item1_price + item2_price + ...)

本节课中我们一起学习了UML类图中的两种核心关系。关联关系(直线连接)表示类之间的“拥有”关系,而继承关系(空心三角箭头)则表示类之间的“是一个”关系,即子类继承父类的特性。理解这两种关系是进行面向对象设计和分析的基础。

37:Python继承实现 🐍

在本节课中,我们将学习Python中继承的概念与实现方式。继承是面向对象编程的核心特性之一,它允许一个类(子类)获取另一个类(父类)的属性和方法,从而实现代码的重用和扩展。


继承的基本概念

上一节我们介绍了类的定义与使用,本节中我们来看看如何通过继承来建立类之间的关系。

在Python中,一个类可以通过在定义时指定父类来继承其特性。这意味着子类会自动拥有父类的所有属性和方法。

公式class ChildClass(ParentClass):


代码示例解析

以下是本节课将使用的一个完整代码示例,它展示了Person类和继承自它的Employee类。

class Person:
    def __init__(self, first, last):
        self.firstname = first
        self.lastname = last

    def __str__(self):
        return self.firstname + " " + self.lastname

    def initials(self):
        return self.firstname[0] + self.lastname[0]

class Employee(Person):
    def __init__(self, first, last, id):
        super().__init__(first, last)
        self.id = id

def main():
    p = Person("Bob", "Evans")
    print(p)
    print(p.initials())

    e = Employee("Mary", "Adams", 12345)
    print(e)
    print(e.initials())

if __name__ == "__main__":
    main()

执行流程详解

让我们逐步分析上述代码在Python Tutor中的执行过程,以理解继承的工作机制。

1. 定义父类 Person

首先,程序定义了Person类。它包含:

  • __init__方法:用于初始化firstnamelastname属性。
  • __str__方法:返回对象的字符串表示(全名)。
  • initials方法:返回姓名的首字母缩写。

2. 定义子类 Employee

接着,定义了Employee类,它继承自Person类。

代码class Employee(Person):

这行代码意味着EmployeePerson的子类,它将自动获得Person的所有方法和属性。

Employee__init__方法中,我们使用super().__init__(first, last)来调用父类Person的初始化方法。这样做可以避免重复编写设置姓名属性的代码。之后,我们再初始化子类特有的id属性。

3. 创建对象与调用方法

main函数中,我们分别创建了PersonEmployee的对象。

  • 创建Person对象p = Person("Bob", "Evans")

    • 调用Person.__init__,初始化firstnamelastname
    • print(p)会调用Person.__str__方法,输出“Bob Evans”。
    • p.initials()调用Person.initials方法,返回并打印“BE”。
  • 创建Employee对象e = Employee("Mary", "Adams", 12345)

    • 调用Employee.__init__
    • super().__init__(first, last)将控制权转交给Person.__init__,为e对象设置firstnamelastname
    • 返回Employee.__init__后,设置e对象特有的id属性。
    • print(e)时,由于Employee类没有定义自己的__str__方法,Python会沿着继承链向上查找,最终执行父类Person__str__方法,输出“Mary Adams”。
    • e.initials()同样沿继承链找到并执行Person.initials方法,返回并打印“MA”。

方法重写与Super关键字

如果子类需要修改父类方法的行为,可以定义同名方法,这称为方法重写

例如,如果Employee类需要不同的字符串表示,可以重写__str__方法:

class Employee(Person):
    def __init__(self, first, last, id):
        super().__init__(first, last)
        self.id = id

    def __str__(self):
        # 调用父类方法获取姓名,再拼接ID
        return super().__str__() + ", ID: " + str(self.id)

代码super().__str__()

super()函数返回一个代表父类的临时对象,允许子类调用已被重写的父类方法。


总结

本节课中我们一起学习了Python中继承的实现。

  • 继承允许子类获取父类的属性和方法,促进代码重用。
  • 定义子类时,在类名后的括号中指定父类。
  • 子类可以重写父类的方法以提供特定实现。
  • super()函数用于在子类中调用父类的方法,特别是在初始化过程中。

通过继承,我们可以构建层次化的类结构,使代码更加清晰、模块化且易于维护。

38:方法调用优先级 🐦

在本节课中,我们将要学习面向对象编程中一个重要的概念:方法调用优先级。具体来说,当一个对象调用一个方法时,Python 解释器是如何决定执行哪个类中的方法的。我们将通过一个关于鸟类和企鹅的生动例子来理解继承、方法重写以及方法查找的顺序。


概述

我们首先定义一个 Bird 类,它包含 flyfeathers 两个方法。然后,我们创建一个继承自 Bird 的子类 Penguin,并在其中重写 fly 方法。接着,我们将分别创建 BirdPenguin 的实例对象,并调用它们的方法。通过观察输出结果,我们可以清晰地看到 Python 在查找和执行方法时的优先级规则。

代码结构分析

以下是示例代码的基本结构:

class Bird:
    def fly(self):
        print("Birds can fly.")

    def feathers(self):
        print("Birds have feathers.")

class Penguin(Bird):
    def fly(self):
        print("Penguins can't fly. Penguins can swim.")

# 创建对象
p1 = Bird()  # Bird 类的实例
p2 = Penguin()  # Penguin 类的实例

上一节我们介绍了类和继承的基本概念,本节中我们来看看当调用 p1.fly()p2.fly() 时,具体会发生什么。

方法调用过程详解

当一个对象(如 p1p2)被要求执行一个方法时,Python 解释器会遵循一个特定的查找顺序来决定执行哪个方法。

以下是方法查找的核心步骤:

  1. 对象自省:每个对象都保存着一个指向其创建类的引用。例如,p1 知道自己是 Bird 类的实例,p2 知道自己是 Penguin 类的实例。
  2. 查找起点:当调用 对象.方法() 时,解释器首先在该对象所属的类中查找同名方法。
  3. 执行与向上查找
    • 如果在当前类中找到了该方法,则立即执行它。
    • 如果在当前类中没有找到,解释器会转向其父类进行查找,并沿着继承链向上追溯,直到找到该方法为止。
    • 如果最终在继承链中都未找到该方法,程序将抛出 AttributeError 异常。

实例演练与输出

让我们按照代码执行顺序,一步步分析每个方法调用的结果。

以下是每一步的调用与对应的输出解释:

  • p1.fly()p1Bird 的实例。解释器直接在 Bird 类中找到 fly 方法并执行。
    • 输出Birds can fly.
  • p2.fly()p2Penguin 的实例。解释器首先在 Penguin 类中查找,并找到了重写的 fly 方法,因此执行子类中的版本,而非父类的。
    • 输出Penguins can't fly. Penguins can swim.
  • p1.feathers()p1Bird 的实例。解释器在 Bird 类中找到 feathers 方法并执行。
    • 输出Birds have feathers.
  • p2.feathers()p2Penguin 的实例。解释器首先在 Penguin 类中查找 feathers 方法,但没有找到。于是,它向上到父类 Bird 中查找,找到了该方法并执行。
    • 输出Birds have feathers.

这个过程清晰地展示了 方法重写 的效果:子类中定义的同名方法会覆盖(Override)父类中的方法。同时,对于子类没有重写的方法,则会继承并使用父类中的实现。

核心概念总结

本节课中我们一起学习了面向对象编程中方法调用的优先级机制。我们可以将其核心规则总结为以下公式:

方法解析顺序 = 对象所属类 -> 父类 -> 更上层父类 -> ...

这意味着:

  1. 优先查找当前类:解释器总是首先尝试在对象自身所属的类中寻找方法。
  2. 支持方法重写:如果子类定义了与父类同名的方法,则调用子类对象时会执行子类的方法,这实现了功能的定制与扩展。
  3. 继承备用方法:如果子类没有某个方法,则会自动沿继承链向上查找并使用找到的第一个匹配方法。

理解这一优先级是掌握继承和多态行为的关键,它确保了代码既能复用父类的通用逻辑,又能通过子类进行特定功能的修改。

39:特化与泛化技术 🧬

在本节课中,我们将学习面向对象编程中继承的两种主要应用方式:特化与泛化。这两种技术帮助我们更高效地组织和管理代码,实现代码的复用和扩展。


特化:继承并扩展父类

上一节我们介绍了继承的基本概念,本节中我们来看看如何通过特化来扩展父类的功能。特化是指子类继承父类后,在某些方面进行修改或增强,使其更具体、更特殊。

例如,假设我们有一个 Person 类,它包含 first_namelast_name 属性,以及一个用于获取姓名缩写的方法 get_initials()。我们可以创建一个 Employee 类来继承 Person 类,因为员工是一种特定类型的人。在 Employee 类中,我们可以添加额外的属性,如 ID,并可以重写继承的方法以满足特定需求。

以下是特化的代码示例:

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def get_initials(self):
        return f"{self.first_name[0]}.{self.last_name[0]}."

class Employee(Person):
    def __init__(self, first_name, last_name, ID):
        super().__init__(first_name, last_name)
        self.ID = ID

    # 可以重写父类方法或添加新方法
    def get_employee_info(self):
        return f"ID: {self.ID}, Name: {self.first_name} {self.last_name}"

泛化:提取公共属性到父类

接下来,我们看看泛化技术。泛化是指将多个子类中相同的属性和方法提取出来,形成一个更通用的父类。这样做可以减少代码重复,提高代码的可维护性。

例如,假设我们正在为交通部门管理车辆收费系统。我们可能有 CarTruck 两个类,它们都有 IDcolor 属性,以及计算费用的方法 fees()。我们可以将这些公共部分提取到一个通用的 Vehicle 父类中,然后让 CarTruck 继承这个父类,并根据各自的特点重写 fees() 方法。

以下是泛化的代码示例:

class Vehicle:
    def __init__(self, ID, color):
        self.ID = ID
        self.color = color

    def fees(self):
        # 基础费用
        return 100

class Car(Vehicle):
    def fees(self):
        # 汽车的费用计算方式
        return super().fees() + 50

class Truck(Vehicle):
    def fees(self):
        # 卡车的费用计算方式,可能更贵
        return super().fees() + 150

特化与泛化的核心区别

以下是特化与泛化的主要区别:

  • 特化:子类继承父类并添加或修改功能,使其更具体。
  • 泛化:从多个子类中提取公共部分,形成更通用的父类。

这两种技术相辅相成,特化用于扩展和定制,泛化用于抽象和复用。


总结

本节课中我们一起学习了继承的两种主要应用方式:特化与泛化。特化允许我们通过继承父类并添加或修改功能来创建更具体的子类;泛化则帮助我们将多个子类中的公共部分提取到父类中,减少代码重复。掌握这两种技术,可以让我们更高效地设计和组织面向对象的代码结构。

0:欢迎与课程概述 🎉

在本课程中,我们将学习如何搭建本地Python开发环境,掌握使用终端窗口的基本方法,并学习一系列预防和修复代码问题的实用技术。我们将涵盖调试、单元测试、文件数据处理以及正则表达式等核心主题,旨在帮助你更高效、更可靠地进行编程。

课程内容概览 📋

以下是本课程将要学习的主要内容:

  • 安装软件与使用终端:你将安装必要的软件,以便在本地计算机上运行Python。同时,你将学习使用终端窗口,这是一种向计算机发出指令的传统而强大的方式。
  • 问题预防与修复技巧:你将学习简单的技术,用于预防和修复代码中可能出现的问题。
  • 使用调试器:你将学习使用调试器来追踪和修复程序中的错误(bug)。
  • 编写与使用单元测试:你将学习使用和编写单元测试,以验证代码是否按预期正确运行。
  • 文件数据读写与处理:你将编写代码来从文件中读取数据、将数据写入文件,并对这些数据进行处理。
  • 使用正则表达式:你将学习使用正则表达式,以便高效地在文本中搜索和匹配特定模式的数据。

总结

本节课中,我们一起了解了第三课程的整体框架和学习目标。从下一节开始,我们将首先动手安装Python并熟悉终端操作,为后续的编程实践打下坚实的基础。祝你学习愉快!

1:本课程的独特之处 🎨

在本节课中,我们将了解这门课程与其他在线课程的不同之处,并介绍其核心的教学理念与学习方式。

概述

欢迎来到这门课程。它将与许多在线课程有所不同。本课程不会包含大量讲座,而是更侧重于动手实践学习。

核心教学理念

上一节我们提到了课程的实践导向,本节中我们来看看其背后的核心理念。

我们将鼓励你与他人合作、以小组形式学习,因为这能提升你的学习效果和动力。人类是社会性生物,我们可以通过观察他人和与他人讨论来学习。

学习资源与练习

了解了合作学习的重要性后,接下来我们看看课程提供的具体学习工具。

电子书中包含大量互动练习。此外,如果你需要复习,可以回顾前面的章节或完成更多练习题。部分练习题具有自适应特性,这意味着它们会帮助你逐步找到正确的解决方案。

以下是练习的主要类型:

  • 互动练习:在电子书中直接完成。
  • 复习与拓展练习:可随时返回之前章节进行。
  • 自适应练习题:系统会根据你的回答提供引导。

作业与项目设计

除了常规练习,课程作业的设计也别具一格,旨在激发创造力。

课程还包含开放式编辑和创意作业。因此,不同于编程入门课程中常见的练习类型(例如计算销售税或将摄氏温度转换为华氏温度),你将绘制艺术作品或创作个性化故事。希望这能让你感觉更有趣、更有动力。

总结

本节课中,我们一起学习了这门课程的独特之处:它强调动手实践小组合作的学习模式,提供自适应的互动练习,并通过创意性项目(如绘图和故事创作)来替代传统的编程练习题,旨在让学习过程更加有趣和富有动力。

2:Python是什么 🐍

在本节课中,我们将要学习Python编程语言的基本概念,了解它的起源、特点以及在现代编程中的应用。

大家好,我是Barbar Erickson。我是信息学院的助理教授。我很高兴能带领大家开启学习Python的旅程。

那么,Python到底是什么呢?它是一条巨蟒吗?不,Python是以《蒙提·派森的飞行马戏团》命名的,这是一部20世纪70年代的BBC喜剧系列。如果你没看过,可以去看看。😊

它是一种非常流行的编程语言,许多公司都在使用它。它的流行部分原因在于它非常强大。有大量的库可以让事情变得更简单,并且它被广泛应用于数据科学和机器学习领域。

与许多其他文本型语言相比,它也相对容易学习一些。它还支持面向对象编程,这是当今人们开发程序最常用的方式。这意味着它支持“类”(即事物的分类)和实际执行程序工作的“对象”。

Python的特点与优势

上一节我们介绍了Python的起源和基本定位,本节中我们来看看它的一些核心特点和优势。

以下是Python的几个关键特性:

  • 强大且功能丰富:Python拥有一个庞大的标准库和第三方库生态系统,可以轻松处理各种任务,从网页开发到科学计算。
  • 易于学习:Python的语法清晰、简洁,类似于英语,这使得它成为编程初学者的理想选择。
  • 支持面向对象编程(OOP):这是一种组织代码的流行范式。在Python中,你可以定义class(类)来创建object(对象)。例如,一个“汽车”类可以生成具有颜色、品牌等属性的具体汽车对象。
    class Car:
        def __init__(self, brand, color):
            self.brand = brand
            self.color = color
    
    my_car = Car("Toyota", "red")
    print(my_car.brand)  # 输出:Toyota
    
  • 应用广泛:Python在数据科学、人工智能、网络开发、自动化脚本等领域占据主导地位。

开始使用Python

了解了Python是什么以及它的优势后,接下来我们就可以准备开始实际使用它了。

所以,让我们开始吧。😊


本节课中我们一起学习了Python编程语言的基础知识。我们了解到Python名称的趣味起源,它并非指代蟒蛇,而是源于一部喜剧。更重要的是,我们认识到Python是一种强大、易学且支持面向对象编程的流行语言,这些特性使其成为初学者和专业开发者的绝佳选择。在接下来的课程中,我们将动手编写我们的第一个Python程序。

3:为何分组协作 👥

在本节课中,我们将探讨在编程课程中采用分组协作模式的原因与价值。我们将了解团队合作如何提升学习效果、增强动力,并培养对职业生涯至关重要的软技能。

上一节我们介绍了课程的基本框架,本节中我们来看看为何鼓励大家进行分组协作。

研究表明,分组协作既能提升学习动力,也能改善学习效果。部分原因在于,向他人解释自己的想法这一过程,本身就能帮助你更好地理解概念。用代码来描述,这个过程类似于将模糊的想法转化为清晰的逻辑:

# 个人思考可能模糊不清
vague_idea = "大概是这样..."

# 向他人解释时,必须使其清晰、结构化
clear_explanation = """
1. 定义问题。
2. 拆解步骤。
3. 用代码实现每一步。
"""

通常,与他人协作也更有趣,并能帮助你在遇到困难时——尤其是在编程过程中——避免陷入僵局。

总体而言,研究表明,多元化的团队表现优于个人。团队往往能解决个人无法独立解决的问题。例如,在密室逃脱游戏中,与他人的合作可能使你成功逃脱,而这或许是你独自一人无法完成的。

以下是分组协作带来的主要益处列表:

  • 提升理解:通过解释巩固知识。
  • 增强动力与乐趣:社交互动使学习过程更愉快。
  • 解决问题:汇集多元视角,突破个人思维局限。
  • 避免卡壳:及时获得反馈与帮助。

此外,团队协作、沟通、自我管理与团队管理的能力对雇主而言非常重要。因此,掌握这类技能在工作场所中也极具价值。

本节课中我们一起学习了分组协作在教育与职业发展中的重要性。它不仅是高效学习的手段,也是培养未来所需核心软技能的关键途径。

4:POGIL角色解析 🧩

在本节课中,我们将学习一种名为POGIL的协作学习方法,并详细解析其团队中的四个核心角色。理解这些角色有助于在小组编程项目中更高效、更有组织地工作。


概述

POGIL代表“过程导向的引导式探究学习”。这是一种在小组中进行的协作学习模式,其特点是团队中的每个成员都承担一个定义明确的特定角色。通过角色分工,团队可以更有效地管理任务、记录进展、沟通问题并反思协作过程。

上一节我们介绍了协作学习的重要性,本节中我们来看看POGIL模式如何通过具体的角色分工来实现高效的团队合作。

POGIL的四个核心角色

在POGIL活动中,通常有四个不同的角色。每个角色都有其独特的职责,共同确保小组活动的顺利进行。

以下是POGIL活动的四个核心角色及其职责:

  1. 管理者

    • 向小组成员分配任务。
    • 帮助确保整个团队按计划、按时完成任务。
    • 确保团队在解决问题。如果团队在某处卡住,管理者可以鼓励大家继续前进。
  2. 记录员

    • 负责写下重要的观点。
    • 记录团队所学到的内容。
  3. 演示者/提问者

    • 如果对活动说明有任何不清楚的地方,负责向指导者提问。
    • 负责展示团队的成果。
  4. 反思者

    • 确保团队中每个人的声音都被听到,避免只有一个人做出贡献。
    • 可以汇报团队的动态,例如团队合作是否良好,以及哪些方面可以改进。

总结

本节课中我们一起学习了POGIL协作学习法中的四个关键角色:管理者记录员演示者/提问者反思者。每个角色都像齿轮一样,在团队机器中发挥着不可替代的作用。明确的分工不仅能提升效率,还能促进更公平的参与和更深入的团队反思。在接下来的小组编程实践中,尝试分配这些角色,体验结构化的团队协作带来的益处。

5:电子书中编写代码的方法 📖

在本节课中,我们将学习如何在电子书中直接编写、运行和调试Python代码。这是一种便捷的学习方式,允许你即时实践并查看结果。

概述

使用电子书的一个显著优点是,你可以直接在书中编写代码。本节将演示如何操作,包括创建函数、运行测试、查看历史记录以及使用辅助工具。

在电子书中编写与运行代码

上一节我们了解了电子书的环境,本节中我们来看看如何具体地编写代码。

假设我们需要创建一个计算矩形面积的函数。该函数接收宽度和长度作为参数,并返回面积。例如,输入 area_of_rectangle(5, 4) 应返回 20

以下是在电子书中实现此功能的步骤:

  1. 定义函数:首先,我们定义一个名为 area_of_rectangle 的函数。

    def area_of_rectangle(width, length):
    
  2. 计算面积:在函数体内,我们计算面积。

        area = width * length
    
  3. 返回结果:最初,我们可能忘记返回计算结果。运行测试后会发现这个问题,因此需要添加返回语句。

        return area
    

完成这些步骤后,函数 area_of_rectangle(5, 4) 就能正确返回 20 了。

使用代码透镜与分享功能

代码编写完成后,你可以利用电子书提供的工具进行验证和协作。

  • 代码透镜:你可以通过代码透镜功能直观地看到测试用例的运行结果,确认代码是否正确。
  • 分享代码:如果你在团队中工作,可以将代码分享给其他成员,方便协作。

辅助工具:Python代码窗口

如果你对某些操作(例如乘法运算符 *)不确定,可以使用辅助工具进行快速测试。

在电子书右上角有一个铅笔图标,点击它可以打开一个独立的Python代码窗口。

在这个窗口中,你可以输入简单的代码进行测试,例如:

print(5 * 4)

执行后会输出 20,这有助于你验证逻辑。测试完成后,可以关闭这个窗口。

查看代码历史记录

最后,还有一个重要的功能是查看代码历史。为了使用此功能,请确保你已登录。

登录后,你会看到一个历史滑块控件。通过拖动这个滑块,你可以回溯查看代码在不同时间点的状态。

例如,你可以看到:

  • 代码最初的样子。
  • 第二次修改后的版本。
  • 最终解决问题的版本。

这让你能够回顾自己的思考和改进过程。

总结

本节课中我们一起学习了在电子书中编写代码的完整流程。我们实践了如何定义函数、运行测试、使用代码透镜和分享功能。我们还介绍了用于快速测试的Python代码窗口,以及用于回顾学习过程的代码历史记录功能。这些工具共同为初学者提供了一个友好且功能强大的编程练习环境。

6:重载代码历史记录 📚

在本节课中,我们将学习如何在编程环境中找回之前保存的代码。如果你曾经离开后又返回,可能会担心代码丢失。实际上,只要你登录了,代码就会被保存。本节将向你展示如何找回这些代码。


上一节我们介绍了编程环境的基本操作,本节中我们来看看如何利用“加载历史记录”功能。

如果你曾离开书本或编程环境后又返回,可能会惊讶地发现代码似乎不见了。但只要你之前是登录状态,代码实际上已被保存。下面演示如何找回它们。

当你返回环境并发现“哦不,我已经解决过这个问题了”时,请注意这个“加载历史记录”按钮。务必点击它。

如果你上次运行代码时处于登录状态,该功能将加载你所有的历史记录。现在,我可以清晰地看到上次运行时所写的代码,甚至可以回溯到更早的版本来查看当时的操作。

所以,无需惊慌。只要你登录时所做的操作都会被保存,但请确保点击“加载历史记录”按钮。

以下是操作的核心步骤:

  1. 确认返回编程环境。
  2. 在界面中找到并点击“加载历史记录”按钮。
  3. 系统将自动加载你保存的所有代码历史版本。
  4. 你可以查看或选择恢复任意一个历史版本。


本节课中我们一起学习了“加载历史记录”功能的重要性与操作方法。关键在于记住:在登录状态下,你的工作会被自动保存;返回后,只需主动点击“加载历史记录”按钮即可找回所有历史代码,避免重复劳动或丢失进度。

7:课程概述 🎯

在本节课中,我们将要学习第一周课程的核心内容。第一周的目标是为你搭建一个坚实的Python编程基础,涵盖从环境配置到代码调试的完整流程。

环境配置与工具使用 💻

上一节我们介绍了本周的学习目标,本节中我们来看看具体需要掌握哪些工具和概念。

你将学习如何在本地计算机上运行Python。这意味着你需要理解文件路径的工作原理,以及如何使用终端窗口

此外,我们将安装AnacondaVisual Studio Code。你将学习如何使用一个集成开发环境来运行你的代码。集成开发环境是一个集成了代码编辑、运行和调试功能的软件。

数据处理与文件操作 📁

掌握了基础工具后,我们将进入实践环节。你将能够处理来自文件的数据。

具体来说,你将学习读取文本文件,并使用字典来统计文件中的值。字典是Python中一种非常实用的数据结构,用于存储键值对。例如,统计一段文本中每个单词出现的次数。

以下是使用字典进行计数的基本代码示例:

word_count = {}
for word in text.split():
    if word in word_count:
        word_count[word] += 1
    else:
        word_count[word] = 1

代码调试与测试 🐛

编写代码难免会遇到问题,因此掌握调试技能至关重要。我们将学习一些调试策略。

首先,我们将探讨如何预防问题的发生,从而减少调试的需要。但一旦出现问题或“Bug”,你将学习如何定位并解决它。

最后,我们将学习如何编写单元测试来自动化测试你的代码,确保其按预期工作。单元测试是针对程序模块的最小可测试单元进行的验证工作。


本节课中我们一起学习了第一周的课程安排。核心内容包括:配置本地Python开发环境、理解文件路径与终端、使用Anaconda和VS Code、通过字典处理文件数据,以及学习调试和编写单元测试的基本策略。

8:软件安装指南 🛠️

在本节课中,我们将学习如何在本地计算机上搭建Python编程环境。这包括安装Python发行版和一个集成开发环境(IDE),以便你能顺利开始编写和运行代码。


概述

为了在本地计算机上运行Python,你需要安装两个核心软件:Anaconda(一个包含Python及其常用库的发行版)和Visual Studio Code(一个免费的、跨平台的集成开发环境)。本节将详细介绍这两个软件的下载、安装和基本配置步骤。


安装Anaconda

Anaconda是一个集成了Python和众多常用科学计算库的发行版,它简化了环境管理过程。

首先,访问Anaconda官方网站:www.anaconda.com。在网站上,找到“Products”部分,选择“Individual Edition”进行下载。当前版本默认提供Python 3,未来版本可能会更新。Anaconda的优势在于它预装了数据分析、机器学习等领域常用的库,省去了逐一安装的麻烦。

下载完成后,运行安装程序。在安装过程中,请务必勾选“Add Anaconda to my PATH environment variable”选项(尤其是在Windows系统上),这能确保系统在任意位置都能识别Anaconda的命令。


安装Visual Studio Code

上一节我们安装了Python环境,本节中我们来看看如何安装一个强大的代码编辑器。我们将使用Visual Studio Code(VS Code),因为它免费、功能强大且在Windows和Mac系统上表现一致。

访问Visual Studio Code官方网站:code.visualstudio.com。网站会根据你的操作系统自动显示对应的下载按钮,点击下载并运行安装程序。

安装完成后,启动VS Code。为了使其能更好地支持Python开发,我们需要安装一个扩展。

以下是安装Python扩展的步骤:

  1. 在VS Code左侧活动栏中,找到并点击扩展图标(形状像四个方块)。
  2. 在扩展市场的搜索框中,输入“Python”。
  3. 在搜索结果中找到由Microsoft发布的“Python”扩展,点击“Install”按钮进行安装。

配置VS Code使用Anaconda环境

软件安装完毕后,需要进行简单配置,将两者关联起来。

核心步骤是确保VS Code使用Anaconda提供的Python解释器。当你用VS Code打开一个Python文件(.py文件)时,注意编辑器窗口底部状态栏的右侧。那里会显示当前使用的Python解释器版本。

点击这个版本信息,会弹出一个选择列表。请从列表中选择标有“Conda”或“Anaconda”的选项。这确保了你的代码将在Anaconda环境中运行,能够访问所有已安装的库。

配置完成的标志是状态栏显示类似 Python 3.x.x (‘conda’: conda) 的信息。


总结

本节课中我们一起学习了搭建本地Python开发环境的完整流程。我们首先安装了Anaconda来获取Python及其科学计算库,然后安装了Visual Studio Code作为代码编辑器,并通过安装Python扩展和选择Conda解释器完成了环境配置。现在,你的计算机已经准备好进行创意编程了。

9:文件操作入门 📂

在本节课中,我们将要学习如何处理文件读取时可能遇到的错误,特别是当程序试图打开一个不存在的文件时。我们将介绍如何使用 tryexcept 语句来优雅地处理这类错误,确保程序不会意外崩溃,而是能够给用户提供友好的提示并继续运行。

文件读取错误与异常处理

上一节我们介绍了基本的文件读写操作。本节中我们来看看,当程序尝试读取一个不存在的文件时会发生什么。

如果尝试读取一个文件,但程序找不到该文件(即文件不存在),程序会抛出一个错误。

例如,运行以下代码:

file = open("unknown.txt", "r")

我们会得到一个 IOError,提示“No such file or directory”。这可能是因为文件名错误(例如 unknown.txt),或者程序在错误的目录中寻找该文件。

使用 Try-Except 处理错误

如果不希望代码因此直接崩溃,该如何解决这个问题呢?

一种方法是使用 tryexcept 语句。你可以将任何可能引发错误的代码包裹在 try 代码块中。

以下是具体做法:

首先,使用 try: 开始一个代码块,并将可能引发问题的代码(例如 open 函数)缩进放在这个块内。

然后,使用 except 子句来指定当特定类型的错误发生时应执行的操作。

例如,以下代码尝试打开一个文件。如果文件不存在,它会捕获错误并提示用户输入另一个文件名,从而更优雅地从错误中恢复,并允许程序继续执行。

try:
    file = open("unknown.txt", "r")
except IOError:
    print("The file doesn‘t exist. Enter another name.")
    # 此处可以添加代码让用户重新输入文件名

使用 tryexcept 是保护代码的一种方式,它能确保程序不会直接崩溃,而是能够优雅地处理错误。

总结

本节课中我们一起学习了文件操作中的一个重要概念:异常处理。我们了解到,当尝试打开不存在的文件时,程序会抛出 IOError。通过使用 try-except 语句,我们可以捕获这个错误,并向用户提供友好的反馈,从而使程序更加健壮和用户友好。这是编写可靠代码的关键一步。

10:GUI文件查找方法 🗂️

在本节课中,我们将学习计算机中文件和目录的组织结构,并了解如何使用图形用户界面(GUI)和命令行两种方式来查找和访问文件。

目录的树形结构 🌳

上一节我们介绍了编程的基本概念,本节中我们来看看计算机如何组织文件。你可能已经习惯了使用图形用户界面来浏览计算机上的文件和目录。计算机中的目录结构类似于一棵倒置的树。

  • 根目录:位于树的顶部。
  • 分支:目录可以包含文件,也可以包含其他目录(子目录),这些子目录又可以包含更多文件或目录,形成向下的分支。

图形界面导航示例 🖱️

让我们通过一个具体例子来理解。假设我们位于“文档”目录中,里面有一个名为“206”的目录(这代表我在密歇根大学教授的一门课程)。

如果打开“206”目录,会看到里面有一个名为“homework1”的子目录。进入“homework1”目录,可以看到文件 homework1.pyhomework1_instructions

同样,“206”目录下还有一个“HW2”目录。打开它,可以看到文件 HW2_instructionsH2.py

以下是使用图形界面操作文件的典型步骤:

  1. 使用鼠标在文件夹图标间点击,导航到目标文件。
  2. 找到文件后,通常可以通过双击来打开它。

命令行操作简介 ⌨️

在图形用户界面普及之前,人们通常通过键入命令来操作计算机。这种方式在某些情况下更为高效和强大。

例如,在命令行中,你可以使用 cd 命令来切换目录,使用 ls(Linux/macOS)或 dir(Windows)命令来列出当前目录下的文件和子目录。

# 示例:切换到“文档”目录下的“206”子目录
cd Documents/206
# 示例:列出“206”目录下的所有内容
ls

本节课中我们一起学习了计算机文件的树形组织结构,并对比了通过图形用户界面和命令行查找、访问文件的两种基本方法。理解这两种方式将帮助你在不同环境下更有效地管理你的代码和项目文件。

11:终端窗口使用技巧 💻

在本节课中,我们将学习如何使用终端窗口。终端窗口是一个强大的工具,它允许我们通过输入命令来与计算机交互、执行Python代码以及管理文件系统。掌握终端的基本操作对于编程和文件管理至关重要。

概述

在图形用户界面普及之前,人们主要通过终端窗口输入命令来操作计算机。如今,终端窗口依然是一个非常有用的工具。本节将介绍终端窗口的基本用法,包括如何启动Python解释器、执行Python文件以及浏览和管理文件系统。

启动Python解释器

终端窗口的一个主要用途是启动并直接运行Python代码。安装Python后,你可以在终端中启动一个交互式解释器,逐行输入并执行Python代码。

例如,你可以输入以下命令来启动Python解释器:

python

启动后,你会看到提示符变为 >>>,表示你已进入Python交互模式。在此模式下,你可以直接输入Python代码并立即看到结果。

以下是几个在Python解释器中操作的例子:

  • 计算 3 + 4,结果是 7
  • 计算 7 % 2(7除以2的余数),结果是 1
  • 你可以设置变量,例如 x = 3,然后计算 y = x * 2,最后使用 print(y) 输出结果。

在Python解释器中,你可以执行任何常规的Python代码,只是一次执行一行。当你想要退出解释器时,输入 quit() 即可。请注意,退出后,命令提示符会从 >>> 变回常规的终端提示符(在Mac/Linux上通常是 $,在Windows上可能是 > 或路径)。

浏览文件系统

终端窗口的另一个重要功能是浏览计算机的文件系统。你可以使用命令在不同的目录(文件夹)之间导航。

以下是一些常用的文件导航命令:

  • pwd(Print Working Directory):显示你当前所在的目录路径。在Windows上,对应的命令是 chdir
  • cd [目录路径](Change Directory):用于切换目录。例如,cd Documents 可以进入“文档”文件夹。
  • ls(List):列出当前目录下的所有文件和子目录。在Windows上,对应的命令是 dir
  • cd ..:返回上一级目录(父目录)。在Mac和Windows上此命令相同。
  • cdcd ~:直接返回用户的主目录。

例如,你可以通过连续使用 cd 命令进入深层目录,如 cd Documents/course_206/hw1。使用 cd .. 可以逐级返回。

执行Python文件

当你位于包含Python脚本的目录时,可以直接在终端中运行该文件。这是测试和运行你编写的程序的主要方式。

运行Python文件的命令格式是:

python 文件名.py

例如,如果有一个名为 hw1.py 的作业文件,你可以输入 python hw1.py 来执行它。如果程序中有语法错误,终端会显示相应的错误信息,帮助你进行调试。

总结

本节课我们一起学习了终端窗口的基本使用技巧。我们了解了如何启动Python交互式解释器来逐行执行代码,如何使用 cdlspwd 等命令在文件系统中导航,以及如何通过 python 文件名.py 的命令来运行编写好的Python脚本。熟练使用终端是每个程序员的基础技能,它能极大地提升你的工作效率。

12:try-except异常处理 🛡️

在本节课中,我们将要学习如何使用 try-except 语句来处理程序运行中可能出现的错误,使程序能够更优雅地应对意外情况,而不是直接崩溃。

为什么需要异常处理? 🤔

上一节我们介绍了文件读取的基本操作。本节中我们来看看,当程序试图执行一个可能失败的操作时会发生什么。

如果你试图读取一个不存在的文件,程序会直接报错并停止运行。例如,运行以下代码:

file = open("unknown.txt", "r")

程序会抛出一个 FileNotFoundError 错误,提示“No such file or directory”。这可能意味着文件名拼写错误,或者文件不在当前目录下。这种直接崩溃的方式对用户不友好。

使用 try-except 捕获异常 🧤

为了使程序更健壮,我们可以使用 try-except 语句来捕获并处理这类异常。

try 块用于包裹可能引发异常的代码。except 块则定义了当异常发生时要执行的操作。其基本结构如下:

try:
    # 可能引发异常的代码
except:
    # 异常发生时的处理代码

以下是处理文件不存在错误的具体步骤:

  1. 将打开文件的操作放入 try 块中。
  2. except 块中,提示用户文件不存在,并请求输入一个新的文件名。
  3. 获取新文件名后,再次尝试打开文件。

实践:优雅地处理文件读取错误 📝

以下是实现上述逻辑的完整代码示例:

try:
    # 尝试打开一个可能不存在的文件
    file = open("unknown.txt", "r")
except:
    # 如果发生异常(如文件不存在),执行以下代码
    print("指定的文件不存在,请输入另一个文件名。")
    filename = input("请输入新文件名:")
    file = open(filename, "r")

# 如果文件成功打开,可以继续执行其他操作
# ... 例如读取文件内容

运行这段代码时,如果 unknown.txt 不存在,程序不会崩溃,而是会提示你:“指定的文件不存在,请输入另一个文件名。” 在你输入一个存在的文件名(例如 dogs.txt)后,程序会继续执行。

总结 📚

本节课中我们一起学习了 try-except 异常处理机制。我们了解到,将可能出错的代码放在 try 块中,并在 except 块中提供备选方案,可以使程序在面对错误时更加稳定和友好。这是编写健壮、用户友好程序的重要技巧。

13:单元测试失败调试 🔍

在本节课中,我们将学习当单元测试用例失败时,如何进行调试。我们将通过一个具体的代码示例,逐步分析问题所在,并尝试修复它。


概述

当运行单元测试时,如果某个测试用例失败,我们需要找出代码中的错误。本节课将通过一个实际案例,演示如何定位和修复由单元测试失败所揭示的问题。

调试失败的测试用例

假设我们有一段代码需要修复。运行测试后,我们发现大部分测试用例都失败了。查看测试说明,我们了解到测试的目标是检查列表中是否存在相邻的重复元素。例如,对于列表 [1, 2, 1],由于没有相邻的重复数字,预期结果应为 False。然而,我们的代码却返回了 True,这显然存在问题。

为了深入分析,我们可以暂时移除单元测试框架的调用,直接运行有问题的函数片段。在Python中,方法必须被显式调用才会执行。因此,我们直接调用该函数并观察其行为。

以下是核心的代码逻辑片段:

def has_adjacent_duplicate(nums):
    if len(nums) == 2:
        # 初始条件判断
        pass
    for i in range(len(nums)):
        cur = nums[i]
        previous = nums[i - 1]
        if cur == previous:
            return True
    return False

通过代码逐步执行,我们发现当 i 为 0 时,nums[i - 1] 变成了 nums[-1],即列表的最后一个元素。在Python中,索引 -1 表示列表的最后一个项。因此,代码实际上是在比较第一个元素和最后一个元素,而非相邻的两个元素。当首尾元素恰好相同时,函数就会错误地返回 True

修复代码逻辑

找到了问题根源,我们就可以进行修复。我们不应该从索引 0 开始循环,而应该从索引 1 开始,以确保 previous 始终是当前元素的前一个相邻元素。

以下是修复后的代码逻辑:

def has_adjacent_duplicate(nums):
    for i in range(1, len(nums)):  # 从索引1开始循环
        cur = nums[i]
        previous = nums[i - 1]
        if cur == previous:
            return True
    return False

修复后再次运行测试,我们发现情况有所改善,但仍存在一些错误。剩下的问题就留给大家尝试分析和解决。

总结

本节课我们一起学习了如何调试失败的单元测试。我们通过一个具体案例,演示了如何定位代码逻辑错误——特别是循环起始索引设置不当导致的问题——并进行了修复。调试是编程中至关重要的技能,需要耐心和细致的观察。

14:减少调试需求的方法 🐛

在本节课中,我们将学习如何通过减少代码中的错误数量,来从根本上避免繁琐的调试工作。核心思想是“预防胜于治疗”,通过良好的编程习惯来降低对调试的依赖。

上一节我们讨论了调试的基本方法,本节中我们来看看如何从源头减少错误。

理解任务与规划

减少错误的第一步是确保你完全理解自己要做什么。在开始编写代码之前,你需要明确以下几点:

  • 输入是什么? 你的程序需要接收什么样的数据。
  • 输出是什么? 你的程序最终应该产生什么样的结果。
  • 代码如何工作? 程序从输入到输出的逻辑流程是怎样的。

一个有效的方法是,先用自然语言(如英语或中文)写下你打算实现的算法。这被称为“先规划,后编码”。明确对于给定的输入,预期的输出应该是什么。这种方法也常被称为“测试驱动开发”,即先编写测试用例。因为如果你不知道代码应有的输入和输出,就无法判断它是否正确工作。

增量开发与频繁测试

理解了任务之后,不要试图一次性写完所有代码。相反,应该采用增量式开发。

以下是实施增量开发的具体步骤:

  1. 从小处着手:先只写几行核心功能的代码。
  2. 立即测试:运行这几行代码,确保它们按预期工作。
  3. 逐步添加:在此基础上,再添加一小部分新功能。
  4. 持续测试:每添加一点新代码,就立即进行测试。

通过这种方式,你能确保每一小段代码在集成前都是正确的。如果某个复杂功能暂时不需要测试,可以将其注释掉,专注于当前正在开发的部分。

使用多行注释隔离代码

在Python中,你可以使用三引号('''""")来创建多行字符串,这常用于临时注释掉大段的代码块。

'''
# 这段代码功能复杂,暂时不需要测试
def complex_function(data):
    # ... 很多行代码 ...
    return result
'''

# 先测试这个简单的函数
def simple_function(x):
    return x * 2

print(simple_function(5))  # 输出应为 10

重要提示:使用此方法时,务必确保你没有注释掉任何对要测试代码的调用语句。因为被注释的代码不会被执行。在完成测试后,记得取消这些注释。

总结

本节课中我们一起学习了三种减少调试需求的核心方法:充分理解任务并先规划后编码采用增量开发并频繁测试、以及合理使用注释来隔离未完成的代码。记住,编写易于理解和测试的简短代码片段,远比写完一大段复杂代码后再去查找隐藏的错误要高效得多。养成良好的编程习惯,是成为优秀程序员的关键一步。

15:课程概述与目标 🎯

在本节课中,我们将要学习第二周的核心内容。本周的重点是处理数据、掌握调试技巧以及学习如何对数据进行排序。我们将从一种常见的数据格式开始。

本周学习内容 📚

上一节我们介绍了本周的整体目标,本节中我们来看看具体要掌握的技能。

以下是本周你将学习到的几个关键主题:

  • 处理CSV文件:学习使用逗号分隔值文件,这是一种标准的数据交换格式。
  • 解析文件:学习如何使用字符串方法(如 findsplitstriprstrip)来解析这些文件。
  • 使用调试器:学习如何设置断点,以及如何使用“继续”、“步入”、“步过”或“停止”命令来逐步执行你的代码。
  • 字典排序:学习如何使用 sorted 函数,根据键或值对字典进行排序。

总结 ✨

本节课中我们一起学习了第二周的计划。我们将接触实际的数据文件,掌握分析和调试代码的工具,并学会如何整理数据。这些技能对于构建更复杂、更有趣的程序至关重要。

16:CSV文件解析 📊

在本节课中,我们将学习什么是CSV文件,以及如何读取和处理其中的数据。CSV文件是一种常见的数据存储格式,广泛用于存储表格数据,如电子表格或数据库导出。通过解析CSV文件,我们可以提取信息并回答关于数据的问题。

什么是CSV文件?

CSV代表“逗号分隔值”。它是一种文件格式,其中包含一系列值,通常每行代表一条记录,而每个值之间由特定的分隔符(通常是逗号)隔开。

例如,一个名为 stock.txt 的文件可能包含以下格式的数据:

01-Jan-2021,150.25,155.30,148.50,152.75
02-Jan-2021,152.80,157.45,151.20,156.00

在这个例子中,每一行包含日期、开盘价、最高价、最低价和收盘价,这些值由逗号分隔。

如何解析CSV文件?

解析CSV文件涉及读取文件内容,并将每行数据分割成独立的值。在Python中,我们可以使用内置的 split() 方法或专门的 csv 模块来实现。

以下是使用 split() 方法解析CSV文件的基本步骤:

with open('stock.txt', 'r') as file:
    for line in file:
        values = line.strip().split(',')
        print(values)

这段代码会读取文件的每一行,去除首尾空白字符,然后按逗号分割成列表。

处理CSV数据

一旦我们将CSV文件解析为列表,就可以进一步处理数据。例如,我们可以计算股票的平均收盘价,或找出最高和最低的股价。

以下是计算平均收盘价的示例代码:

closing_prices = []
with open('stock.txt', 'r') as file:
    for line in file:
        values = line.strip().split(',')
        closing_price = float(values[4])  # 假设收盘价在第5列
        closing_prices.append(closing_price)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-crt-prog/img/13328569b14ae085819c941f840776dd_3.png)

average_close = sum(closing_prices) / len(closing_prices)
print(f"平均收盘价: {average_close}")

总结

本节课中,我们一起学习了CSV文件的基本概念和解析方法。我们了解到CSV文件是一种以逗号分隔值的文件格式,常用于存储表格数据。通过Python的 split() 方法或 csv 模块,我们可以轻松读取和处理这些数据,从而进行各种分析和计算。掌握CSV文件解析是数据处理的基础,对于后续学习更复杂的数据操作至关重要。

17:修复get_total_for_year函数 🛠️

在本节课中,我们将学习如何调试一个处理数据的Python程序。具体任务是修复一个名为get_total_for_year的函数,该函数用于计算特定年份的航空旅行数据总和。我们将通过识别和解决数据读取、字符串处理以及类型转换中的常见错误来完成这个任务。


问题背景与目标

上一节我们介绍了数据处理的基本概念,本节中我们来看看一个具体的调试案例。程序需要读取包含多年份、多月份航空旅行数据的文本,并计算指定年份的数据总和。初始代码运行时出现了错误,我们的目标是找出并修复这些错误。

初始代码与错误分析

首先,我们运行初始代码,观察出现的错误。

# 示例:尝试计算1958年数据总和的代码片段
# 此处省略了数据读取和循环的具体代码
total = total + data[year]  # 第38行,发生错误

运行后,程序在第38行报错,提示“KeyError”。这意味着代码试图访问字典中不存在的键。此外,后续还会遇到“无法将整数与字符串相加”的类型错误。

调试步骤与解决方案

以下是修复这些问题的具体步骤。

步骤一:清理数据键名

第一个错误是键错误。打印出读取的原始数据后,我们发现键名(年份)被多余的双引号空格包围。例如,数据中的键是"1958 ",而不是我们期望的1958

解决方案:在将字符串作为键存入字典前,使用.strip()方法去除首尾的指定字符。

# 清洗头部字符串,去除双引号和空格
header1 = header1.strip('\" ')
header2 = header2.strip('\" ')
header3 = header3.strip('\" ')

.strip('\" ')会移除字符串开头和结尾的所有双引号和空格字符。

步骤二:转换数据类型

修复键名后,再次运行程序,出现了新的类型错误:“TypeError: unsupported operand type(s) for +: 'int' and 'str'”。这是因为从文件读取的数据值是字符串类型,无法直接与整数相加。

解决方案:在将数据值加入总和前,使用int()函数将其转换为整数。int()函数会自动处理字符串中的空格。

# 将字符串数据转换为整数后相加
total = total + int(data_value)

步骤三:处理换行符问题

完成上述两步后,1958和1959年的计算正常了,但1960年仍然出错。检查发现,1960年的数据键末尾还有一个换行符\n,导致.strip('\" ')未能完全清理干净。

解决方案:将换行符也加入.strip()需要移除的字符列表中。

# 清洗头部字符串,去除双引号、空格和换行符
header1 = header1.strip('\" \n')
header2 = header2.strip('\" \n')
header3 = header3.strip('\" \n')

最终代码与验证

应用所有修复后,完整的get_total_for_year函数(或相关数据处理循环)应能正确运行,计算出各年份的数据总和。

# 修复后的关键逻辑示例
for line in file_lines[1:]:  # 跳过标题行
    parts = line.split(',')
    year_key = parts[0].strip('\" \n')  # 彻底清理键
    month_data = int(parts[1].strip())  # 清理并转换值
    if year_key == target_year:
        total += month_data

运行程序,现在它应该能成功输出1958、1959和1960各年份的正确总和。


总结

本节课中我们一起学习了如何系统地调试一个数据处理程序。我们遇到了三个主要问题:多余的字符导致键错误字符串与整数类型不匹配以及隐藏的换行符。通过使用.strip()方法清理字符串和使用int()函数进行类型转换,我们逐一解决了这些问题。记住,在处理外部数据时,仔细检查数据的实际格式并进行适当的清洗和转换是至关重要的。

18:修复含逗号的列数据

在本节课中,我们将要学习如何处理CSV文件中包含逗号的列数据。当数据列本身包含逗号时,简单的按逗号分割会导致数据解析错误。我们将探讨如何识别并修复这类问题。

问题背景

我们一直在通过按逗号分割的方式来处理CSV文件。但是,如果某个列本身包含一个或多个逗号,会发生什么情况呢?以下是一个例子。

我们有一些关于奥斯卡最佳女主角的数据。数据包含年份、获奖者年龄、获奖者姓名和电影片名。请注意,这里的电影片名中包含了一个逗号。

识别问题

在处理CSV文件时,你可以做的一件事是检查列数是否正确。如果某行的列数超出预期,你可能需要打印出该行以查看发生了什么。

以下是我打印出的问题行。可以看到,电影片名中确实包含了一个逗号。目前我们的程序只是简单地退出。但如果我们想实际处理这个问题,就需要采取不同的方法。

初步解决方案

当我们按逗号分割时,电影片名被错误地分成了两部分。我们需要将其重新组合起来。

对于电影片名,我们原本将其赋值为 values[4]。现在,我们需要将其修改为使用F字符串将两部分重新组合。具体做法是,将 values[4] 和一个逗号以及 values[5] 拼接起来。

否则,如果分割后的 values 列表长度等于5,我们再将电影片名设置为 values[4]。然后,我们设定一个条件:只有当 values 的长度大于或等于5时,才执行后续的处理逻辑。这样,我们仍然可以处理数据的其余部分。

通过这种方法,我们解决了第一个电影片名的问题。现在,包含逗号的完整电影片名被正确显示,一切看起来都正常了。

方法的局限性

这是一种解决问题的方法。但是,如果多个列中都包含多个逗号呢?显然,我们需要一个更好的方法。

总结

本节课中,我们一起学习了如何处理CSV文件中因数据列包含逗号而导致的解析问题。我们首先通过检查列数来识别问题行,然后尝试通过手动拼接被错误分割的字段来进行修复。虽然这种方法在简单情况下有效,但它无法应对更复杂的情况,例如多个列都包含逗号。这提示我们需要寻找更健壮、通用的解决方案,例如使用Python内置的 csv 模块。

19:sorted()与sort()方法对比 📊

在本节课中,我们将学习Python中两种对列表进行排序的方法:内置函数sorted()和列表方法.sort()。我们将通过对比它们的行为、用法和区别,来理解何时该使用哪一种。

概述

排序是编程中常见的操作。Python提供了两种主要方式来实现排序:一种是sorted()函数,它返回一个新的已排序列表;另一种是.sort()方法,它直接在原列表上进行排序。理解两者的区别对于编写高效、清晰的代码至关重要。

sorted()函数

sorted()是一个内置函数,它可以对任何可迭代对象(如列表、字符串、元组)进行排序,并返回一个新的已排序列表。重要的是,它不会改变原始数据

以下是sorted()函数的基本语法:

sorted(iterable, key=None, reverse=False)

让我们通过一个例子来理解它的工作方式。

a = [5, 1, 4, 3]
print(sorted(a))  # 输出:[1, 3, 4, 5]
print(a)          # 输出:[5, 1, 4, 3] (原列表未改变)

如你所见,sorted(a)返回了排序后的新列表[1, 3, 4, 5],但变量a本身的值仍然是[5, 1, 4, 3]

.sort()方法

上一节我们介绍了不改变原数据的sorted()函数,本节中我们来看看.sort()方法。.sort()是列表对象的一个方法,它直接修改原列表,使其变为有序状态,并且不返回任何值(返回None)。

以下是.sort()方法的基本语法:

list.sort(key=None, reverse=False)

让我们用同样的列表来演示:

a = [5, 1, 4, 3]
print(a.sort())   # 输出:None
print(a)          # 输出:[1, 3, 4, 5] (原列表已被改变)

调用a.sort()后,a本身被永久地排序了。方法调用返回None,这是需要特别注意的地方。

控制排序顺序:升序与降序

无论是sorted()函数还是.sort()方法,都允许我们通过reverse参数来控制排序是升序还是降序。默认情况下reverse=False,即升序排序。将其设置为True即可实现降序排序。

以下是使用sorted()进行降序排序的例子:

strings = [‘a‘, ‘B‘, ‘z‘, ‘C‘]
print(sorted(strings, reverse=True))

以下是使用.sort()进行降序排序的例子:

strings = [‘a‘, ‘B‘, ‘z‘, ‘C‘]
strings.sort(reverse=True)
print(strings)

关于字符串排序,有一个重要的细:在Python的默认排序规则中,大写字母的ASCII码值小于小写字母。因此,在升序排序时,所有大写字母会排在小写字母之前。

核心区别总结

为了帮助你清晰地选择,以下是两种方法的核心区别对比:

  • 返回值sorted()返回一个新的已排序列表。.sort()返回None(不返回新列表)。
  • 对原数据的影响sorted()不修改原始可迭代对象。.sort()直接修改原列表。
  • 适用对象sorted()适用于任何可迭代对象(列表、元组、字符串等)。.sort()仅适用于列表对象。

如何选择?

选择使用sorted()还是.sort(),取决于你的具体需求:

  • 当你需要保留原始数据不变,或者需要对非列表的可迭代对象(如元组)进行排序时,使用 sorted()
  • 当你明确想要改变原列表,并且不需要保留其原始顺序时,使用 .sort()。这种方式通常更节省内存,因为它不需要创建列表的副本。

总结

本节课中我们一起学习了Python中两种排序方式。sorted()函数返回排序后的新列表而不影响原数据,而.sort()方法则直接修改原列表。记住sorted()适用于更多数据类型,而.sort()仅用于列表。根据你是否需要改变原始列表以及处理的数据类型,可以明智地选择使用哪一种方法。

20:按值排序字典技巧 📊

在本节课中,我们将学习如何对Python字典进行排序。字典本身是无序的数据结构,但我们可以通过一些技巧,根据其键或值来获取一个有序的列表。本节将重点介绍如何根据字典的进行排序。


直接对字典排序

首先,我们来看看如果直接对字典使用 sorted() 函数会发生什么。

假设我们有一个字典,记录了颜色及其对应的数值:

my_dict = {'red': 3, 'green': 1, 'blue': 2}

如果我们直接打印 sorted(my_dict)

print(sorted(my_dict))

输出结果将是字典的升序列表:['blue', 'green', 'red']

如果加上 reverse=True 参数:

print(sorted(my_dict, reverse=True))

则会得到键的降序列表:['red', 'green', 'blue']

这种方法只对键排序,无法看到对应的值,通常不是我们想要的结果。


对字典项(键值对)排序

为了同时获取键和值,我们需要先使用 .items() 方法获取字典的键值对列表,每个键值对是一个元组。

print(sorted(my_dict.items()))

输出是一个元组列表,但排序依据仍然是(元组的第一个元素):
[('blue', 2), ('green', 1), ('red', 3)]

同样,使用 reverse=True 会按键降序排列。

这仍然不是按值排序。如果我们想根据字典的(元组的第二个元素)来排序,就需要使用一个关键函数来指定排序依据。


使用Lambda函数按值排序

为了按值排序,我们需要在 sorted() 函数中使用 key 参数,并传入一个 lambda函数。Lambda函数是一种临时、简洁的函数定义方式。

我们的目标是告诉Python:请根据每个元组的第二个元素(即值)进行排序。

以下是具体做法:

print(sorted(my_dict.items(), key=lambda item: item[1]))
  • my_dict.items(): 生成一个如 [('red', 3), ('green', 1), ('blue', 2)] 的列表。
  • key=lambda item: item[1]: 这是一个lambda函数。它接收一个参数 item(即每个元组),并返回 item[1],也就是元组的第二个元素——字典的值。

执行这行代码后,输出为:[('green', 1), ('blue', 2), ('red', 3)]。列表已经根据值 1, 2, 3 升序排列。

如果想要按值降序排列,只需添加 reverse=True 参数:

print(sorted(my_dict.items(), key=lambda item: item[1], reverse=True))

输出将变为:[('red', 3), ('blue', 2), ('green', 1)]


理解Lambda函数

在上面的代码中,lambda item: item[1] 是一个临时函数。你可以这样理解它:

def get_value(item):
    return item[1]

然后这样调用:key=get_value。Lambda函数只是将这个定义和调用过程浓缩成了一行。

它指导排序算法在比较两个元组时,不是比较元组本身(默认比较第一个元素),而是比较它们各自通过lambda函数返回的值(即 item[1])。


总结

本节课中我们一起学习了如何对Python字典进行排序。

  1. 直接对字典使用 sorted() 会返回的排序列表。
  2. 使用 .items() 方法可以获取键值对元组列表,但默认仍按排序。
  3. 要按排序,需在 sorted() 函数中使用 key 参数,并借助 lambda函数(如 key=lambda item: item[1])来指定根据元组的第二个元素(值)进行排序。
  4. 结合 reverse=True 参数可以实现降序排列。

通过掌握 sorted() 函数与 lambda 表达式的结合使用,你就能灵活地根据需要对字典内容进行各种排序了。

21:调试技术解析 🐛

在本节课中,我们将学习什么是调试,以及如何系统地找出并修复代码中的错误。

什么是调试?

调试是指尝试找出代码中存在的问题并加以解决的过程。当代码存在“漏洞”或无法正常工作时,我们就需要进行调试。这可能意味着代码无法编译,也可能意味着代码虽然能运行,但产生的结果与预期不符。

在代码中发现的错误通常被称为“漏洞”或“Bug”。这个术语由来已久,至少可以追溯到1878年。偶尔会有人误以为“Bug”一词源于20世纪40年代格蕾丝·霍珀在计算机中发现一只真正的虫子,但这个故事并不准确。实际上,“Bug”这个术语在那之前早已存在。不过,当时确实有人将那只导致计算机故障的虫子粘在日志本上,以示这是第一个被发现的、引发实际问题的“真虫子”。

调试的本质

通常,我们要找的并非真正的虫子,而是代码中的某种逻辑或语法错误,并试图修复它。

上一节我们介绍了调试的基本概念,本节中我们来看看一些常见的调试场景和基本思路。

调试的常见场景

以下是代码可能出现问题的几种典型情况:

  1. 编译错误:代码无法通过解释器或编译器的语法检查,无法运行。
  2. 运行时错误:代码可以运行,但在执行过程中崩溃或抛出异常。
  3. 逻辑错误:代码可以顺利运行,但产生的结果或行为与程序员的预期不符。

总结

本节课中我们一起学习了调试的核心概念。我们了解到,调试是定位和修复代码错误的过程,这些错误可能阻止代码运行,也可能导致程序行为异常。理解“Bug”这一术语的历史,有助于我们认识到寻找和修复代码问题是软件开发中一项长期且基础的工作。

记住,调试不仅是一项技术,更是一种系统化解决问题的思维方式。在后续课程中,我们将学习更多具体的调试工具和技巧。

22:问题定位方法 🐛

在本节课中,我们将学习当代码出现错误或缺陷(Bug)时,如何进行定位和诊断。我们将介绍两种核心方法:使用打印语句和利用调试器。

当代码出现错误时,首要任务是隔离问题,即确定错误在代码中的位置、发生的原因以及触发的时机。

以下是两种常用的定位方法。

使用打印语句

一种直接的方法是添加打印语句。你可以通过注释掉部分代码,仅运行你想要测试的部分,然后在关键位置添加打印语句来查看特定变量的值。

例如,在Python中:

def calculate_total(price, quantity):
    print(f"[DEBUG] 进入函数,price={price}, quantity={quantity}") # 打印输入值
    total = price * quantity
    print(f"[DEBUG] 计算出的 total={total}") # 打印计算结果
    return total

这种方法通过输出中间状态来帮助你理解程序的执行流程。

使用调试器

上一节我们介绍了通过修改代码来打印信息的方法,本节中我们来看看另一种更强大的工具:调试器。

调试器是一种允许你在一个或多个点暂停程序执行,并查看变量值的工具。它的原理类似于添加打印语句,但优势在于无需修改你的源代码

在大多数集成开发环境(IDE)中,你可以通过设置断点来使用调试器。程序运行到断点时会暂停,此时你可以检查所有变量的当前状态。

本节课中我们一起学习了两种定位代码问题的方法:使用打印语句进行手动检查,以及利用调试器进行交互式调试。掌握这些技能将帮助你更高效地发现和修复程序中的错误。

23:调试器使用优势 🐛

在本节课中,我们将探讨为什么在调试代码时,使用调试器(Debugger)比单纯使用 print 语句更具优势。我们将通过对比两者的工作方式,帮助你理解调试器如何提升你的开发效率和代码理解深度。

概述

调试是编程中不可或缺的一环。初学者常常依赖插入 print 语句来查看变量值,但这种方法存在诸多局限。本节将详细介绍调试器的核心优势,它能让你在不修改代码的情况下,更精确、更高效地洞察程序的运行状态。

为何选择调试器而非 print 语句?

上一节我们介绍了调试的基本概念,本节中我们来看看调试器相较于 print 语句的具体优势。主要理由有以下几点:

1. 避免信息过载

当你使用 print 语句进行调试,并且代码涉及大量循环(例如读取一个很长的文件)时,控制台会输出海量的信息。你并不想查看所有的 print 输出,那会非常繁琐。

调试器的优势在于:你可以在程序的特定点设置断点并暂停,然后只查看该点的变量值。这样你就不必一直坐在那里打印每一个值。

2. 深入理解代码执行流程

调试器能帮助你更好地理解程序。你可以单步执行代码,精确观察每一行代码执行时发生了什么。

以下是调试器帮助你理解的关键方面:

  • 变量的变化:你可以实时观察每个变量的值如何随着代码执行而改变。
  • 控制流的跳转:当代码调用一个函数时,你可以看到执行点跳转到该函数内部。这让你能清晰地跟踪程序的执行路径。

通过这种方式,你能真正地、更深入地理解代码执行时究竟在发生什么。如果你理解了正在发生的事情,这将为你节省大量时间。

3. 保持代码纯净,避免引入错误

使用 print 语句调试需要你编辑源代码。这可能会带来风险:

  • 你可能意外地破坏了一些不想改动的内容。
  • 你可能在发布代码时忘记删除调试用的 print 语句,而正式代码中通常不希望包含这些语句。

调试器解决了这个问题:它允许你在想要的地方暂停,而无需修改代码。你可以精确地查看正在发生的情况,并单步执行以观察细节。

总结

本节课中我们一起学习了调试器相对于 print 语句的三大核心优势:避免输出信息过载深入理解代码执行细节以及无需修改源代码,保持代码纯净。因此,我推荐你在调试时积极使用调试器。掌握这个工具将显著提升你排查问题和理解代码的能力。

24:VS Code调试实践 🐛

在本节课中,我们将学习如何在VS Code中使用调试器来查找和修复代码中的错误。调试是编程中至关重要的技能,它能帮助我们理解代码的执行流程,并定位问题所在。

概述

我们将通过一个具体的例子,演示在VS Code中设置断点、启动调试器、以及使用各种调试控制命令(如继续、单步跳过、单步进入等)来逐步分析代码的执行过程。目标是修复一个存在逻辑错误的函数。

设置断点

调试的第一步是设置断点。断点会告诉调试器在代码的特定位置暂停执行。

在VS Code中,设置断点的方法是在代码行号的左侧区域点击。点击后会出现一个红色圆点,表示断点已设置。若要清除断点,再次点击该红点即可。你可以在代码的多个位置设置断点。

启动调试器

设置好断点后,下一步是启动调试器。

点击VS Code侧边栏的“运行和调试”图标(虫子图标),然后点击“运行和调试”按钮。代码将开始执行,并在遇到第一个断点时暂停。

当程序在断点处暂停时,你会看到断点红点周围有一个黄色箭头,表示当前执行暂停在此行之前。此时,左侧的“变量”面板会显示当前已定义的所有变量及其值,方便你检查程序状态。

调试控制命令

当程序在断点处暂停后,你可以使用顶部的一排控制按钮来决定下一步如何执行。以下是这些按钮的含义:

  • 继续 (Continue):继续执行程序,直到遇到下一个断点、发生错误或程序自然结束。
  • 单步跳过 (Step Over):执行当前行代码。如果该行包含函数调用,会直接执行完整个函数,然后停在下一行。这是最常用的调试步骤。
  • 单步进入 (Step Into):执行当前行代码。如果该行包含函数调用,调试器会进入该函数内部,并停在函数的第一行。
  • 单步跳出 (Step Out):如果当前在某个函数内部,此命令会执行完该函数剩余的所有代码,并返回到调用该函数的位置。
  • 重启 (Restart):重新启动整个调试会话。
  • 停止 (Stop):终止当前的调试会话。

调试实践示例

现在,我们通过一个实际的例子来应用这些知识。假设我们有一个函数 count_matches,用于计算两个已排序列表中相同元素的数量,但它存在一些错误。

首先,我们直接运行代码,发现了一个断言错误:函数返回了 None,而不是预期的数字 0。这通常意味着函数忘记返回一个值。我们修复了这个问题,添加了 return count 语句。

修复后再次运行,又出现了新的断言错误:预期匹配数为 2,但函数返回了 1。这表明我们的逻辑还存在问题。此时,使用调试器来逐步分析就非常合适。

我们启动调试器,并在函数的关键循环处设置断点。程序在断点处暂停后,我们开始使用“单步跳过”命令逐行执行。

通过观察变量值的变化,我们发现了一个“差一错误”:循环条件 while L1 > 0 and L2 > 0 导致我们漏掉了索引为 0 的元素比较。我们将循环条件修改为 while L1 >= 0 and L2 >= 0

保存修改并重新运行所有测试,这次所有测试都通过了。这个例子清晰地展示了如何使用调试器定位并修复一个逻辑错误。

总结

本节课中,我们一起学习了VS Code调试器的基本使用方法。我们掌握了如何设置断点、启动调试会话,以及使用“继续”、“单步跳过”、“单步进入”等控制命令来逐步跟踪代码执行。通过一个实际的调试案例,我们实践了如何利用这些工具来观察程序状态、分析逻辑错误,并最终成功修复代码。熟练使用调试器将极大地提升你解决问题的效率和代码质量。

25:课程概述 🎯

在本节课中,我们将要学习第三周的核心内容。本周我们将重点探讨如何使用CSV文件的读取器和写入器,深入理解单元测试中的setup方法,并掌握更高级的调试技巧,包括设置多个断点以及区分“单步跳过”与“单步进入”操作。


CSV文件的读写操作 📄

上一节我们介绍了本周的学习目标,本节中我们来看看CSV文件的操作。CSV文件非常常见且实用,Python提供了一个专门的类来简化其处理,尤其是在某个字段本身包含逗号时,可以避免错误地分割数据。

以下是使用Python内置csv模块处理文件的核心概念:

代码示例:使用csv.reader

import csv
with open('data.csv', 'r') as file:
    csv_reader = csv.reader(file)
    for row in csv_reader:
        print(row)

代码示例:使用csv.writer

import csv
data = [['Name', 'Age'], ['Alice', 30], ['Bob', 25]]
with open('output.csv', 'w', newline='') as file:
    csv_writer = csv.writer(file)
    csv_writer.writerows(data)

深入单元测试:setup方法 🧪

了解了CSV操作后,我们将更深入地学习单元测试。本节重点介绍setup方法,该方法会在每个测试用例运行前被调用,用于预先设置测试所需的数据。

以下是使用unittest框架中setUp方法的示例:

代码示例:使用setUp方法

import unittest

class TestExample(unittest.TestCase):
    def setUp(self):
        # 该方法在每个测试方法前运行
        self.data = [1, 2, 3, 4, 5]

    def test_sum(self):
        self.assertEqual(sum(self.data), 15)

    def test_length(self):
        self.assertEqual(len(self.data), 5)

if __name__ == '__main__':
    unittest.main()

高级调试技巧:断点与步进 🔍

掌握了测试数据准备后,我们进入调试环节。本周我们将学习更深入的调试器使用技巧,包括设置多个断点,并使用“继续”命令在断点间跳转。

以下是调试过程中的核心操作概念:

关键概念:

  • 设置断点:在代码的特定行暂停执行。
  • 继续 (Continue):从当前断点运行到下一个断点。
  • 单步跳过 (Step Over):执行当前行,但不进入该行调用的函数内部。
  • 单步进入 (Step Into):执行当前行,并进入该行调用的函数内部。
  • 观察变量 (Watch Variables):在调试过程中监视变量的值如何变化。

在调试时,理解何时使用“单步跳过”与“单步进入”至关重要。通常,当你信任某个函数本身的正确性,只想关注当前流程时,使用“单步跳过”。当你需要深入检查某个函数的具体实现时,则使用“单步进入”。


总结 📝

本节课中我们一起学习了第三周的核心技能。我们掌握了如何使用csv模块安全高效地读写CSV文件,了解了如何在单元测试中使用setUp方法为每个测试用例准备数据,并实践了设置多个断点、使用“继续”命令以及区分“单步跳过”和“单步进入”等高级调试技术。希望你能享受本周的学习,并灵活运用这些工具来提升编程效率与代码质量。

26:修复年度总计函数 🐛

在本节课中,我们将学习如何调试一个从CSV文件读取数据并计算年度总计的函数。我们将逐步分析代码中的问题,并修复它们,最终使函数能够正确运行。


概述

我们有一个从CSV文件读取数据的函数,目标是创建一个字典,其中键是年份,值是各月份的数值列表。然而,当前代码存在几个错误,导致无法正确生成字典和计算年度总计。本节将引导你逐一识别并修复这些问题。

导入CSV模块

首先,我们需要导入CSV模块来处理CSV文件。这是读取文件数据的基础步骤。

import csv

创建空字典

在读取数据之前,我们初始化一个空字典,用于存储后续从文件中提取的数据。

data_dict = {}

读取文件并创建CSV阅读器

我们打开文件并创建一个CSV阅读器对象。这里需要注意阅读器对象名称的大小写。

with open('data.csv', 'r') as file:
    csv_reader = csv.reader(file)

上一节我们介绍了如何导入模块和初始化数据结构,本节中我们来看看如何从CSV文件中读取数据。

处理表头行

CSV文件的第一行通常是表头,包含了列名(如月份和年份)。我们需要正确读取并处理这些表头。

以下是处理表头的步骤:

  • 读取第一行作为表头。
  • 去除每个表头项可能存在的多余空格和引号。
headers = next(csv_reader)
cleaned_headers = [header.strip().strip('"') for header in headers]

遍历数据行并填充字典

处理完表头后,我们遍历文件的剩余行,将数据填充到之前创建的字典中。

以下是填充字典的步骤:

  • 遍历CSV阅读器中的每一行。
  • 将年份(来自处理后的表头)作为字典的键。
  • 将该年份对应的各月数据(转换为整数)作为值存入列表。
for row in csv_reader:
    for i, year in enumerate(cleaned_headers[1:]):  # 跳过“月份”列
        if year not in data_dict:
            data_dict[year] = []
        # 假设行中第一个元素是月份,后续是各年数据
        data_dict[year].append(int(row[i+1]))

调试与修复错误

在运行初始代码时,我们遇到了错误。让我们来逐一分析并修复。

首先,创建CSV阅读器时对象名应为小写 reader,而不是 Reader

# 错误示例
csv_Reader = csv.Reader(file)
# 正确示例
csv_reader = csv.reader(file)

其次,在遍历行时,我们应循环 csv_reader 对象,而不是原始文件对象 file

# 错误示例
for row in file:
# 正确示例
for row in csv_reader:

最后,我们发现从表头读取的年份字符串包含了额外的引号和空格,导致字典键匹配失败。我们需要在将年份用作键之前对其进行清理。

# 清理前的表头可能类似:' "1958" '
# 使用 .strip().strip('"') 进行清理
cleaned_year = year.strip().strip('"')

计算年度总计

字典正确填充数据后,计算某个年份的总计就变得简单了。我们只需获取该年份对应的列表,并使用 sum() 函数求和。

def get_total_for_year(data_dict, year):
    """
    计算给定年份的总计。
    参数:
        data_dict (dict): 数据字典
        year (str): 指定的年份
    返回:
        int: 该年份所有月份数据的总和
    """
    return sum(data_dict.get(year, []))

总结

本节课中我们一起学习了如何调试一个数据处理的函数。我们修复了CSV阅读器对象名称的大小写错误、更正了数据遍历的对象,并清理了表头字符串中的多余字符。通过这些步骤,我们成功构建了数据字典,并实现了计算年度总计的功能。调试是一个系统化的过程,关键在于仔细阅读错误信息、检查数据流,并逐步验证代码的每个部分。

27:修正最大月份错误 🐛

在本节课中,我们将学习如何调试并修复一个名为 get_max_month 的函数。该函数旨在从CSV数据文件中找出降雨量最高的月份,但在运行过程中出现了多个错误。我们将逐步分析这些错误,并逐一修正它们。

概述与问题定位

首先,我们尝试运行代码,遇到了第一个错误:CSB is not defined。这表明代码中引用了未定义的变量或模块。

检查代码顶部,发现没有导入必要的 csv 模块。因此,我们需要添加 import csv 语句来导入该模块。修正后,这个错误消失了。

修正CSV读取器错误

接下来,代码报错:CSV reader expects at least one argument got zero。这意味着在创建 csv.reader 对象时,没有传入必要的参数。

查看代码,csv.reader 应该接收一个已打开的文件对象作为参数。修正方法是将正确的文件对象 in_file 传递给 csv.reader

修正数据读取逻辑

随后,代码在 skipping the header 部分遇到了问题。错误提示 length of values equal equal 4 表明逻辑有误。

分析发现,代码中错误地引用了 values 变量,而实际上应该使用 row 变量来获取行的长度。同时,月份信息也应该从 row 中提取,而不是 values。我们修正了这些引用。

修正表头处理

接着,出现了 header is not defined 的错误。检查代码发现,变量名拼写不一致,代码中使用了 headers(复数),但引用时却用了 header(单数)。我们将引用统一修正为 headers

此外,表头是从错误的对象(in_file 作为字符串)读取的。正确的做法是从 csv.reader 对象 csv_file 中读取表头行。

修正后,我们发现表头内容仍然异常,包含了额外的空格和双引号。因此,我们需要对每个表头字符串进行处理,使用 .strip() 方法去除首尾空格和双引号。

修正最大值计算逻辑

尽管上述修正完成,单元测试仍然失败,提示“September does not equal August”。这表明计算最大降雨量月份的逻辑有误。

我们检查了数据读取部分,月份和年份的对应关系看起来是正确的。问题出在后续查找最大值的逻辑上。

初始代码创建了一个空字典,并试图用 lambda 表达式按字典的键(t[0])排序。然而,我们需要的是按字典的值(即降雨量)进行排序,以找到最大值。因此,应将排序的 key 参数改为 lambda t: t[1],这样就能根据降雨量数值正确排序并找到最大值。

完成此修正后,所有单元测试都通过了。

总结

本节课中,我们一起调试并修复了 get_max_month 函数的多个错误。我们学习了:

  1. 导入必要的Python模块(import csv)。
  2. 正确初始化 csv.reader 对象。
  3. 准确引用变量(如 row 而非 values)。
  4. 保持变量名一致。
  5. 从正确的数据源读取并清洗数据(处理表头)。
  6. 修正核心算法逻辑,确保按正确的值(降雨量)进行排序以找到最大值。

通过这个练习,我们实践了系统化调试代码的流程:运行代码、阅读错误信息、定位问题根源,并实施针对性修正。

28:修复年龄前五错误 🐛

在本节课中,我们将学习如何调试和修复一个Python脚本中的错误。该脚本旨在读取一个包含女演员信息的CSV文件,统计每个年龄出现的次数,并将结果写入新的CSV文件。我们将逐步分析错误信息并修正代码。


上一节我们介绍了调试的基本概念,本节中我们来看看一个具体的调试案例。

问题描述与目标

脚本的目标是遍历一个关于女演员信息的文件,返回一个字典,其中包含年龄(age)以及在该年龄获奖的女演员(actresses)数量。

以下是输入文件的结构示例。文件包含一个标题行,列名分别为:indexyearageWhitname movie。随后是数据行。请注意,电影标题(movie title)可能包含逗号,这正是我们需要使用CSV读取器的原因之一。

运行代码与错误分析

当我们尝试运行初始代码时,遇到了第一个错误。

错误1: CSF file is not defined on line 13

  • 分析: 第13行引用了CSF_file,但查看上方代码发现变量名是CSV_file,存在拼写不一致。
  • 修复: 将第13行的CSF_file统一更正为CSV_file

修复第一个错误后,我们再次运行代码,遇到了第二个错误。

错误2: CSF files 16

  • 分析: 第16行再次出现了对变量CSF_file的错误引用或拼写变化。
  • 修复: 将第16行的引用也更正为CSV_file

继续运行,我们遇到了第三个错误。

错误3: values is not defined on line 20

  • 分析: 第20行的条件判断if len(values) == 5中,变量values未定义。根据上下文,这里应该判断的是当前行row的长度。
  • 修复:len(values)更正为len(row)

修正后再次运行,遇到了第四个错误。

错误4: no attribute right on a CSB writer

  • 分析: 第46行尝试写入数据时,使用了错误的方法名right。CSV写入器的正确方法名是writerow
  • 修复:writer.right(row)更正为writer.writerow(row)

调试步骤总结

以下是本次调试过程中识别和修复的主要问题列表:

  1. 变量名拼写不一致:CSF_file统一更正为CSV_file
  2. 未定义变量: 将条件判断中的values更正为row
  3. 方法名错误: 将写入器的right方法更正为writerow方法。

完成以上修正后,代码即可成功运行。


本节课中我们一起学习了如何通过阅读错误信息、定位代码行以及逻辑推理来逐步调试程序。我们修复了变量名拼写错误、未定义变量引用以及错误的方法调用,最终使脚本能够正确执行其统计功能。

29:CSV写入单元测试 📝

在本节课中,我们将学习如何为那些不返回任何值,但会产生“副作用”(例如写入文件)的函数编写单元测试。我们将以测试一个将嵌套字典写入CSV文件的函数为例,学习如何通过读取生成的文件来验证函数的行为是否正确。


上一节我们介绍了如何测试返回值的函数。本节中我们来看看,如果一个函数不返回任何值,我们该如何测试它。这类函数通常会产生某种“副作用”,比如修改数据、打印信息或写入文件。我们的测试目标就是验证这个“副作用”是否按预期发生。

具体到本例,我们将测试一个写入CSV文件的函数。测试思路是:先调用函数写入文件,然后读取该文件,最后验证文件内容是否符合预期。

以下是测试一个写入CSV文件的函数的基本步骤:

  1. 准备测试数据:创建一个嵌套字典作为函数的输入。
  2. 执行待测函数:调用函数,将数据写入指定的CSV文件。
  3. 读取并验证结果:打开被写入的CSV文件,读取其内容,并与预期值进行比较。

让我们通过代码来具体实现这个测试流程。

import unittest
import csv

# 假设这是我们要测试的函数
def write_dict_to_csv(filename, data_dict):
    """将嵌套字典写入CSV文件。"""
    with open(filename, 'w', newline='') as csvfile:
        # 假设字典的键作为表头,值作为第一行数据(简化示例)
        writer = csv.writer(csvfile)
        headers = list(data_dict.keys())
        writer.writerow(headers)
        writer.writerow([data_dict[h] for h in headers])

class TestCSVWrite(unittest.TestCase):
    """测试CSV写入功能的测试类。"""

    def test_write_dict_to_csv(self):
        """测试 write_dict_to_csv 函数。"""
        # 1. 准备测试数据
        test_data = {'Name': 'Alice', 'Age': 30, 'City': 'New York'}
        test_filename = 'test_output.csv'

        # 2. 执行待测函数(不接收返回值)
        write_dict_to_csv(test_filename, test_data)

        # 3. 读取并验证结果
        with open(test_filename, 'r', newline='') as csvfile:
            reader = csv.reader(csvfile)
            # 读取表头
            header = next(reader)
            # 读取数据行
            row = next(reader)

        # 使用 assertEqual 断言验证内容
        self.assertEqual(header, ['Name', 'Age', 'City'])
        self.assertEqual(row, ['Alice', '30', 'New York'])

        # (可选)测试完成后清理生成的文件
        import os
        os.remove(test_filename)

# 如果直接运行此脚本,则执行测试
if __name__ == '__main__':
    unittest.main()

在上面的代码中,我们首先定义了一个待测试的函数 write_dict_to_csv。接着,我们创建了一个继承自 unittest.TestCase 的测试类 TestCSVWrite。在测试方法 test_write_dict_to_csv 中,我们遵循了“准备-执行-验证”的步骤。最后,我们使用 assertEqual 来断言文件内容是否与预期完全一致。


本节课中我们一起学习了如何为具有“副作用”的函数(特别是文件写入操作)编写单元测试。核心方法是执行函数后,读取其产生的效果(如文件内容),并与预期结果进行比对。通过这种方式,我们可以确保那些不直接返回值的函数也能被可靠地测试和验证。

30:VS Code 调试器深度探索 🔍

在本节课中,我们将深入学习如何使用 Visual Studio Code 的调试器来诊断和修复代码中的问题。我们将通过一个处理电子书日志文件数据的实际例子,演示如何设置断点、单步执行代码、检查变量状态,从而高效地定位错误。


概述

我们将分析一个来自电子书系统的日志文件片段,该文件记录了用户回答多选题的情况。我们的目标是编写代码来统计有多少个不同的唯一问题、有多少人尝试了这些问题,以及最终有多少人答对了。在运行初始代码时,我们遇到了断言错误,这表明我们的统计结果与预期不符。接下来,我们将使用 VS Code 调试器来一步步探究问题所在。

日志数据结构

首先,我们需要理解日志数据的格式。每一行日志都包含多个字段,以下是一个示例:

6 1 8 [日期时间] [用户ID] MChoice [答案信息] [是否正确] [唯一问题ID] ...

对于多选题,我们主要关心的信息包括:用户给出的答案、该答案是否正确,以及该问题的唯一标识符(例如 Q2_2Q_1)。我们已经提取了一个小的日志片段,并基于此编写了测试用例。

初始问题与调试准备

当我们运行初始代码时,遇到了一个断言错误:预期有 3 个唯一问题,但代码只统计出 1 个。这显然有问题,是使用调试器的绝佳时机。

我们在代码中设置两个断点:

  1. 第一个断点设在读取并解析日志数据之后,目的是检查我们是否正确提取了各个字段。
  2. 第二个断点设在计算用户正确率的核心逻辑附近,以便观察数据是如何被处理和统计的。

在 VS Code 中,只需点击行号左侧的灰色区域即可设置或取消断点。

调试过程:定位第一个错误

启动调试后,程序在第一个断点处暂停。我们检查此时变量的值,特别是 div(问题唯一ID)字段。

我们发现 div 的值是 12,但这并不是我们期望的问题ID。通过观察数据数组的索引,我们意识到在分割日志行时,索引计算出现了偏差。我们错误地将索引 5 的数据当作了 div,而它实际应该是索引 6 的数据。

修正后的字段索引对应关系应为:

# 假设 data 是分割后的列表
timestamp = data[2]
user_id = data[3]
event_type = data[4]
answer_info = data[5]
div_id = data[6]  # 修正后的索引
correct = data[7]

修正这个索引错误后,我们保存代码并重新运行。现在,div 字段能正确显示为类似 Q2_2Q_1 的问题ID了。

调试过程:单步执行与深入函数

修正第一个错误后,我们继续调试。使用 “单步跳过” 按钮逐行执行,不进入函数内部。我们看到 answercorrect 等变量被正确赋值。

当执行到条件判断 if div not in problem_dictionary: 时,由于字典初始为空,条件为真,程序进入相应的代码块。这里调用了 add_answer 函数。

为了理解 add_answer 函数内部的逻辑,我们这次使用 “单步进入” 按钮。调试器会跳转到该函数的定义处并暂停。

在函数内部,我们可以:

  • 单步执行:观察它如何初始化字典、设置键值对。
  • 检查变量:在调试侧边栏或通过鼠标悬停查看 correct 等变量的实时值。
  • 使用“单步跳出”:在理解函数逻辑后,可以直接执行完该函数并返回到调用处。

高效使用断点与继续执行

在循环体中设置断点时,调试器会在每次循环迭代时都暂停,这可能很低效。我们可以:

  1. 在循环开始后设置一个断点,检查一轮迭代的数据是否正确。
  2. 确认逻辑无误后,移除该断点。
  3. 点击 “继续” 按钮,程序将一直运行,直到遇到下一个剩余的断点或程序结束。

这种方法允许我们灵活地控制调试节奏,快速跳过已知正常的代码段,直接聚焦于可能存在问题的部分。


总结

本节课我们一起深入探索了 VS Code 调试器的核心功能。我们学习了:

  • 如何根据错误信息设置断点。
  • 如何使用 单步跳过单步进入继续 来控制调试流程。
  • 如何在调试过程中检查变量的值,以验证逻辑是否正确。
  • 如何通过动态管理断点来提高调试效率。

调试是一个强大的技能,它能帮助你深入理解代码的执行过程,并快速定位逻辑错误。掌握这些调试技巧,将使你在面对复杂代码问题时更加从容自信。

31:setup方法应用 🧪

在本节课中,我们将学习如何在处理CSV文件时创建单元测试。我们将重点探讨测试类中的 setup 方法,它如何在每个测试用例运行前被调用,以及如何利用它来准备测试数据。


上一节我们介绍了单元测试的基本概念,本节中我们来看看一个具体的应用场景:测试处理CSV文件的代码。

假设我们有一段代码,用于处理一个包含多选题及其答案的日志文件数据。我们的目标是统计其中有多少个不同的唯一多选题,每个问题有多少人尝试回答,以及最终有多少人回答正确。在修复代码后,我们需要确保它能通过一系列单元测试。

让我们仔细查看这些单元测试。这里特别需要注意的是 setup 方法。在你的测试用例类中,可以选择性地定义一个 setup 方法。这个方法会在每一个测试用例运行之前被调用。例如,它会在运行 test_length 之前被调用,也会在运行 test_data 之前被调用。

setup 方法中,我们通常执行一些准备工作。在这个例子里,代码会读取一个CSV文件的数据,并将其存储在一个字典中。我们使用 self.data_d 来引用这个字典。在单元测试的类中,self 代表当前类的实例对象,因此 self.data_d 是为该对象添加的一个属性。

随后,在每个具体的测试用例方法中,我们都可以直接使用 self.data_d 来访问这些预先准备好的数据。这样,我们就可以进行各种断言检查,例如检查字典的长度是否为3,或者检查某个问题ID对应的数据是否正确。

以下是编写此类测试时需要注意的几个要点:

  • 整数比较:比较整数值时,可以使用 assertEqual
  • 浮点数比较:比较可能为浮点数的值时,应使用 assertAlmostEqual。这个方法允许你指定要比较的两个值,以及精确到小数点后多少位。

通过这种方式,我们可以在使用CSV读取器和写入器时,有效地编写和运行单元测试。


本节课中我们一起学习了如何在单元测试中应用 setup 方法来为测试准备CSV数据。我们了解了 setup 方法在每个测试前的执行机制,以及如何使用 self 在测试用例间共享准备好的数据。同时,我们也回顾了针对整数和浮点数进行断言检查的正确方法。

32:正则表达式入门指南 🧩

在本节课中,我们将要学习如何使用正则表达式。正则表达式是一种强大的工具,它能让我们轻松地搜索文本或验证文本格式,例如检查密码是否符合规则,或在文件中查找特定内容。

什么是正则表达式? 🤔

正则表达式是一系列字符,它们能非常简洁地指定如何匹配文本。正则表达式主要有两个用途:一是验证文本格式(例如密码或电子书中的填空题),二是查找文本(例如在文件中搜索特定模式)。

上一节我们介绍了正则表达式的定义和用途,本节中我们来看看正则表达式的一些核心组成部分。

正则表达式的核心概念

以下是正则表达式中一些常用的匹配模式:

  • 精确匹配:直接匹配指定的字符。例如,正则表达式 cat 匹配字符串中的 “cat”。
  • 通配符:句点 . 可以匹配除换行符外的任意单个字符。
  • 字符范围:使用方括号 [] 指定一个字符集合。例如,[a-z] 匹配任意一个小写字母。
  • 否定范围:在方括号内使用 ^ 表示“除了”。例如,[^0-9] 匹配任意一个非数字字符。
  • 量词:指定前面元素出现的次数。
    • * 匹配0次或多次。
    • + 匹配1次或多次。
    • ? 匹配0次或1次(即可选)。
  • 字符类:一些预定义的快捷方式。
    • \s 匹配任何空白字符(如空格、制表符)。
    • \S 匹配任何非空白字符。
    • \w 匹配字母、数字或下划线。
    • \W 匹配非字母、数字或下划线的字符。
  • 锚点:指定匹配发生的位置。
    • ^ 匹配字符串的开头。
    • $ 匹配字符串的结尾。
    • \b 匹配单词边界。

了解了基本匹配模式后,我们需要注意一个特殊的符号:圆括号。

需要注意的符号:圆括号

在正则表达式中,圆括号 () 用于定义“捕获组”,它不仅能将模式分组,还会记住匹配的内容以供后续使用。如果只想分组而不需要捕获,可以使用非捕获组语法 (?:...)

本节课中我们一起学习了正则表达式的基础知识,包括其定义、用途以及核心的匹配模式,如通配符、量词、字符类和锚点。我们还了解了圆括号在分组和捕获中的作用。掌握这些概念将帮助你更高效地处理文本搜索和验证任务。

33:正则表达式入门 🧩

在本节课中,我们将深入学习正则表达式,并了解如何使用它们来精确地查找和处理文本数据。

深入探索正则表达式

上一节我们介绍了正则表达式的基本概念,本节中我们来看看一个具体的应用实例。

这是一个非常简单的例子,它只是在文本中查找特定的字符。代码从文件中读取内容,并检查每一行是否包含“From:”这个字符串。如果包含,就打印出该行。

# 示例:查找包含“From:”的行
import re

with open('example.txt', 'r') as file:
    for line in file:
        if re.search('From:', line):
            print(line)

运行这段代码,我们会得到类似“From: someone@example.com”这样的结果。我们可以从这行文本中提取出电子邮件地址。例如,我们可以使用字符串的split方法在冒号处分割,然后去除多余的空格,从而得到邮箱地址。

但是,有更简便的方法来完成这个任务。此外,当前的代码并不能确保“From:”只出现在一行的开头,而且它也没有完全展示正则表达式的强大功能,因为仅用普通的字符串查找也能实现同样的效果。

使用特殊字符进行精确匹配

为了更精确地匹配,我们可以使用特殊字符。例如,使用脱字符^可以指定只匹配字符串的开头。

# 示例:只匹配以“From:”开头的行
import re

with re.search('^From:', line):
    print(line)

这样做,我们得到的结果可能会略有不同,但本质上我们仍然可以使用字符串分割来提取邮箱部分。然而,正则表达式的真正优势在于,它能让我们编写更复杂的模式,以精确地定位我们想要的信息片段。

以下是正则表达式中一些常用的特殊字符及其功能:

  • ^:匹配字符串的开头。
  • $:匹配字符串的结尾。
  • .:匹配任意单个字符(除了换行符)。
  • *:匹配前面的字符零次或多次。
  • +:匹配前面的字符一次或多次。
  • ?:匹配前面的字符零次或一次。
  • []:匹配括号内的任意一个字符。
  • |:表示“或”的关系。

总结

本节课中我们一起学习了正则表达式的基础应用。我们从一个简单的文本查找例子开始,了解了如何使用re.search进行基本匹配。接着,我们引入了脱字符^来指定匹配必须发生在行首,这展示了正则表达式在精确控制匹配位置方面的能力。掌握这些特殊字符是编写高效、精准文本匹配模式的关键。在后续的学习中,我们将继续探索如何组合这些字符,以构建更强大的正则表达式模式。

34:searchfind_all的区别 🔍

在本节课中,我们将要学习Python正则表达式库中search方法与find_all方法的核心区别。我们将通过具体的代码示例,清晰地展示它们各自的行为和输出结果。

导入正则表达式库

首先,我们需要导入Python的正则表达式库re。这是使用正则表达式功能前必须进行的操作。

import re

创建示例字符串

接下来,我们创建一个用于搜索的示例字符串。

string = "The rain in Spain stays mainly in the plain."

使用search方法

上一节我们导入了必要的库并准备了数据,本节中我们来看看search方法的行为。search方法用于在字符串中搜索第一个匹配指定模式的位置。

以下是使用search方法查找“AI”的步骤:

x = re.search("AI", string)
print(x)

执行上述代码,会输出一个匹配对象(Match Object),它告诉我们匹配到的内容及其位置。例如,它可能显示在索引5处找到了“AI”。

我们可以利用这个结果进行条件判断:

if x:
    print(True)
else:
    print(False)

如果搜索一个不存在的模式,例如“blue”,search方法会返回None

x = re.search("blue", string)
print(x)  # 输出:None
if x:
    print(True)
else:
    print(False)  # 输出:False

使用find_all方法

了解了search方法后,我们再来看看find_all方法。find_all方法会返回字符串中所有匹配指定模式的子串,并以列表形式呈现。

以下是使用find_all方法查找所有“AI”的步骤:

all_matches = re.findall("AI", string)
print(all_matches)

在我们的示例字符串中,“AI”出现了两次,因此findall会返回一个包含两个元素的列表。

如果搜索一个不存在的模式,find_all方法会返回一个空列表。

all_matches = re.findall("blue", string)
print(all_matches)  # 输出:[]

核心区别总结

本节课中我们一起学习了searchfind_all的核心区别。

  • re.search(pattern, string):在字符串中搜索第一个匹配项。如果找到,返回一个匹配对象(Match Object);如果未找到,返回 None
  • re.findall(pattern, string):在字符串中搜索所有匹配项。如果找到,返回一个包含所有匹配子串的列表(List);如果未找到,返回一个空列表 []

简单来说,search用于查找“是否有”以及“第一个在哪里”,而find_all用于查找“一共有哪些”。

35:正则表达式101工具使用指南 🛠️

在本节课中,我们将学习如何使用在线工具 regex101.com 来测试和构建正则表达式。这能帮助我们在将正则表达式写入Python代码之前,先验证其功能是否正确。

概述

正则表达式是一种强大的文本匹配工具。在编写复杂的匹配规则时,直接写代码调试可能比较困难。幸运的是,我们可以利用在线工具来可视化地构建和测试正则表达式,regex101.com 就是其中一个优秀的工具。

上一节我们介绍了正则表达式的基本概念,本节中我们来看看如何利用工具来辅助开发。

使用 regex101.com 进行测试

该工具允许你在一个界面中输入正则表达式和测试字符串,并实时看到匹配结果。

1. 精确匹配

最简单的正则表达式就是精确的字符匹配。如果你不使用任何具有特殊含义的元字符,那么正则表达式将直接匹配这些字符本身。

例如,输入正则表达式 art

  • 它会匹配字符串中连续出现的 “a”、“r”、“t” 这三个字符。
  • 在测试字符串 “I can recognize art” 中,它会匹配到 “art” 这个词。
  • 在 “part” 中,它也会匹配到结尾的 “art”。
  • 在 “spirit” 中,则没有任何匹配,因为其中没有连续的 “art” 字符。

核心概念:无特殊字符的正则表达式进行精确匹配

2. 使用特殊字符:点号(.)

正则表达式的强大之处在于其特殊字符(元字符)。其中一个最常用的元字符是点号 .

公式/含义. 匹配除换行符外的任意单个字符。

现在,我们将正则表达式改为 .rt

  • 这个模式的意思是:任意一个字符,后面紧跟着 “r” 和 “t”。
  • 在测试字符串中:
    • “art” 会被匹配(a 匹配 .)。
    • “ert” 会被匹配(e 匹配 .)。
    • “irt” 会被匹配(i 匹配 .)。
  • 因此,所有以 “rt” 结尾的三字母单词都会被高亮显示。

以下是 . 元字符的匹配示例列表:

  • art 中的 a 匹配 .
  • ert 中的 e 匹配 .
  • irt 中的 i 匹配 .

通过这个例子,你可以直观地看到元字符如何扩展了匹配模式。

总结

本节课中我们一起学习了 regex101.com 这个在线工具的基本用法。我们了解到:

  1. 它可以作为正则表达式的“演练场”,在编写代码前进行测试。
  2. 不使用特殊字符时,正则表达式执行精确匹配。
  3. 点号 . 是一个基础且重要的元字符,用于匹配几乎任何单个字符。

掌握这个工具将极大地提升你编写和调试正则表达式的效率。这就是正则表达式工具的基础使用方法。

36:量词应用解析 🔍

在本节课中,我们将学习正则表达式中量词(Quantifiers)的使用。量词用于指定其左侧表达式需要匹配的次数,是构建灵活匹配模式的关键工具。

导入库与准备数据

首先,我们需要导入Python的re库,并声明一个用于测试的字符串。

import re

text = "ABC ABCC ABCCC A B"

量词 +:匹配一次或多次

上一节我们介绍了基础的正则表达式匹配,本节中我们来看看如何使用量词++表示其左侧的字符必须出现一次或多次

以下是使用+的示例:

pattern1 = r"ABC+"
result1 = re.findall(pattern1, text)
print(result1)  # 输出: ['ABC', 'ABCC', 'ABCCC']

在这个例子中,模式ABC+匹配了“ABC”、“ABCC”和“ABCCC”,但没有匹配“A B”。因为模式要求字母C至少出现一次。

量词 ?:匹配零次或一次

接下来,我们探讨量词?。它表示其左侧的字符是可选的,可以出现零次或一次。

以下是使用?的示例:

pattern2 = r"colou?r"
text2 = "color colour colouur"
result2 = re.findall(pattern2, text2)
print(result2)  # 输出: ['color', 'colour']

模式colou?r匹配了“color”和“colour”,但没有匹配“colouur”。因为?只允许字母u出现零次或一次。

量词 *:匹配零次或多次

现在,让我们看看量词*。它表示其左侧的字符可以出现零次或多次

以下是使用*的示例:

pattern3 = r"\d*"
text3 = "123 45 6"
result3 = re.findall(pattern3, text3)
print(result3)  # 输出: ['123', '', '45', '', '6', '']

模式\d*匹配了所有数字序列,甚至包括空字符串(因为*允许零次匹配)。\d是一个特殊字符,代表任何数字(0-9)。

量词 {n}:匹配恰好n次

有时我们需要精确匹配特定次数,这时可以使用花括号{}{n}表示其左侧的字符必须出现恰好n次

以下是使用{n}的示例:

pattern4 = r"\d{2}"
text4 = "1 12 123 1234"
result4 = re.findall(pattern4, text4)
print(result4)  # 输出: ['12', '12', '34']

模式\d{2}只匹配恰好由两个数字组成的序列,如“12”和“34”。

量词 {m,n}:匹配m到n次

最后,我们介绍范围量词{m,n}。它表示其左侧的字符可以出现m到n次(包含m和n)

以下是使用{m,n}的示例:

pattern5 = r"C{1,3}"
text5 = "C CC CCC CCCC"
result5 = re.findall(pattern5, text5)
print(result5)  # 输出: ['C', 'CC', 'CCC', 'CCC']

模式C{1,3}匹配了包含1到3个字母C的序列。对于“CCCC”,它匹配了前三个C(“CCC”)。


本节课中我们一起学习了正则表达式中五种核心量词的用法:

  • + 匹配一次或多次
  • ? 匹配零次或一次(可选)。
  • * 匹配零次或多次
  • {n} 匹配恰好n次
  • {m,n} 匹配m到n次

掌握这些量词能帮助你构建更精确、更灵活的正则表达式模式。

37:字符范围匹配

在本节课中,我们将学习正则表达式中一个非常实用的功能:字符范围匹配。通过使用方括号 [],我们可以指定一组字符,让正则表达式匹配其中的任意一个。这极大地增强了模式匹配的灵活性。

匹配指定字符集合

上一节我们介绍了基础的模式匹配,本节中我们来看看如何使用方括号来定义一个字符集合。在方括号内列出字符,意味着匹配其中任意一个字符。

例如,模式 [AC]+ 表示匹配一个或多个由字母 AC 组成的序列。

以下是该模式的一些匹配示例:

  • CA
  • AC
  • AAA
  • CCC

该模式不会匹配包含 AC 之外字符的字符串,例如 ABCCAT

匹配字符范围

除了列出每个字符,我们还可以使用连字符 - 在方括号内定义一个连续的字符范围。这可以简化对一系列字符的匹配。

例如,模式 [A-C]+ 表示匹配一个或多个从 AC 的字母(即 ABC)。因此,它会匹配 cab,因为 cab 都在 ac 的范围内。

我们也可以定义更常见的范围,例如:

  • [a-z]+:匹配一个或多个小写字母。
  • [A-Z]+:匹配一个或多个大写字母。

需要注意的是,[a-z] 只匹配小写字母,不会匹配大写字母 A

组合多个字符范围

在一个方括号内,我们可以组合多个字符范围,从而匹配更广泛的字符集合。

例如,模式 [A-Za-z]+ 表示匹配一个或多个大写或小写字母。这个模式可以匹配像 Hello 这样的单词。

方括号内的特殊字符

在方括号内部,大多数正则表达式的特殊字符(如 .?)会失去其特殊含义,仅代表字符本身。

这意味着:

  • 模式 [.?] 只会匹配字面的句点 . 或问号 ?,而不会像外面的 . 那样匹配任意字符。
  • 因此,[.?] 可以匹配字符串 "whoop." 中的最后一个句点,但不会匹配其中的 op

使用量词

与之前学习的模式一样,我们可以在包含字符集合或范围的方括号后面加上量词,来指定需要匹配的次数。

例如:

  • [AC]{3}:精确匹配由 AC 组成的 3 个字符序列,如 "ACA"
  • [0-9]*:匹配零个或多个数字。
  • [A-Za-z]?:匹配零个或一个字母。

总结

本节课中我们一起学习了正则表达式中字符范围匹配的用法。关键点在于使用方括号 [] 来定义一个字符集合,匹配其中的任意一个字符。我们可以直接列出字符,也可以用连字符 - 定义范围,还可以组合多个范围。需要注意的是,在方括号内,特殊字符通常只表示其字面含义。最后,我们可以在方括号外使用量词来控制匹配的字符数量。掌握这个功能能让你更精确、更灵活地定义想要查找的文本模式。

38:特殊字符处理 🧩

在本节课中,我们将学习正则表达式中的特殊字符。特殊字符拥有特定的匹配功能,例如匹配数字、字母或空白。理解这些字符是掌握正则表达式的关键。

上一节我们介绍了使用点号.作为通配符。本节中我们来看看其他以反斜杠\开头的特殊字符。

特殊字符 \d:匹配数字

\d 是一个特殊字符,它匹配任何数字,即从 0 到 9 的任意一个值。

代码示例\d

请注意,它不匹配负数前的减号,也不匹配数字前的其他字符,但它会匹配所有数字。

特殊字符 \w:匹配字母数字字符

\w 是另一个特殊字符,它匹配字母数字字符。这意味着它匹配所有小写字母 a 到 z、大写字母 A 到 Z、数字 0 到 9 以及下划线 _

代码示例\w

例如,它会匹配大写字母 Z 和下划线,但不会匹配像 & 这样的符号。

特殊字符 \s:匹配空白字符

\s 用于匹配空白字符,例如空格、制表符等。

代码示例\s

它会匹配字符串中单词之间的空格,但不会匹配其他字符。

对应的大写字符:取反匹配

此外,这些特殊字符还有对应的大写版本,它们的功能是相反的。

以下是这些大写版本字符的说明:

  • \D:匹配任何非数字字符。
  • \W:匹配任何非字母数字字符(即不属于 \w 范围的字符)。例如,它会匹配 & 符号。
  • \S:匹配任何非空白字符。

本节课中我们一起学习了正则表达式的几组核心特殊字符:用于匹配数字的 \d、匹配字母数字的 \w、匹配空白字符的 \s,以及它们对应的取反版本 \D\W\S。掌握这些字符能帮助你更精确地定义文本匹配模式。

39:转义字符技巧

在本节课中,我们将要学习正则表达式中一个重要的概念:转义字符。当我们需要匹配正则表达式中的特殊字符本身时,就需要用到这个技巧。

上一节我们介绍了正则表达式中的特殊字符,如点号.和问号?。本节中我们来看看,如果我们想要匹配这些特殊字符本身,而不是它们所代表的特殊含义,应该怎么做。

让我们来看一个具体的情况。假设我们有一些数字,它们之间可能由空格、点号或短横线分隔。如果我们使用正则表达式 \d+.\d+.\d+ 进行匹配,其中 \d+ 表示一个或多个数字,. 表示“任何单个字符”,那么我们会匹配到所有情况:包含空格的、包含点号的、包含短横线的都会被匹配。

如果我们只想匹配那些真正由点号分隔的数字序列呢?这时,我们就需要“转义”点号这个特殊字符。当我们想将一个特殊字符当作普通字符本身来处理时,就在它前面加上一个反斜杠 \

以下是应用转义后的正则表达式:

\d+\.\d+\.\d+

在这个表达式中,\. 不再代表“任何字符”,而是代表一个字面意义上的点号字符。因此,现在只有像 123.456.789 这样的字符串才会被匹配,而 123 456 789123-456-789 则不会被匹配。

所以,这就是使用反斜杠进行转义的方法,它允许我们将正则表达式中的特殊字符当作普通字符来处理。

本节课中我们一起学习了转义字符的用法。核心要点是:当需要在正则表达式中匹配特殊字符(如 .?* 等)本身时,必须在其前面加上反斜杠 \ 进行转义,例如 \. 表示匹配一个点号。这个技巧能让我们更精确地控制匹配模式。

40:贪婪与非贪婪匹配 🧐

在本节课中,我们将要学习正则表达式中一个非常重要的概念:贪婪匹配非贪婪匹配。理解这两种匹配模式的区别,能帮助我们更精确地从文本中提取所需内容。


默认行为:贪婪匹配

首先,我们需要了解正则表达式的默认匹配行为是贪婪的。这意味着,当使用像 +* 这样的量词时,正则表达式引擎会尝试匹配尽可能多的字符。

让我们通过一个具体的代码示例来理解这一点。

import re

# 示例字符串,其中包含两个冒号
text = "From: user@example.com: This is a sample text"

在上面的字符串中,我们有两个冒号。现在,我们使用正则表达式 F.+: 进行匹配。这个模式的意思是:寻找字母 F,然后匹配一个或多个任意字符. 表示任意字符,+ 表示一个或多个),最后匹配一个冒号 :

由于默认是贪婪匹配,.+ 会尽可能多地匹配字符,直到字符串的末尾,然后回溯以满足最后一个冒号 : 的条件。因此,它会匹配到第二个冒号为止。

# 贪婪匹配示例
pattern_greedy = r'F.+:'
result_greedy = re.findall(pattern_greedy, text)
print(result_greedy)  # 输出: ['From: user@example.com:']

如你所见,匹配结果是从第一个 F 开始,一直到字符串中的第二个冒号结束。这就是贪婪匹配的效果。


如何实现非贪婪匹配

上一节我们介绍了贪婪匹配的默认行为,本节中我们来看看如何改变这种行为,实现非贪婪匹配(也称为“懒惰匹配”)。

非贪婪匹配的目的是让量词匹配尽可能少的字符。实现方法非常简单:只需在量词(如 +*?{m,n})后面加上一个问号 ?

以下是修改后的非贪婪匹配模式:

# 非贪婪匹配示例
pattern_non_greedy = r'F.+?:'
result_non_greedy = re.findall(pattern_non_greedy, text)
print(result_non_greedy)  # 输出: ['From:']

在这个模式 F.+?: 中,+? 组合表示“匹配一个或多个任意字符,但尽可能少地匹配”。因此,引擎在找到第一个满足条件的冒号 : 后就会停止,最终只匹配到 "From:"


核心要点总结

以下是贪婪与非贪婪匹配的核心区别和用法:

  • 贪婪匹配是默认行为。量词会尝试匹配尽可能长的字符串。
    • 公式表示:量词(如 +, *
  • 非贪婪匹配需要显式指定。在量词后添加 ? 使其匹配尽可能短的字符串。
    • 公式表示:量词?(如 +?, *?
  • 选择哪种模式取决于你的需求:如果你想获取一个大块文本中最后一个分隔符之前的所有内容,使用贪婪匹配;如果你想获取第一个分隔符之前的内容,则使用非贪婪匹配。


本节课中我们一起学习了正则表达式中贪婪与非贪婪匹配的关键区别。记住,默认是贪婪的,当你需要更精确、更小范围的匹配时,别忘了在量词后面加上 ? 来启用非贪婪模式。通过灵活运用这两种模式,你可以更有效地控制和提取文本数据。

41:正则表达式中括号的使用问题 🧩

在本节课程中,我们将探讨在正则表达式中使用逻辑“或”运算符 | 时,括号 () 可能带来的一个常见问题。我们将学习为什么括号有时会改变匹配结果,以及如何通过两种不同的方法来解决这个问题。


上一节我们介绍了正则表达式的基本匹配。本节中我们来看看当我们在逻辑“或”运算中使用括号时会发生什么。

当我们使用逻辑“或” | 时,我们经常将左右两侧的条件用括号括起来。但这样做会发生什么?

观察以下正则表达式:它试图匹配“1-9”或者“10-12”,后面跟着一个斜杠 /,然后是两位数的日期 \d\d,再一个斜杠,最后是四位数的年份 \d\d\d\d

pattern = r‘(1[0-2]|[1-9])/\d\d/\d\d\d\d‘

如果我们运行这个模式,会发现返回的结果仅仅是括号内的内容,例如 ‘10‘‘9‘,而不是完整的日期。这是因为在正则表达式中,括号 () 默认表示一个捕获组。引擎看到括号,就认为你只想提取括号内的部分。


为了解决这个问题,我们有两种主要方法。

方法一:将整个表达式包裹在括号中
我们可以给整个表达式外加一层括号,使其成为一个大的捕获组。

pattern = r‘((1[0-2]|[1-9])/\d\d/\d\d\d\d)‘

这样做会使整个日期成为一个匹配组。但此时,我们得到的结果会是一个元组列表。在每个元组中,第一个元素是整个匹配项,第二个元素是内层括号(原始括号)的匹配项。如果你有更多层括号,就会得到更多元素。

以下是处理这种结果的示例:

import re
matches = re.findall(pattern, text)
for match in matches:
    full_date = match[0] # 提取整个日期
    print(full_date)

方法二:使用非捕获组
另一种更优雅的解决方法是告诉正则表达式引擎:这些括号仅用于分组逻辑,而不是为了捕获匹配内容。我们可以使用 (?: ... ) 语法来创建非捕获组

pattern = r‘(?:1[0-2]|[1-9])/\d\d/\d\d\d\d‘

通过这种方式,我们得到了完整的日期匹配,而不会意外地只捕获月份部分。引擎会忽略 (?: ) 内的内容作为一个独立的结果返回。


本节课中我们一起学习了正则表达式中括号的双重角色:逻辑分组和捕获组。我们看到了默认的捕获行为如何导致意外的匹配结果,并掌握了两种应对策略:一是接受嵌套捕获组并处理其结果结构,二是使用非捕获组 (?: ) 来纯粹进行逻辑分组。理解这一区别对于编写准确、高效的正则表达式至关重要。

42:锚点应用解析 🔍

在本节课中,我们将学习正则表达式中的“锚点”或“边界”字符。这些特殊字符允许我们精确地指定匹配模式在字符串中出现的位置,例如只在开头、结尾,或者作为独立的单词出现。

上一节我们介绍了正则表达式的基本模式匹配,本节中我们来看看如何控制匹配发生的位置。


匹配字符串开头:^ 符号

^ 符号(脱字符)用于确保匹配只发生在字符串的开头。

观察以下字符串和模式:

  • 模式:r'^FROM: [A-Za-z0-9_]+'
  • 目标字符串包含多个“FROM:”,但只有位于字符串开头的那个会被匹配。
import re
text = "FROM: Sender FROM: AnotherSender"
pattern = r'^FROM: [A-Za-z0-9_]+'
matches = re.findall(pattern, text)
print(matches)  # 输出: ['FROM: Sender']

如果去掉 ^,模式 r'FROM: [A-Za-z0-9_]+' 则会匹配字符串中所有出现的“FROM:”。

因此,当你只想匹配出现在字符串开头的模式时,请使用 ^


匹配字符串结尾:$ 符号

$ 符号用于确保匹配只发生在字符串的结尾。

观察以下示例:

  • 模式:r'\d+\.\d{2}$'
  • 此模式匹配一个或多个数字,后跟一个点,再跟两位数字,并且整个序列必须位于字符串的末尾。
text = "Price 12.99 and 45.50"
pattern = r'\d+\.\d{2}$'
matches = re.findall(pattern, text)
print(matches)  # 输出: ['45.50']

如果去掉 $,模式 r'\d+\.\d{2}' 则会匹配字符串中所有符合该格式的数字序列。

注意:由于 $ 本身是特殊字符,如果你想匹配字面意义上的美元符号,需要使用反斜杠进行转义:\$


匹配单词边界:\b 符号

有时我们需要匹配一个完整的单词,而不是它作为其他单词一部分的情况。使用空格来界定并不总是可靠,因为单词可能以标点结尾。

这时可以使用单词边界锚点 \b。它匹配单词字符(字母、数字、下划线)和非单词字符之间的位置。

观察以下示例:

  • 模式:r'\bin\b'
  • 此模式匹配独立的单词“in”,而不会匹配“inside”或“pin”中的“in”。
text = "in the beginning. inside the box. pin it."
pattern = r'\bin\b'
matches = re.findall(pattern, text)
print(matches)  # 输出: ['in']

注意:由于 \b 在普通字符串中代表退格键,在定义正则表达式模式时,应使用原始字符串(r'...')来避免转义问题。

以下是使用这些锚点字符的要点总结:

  • ^: 匹配字符串的开头。
  • $: 匹配字符串的结尾。
  • \b: 匹配单词的边界。


本节课中我们一起学习了正则表达式的三个核心锚点字符:^$\b。它们通过控制匹配发生的位置,极大地增强了模式匹配的精确性。掌握这些锚点,能帮助你更有效地进行文本搜索和数据提取。

43:否定字符范围 🚫

在本节课中,我们将学习如何在正则表达式中使用否定字符范围。这是一种强大的技巧,用于匹配“除了指定字符之外”的任何字符。

上一节我们介绍了如何使用方括号 [] 来定义字符范围。本节中我们来看看如何否定一个字符范围,即匹配所有不在该范围内的字符。

核心概念:否定字符范围

否定字符范围通过在方括号内的开头添加一个脱字符 ^ 来实现。其基本语法如下:

[^...]

这个模式会匹配任何出现在 ... 所定义字符集合中的单个字符。

应用实例:密码验证

一个常见的应用场景是验证密码的合法性。例如,假设我们要求密码只能包含以下字符:

  • 小写字母 a-z
  • 大写字母 A-Z
  • 数字 0-9

我们的目标是检查密码中是否含有除此之外的任何字符(如特殊符号 @#$ 或空格等)。

以下是实现此逻辑的思路:

  1. 使用否定字符范围 [^a-zA-Z0-9] 来搜索任何“非字母数字”的字符。
  2. 如果在字符串中找到了这样的字符,说明密码非法。
  3. 如果没找到,则密码合法。

用伪代码可以表示为:

if re.search('[^a-zA-Z0-9]', password):
    return False  # 密码包含非法字符
else:
    return True   # 密码合法

语法要点总结

以下是关于否定字符范围的关键点:

  • 脱字符 ^ 必须紧跟在左方括号 [ 之后,即 [^
  • 它否定的是整个方括号内定义的字符集合。
  • 它仍然只匹配一个字符位置,除非与量词(如 *+)结合使用。

本节课中我们一起学习了正则表达式中否定字符范围 [^...] 的用法。通过这个技巧,我们可以方便地检查字符串中是否包含不被允许的字符,这在数据验证和清洗任务中非常实用。记住,^ 在方括号内开头位置时,功能是“取反”,这与它在方括号外表示“字符串开头”的含义完全不同。

44:电话号码匹配实践 📞

在本节课中,我们将学习如何使用正则表达式来匹配电话号码。电话号码的格式通常包含数字和分隔符,我们将逐步构建一个能够灵活匹配多种常见格式的正则表达式。

电话号码的基本模式

一个典型的美国电话号码格式包含三组数字:前三位、中间三位和最后四位,通常由连字符或空格分隔。例如:123-456-7890

上一节我们介绍了正则表达式的基本元字符,本节中我们来看看如何将它们组合起来匹配具体的模式。

构建匹配模式

以下是构建电话号码匹配模式的核心步骤:

  1. 匹配数字:使用 \d[0-9] 来匹配单个数字。
  2. 指定数量:使用花括号 {n} 来指定需要匹配的数字个数。
  3. 匹配分隔符:电话号码中的分隔符通常是连字符 - 或空格。

根据以上概念,一个匹配 123-456-7890 格式的初始正则表达式可以写成:

\d{3}-\d{3}-\d{4}

或者

[0-9]{3}-[0-9]{3}-[0-9]{4}

这个模式会精确匹配上述格式,如果数字位数过多或过少,或者缺少连字符,则无法匹配。

处理可选的分隔符

然而,电话号码并不总是使用连字符。有时人们会用空格分隔,或者混合使用连字符和空格。因此,我们需要让分隔符变得可选。

我们可以使用方括号 [] 来定义一个字符集,匹配其中任意一个字符。为了匹配一个连字符或一个空格,模式可以写成 [- ]。需要注意的是,在方括号内,连字符 - 通常表示一个范围(如 [a-z]),因此为了将其作为普通字符处理,有时需要对其进行转义,写成 [\- ]

以下是更新后的模式,允许分隔符是连字符或空格:

\d{3}[- ]\d{3}[- ]\d{4}

现在,这个模式可以匹配 123-456-7890123 456 7890,甚至是 123-456 7890 这样的混合格式。

处理可选的括号

有时,电话号码的前三位数字会被括号括起来,例如 (123) 456-7890。但括号并不是必须的。

在正则表达式中,圆括号 () 有特殊含义,它们用于定义捕获组。为了匹配字面意义上的括号,我们需要使用反斜杠进行转义:\(\)

为了让括号成为可选项,我们可以在其后面加上问号 ?,这表示前面的元素出现零次或一次。

以下是最终的模式,它允许电话号码的开头有可选的括号:

\(?\d{3}\)?[- ]\d{3}[- ]\d{4}

这个模式现在可以成功匹配多种格式:

  • 123-456-7890
  • 123 456 7890
  • (123) 456-7890
  • (123)456-7890

实践建议

在构建复杂的正则表达式时,建议从一个简单的、能匹配最小核心模式的表达式开始。然后,逐步添加功能,并频繁使用像 RegEx 101 这样的在线工具进行测试,确保每一步的修改都按预期工作。

本节课中我们一起学习了如何构建一个灵活的电话号码匹配正则表达式。我们从匹配固定格式开始,逐步引入了对可选分隔符(连字符或空格)和可选括号的支持。关键点在于理解如何使用字符集 [] 匹配多个选项,以及如何使用转义字符 \ 和量词 ? 来处理具有特殊含义的字符和可选元素。通过这种渐进式的构建和测试方法,你可以创建出强大且适应性强的模式来匹配各种文本数据。

0:遇见Barb Ericson博士 👩‍🏫

在本节课中,我们将通过Barb Ericson博士的自我介绍,了解她的学术与职业背景,以及她为计算机科学教育所做的贡献。

个人背景介绍

Barb Ericson博士是密歇根大学信息学院的助理教授。她担任此教职已有三年,但其在计算机科学领域的经验远不止于此。

教育与早期职业选择

她的高中职业规划曾是在兽医和律师之间选择。她热爱动物,同时也在高中参加过辩论队。后来,她获得了底特律市中心的韦恩州立大学的奖学金。由于该校没有兽医学院,她决定主修政治学,并考虑将来就读法学院。为了增加选择,她决定攻读双学位。

以下是她在高中时期接触编程的经历:

  • 她在高中时曾学习过一门使用BASIC语言的编程课程,当时使用的是电传打字机。
  • 她觉得编程很有趣,因此决定将计算机科学作为她的第二专业。

最终,她发现自己更喜欢计算机科学。通过几次实习,她确认了自己对此领域的热情,并决定坚持走下去。她于1983年获得了韦恩州立大学的理学学士学位。

职业生涯发展

上一节我们介绍了Barb Ericson博士的教育背景,本节中我们来看看她丰富的职业生涯。

她的第一份工作是在通用汽车研究实验室。起初她在计算机机房工作,后来负责支持使用3D图形软件包的人员。

之后,她转到贝尔通信研究所,从事了大量数据库相关工作。公司资助她在密歇根大学攻读硕士学位。在此期间,她做了许多关于双音多频信号输入和语音合成输出的工作,旨在帮助电话维修人员。她曾跟随维修人员实地考察,以了解他们如何使用她开发的软件。

接着,她回到通用汽车研究实验室,开发了帮助设计师从油泥模型中创建3D数学模型的软件。汽车设计师会先用油泥雕塑出汽车模型。她在此主要从事用户界面设计和3D图形方面的工作。

随后,她先后在造纸科学技术研究所和克拉克亚特兰大大学工作。在克拉克亚特兰大大学,她参与了一个有趣的项目,旨在利用人工智能帮助医生在X光片中检测肿块等异常。

这项工作又引领她与NCR公司合作,研究基于案例的推理技术,应用于中风和脑肿瘤的诊断,试图用人工智能辅助医生进行诊断。

后来,她在佐治亚理工学院工作了很长时间,担任讲师和计算推广主任,并最终在那里获得了博士学位。

教育推广与贡献

在获得博士学位前后,Barb Ericson博士投入了大量精力进行教育推广工作。

她为教师举办了大量研讨会,帮助他们学习如何教授计算机科学。她还为学生组织了许多夏令营,并协助修订了佐治亚州的课程。

其中,她最为人所知的工作之一是与她的丈夫Mark Guzdial博士合作编写关于“媒体计算”的书籍。这是一种通过编写程序来操作媒体(如图片中的像素、声音中的样本、视频中的帧)来教授计算的方法。

作为推广人员,她还与学生,特别是低龄学生,开展了许多活动。例如,他们举办了计算夏令营、与青年服务组织合作的研讨会、小学周末项目等。此外,她还领导了一个名为“Rise Up for CS”或“Sisters Rise Up for CS”的项目,旨在帮助来自代表性不足群体的学生在高中AP计算机科学课程中取得成功。

近期研究方向与教学理念

获得博士学位后,她近期专注于交互式电子书的研究与开发,本课程中你们将会使用到这些电子书。

这些电子书基于教育心理学的原则设计。核心理念是:通过“示例+练习”的模式可以更好地学习。因此,她的电子书包含了交互式分步示例和带有子目标标签的练习。子目标标签是一种试图将代码抽象到更高层次的注释。

书中提供了大量不同类型的练习题,并能即时给出反馈,这对学习很有帮助。她特别研究的一类问题是“代码排序题”,即给出解决一个问题的正确代码块,但顺序被打乱,学习者需要将其拖拽到正确的顺序。

她已创建了多本交互式电子书,你们将在本课程中使用其中一本。

总结

本节课中我们一起学习了Barb Ericson博士从学生到教育者的历程,了解了她在工业界的丰富实践、对计算机科学教育的热情投入,以及她基于教育心理学原理开发交互式电子书的教学创新。她的经历展示了计算机科学应用的广泛性和教育工作的多样性。

1:欢迎来到本课程 🎉

在本课程中,我们将学习如何从网络获取数据、处理数据、将其存储到数据库,并最终通过图表来分析和理解数据。

课程概述

我们将从网络数据的基础开始。网络页面主要由HTML构成,这是一种用于创建网页的标准标记语言。您将学习HTML的基本结构、常见标签以及它们之间的关系。

接下来,我们将介绍如何使用Beautiful Soup库来解析HTML页面,从而精确地提取您需要的信息。此外,我们还将处理来自文件和API的JSON数据,这是一种轻量级的数据交换格式。

掌握了数据获取与解析后,我们将进入数据库部分。您将学习关键的数据库概念,包括如何创建规范化的数据表,以及如何向表中插入数据和从表中查询数据。

最后,我们将利用处理好的数据,使用图表库(如Matplotlib)来创建可视化图表,帮助我们更直观地理解数据背后的模式和趋势。

以下是本课程将涵盖的核心主题列表:

  • HTML基础:学习网页的构成元素——HTML(超文本标记语言),了解其常见标签和结构关系。
  • 数据解析:使用Beautiful Soup解析HTML页面,精准提取目标数据。
  • JSON处理:学习如何处理来自文件和API的JSON格式数据。
  • 数据库操作:掌握核心数据库概念,学习创建规范化表、插入及查询数据。
  • 数据可视化:使用图表库(如matplotlib)基于数据创建图表,进行数据分析。

总结

本节课我们一起了解了本课程的整体框架和学习目标。我们从网络数据的源头HTML讲起,到数据的解析、存储,最终落脚于数据的可视化呈现。在接下来的章节中,我们将逐一深入这些主题,带领您从零开始,逐步掌握用Python进行创造性数据处理的全套技能。

2:本课程的独特之处 🎨

在本节课中,我们将了解这门课程与其他在线课程的不同之处,以及它如何通过实践、协作和创意项目来帮助你学习编程。

欢迎来到这门课程。它将与许多在线课程不同,不会包含大量讲座,而是更侧重于动手实践学习。

我们将鼓励你与他人合作,进行小组学习,因为这能提升你的学习效果和动力。人类是社会性生物,我们可以通过观察他人和与他人讨论来学习。电子书中包含大量互动练习,并且如果你需要复习,可以随时返回前面的章节或完成更多练习题。其中一些练习题是自适应的,这意味着它们会帮助你真正找到正确的解决方案。

创意与实践作业 ✨

上一节我们介绍了课程的实践与协作特色,本节中我们来看看具体的作业形式。

除了常见的编程入门练习,这门课程还包含开放式编辑和创意作业。常见的入门编程练习通常是计算销售税或将摄氏温度转换为华氏温度。而在这门课程中,你将绘制艺术作品或创作个性化的故事。希望这能让你感觉更有趣,并激发你的学习动力。

以下是课程作业的几个核心特点:

  • 实践导向:课程强调动手操作,而非单纯听讲。
  • 协作学习:鼓励小组合作,通过观察和讨论共同进步。
  • 自适应练习:练习题能引导你找到正确答案,巩固学习。
  • 创意项目:作业内容围绕艺术创作和故事叙述,更具趣味性和激励性。

本节课中我们一起学习了这门课程的独特教学理念。它通过强调动手实践、鼓励小组协作、提供自适应练习以及设计富有创意的项目,旨在为你提供一个更引人入胜且高效的学习体验。

3: Python是什么 🐍

在本节课中,我们将要学习Python编程语言的基本概念,了解它的起源、特点以及为什么它在当今如此流行。

大家好,我是Barbar Erickson。我是信息学院的助理教授。我很高兴能带领大家开启学习Python的旅程。

那么,Python到底是什么?它是一条巨蟒吗?不,Python是以《蒙提·派森的飞行马戏团》命名的。这是一部20世纪70年代的BBC喜剧系列。如果你没看过,可以去看看。

Python是一门非常流行的编程语言,许多公司都在使用它。它之所以流行,部分原因在于它非常强大。有大量的库可以让编程变得更简单,它被广泛应用于数据科学和机器学习领域。

与许多其他文本编程语言相比,Python也相对更容易学习。它还支持面向对象编程,这是当今人们开发程序最常用的方式。这意味着它支持“类”(即事物的分类)和实际执行程序工作的“对象”。

上一节我们了解了Python的命名由来和基本定位,本节中我们来看看它的核心特性。

以下是Python的几个关键特点:

  • 强大且功能丰富:拥有大量库,简化开发。
  • 应用领域广泛:尤其在数据科学和机器学习中占据重要地位。
  • 易于学习:语法相对简洁明了。
  • 支持面向对象编程(OOP):这是现代软件开发的主流范式。

面向对象编程的核心概念可以用以下方式描述:

  • 类(Class):是创建对象的蓝图或模板,定义了对象的属性和方法。例如,可以定义一个“汽车”类。
    class Car:
        def __init__(self, brand, color):
            self.brand = brand  # 属性
            self.color = color  # 属性
    
  • 对象(Object):是类的实例,是根据类创建出来的具体实体。例如,根据“汽车”类创建一辆红色的特斯拉。
    my_car = Car("Tesla", "red")  # my_car 是一个对象
    

所以,让我们开始吧。

本节课中我们一起学习了Python的起源、它作为一门流行编程语言的特点,以及其核心的面向对象编程概念。Python因其强大、易学和广泛的应用而成为入门和进阶开发的优秀选择。

4:为何分组协作

在本节课中,我们将探讨在编程学习过程中进行分组协作的重要性及其背后的原因。


上一节我们介绍了课程的整体结构,本节中我们来看看为何分组协作是课程设计的关键部分。

大量研究表明,分组协作既能提升学习动力,也能改善学习效果。部分原因在于,向他人解释自己的想法这一过程,本身就能帮助你更好地理解概念。

通常,与他人协作也更有趣,并能帮助你避免陷入困境,尤其是在编程时。总体而言,研究表明,多样化的团队表现优于个人。团队往往能解决个人无法独立解决的问题。例如,在密室逃脱游戏中,你可能需要借助他人的帮助才能成功逃脱,而仅凭自己则难以完成。

同样重要的是,团队协作能力、沟通能力、自我管理及管理他人的能力,对雇主而言至关重要。因此,掌握这类技能在工作场所也非常有用。

以下是分组协作的主要益处:

  • 提升理解:通过向他人解释,深化自身对知识的掌握。
  • 增加动力与乐趣:协作过程通常更具趣味性,能维持学习热情。
  • 突破瓶颈:在遇到困难时,团队成员可以提供不同视角和帮助。
  • 解决复杂问题:团队能汇集多样化的技能与想法,解决个人难以应对的挑战。
  • 培养职场技能:锻炼沟通、协作与管理能力,这些是未来职业发展的重要基础。

本节课中我们一起学习了分组协作在编程学习中的多重价值。从提升个人理解到培养关键职场技能,团队合作是高效学习和成功解决问题的重要途径。

5:POGIL角色解析 🧩

在本节课中,我们将学习一种名为POGIL的协作学习方法,并详细解析其团队中的四个核心角色。理解这些角色有助于在小组编程项目中更高效、更有组织地工作。


什么是POGIL?🤔

上一节我们介绍了小组协作的重要性,本节中我们来看看POGIL的具体含义。POGIL代表过程导向的引导式探究学习。在这种模式下,小组成员共同工作,但每位成员都承担一个被明确定义的角色。

POGIL的四个核心角色 👥

以下是POGIL活动中通常包含的四个角色及其职责。

  1. 管理者

    • 为组内成员分配任务。
    • 确保整个团队按计划、按时完成任务。
    • 在团队遇到困难时,鼓励大家继续前进或调整方向。
  2. 记录员

    • 负责记录讨论中的重要观点。
    • 写下团队所学到的知识或达成的结论。
  3. 汇报者

    • 当对任务说明有不清楚之处时,负责向指导者提问。
    • 负责向全班或指导者展示团队的工作成果。
  4. 反思者

    • 确保团队中每位成员的意见都被倾听,避免只有一个人主导讨论。
    • 观察并反馈团队的协作动态,评估团队合作是否良好,并提出可以改进的地方。


本节课中,我们一起学习了POGIL协作学习法的四个关键角色:管理者记录员汇报者反思者。每个角色都有其独特的职责,共同保障团队高效、公平地运作。在未来的小组编程项目中,尝试分配这些角色,体验结构化的团队协作。

6:电子书中编写代码的方法 📖

在本节课中,我们将学习如何在电子书中直接编写、测试和调试Python代码。这是一种便捷的学习方式,允许你即时实践并查看结果。


概述

电子书的一个显著特点是允许你直接在书中编写代码。本节将演示如何操作,包括编写函数、运行测试、调试代码以及使用历史记录功能。


在电子书中编写代码

上一节我们介绍了电子书的基本界面,本节中我们来看看如何具体编写代码。

电子书允许你直接在页面内编写Python代码。例如,创建一个计算矩形面积的函数。

def area_of_rectangle(width, length):
    area = width * length
    return area

以上代码定义了一个函数 area_of_rectangle,它接收宽度和长度两个参数,计算并返回矩形的面积。例如,调用 area_of_rectangle(5, 4) 应返回 20


运行与测试代码

编写代码后,你可以直接运行它来测试功能。

初次尝试时,你可能会忘记返回计算结果。例如,只写了计算语句 area = width * length 而缺少 return area。运行测试用例(如 area_of_rectangle(5, 4))会提示错误,帮助你发现遗漏。

修正后再次运行,测试用例将通过。你可以在“代码透镜”中查看运行结果,也可以将代码分享给团队中的其他成员。


使用代码编辑窗口

如果你对某些语法或操作不确定,可以使用额外的编辑窗口进行尝试。

在电子书右上角有一个铅笔图标。点击它会弹出一个独立的Python代码编辑窗口。你可以在此编写临时代码进行测试,例如:

print(5 * 4)

执行后,窗口会显示结果 20。测试完成后,可以关闭该窗口。这个功能非常适合快速验证想法或调试代码片段。


查看代码历史记录

另一个有用的功能是代码历史记录,它允许你回顾代码的修改过程。

请确保你已登录账户。登录后,你会看到一个历史滑块控件。通过拖动滑块,你可以回溯查看代码的所有历史版本:

以下是你可以查看的内容:

  • 代码最初的样子。
  • 第二次修改后的状态。
  • 后续各次修改的记录。

这让你能够清晰地追踪自己的编程思路演变过程,或在需要时恢复到之前的某个版本。


总结

本节课中我们一起学习了在电子书中编写代码的完整流程。我们实践了如何直接编写函数、运行测试用例来验证代码正确性,并利用编辑窗口进行快速测试。我们还了解了查看代码历史记录的功能,这有助于跟踪修改和调试。掌握这些方法能让你的学习过程更加高效和直观。

7:重载代码历史记录 🔄

在本节课中,我们将学习如何在编程环境中找回之前保存的代码。如果你离开后又返回,可能会发现代码编辑器是空的,但只要你之前登录过,你的代码其实已经被保存了。本节将指导你如何通过“加载历史记录”功能恢复你的工作。


如果你在书中进行编程练习,中途离开后又返回,可能会惊讶地发现编辑器里似乎没有保存你的代码。但请放心,只要你上次操作时处于登录状态,你的代码就已经被保存了。

接下来,我们来看看如何找回这些已保存的代码。

以下是恢复代码的具体步骤:

  1. 返回编程练习界面。
  2. 注意并找到“加载历史记录”按钮。
  3. 点击该按钮。

如果你上次运行代码时是登录状态,系统将加载你所有的历史记录。这样,你就能看到上次运行时所写的全部代码。

你甚至可以查看更早的版本来回顾之前的操作。因此,无需惊慌,只要登录后所做的所有操作都会被保存。

最后,请务必记住点击“加载历史记录”按钮来恢复你的工作进度。


本节课中,我们一起学习了“加载历史记录”功能的重要性与操作方法。关键在于:只要保持登录,你的代码就会被自动保存;返回后,记得点击“加载历史记录”按钮来恢复之前的工作。这能有效防止意外丢失进度,让你能从容地继续编程练习。

8:欢迎进入第一周 🚀

在本节课中,我们将要学习网页开发的基础知识,包括超文本标记语言和层叠样式表。我们还将介绍如何使用Beautiful Soup库来处理HTML数据。

概述 📋

第一周的学习目标是掌握HTML和CSS的基础。HTML是构建网页结构的语言,而CSS则用于美化这些结构。我们将学习常见的HTML标签、它们的属性以及标签之间的关系。最后,我们将使用Beautiful Soup库来解析和处理HTML文档,为后续的数据收集工作打下基础。

HTML与CSS基础 🏗️

上一节我们概述了本周的学习内容,本节中我们来看看HTML和CSS的具体定义。

超文本标记语言是创建网页的标准标记语言。它使用一系列标签来描述网页的内容和结构。层叠样式表则是一种样式表语言,用于描述HTML文档的呈现方式,包括布局、颜色和字体等。

常见HTML标签 🏷️

了解了基本概念后,我们来认识一些构建网页时最常用的HTML标签。

以下是您将学习到的一些核心HTML标签:

  • <h1><h6>: 标题标签,用于定义不同级别的标题。
  • <head><body>: 文档的头部和主体部分。<head>包含元信息,<body>包含可见内容。
  • <title>: 定义浏览器工具栏或页面标签的标题。
  • <p>: 段落标签。
  • <ol><ul>: 分别用于创建有序列表和无序列表。
  • <span><div>: 用于对行内元素和块级元素进行分组和样式化。
  • <table>: 用于创建表格。

标签属性与关系 🔗

仅仅知道标签还不够,我们还需要了解如何通过属性来配置它们,以及标签之间是如何组织的。

每个HTML标签都可以拥有属性,这些属性提供了关于元素的额外信息。例如,<a>标签的href属性定义了链接的目标地址。

HTML标签之间存在层级关系,这种关系通常被描述为“树状结构”。

  • 父元素与子元素: 一个标签可以包含其他标签,被包含的标签称为子元素,包含它们的标签称为父元素。
  • 兄弟元素: 拥有相同父元素的多个子元素互为兄弟元素。

理解这些关系对于使用CSS选择器或Beautiful Soup定位元素至关重要。

使用CSS样式化 🎨

在构建好网页结构之后,下一步就是使用CSS来为其添加样式,使其更加美观。

CSS允许您为HTML元素设置样式规则。您可以通过标签名、类名或ID来选中元素,然后为其定义颜色、字体、边距等属性。例如,将段落文字设置为红色的CSS代码是:p { color: red; }

使用Beautiful Soup处理HTML 🥣

掌握了静态网页的构建与样式化后,我们将转向动态数据处理。本节将介绍如何使用Python的Beautiful Soup库来解析和提取HTML文档中的信息。

Beautiful Soup是一个Python库,能够从HTML或XML文件中提取数据。它提供了简单的方法来导航、搜索和修改解析树。

以下是使用Beautiful Soup时的两个核心步骤:

  1. 识别标签或类: 您需要确定要搜索的特定HTML标签或CSS类。
  2. 应用常见算法: 我们将展示一些常用的数据收集算法,例如遍历文档树、按条件过滤元素以及提取文本或属性值。

总结 🎯

本节课中我们一起学习了第一周的核心内容。我们介绍了HTML和CSS作为网页开发基础语言的角色,列举了常见的HTML标签及其属性和相互关系,解释了如何使用CSS为网页添加样式,并初步了解了使用Beautiful Soup库处理HTML文档以收集数据的方法。这些知识是进行创造性编程和网络数据操作的重要基石。

9:HTML入门 🚀

在本节课中,我们将要学习HTML的基础知识。HTML是构建网页的核心技术,它并非编程语言,而是一种用于描述网页内容和结构的标记语言。理解HTML是进行Web开发和创意编程的重要第一步。

什么是HTML?

上一节我们介绍了课程背景,本节中我们来看看HTML究竟是什么。

HTML代表超文本标记语言。它不是一种编程语言,而是一种用于创建网页的标记语言。HTML使用标签来指定文档的各个部分。

例如,你可以在这里看到一个简单的页面,它来自密歇根大学的在线日报《密歇根日报》。页面以一个文档类型声明开始,用以说明这是一个HTML文档。接着是<html>标签,然后有<head>标签和闭合的</head>标签,以及一个打开的<body>标签。最后是闭合的</body>标签和闭合的</html>标签。

因此,HTML通常由成对出现的开始和结束标签组成,这些标签描述了文档的内容。


HTML文档的基本结构

了解了HTML的定义后,我们来看看一个标准HTML文档的基本骨架。

一个最简单的HTML文档结构如下所示:

<!DOCTYPE html>
<html>
  <head>
    <!-- 头部信息,如标题、字符集、样式表链接等 -->
    <title>页面标题</title>
  </head>
  <body>
    <!-- 网页主体内容在这里显示 -->
    <h1>这是一个标题</h1>
    <p>这是一个段落。</p>
  </body>
</html>

以下是上述代码中关键部分的解释:

  • <!DOCTYPE html>:声明文档类型为HTML5,确保浏览器以标准模式渲染页面。
  • <html>:根元素,包裹整个HTML文档。
  • <head>:头部元素,包含不直接显示在页面上的元信息,如标题、字符集定义和对外部资源(CSS, JavaScript)的链接。
  • <title>:定义浏览器标签页上显示的页面标题。
  • <body>:主体元素,包含所有在网页上可见的内容,如文本、图片、链接等。

核心概念:标签与元素

上一节我们看到了HTML的骨架,本节中我们来深入理解其核心构件:标签和元素。

HTML通过标签来标记内容。大多数标签是成对出现的:一个开始标签和一个结束标签。它们共同定义了一个HTML元素

开始标签的格式是<tagname>,结束标签的格式是</tagname>。两者之间的内容就是该元素的内容。

例如,段落元素:
<p>这是一个段落。</p>

有些标签是自闭合的,不需要单独的结束标签,例如换行标签:
<br> 或图像标签 <img src="image.jpg" alt="描述">


总结

本节课中我们一起学习了HTML的基础知识。我们了解到HTML是一种用于构建网页结构的标记语言,其核心是通过成对的标签来定义文档中的各种元素。我们掌握了一个标准HTML文档的基本结构,包括<!DOCTYPE><html><head><body>等关键部分。理解这些概念是后续学习更复杂的网页样式(CSS)和行为(JavaScript)的坚实基础。

10:修复HTML代码 🛠️

在本节课中,我们将学习如何识别并修复一段存在错误的HTML代码。我们将遵循HTML的基本结构规则,确保每个开始标签都有对应的结束标签,并理解文档各部分(如头部和主体)的正确组织方式。

识别缺失的标签

上一节我们介绍了HTML的基本概念,本节中我们来看看如何修复一段结构不完整的HTML代码。首先,我们需要检查代码,找出所有缺失的开始或结束标签。

以下是修复步骤:

  1. 添加缺失的HTML开始标签:文档应以<html>标签开始,但示例中只有</html>结束标签。因此,我们需要在文档顶部添加<html>
  2. 修复头部(Header)区域:在<head>部分,有一个</title>结束标签,但没有对应的<title>开始标签。我们需要添加<title>开始标签。
  3. 修复主体(Body)内的标题:在主体部分,有一个</h1>结束标签,但没有对应的<h1>开始标签。我们需要添加<h1>开始标签。
  4. 确保头部正确闭合:在添加了<title>标签后,需要确保<head>部分被</head>标签正确闭合。
  5. 添加主体开始标签:所有显示在页面上的内容都应放在<body>标签内。因此,在</head>之后,我们需要添加<body>开始标签。

修复后的代码结构

遵循以上步骤修复后,完整的、结构正确的HTML代码应如下所示:

<!DOCTYPE html>
<html>
<head>
    <title>页面标题</title>
</head>
<body>
    <h1>这是一个标题</h1>
    <p>这是一个段落。</p>
</body>
</html>

核心规则总结

本节课中我们一起学习了修复HTML代码的关键点。请记住以下核心规则:

  • 对于每一个开始标签(如<tag>),都必须有一个对应的结束标签(如</tag>)。
  • 文档可以(也建议)以<!DOCTYPE html>声明开始。
  • 文档内容必须包含在<html></html>标签之内。
  • <html>标签内,通常包含<head><body>两部分。
  • <head>部分包含不直接显示在页面上的信息,例如标题(<title>)或脚本。
  • <body>部分包含所有会显示在网页上的内容,如标题、段落、图片等。

通过应用这些规则,我们就能将混乱的HTML代码修复为结构清晰、能被浏览器正确渲染的有效文档。

11:文档对象模型(DOM)解析

在本节课中,我们将要学习文档对象模型(DOM)的基本概念。DOM是HTML文档的结构化表示,理解它对于解析网页和提取信息至关重要。我们将探讨DOM如何将文档内容组织成树形结构,并了解其核心组成部分。

结构与表现的分离

在HTML中,一个核心思想是分离文档的结构、样式和交互行为。文档的结构由HTML标签定义,例如 <h1><p> 等。文档的外观则由层叠样式表(CSS)指定。而文档的交互行为,则通过JavaScript来实现。

什么是文档对象模型(DOM)?

文档对象模型(DOM)是一种树形结构。你可以将其想象成一棵倒置的树,树根在顶部,树枝向下延伸。

在HTML文档中,<html> 标签是这棵树的根元素。它有两个主要的子节点:<head><body>

  • <head> 包含了所有不会直接显示在页面上的内容,例如文档的标题(<title>)。
  • <body> 包含了所有将要显示的内容,即文档的主体结构。

以下是一个简单的HTML示例及其对应的DOM树:

<html>
  <head>
    <title>我的页面</title>
  </head>
  <body>
    <h1>标题</h1>
    <p>这是一个包含<a href="#">链接</a>的段落。</p>
  </body>
</html>

对应的DOM树结构可以这样理解:

  • 根节点:<html>
    • 子节点1:<head>
      • 子子节点:<title>(内容:“我的页面”)
    • 子节点2:<body>
      • 子子节点1:<h1>(内容:“标题”)
      • 子子节点2:<p>
        • 子子子节点:<a href="#">(内容:“链接”)

当这个页面在浏览器中显示时,你只会看到“标题”和“这是一个包含链接的段落。”这些文本内容。

解析HTML与DOM的应用

上一节我们介绍了DOM的树形结构,本节中我们来看看它的一个关键用途。当我们编写程序来自动化地从网页中查找和提取信息时,就会用到DOM解析。程序会将HTML文档加载并构建成DOM树,然后我们可以像在文件系统中导航文件夹一样,在这棵树上定位和访问特定的元素、属性或文本内容。

例如,我们可以编写代码来找到所有 <a> 标签并提取它们的 href 属性值,从而收集页面上的所有链接。

本节课中我们一起学习了文档对象模型(DOM)的基础知识。我们了解到DOM是HTML文档的树形表示,它清晰地展示了标签之间的嵌套关系。理解DOM是进行网页数据抓取和解析的第一步,为我们后续学习如何使用Python库来操作DOM打下了基础。

12:HTML属性详解 🏷️

在本节课中,我们将深入学习HTML标签的另一个重要组成部分——属性。我们将了解什么是属性,它们如何为标签提供额外信息,以及如何在代码中正确地使用它们。

上一节我们介绍了HTML标签的树状结构和关系。本节中我们来看看如何通过属性为这些标签增添更多细节和功能。

什么是属性? 🔍

标签可以拥有属性。属性为标签提供了所需的额外信息。属性总是在标签的开始部分进行指定。

例如,一个图像标签 <img> 没有结束标签,其所有信息都包含在开始标签中。一个图像标签通常有两个属性:src(来源)和 alt(替代文本)。它们以“属性名=值”的成对形式出现。

以下是属性的基本语法格式:

<tag_name attribute_name="attribute_value">

属性示例 📸

让我们通过两个常见的例子来具体理解属性的用法。

图像标签 (<img>)

图像标签使用 src 属性来指定图片的路径,使用 alt 属性来提供替代文本。替代文本对于使用屏幕阅读器的用户(例如有视力障碍的人)非常重要。

其代码结构如下:

<img src="image_path.jpg" alt="描述图片的文字">

在这个例子中,src 是属性名,"image_path.jpg" 是属性值;alt 是属性名,"描述图片的文字" 是属性值。

锚点/超链接标签 (<a>)

另一个例子是 <a> 标签,或称锚点标签、超链接标签。它有一个开始标签,其中包含 href 属性。href 的值是点击链接后将要跳转的网址。

在开始标签和结束标签之间,是实际显示给用户、可供点击的文本。

其代码结构如下:

<a href="https://example.com">点击这里的文字</a>

这里,href 是属性名,"https://example.com" 是属性值。href 代表“超文本引用”。

以下是关于属性的关键点总结:

  • 属性以名称/值对的形式出现,格式为 name="value"
  • 它们位于HTML标签的开始部分。
  • 它们为标签提供了核心功能所需的必要信息(如图片地址、链接目标)。

总结 📝

本节课中我们一起学习了HTML属性。我们了解到属性是为标签提供额外信息的名称/值对,始终位于开始标签内。我们通过 <img> 标签的 srcalt 属性,以及 <a> 标签的 href 属性,具体分析了属性的语法和作用。记住属性是构建功能丰富网页的基础。

13:CSS类应用 🎨

在本节课中,我们将学习CSS类的基本概念和应用。我们将了解如何将HTML文档的结构与其样式分离,并通过定义和应用CSS类来控制网页元素的外观。


让我们来看一个小组作业中的例子。

这里有一个HTML页面,其头部包含一些CSS。CSS代表层叠样式表,其核心理念是将文档结构(如什么是标题、什么是段落)与其外观样式分离开来。层叠样式表就是我们用来指定事物外观的方式。

那么,如何将两者(结构和样式)连接起来呢?我们可以使用不同的方法。一种方法是添加一组类。例如,我可以在段落标签上创建一个名为 warning 的类,并规定当看到这个类时,使用红色。同时,我还可以在段落标签上创建一个名为 large 的类,并规定当看到这个类时,使用特定的字体大小。

然后,我可以将它们组合使用。例如,我可以创建一个只应用了 warning 类的段落,也可以创建一个同时应用了 warninglarge 两个类的段落。这样,该段落就会同时应用两种样式。

这就是我们在这里看到的情况。如果我们使用只带有 warning 类的段落,我们会看到它是红色的。如果它同时带有 warninglarge 类,那么它既是红色的,字体也更大。

如果我再在这里添加一个段落,我可以添加一个只应用 larger 类的段落。请注意,属性值需要用引号括起来。

并且,我会记得闭合段落标签。大多数浏览器都比较宽容,即使你忘记闭合标签,它也能工作,但你真的应该包含所有闭合标签。

现在你可以看到,这个段落使用了 larger 样式,但仍然是黑色的,因为我并没有同时添加两个类。

所以,HTML的基础是:使用HTML来构建你的文档结构,使用CSS来指定该文档的外观样式。


本节课中,我们一起学习了CSS类的基本应用。我们了解到,通过定义不同的CSS类(如 .warning.large),并将其组合应用到HTML元素上,可以灵活地控制网页的视觉呈现。记住,良好的实践是将文档结构与样式分离,并使用正确的语法(如用引号包裹属性值、闭合所有标签)来编写代码。

14:Beautiful Soup入门 🍲

在本节课中,我们将学习如何使用Python库Beautiful Soup来处理HTML页面。我们将了解如何创建Beautiful Soup对象,以及如何从HTML文档中提取信息,例如标签、属性和文本内容。


什么是Beautiful Soup?

Beautiful Soup是一个Python库,它允许我们处理HTML或XML格式的文档。通过这个库,我们可以根据标签从HTML文档中获取信息,也可以查看标签的属性。

导入库与创建对象

要使用Beautiful Soup,首先需要导入该库。然后,我们可以从一个代表HTML页面的字符串创建一个Beautiful Soup对象。

以下是导入库和创建对象的代码示例:

from bs4 import BeautifulSoup

# 假设html_doc是一个包含HTML内容的字符串
soup = BeautifulSoup(html_doc, 'html.parser')

这段代码将解析HTML字符串,并返回一个结构化的soup对象,我们可以通过这个对象来查询文档内容。

解析HTML文档示例

为了更好地理解,我们来看一个来自Beautiful Soup教程的简单HTML页面示例。该页面结构如下:

<html>
<head>
    <title>The Dormouse's story</title>
</head>
<body>
    <p class="title"><b>The Dormouse's story</b></p>
    <p class="story">
        Once upon a time there were three little sisters; and their names were
        <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
        <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
        <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
        and they lived at the bottom of a well.
    </p>
</body>
</html>

从soup对象提取信息

创建soup对象后,我们可以开始查询其中的信息。以下是几种常见的查询方式:

获取标题

我们可以询问soup对象的标题。请注意,.title会返回包含开始和结束标签的完整元素。

print(soup.title)
# 输出: <title>The Dormouse's story</title>

如果只想获取标题的文本内容,可以使用.text属性。

print(soup.title.text)
# 输出: The Dormouse's story

获取第一个段落

我们可以获取文档中的第一个段落标签。

print(soup.p)
# 输出: <p class="title"><b>The Dormouse's story</b></p>

同样,也可以只获取该段落的文本。

print(soup.p.text)
# 输出: The Dormouse's story

获取第一个链接及其属性

我们可以获取文档中的第一个链接(<a>标签)。

print(soup.a)
# 输出: <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

要获取链接的特定属性(例如href),可以像使用字典一样使用.get()方法。

print(soup.a.get('href'))
# 输出: http://example.com/elsie

查找所有链接

如果我们想找到文档中的所有链接,可以使用.find_all()方法。

all_links = soup.find_all('a')
print(all_links)
# 输出: 一个包含所有<a>标签的列表

然后,我们可以遍历这个列表,获取每个链接的属性。

for link in all_links:
    print(link.get('href'))


本节课中,我们一起学习了Beautiful Soup库的基础知识。我们了解了如何导入库、创建Beautiful Soup对象,以及如何从HTML文档中提取标题、段落、链接和属性等信息。掌握这些基本操作是进行网页数据抓取和分析的第一步。

15:Beautiful Soup章节阅读指南 📖

在本节课中,我们将学习如何正确阅读《创造性编程》电子书中关于Beautiful Soup的章节。这一章的阅读方式与常规章节不同,我们将详细介绍其结构和使用方法。

章节结构概述

上一节我们介绍了网络爬虫的基础概念。本节中,我们来看看第15章“Beautiful Soup”的具体阅读方法。这一章的核心是教你如何使用Beautiful Soup库来抓取网页或HTML数据。

独特的阅读流程

本章的阅读方式与本书其他部分不同。它并非简单的线性阅读。

如果你从第14章“学习网络爬虫”进入本章,你会先看到关于网络爬虫的简要定义和本书工作方式的说明。

接着,你会点击一个链接,进入“抓取所有小屋和披萨店位置”的示例。这个示例展示了如何使用Beautiful Soup抓取网络数据或HTML数据,并提供了一个可运行的示例代码。

分步编程计划

本章内容随后会分解为多个不同的编程计划。

以下是阅读本章的正确步骤:

  1. 查看每个编程计划,每个计划都会引导你进入一个新页面。
  2. 在该页面末尾,你可以返回到“小屋和披萨店”示例。
  3. 然后,进入下一个计划,重复相同的过程:浏览页面、学习页面上的所有内容、完成所有练习。
  4. 完成后再返回主示例。

关键提醒:不要像阅读普通章节那样,仅仅使用“下一页”按钮来浏览本章。必须按照上述计划流程进行深入学习。

总结

本节课中,我们一起学习了《创造性编程》第15章关于Beautiful Soup的特殊阅读方法。重点是理解其非线性的结构,通过跟随不同的编程计划来深入实践和掌握使用Beautiful Soup进行网络数据抓取的技能。

16:findfind_all方法对比 🔍

在本节课中,我们将学习Beautiful Soup库中两个最常用的方法:findfind_all。我们将通过一个具体的HTML示例,清晰地展示它们的功能差异和使用场景。

概述

Beautiful Soup是一个用于解析HTML和XML文档的Python库。在处理网页数据时,我们经常需要定位和提取特定的HTML元素。findfind_all方法正是完成这项任务的核心工具。简单来说,find用于查找并返回第一个匹配的元素,而find_all则返回所有匹配的元素列表。

示例HTML文档

为了理解这两个方法,我们首先需要一个HTML文档作为示例。假设我们有以下HTML结构:

<html>
    <head>
        <title>示例页面</title>
    </head>
    <body>
        <p>第一个段落:老鼠的故事。</p>
        <p>第二个段落包含一个故事,其中有三个链接:<a href=“#link1”>链接一</a>,<a href=“#link2”>链接二</a>,和<a href=“#link3”>链接三</a>。</p>
    </body>
</html>

这个文档包含一个<body>,其中有两个段落(<p>标签)。第二个段落内包含了三个锚点(<a>)或链接元素。

使用 find 方法

find方法用于在文档中查找并返回第一个匹配指定条件的标签。

假设我们已经将上面的HTML字符串解析为一个Beautiful Soup对象,命名为soup。当我们执行以下代码时:

first_a_tag = soup.find(‘a’)
print(first_a_tag)

这段代码会搜索整个文档,找到第一个出现的<a>标签并将其返回。打印出的结果将是这个完整的标签,包括其开始标签、属性、文本内容以及结束标签。在我们的示例中,它会输出第一个链接“链接一”对应的整个<a href=“#link1”>链接一</a>标签。

核心概念find方法返回单个匹配的Tag对象。如果未找到任何匹配项,则返回None

使用 find_all 方法

find不同,find_all方法会查找文档中所有匹配指定条件的标签,并将它们作为一个列表返回。

继续使用同一个soup对象,执行以下代码:

all_a_tags = soup.find_all(‘a’)
print(all_a_tags)

这段代码会找到文档中所有的<a>标签。打印all_a_tags,你将得到一个Python列表,其中包含了三个Tag对象,分别对应示例中的三个链接。

核心概念find_all方法返回一个列表,包含所有匹配的Tag对象。如果没有找到任何匹配项,则返回一个空列表[]

方法对比总结

上一节我们分别介绍了findfind_all的独立用法,现在我们来对它们进行直接对比。

以下是两个方法的核心区别:

  • find(‘tag_name’):返回文档中第一个匹配的‘tag_name’标签。结果是单个对象。
  • find_all(‘tag_name’):返回文档中所有匹配的‘tag_name’标签。结果是一个对象列表。

简单记忆:当你只需要一个特定元素(如页面第一个链接)时,使用find;当你需要收集所有同类元素(如页面所有链接)时,使用find_all

总结

本节课中,我们一起学习了Beautiful Soup中两个至关重要的方法:findfind_all。我们通过一个包含多个链接的HTML示例,清晰地演示了find如何返回第一个匹配的标签,而find_all如何返回所有匹配标签的列表。掌握这两个方法的区别和适用场景,是使用Beautiful Soup高效提取网页数据的基础。在接下来的实践中,你可以灵活运用它们来定位和抓取所需的网页内容。

17:查看页面源码与审查元素 🔍

在本节课中,我们将学习如何在复杂的真实网页中定位特定元素。我们将介绍两种关键方法:查看页面源码和使用浏览器的“审查元素”功能。这些技能对于使用Beautiful Soup等工具进行网页抓取至关重要。

在真实HTML页面中定位元素

上一节我们介绍了基础的HTML结构。本节中我们来看看如何处理远比我们之前所见更复杂的实际网页。

如何在实际的HTML页面中找到特定的内容?真实的网页可能比我们目前所看的例子复杂得多。

查看页面源码

以下是查看页面源码的步骤:

  1. 在浏览器中打开目标网页。
  2. 在页面任意位置右键点击。
  3. 从上下文菜单中选择“查看页面源代码”。

你可以通过查看页面源码来了解网页的实际构成。例如,这是一个像《纽约时报》这样的页面,它比我们之前处理的简单网页要复杂得多。

如果我们想在其中找到特定的内容,直接查找可能会很困难。例如,我正在寻找“world”链接,而源码中有很多“world”出现,因此很难定位。

通过查看页面源码,我可以看到HTML标签,比如<h>标题标签。如果你能在HTML源码中找到目标内容,那么你就能使用Beautiful Soup来定位它。

动态网页的局限性

然而,也存在一些动态网页,其内容是由JavaScript脚本生成的。如果你在页面源码中找不到目标内容,那么你将无法使用Beautiful Soup来抓取它。

使用“审查元素”功能

除了查看整个页面的源码,我们还可以在页面上审查特定元素,这非常有用。

以下是使用“审查元素”功能的步骤:

  1. 在网页上找到你感兴趣的元素。
  2. 在该元素上右键点击(在Windows机器上,也可以使用Ctrl+右键点击)。
  3. 从菜单中选择“检查”或“审查元素”。

例如,我右键点击并检查该元素,它显示这是一个带有css-1类的<a>标签,并且有“world”部分。如果我想获取每个小标题的URL,我可以通过查找所有具有这个特定类的<a>标签来实现。

总结

本节课中我们一起学习了在复杂网页中定位元素的两种核心方法。首先,我们介绍了如何通过查看页面源码来获取网页的原始HTML结构。其次,我们探讨了如何使用浏览器的审查元素功能来精确定位页面上的特定部分及其属性(如class)。理解这些方法是有效使用Beautiful Soup等网页抓取工具的基础,同时我们也认识到对于由JavaScript动态生成内容的页面,这些静态分析方法存在局限性。

18:为何在查找时使用类属性

在本节课中,我们将探讨在使用BeautifulSoup库解析HTML时,为何需要使用class_(带下划线)来查找具有特定CSS类的元素,而不是直接使用class。我们将解释其中的原因,并说明这种查找方式如何帮助我们更精确地定位页面中的元素。

为何使用class_而非class

上一节我们介绍了如何使用BeautifulSoup查找元素。本节中我们来看看一个特殊的查找场景:通过CSS类名查找。

在BeautifulSoup中,当我们需要查找带有特定CSS类的HTML元素时,必须使用class_参数,而不是class。这是因为class是Python编程语言中的一个保留关键字,用于定义类。如果我们在代码中直接使用class作为参数名,会导致语法错误。

以下是正确的查找方式示例:

# 查找所有CSS类名为"example"的元素
soup.find_all(class_="example")

使用类属性查找的优势

使用CSS类名进行查找,尤其是在处理结构复杂的HTML页面时,具有显著优势。它可以帮助我们缩小查找范围,精确地定位到我们真正关心的元素。

以下是使用类属性查找的两个主要好处:

  1. 提高精确性:我们可能不希望获取页面中所有的<a>(锚)标签,而只想要那些具有特定样式或功能的链接。通过指定类名,我们可以过滤掉无关的元素。
  2. 简化选择逻辑:相比于编写复杂的层级选择器,直接使用类名通常更简洁、更易读。

与通过ID查找的对比

需要注意的是,通过ID查找元素则没有这个限制。因为id不是Python的保留字,所以我们可以直接使用id参数。

# 查找ID为"main-content"的元素
soup.find(id="main-content")

本节课中我们一起学习了在BeautifulSoup中使用class_参数查找CSS类元素的原因和方法。我们了解到,由于class是Python的保留关键字,因此必须使用带下划线的class_作为替代。同时,我们也认识到通过CSS类进行查找能够帮助我们更精确、更高效地从复杂的HTML文档中提取目标数据。

19:从URL创建Soup对象 🕸️

在本节课中,我们将学习如何直接从网络URL获取HTML数据,并使用BeautifulSoup库来解析它,而无需先将HTML内容保存到本地文件。

之前我们已经介绍了如何从字符串或本地文件中读取HTML并创建Soup对象。本节中,我们来看看如何直接从互联网上的URL获取数据并进行处理。

核心步骤

要从URL创建Soup对象,需要遵循以下步骤。以下是具体操作流程:

  1. 导入必要库:首先,需要导入requests库来获取网页内容,以及BeautifulSoup库来解析HTML。
  2. 发送网络请求:使用requests.get()函数,传入目标网页的URL地址,向服务器发送请求。
  3. 获取响应内容requests.get()会返回一个响应对象。可以通过检查响应对象的.status_code属性(例如,200表示成功,404表示未找到)来确认请求是否成功。此步骤是可选的,如果你确信URL有效,可以跳过。
  4. 解析HTML内容:从响应对象的.content属性中获取原始的HTML字节数据,然后将其传递给BeautifulSoup构造函数进行解析。需要指定解析器,例如‘html.parser’
  5. 使用Soup对象:成功创建BeautifulSoup对象(通常命名为soup)后,它就成为了整个HTML文档的根节点。之后,你就可以使用之前学过的各种方法(如.find(), .find_all())来查找和提取所需的页面内容了。

代码示例

以下是一个将上述步骤整合在一起的代码示例:

import requests
from bs4 import BeautifulSoup

# 目标网页的URL
url = ‘https://example.com‘

# 发送GET请求获取网页内容
response = requests.get(url)

# (可选)检查请求是否成功
if response.status_code == 200:
    print(“请求成功!”)
else:
    print(f“请求失败,状态码:{response.status_code}”)

# 使用响应内容创建BeautifulSoup对象,指定HTML解析器
soup = BeautifulSoup(response.content, ‘html.parser’)

# 现在可以使用soup对象来查找元素了
# 例如,查找所有的段落标签
paragraphs = soup.find_all(‘p’)
for p in paragraphs:
    print(p.get_text())

关键概念解析

  • requests.get(url):这是requests库的核心函数之一,用于向指定的url发起HTTP GET请求。它返回一个Response对象,其中包含了服务器返回的所有信息,如状态码、头部信息和网页内容。
  • response.content:这是Response对象的一个属性,它包含服务器返回的原始字节流内容。对于HTML网页,这就是我们需要解析的源代码。使用.content而非.text可以确保编码正确。
  • BeautifulSoup(response.content, ‘html.parser’):这是创建Soup对象的关键语句。我们将从网络获取的原始内容(response.content)和指定的解析器(‘html.parser’)传递给BeautifulSoup类,从而构造出一个可以方便地进行导航、搜索和修改的文档树对象。

本节课中我们一起学习了如何直接从URL获取网页数据并创建BeautifulSoup对象。你掌握了使用requests库发起网络请求,以及将响应内容传递给BeautifulSoup进行解析的完整流程。这为从动态的、在线的网页中提取信息打开了大门,是网络数据抓取(Web Scraping)的基础技能。

20:欢迎进入第二周 🚀

在本节课中,我们将要学习如何与两种重要的数据格式——XML和JSON——进行交互,并探索如何通过API(应用程序编程接口)从其他应用程序请求数据。

概述

本周的学习内容将围绕数据交换和处理展开。首先,我们将介绍XML和JSON这两种用于数据交换的格式。接着,我们将学习如何处理这些格式的数据。最后,我们将深入理解什么是API,并掌握如何向API发起请求以获取数据,包括使用API密钥和请求参数。

学习XML与JSON

上一节我们概述了本周的学习目标,本节中我们来看看具体要掌握的内容。

以下是本周你将学习到的核心技能:

  • 你将学习如何使用XML和JSON。这两种是可用于交换数据的格式。
  • 你还将学习如何处理XML和JSON数据。

理解与使用API

在掌握了数据格式后,我们将进入更动态的数据获取环节。

接下来,你将学习什么是API。这是一种应用程序向另一个应用程序请求数据的方式,其全称是应用程序编程接口

具体而言,我们将使用 requests.get() 方法发起HTTP请求,以从API获取数据。

以下是关于API使用的关键学习点:

  • 你将学习如何向API请求数据。
  • 你将学习在需要时如何使用API密钥。
  • 你将学习如何在请求中使用参数。

总结

本节课中,我们一起学习了第二周的核心内容:XML与JSON数据格式的处理,以及通过API进行数据请求的基本概念和方法。掌握这些技能将使你能够与各种网络服务和数据源进行交互,为你的创意编程项目注入丰富的外部数据。

21:XML概念解析 📚

在本节课中,我们将要学习XML(可扩展标记语言)的基本概念。XML是一种用于存储和交换数据的标记语言,它在结构上与HTML相似,但用途更为广泛。我们将探讨XML的基本语法、结构以及它与HTML的异同。


什么是XML? 🤔

XML代表可扩展标记语言。其中,“可扩展”意味着你可以根据需要添加和定义自己的标签。它在某种程度上类似于HTML。

以下是一个XML示例:

<person>
    <name>John Doe</name>
    <age>30</age>
    <city>New York</city>
</person>

如你所见,XML是一种标记语言,它使用与HTML相同的尖括号(<>)。它同时包含开始标签和结束标签,并且标签可以嵌套。在上面的例子中,<person>标签内部嵌套了包含个人信息的其他标签。

本质上,XML是另一种我们可以用来存储和交换数据的格式。


XML与HTML的异同 🔄

上一节我们介绍了XML的基本定义和结构,本节中我们来看看XML与HTML的主要异同。

两者都使用类似的标签语法,但目的不同:

  • HTML用于描述网页的结构和内容(如标题、段落、链接)。
  • XML用于描述和传输数据本身,其标签含义由使用者自定义。

XML的核心结构 🏗️

理解了XML的用途后,我们来详细解析其核心结构。一个格式良好的XML文档必须遵循以下规则:

以下是构成XML文档的基本元素列表:

  1. 根元素:XML文档必须有一个唯一的根元素,所有其他元素都包含在其中。例如,<catalog>
  2. 开始与结束标签:每个元素必须由开始标签(如<book>)和对应的结束标签(如</book>)闭合。
  3. 元素嵌套:元素可以相互嵌套,但必须正确闭合,不能交叉。例如,<a><b></b></a>是正确的,而<a><b></a></b>是错误的。
  4. 属性:开始标签内可以包含属性来提供元素的额外信息。例如,<book id="123">
  5. 文本内容:元素可以包含文本内容,如<title>Python编程</title>

为什么使用XML? 💡

我们已经了解了XML的语法规则,你可能会问,为什么要使用它?XML的主要优势在于其可扩展性平台无关性

由于标签是自定义的,XML可以描述各种类型的数据结构,从简单的通讯录到复杂的配置文档。同时,纯文本格式使其易于被不同的系统和编程语言读取和处理,因此它成为应用程序之间交换数据的常用格式。


本节课中我们一起学习了XML的基础概念。我们了解到XML是一种可扩展的标记语言,用于存储和传输数据。我们分析了它的基本语法结构,包括标签、嵌套和属性,并比较了它与HTML的异同。最后,我们探讨了XML在数据交换和存储中的实用价值。掌握XML是理解许多现代数据格式和网络通信协议的重要一步。

22:XML术语详解 📖

在本节课中,我们将详细学习XML文档的基本结构和相关术语。我们将通过一个具体的例子,了解XML中的标签、元素、属性以及树形结构是如何组织的。


XML文档示例

以下是一个XML文档的例子。

<person>
  <name>Chuck</name>
  <phone type="intl">+1 734 303 4456</phone>
  <email hide="yes"/>
</person>

XML术语详解

上一节我们看到了一个XML示例,本节中我们来详细看看其中的各个组成部分。

开始标签与结束标签

XML文档由标签构成。一个元素通常由开始标签结束标签包围。例如,<name>是开始标签,</name>是结束标签,它们之间的内容是元素的值。

自闭合标签

如果一个标签没有内容,即开始标签和结束标签之间没有任何文本或其他元素,则可以使用自闭合标签。其写法是在开始标签的末尾加上一个斜杠 / 和右尖括号 >

例如,<email hide="yes"/> 就是一个自闭合标签。它等价于 <email hide="yes"></email>

树形结构

XML数据可以表示为一个树形结构。在这个结构中,有一个根元素,其他元素都是它的子节点。

以下是上述XML示例对应的树形结构:

  • 根节点person
    • 子节点name
      • 文本值Chuck
    • 子节点phone
      • 属性type="intl"
      • 文本值+1 734 303 4456
    • 子节点email
      • 属性hide="yes"

元素与属性

  • 元素:如 <name><phone>,是树形结构中的节点。
  • 属性:提供关于元素的额外信息,位于开始标签内。例如,在 <phone type="intl"> 中,type 是属性名,"intl" 是属性值。

层级关系

XML结构体现了清晰的父子关系层级关系

  • 在上面的例子中,person 是根元素,也是 namephoneemail父节点
  • 反过来,namephoneemailperson子节点


本节课中我们一起学习了XML的核心术语。我们了解了XML文档由开始标签、结束标签和自闭合标签构成,数据可以组织成具有父子关系的树形结构,并且元素可以拥有提供额外信息的属性。理解这些基本概念是后续解析和处理XML数据的基础。

23:XML解析技术

在本节课中,我们将要学习如何使用Python解析XML数据。XML是一种常见的数据格式,用于存储和传输结构化信息。我们将使用Python内置的xml.etree.ElementTree库来读取XML字符串,并从中提取所需的数据。

导入库与解析XML

首先,我们需要导入处理XML所需的库。与Beautiful Soup类似,xml.etree.ElementTree是一个强大的工具,它允许我们将XML数据解析成一个树形结构,从而方便地访问其中的元素。

import xml.etree.ElementTree as ET

我们使用as ET为这个模块创建一个简短的别名,以便在后续代码中更方便地引用它。

接下来,我们创建一个包含XML数据的多行字符串。在Python中,使用三个引号可以定义跨越多行的字符串。

data = '''
<person>
  <name>Chuck</name>
  <phone type="intl">
    +1 734 303 4456
  </phone>
  <email hide="yes"/>
</person>
'''

然后,我们使用ET.fromstring()函数来解析这个字符串。这个函数会读取XML数据,将其转换为一个树形结构,并返回代表整个XML文档的根节点对象。

tree = ET.fromstring(data)

现在,变量tree指向XML树的根节点,即<person>标签。

查找元素与提取数据

上一节我们介绍了如何将XML字符串解析为树结构。本节中我们来看看如何从这棵树中查找特定的元素并提取信息。

我们可以使用find()方法来查找当前节点的子元素。例如,要查找<name>标签,可以这样做:

name_element = tree.find('name')

find('name')会返回第一个匹配的<name>子元素对象。要从这个元素对象中获取其包含的文本内容,我们可以访问其.text属性。

name_text = name_element.text

以下是提取XML中不同数据的步骤列表:

  1. 查找并提取文本内容:使用find()方法定位标签,然后使用.text属性获取其内部的文本。
  2. 查找并提取属性值:同样使用find()方法定位标签,然后通过类似字典的方式访问其属性,例如element.get('attribute_name')

让我们实践一下。为了提取电子邮件标签的hide属性,我们可以这样做:

email_element = tree.find('email')
hide_attribute = email_element.get('hide')

最后,我们可以将提取出的信息打印出来:

print('Name:', name_text)
print('Attribute:', hide_attribute)

这段代码将输出:

Name: Chuck
Attribute: yes

总结

本节课中我们一起学习了XML解析的基础技术。我们首先导入了xml.etree.ElementTree库,然后将一个XML字符串解析为树形结构。接着,我们使用find()方法在树中定位特定的元素标签,并通过.text属性获取元素的文本内容,或通过.get()方法获取元素的属性值。这些操作为处理更复杂的XML数据打下了坚实的基础。

24:修复XML文档 🛠️

在本节课中,我们将学习如何识别和修复XML文档中的常见错误。XML是一种用于存储和传输数据的标记语言,其结构必须严格遵守语法规则,否则解析器将无法正确读取数据。我们将通过一个具体的例子,逐步分析错误原因并实施修复。

识别错误

上一节我们介绍了XML的基本结构,本节中我们来看看如何定位文档中的错误。首先,尝试运行一个有问题的XML文件,解析器通常会返回错误信息,指出问题所在的行和大致原因。

例如,解析器可能报告“第14行错误,XML无效标记位于第6行”。这表明我们需要重点关注文档的第6行附近。

分析并修复错误

以下是修复XML文档时需要检查的几个关键点:

  1. 检查标签是否闭合:每个开始标签都必须有一个对应的结束标签。例如,<title> 必须有 </title> 与之匹配。在上述错误中,第6行附近的 <title> 标签就缺少了闭合标签。
  2. 检查标签嵌套顺序:标签必须正确嵌套,后开始的标签必须先闭合。确保结束标签的位置没有放错。
  3. 检查属性值引号:所有属性的值都必须用双引号 " 或单引号 ' 括起来。例如,category="cooking" 是正确的,而 category=cooking 会导致解析失败。案例中 Gregorian 属性值就缺少了引号。

通过逐一检查并修正这些问题,例如补全缺失的 </title></author> 标签,为 Gregorian 属性加上引号,最终可以使XML文档变得有效。

总结

本节课中我们一起学习了修复XML文档的核心步骤。关键在于确保每个开始标签都有匹配的结束标签,并且所有属性值都被引号正确包裹。遵循这些简单的规则,就能解决大部分XML格式错误,使文档能够被顺利解析。

25:JSON基础介绍 📚

在本节课中,我们将要学习JSON的基础知识。JSON是一种轻量级的数据交换格式,广泛应用于网络数据传输和配置文件。理解JSON的结构和用法,对于从网络API获取数据或存储结构化信息至关重要。

什么是JSON? 🤔

上一节我们介绍了课程主题,本节中我们来看看JSON的具体定义。

JSON代表JavaScript对象表示法。它是一种用于存储和交换数据的方式,特别是我们可以从Web服务器以字符串形式获取数据。然而,该字符串表示的是嵌套的字典和列表,这与我们在Python中看到的结构非常相似。

JSON的结构示例 📊

了解了JSON的基本概念后,我们通过一个具体例子来观察其典型结构。

以下是一个JSON示例,它展示了一个包含员工信息的结构:

{
  "employees": [
    {
      "firstName": "John",
      "lastName": "Doe"
    },
    {
      "firstName": "Anna",
      "lastName": "Smith"
    }
  ]
}

在这个例子中,我们有一个字典,它有一个键"employees",其值是一个字典列表。然后,每个内部字典都有"firstName""lastName"键。

JSON与Python数据结构的异同 🔄

看过了JSON的示例结构,接下来我们将其与熟悉的Python数据结构进行对比。

JSON与Python非常相似,但它使用null而不是None来表示空值。以下是核心数据类型的对比:

  • 对象/字典: 在JSON中用花括号{}表示,在Python中用dict表示。
  • 数组/列表: 在JSON中用方括号[]表示,在Python中用list表示。
  • 字符串: 在JSON中必须使用双引号"",在Python中单双引号均可。
  • 数字: 表示方式相同(整数、浮点数)。
  • 布尔值: JSON中使用truefalse(小写),Python中使用TrueFalse(首字母大写)。
  • 空值: JSON中使用null,Python中使用None

总结 📝

本节课中我们一起学习了JSON的基础知识。我们了解了JSON是一种用于数据交换的文本格式,其结构类似于Python的字典和列表。我们比较了JSON与Python在数据类型表示上的关键区别,例如使用null而非None。掌握JSON是进行网络数据交互和处理结构化配置文件的重要一步。

26:XML与JSON对比 🆚

在本节课中,我们将要学习两种常见的数据交换格式:XML和JSON。我们将通过对比它们的结构、特点和使用场景,帮助你理解它们之间的异同,并了解在何种情况下选择使用哪一种格式。

XML与JSON概述

上一节我们介绍了数据交换的基本概念,本节中我们来看看两种具体的数据格式:XML和JSON。它们都是用于存储和传输结构化数据的常用格式。

XML格式示例

以下是XML格式的一个简单示例。XML是一种标记语言,使用尖括号来定义标签,其外观与HTML非常相似。

<person>
  <name>John Doe</name>
  <phone type="mobile">123-456-7890</phone>
  <email hide="yes"/>
</person>

在XML中,你可以在开始标签中包含属性,也可以使用自闭合标签。它通过标签的嵌套来构建数据的层次结构。

JSON格式示例

以下是JSON格式的一个简单示例。JSON使用嵌套的字典和列表结构来表示数据。

{
  "name": "John Doe",
  "phone": {
    "type": "mobile",
    "number": "123-456-7890"
  },
  "email": {
    "hide": "yes"
  }
}

JSON通过键值对来组织数据,可以轻松地表示复杂的嵌套结构。

两者对比分析

现在我们来具体对比XML和JSON的特性。以下是它们的主要区别和共同点:

  • 自描述性:两者都具有一定的自描述性,能够解释它们所包含的数据类型。XML通常在自描述性方面更胜一筹,因为它提供了更多关于数据类型的细节(例如,明确指出<phone>标签)。JSON则通过键名(如"phone")来实现类似的功能。
  • 层次结构:两者都是分层的,具有树状结构。
  • 数据交换:两者都可用于从Web服务器获取数据。
  • 语法差异:JSON没有像XML那样的开始和结束标签(例如,没有<person></person>这样的配对标签)。JSON的语法通常更简洁。
  • 数据结构:JSON可以包含列表(数组),而XML可以通过元素序列实现类似功能。
  • 解析方式:JSON可以被JavaScript函数直接解析,这在Web开发中非常方便。XML则通常需要特定的解析器。
  • 属性处理:XML支持标签内的属性。在JSON中,这些属性通常被转化为嵌套的字典值。

使用场景总结

本节课中我们一起学习了XML和JSON的对比。总的来说,XML的自描述性通常比JSON更强。

目前,大多数人在传输数据时更倾向于使用JSON。XML则更多地被用于需要高度自描述性的场景,例如文字处理器文档格式或电子书的创建。

在选择使用XML还是JSON时,你可以根据项目需求、数据结构的复杂性以及与现有系统的兼容性来决定。

27:JSON数据处理 🗂️

在本节课中,我们将学习如何在Python中处理JSON数据。JSON是一种常见的数据交换格式,广泛用于网络通信和配置文件。我们将了解如何将JSON字符串转换为Python对象,以及如何将Python对象转换回JSON字符串。


上一节我们介绍了JSON的基本概念,本节中我们来看看具体的转换方法。

要在Python对象和JSON字符串之间进行转换,我们必须导入json模块。这是一个允许我们处理JSON的库。

然后,我们可以获取一个字符串,并使用json.loads()函数将其转换为Python对象(列表或字典)。该函数接收一个字符串参数,并返回一个Python列表或字典。因此,我们需要知道我们期望的JSON结构是什么,或者需要立即检查它。

接着,如果我们有一个Python列表或字典,我们可以使用json.dumps()函数将其转换为JSON字符串。该函数接收一个对象参数,并返回一个字符串。然后,我们可以将这个字符串写入文件。

以下是处理JSON的基本步骤:

  1. 导入模块:首先,需要导入json模块。
  2. 解析JSON:使用json.loads()将JSON字符串转换为Python对象。
  3. 生成JSON:使用json.dumps()将Python对象转换为JSON字符串。

这就是处理JSON的方法。


本节课中我们一起学习了JSON数据处理的核心操作。我们掌握了使用json.loads()函数将JSON字符串解析为Python列表或字典,以及使用json.dumps()函数将Python对象序列化为JSON字符串。这些是进行数据交换和存储的基础技能。

28:API核心概念 🧩

在本节课中,我们将要学习API的核心概念。API是连接不同应用程序的桥梁,它允许程序之间相互请求和交换数据。我们将了解API的含义、常见的数据格式以及它在现代软件架构中的作用。


什么是API?

API代表应用程序编程接口。它是两个应用程序之间进行通信的一种方式。一个应用程序可以请求数据,另一个应用程序则返回数据。

我们倾向于使用我们刚刚学过的两种数据格式:XML和JSON。如今,你更可能使用的是JSON。


面向服务的架构

如果一个应用程序使用了其他应用程序的服务,我们称之为面向服务的架构SOA

例如,你可能访问一个网站,一次性预订了最喜欢的书籍、航班、酒店和租车服务。该网站本身并不存储所有这些数据,而是通过API联系其他网站来获取这些信息并完成预订。


Web服务

如果上述所有交互都通过网络进行,我们称之为Web服务


本节课中,我们一起学习了API的核心概念。我们了解到API是应用程序间的通信接口,常用JSON格式交换数据。我们还探讨了面向服务的架构,以及API如何支撑起现代Web服务,让不同平台能够无缝协作。

29:免费API资源

在本节课中,我们将学习如何寻找和利用免费的API资源。我们将介绍一个包含大量免费API列表的GitHub仓库,并演示如何查看API的详细信息、理解其授权方式,以及如何初步测试和使用一个API。

寻找免费API资源

当你开始使用API时,拥有一份可用资源列表会很有帮助。这份列表可以告诉你能够获取哪些数据,以及需要何种授权。

这里有一个非常棒的GitHub仓库,其URL如下。该仓库的索引中列出了不同主题的API。当你访问该仓库并向下滚动时,会看到一个分类列表。

探索API列表

以下是探索该列表的步骤:

  1. 浏览分类:在仓库主页,你可以看到按主题(如健康、动物等)分类的列表。
  2. 查看详情:点击某个分类,例如“动物”,你会看到一个表格,其中包含该类别下的各个API信息。
  3. 理解信息:表格中通常包含以下列:
    • API名称:你可以点击它直接跳转到API的官方文档或网站。
    • 描述:关于该API功能的简要说明。
    • 授权方式:访问该API所需的授权类型,例如“无需授权”、“需要API密钥”或“需要OAuth授权”。
    • HTTPS:该API是否支持HTTPS安全连接。
    • CORS:跨源资源共享设置。这关系到你的网页应用能否从不同域访问该API。“否”通常意味着没有限制,“是”则可能有某些限制,“未知”则需要自行查明。

使用一个示例API

一旦你找到一个免费的API,例如“动物”分类下的“免费猫咪趣事”API,你就可以开始尝试使用它。

首先,阅读其文档以了解如何发起请求。文档会指明基础请求URL。例如,该API的基础URL可能是:
https://cat-fact.herokuapp.com

然后,你可以在URL后添加不同的端点或路径来获取特定数据。例如,添加 /facts 端点可以获取猫咪趣事列表,完整的请求URL如下:
https://cat-fact.herokuapp.com/facts

格式化JSON响应

直接从浏览器或命令行获取的JSON数据有时难以阅读。为了使数据结构更清晰,你可以使用在线JSON格式化工具。

  1. 访问一个JSON格式化网站,例如 https://jsonformatter.org
  2. 将原始的JSON响应文本粘贴到输入框中。
  3. 点击“格式化”或“美化”按钮。
  4. 工具会输出结构清晰、缩进分明的JSON,这让你更容易理解数据的层次和内容。

本节课中,我们一起学习了如何利用GitHub上的公共仓库寻找免费的API资源,如何解读API列表中的关键信息(如授权方式和CORS),并通过一个具体的猫咪趣事API演示了查找文档、构建请求URL的过程。最后,我们还介绍了使用在线工具格式化JSON响应,以便更轻松地处理和理解数据。掌握这些步骤,你就能自信地探索和集成各种网络API到你的创意编程项目中了。

30:API授权机制 🔑

在本节课中,我们将要学习如何与需要授权密钥的API进行交互。我们将了解API密钥的作用、如何获取它们,以及如何在请求中使用它们来获取数据。

什么是API密钥?

当您使用API时,某些API会要求您提供一个API密钥。服务提供方希望通过密钥来识别谁在使用其服务。他们可能借此来限制您的使用量,甚至可能需要您提供账单信息。此外,一些服务器可能需要更强大的授权机制,称为OAuth授权。OAuth是一个允许第三方应用程序访问网络服务的框架,您可以在此URL阅读更多相关信息。

如何获取并使用API密钥?

如果您发现想要连接的API需要一个密钥,例如在线电影数据库OMDB,您通常可以申请一个。以下是获取和使用API密钥的步骤。

以下是获取OMDB API密钥的步骤:

  1. 访问 omdbapi.com/apikey.aspx
  2. 申请免费密钥,它通常有每日1000次请求的限制,对于学习来说应该足够了。
  3. 点击提交并填写您的信息。

获取密钥后,您需要在请求中添加它。具体做法是在您的请求URL中添加 apikey= 参数,后面跟上您的密钥。

理解API文档

每个API都有不同的使用方式,因此您必须阅读其API文档来了解如何获取所需信息。以OMDB API为例,其文档会说明您可以使用的参数。

以下是OMDB搜索功能的一些关键参数:

  • s (必需):要搜索的电影标题。
  • type (可选):指定类型,如电影、系列或剧集。
  • y (可选):指定发布的年份。

实践示例

上一节我们介绍了API密钥的基本概念和获取方法,本节中我们来看看一个具体的请求示例。

以下是一个搜索“教父”电影的请求URL示例:

http://www.omdbapi.com/?apikey=你的密钥&s=godfather

在这个URL中,不同的参数用“&”符号分隔。API会自动处理特殊字符,例如引号会被转换为%22。执行此请求后,服务器会返回关于“教父”的大量信息。

总结

本节课中我们一起学习了API授权机制。我们了解到API密钥用于识别用户和控制访问,学习了如何为OMDB等服务申请密钥,并掌握了在HTTP请求中添加apikey参数来使用密钥的方法。关键在于,每个API都有其独特的参数和要求,因此仔细阅读官方文档是成功集成API的必要步骤。

31:从API获取JSON数据 🚀

在本节课中,我们将学习如何使用Python从网络API获取数据,并将返回的JSON格式数据转换为Python对象进行处理。

概述

我们可以使用Python从API获取数据。具体方法是使用 requests.get() 函数向目标URL发送请求。你需要根据API的要求组装请求。之后,我们可以检查响应的状态码。如果状态码是 200,表示请求成功;如果是 404,则表示未找到资源。我们可以从响应中获取数据,通过 .text 属性获取原始文本,然后使用 json.loads() 函数将JSON文本转换为Python的列表或字典。在本例中,转换结果将是一个列表。我们可以打印该对象的类型,也可以直接打印其内容。

核心步骤详解

上一节我们介绍了从API获取数据的基本概念,本节中我们来看看具体的实现步骤和细节。

1. 发送请求与检查状态

首先,使用 requests.get() 方法向指定的API端点URL发送HTTP GET请求。发送请求后,检查返回的响应对象的状态码至关重要,它能告诉我们请求是否被服务器成功处理。

以下是发送请求和检查状态码的核心代码:

import requests

response = requests.get('https://api.example.com/data')
status_code = response.status_code
  • 200: 表示请求成功。
  • 404: 表示请求的资源未找到。
  • 其他状态码(如500)表示服务器内部错误。

2. 解析JSON数据

如果请求成功(状态码为200),我们就可以处理响应体中的数据。API通常返回JSON格式的数据。我们需要将JSON字符串解析为Python能够直接操作的数据结构。

以下是解析JSON数据的核心代码:

import json

if response.status_code == 200:
    # 获取响应的文本内容
    response_text = response.text
    # 将JSON字符串解析为Python对象(列表或字典)
    data = json.loads(response_text)
    print(type(data))  # 查看转换后的数据类型
    print(data)        # 打印数据内容
else:
    print(f"请求失败,状态码:{response.status_code}")

工作流程总结

本节课中我们一起学习了从API获取并处理数据的完整流程。

  1. 发送请求:使用 requests.get() 方法。
  2. 检查响应:通过 response.status_code 验证请求是否成功。
  3. 转换数据:使用 json.loads(response.text) 将JSON字符串转换为Python对象(列表或字典)。
  4. 处理数据:像操作普通Python列表或字典一样使用转换后的数据。

记住,你可以使用 requests.get 发送请求,可以选择性地检查响应结果以确认操作是否成功,可以将JSON数据转换为Python对象,然后就可以在Python中自由地使用这些数据了。

32:使用params指定API参数 🛠️

在本节课中,我们将学习如何更便捷地向API发送请求,特别是如何通过params参数来指定API密钥和其他查询参数,而无需手动拼接复杂的URL字符串。

概述

调用网络API时,通常需要提供一些参数,例如API密钥、搜索条件或筛选选项。虽然你可以手动将这些参数拼接成一个带有&符号的查询字符串,但Python的requests库提供了一个更简单、更安全的方法。

使用params字典

requests库允许你创建一个字典,其中包含所有需要发送给API的键值对参数。然后,你可以将这个字典传递给requests.get()函数的params参数。库会自动处理URL的编码和拼接,确保特殊字符被正确转换。

以下是使用params参数的基本步骤:

  1. 导入requests:首先确保你已经安装了requests库。
  2. 定义参数字典:创建一个Python字典,键是API要求的参数名,值是对应的参数值。
  3. 发送请求:调用requests.get(),传入基础URL和参数字典。

实践示例

假设我们正在使用一个查询特定国家在某一年所有假日的API。该API要求提供API密钥、国家代码和年份。

以下是如何使用params参数来实现的代码示例:

import requests

# 1. 定义基础URL(不包含查询参数)
base_url = "https://api.example.com/holidays"

# 2. 创建参数字典
params = {
    "api_key": "YOUR_ACTUAL_API_KEY_HERE",  # 替换为你的真实API密钥
    "country": "US",
    "year": 2024
}

# 3. 发送GET请求,并传递params参数
response = requests.get(base_url, params=params)

# 4. 处理响应数据
data = response.json()
print(data)

在上面的代码中:

  • base_url是API的端点地址,不包含?后面的查询字符串。
  • params字典清晰地列出了所有必需的参数。
  • requests.get()函数接收这两个参数,并自动构建出完整的请求URL(例如:https://api.example.com/holidays?api_key=...&country=US&year=2024)。

优势总结

使用params参数的方法相比手动拼接字符串有以下几个主要优势:

  • 代码更清晰:参数字义明确,易于阅读和维护。
  • 自动编码requests库会自动处理URL编码,确保特殊字符(如空格、中文等)被正确转换为%20等形式,避免错误。
  • 防止错误:减少了因手动拼接导致的格式错误,如漏掉&?符号。

总结

本节课我们一起学习了如何高效地使用requests库的params参数来指定API请求参数。通过创建一个参数字典并将其传递给get()函数,我们可以让代码更简洁、更健壮,并避免手动处理URL编码的麻烦。这是与Web API进行交互时一个非常实用且重要的技巧。

33:数据库与SQL入门 🗄️

在本节课中,我们将开始学习数据库。数据库是大多数商业应用的核心,我们将了解其基本概念,学习如何使用SQL语言操作数据,并最终掌握如何在Python程序中连接和操作数据库。

数据库核心概念 📊

上一节我们开启了第三周的学习,本节中我们来看看数据库的基础构成。数据库用于存储和管理大量结构化数据。其核心概念包括表、行和列。

  • :数据存储的基本单位,类似于Excel中的一个工作表。
  • :表中的一条记录,代表一个独立的数据实体。
  • 列/属性:表中的字段,定义了记录所包含的数据类型,如“姓名”、“年龄”。

使用SQL操作数据库 🔍

了解了数据库的基本结构后,本节我们将学习如何与数据库交互。我们将使用DB Browser工具来查看数据库,并执行SQL语句。SQL是“结构化查询语言”的缩写,是与关系型数据库交互的标准方式。

以下是SQL中最核心的几种操作:

  • 创建与删除表:使用 CREATE TABLEDROP TABLE 语句。
  • 插入数据:使用 INSERT INTO 语句向表中添加新记录。
  • 更新数据:使用 UPDATE 语句修改表中已有的记录。
  • 删除数据:使用 DELETE FROM 语句移除表中的记录。
  • 查询数据:使用 SELECT 语句从表中检索所需的数据。

在Python中连接数据库 🐍

我们已经学会了直接使用SQL操作数据库,现在来看看如何将这些操作集成到Python程序中。这需要使用特定的库来建立连接并执行命令。

在Python中操作数据库主要涉及两个对象:连接和游标。

  • 连接:代表与数据库服务器的一个会话。通过它建立通信渠道。创建连接的代码通常类似于:conn = sqlite3.connect('database.db')
  • 游标:类似于一个指针或导航器,用于在连接上执行SQL命令并获取结果。创建游标的代码通常类似于:cursor = conn.cursor()

我们将学习如何创建连接和游标,解释两者之间的区别,并最终在Python环境中执行SQL语句来操作数据库。


本节课中我们一起学习了数据库的基础知识,包括表、行、列等核心概念。我们介绍了使用SQL语言进行增删改查操作,并初步探索了如何在Python中通过建立连接和游标来与数据库进行交互。这些是构建数据驱动应用的重要基石。

34:为何使用数据库

在本节课中,我们将要学习数据库的基本概念,并探讨在编程项目中为何以及何时需要使用数据库。

什么是数据库?🤔

上一节我们介绍了数据持久化的重要性,本节中我们来看看数据库的具体定义。你可以将数据库视为一种文件,因为它提供了永久存储。这意味着即使你的程序运行结束,数据依然存在。同时,数据库也类似于一个字典,因为它能让你通过一个键(key)轻松地查找对应的值(value)。

因此,我们可以将数据库理解为一种功能更强大的字典。它不仅能存储比内存中的字典多得多的数据,而且这种存储是永久性的。

数据库的组织结构 📊

理解了数据库的基本概念后,我们来看看它是如何组织数据的。数据库将数据组织成。每个表由行和列组成,类似于电子表格。

以下是数据库表结构的一个简单示例:

# 一个表示“用户”表的简化结构
# 列:id, name, email
# 行:具体的数据记录
users_table = [
    {"id": 1, "name": "Alice", "email": "alice@example.com"},
    {"id": 2, "name": "Bob", "email": "bob@example.com"}
]

使用数据库的核心优势 ⚡

了解了数据库的结构后,现在我们来探讨使用数据库的几个关键优势。

以下是使用数据库的主要好处:

  1. 高效的数据检索:给定一个键(如用户ID),我们可以非常高效地从数据库中检索出对应的数据行。这在数据库变得非常庞大时尤为重要。
  2. 强大的关联查询:数据库允许我们进行跨多个表的搜索和关联查询。现实中的数据通常分散在不同的表中,数据库能高效地处理这种关联。
  3. 并发处理能力:一个非常关键的理念是,多个用户可以同时读取和修改数据库中的数据。例如,当你在网上预订机票或购买衣服时,可能有成千上万的用户也在进行同样的操作。数据库系统就是为处理这种高并发负载而设计的。

总结 📝

本节课中我们一起学习了数据库的基础知识。我们了解到数据库是一种提供永久存储的数据组织方式,它结合了文件的持久性和字典的快速查找能力。通过将数据组织成表,并支持高效检索、关联查询和高并发访问,数据库成为处理大量、复杂且需要共享的数据时的理想选择。在后续的课程中,我们将学习如何在实际编程中使用数据库。

35:SQLite入门指南 🗄️

在本节课中,我们将要学习SQLite数据库的基础知识。SQLite是一个免费、快速且小巧的数据库系统,它已经内嵌在Python和许多其他编程语言中。学习使用SQLite将帮助你存储和管理程序中的数据。

什么是SQLite? 🤔

上一节我们介绍了本课程的目标,本节中我们来看看我们将要使用的工具——SQLite。

我们将使用SQLite,这是一个非常流行的数据库,它免费、快速且小巧。这些特性都是你所需要的。它已经内嵌在Python和许多其他语言中。因此,一旦你学会了它,你可以在很多地方使用它。

核心概念与连接数据库 🔌

了解了SQLite的基本优点后,接下来我们看看如何开始使用它。使用SQLite的第一步是连接到数据库文件。

在Python中,我们使用 sqlite3 模块来操作SQLite数据库。连接数据库的代码如下:

import sqlite3

# 连接到数据库文件(如果不存在则会创建)
connection = sqlite3.connect('my_database.db')

这段代码会创建一个名为 my_database.db 的数据库文件,并建立与它的连接。

基本操作步骤 📝

以下是使用SQLite进行数据操作的基本步骤流程:

  1. 导入模块:首先需要导入 sqlite3 模块。
  2. 建立连接:使用 connect() 函数连接到数据库文件。
  3. 创建游标:通过连接对象创建一个游标(Cursor),用于执行SQL命令。
  4. 执行SQL语句:使用游标的 execute() 方法来创建表、插入或查询数据。
  5. 提交更改:对数据库进行修改后(如插入、更新、删除),需要使用 commit() 方法保存更改。
  6. 关闭连接:完成所有操作后,使用 close() 方法关闭数据库连接。

总结 📚

本节课中我们一起学习了SQLite数据库的入门知识。我们了解到SQLite是一个轻量级、易用的数据库,非常适合初学者和小型项目。我们掌握了使用Python连接SQLite数据库的基本代码和操作步骤,这是进行后续数据存储和管理操作的基础。

36:DB Browser下载教程 🛠️

在本节课中,我们将学习如何下载并安装一个名为“DB Browser for SQLite”的免费图形化工具。这个工具能帮助你更直观地查看和管理数据库,并练习SQL命令。

工具介绍与下载步骤

上一节我们介绍了数据库的基本概念,本节中我们来看看如何获取一个实用的数据库管理工具。

我推荐你下载DB Browser for SQLite。这是一个免费工具,它提供了一个图形用户界面来操作数据库。使用这个工具可以让你更容易地查看数据库的状态,并练习SQL命令。你可以在其中编写SELECTCREATE等语句,执行它们,并观察执行效果。

以下是下载和安装该工具的步骤:

  • 访问指定的URL,该链接将直接带你进入下载页面。
  • 根据你的操作系统,找到对应的正确版本。
  • 下载并安装该软件。

工具界面预览

安装完成后,你将看到该工具的界面。接下来,我将在一个练习数据库上向你展示它的具体样子。


本节课中我们一起学习了如何下载和安装DB Browser for SQLite。这个图形化工具将帮助我们更轻松地管理和探索数据库,为后续的SQL命令练习打下基础。

37:使用DB Browser探索数据库 🗄️

在本节课中,我们将学习如何使用DB Browser for SQLite工具来打开和探索一个SQLite数据库文件。我们将了解数据库的基本结构,包括表、行和列的概念,以及它们如何与面向对象编程中的类和对象相对应。


上一节我们介绍了数据库的基本概念,本节中我们来看看如何使用DB Browser这个图形化工具来实际查看一个数据库。

如果你下载了DB Browser,接下来需要打开Chinook.db数据库文件并查看其内容。你可以通过点击“打开数据库”按钮来完成此操作,这将弹出一个导航窗口,你可以用它找到你存放Chinook.db文件的位置,然后打开它。

打开后,你将看到数据库的结构。界面上有不同的选项卡。例如,我可以浏览数据,查看不同表中的数据。请记住,在关系型数据库中,数据被分解到不同的表中。你可以将每个表视为一个不同的类,或者代表一个不同的实体或事物类型,这就是为什么我们将其类比为类或分类。

在每个表中,你将看到数据行。我们可以将这些数据行视为该类的对象,用于描述一个特定的事物。以专辑为例,专辑可以有一个ID作为主键以便于查找,但专辑还有标题和创作它的艺术家。请注意,我们这里没有直接存放艺术家姓名,而是存放了一个艺术家ID。一个完全规范化的关系型数据库会有很多表,因为我们不希望有任何重复的数据。

我们也可以看看艺术家表里有什么。例如,对于艺术家ID 1,对应的乐队是AC/DC;艺术家ID 2是Aerosmith。我们将专辑和艺术家信息分开了,在专辑表中存放的是艺术家ID,而不是艺术家姓名。这样,当不同专辑有同一位艺术家时,我们就不会重复存储字符串信息,而只是存储一个ID。这既节省了空间,也提高了效率。

因此,我们将数据库分解为独立的表,每个表代表一个实体或事物。表中的行代表对象,而列则代表该对象的具体数据属性。例如,在员工表中,可能有“名”和“姓”两列,这些就是员工或人员对象的不同属性。

以上是关于如何导航数据库以及一些数据库术语的简要介绍。请查看Chinook数据库中的不同表,并通过查看数据库结构来了解所有表。这是一个功能完整的关系型数据库。


本节课中我们一起学习了如何使用DB Browser打开和探索SQLite数据库。我们了解了数据库的核心组成部分:(类似于类)、(类似于对象)和(类似于属性)。我们还看到了如何通过使用ID关联不同表来避免数据冗余,这是关系型数据库设计的关键原则。通过图形化工具,我们可以直观地理解数据的组织方式。

38:DB Browser中的SQL基础 🗄️

在本节课中,我们将学习SQL(结构化查询语言)的基础知识。SQL是用于操作数据库的标准语言。我们将学习如何使用INSERT命令创建数据,使用SELECT命令检索数据,以及如何更新和删除数据。

概述

我们将通过一个示例文件来实践SQL命令,并使用DB Browser for SQLite工具来观察这些命令的执行效果。这有助于我们理解如何创建表、插入数据以及处理重复数据等问题。

创建数据库与表

首先,我们需要在DB Browser中创建一个新的数据库。以下是具体步骤:

  1. 打开DB Browser,选择“新建数据库”。
  2. 为数据库命名并保存。
  3. 系统可能会提示创建新表,但我们可以直接执行SQL语句来创建。

接下来,我们从示例文件中获取创建表的SQL语句。例如:

CREATE TABLE users (name TEXT, email TEXT);

在DB Browser的“执行SQL”标签页中,粘贴并执行上述语句。执行成功后,你可以在“数据库结构”标签页中看到一个名为users的表,它包含nameemail两个文本类型的字段。

插入数据

创建表之后,表中还没有任何数据。我们可以使用INSERT命令来添加数据。

从示例文件中获取插入数据的SQL语句,例如:

INSERT INTO users VALUES ('Ted', 'ted@example.com');

回到DB Browser的“执行SQL”标签页,执行这条语句。执行后,你可以通过“浏览数据”标签页查看已插入的数据。

执行多条SQL语句

你可以一次性执行多条SQL语句。从示例文件中复制剩余的所有INSERT语句,粘贴到DB Browser中。

需要注意的是,当执行多条语句时,每条语句必须用分号(;)分隔。否则,执行可能会出错。

以下是示例:

INSERT INTO users VALUES ('Alice', 'alice@example.com');
INSERT INTO users VALUES ('Bob', 'bob@example.com');
INSERT INTO users VALUES ('Ted', 'ted@example.com'); -- 注意:这条数据与之前插入的重复

执行这些语句后,系统可能会提示操作成功。但值得注意的是,如果表结构没有设置唯一性约束,数据库是允许插入重复数据的。正如示例中所示,名为“Ted”的记录被插入了两次。

总结

本节课我们一起学习了在DB Browser中使用SQL的基础操作。我们实践了如何创建数据库和表,如何使用INSERT命令添加数据,以及如何执行多条SQL语句。同时,我们也了解到,在没有约束的情况下,数据库默认允许重复数据的存在。这个工具对于练习创建表、查询数据等操作非常有帮助。

39:基础SQL语句 📊

在本节课中,我们将学习一些基础的SQL语句,用于操作数据库。SQL是用于管理和查询关系型数据库的标准语言。掌握这些基础语句是进行数据存储和检索的第一步。

创建表

上一节我们介绍了数据库的概念,本节中我们来看看如何创建表来存储数据。使用CREATE TABLE语句可以定义一个新表及其结构。

以下是创建表的基本语法:

CREATE TABLE table_name (
    column1_name column1_type,
    column2_name column2_type,
    ...
);
  • CREATE TABLE是SQL关键字。
  • table_name是你为表起的名字。
  • 括号内定义了表的列(字段),包括列名和数据类型(如TEXT,类似于Python中的字符串)。

例如,创建一个名为users的表,包含nameemail两个文本字段:

CREATE TABLE users (
    name TEXT,
    email TEXT
);

插入数据

表创建好后,下一步就是向其中添加数据。我们使用INSERT INTO语句来完成这个操作。

以下是插入数据的基本语法:

INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
  • INSERT INTO是SQL关键字。
  • 指定表名和要插入数据的列名列表。
  • VALUES关键字后跟着与列名顺序对应的值列表。

例如,向users表插入一条记录:

INSERT INTO users (name, email)
VALUES ('Alice', 'alice@example.com');

删除数据

有时我们需要从表中移除数据,这时可以使用DELETE FROM语句。

以下是删除数据的基本语法:

DELETE FROM table_name
WHERE condition;
  • DELETE FROM是SQL关键字。
  • 指定要删除数据的表名。
  • WHERE子句是可选的,用于指定删除哪些行的条件。如果不加WHERE子句,将删除表中的所有数据。

例如,从users表中删除邮箱为alice@example.com的用户:

DELETE FROM users
WHERE email = 'alice@example.com';

注意:SQL中使用单个等号=进行比较判断,而不是Python中的双等号==

更新数据

如果数据需要修改,我们使用UPDATE语句来更新表中已有的记录。

以下是更新数据的基本语法:

UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;
  • UPDATE是SQL关键字。
  • 指定要更新的表名。
  • SET关键字后指定要修改的列及其新值。
  • WHERE子句同样是可选的,用于限定要更新哪些行。

例如,将用户Alice的邮箱更新为新地址:

UPDATE users
SET email = 'alice_new@example.com'
WHERE name = 'Alice';

查询数据

从数据库中检索数据是最常见的操作,我们使用SELECT语句来实现。

以下是查询数据的基本语法:

SELECT column1, column2, ...
FROM table_name
WHERE condition;
  • SELECT是SQL关键字。
  • 指定要检索的列名,使用星号*可以代表所有列。
  • FROM指定要从哪个表查询。
  • WHERE子句用于过滤结果,只返回满足条件的行。

以下是几个查询示例:

  1. 查询users表中的所有数据:

    SELECT * FROM users;
    
  2. 只查询users表中的name列:

    SELECT name FROM users;
    
  3. 查询users表中名为Alice的用户的所有信息:

    SELECT * FROM users
    WHERE name = 'Alice';
    

总结

本节课中我们一起学习了SQL的五个基础语句:CREATE TABLE(创建表)、INSERT INTO(插入数据)、DELETE FROM(删除数据)、UPDATE(更新数据)和SELECT(查询数据)。这些是操作关系型数据库的核心命令,通过组合使用它们,你可以有效地管理和利用数据库中的数据。

40:Python执行SQL语句教程 🐍

在本节课中,我们将学习如何在Python程序中执行SQL语句,与数据库进行交互。我们将介绍建立连接、创建游标、执行SQL命令以及提交更改和关闭连接的基本步骤。

建立连接与游标 🔗

上一节我们介绍了SQL的基本概念,本节中我们来看看如何在Python中实际操作数据库。首先,我们需要进行一些必要的设置。

要在Python中使用SQL,必须导入一个知道如何与数据库交互的模块。我们使用import sqlite3

接着,需要创建一个到数据库的连接。通常,数据库可能位于另一台计算机上。本教程中,我们将在同一台计算机上操作,但连接机制同样适用于远程数据库。连接负责处理通信。

我们使用sqlite3.connect()函数,并传入数据库名称。该函数返回一个连接对象,我们将其保存起来。示例中我们将其命名为conn

然后,我们创建一个游标对象。可以将游标理解为类似文件句柄的东西。文件句柄知道如何读取文件,而游标知道如何执行SQL命令并处理结果。

我们通过connection.cursor()来创建并返回游标对象。示例中我们将其命名为cr

以下是建立连接和创建游标的代码示例:

import sqlite3
conn = sqlite3.connect('my_database.db')
cr = conn.cursor()

执行SQL命令 ⚙️

在成功建立连接和游标后,就可以实际执行SQL语句了。

使用游标对象的execute()方法。你需要在一个字符串中指定要运行的SQL语句。例如,我们可以执行DROP TABLE IF EXISTS语句,这是SQL中的关键字,用于在表存在时删除它。后面跟上表名。

如果该表存在,它将被删除。当你已经有一个需要保留的数据库时,通常不希望这样做。但如果你是从文件开始创建一切,可能希望先删除旧表再重新创建。

以下是执行删除表语句的示例:

cr.execute("DROP TABLE IF EXISTS my_table")

提交更改与关闭连接 💾

执行SQL命令后,如果对数据库进行了修改,就需要提交这些更改。

SELECT语句只是获取数据,不会改变数据库中的任何内容,因此不需要提交。但如果你执行了删除数据、更新数据或添加数据的操作,就需要使用连接对象进行提交。

提交使用connection.commit()方法。

最后,需要关闭连接。这会释放连接资源,以便其他人可以使用。因为通常许多不同的人会同时使用同一个数据库,所以可能存在多个到它的连接。

以下是提交更改和关闭连接的代码示例:

conn.commit()
conn.close()

总结 📝

本节课中我们一起学习了使用Python执行SQL语句的基础知识。我们了解了从导入模块、建立数据库连接、创建游标,到执行SQL命令、提交更改,最后关闭连接的完整流程。记住,对于修改数据的操作,提交是关键步骤;操作完成后,及时关闭连接是良好的实践。

41:SELECT进阶关键字

在本节课中,我们将学习SQL SELECT语句中几个非常实用的进阶关键字。这些关键字能帮助我们进行数据统计、排序和限制结果数量,从而更高效地从数据库中获取所需信息。

聚合函数:COUNT与MAX

上一节我们介绍了基础的SELECT查询,本节中我们来看看如何对数据进行聚合计算。SQL提供了聚合函数,可以对一组值进行计算并返回单个值。

以下是两个常用的聚合函数及其用法:

  • COUNT:此函数用于统计某个字段中非空值的数量。其基本语法为 COUNT(字段名)。例如,COUNT(user_id) 会返回user_id字段中记录的总数。
  • MAX:此函数用于找出某个字段中的最大值。其基本语法为 MAX(字段名)。例如,MAX(age) 会返回age字段中最大的年龄数值。

结果排序:ORDER BY

获取数据后,我们经常需要对其进行排序。ORDER BY子句允许我们根据一个或多个字段对结果集进行排序。

以下是ORDER BY的两种排序方式:

  • 升序排序:使用 ORDER BY 字段名。默认情况下,排序是升序的(从A到Z,从小到大)。
  • 降序排序:使用 ORDER BY 字段名 DESC。关键字DESC表示降序排列(从Z到A,从大到小)。

限制结果数量:LIMIT

有时,我们可能只需要查看结果集中的前几条记录,而不是全部数据。这时可以使用LIMIT子句。

  • 限制返回条数:使用 LIMIT n。其中n是一个数字,例如5。此语句将只返回查询结果中的前n条记录。

本节课中我们一起学习了SQL SELECT语句的几个进阶关键字:用于计数的COUNT、用于查找最大值的MAX、用于排序的ORDER BY(包括升序和降序DESC),以及用于限制返回记录数量的LIMIT。掌握这些关键字能让你更精准、更高效地查询和分析数据库中的数据。

42:使用Python查询数据 📊

在本节课中,我们将学习如何使用Python编程语言来连接和查询SQLite数据库。我们将看到如何从数据库中获取数据,并在Python环境中处理和显示这些数据。


上一节我们介绍了如何使用SQL命令手动操作数据库。本节中我们来看看如何通过Python代码自动化这一过程。

首先,我们需要确保数据库已经按照讲义中的步骤创建好。具体来说,我们创建了一个名为 users 的表,它包含两个文本类型的字段:nameemail。我们向表中插入了一些用户数据,随后删除了邮箱为“TEed”的用户,并将邮箱为指定值的用户姓名更新为“Charles”。

在开始编写Python代码之前,我们可以使用DB Browser这样的图形化工具来直观地查看数据库的当前状态。这有助于我们确认数据是否已按预期被修改。

以下是使用DB Browser查看数据的步骤:

  • 打开数据库文件。
  • 导航到“浏览数据”选项卡。
  • 确认 users 表中已没有名为“Ted”的记录,并且“Ch”已被更新为“Charles”。

通过这种方式,我们可以清晰地看到数据库中实际存储的内容。


现在,让我们转向Python,看看如何通过代码实现同样的数据查询功能。

以下是通过Python连接数据库并查询数据的完整代码示例:

import sqlite3

# 1. 创建到数据库的连接
connection = sqlite3.connect('/users/barbara/desktop/your_database_file.db')

# 2. 创建一个游标对象
cursor = connection.cursor()

# 3. 使用游标执行SQL查询语句
cursor.execute("SELECT * FROM users")

# 4. 遍历查询结果
for row in cursor:
    print(row)

# 5. 关闭游标和连接,释放资源
cursor.close()
connection.close()

让我们逐步解析这段代码:

  1. 导入模块:首先,我们导入Python内置的 sqlite3 库,它提供了操作SQLite数据库的所有必要功能。
  2. 建立连接:使用 sqlite3.connect() 函数创建与数据库文件的连接。你需要将路径替换为你自己的数据库文件位置。
  3. 创建游标:从连接对象创建一个游标。游标是用于执行SQL命令和获取结果的主要工具。
  4. 执行查询:使用游标的 .execute() 方法运行SQL语句。这里我们执行 SELECT * FROM users 来获取 users 表中的所有行。
  5. 处理结果:游标对象本身可以被迭代。在 for 循环中,每次迭代会获取结果集中的下一行数据(以元组形式),然后我们将其打印出来。
  6. 关闭资源:操作完成后,关闭游标和数据库连接是一个好习惯,这可以释放系统资源。

运行这段代码后,你将在Python控制台中看到与DB Browser中完全一致的数据列表。这验证了我们的Python代码成功地与数据库进行了交互并获取了数据。


本节课中我们一起学习了如何使用Python的 sqlite3 模块来连接SQLite数据库、执行SQL查询语句以及遍历和处理返回的结果。通过将数据库操作集成到Python脚本中,我们可以更灵活、自动化地管理和分析数据,这是将创意编程与数据持久化存储相结合的关键一步。

43:Python实现SQL操作 🐍💾

在本节课中,我们将学习如何使用Python来执行SQL数据库操作。我们将涵盖建立连接、创建表、插入数据以及查询数据等核心步骤,并介绍如何安全地执行这些操作。


上一节我们介绍了数据库的基本概念,本节中我们来看看如何在Python中具体执行SQL语句。我们将使用Python内置的sqlite3模块来操作一个SQLite数据库。

首先,我们需要导入必要的模块并建立与数据库的连接。如果指定的数据库文件不存在,系统会自动创建它。

import sqlite3

# 建立数据库连接。如果‘mydatabase.db’文件不存在,它将被创建。
conn = sqlite3.connect('mydatabase.db')

连接建立后,我们需要创建一个游标对象。游标是执行SQL命令和获取结果的主要接口。

# 从连接创建游标
cur = conn.cursor()

有了游标,我们就可以开始执行SQL语句了。以下是一个完整的操作示例,它展示了从创建表到查询数据的全过程。

以下是使用游标执行SQL语句的步骤:

  1. 删除已存在的表(可选):如果你想重新开始,可以先删除可能已存在的表。

    cur.execute('DROP TABLE IF EXISTS tracks')
    
  2. 创建新表:定义一个具有特定字段的新表。

    cur.execute('''
        CREATE TABLE tracks (
            title TEXT,
            plays INTEGER
        )
    ''')
    
  3. 插入数据:向表中插入数据。这里使用参数化查询(?占位符)来防止SQL注入攻击,这是一种安全的最佳实践。

    cur.execute('INSERT INTO tracks (title, plays) VALUES (?, ?)', ('My Song', 100))
    
  4. 提交更改:对数据库的修改(如插入、更新、删除)必须提交才会永久保存。

    conn.commit()
    
  5. 查询数据:执行SELECT语句来检索数据。游标本身可以作为一个迭代器来遍历结果。

    cur.execute('SELECT title, plays FROM tracks')
    for row in cur:
        print(row)
    
  6. 关闭连接:所有操作完成后,应关闭游标和数据库连接以释放资源。

    cur.close()
    conn.close()
    


本节课中我们一起学习了如何使用Python的sqlite3模块执行基本的SQL操作。关键步骤包括:建立连接、创建游标、执行SQL语句(特别是使用参数化查询以确保安全)、提交事务以及最后关闭连接。通过将Python与SQL结合,你可以灵活地以编程方式管理和操作数据。

44:fetchone与fetchall应用 🐍

在本节课中,我们将学习如何使用Python数据库游标对象的 fetchone()fetchall() 方法来处理SQL查询结果。我们将了解它们各自的适用场景以及如何从返回的元组中提取所需的数据。

上一节我们介绍了使用游标作为迭代器来遍历查询结果。本节中我们来看看另外两种处理结果的方式。

使用 fetchone() 获取单个结果

当你执行的查询只返回一个值(例如使用 COUNT 聚合函数)时,fetchone() 方法非常有用。它从结果集中获取下一行,并以元组的形式返回。

out = cursor.fetchone()
print(out)

执行上述代码会打印一个元组,例如 (4,)。要获取实际的计数值,你需要访问该元组的第一个元素。

out = cursor.fetchone()
print(out[0])  # 这将只打印数字,例如 4

使用 fetchall() 获取所有结果

当你需要获取查询返回的所有行时,可以使用 fetchall() 方法。它将所有结果作为一个由元组组成的列表返回。

以下是使用 fetchall() 的步骤:

  1. 执行一个返回多行的查询(例如 SELECT name FROM users)。
  2. 使用 fetchall() 获取所有结果。
  3. 遍历结果列表进行处理。
# 获取所有结果,存储为元组列表
name_tuple_list = cursor.fetchall()

# 遍历列表
for name_tuple in name_tuple_list:
    # 打印每个元组的第一个元素(即名字)
    print(name_tuple[0])

通过访问 name_tuple[0],我们打印出的是单纯的名字字符串,而不是包含名字的元组。如果直接打印 name_tuple,则会看到元组形式的结果。

方法对比与总结

本节课中我们一起学习了三种处理SQL查询结果的方法:

  1. 迭代游标:将游标本身作为迭代器,在循环中逐行获取元组。
  2. fetchone():当查询预期只返回单行单列结果(如计数)时使用。它返回一个元组,通常需要访问其第一个元素 [0] 来获取实际值。
  3. fetchall():当需要一次性获取查询的所有结果时使用。它返回一个列表,列表中的每个元素都是代表一行的元组。你可以遍历这个列表进行处理。

记住,无论使用哪种方法,你操作的基本单位都是包含行数据的元组。根据你的具体需求——是处理单个值、逐行处理还是一次性获取所有数据——来选择最合适的方法。

45:课程概述与目标 🎯

在本节课中,我们将要学习第四周的核心内容,包括数据库规范化的应用、键的定义与使用,以及如何使用Matplotlib库创建各种图表。

欢迎来到本课程的最后一周。本周你将学习如何应用数据库规范化。这是一个将数据库中的内容分解为不同实体或类的过程。然后,你需要确保数据库中不存在重复的字符串数据。

你将学习定义三种类型的键:外键、逻辑键和主键。你将能够创建共享键的表,并使用JOIN操作从多个表中选取数据。

此外,你将学习Matplotlib图表库中的主要术语。你将编写代码来创建不同类型的图表或图形,例如折线图、条形图、散点图和饼图。

这些都是非常实用的技能。祝你本周学习顺利。


总结

本节课中我们一起学习了第四周的学习目标。我们了解到,本周的重点是掌握数据库规范化的方法、理解并运用不同的键来关联数据表,以及使用Matplotlib库进行数据可视化,绘制多种类型的图表。这些技能对于构建结构清晰、高效的数据应用至关重要。

46:Twitter爬虫入门 🕷️

在本节课中,我们将学习如何使用数据库来爬取Twitter数据。这个程序会从一个特定的Twitter用户开始,逐步收集其数据,然后找到该用户的五位好友,并对每位新用户重复此过程。你可以想象,这个过程可能会持续很长时间,并且会在用户之间形成许多相互关联。请先阅读本章内容,之后我们将进行更详细的讨论。

概述

我们将构建一个Twitter数据爬虫。其核心思路是:从一个“种子”用户开始,获取其数据,然后找到他的五位好友,再对这五位好友中的每一位重复同样的操作。这个过程会像蜘蛛网一样扩散开来,逐步建立起一个用户关系网络。

爬虫工作原理

上一节我们介绍了项目的整体目标,本节中我们来看看爬虫具体是如何工作的。

程序的核心是一个循环。它从一个初始用户(例如 drchuck)开始,然后执行以下步骤:

以下是爬虫工作的基本步骤:

  1. 检查数据库中是否已存在该用户。如果不存在,则从Twitter API获取该用户的个人信息并存入数据库。
  2. 从该用户的Twitter时间线中获取其最新发布的推文,并存入数据库。
  3. 获取该用户的关注者(好友)列表。
  4. 从关注者列表中,选择尚未在数据库中的前五位用户。
  5. 将这五位新用户加入待处理队列。
  6. 从队列中取出下一个用户,重复上述过程。

这个过程可以用一个简单的伪代码循环来描述:

while len(queue) > 0:
    user = queue.pop(0) # 从队列中取出一个用户
    if user not in database: # 如果用户不在数据库中
        add_user_to_database(user) # 获取并存储用户信息
        add_tweets_to_database(user) # 获取并存储用户推文
    friends = get_friends(user) # 获取用户的好友列表
    new_friends = [f for f in friends if f not in database][:5] # 筛选出前5个新好友
    queue.extend(new_friends) # 将新好友加入队列

数据结构与数据库

为了高效地存储和查询数据,我们需要一个数据库。我们将使用SQLite数据库,并创建三张表。

以下是数据库中的核心表结构:

  • People:存储用户信息。
    • id:主键,自增整数。
    • name:用户的真实姓名。
    • twitter_id:用户在Twitter平台上的唯一ID。
    • retrieved:一个布尔值(0或1),标记该用户的数据是否已被爬取过。
  • Follows:存储用户之间的关注关系。
    • from_id:关注者的ID(对应 People 表中的 id)。
    • to_id:被关注者的ID(对应 People 表中的 id)。
  • Tweets:存储用户发布的推文。
    • id:主键,自增整数。
    • people_id:发布此推文的用户ID(对应 People 表中的 id)。
    • tweet:推文的文本内容。
    • created_at:推文的发布时间。

关键代码逻辑解析

理解了数据结构后,我们来看看程序中的几个关键函数是如何运作的。

1. 添加新用户 (add_user)
这个函数接收一个Twitter用户ID,首先检查该用户是否已在 People 表中。如果不在,则调用Twitter API获取用户信息,并插入一条新记录,同时将 retrieved 字段设为0(表示其好友和推文尚未被爬取)。

2. 获取并存储推文 (add_tweets)
这个函数为一个指定的用户获取其最新的推文。它通过API获取推文列表,然后遍历列表,将每条推文的文本和发布时间插入到 Tweets 表中。

3. 获取并处理好友 (get_friends)
这是爬虫扩散的核心。函数为一个用户获取其关注者列表。对于列表中的每个关注者:

  • 调用 add_user 确保该关注者已在 People 表中。
  • Follows 表中插入一条从当前用户到该关注者的关系记录。
  • 如果该关注者是“新”用户(即其 retrieved 字段为0),则将其加入待处理的“朋友”列表。程序最终会返回这个新用户列表的前5位。

主循环与流程控制

最后,我们来看驱动整个爬虫运行的主循环。程序从一个种子用户开始,将其放入一个待处理列表(friends)。

以下是主循环的控制流程:

  1. friends 列表前端取出一个用户。
  2. 如果该用户的 retrieved 标志为0(表示未处理):
    • 打印处理信息。
    • 调用 add_tweets 获取其推文。
    • 调用 get_friends 获取其好友,并将返回的新好友列表添加到 friends 列表的末尾。
    • 将该用户的 retrieved 标志更新为1(表示已处理)。
  3. 如果该用户的 retrieved 标志已为1,则跳过处理。
  4. 检查 friends 列表是否为空,或已处理的用户数是否达到预设限制(如75人)。如果满足任一条件,则退出循环;否则,回到步骤1。

这个设计确保了爬虫会优先处理“新发现”的用户,并以广度优先的方式在网络中探索。

总结

本节课中我们一起学习了如何构建一个Twitter关系网络爬虫。我们掌握了其核心工作流程:从种子用户出发,获取其推文和好友,然后迭代地处理新发现的好友。我们还设计了相应的数据库结构(PeopleFollowsTweets 表)来存储用户信息、社交关系和内容。通过主循环中的队列机制和状态标志(retrieved),我们实现了爬虫的自动化运行与流程控制。这个项目综合运用了API调用、数据库操作和循环控制,是创造性编程的一个很好的实践。

47:Twitter爬虫代码实现 🕷️

在本节课中,我们将学习如何实现一个Twitter数据爬虫。我们将使用Tweepy库来访问Twitter API,并将获取到的数据存储到SQLite数据库中。整个过程包括设置授权、建立数据库连接、获取用户的朋友列表以及更新数据库记录。


概述

在开始爬取Twitter数据之前,你需要先安装Tweepy库,并获取一个Twitter开发者账户。本教程将指导你完成这些步骤,并展示如何编写代码来获取和存储Twitter用户的朋友信息。


安装与授权设置

在爬取Twitter数据之前,首先需要安装Tweepy库,这是一个用于访问Twitter的API。接着,你需要获取一个Twitter开发者账户。请按照文件中的指示操作,其中一项是获取Bearer Token,你需要将其放入名为twitter_info.py的文件中。

将授权信息分离出来是最佳实践。我们在代码中导入twitter_info文件,然后使用该文件中的信息进行授权,创建Bearer Token,并利用它来创建API对象。

这样做的好处是避免将授权信息硬编码在共享给其他人的文件中,从而提高了安全性。


数据库设置

接下来,我们设置数据库并获取访问权限。代码在与执行文件相同的目录中查找数据库文件,并创建到该文件的连接。我们使用spider.db作为数据库文件名,创建游标对象,并执行CREATE TABLE IF NOT EXISTS语句。

由于我们可能会多次停止和重启代码,因此需要确保每次运行时不会删除表,否则将无法添加新数据。通过使用IF NOT EXISTS,第一次运行时表会被创建,后续运行时则不会改变表结构。


用户输入与数据获取

while True循环中,我们要求用户输入一个Twitter用户名,或者直接按回车键,或者输入“quit”。如果用户输入“quit”,则退出循环。如果用户直接按回车键(输入长度小于1),则从数据库中随机选择一个尚未检索过的用户。

我们使用LIMIT 1来限制查询结果,并通过fetchone()获取一个元组,然后使用索引[0]提取第一个元素。

接着,我们打印出正在为该用户名刷新朋友列表的信息。无论用户是输入了用户名还是我们从数据库中选择了未访问过的用户,都会执行此操作。


获取与更新朋友列表

我们获取该用户的朋友列表,并更新数据库中的记录,标记该用户的朋友列表已被检索。然后,我们循环遍历前五个朋友(或朋友列表的长度,取较小值)。

在循环中,我们获取当前索引对应的朋友信息,并提取其用户名。接着,检查该用户是否已存在于数据库中。如果存在,则更新该用户的出现次数;如果不存在,则将其插入数据库。

完成插入或更新操作后,我们提交更改。


运行示例

让我们运行代码看看会发生什么。程序会要求输入一个用户名,我输入了“DrChuck”(他是《Python for Everyone》电子书的原作者,我将其改编成了交互式版本)。

程序获取了他的五个朋友,然后我输入“quit”退出。接着,我们查看数据库中的数据,发现DrChuck的五个朋友已被记录,且他们的“已检索”状态均为0,表示尚未获取他们的朋友列表。

如果我们再次运行程序,并直接按回车键,程序会从数据库中选择一个未访问过的用户(例如“RavenMaster1”),并尝试获取其五个朋友。这次,我们成功获取了四个新账户和一个已存在的账户。

再次查看数据库,我们可以看到原始的五条记录,以及新增的朋友记录。同时,数据库更新了“RavenMaster1”和“BretSeverance”的“已检索”状态为1,而“PrairieCrt”的出现次数被更新为2,表示有两个用户共享了这个朋友。


总结

本节课我们一起学习了如何实现一个Twitter数据爬虫。我们涵盖了从安装Tweepy库和设置Twitter开发者账户,到编写代码获取用户朋友列表并存储到SQLite数据库的完整流程。通过分离授权信息、使用条件创建表以及处理用户输入,我们构建了一个可以持续运行并扩展的爬虫程序。

48:多表操作代码解析 🕷️

在本节课中,我们将学习如何修改网络爬虫程序,使其能够记录“谁关注了谁”的关系,并将数据存储在两个独立的数据库表中。我们将详细解析实现这一功能的代码。


概述

上一节我们介绍了基本的网络爬虫和数据存储。本节中,我们来看看如何将数据组织到两个关联的表中:一个存储用户信息(people),另一个存储关注关系(follows)。这种设计能更清晰地分离实体和关系,是数据库建模的常见实践。

代码结构与初始化

首先,代码进行了一些必要的导入和初始化设置。这与之前的爬虫类似,包括设置API授权、建立数据库连接和创建游标。

import sqlite3
import tweepy
# ... 其他导入和授权设置

conn = sqlite3.connect('spider.sqlite')
cur = conn.cursor()

为了保证程序可以多次运行而不出错,代码使用了CREATE TABLE IF NOT EXISTS语句。首次运行时创建表,后续运行时则直接使用已存在的表。

以下是创建两个表的SQL语句:

-- 创建用户表
CREATE TABLE IF NOT EXISTS People
    (id INTEGER PRIMARY KEY, name TEXT UNIQUE, retrieved INTEGER)

-- 创建关注关系表
CREATE TABLE IF NOT EXISTS Follows
    (from_id INTEGER, to_id INTEGER, UNIQUE(from_id, to_id))

People表中,id是主键,name必须唯一。在Follows表中,from_idto_id的组合被设置为唯一,这确保了不会记录重复的关注关系。在数据库设计初期定义这些约束(主键、唯一键)非常重要,可以有效防止数据重复。

主循环逻辑

程序的主循环负责处理用户输入并获取数据。逻辑与之前类似,但增加了对多表的操作。

  1. 用户输入:程序提示用户输入一个Twitter用户名,或直接按回车让程序自动选择一个未爬取的用户。
  2. 退出机制:如果用户输入“quit”,则退出循环。
  3. 选择目标用户:如果用户直接按回车,程序会从People表中选择一个retrieved字段为0(即未爬取过其关注列表)的用户。
cur.execute('SELECT id, name FROM People WHERE retrieved = 0 LIMIT 1')
try:
    row = cur.fetchone()
    # ... 处理选中的用户

插入用户与处理关注者

这是代码的核心部分,涉及向两个表中插入数据。

  1. 确保用户存在:无论是用户输入的还是程序自动选择的用户名,程序都首先尝试将其插入People表。这里使用了INSERT OR IGNORE语句。

    INSERT OR IGNORE INTO People (name, retrieved) VALUES ( ?, 0)
    

    这条命令会尝试插入数据,但如果name违反了唯一性约束(即用户已存在),则会忽略此次插入,而不会报错。这对于避免重复数据至关重要。

  2. 获取关注列表:程序通过Twitter API获取目标用户的关注者列表。如果成功,则将该用户的retrieved字段更新为1,表示已爬取。

  3. 处理每个关注者:程序遍历获取到的关注者列表(数量上限为5个或列表长度)。对于每个关注者:

    • 检查该关注者是否已存在于People表中。
    • 如果不存在,则使用INSERT OR IGNORE将其插入People表。
    • 无论新旧,都获取其在People表中的id
    • 将“关注关系”(即from_id(目标用户)关注to_id(该关注者))插入Follows表。这里同样使用了INSERT OR IGNORE来防止重复关系。
    friend_id = cur.lastrowid # 获取新插入用户的ID
    countnew = countnew + 1
    cur.execute('INSERT OR IGNORE INTO Follows (from_id, to_id) VALUES (?, ?)', (id, friend_id))
    

数据提交与程序运行

以下是程序结束前的收尾工作:

  • 打印统计信息:程序会打印本次运行中新添加的用户数量和已存在的用户数量。
  • 提交与关闭:所有数据库操作完成后,必须执行conn.commit()来提交事务,确保数据写入磁盘,然后关闭游标和连接。

让我们尝试运行这个程序。首先输入“DrChuck”作为起始用户。程序会获取他的5个关注者并存入数据库。

退出后,我们可以查询数据库。People表会显示用户信息及其retrieved状态(1表示已爬取其关注者)。Follows表则记录了关注关系,例如,前5条记录显示有5个用户关注了DrChuck(from_id为DrChuck的id)。

再次运行程序,直接按回车,程序会自动选择一个retrieved为0的用户(例如第二个用户)并获取其关注者。查询数据库会发现,People表中增加了新用户,Follows表中也增加了新的关注关系,并且可能存在跨用户的重复关注者,这体现了真实社交网络的复杂性。


总结

本节课中我们一起学习了如何构建一个使用多张数据库表的网络爬虫。关键点包括:

  1. 使用CREATE TABLE IF NOT EXISTSINSERT OR IGNORE来优雅地处理表的创建和重复数据的插入。
  2. 通过定义主键唯一约束来保证数据的完整性和一致性。
  3. 将“用户”实体和“关注”关系分离到不同的表中,使数据模型更清晰、更易于管理和扩展。
  4. 整个程序流程涵盖了从用户输入、API调用、数据校验到多表关联存储的完整过程,是一个实用的数据库应用示例。

49:JOIN语句应用 🧩

在本节课中,我们将学习如何使用SQL的JOIN语句,从数据库的多个表中组合数据。JOIN是处理关系型数据库时一个非常强大的工具,它允许我们将分散在不同表中的数据临时连接起来,以便进行查询和分析。

概述

上一节我们介绍了数据库的基本查询操作。本节中,我们来看看如何通过JOIN语句,将“people”表和“follows”表中的数据关联起来,从而获取更丰富的信息。

代码实现与数据连接

首先,我们需要编写代码来执行一个JOIN查询。以下是实现这一过程的步骤:

  1. 导入必要的模块。
  2. 确定当前文件所在目录,并据此定位数据库文件。
  3. 建立与数据库的连接,并获取一个游标(cursor)来执行SQL语句。
  4. 分别查询“people”表和“follows”表,以了解基础数据。
  5. 执行一个JOIN查询,将两个表的数据按特定条件关联起来。

以下是实现上述步骤的核心代码:

import sqlite3
import os

# 获取当前文件目录并定位数据库
dirname = os.path.dirname(__file__)
database_path = os.path.join(dirname, ‘your_database.db’)

# 连接数据库并获取游标
conn = sqlite3.connect(database_path)
cur = conn.cursor()

# 查询people表的前几条记录
cur.execute(‘SELECT * FROM people LIMIT 5’)
people_data = cur.fetchall()
print(“People Table:”, people_data)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-crt-prog/img/6c4856607fd08533b976e7d62f648bd8_3.png)

# 执行JOIN查询
cur.execute(‘’’
    SELECT *
    FROM follows
    JOIN people
    ON follows.to_id = people.id
    WHERE follows.from_id = 2 AND follows.count = 0
    LIMIT 5
‘’’)
joined_data = cur.fetchall()
print(“Joined Result:”, joined_data)

# 关闭连接
cur.close()
conn.close()

理解JOIN查询结果

运行上述代码后,我们得到了组合后的数据。为了更直观地理解JOIN的工作原理,我们也可以使用DB Browser这样的工具查看。

在DB Browser中查看“follows”表,我们看到WHERE子句follows.from_id = 2筛选出的记录,其to_id为27、28、29。

同时,在“people”表中,ID为27、28、29对应的正是这些用户的信息。

JOIN语句所做的工作,就是根据ON follows.to_id = people.id这个条件,将“follows”表中符合条件的每一行,与“people”表中id匹配的那一行数据横向拼接在一起,形成一个临时的、更宽的结果表。

JOIN的强大之处

数据库设计通常遵循“规范化”原则,将数据拆分到不同的表中以避免冗余。JOIN语句的强大之处在于,它能根据需要,临时地将这些分散的数据重新组合起来。这样,我们就能像操作单个表一样,对关联后的完整数据集进行查询、分析和报告,从而获得深层次的洞察。

总结

本节课中我们一起学习了SQL JOIN语句的应用。我们通过具体代码演示了如何连接两个表,并解释了JOIN操作在数据库“规范化”设计背景下所起的关键作用——它让我们能够灵活地重组数据,以满足复杂的查询需求。

50:数据库设计目标 🎯

在本节课中,我们将探讨数据库设计的基本目标。我们将学习如何避免常见的设计错误,并理解如何构建一个清晰、易于理解的数据模型。


数据库设计是一门融合了技巧与经验的艺术。我们的核心目标是避免那些非常糟糕的设计错误,并构建出清晰、易于理解的结构。性能优化通常是后续步骤。

上一节我们介绍了数据库设计的重要性,本节中我们来看看具体的设计原则。

避免数据冗余

一个关键原则是避免在数据库中重复存储字符串数据。这意味着,对于现实世界中的同一个实体,在数据库里只应保存一份副本。

以下是实现这一目标的方法:

  • 不要将同一字符串数据存入数据库两次。
  • 使用关系(Relationship)和主键(Primary Key)来引用数据。通常,主键是数字(如ID),它关联着对应的字符串数据。
  • 通过ID来引用字符串数据,而不是直接复制它。

这样做的好处是:

  • 易于维护:当需要修改数据时,只需在一个地方更新,无需四处查找和更改所有副本。
  • 便于查询:数据唯一性能让检索和查找操作变得更简单、更高效。


本节课中我们一起学习了数据库设计的核心目标:避免数据冗余。我们了解到,通过使用主键和关系来引用数据,而非直接复制字符串,可以构建出更清晰、更易于维护的数据库结构。记住,现实世界中的一个实体,在数据库中最好只对应一份数据副本。

51:OMDB接口解析 🎬

在本节课中,我们将学习如何使用OMDB API来获取电影数据。我们将从如何获取API密钥开始,逐步讲解如何构建请求URL、解析返回的JSON数据,并最终提取出我们所需的信息。

获取API密钥 🔑

要使用OMDB API,首先需要获取一个API密钥。

以下是获取API密钥的步骤:

  1. 访问OMDB网站(www.omdbapi.com)。
  2. 点击“API Key”链接。
  3. 在表单中填写你的邮箱地址、名、姓以及使用目的(例如,可以填写“用于在线课程”)。
  4. 点击“Submit”提交表单。
  5. 提交后,你将收到一个专属的API密钥。

构建API请求 🌐

上一节我们获得了访问OMDB的“钥匙”,本节中我们来看看如何使用它来请求数据。

使用API的基本方式是访问一个特定的URL。你需要在URL的问号(?)后面添加参数。最基本的参数是你的API密钥。

核心请求URL结构如下:

http://www.omdbapi.com/?apikey=你的API密钥&其他参数

除了apikey,OMDB API还支持许多其他参数来筛选数据。例如,如果你想搜索特定电影,可以使用t(代表title,标题)参数。

以下是几个常用的参数示例:

  • t:按电影标题搜索(例如,&t=Shaft)。
  • y:按年份筛选。
  • i:按IMDb ID搜索。

例如,要搜索电影《Shaft》(2000年版),可以构建如下URL:

http://www.omdbapi.com/?apikey=你的密钥&t=Shaft&y=2000

解析返回的JSON数据 📊

当我们向OMDB发送请求后,它会返回一个JSON格式的数据。JSON是一种易于机器和人类阅读的数据交换格式。

以下是请求《Shaft》电影数据可能返回的JSON示例(经过格式化以便阅读):

{
  "Title": "Shaft",
  "Year": "2000",
  "Rated": "R",
  "Actors": "Samuel L. Jackson, Vanessa Williams, Jeffrey Wright, Christian Bale",
  "Ratings": [
    {
      "Source": "Internet Movie Database",
      "Value": "6.1/10"
    },
    {
      "Source": "Rotten Tomatoes",
      "Value": "69%"
    }
  ]
}

从上面的数据可以看出:

  • 返回的数据本质上是一个字典(在Python中)。
  • 字典中包含Title(标题)、Year(年份)、Rated(分级)等键值对。
  • Actors(演员)键对应的值是一个字符串,里面包含了用逗号分隔的多个演员名字。
  • Ratings(评分)键对应的值是一个列表。列表中的每个元素又是一个字典,包含了评分来源(Source)和分数(Value)。

为了更直观地查看和理解复杂的JSON结构,你可以使用在线JSON格式化工具(如 jsonformatter.org),将原始的JSON数据粘贴进去,它会帮你整理成清晰的树状结构,方便你找到需要提取的数据路径。

总结 📝

本节课中我们一起学习了OMDB API的基本使用方法。我们首先了解了如何获取必要的API密钥,然后学习了如何通过添加参数来构建请求URL以查询特定电影信息。最后,我们解析了API返回的JSON数据格式,认识到它由字典、列表和字符串等多种结构组成,这为我们后续在Python程序中提取“标题”、“演员”或“评分”等具体信息打下了基础。

52:OMDB数据表设计 🎬

在本节课中,我们将学习如何为来自OMDB(互联网电影数据库)的数据设计数据库表。我们将探讨数据库规范化的基本原则,特别是如何处理“多对多”关系,以避免数据冗余并确保数据完整性。


从单一表开始

如果我们处理来自OMDB的数据,并试图弄清楚如何将其放入数据库表中,人们通常从一张表开始。

例如,如果我们想存储电影的标题、年份和演员,可能会设计如下表结构:

  • Title: 字符串
  • Year: 数字或字符串
  • Actors: 字符串(一个用逗号分隔的演员列表)

代码示例:

CREATE TABLE movies_naive (
    title VARCHAR(255),
    year INT,
    actors TEXT
);

识别问题与实体

然而,请注意,上述设计违反了数据库规范化的一个核心原则:我们不希望在数据库中存在重复的字符串。

例如,演员“塞缪尔·L·杰克逊”可能会在表中出现多次,这是不理想的。更重要的是,我们实际上在讨论不同的实体

审视数据时,需要做的一件事是判断数据属于哪种事物或实体。标题和年份都属于电影这个实体。而演员是另一种事物,是,是一个不同的实体。因此,我们需要将其分离出来。

我们希望有一个电影表和一个演员表,以避免重复的字符串数据。


设计分离的表

因此,我们将数据拆分开:

  • 电影表:存储电影数据。
  • 演员表:存储演员数据。

我们需要为每个表分配一个主键。因为可能存在同名演员,也可能存在同名但不同年份的电影。为了标识它们的唯一性,我们可以规定标题和年份的组合必须是唯一的。

公式/概念:

  • 主键 (Primary Key): 表中每条记录的唯一标识符。
  • 实体 (Entity): 数据库中可被唯一标识的对象或概念,如“电影”、“演员”。

处理多对多关系

现在,我们有了电影表和演员表,它们代表了独立的实体。但问题是:如何将电影和演员关联起来?

我们知道,一部电影可以有多位演员,一位演员也可以参演多部电影。这被称为多对多关系

为了处理这种关系,我们创建第三个表。这个表只使用来自电影表和演员表的ID。

代码示例:

CREATE TABLE movie_actor (
    movie_id INT,
    actor_id INT,
    FOREIGN KEY (movie_id) REFERENCES movies(id),
    FOREIGN KEY (actor_id) REFERENCES actors(id)
);

对于一部电影,我们可以在这个关联表中添加多条记录,每条记录对应电影中的一位演员(通过actor_id关联)。这样,我们就实现了数据的规范化。


总结与优势

本节课中,我们一起学习了为OMDB数据设计数据库表的过程。

我们首先从单一表设计开始,然后识别出其中的数据冗余问题。通过区分“电影”和“演员”两个不同的实体,我们将它们拆分到独立的表中,并为每个表设置了主键。最后,为了处理电影和演员之间的“多对多”关系,我们创建了一个使用双方ID的关联表。

这种方式的好处是:

  1. 数据规范化:消除了重复数据(如演员姓名)。
  2. 使用主键:用数字ID高效地代表对象。
  3. 关系清晰:通过关联表明确表达了“多对多”关系。
  4. 查询灵活:后续可以使用SQL的JOIN操作,轻松地将这些分散的数据重新组合在一起进行查询。

通过这样的设计,我们建立了一个结构清晰、高效且易于维护的数据库模型。🎯

53:键类型与最佳实践 🔑

在本节课中,我们将要学习数据库中三种不同类型的键,并了解在数据库设计中使用键的最佳实践。理解这些概念对于构建高效、可维护的数据模型至关重要。

三种键类型

上一节我们介绍了数据库的基本概念,本节中我们来看看数据库中三种核心的键类型。

主键:这是我们一直在使用的一种键。它通常是一个自动递增的整数,被分配给数据库中的每一个实体。在代码中,这通常表示为:

id INTEGER PRIMARY KEY AUTOINCREMENT

逻辑键:这是外部世界通常用于查找数据的键。例如,对于一张专辑,你可能会使用它的标题来查找。然而,逻辑键并不适合作为主键,因为可能存在两张标题相同的专辑,这时你可能需要通过年份等其他信息来区分。

外键:这是另一个整数键,它是另一个表中的主键。例如,在专辑表中,artist_id 字段就是一个外键,因为它对应的是艺术家表中的主键。

使用键的最佳实践

了解了键的类型后,以下是关于如何使用键的一些最佳实践建议。

  • 不要使用逻辑键作为主键:应为每个表创建一个独立的、自动递增的主键。因为逻辑键可能会发生变化,例如,用户的用户名可能会随着时间而改变。
  • 优先使用整数键建立关系:基于字符串字段进行匹配和关联的效率,通常低于使用整数键。因此,最佳实践是创建整数类型的主键,并使其与逻辑键区分开来。

总结

本节课中我们一起学习了数据库中的三种键类型:主键、逻辑键和外键。我们明确了最佳实践是创建独立的整数主键,而不是依赖可能变化的逻辑键。这有助于确保数据库的稳定性、高效性和易于维护性。

54:Matplotlib基础 📊

在本节课中,我们将要学习Python中一个强大的绘图库——Matplotlib。我们将了解它是什么,以及如何使用它来创建一些常见的图表。

什么是Matplotlib? 🤔

Matplotlib是一个Python绘图库,可用于创建多种常见图表。它非常易于使用,可以轻松制作简单的图表。

上一节我们介绍了Matplotlib的基本概念,本节中我们来看看它能创建哪些类型的图表。

以下是Matplotlib可以轻松创建的一些图表类型:

  • 条形图
  • 饼图
  • 散点图
  • 折线图

接下来,我们将向您展示如何使用Matplotlib来创建这些图表。

55:数据可视化意义 📊

在本节课中,我们将要学习数据可视化的核心目的与意义。通过一个经典的历史案例,我们将理解为何将数据转化为图表是揭示隐藏信息、发现规律和验证假设的强大工具。


我们绘制数据图,是为了理解那些难以通过原始数据直接观察到的关系和模式。

如果你只有两个数据点,你不需要绘制图表。但当你拥有大量数据时,可视化会变得非常有用。

以下是数据可视化的一个经典历史案例:

  • 背景:1854年,伦敦爆发了一场霍乱疫情。
  • 行动:一位名叫约翰·斯诺的医生开始绘制病例地图。
  • 可视化:地图上的每个病例都用一条短线标记。
  • 发现:从地图上可以清晰地看到,病例异常密集地聚集在某一个水泵周围,人们通常从那里取水。
  • 异常值分析:斯诺医生也发现了一些离群病例(即不居住在该区域却感染霍乱的人)。经过调查,他发现这些人因为喜欢该水泵的水,所以即使不住在附近,也会让人把水送过去。
  • 反面证据:同时,该区域也有一些居民没有感染霍乱。调查显示,这些人通常在拥有独立水源的地方工作,因此不使用这个可疑的水泵。
  • 结论:这些通过地图可视化呈现的数据,帮助人们最终确定霍乱是通过受污染的水源传播的。

本节课中我们一起学习了数据可视化的根本意义。它不仅仅是绘制漂亮的图表,更是一种强大的分析工具,能够将抽象的数字转化为直观的空间关系,帮助我们识别模式、调查异常,并最终推导出深刻的结论,正如约翰·斯诺医生通过地图破解霍乱传播途径一样。

56:核心术语解析 📊

在本节课中,我们将学习 Matplotlib 库中的核心术语和概念。理解这些术语是创建和定制图表的基础。

概述

Matplotlib 是一个用于创建静态、动态和交互式可视化的 Python 库。要有效地使用它,首先需要理解其核心组件,如图形(Figure)坐标轴(Axes)子图(Subplots)。本节将逐一解析这些关键术语。

图形(Figure)与坐标轴(Axes)

上一节我们介绍了编程的基本概念,本节中我们来看看 Matplotlib 的核心结构。

图形(Figure) 是绘制图表的最顶层容器。你可以把它想象成一个画布或窗口,可以在上面放置一个或多个坐标轴(Axes)绘图(Plots)。图形可以有标题。

在图形内部,你可以有绘图(Plots),并且可以拥有多个绘图。这些绘图是可以通过坐标指定点的区域。你会有一个坐标轴(Axes)对象。

你可以参考这个展示图形解剖结构的 URL。在一个图形中,你可以有 X 轴和 Y 轴。你可以为这些轴设置 Y 轴标签和 X 轴标签。你可以有主刻度(Major Ticks)次刻度(Minor Ticks)。总之,有很多不同的元素。

以下是图形中可以包含的主要元素列表:

  • 图形标题(Figure Title):整个图形的标题。
  • 图例(Legend):解释图表中颜色(例如这里的蓝色和橙色)或线型所代表含义的说明框。
  • 线图(Line Plot):由线段连接数据点形成的图表。
  • 标记(Markers):这些是图表上显示特定数据点的小图标,它们可以是不同的形状(如圆形、方形、三角形)。

因此,在使用图表时,需要考虑大量的词汇。

子图(Subplots)

在单个图形中创建多个图表时,就会用到子图(Subplots)。你可以有一个主绘图区域,也可以将其划分为多个子图区域。

当你创建子图时,会使用一个有趣的约定。例如,代码 figure.add_subplot(1, 2, 1) 表示:1行,2列,第一个图表。所以,这行代码创建了一个1行2列布局中的第一个图表。然后,你可以添加另一个子图:add_subplot(1, 2, 2) 表示1行2列布局中的第二个图表。

在示例中,对于第一个坐标轴(axes1),我们创建了一个垂直条形图。对于第二个坐标轴(axes2),我们创建了一个水平条形图。你可以看到,创建这些图表非常简单。我们只需要准备一些数据,然后调用 bar 函数制作条形图,或调用 barh 函数制作水平条形图即可。入门非常简单。

总结

本节课中我们一起学习了 Matplotlib 的核心术语。我们了解了图形(Figure) 作为顶层容器的作用,坐标轴(Axes) 作为实际的绘图区域,以及如何在单个图形中通过 add_subplot() 方法创建和管理多个子图(Subplots)。我们还简要介绍了图形中的其他元素,如标题、标签、刻度和图例。掌握这些基本概念是使用 Matplotlib 进行有效数据可视化的第一步。

57:绘图实例展示 📊

在本节课中,我们将学习如何使用Python的Matplotlib库快速创建不同类型的图表。我们将通过一个简单的例子,展示如何在一个图形中并排绘制条形图、散点图和折线图。


上一节我们介绍了Matplotlib的基础概念,本节中我们来看看如何实际应用这些知识来创建组合图表。

首先,我们需要准备数据和设置图形。以下是创建图表的基本步骤:

以下是创建组合图表的代码示例:

import matplotlib.pyplot as plt

# 准备数据
names = ['Group A', 'Group B', 'Group C']
values = [110, 100, 90]

# 创建一个图形,设置其大小为9x3(宽x高,单位英寸)
plt.figure(figsize=(9, 3))

# 创建第一个子图(1行,3列,第1个位置)
plt.subplot(1, 3, 1)
plt.bar(names, values)  # 绘制条形图
plt.title('Bar Plot')

# 创建第二个子图(1行,3列,第2个位置)
plt.subplot(1, 3, 2)
plt.scatter(names, values)  # 绘制散点图
plt.title('Scatter Plot')

# 创建第三个子图(1行,3列,第3个位置)
plt.subplot(1, 3, 3)
plt.plot(names, values)  # 绘制折线图
plt.title('Line Plot')

# 为整个图形添加一个总标题
plt.suptitle('Categorical Plotting')

# 显示图形
plt.show()

运行上述代码后,你将看到一个包含三个子图的图形窗口。总标题为“Categorical Plotting”。这三个子图从左到右分别是:

  1. 条形图:使用垂直矩形表示数据。你也可以使用plt.barh()来创建水平条形图。
  2. 散点图:用未连接的点来表示数据。
  3. 折线图:用线或标记点连接数据点,展示趋势。


本节课中我们一起学习了如何使用Matplotlib的subplot功能在一个画布上创建多个图表。我们实践了绘制条形图散点图折线图,这是数据可视化中非常基础和有效的方法。通过这个简单的例子,你可以看到用几行代码就能生成清晰、多样的图表,这对于快速探索和展示数据非常有帮助。

58:图表增强技巧 📈

在本节课中,我们将学习如何为Matplotlib图表添加图例、自定义线条颜色和标记,以增强图表的可读性和美观性。

添加图例

上一节我们介绍了基础的图表绘制,本节中我们来看看如何为包含多条数据线的图表添加图例。图例能帮助观众区分图表中不同线条所代表的数据系列。

如果你有一个折线图或普通图表,其中包含多条数据线,例如Y轴数据对应年份,而D1、D2等代表不同的数据系列,你可以为每条线添加标签,然后调用图例功能。

以下是具体步骤:

  • 使用 axis.plot(Y, D1, label='group 1') 绘制第一条线并为其设置标签。
  • 使用 axis.plot(Y, D2, label='group 2') 绘制第二条线并设置另一个标签。
  • 最后,调用 axis.legend() 方法,图表上就会自动添加一个显示这些标签的图例框。

自定义线条颜色与样式

除了图例,我们还可以精细控制线条的外观,例如颜色和样式。Matplotlib提供了一套简洁的符号语言来实现这一点。

你可以通过在 plot 函数中使用特定的颜色和线条样式代码来定义线条外观。例如:

  • ‘b--‘ 表示蓝色虚线。
  • ‘g--‘ 表示绿色虚线。

使用数据点标记

为了让数据点的位置更加清晰可见,我们可以在线条上添加标记。标记符号同样使用简写代码。

以下是标记的示例:

  • ‘bo‘ 表示使用蓝色圆圈作为数据点标记。
  • ‘g^‘ 表示使用绿色上三角作为数据点标记。

你可以访问Matplotlib官方文档查看所有可用的标记类型列表,从而选择最适合的样式来突出显示你的数据点。

本节课中我们一起学习了如何通过添加图例、自定义线条颜色样式以及使用数据点标记来增强图表的表现力。这些技巧能让你的数据可视化结果更加专业和清晰。

59:饼图绘制实例 📊

在本节课中,我们将学习如何使用Python的Matplotlib库来创建一个饼图。我们将通过一个具体的例子,了解如何定义数据、设置样式并最终生成一个带有百分比标签和阴影效果的饼图。

概述

饼图是一种常用的数据可视化方式,它通过扇形的大小来展示各部分数据在整体中的占比。我们将使用Matplotlib库中的pie函数来实现这一功能。

数据准备

首先,我们需要准备绘制饼图所需的数据。这包括标签、对应的数值、颜色以及我们想要“突出”显示的扇区。

以下是定义这些数据的代码:

import matplotlib.pyplot as plt

# 定义数据
labels = ['Python', 'C++', 'Ruby', 'Java']
values = [40, 25, 20, 15]
colors = ['gold', 'lightcoral', 'lightskyblue', 'lightgreen']
explode = (0.1, 0, 0, 0)  # 突出显示第一个扇区(Python)
  • labels:一个列表,包含每个扇区的名称。
  • values:一个列表,包含每个扇区对应的数值。
  • colors:一个列表,定义每个扇区的颜色。
  • explode:一个元组,用于指定哪些扇区需要从饼图中心“突出”显示。0.1表示将第一个扇区向外移动一定距离。

绘制饼图

准备好数据后,我们就可以调用plt.pie()函数来绘制饼图了。这个函数提供了丰富的参数来控制饼图的外观。

以下是绘制饼图的代码:

# 绘制饼图
plt.pie(values, labels=labels, colors=colors, explode=explode,
        autopct='%1.1f%%', shadow=True, startangle=140)

# 确保饼图是正圆形
plt.axis('equal')

# 显示图形
plt.show()
  • autopct='%1.1f%%':这个参数用于在扇区内显示百分比。%1.1f%%表示格式化为保留一位小数的百分比。Matplotlib会自动根据values计算每个扇区的占比。
  • shadow=True:为饼图添加阴影效果,使其看起来更有立体感。
  • startangle=140:设置饼图的起始角度为140度,这会影响第一个扇区(Python)的起始位置。

结果展示

运行上述代码后,将生成一个饼图。其中,“Python”扇区被突出显示,每个扇区都标有名称和精确的百分比,整个图形带有阴影效果,并且是一个标准的正圆形。

总结

本节课我们一起学习了如何使用Matplotlib库创建饼图。我们掌握了定义数据标签、数值、颜色和突出效果的方法,并学会了使用plt.pie()函数的关键参数,如autopct来显示百分比、shadow来添加阴影以及axis('equal')来确保图形为正圆形。这是一个简单而完整的饼图绘制流程。

60:密歇根与佐治亚数据可视化 📊

在本节课中,我们将学习如何从JSON格式的数据文件中读取信息,并利用Python的matplotlib库,将两个不同地区(密歇根州和佐治亚州)的数据绘制在同一张折线图中进行可视化对比。我们将涵盖数据读取、列表处理、图表创建和美化等核心步骤。


概述与数据背景

我的一项工作是处理大学先修课程(AP)计算机科学的数据。通常,我会编写程序来分析多个电子表格中的数据,将它们整合到一个表格中,然后进行绘图。

这里有一个包含JSON数据的示例文件。数据记录了每个州、每一年参加AP计算机科学A考试的人数、该州的人口以及考试人数与人口的比例等信息。我们的目标是读取这些JSON数据,将其整理到列表中,并绘制不同州的数据变化趋势。

例如,我曾在佐治亚州工作很长时间,而我最初来自密歇根州,现在又回到了密歇根。因此,我想了解在特定时间段内,AP计算机科学A考试的参考数据在这两个州是如何变化的。


实现步骤详解

上一节我们介绍了项目的背景和目标,本节中我们来看看具体的实现步骤。整个过程可以分为数据读取、数据处理和图表绘制三个主要部分。

1. 导入必要的库

首先,我们需要导入处理JSON文件和绘制图表所需的Python库。

import json
import matplotlib.pyplot as plt

2. 从JSON文件读取数据

接下来,我们从文件中读取JSON格式的数据,并将其加载到一个Python字典中,以便后续处理。

with open(‘ap_data.json‘, ‘r‘) as file:
    data_dict = json.load(file)

3. 提取特定州的数据

当数据存储在字典中后,我们需要分别提取密歇根州和佐治亚州的数据。以下是提取数据并将其存入列表的方法。

  • 提取密歇根州数据:遍历字典中密歇根州的数据项,将每年的考试人数添加到一个列表中。
  • 提取佐治亚州数据:用同样的方法,遍历字典中佐治亚州的数据项,生成另一个列表。
michigan_exams = []
georgia_exams = []

for year_data in data_dict[‘Michigan‘]:
    michigan_exams.append(year_data[‘num_exams‘])

for year_data in data_dict[‘Georgia‘]:
    georgia_exams.append(year_data[‘num_exams‘])

4. 创建并配置图表

现在我们已经有了两个列表的数据,可以开始绘制图表了。以下是配置图表的各个步骤。

  • 创建图形和坐标轴:使用 plt.subplots() 初始化画布。
  • 绘制折线图:用蓝色线条绘制密歇根州的数据,用绿色线条绘制佐治亚州的数据。
  • 添加图例:为两条线添加图例,标明哪条线代表哪个州。
  • 设置标签和标题:为X轴(年份)、Y轴(考试人数)设置标签,并为整个图表添加一个标题。
  • 添加网格:启用网格线,使数据点更容易观察。
  • 保存和显示图表:将图表保存为PNG格式的图片文件,并在屏幕上显示出来。
fig, ax = plt.subplots()

ax.plot(years, michigan_exams, ‘b-‘, label=‘Michigan‘)
ax.plot(years, georgia_exams, ‘g-‘, label=‘Georgia‘)

ax.legend()
ax.set_xlabel(‘Year‘)
ax.set_ylabel(‘Number of Exams‘)
ax.set_title(‘AP CS A Exam Participation: MI vs GA‘)
ax.grid(True)

plt.savefig(‘mi_ga_comparison.png‘)
plt.show()

运行结果

当我们运行完整的程序后,会生成一个直观的折线图。图表清晰地展示了在选定年份内,密歇根州和佐治亚州AP计算机科学A考试参考人数的变化趋势。通过图例、坐标轴标签和网格线,我们可以轻松地对两者进行比较。

保存的PNG图像文件可以方便地用于提交作业、打印或包含在报告中。


总结

本节课中我们一起学习了如何完成一个完整的数据可视化项目。我们从JSON文件中读取了结构化的数据,通过遍历字典提取出特定州的信息并存入列表,最后使用matplotlib库创建了一张包含两条折线、图例、标签、标题和网格的对比图表,并将结果保存为图像文件。这个过程展示了数据处理与可视化编程的基本工作流程。

61:并列条形图绘制 📊

在本节课中,我们将学习如何使用Python的Matplotlib库绘制并列条形图。并列条形图是一种将多组数据并排展示的图表,便于直观比较不同数据集在同一类别下的数值差异。我们将通过一个具体的例子,展示如何将密歇根州和佐治亚州的AP考试数据并排绘制。


概述

并列条形图适用于比较两个或多个数据集在同一分类下的数值。例如,比较不同州份每年的AP考试通过率。为了实现这种图表,我们需要对数据进行额外处理,包括计算条形位置和宽度。

数据准备

首先,我们需要导入必要的库并加载数据。数据通常以JSON格式存储,我们可以将其读取为字典,然后提取所需州份的数据列表。

import matplotlib.pyplot as plt
import numpy as np
import json

# 假设数据已从JSON文件加载到字典 `data_dict` 中
michigan_data = data_dict['Michigan']  # 密歇根州数据列表
georgia_data = data_dict['Georgia']    # 佐治亚州数据列表

创建图表与坐标轴

接下来,我们创建图形和坐标轴。这是绘制任何Matplotlib图表的标准步骤。

fig, ax = plt.subplots()

计算条形位置与宽度

为了并排显示条形,我们需要计算每个条形的位置和宽度。首先,确定数据点的数量(例如,11年),然后设置条形宽度。使用NumPy的arange函数生成索引位置。

n = len(michigan_data)  # 数据点数量,例如11
bar_width = 0.35        # 条形宽度
indices = np.arange(n)  # 生成位置索引 [0, 1, 2, ..., n-1]

绘制并列条形

现在,我们可以绘制并列条形。第一个条形(密歇根州)使用indices作为x轴位置。第二个条形(佐治亚州)需要向右移动一个条形宽度的距离,因此使用indices + bar_width作为x轴位置。

# 绘制密歇根州数据(蓝色条形)
ax.bar(indices, michigan_data, bar_width, color='blue', label='Michigan')

# 绘制佐治亚州数据(绿色条形),位置向右移动
ax.bar(indices + bar_width, georgia_data, bar_width, color='green', label='Georgia')

设置坐标轴标签

为了使刻度标签位于两组条形的中间,我们需要调整x轴刻度位置。将刻度设置在indices + bar_width / 2处,并设置对应的年份标签。

ax.set_xticks(indices + bar_width / 2)
ax.set_xticklabels(['Year 1', 'Year 2', ..., 'Year 11'])  # 替换为实际年份标签
ax.set_xlabel('Year')
ax.set_ylabel('AP Pass Rate')
ax.legend()

最终效果

完成上述步骤后,图表将清晰展示密歇根州和佐治亚州每年的AP考试通过率。蓝色条形代表密歇根州,绿色条形代表佐治亚州,两者并排排列,便于比较。


总结

本节课中,我们一起学习了如何绘制并列条形图。通过计算条形位置和宽度,我们可以将多组数据并排展示,便于比较。关键步骤包括数据准备、位置计算、条形绘制以及坐标轴标签调整。掌握这些技巧后,你可以轻松创建各种并列条形图,有效展示和比较数据。

posted @ 2026-03-26 13:08  布客飞龙IV  阅读(6)  评论(0)    收藏  举报