杜克大学-Python-Bash-SQL-专项笔记-全-

杜克大学 Python、Bash、SQL 专项笔记(全)

001:Python、Bash与SQL概述 🚀

在本课程中,我们将学习数据工程的基础知识。这是一门令人兴奋的课程,它将引导你从入门到掌握成为一名数据工程师或机器学习工程师所需的全部核心技能。无论你选择哪个领域,这些技能都至关重要。接下来,我们将逐一探讨本系列课程的内容,并解释它们为何对你在数据行业成为专业人士的目标如此重要。

第一门课程:Python基础 🐍

首先,我们从第一门课程开始,它主要涵盖Python语句。

Python之所以重要,是因为你可以在Jupyter笔记本中执行这些语句。这个Jupyter笔记本可以成为一个文档或记录,让你能够逐步构建结构,并与他人分享该结构。

此外,我们还会讨论序列。在处理数据时,你经常需要遍历某种形式的结果,这是我们在第一门课程中会涵盖的内容。

我们也将深入探讨pandas库本身。当你使用pandas将数据移动到CSV文件或将其带回Python时,了解其重要性至关重要。pandas版本与常规Python版本之间的权衡是什么?这有点像西班牙语和葡萄牙语的关系,它们有相似之处,但也存在差异。pandas也是如此,它是一个在处理列式数据时非常有用的语言工具,我们将在课程中更详细地讨论这一点。

最后,在第一门课程中,我们会进入开发环境的话题。我喜欢将开发环境比作烹饪:你使用多种工具,可能有一把小刀、一把大刀,还有一些其他专用工具。开发环境也是如此。在我们的案例中,我们将讨论Vim,它就像一把切蔬菜的小刀。我们也会讨论像Visual Studio和Visual Studio Code这样更大型的工具,它们非常常见,因为当你处理项目结构时,它们能让你更优雅地处理多个文件。

第二门课程:Bash与文件系统管理 💻

现在,让我们谈谈第二门课程。在第二门课程中,我们将学习如何实际使用Bash,特别是来管理文件系统,这通常与Linux操作系统相关。

如今,几乎所有的软件工程项目都将Linux操作系统作为部署目标,因此了解如何操作它非常重要。具体来说,你会经常看到像 ~/.bashrc 这样的东西。那么,.bashrc 到底是做什么的呢?.bashrc 是你配置环境的地方,以便你可以导出一些密钥,或者在其中设置Python虚拟环境。通过编辑你的.bashrc,你可以做很多事情来自动化你的Bash环境。

我们还将深入Bash本身,学习如何编写核心功能,比如函数。如何编写一个Bash函数并使用它?例如,如何使用数组或哈希表?这些数据结构在自动化和编写脚本时非常有用。

我们也会探讨Bash脚本的特性:什么是shebang行?如何使脚本可执行?以及如何实际使用这些脚本进行自动化。

最后,我们将总结文件和数据方法论:如何使用 findlocate 等命令搜索文件系统?为什么要这样做?以及如何利用这一点在处理大数据问题时取得成功。

第三门课程:数据处理与SQL 📊

现在,让我们谈谈这里的第三门课程。第三门课程的理念是帮助你真正开始朝着构建现实世界解决方案的方向前进。

具体来说,你将学习如何处理数据。这意味着,例如,如果你想将文件写入文件系统,该如何操作?在Python中有几种方法可以实现。你如何持久化数据?这是另一个需要记住的重要事项:你可以获取Python数据并将其序列化(pickle)到磁盘,这样数据就会按照Python喜欢的方式被精确保存,并且你可以将其加载回来。

我们还将深入SQL,这对数据科学家、数据工程师和机器学习工程师来说非常重要,因为它是一种专为查询数据而设计的语言。我们将讨论如何在Python中实现它、如何与之交互,以及如何用SQL构建解决方案。

我在数据科学中经常看到的另一个常见需求是,人们需要学习如何从其他网站抓取数据。有时,网站并不是为了方便你提取数据而构建的。我们将在第三门课程中深入探讨这一点,并向你展示如何实际构建抓取工具。

第四门课程:项目实践与部署 🚀

现在,让我们进入第四门课程。到了这个阶段,你应该已经掌握了所有这些基础知识,我们开始将其付诸实践。

具体来说,我们讨论Jupyter对机器学习工程和机器学习运维的重要性。特别是,Jupyter被用于像SageMaker这样的技术。SageMaker是亚马逊的一项机器学习技术,你可以用它构建非常复杂的分布式计算流水线和机器学习预测流水线。其他平台也有类似的技术,例如微软Azure有Azure ML Studio,谷歌平台有Vertex AI。所有这些类型的技术都围绕着使用Jupyter笔记本。

在第四门课程中,我还将介绍如何使用Web命令和工具,以及Python项目结构。当你进入下一个阶段并希望将项目投入生产时,这些内容非常重要。

具体来说,让我们谈谈Python项目结构为何如此重要。通常,你希望为项目搭建一个脚手架或布局。当你以易于测试的方式设置项目时,例如使用持续集成或我称之为“持续改进”的方法,这样做的好处是它能确保你的项目不断变得更好,一旦结构建立起来,它就在持续改进。

我们还讨论如何通过原型解决方案构建命令行工具。命令行工具让你能够轻松接收输入、操作和移动数据,这对于数据工程师来说至关重要,因为它提供了快速原型设计的能力。

最后,我们讨论Web开发。尽管对于数据科学家或数据工程师来说,Web开发似乎不那么重要,但它确实可能成为你工作的关键组成部分,特别是在开发所谓的“微服务”时。因此,我们将深入探讨如何处理这些微服务,以及微服务如何让你能够快速将某些东西部署到云端。


课程总结

在本节课中,我们一起学习了数据工程基础系列课程的全貌。我们从Python基础及其在Jupyter环境中的应用开始,探讨了Bash脚本在Linux系统管理和自动化中的核心作用,深入了SQL数据处理与网络数据抓取的关键技能,最后了解了如何将这些知识整合到实际项目开发、部署及微服务构建中。这套课程为你构建了从基础工具使用到解决实际数据工程问题的完整知识路径。

002:肯尼迪·贝尔曼 👨‍🏫

在本节课中,我们将认识本课程的讲师肯尼迪·罗伯特·贝尔曼,并了解课程的核心内容和目标。

大家好,欢迎来到《数据工程中的Python与Pandas》课程。我是肯尼迪·罗伯特·贝尔曼。

我是一名高级数据工程师、作家和教育工作者。我使用Python管理数据已有数十年经验。

本课程将为你提供开启数据工程职业生涯所需的技能。

我们将涵盖Python项目的正确设置、Python编程基础、介绍强大的Pandas库,并介绍一些流行的开发环境。

我希望你和我一样对深入学习这些内容感到兴奋。让我们开始吧。


上一节我们认识了讲师,本节中我们来看看本课程将要涵盖的具体内容。以下是课程的核心模块:

  1. Python项目设置:学习如何为数据工程项目搭建一个规范、高效的Python开发环境。
  2. Python编程基础:掌握Python语言的核心语法和概念,为数据处理打下坚实基础。
  3. Pandas库介绍:探索专为数据分析设计的强大库Pandas,学习其核心数据结构和操作。
  4. 开发环境:了解并实践几种在数据工程领域流行的集成开发环境(IDE)和工具。

本节课中我们一起学习了课程讲师肯尼迪·贝尔曼的简介,并概述了本课程将涵盖的四个主要技能模块:Python项目设置、Python基础、Pandas库以及开发环境。这些内容是步入数据工程领域的重要第一步。

003:03_01_05_核心概念概述 📚

欢迎来到课程1。本节将介绍课程中需要掌握的一些核心概念。

概述

在本节课中,我们将学习三个核心概念:Pandas与Python的选择、编辑器与终端的区别,以及处理大数据时的工具考量。掌握这些概念将帮助你更高效地进行数据工程工作。


1. Pandas 与 Python 的选择 🤔

初学者常面临一个困惑:何时使用Pandas,何时使用原生Python,甚至何时两者都不适用。本课程将阐明,使用Pandas和原生Python时,编程风格是不同的。

具体来看,这是一个非常简单的Python函数:

def process(x):
    return x + 1

这个函数接收一个参数 x,并返回 x + 1。这是最简单的函数之一。输入1,返回2;输入2,返回3。这类函数与Pandas配合得非常好,因为在Pandas中,你通常不通过循环处理数据,而是应用函数。

你可以将Pandas的DataFrame想象成一个电子表格,例如Google Sheets或Excel。你不是手动逐行输入所有内容,而是选择所有行并执行某种操作,这要高效得多。Pandas的核心思想也是如此:如果你想处理某些数据,不要循环遍历,而是应用一个函数。

因此,在处理Pandas时,请思考一个能应用于整列数据的函数。


当我们讨论原生Python时,最常见的操作方式是使用 for 循环来遍历某个序列。

例如:

for i in range(1, 10):
    # 执行一些操作
    if i == 3:
        # 执行其他操作
        pass

在循环内部,你可能会进行条件判断。掌握循环和条件语句,以及在适当时候跳出循环,是Python编程中最重要的技能之一。

所以,选择哪种工具取决于你面临的具体问题,你需要为正确的工作选择正确的工具。


此外,还有第三种重要场景需要讨论,那就是处理大数据时。

需要注意的一点是,你可能无法使用常规的Python Pandas库,因为Pandas可能需要占用你所处理数据结构内存的5到10倍。因此,你可能需要转向更强大的大数据工具。

例如,Spark就是一个非常常用的大数据处理工具,因为它可以操作大型数据集,并能调用计算和存储系统进行并行工作。如果你开始遇到大数据相关的问题,Pandas可能也不再是合适的工具,你需要了解这类替代方案。


2. 编辑器与终端 🛠️

课程1的第二个核心概念是编辑器与终端的区别,以及为何需要同时掌握两者才能取得成功。

关于编辑器,重要的是要知道它能完成非常复杂的任务,例如建议你应该输入的代码。事实上,我们现在正处于这样一个时代:像GitHub Copilot这样的工具可以帮助你构建解决方案,提示你导入某个库或补全代码。你还可以在编辑器内自动获得代码检查(Linting),这非常有帮助,能告诉你正在编写哪些错误。

因此,在我看来,如果你正在项目中构建某些东西,编辑器是必备工具。为什么不使用最适合这项工作的工具呢?我认为Visual Studio Code这类编辑器是非常棒的工作环境。

更进一步,我们现在看到许多这类工具都可在基于云的环境中使用。例如,GitHub Codespaces就是一个绝佳的平台,你可以在其中实际开发代码,并在Codespaces内部使用Visual Studio Code环境。AWS的Cloud9等工具也允许你这样做。

所以,为正确的工作使用正确的工具是取得成功的关键。


同样,在我看来,你也需要了解终端。

尽管它是一个基于命令行的工具,但其优势在于快速和高效。特别是,Vim很可能存在于你将使用的每一个系统上。

我喜欢这样比喻:当你在厨房做饭时,可能会用大刀处理某些食材,用小刀处理另一些。终端就像一把小刀,非常快速、高效。尤其是在编辑像 .bashrc 这样的文件时,使用Vim或类似工具来编辑某些配置文件或快速构建某些东西几乎是无可替代的。你可能处于一个只有终端的环境,无法使用图形化编辑器。

因此,了解并掌握两者,你将会变得极其高效。


总结

本节课我们一起学习了三个核心概念。首先,我们探讨了根据任务性质在Pandas、原生Python或更强大的大数据工具(如Spark)之间做出选择。其次,我们比较了功能强大的代码编辑器与快速高效的终端环境,并理解了在数据工程工作中同时掌握两者的必要性。希望这些概念能为你后续的学习和实践打下坚实的基础。

004:Python环境配置入门 🐍

在本节课中,我们将学习如何搭建一个Python项目环境。你将掌握使用PIP工具管理Python库,以及创建和使用虚拟环境来隔离项目依赖。


概述

一个良好的开发环境是高效编程的基础。本节将引导你完成Python环境配置的核心步骤,包括包管理工具PIP的使用和虚拟环境的创建与管理。


安装、卸载与更新Python库

上一节我们了解了环境配置的重要性,本节中我们来看看如何使用PIP(Python包安装程序)来管理第三方库。

PIP是Python的包管理器,用于从Python包索引(PyPI)安装、卸载和更新软件包。其基本命令格式如下:

安装库:

pip install package_name

卸载库:

pip uninstall package_name

更新库:

pip install --upgrade package_name

使用Requirements文件

手动记录每个依赖库的版本非常繁琐。为此,我们可以使用requirements.txt文件来统一管理项目依赖。

以下是requirements.txt文件的核心操作:

生成依赖列表:
将当前环境中已安装的包及其版本导出到一个文件中。

pip freeze > requirements.txt

从文件安装依赖:
在新环境中,可以一键安装文件中列出的所有依赖。

pip install -r requirements.txt

创建与管理Python虚拟环境

不同的项目可能需要不同版本的库。为了避免冲突,我们需要为每个项目创建独立的虚拟环境。

以下是创建和使用虚拟环境的步骤:

创建虚拟环境:
使用venv模块创建一个新的隔离环境。myenv是环境文件夹的名称,你可以自定义。

python -m venv myenv

激活虚拟环境:
激活后,终端提示符通常会变化,表示你已进入该虚拟环境。

  • 在Windows上:
    myenv\Scripts\activate
    
  • 在macOS/Linux上:
    source myenv/bin/activate
    

停用虚拟环境:
完成工作后,可以退出当前虚拟环境,回到系统全局的Python环境。

deactivate


总结

本节课中我们一起学习了Python环境配置的核心技能。我们掌握了使用PIP进行库的安装、卸载和更新,学会了通过requirements.txt文件管理项目依赖,并实践了创建、激活和停用Python虚拟环境来保持项目间的独立性。这些是开始任何Python数据工程项目前必须掌握的基础。

005:使用pip安装Python包 📦

在本节课中,我们将学习如何使用pip来管理Python包。我们将涵盖如何列出已安装的包、安装新包(包括特定版本)、卸载包以及升级现有包。

什么是Python包?🤔

Python代码可以通过在命令行输入python进入交互式会话运行,也可以从文件中运行。

当Python代码被保存到文件中时,该文件中的代码可以作为一个独立的程序运行,或者被导入并在另一个Python程序中使用。

当代码被保存到文件中以便重复使用时,它被称为一个模块

当一个模块或一组模块被打包在一起,专门用于分发和安装到多个Python环境中时,它就被称为一个

Python标准库与第三方包

每个Python安装都附带一组被称为Python标准库的包。这些包可以在任何Python程序中使用。

除了标准库,还有一个庞大的第三方包生态系统。这些包为默认安装增添了各种专业功能。这些包的主要存放地是Python包索引,也被称为PyPI。

pip:Python包管理器

pip是Python的包安装程序。它是管理Python包安装的默认工具。默认情况下,它会从Python包索引下载并安装包,但它也可以指向其他位置。从Python 3.4版本开始,pip作为Python默认安装的一部分被安装。

检查Python环境

要查看您环境中使用的Python版本,请打开一个shell并输入带有版本标志的python命令:

python --version

在许多系统上,存在多个Python安装。通常有一个遗留的Python 2副本和一个较新的Python 3版本。可以通过别名访问它们,通常py2python2对应Python 2版本,py3python3对应Python 3版本。如果您的默认Python版本是Python 2,请在本课程的示例中使用python3别名。

使用pip

pip通常可以通过两种方式调用:

  1. 使用Python模块方式调用:python -m pip
  2. 作为一个独立的命令:pip

pip help命令会列出所有可用的命令和通用选项。要查看特定命令的文档,请在help命令后键入命令名称。

在本节课的剩余部分,我们将使用三个命令:listinstalluninstall

列出已安装的包

list命令会列出当前环境中已安装的包及其版本号。

pip list

安装包

使用install命令,后跟一个模块名或模块名列表来安装它们。

以下是安装流行的测试库pytest的示例:

pip install pytest

我们可以看到,pip不仅安装了pytest,还安装了我们没有明确要求的其他包。这些是pytest所依赖的包。pip会自动安装我们请求的包所需的库。

安装特定版本

默认情况下,install命令会安装可用的最新版本。我们可以通过在模块名后使用双等号来安装特定版本。

一个查看有哪些版本可用的技巧是留出版本号空白:

pip install pytest==

这会显示一个错误,但错误信息会报告所有可用的版本。

要安装特定版本,请将版本号直接放在双等号后面:

pip install pytest==6.2.5

我们可以看到指定的版本被安装了。

卸载包

要卸载一个包,请使用uninstall命令,后跟包名。

pip uninstall pytest

请注意,pip卸载了pytest,但没有卸载其依赖项。我们也必须用uninstall命令明确命名它们才能将其移除。

升级包

如果我们希望升级到包的最新版本,我们可以先卸载再重新安装,或者使用install命令的--upgrade选项。

pip install --upgrade pytest

现在,该包已升级到最新版本。

总结

在本节课中,我们一起学习了Python包的基本概念以及如何使用pip进行包管理。您学会了如何列出环境中的已安装包、安装新包(包括指定版本)、卸载包以及升级现有包。现在,是时候在您自己的环境中尝试这些操作了。

006:06_01_04_保存Python依赖文件 📦

在本节课中,我们将学习如何保存Python包安装的当前状态到一个需求文件中,以及如何使用该文件来安装和卸载包。掌握这项技能是确保项目可移植性和可复现性的关键。

概述

当你完成一个Python项目所需的包安装后,最佳实践是保存这些包的记录。这能使你的项目具有可移植性,意味着你可以在不同的机器上设置并运行你的项目。例如,当你的Python程序开发完成后,你可能需要将其部署到生产服务器或分享给同事使用。在这两种情况下,你都需要记录程序运行所依赖的包。

准备工作:了解两个Shell命令

在开始之前,我们需要了解两个在本课中会用到的Shell命令:lscat

  • ls 命令用于显示当前目录的内容。如果我们提供一个路径,它将显示该路径下的内容。
  • cat 命令用于将文件的内容回显到你的屏幕上。

此外,请记住 pip list 命令会显示当前已安装的包及其版本号。

第一步:查看已安装的包

首先,让我们查看当前环境中已经安装了哪些包。我们可以使用 pip list 命令。

pip list

pip freeze 命令会显示相同的信息,但其输出格式可以与 installuninstall 命令配合使用。

第二步:生成需求文件

我们可以使用重定向操作符(即右尖括号 >)将 pip freeze 的输出重定向到一个文件中,从而生成需求文件。

pip freeze > requirements.txt

这里,我们将文本输出到一个名为 requirements.txt 的文件。任何文件名都可以,但惯例是将其命名为 requirements.txt

我们可以使用 cat 命令来查看这个文件的内容。

cat requirements.txt

你会看到文件中列出的包与环境中安装的包是匹配的。

第三步:使用需求文件卸载包

要卸载需求文件中列举的所有包,可以在 pip uninstall 命令后使用 -r 标志并指定文件名。

pip uninstall -r requirements.txt -y

(注意:-y 参数用于自动确认卸载,避免交互式提示。)

执行后,我们可以再次使用 pip list 来查看,会发现只剩下Python自身运行所必需的基础包。

第四步:使用需求文件安装包

更常见的用法是将需求文件与 pip install 命令结合使用,以在新环境中一键安装所有依赖。同样使用 -r 标志和文件名。

pip install -r requirements.txt

安装完成后,我们可以再次使用 pip list 命令来检查安装结果。

这是为他人项目进行环境配置,或在自己新环境中重建项目依赖的标准方法。

总结

在本节课中,我们一起学习了如何创建一个记录包安装状态的需求文件,以及如何使用该文件来安装和卸载包。现在,你应该练习创建和使用需求文件,这是管理Python项目依赖的核心技能。

007:创建与使用Python虚拟环境 🐍

在本节课中,我们将要学习如何创建和使用Python虚拟环境。虚拟环境是管理项目依赖、避免版本冲突的重要工具。我们将从创建环境开始,逐步学习激活、使用以及退出环境的完整流程。


概述 📋

默认情况下,安装Python包会将其安装到全局用户环境中。这意味着该环境中的所有项目都会共享这些包。

如果仅在本地开发,这通常没有问题。但如果希望开发可移植的项目,共享依赖会导致两个问题。

以下是这两个问题的具体说明:

第一,确定单个项目具体需要哪些包具有挑战性。如果从一个共享环境中重新生成需求文件,该文件可能会引用当前项目不需要的包。当随项目分发这些需求时,安装不需要的包会浪费时间和磁盘空间。

第二,随着时间的推移,可能会出现不同项目需要同一包的不同版本的情况。如果共享已安装的包,则需要在切换项目时更改已安装的版本,或者不断更新旧项目以使用新的包版本。

为了避免这些挑战,理想的做法是为每个项目配置一个独立的环境。

实现这一目标有多种方法,包括虚拟机和容器。但对于Python项目,一个轻量级的选择是使用Python虚拟环境。

当处于Python虚拟环境中时,安装的包仅会安装到该环境中。为每个项目创建一个虚拟环境很容易,从而可以隔离每个项目的依赖关系。

创建虚拟环境时,需要指定一个位置来存储相关的包和配置。

关于存储虚拟环境的最佳位置存在不同观点。一种惯例是将所有虚拟环境存储在一个中心位置,通常是在主目录的子目录中。另一种是将其存储在相关的项目目录中。

历史上,创建虚拟环境的能力依赖于virtualenv包。但从Python 3.5版本开始,标准库提供了venv模块。

在本节课中,我们将介绍以下Shell命令:

  • mkdir:用于创建新目录。
  • pwd:显示当前位置的路径。
  • cd:进入指定目录。
  • source:读取并执行文件内容。

创建项目目录 📁

设置新项目的第一步是创建一个目录来存放其代码和文档。

让我们使用mkdir命令创建一个名为my_first_project的目录。

mkdir my_first_project

现在,我们可以使用cd命令进入这个目录,并使用pwd命令查看我们当前所在的位置。

cd my_first_project
pwd

创建虚拟环境 ⚙️

上一节我们创建了项目目录,本节中我们来看看如何在该目录中创建虚拟环境。

要创建虚拟环境,我们使用Python的-m模块标志调用venv模块,并指定我们希望存储新环境的路径。

在这个例子中,我们将环境命名为.venv,并将其存储在当前项目目录中。

python3 -m venv .venv

使用ls命令,我们可以看到该命令创建了一个以环境名称命名的新目录。

ls -la

如果我们查看这个目录内部,会看到三个子目录和一个配置文件。

ls -la .venv/

激活与使用虚拟环境 🚀

创建环境后,我们需要激活它才能使用。

我们通过执行环境目录下bin子目录中的activate脚本来实现。

source .venv/bin/activate

现在,我们已处于虚拟环境中。命令提示符通常会发生变化,显示环境名称(如(.venv))。

我们可以使用pip list来查看当前环境中可用的包,此时应该只显示必需的最小包集合。

pip list

如果我们在环境中安装一个包,它将仅为此环境安装。

pip install requests

这意味着,如果我们在该环境中创建需求文件,它将仅反映我们为此项目安装的包。

pip freeze > requirements.txt

停用虚拟环境 🛑

当我们准备停止在此项目上工作时,需要停用该环境。

venv模块会自动提供一个deactivate命令。

deactivate

现在,我们回到了共享的全局用户环境中。


总结 ✨

本节课中,我们一起学习了Python虚拟环境的完整使用流程。

我们首先了解了为什么需要虚拟环境,然后逐步实践了如何创建项目目录、使用venv模块创建虚拟环境、通过source命令激活环境、在环境中使用pip安装包,以及最后使用deactivate命令退出环境。

掌握虚拟环境的使用,是进行规范、可移植的Python项目开发的基础技能。

008:Python中的表达式语句 🐍

在本节课中,我们将学习如何安装和运行IPython,使用IPython交互式shell来运行代码,并执行Python的表达式语句。

Python程序由一系列语句构成。语句被定义为一行独立的逻辑代码。语句通常可以分为简单语句和复合语句。我们将要学习的第一个简单语句就是表达式语句。

一个Python表达式是一段能够计算出某个值的代码。表达式语句是一种只包含一个表达式的简单语句。

启动交互式Python Shell 💻

表达式语句在交互式Python shell中通常更为实用。正如我们之前所见,你可以在命令行中通过输入 python 来打开一个交互式shell。这会打开默认的Python shell,它包含在任何Python安装中。

多年来,人们在这个shell的基础上构建了功能更丰富的shell。其中最流行的是IPython shell。

以下是安装和启动IPython的步骤:

  • 你可以使用 pip 来安装IPython shell。
  • 安装完成后,你可以使用命令 ipython 来打开这个shell。

执行表达式语句 ➕➖✖️➗

在交互式shell中输入一个语句并按回车键后,该语句会被执行,并且任何返回值都会显示出来。

例如,我们输入了简单的表达式 1 + 1,可以看到结果在下一行显示出来。

你可以在shell中执行其他数学运算,例如:

  • 乘法2 * 3
  • 减法5 - 2
  • 除法10 / 2
  • 幂运算2 ** 3
  • 取模运算7 % 3

你也可以使用括号来组合这些表达式,例如:(1 + 2) * 3

查看对象的值 👀

如果你在输入一个单独的对象后按回车键,例如数字 12 或引号中的 ‘a’(我们称之为字符串),该值会被回显为输出。

在我们介绍了变量和导入模块之后,这个功能会变得非常有用。但现在,你只需要记住:如果你想查看一个Python对象包含什么值,你可以在交互式shell中直接输入它的名字,然后查看返回的结果。


在本节课中,我们一起学习了如何启动交互式Python shell,使用IPython shell交互式地运行代码,以及执行Python表达式语句。现在,你应该尝试在IPython shell中使用表达式语句了。

009:Python中的赋值语句

在本节课中,我们将学习如何为变量赋值、如何在表达式中使用变量、如何为多个变量赋值,以及如何通过表达式更新变量的值。


接下来我们将讨论的语句类型是赋值语句。这些语句用于为变量赋值。这里的“值”可以是Python中的任何数据类型,例如数字、文本或其他对象。

变量是一个指向数据的名称。理解变量是指向数据的引用,而非数据本身,这一点非常重要。多个变量可以指向同一个值,并且已存在的变量可以被改变以指向不同的值。

变量名必须以字母或下划线开头。变量名中的所有字符必须是字母、数字或下划线。

请记住,Python的变量名是区分大小写的。


让我们打开一个iPython交互式环境来演示变量赋值。要为变量赋值,我们使用赋值操作符,即等号 =

A = 1

这里我们将值 1 赋给了名为 A 的变量。在交互式会话中,我们可以通过直接输入变量名来轻松检查其值。

A

我们现在可以在表达式中使用这个变量。例如,由于我们变量 A 的值是一个数字,我们可以对其执行数学运算。

A + 2

我们也可以将一个表达式的结果赋值给一个变量,并在变量之间进行操作。

B = A * 3
C = A + B

我们可以通过链式赋值,将多个变量赋给同一个值。

x = y = z = 10

我们也可以通过逗号分隔,将多个值赋给多个变量。这些值将按照给定的顺序进行赋值。

a, b, c = 1, 2, 3

如果你想赋值的数量多于变量名的数量,可以使用星号操作符 *。当给定的值组比变量名组长时,带有星号的变量名将被赋予额外的值。

first, *rest = [1, 2, 3, 4, 5]

星号可以放在任何一个变量名前。


你可以将一个使用相同变量的表达式的结果赋值给该变量本身。例如,如果我们先将变量 A 赋值为 1

A = 1

然后,我们可以使用其当前值在一个表达式中更新它的值。

A = A + 1

这种操作非常常见,因此有专门的语法快捷方式,称为增强赋值。所以,我们可以用增强赋值操作符 += 来代替之前的表达式。

A += 1

现在我们可以看到 A 的值增加了我们指定的量。所有数学运算都有对应的增强赋值操作符。

例如,如果我们设置 B = 14,然后希望将其重新赋值为除以 2 的结果,我们可以使用增强除法赋值操作符 /=

B = 14
B /= 2

操作符后面跟上我们希望除以的数字。


要退出Python环境,需要使用 exit() 命令。


本节课中,我们一起学习了如何为变量赋值、如何在表达式中使用变量、如何为多个变量赋值,以及如何通过表达式更新变量的值。现在你应该尝试练习使用变量了。

010:Python中的导入语句 📦

在本节课中,我们将要学习如何在Python程序中导入模块。导入是使用外部代码库功能的基础,掌握不同的导入方式能让你的代码更清晰、更高效。

概述

为了在Python程序中使用包或模块,必须将其加载到程序的运行内存中。这通过导入语句实现。你可以导入Python标准库中的模块、使用PIP安装的库,或者你自己编写的模块。

导入基础模块

上一节我们介绍了导入的必要性,本节中我们来看看最基本的导入方式。

要导入一个模块,使用 import 关键字,后跟模块名。导入后,该模块在当前交互会话或运行的程序中即可用。

以下是导入标准库 os 模块的示例:

import os

现在,我们可以使用点语法访问该模块中定义的函数,例如获取当前工作目录的函数:

os.getcwd()

点语法用于访问模块的函数、属性和子模块。

导入子模块和特定函数

有时我们只需要模块的一部分。Python提供了 from ... import ... 语法来实现精确导入。

以下是导入子模块和特定函数的两种方式:

  1. 仅导入子模块:
    from os import path
    
    之后可以直接使用 path,而无需前缀 os.
  2. 导入特定函数:
    from os.path import isdir
    
    之后可以直接调用 isdir() 函数。

使用星号语法导入

Python允许使用星号 * 作为通配符,一次性导入模块的所有内容。

以下是使用星号语法导入的示例:

from os import *

虽然这种方法可行,但通常不推荐。通用约定是导入整个模块,或者仅导入程序中需要使用的子模块和函数,以避免命名冲突和保持代码清晰。

使用别名导入

在导入时,可以使用 as 关键字为模块或函数创建别名。这在模块名较长或遵循特定社区惯例时非常有用。

在数据科学领域,一个常见的惯例是将 pandas 库导入并重命名为 pd

import pandas as pd

之后,便可以使用 pd 来访问该模块的所有功能:

df = pd.DataFrame()

总结

本节课中我们一起学习了Python中多种导入模块的方法。你学会了如何将模块导入Python程序、如何导入特定的子模块或方法、如何使用星号语法导入,以及在导入时如何为模块或函数设置别名。现在,是时候尝试在你自己的Python会话中实践这些导入技巧了。

011:Python中的其他简单语句 🐍

在本节课中,我们将学习Python中除表达式、赋值和导入语句之外的其他简单语句。我们将重点探讨与异常处理相关的 raise 语句和 assert 语句,理解它们的作用和使用场景。


什么是异常? ⚠️

异常是会导致程序停止执行的错误。

例如,在下面的程序中,我们尝试将一个表达式的值赋给变量,然后使用 print 函数显示该变量的值。

result = 10 / 0
print(result)

问题在于,表达式 10 / 0 进行了除以零的操作。当我们运行这个程序时,会看到抛出了一个 ZeroDivisionError 异常。这个异常在发生点停止了程序,后续的代码行(如 print 语句)将不会被执行。

异常信息中包含一个“回溯”,它显示了异常发生的行以及导致异常的语句。

在交互式环境(如IPython shell)中执行相同的表达式,异常信息会被显示,但shell本身不会退出,这与运行脚本文件不同。


raise 语句 🚨

raise 语句用于主动引发一个异常。其语法是关键字 raise,后跟一个异常类,括号内可以包含一个可选的消息。

raise NotImplementedError("这个功能尚未实现")

上面的代码引发了内置的 NotImplementedError 异常。当我们定义了一个函数名但尚未实现其行为时,这个异常非常有用。

Python 提供了许多内置的异常类型。你也可以通过对象继承定义自己的异常类,但这超出了本课程的范围。


assert 语句 ✅

assert 语句用于确认程序中的某个条件为真。它可以是一个表达式,也可以是一个变量。

如果表达式为真,assert 语句不会执行任何操作。

assert 1 + 1 == 2  # 条件为真,无事发生
assert my_variable > 0  # 假设 my_variable 值为 5,条件为真

然而,如果表达式为假,则会引发一个 AssertionError 异常。

assert 1 + 1 == 3  # 条件为假,引发 AssertionError

你可以在 assert 语句后添加一个错误消息。

assert temperature > 0, "温度必须高于零度"

当断言失败时,这个错误消息会作为异常信息的一部分显示出来。

assert 语句通常用于程序开发或调试阶段。因为它们会减慢程序的运行速度,所以在运行生产代码时通常会禁用它们。这可以通过在运行 Python 程序时添加 -O 标志来实现。

python -O my_program.py

使用 -O 标志后,程序中的 assert 语句将不会被执行。


总结 📚

在本节课中,我们一起学习了 Python 中两个重要的简单语句:raise 语句和 assert 语句。

  • raise 语句允许你主动引发异常,用于指示程序执行过程中出现了预期或非预期的错误情况。
  • assert 语句是一种调试辅助工具,用于在代码中声明必须为真的条件,如果条件不成立,则会引发异常。

现在,你应该尝试在代码中练习使用 raise 语句和 assert 语句,以更好地掌握它们在实际编程中的应用。

012:Python中的复合语句 🐍

在本节课中,我们将要学习Python中复合语句的基本语法。你将了解什么是代码块,以及如何将表达式评估为真(True)或假(False)。这些概念是编写条件判断和循环等复杂程序逻辑的基础。


复合语句的通用语法

复合语句由一个控制语句和一组被控制的语句组成。控制语句决定了被控制语句的执行方式。

控制语句通常是一个关键字、一个表达式,后面跟着一个冒号 :

被控制的语句可以通过以下两种方式之一进行分组。


代码块分组方式

更常见的方式是将它们分组为一个代码块。在Python中,一个代码块是一组共享相同缩进级别的语句。当出现缩进更少的行时,代码块即告结束。

以下是代码块的示例:

if condition:
    # 这是代码块内的语句
    statement1
    statement2
# 缩进恢复,代码块结束

单行分组方式

另一种分组方式是将被控制的语句放在与控制语句同一行,并用分号 ; 分隔。这种方式通常只在被控制语句简短且易于在一行内阅读时使用。

例如:

if x > 0: print(“Positive”); y = x

复合语句的关键字

我们将要学习的几种复合语句的关键字是:ifwhilefordef

ifwhile 语句依赖于评估为真或假的表达式。这些表达式主要来自以下几种操作:相等性操作比较操作布尔操作对象评估


评估为真或假的表达式

上一节我们介绍了复合语句的结构,本节中我们来看看构成其判断条件的各种表达式。让我们在IPython环境中逐一查看。

相等性操作符

有两种相等性操作符:等于操作符和不等于操作符。

  • 等于操作符:形式为双等号 ==。它接受两个参数,如果这两个参数的值相同,则返回 True;如果值不同,则返回 False

    • 通常,比较不同类型的对象(例如字符串和数字)时,== 操作符总是返回 False
    • 例外情况是数值类型(如浮点数 float 和整数 int),如果它们的值相同,可以返回 True
  • 不等于操作符:形式为感叹号加等号 !=。如果它的两个参数不相等,则返回 True;如果相等,则返回 False

比较操作符

比较操作符基于顺序的概念。它们是:小于 <、小于或等于 <=、大于 >、大于或等于 >=

以下是每个操作符的示例:

  • 3 < 5 返回 True,因为第一个参数小于第二个。
  • 5 <= 5 返回 True,因为两个参数相等(满足“小于或等于”)。
  • 大于 > 和大于或等于 >= 的逻辑与之类似。

布尔操作符

布尔操作符是 andornot

  • and 操作符接受两个参数。仅当两个参数评估都为 True 时,它才返回 True;如果任一参数为 False,则返回 False
  • or 操作符接受两个参数。如果任一参数评估为 True,它就返回 True;仅当两个参数都为 False 时,才返回 False
  • not 操作符接受一个参数,并返回与该参数布尔值相反的值。

对象的真值评估

Python中的所有对象都可以被评估为 TrueFalse

  • 一般来说,大多数对象评估为 True
  • 评估为 False 的例外情况包括:
    • 常量 NoneFalse
    • 值为 0 的数值类型。
    • 任何长度为0的对象(如空字符串 ""、空列表 [])。

总结

本节课中我们一起学习了Python复合语句的核心概念。我们了解了复合语句由控制语句和代码块组成,掌握了代码块通过缩进定义的原则,并深入探讨了用于条件判断的各种表达式,包括相等性比较、大小比较、布尔逻辑以及Python对象的真值评估规则。这些是构建 ifwhile 等流程控制语句的基石。


接下来,我们将具体学习不同类型的复合语句。

013:Python中的条件语句 🐍

在本节课中,我们将要学习Python中条件语句的语法。具体内容包括:if语句、else关键字、elif代码块以及match语句。

概述 📋

条件语句是编程中用于根据特定条件执行不同代码块的核心结构。它们让程序能够做出决策,从而实现更复杂和动态的逻辑。

if语句基础

上一节我们介绍了课程概述,本节中我们来看看if语句的基本语法。

if语句使用关键字if,后跟一个表达式。如果该表达式的求值结果为True,则执行其控制的代码块;如果结果为False,则跳过该代码块。

例如,在以下代码中,表达式是常量True

if True:
    print("This block is executed.")
print("This runs after the if statement.")

执行此代码时,if语句控制的代码块和其后的代码都会被执行。

如果表达式的求值结果为False,则其控制的代码块会被跳过。

else关键字

else关键字用于为主要的if代码块提供一个替代的执行路径。当控制if代码块的表达式结果为False时,将执行else控制的代码块。

以下是else关键字的使用示例:

score = 1
if score > 3:
    print("Score is greater than 3.")
else:
    print("Score is 3 or less.")

在这个例子中,因为score等于1,表达式score > 3求值为False,所以第一个代码块被跳过,第二个由else控制的代码块被执行。

如果将score改为一个大于3的值,则第一个代码块会执行,而else代码块会被跳过。

嵌套if语句与elif代码块

我们可以将if语句嵌套在else上下文中,以创建更精细的条件分支。

以下是一个嵌套if语句的例子:

score = 4
if score > 3:
    print("Score is greater than 3.")
else:
    if score == 3:
        print("Score is exactly 3.")
    else:
        print("Score is less than 3.")

这种逻辑很常见,但代码量较多。因此,Python提供了elif关键字,它结合了else和嵌套if的功能,使代码更简洁。

以下是使用elif的等效代码:

score = 4
if score > 3:
    print("Score is greater than 3.")
elif score == 3:
    print("Score is exactly 3.")
else:
    print("Score is less than 3.")

elif语句会按顺序测试每个表达式。如果第一个表达式为False,则测试下一个,依此类推。在所有elif语句之后,仍然可以有一个else语句来捕获前面所有条件都不满足的情况。

match语句

从Python 3.10版本开始,引入了一种新的语句类型:match语句。它在逻辑上与ifelif语句组合非常相似。

match语句的工作方式是匹配一个变量。以下是一个基本示例:

temp = 35
match temp:
    case 33:
        print("Temperature is 33.")
    case 40:
        print("Temperature is too high.")
    case _:
        print("Temperature is something else.")

在这个例子中,case 33意味着检查变量temp是否等于33。最后一个使用下划线_case被称为“通配符”,类似于else语句,当所有其他case都不匹配时执行。

match语句的一个特点是可以在case中使用变量进行解构匹配。

以下是一个使用变量匹配的示例:

pos = (12, 23)
match pos:
    case (12, y):
        print(f"Matched first value 12, second value is {y}.")
    case _:
        print("No match.")

在这个例子中,代码匹配了元组的第一个值12,并将第二个值23赋值给了变量y,然后打印出来。

总结 🎯

本节课中我们一起学习了Python条件语句的核心内容。我们掌握了if语句的基本语法,了解了如何使用else关键字提供备选路径。我们还学习了如何使用elif代码块来简化多条件判断的逻辑。最后,我们初步认识了Python 3.10引入的match语句,它提供了一种强大的模式匹配功能。

现在,是时候尝试编写一些你自己的条件语句了!

014:Python中的while循环 🔄

在本节课中,我们将要学习Python中while循环的语法。我们将了解break语句的用法,并重点探讨提供和保证循环退出条件的重要性。


循环语句类型

在Python中,有两种复合语句类型被称为循环,分别是while循环和for循环。

上一节我们介绍了循环的基本概念,本节中我们来看看while循环的具体语法。

while循环语法

while循环的控制语句由关键字while后跟一个表达式组成。

以下是while循环的基本结构:

while 表达式:
    # 被控制的代码块

while循环会反复执行其代码块,只要控制表达式的结果为True。通常,我们会使用一个变量,并在代码块中改变这个变量的值,最终使表达式变为False,从而结束循环。

一个简单的while循环示例

让我们来看一个具体的例子。在这个循环中,每次迭代都会打印出一个值。

count = 0
while count < 5:
    print(count)
    count += 1

我们可以观察到,在循环的每次迭代中,变量count的值都会被打印出来,并且其值会递增,直到不满足count < 5的条件。

使用break语句

现在,我们在同一个循环中添加一个break语句。break语句会中断循环的执行。

count = 0
while count < 5:
    print(count)
    break

在这个例子中,循环只运行了一次。一旦执行到break语句,循环就会立即停止。

break语句通常与嵌套的if语句结合使用。

以下是结合使用的示例:

count = 0
while count < 5:
    print(count)
    if count == 3:
        break
    count += 1

这里,当变量count等于3时,break语句会被执行,循环随之终止。运行结果也证实了这一点。

循环的退出条件

在我们目前看到的循环中,都有一个条件决定循环何时结束,这被称为退出条件。退出条件可以是循环表达式变为False,也可以是执行了break语句。

对于循环来说,确保存在退出条件至关重要。否则,你可能会陷入无限循环,这会导致程序在运行一段时间后崩溃。

有时,我们会看到一种循环约定:使用一个永远为True的表达式。

while True:
    # 代码块

在这种情况下,就必须使用break语句来提供退出途径。

导致无限循环的常见错误

以下是两种常见的导致无限循环的情况:

第一种情况是,循环表达式永远为True,且内部没有break语句。

# 错误示例:缺少break语句
while True:
    print("这将永远运行")

第二种情况是,在嵌套的if语句中,设计了一个永远不可能为True的条件。

count = 0
while count < 5:
    print(count)
    # 条件永远不成立,因为count从0开始递增
    if count < 0:
        break
    count += 1

由于count从0开始并递增,它永远不会小于0,因此break语句永远不会执行。

另一种常见的导致无限循环的错误是,在受控代码块中忘记改变变量的值。

count = 0
while count < 5:
    print(count)
    # 忘记递增 count += 1

当你的代码块变得更复杂时,这尤其是一个容易出错的地方。如果发生这种情况,循环将没有退出条件,从而形成无限循环。


本节课中我们一起学习了while语句的语法,如何在while循环中使用break语句,以及提供和保证退出条件的重要性。

现在,你应该尝试编写一些自己的while循环来巩固理解。

015:Python中的函数

在本节课中,我们将学习Python中的函数。你将学会如何编写函数、调用函数、按顺序或按名称给参数赋值,以及如何为参数提供默认值。


📝 函数定义

函数语句用于定义一个函数。函数是一个命名的包装器,允许你在多个位置执行同一段代码。

函数定义的语法如下:首先是关键字def,接着是你选择的函数名,然后是括号。括号内可以包含函数中使用的参数名,我们将在本课后面详细讨论。最后是一个代码块。

要执行一个函数,我们输入函数名和括号。这会执行函数内的代码块。

def my_function():
    # 代码块
    pass

pass语句是一个不执行任何操作的语句。但由于函数需要一个代码块,当你在规划程序架构时,可能只想先放入函数名但尚未实现其行为,pass语句就很有用。

我们可以看到,pass语句允许你有一个不执行任何操作的代码块,因此你的函数将被定义而不会引发错误。


🔄 函数返回值

在Python中,所有函数都会返回一个值。例如,我们这里的“什么都不做”函数返回None值,我们可以通过使用双等号将其与None进行比较来验证这一点。

默认情况下,所有函数都会返回None。但我们可以使用return语句来定义一个返回值。例如,这个函数将返回整数2

def return_two():
    return 2

🎯 函数参数

正如我之前提到的,函数定义中的括号有机会定义参数。参数是函数代码块中可用的变量,可以在函数被调用时传入。

这里我们有一个函数,我们定义了一个名为num的参数,函数返回该参数加一。因此,如果我们向函数传入整数2,它将返回3。如果我们传入5,它将返回6

def add_one(num):
    return num + 1

这种为同一函数传入不同参数值的能力使得函数非常强大,因为你可以在代码中重复使用相同的逻辑,但使用不同的值,而只需定义函数一次。


🔢 多个参数

函数可以接受多个参数。当我们通过调用函数为参数赋值时,有两种方式可以做到这一点。

第一种方式是按顺序赋值。也就是说,函数定义中参数的顺序将决定函数调用时传入的值被分配给这些参数的顺序。

def example_function(a, b, c):
    return a + b + c

result = example_function(1, 3, 4)

这里我们看到整数1被分配给第一个参数,整数3被分配给第二个参数,整数4被分配给第三个参数。

第二种调用函数的方式是按名称赋值。我们使用函数定义中为参数定义的名称,然后在调用函数时使用等号赋值。当我们这样做时,参数的顺序无关紧要。

result = example_function(c=4, a=1, b=3)

这里我们为第三个定义的参数先赋值,第一个定义的参数第二个赋值,第二个定义的参数第三个赋值,但这都不重要。

我们可以在同一个函数调用中结合按顺序调用和按名称调用。但如果我们这样做,按顺序调用必须在前,不能在按名称调用之后进行按顺序调用,否则会出错。


⚙️ 默认参数值

我们还可以为参数定义默认值。在函数定义期间,我们使用等号给它一个值。

这里我们有一个名为say_hello的函数,它有一个名为name的参数。在函数定义中,我们给该参数一个默认值"Enri"

def say_hello(name="Enri"):
    return f"Hello, {name}!"

当我们调用它时,现在我们可以调用函数而不为该参数提供参数或值,你会看到它使用默认值。我们也可以用一个值调用它,我们会看到它将使用该值代替默认值。


📚 总结

本节课中,我们一起学习了如何定义和调用函数,以及如何使用顺序和关键字为参数提供值,并为参数提供默认值。现在你应该尝试自己编写一些函数。

016:Python基础入门 🐍

欢迎来到数据工程中的Python与Pandas第二周。在本周的课程中,我们将学习管理数据所必需的Python核心技能。

具体来说,你将学习如何组合不同类型的Python序列并使用序列操作,如何在Python中创建字典、元组和集合,以及如何编写列表推导式、构造生成器表达式和函数。

让我们开始吧。

概述

在本节课中,我们将要学习Python编程的基础知识,这些知识是进行数据工程操作的基石。我们将从理解Python中的基本数据结构开始,逐步深入到更高效的数据处理技巧。

Python序列:列表、元组和字符串

上一节我们介绍了课程的整体目标,本节中我们来看看Python中最基础的数据结构——序列。序列是一种有序的数据集合,Python中常见的序列类型包括列表、元组和字符串。

以下是序列的一些共同特性:

  • 有序性:序列中的元素按特定顺序排列。
  • 索引访问:可以使用索引(从0开始)来访问单个元素,例如 my_sequence[0]
  • 切片操作:可以使用切片语法获取子序列,例如 my_sequence[1:4]
  • 可迭代:可以使用 for 循环遍历序列中的每个元素。

列表是可变的序列,意味着创建后可以修改其内容。你可以使用方括号 [] 来创建列表。

# 创建一个列表
fruits = [“apple”, “banana”, “cherry”]
# 修改列表元素
fruits[1] = “blueberry”
# 在列表末尾添加元素
fruits.append(“date”)

元组是不可变的序列,一旦创建,其内容就不能更改。元组使用圆括号 () 创建,通常用于存储不应被修改的数据集合。

# 创建一个元组
dimensions = (1920, 1080)
# 访问元组元素
width = dimensions[0]

字符串本质上是由字符组成的不可变序列,支持所有通用的序列操作。

# 字符串也是序列
greeting = “Hello”
first_char = greeting[0]  # 结果为 ‘H’

字典与集合:存储键值对与唯一元素

了解了有序序列后,我们来看看Python中另外两种重要的数据结构:字典和集合。它们属于无序的数据集合。

字典用于存储键值对。每个键都是唯一的,用于快速查找对应的值。字典使用花括号 {} 创建。

# 创建一个字典
student = {“name”: “Alice”, “age”: 25, “major”: “Data Science”}
# 通过键访问值
student_name = student[“name”]
# 添加或修改键值对
student[“year”] = 2024

集合是一个无序的、不重复元素的集合。它主要用于成员关系测试和消除重复项。集合也使用花括号 {} 创建,但内部是单个元素而非键值对。

# 创建一个集合
unique_numbers = {1, 2, 3, 3, 4}  # 结果会是 {1, 2, 3, 4}
# 检查成员关系
is_present = 2 in unique_numbers  # 结果为 True

列表推导式与生成器:高效的数据处理

掌握了基础数据结构后,本节我们学习两种能极大提升代码效率和简洁性的工具:列表推导式和生成器。它们能让你用一行代码完成复杂的循环和转换操作。

列表推导式提供了一种从现有序列(或其他可迭代对象)创建新列表的简洁方法。其基本语法是在方括号内包含一个表达式和一个 for 循环。

# 使用循环创建平方数列表
squares = []
for x in range(10):
    squares.append(x**2)

# 使用列表推导式实现相同功能
squares = [x**2 for x in range(10)]

你还可以在推导式中加入条件判断。

# 只保留偶数的平方
even_squares = [x**2 for x in range(10) if x % 2 == 0]

生成器与列表推导式语法相似,但它使用圆括号 (),并且惰性地生成值。这意味着它不会一次性在内存中创建所有元素,而是在迭代时逐个生成,这对于处理大量数据非常高效。

# 生成器表达式
squares_generator = (x**2 for x in range(1000000))  # 不会立即占用大量内存
# 使用时逐个产出值
for num in squares_generator:
    if num > 100:
        break
    print(num)

你还可以使用 yield 关键字定义生成器函数,它能在每次迭代时返回一个值并暂停其状态,下次迭代时从暂停处继续执行。

def count_up_to(max):
    count = 1
    while count <= max:
        yield count  # 每次产出当前值并暂停
        count += 1

# 使用生成器函数
counter = count_up_to(5)
for number in counter:
    print(number)  # 依次输出 1, 2, 3, 4, 5

总结

本节课中我们一起学习了Python编程的核心基础,为数据工程任务做好了准备。

我们首先探讨了Python的序列类型,包括可变的列表、不可变的元组和字符串,并了解了它们的通用操作。

接着,我们学习了字典和集合这两种无序数据结构,分别用于存储键值对和唯一元素。

最后,我们掌握了高效处理数据的强大工具:列表推导式用于快速创建和转换列表,而生成器(包括生成器表达式和生成器函数)则能以惰性、内存高效的方式处理数据流。

这些概念和技巧是你在后续数据清洗、转换和分析中不可或缺的工具。

017:Python中的序列类型 📚

在本节课中,我们将要学习Python中的序列。序列是Python中最广泛使用的数据结构家族,用于有序地存储和操作数据。我们将重点了解序列的成员关系操作、索引、切片、查询以及数学运算。


什么是序列? 🧱

数据结构是存储和处理数据的组织化方法。Python中有专门的数据结构适用于各种问题,无论是优化数据检索还是高效处理大型数据集。其中,序列是最核心的一类。

序列是有序的、有限的数据集合

Python中主要的序列类型包括:

  • 列表 (list)
  • 元组 (tuple)
  • 字符串 (str)

此外,还有:

  • 二进制字符串 (bytes, bytearray)
  • 范围对象 (range)

所有序列类型都共享一组核心操作,这些操作可以分为以下几类:成员关系操作、索引操作、切片操作、查询操作和数学运算。


成员关系操作(innot in)🔍

上一节我们介绍了序列的基本概念,本节中我们来看看如何检查一个元素是否存在于序列中。我们使用关键字 in 来测试。

以下是使用 innot in 的示例:

# 检查数字 3 是否在列表中
my_list = [1, 2, 3, 4, 5]
print(3 in my_list)  # 输出: True

# 检查数字 12 是否不在列表中
print(12 not in my_list)  # 输出: True

in 操作符返回布尔值(TrueFalse)。not in 则返回相反的结果。


索引操作 📍

因为序列中的元素是有序排列的,所以我们可以使用索引来访问序列中的特定元素。Python中的索引通常从0开始。

以下是索引操作的示例:

name = "Monteverde"
# 访问第一个元素(索引0)
print(name[0])  # 输出: M
# 访问第四个元素(索引3)
print(name[3])  # 输出: t

我们也可以使用负索引从序列末尾开始计数。索引 -1 代表最后一个元素。

name = "Monteverde"
# 访问最后一个元素
print(name[-1])  # 输出: e
# 访问倒数第二个元素
print(name[-2])  # 输出: d

切片操作 🔪

切片操作用于从原序列中创建一个新的子序列。其语法是在方括号中使用起始索引、结束索引和可选的步长,用冒号分隔。

以下是切片操作的示例:

name = "Palestrina"
# 从索引2(包含)切片到索引5(不包含)
print(name[2:5])  # 输出: les

# 省略起始索引,默认从0开始
print(name[:4])   # 输出: Pale
# 省略结束索引,默认到序列末尾
print(name[4:])   # 输出: strina

# 使用负索引进行切片
print(name[-3:])  # 输出: ina

# 使用步长参数(每隔一个元素取一个)
print(name[::2])  # 输出: Plsia
# 使用负步长(反转序列)
print(name[::-1]) # 输出: anirtselaP

切片公式sequence[start:stop:step]

  • start:起始索引(包含),默认为0。
  • stop:结束索引(不包含),默认为序列长度。
  • step:步长,默认为1。

序列查询操作 ❓

序列提供了一些内置方法来查询其属性。了解如何查询序列后,我们来看看如何获取序列的长度、查找极值等。

以下是序列查询操作的示例:

my_seq = "Python"

# 获取序列长度
print(len(my_seq))  # 输出: 6

# 查找序列中的最小元素(按字符编码比较)
print(min(my_seq))  # 输出: P

# 查找序列中的最大元素
print(max(my_seq))  # 输出: y

# 计算某个元素在序列中出现的次数
print(my_seq.count('o'))  # 输出: 1

# 查找某个元素首次出现的索引
print(my_seq.index('t'))  # 输出: 2

注意max()min() 函数要求序列中的元素是可比较的。如果序列包含不同类型(如字符串和整数混合),调用这些函数会引发错误。

mixed_list = [1, 'a', 3]
# print(max(mixed_list))  # 这会引发 TypeError

序列的数学运算 ➕✖️

最后,我们来探讨序列的数学运算。需要特别注意的是,序列的数学运算作用于序列本身,而不是序列内部的元素。

以下是序列数学运算的示例:

# 字符串拼接(加法)
str1 = "Hello, "
str2 = "World"
str3 = "!"
print(str1 + str2 + str3)  # 输出: Hello, World!

# 列表拼接(加法)
list1 = [1, 2]
list2 = [3, 4]
print(list1 + list2)  # 输出: [1, 2, 3, 4] (注意:不是 [4, 6])

# 序列乘法(重复)
seq = [1, 2]
print(seq * 4)  # 输出: [1, 2, 1, 2, 1, 2, 1, 2]

关键点:序列的加法和乘法是连接重复操作,而不是对内部元素进行数值计算。


总结 🎯

本节课中我们一起学习了Python中的序列。我们了解到序列是有序的数据集合,主要包括列表、元组和字符串等类型。

我们详细探讨了序列共有的五种核心操作:

  1. 成员关系操作:使用 innot in 检查元素是否存在。
  2. 索引操作:使用 [index] 访问特定位置的元素,支持正索引和负索引。
  3. 切片操作:使用 [start:stop:step] 创建子序列。
  4. 查询操作:使用 len(), min(), max(), .count(), .index() 获取序列信息。
  5. 数学运算+ 用于连接序列,* 用于重复序列。

现在,你应该尝试自己创建一些序列并练习这些操作,以加深理解。

018:Python中的列表与元组

在本节课中,我们将学习如何创建列表和元组,如何向列表中添加和删除元素,以及如何对列表进行重新排序。

列表和元组是Python中两种密切相关的序列类型。它们都可以存储任意数据类型的元素,甚至可以在同一个列表或元组中混合存储不同类型的元素。列表和元组的主要区别在于:列表是可变的,而元组是不可变的。这意味着列表创建后,其内容可以通过添加、删除或重新排序来改变;而元组的内容在创建时确定,之后无法更改。

接下来,我们通过一些例子来具体了解列表和元组。

创建列表与元组

有多种方法可以创建元组,其中一种是使用 tuple() 函数。如果不传入参数,它将创建一个空元组。可以看到,元组在表示时使用圆括号 ()

empty_tuple = tuple()

类似地,可以使用 list() 函数创建一个空列表。

empty_list = list()

我们也可以直接使用圆括号并在其中放置元素来显式创建元组。注意,元素之间需要用逗号分隔。

my_tuple = (1, 2)

列表则用方括号 [] 表示,可以用类似的方式创建。

my_list = [1, 2]

需要注意的是,如果要创建只包含一个元素的元组,必须在元素后面加上逗号,否则它会被解释为普通的括号分组。

single_item_tuple = (1,)  # 这是一个元组
not_a_tuple = (1)         # 这只是一个整数 1

此外,仅通过用逗号分隔元素(不加括号)也可以创建元组,Python会自动将其识别为元组。

another_tuple = 1, 2, 3

我们可以通过“类型转换”将其他序列类型(如字符串)转换为列表或元组。具体做法是,将该序列作为参数传递给 list()tuple() 函数。

my_string = "hello"
list_from_string = list(my_string)   # 结果为 ['h', 'e', 'l', 'l', 'o']
tuple_from_string = tuple(my_string) # 结果为 ('h', 'e', 'l', 'l', 'o')

修改列表内容

元组一旦创建就无法更改,但列表可以。下面我们来看看如何修改列表。

首先,创建一个包含两个字符串的列表。

my_list = ["apple", "banana"]

向列表中添加元素最常用的方法是使用 append() 方法,它会将元素添加到列表的末尾。

my_list.append("cherry")  # 列表变为 ["apple", "banana", "cherry"]

也可以使用 insert() 方法,它接受一个索引参数,指定在哪个位置插入新元素。例如,在索引0(即列表开头)插入元素。

my_list.insert(0, "date")  # 列表变为 ["date", "apple", "banana", "cherry"]

要从列表中移除元素,可以使用 pop() 方法。默认情况下,pop() 会移除并返回列表的最后一个元素。我们也可以指定一个索引参数来移除特定位置的元素。

last_item = my_list.pop()      # 移除 "cherry"
first_item = my_list.pop(0)    # 移除 "date"

合并列表

假设我们有两个列表,并希望将一个列表的所有元素添加到另一个列表中。这时可以使用 extend() 方法。

composers = ["Beethoven", "Mozart"]
my_list = ["apple", "banana"]
composers.extend(my_list)  # composers 列表变为 ["Beethoven", "Mozart", "apple", "banana"]

列表乘法与潜在问题

我们已经讨论过乘法运算,但需要理解一个重要的细节:如果乘法的操作数是一个列表,结果会是一个包含多个相同子列表的列表。

list_of_lists = [[]] * 3  # 创建 [[], [], []]

然而,通过这种方式创建的列表,其内部的三个空列表实际上是同一个列表对象的多个引用。这意味着,如果我们修改其中一个子列表,所有子列表都会同时改变,这可能导致难以察觉的错误。

list_of_lists[0].append(4)
print(list_of_lists)  # 输出 [[4], [4], [4]],而不是 [[4], [], []]

为了避免这个问题,在需要创建包含多个独立空列表的列表时,推荐使用列表推导式

list_of_lists = [[] for _ in range(3)]
list_of_lists[0].append(4)
print(list_of_lists)  # 输出 [[4], [], []]

列表排序

Python提供了内置方法来对列表中的元素进行重新排序。例如,我们可以将一个字符串转换为字符列表,然后对其进行排序。

name = list("Beethoven")  # 结果为 ['B', 'e', 'e', 't', 'h', 'o', 'v', 'e', 'n']

调用列表的 sort() 方法可以按字母数字顺序(默认根据ASCII码)对列表进行原地排序。需要注意的是,大写字母的ASCII码值小于小写字母,因此排序时会排在小写字母前面。

name.sort()  # 列表变为 ['B', 'e', 'e', 'e', 'h', 'n', 'o', 't', 'v']

我们也可以使用 reverse() 方法将列表中的元素顺序完全颠倒。

name.reverse()  # 列表变为 ['v', 't', 'o', 'n', 'h', 'e', 'e', 'e', 'B']

总结

在本节课中,我们一起学习了Python中的列表和元组。你掌握了多种创建列表和元组的方法,并学会了如何通过添加、删除和重新排序来修改列表的内容。现在,你应该尝试自己动手练习使用列表和元组,这是掌握它们的关键步骤。

019:Python中的字符串 🧵

在本节课中,我们将要学习Python中字符串的基础知识。我们将了解如何创建字符串,如何使用字符串方法来改变大小写、检查内容,以及如何动态地将值插入到字符串中。


创建字符串

上一节我们介绍了课程概述,本节中我们来看看如何创建字符串。

字符串是表示文本的序列。它由任何类型的Unicode字符组成,并用引号包裹。

字符串可以用单引号或双引号表示。如果单引号和双引号字符串的内容相同,它们被认为是等价的。

'Hello' == "Hello"  # 结果为 True

这种特性很有价值,因为它意味着如果你想在字符串内容中包含引号,只需确保使用另一种引号来包裹字符串即可。

# 在字符串中包含双引号
text = 'He said, "Hello!"'
# 在字符串中包含单引号
text2 = "It's a beautiful day."

我们还可以使用三引号创建多行字符串。

multi_line_string = """This is
a multi-line
string."""
print(multi_line_string)

特殊字符与原始字符串

字符串中的某些特殊字符会被特殊解释。例如,\t 被解释为制表符,\n 被解释为换行符。

print("Hello\tWorld")  # 输出: Hello    World
print("Hello\nWorld")  # 输出: Hello
                       #        World

当你处理像Windows路径这样的字符串,希望反斜杠保持原样时,这可能会带来麻烦。

path = "C:\Users\Name\Documents"
print(path)  # 输出可能混乱,因为 \U, \N 等被解释

为了避免这种情况,可以在字符串前加上 r,使其成为原始字符串。在原始字符串中,所有字符都被纯粹地解释为它们本身。

raw_path = r"C:\Users\Name\Documents"
print(raw_path)  # 输出: C:\Users\Name\Documents

字符串方法:改变大小写

以下是用于改变字符串大小写的常用方法。

  • capitalize(): 将字符串的第一个字母大写。
  • lower(): 将整个字符串转换为小写。
  • upper(): 将整个字符串转换为大写。
my_string = "Hello World"
print(my_string.capitalize())  # 输出: Hello world
print(my_string.lower())       # 输出: hello world
print(my_string.upper())       # 输出: HELLO WORLD

字符串检查方法

上一节我们介绍了如何改变字符串,本节中我们来看看如何检查字符串的内容。

字符串也支持序列检查方法。index() 方法返回子字符串的起始索引(从0开始)。

my_string = "Hello World"
print(my_string.index("World"))  # 输出: 6

需要注意的是,如果搜索的项不存在于字符串中,index() 方法会抛出错误。find() 方法是 index() 的替代方案,如果未找到项,它会返回 -1,而不会抛出错误。

print(my_string.find("Universe"))  # 输出: -1

字符串还有 startswith()endswith() 方法,用于检查前缀和后缀。

print(my_string.startswith("Hello"))  # 输出: True
print(my_string.endswith("World"))    # 输出: True

我们还可以更专业地检查字符串内容。

  • isalpha(): 检查字符串是否只包含字母字符。
  • isalnum(): 检查字符串是否只包含字母和数字字符。
  • islower() / isupper(): 检查字符串是否全为小写或大写。
  • isnumeric(): 检查字符串是否只包含数字字符。这在尝试将字符串转换为数字之前非常有用。
print("Hello".isalpha())      # 输出: True
print("Hello123".isalnum())   # 输出: True
print("123".isnumeric())      # 输出: True

格式化字符串(F-strings)

格式化字符串是Python 3.6引入的一种特殊字符串。它以大写或小写的 f 为前缀,允许你在运行时插入值。

name = "Alice"
age = 30
greeting = f"My name is {name} and I am {age} years old."
print(greeting)  # 输出: My name is Alice and I am 30 years old.

花括号 {} 被称为替换字段。替换字段的内容可以是任何表达式,该表达式的结果将被插入到字符串中。

# 使用数学表达式
result = f"Five times six is {5 * 6}."
print(result)  # 输出: Five times six is 30.

# 使用序列索引
players = ["Alice", "Bob", "Charlie"]
team = f"Team: {players[0]}, {players[1]}, and {players[2]}."
print(team)  # 输出: Team: Alice, Bob, and Charlie.

格式化选项

替换字段允许你对插入的项进行格式化。格式化通常在冒号 : 之后指定。

number = 42
# 填充空格,使其总宽度为5个字符
print(f"The number is: {number:5d}")  # 输出: The number is:    42

# 使用变量指定填充宽度
width = 10
print(f"The number is: {number:{width}d}")  # 输出: The number is:         42

# 为数字添加千位分隔符
big_number = 1234567
print(f"Big number: {big_number:,}")  # 输出: Big number: 1,234,567

处理空白字符

字符串有许多内置方法用于处理空白字符。这在处理用户输入(如网页表单)时很常见。

user_input = "   Hello World   "
print(user_input.strip())    # 输出: "Hello World" (移除首尾空白)
print(user_input.lstrip())   # 输出: "Hello World   " (移除左侧空白)
print(user_input.rstrip())   # 输出: "   Hello World" (移除右侧空白)

拆分与连接字符串

另一个常见的字符串操作是拆分和连接。

split() 方法根据指定的分隔符将字符串拆分为子字符串列表。

csv_string = "apple,banana,orange"
fruit_list = csv_string.split(",")
print(fruit_list)  # 输出: ['apple', 'banana', 'orange']

join() 方法使用一个字符串作为连接符,将一个字符串序列连接成一个字符串。

new_string = " and ".join(fruit_list)
print(new_string)  # 输出: apple and banana and orange

对于按换行符拆分这种常见情况,有一个专门的方法 splitlines()

multi_line = "Line 1\nLine 2\nLine 3"
lines = multi_line.splitlines()
print(lines)  # 输出: ['Line 1', 'Line 2', 'Line 3']

总结

本节课中我们一起学习了Python字符串的核心操作。你学会了如何使用单引号和双引号创建字符串,如何使用字符串方法来改变大小写和检查内容,以及如何使用F-strings在运行时动态插入值。我们还介绍了如何处理空白字符、拆分和连接字符串。现在你应该尝试练习使用字符串,以获得更好的理解。

020:创建Python的range对象 📊

在本节课中,我们将学习如何创建Python中的range对象,并了解如何控制其方向和步长。


概述

range对象用于表示一个整数序列。它非常高效,是Python中处理循环和序列计数的核心工具之一。本节我们将学习其创建方法、参数含义以及内存效率优势。


什么是range对象?

range对象代表一个整数范围。例如,一个从0到10的range对象。

默认情况下,创建一个range对象只需要一个参数,即计数停止的值。它会从0开始计数。

我们也可以指定起始点。

如果将range对象转换为列表,它会从第一个数开始计数,直到最后一个数。


range对象的内存效率

我们可以使用sys模块来获取Python中对象的大小。

以下代码创建了一个包含数字0、1、2的range对象r,并获取其大小:

import sys
r = range(3)
size = sys.getsizeof(r)

现在,我们创建一个范围大得多的第二个range对象,并注意其大小与第一个相同。

这是range对象的重要特性之一:它可以表示一个非常长的数字序列,而不会占用大量内存。

为了对比,我们将相同的range转换为列表,然后获取列表的大小:

list_from_range = list(r)
list_size = sys.getsizeof(list_from_range)

可以看到,列表占用的内存要大得多。这正是引入range对象的原因之一。


控制步长与方向

我们可以为range对象添加第三个参数,即步长。这类似于切片操作中的步长,它决定了计数的间隔。

例如,以下代码从0开始,以步长2计数,得到小于10的偶数:

even_numbers = range(0, 10, 2)
# 转换为列表查看: [0, 2, 4, 6, 8]

我们也可以使用负步长来倒序计数,就像在切片操作中一样。


range对象的常见用途:循环

range对象最常见的用途是在循环中,用于控制循环执行的次数。

以下是一个示例,我们有一个变量start,并遍历范围0到4(结束于5)。我们使用范围中的每个值作为计数,将其与start相加并打印结果:

start = 5
for count in range(5):  # 生成 0, 1, 2, 3, 4
    result = start + count
    print(result)

总结

本节课我们一起学习了如何创建和使用Python的range对象。我们了解了其表示整数序列的基本功能,通过sys.getsizeof()验证了其高效的内存特性,并掌握了如何使用起始值、结束值和步长参数来控制序列。最后,我们看到了它在for循环中的典型应用。现在,你应该尝试练习使用range对象来巩固理解。

021:创建Python字典 📚

在本节课中,我们将要学习如何创建Python字典。我们将介绍两种主要方法:使用dict()构造函数和使用花括号{}语法。同时,我们会探讨字典的核心概念,包括键值对映射和哈希函数。


什么是字典? 🔑

上一节我们介绍了课程概述,本节中我们来看看字典的基本定义。

字典是一种数据结构,它将键映射到值。字典使用一种高效的机制,通过这些键来查找数据,这种机制依赖于所谓的哈希函数。

Python中所有不可变的数据类型都有哈希函数。例如,我们创建一个值为字符串的变量,可以调用它的哈希函数,该函数会返回一个可预测的值,这个值被用于字典查找。

我们之前已经讨论过可变与不可变数据类型的概念。这意味着,可变的数据类型(如Python列表)不能用作字典键,也没有哈希函数;而不可变的类型(如元组或字符串)可以用作键。


使用 dict() 构造函数创建字典 🛠️

了解了字典的基本概念后,本节我们来看看第一种创建字典的方法:使用dict()构造函数。

如果我们不带参数调用它,它会创建一个空字典。字典用花括号表示。

我们也可以给它提供参数,这些参数将是键值对。现在我们可以看到这个字典有键nameage,以及值Henry16

以下是使用dict()构造函数的具体方式:

  • 不带参数创建空字典:my_dict = dict()
  • 带键值对参数创建字典:my_dict = dict(name=‘Henry‘, age=16)

我们还可以使用dict()构造函数,将一个列表的列表作为参数传入。在这种情况下,每个子列表应该包含一个键和一个值。我们可以看到它生成了相同的字典,只是我将16的数据类型从字符串改为了整数。

以下是使用列表的列表创建字典的示例:

  • my_dict = dict([[‘name‘, ‘Henry‘], [‘age‘, 16]])

使用花括号 {} 语法创建字典 ✏️

上一节我们介绍了使用构造函数创建字典,本节中我们来看看另一种更常用的方法:直接使用花括号{}语法。

在这种情况下,我们按照字典的表示方式直接书写,它会返回一个字典对象。

字典中的键值对是按照它们输入字典的顺序创建的,但这个顺序并不用于判断两个字典是否相等。例如,如果我们创建一个字典,其键值对顺序不同,但键和值相同,然后使用==操作符进行测试,我们可以看到这两个字典被认为是相等的,尽管它们的顺序不同。

以下是使用花括号语法创建字典的示例:

  • 直接创建字典:my_dict = {‘name‘: ‘Henry‘, ‘age‘: 16}
  • 顺序不同的等值字典:my_dict2 = {‘age‘: 16, ‘name‘: ‘Henry‘}, 此时 my_dict == my_dict2 的结果为 True

总结 📝

本节课中我们一起学习了如何创建Python字典。我们掌握了两种主要方法:使用dict()构造函数和使用花括号{}语法。我们理解了字典是键值对的映射,其高效的查找依赖于键的哈希函数,因此键必须是不可变类型。我们还了解到字典的键值对顺序不影响字典的相等性比较。

现在你应该尝试自己创建一些字典了。

022:访问Python字典数据 📚

在本节课中,我们将要学习如何从Python字典中访问和操作数据。具体来说,我们将掌握两种访问字典值的方法,以及如何向字典中添加新的键值对或更新已有的键值对。


访问字典数据的两种方法 🔑

有两种主要方法可以从字典中访问值。

以下是访问字典值的两种方式:

  1. 方括号语法:通过将键名放在方括号内来直接访问值。
  2. get方法:使用字典的.get()方法来安全地访问值,即使键不存在也不会引发错误。


使用方括号语法访问数据

上一节我们介绍了访问字典的两种基本方法,本节中我们来看看第一种方法:方括号语法。这种语法与我们之前通过索引访问序列(如列表)中的数据非常相似。

让我们看一个例子。这里我们有一个名为student的字典,其键为nameagemajor

student = {'name': 'Alice', 'age': 21, 'major': 'Computer Science'}

如果我们想获取age键对应的值,我们将键名(即age)放在字典名称后面的方括号中。

age = student['age']
print(age)  # 输出:21

现在,如果我们尝试访问一个字典中不存在的键,Python会抛出一个KeyError错误。

# 这行代码会引发 KeyError
# grade = student['grade']

我们可以通过使用if语句来避免这种情况,在尝试访问之前先检查键是否存在于字典中。

if 'grade' in student:
    grade_value = student['grade']
else:
    print("键 'grade' 不存在于字典中。")

这样,因为键不在字典中,所以永远不会执行方括号语法的访问。


使用 get 方法安全访问数据

上一节我们看到了使用方括号语法可能引发错误的情况。本节中,我们来看看更安全的替代方法:get方法。这种情况非常常见,因此Python提供了一个特殊的方法——get方法。

使用get方法时,如果我们提供一个错误的键,它将返回None(或者我们指定的默认值),而不是引发错误。

# 键存在,返回对应值
major = student.get('major')
print(major)  # 输出:Computer Science

# 键不存在,返回 None
grade = student.get('grade')
print(grade)  # 输出:None

get方法还允许我们提供一个默认值作为第二个参数,当键不正确时返回这个默认值。

# 提供默认值
grade = student.get('grade', 'missing key')
print(grade)  # 输出:missing key

在这里,我提供了字符串‘missing key’作为回退值。如果键缺失,你可以在这里放置任何类型的其他值。


更新字典中的现有数据

上一节我们学习了如何安全地获取数据。本节中,我们来看看如何修改字典中已有的数据。如果我们想更新一个现有键对应的值,我们同样使用方括号语法。

假设我们想更改这位学生的专业。

# 更新 ‘major’ 键的值
student['major'] = 'Data Science'
print(student)  # 输出:{'name': 'Alice', 'age': 21, 'major': 'Data Science'}

我们通过方括号语法指定键,然后为其分配一个新值。现在查看字典,可以看到major键对应的值确实已被更新。


向字典添加新的键值对

如果我们想向字典中添加一个新的键值对,也可以使用方括号索引。

在这种情况下,如果我们使用一个当前字典中不存在的键,然后为其赋值,我们可以看到这个新的键值对已被添加到字典中。

# 添加新的键值对
student['grade'] = 'A'
print(student)  # 输出:{'name': 'Alice', 'age': 21, 'major': 'Data Science', 'grade': 'A'}

总结 📝

本节课中我们一起学习了如何操作Python字典。我们掌握了两种访问字典值的方法:直接的方括号语法和更安全的get方法。我们还学习了如何使用方括号语法来更新现有键的值,以及如何添加全新的键值对到字典中。你应该自己尝试使用这些方法,以加深理解。

023:Python字典视图 📚

在本节课中,我们将要学习Python字典的视图对象。具体来说,我们将了解dict_keysdict_valuesdict_items这三种特殊的视图对象,以及如何使用它们来高效地访问和操作字典中的数据。


什么是字典视图? 👀

上一节我们介绍了字典的基本操作,本节中我们来看看字典的视图对象。字典提供了三个方法,它们会返回特殊的“字典视图”对象。这些方法分别是keys()values()items()。每个方法返回的对象都提供了观察字典内容的不同视角。

例如,我们有一个名为student的字典:

student = {'name': 'Alice', 'age': 25, 'course': 'Data Engineering'}

我们可以使用keys()方法来返回一个dict_keys对象,它包含了字典中所有键的信息。

keys_view = student.keys()

values()方法返回一个dict_values对象,它代表了字典中的所有值。

values_view = student.values()

items()方法返回一个dict_items对象,它代表了字典中所有的键值对。

items_view = student.items()

字典视图的常见操作 ⚙️

了解了视图对象是什么之后,我们来看看可以对它们进行哪些操作。一个常见的操作是测试成员关系,即检查一个键是否存在于字典的键视图中。

实际上,对于keys()方法,即使不显式调用它,我们也可以直接测试一个键是否在字典中。以下两种方式是等效的:

# 方式一:使用 in 关键字直接测试字典
'name' in student
# 方式二:通过 keys() 视图测试
'name' in student.keys()

这两种方法都测试了键'name'是否存在于字典student中。


在循环中使用字典视图 🔄

使用这些字典视图最常见的方式是在循环中。例如,我们可以遍历字典的所有键并打印出来:

以下是遍历字典键的示例:

for key in student.keys():
    print(key)

或者,我们也可以遍历字典的items(),即所有的键值对。通常,在遍历items()时,我们会将每个键值对解包,以便在循环的代码块中分别使用键和值。

以下是遍历字典键值对并解包的示例:

for key, value in student.items():
    print(f"Key: {key}, Value: {value}")

从上面的代码可以看到,它打印出了字典中每一对键和值。


总结 📝

本节课中我们一起学习了Python字典的视图对象。我们了解到,通过keys()values()items()方法可以获得字典的不同视图。我们学习了如何用它们来测试成员关系,以及如何在for循环中高效地遍历字典的键、值或键值对。你应该尝试在循环中使用这些特殊的方法来更好地处理字典数据。

024:Python集合与集合运算 🧮

在本节课中,我们将学习Python中的集合。集合是一种无序且元素唯一的数据结构。我们将学习如何创建集合、向集合添加元素、从集合中移除元素,以及如何使用集合特有的运算操作。

概述

集合是数学中常见的概念,在Python中,集合是一个无序的、元素唯一的容器。它可以包含任何不可变(可哈希)的数据类型,但不能包含可变对象。本节将详细介绍集合的创建、基本操作和集合运算。

创建集合

我们可以使用 set() 构造函数来创建一个集合。该函数可以接受一个序列作为参数,并返回一个包含该序列中所有唯一元素的集合。

# 创建一个空集合
empty_set = set()
print(empty_set)  # 输出: set()

# 使用序列创建集合
my_list = [1, 2, 2, 3, 4]
my_set = set(my_list)
print(my_set)  # 输出: {1, 2, 3, 4}

集合的表示形式是用逗号分隔的元素,并用花括号 {} 括起来。它与字典的区别在于没有键值对,只有单独的元素。

我们也可以直接使用花括号语法来创建集合。

# 直接创建集合
direct_set = {1, 2, 3, 3, 4}
print(direct_set)  # 输出: {1, 2, 3, 4}

即使我们在创建时添加了重复的元素,集合也只会保留唯一的元素。

向集合添加元素

上一节我们介绍了如何创建集合,本节中我们来看看如何向集合中添加元素。由于集合是无序的,我们不能像列表那样通过索引来添加元素。相反,我们使用 add() 方法。

# 创建一个集合
s = {'a', 'b', 'c'}
print(s)  # 输出: {'a', 'b', 'c'}

# 向集合添加元素
s.add('d')
print(s)  # 输出: {'a', 'b', 'c', 'd'}

使用 add() 方法可以将一个新元素添加到集合中。如果该元素已存在,集合不会发生任何变化,因为集合的元素是唯一的。

从集合中移除元素

我们可以从集合中移除元素。有两种主要方法:pop()remove()

pop() 方法会随机移除并返回集合中的一个元素。由于集合是无序的,你不能依赖它移除“最后一个”元素。

# 使用 pop() 方法
s = {'a', 'b', 'c', 'd'}
removed_item = s.pop()
print(f"被移除的元素是: {removed_item}")
print(f"移除后的集合: {s}")

remove() 方法用于移除集合中指定的元素。如果元素不存在,它会引发一个错误。

# 使用 remove() 方法
s = {'a', 'b', 'c', 'd'}
s.remove('b')
print(s)  # 输出: {'a', 'c', 'd'}

集合运算

集合支持多种数学集合运算,例如判断交集、并集、差集等。以下是几个核心的集合运算方法。

判断是否不相交

如果两个集合没有任何共同的元素,则称它们为“不相交”集合。我们可以使用 isdisjoint() 方法来判断。

set1 = {0, 1, 2}
set2 = set('Herman')  # 从字符串创建集合,得到唯一字符
print(set2)  # 输出: {'H', 'e', 'r', 'm', 'a', 'n'}

# 判断是否不相交
result = set1.isdisjoint(set2)
print(result)  # 输出: True

如果两个集合有共同的元素,isdisjoint() 将返回 False

set1 = {0, 1, 2}
set2 = {1, 3}
result = set1.isdisjoint(set2)
print(result)  # 输出: False

子集与超集

一个集合是另一个集合的子集,如果它的所有元素都包含在另一个集合中。使用 issubset() 方法进行判断。

set_a = {2, 3, 4, 5}
set_b = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

# 判断 set_a 是否是 set_b 的子集
result = set_a.issubset(set_b)
print(result)  # 输出: True

超集是子集的反向关系。如果集合A包含集合B的所有元素,那么A就是B的超集。使用 issuperset() 方法判断。

# 判断 set_b 是否是 set_a 的超集
result = set_b.issuperset(set_a)
print(result)  # 输出: True

并集、交集与差集

以下是三个最常用的集合运算:

  • 并集:返回包含两个集合所有元素的新集合。使用 union() 方法或 | 运算符。

    set1 = {'a', 'b', 'c'}
    set2 = {1, 2, 3}
    union_set = set1.union(set2)
    print(union_set)  # 输出: {1, 2, 3, 'a', 'b', 'c'}
    
  • 交集:返回两个集合中共有元素的新集合。使用 intersection() 方法或 & 运算符。

    set1 = {0, 1, 2, 3, 4}
    set2 = {2, 3, 4, 5, 6, 7, 8}
    intersect_set = set1.intersection(set2)
    print(intersect_set)  # 输出: {2, 3, 4}
    
  • 差集:返回存在于第一个集合但不在第二个集合中的元素的新集合。使用 difference() 方法或 - 运算符。

    set1 = {0, 1, 2, 3, 4}
    set2 = {2, 3, 4, 5, 6, 7, 8}
    diff_set = set1.difference(set2)
    print(diff_set)  # 输出: {0, 1}
    

总结

本节课中我们一起学习了Python集合。我们了解了集合是一个无序且元素唯一的容器。我们学习了如何使用 set() 构造函数或花括号 {} 来创建集合。掌握了使用 add() 方法添加元素,以及使用 pop()remove() 方法移除元素。最后,我们探讨了集合的核心运算,包括判断不相交、子集与超集关系,以及计算并集、交集和差集。现在你应该尝试自己使用集合来解决一些问题。

025:Python列表推导式 📝

在本节课中,我们将要学习Python中的列表推导式。这是一种简洁、高效的语法结构,用于创建列表,可以替代某些类型的for循环。我们将学习如何使用列表推导式来映射函数、过滤序列元素,甚至创建嵌套的列表推导式。


列表推导式基础

上一节我们介绍了课程概述,本节中我们来看看列表推导式的基本概念。列表推导式是一种语法结构,用于执行与一部分for循环相同的功能。

具体来说,列表推导式适用于那些遍历一个序列,对序列中的每个元素执行某些操作,然后将结果作为新列表的一部分返回的循环。

以下是一个标准的for循环示例:

output = []
for i in range(10):
    output.append(i**2)
print(output)  # 输出: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

这个循环遍历一个range对象(0到9),对每个数字进行平方运算,并将结果添加到名为output的列表中。

现在,我们来看看如何用列表推导式替换这个for循环。

列表推导式用方括号[]括起来。其结构如下:

  1. 首先放入要添加到输出列表中的元素表达式。
  2. 然后模拟for循环第一行的语法,即迭代部分。

因此,上面的for循环可以改写为:

output = [i**2 for i in range(10)]
print(output)  # 输出: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

我们可以将列表推导式的结果赋值给一个变量,就像处理for循环的输出一样。可以看到,我们得到了相同的列表。因此,列表推导式是一种更简洁的语法方式,用于表示返回列表的for循环。


使用列表推导式映射函数

上一节我们学习了基础列表推导式,本节中我们来看看如何使用它来映射函数。映射函数是指将一个函数应用到序列的每个元素上。

以下是一个使用for循环映射函数的例子:

import random
scores = []
for i in range(5):
    scores.append(random.randint(1, 10))
print(scores)  # 输出示例: [3, 7, 1, 9, 4]

在这个例子中,序列是range对象,函数是random模块中的randint函数。循环遍历序列,对每个元素调用函数,并将结果添加到列表中。

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

import random
scores = [random.randint(1, 10) for i in range(5)]
print(scores)  # 输出示例: [8, 2, 5, 10, 6]

我们使用方括号定义列表推导式。首先放入我们希望添加到新列表中的函数调用结果,然后定义遍历序列的变量。虽然由于随机数生成,结果可能不同,但逻辑是相同的。列表推导式将原本需要多行代码完成的任务压缩到了一行。


使用列表推导式过滤元素

除了映射,列表推导式另一个强大的功能是过滤序列中的元素。这意味着我们可以根据条件选择性地将元素添加到新列表中。

以下是一个使用for循环和条件语句进行过滤的例子:

string = "HeLlO WoRlD"
output = []
for letter in string:
    if letter.isupper():
        output.append(letter)
print(output)  # 输出: ['H', 'L', 'O', 'W', 'R', 'D']

这个循环遍历字符串中的每个字母,如果字母是大写的(letter.isupper()返回True),则将其添加到输出列表中。

现在,我们使用列表推导式来替换它。列表推导式同样允许我们添加条件语句。

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

string = "HeLlO WoRlD"
output = [letter for letter in string if letter.isupper()]
print(output)  # 输出: ['H', 'L', 'O', 'W', 'R', 'D']

我们再次使用方括号。首先放入要添加到输出中的变量(letter),然后是序列(string)。新的部分是在列表推导式的末尾添加了一个if条件。它的作用是:只将序列中满足条件(即是大写字母)的letter添加到输出列表中。可以看到,结果与上面的for循环一致。


嵌套列表推导式

最后,我们来看看更高级的用法:嵌套列表推导式。这常用于处理嵌套结构的数据,例如“展平”一个列表的列表(即将所有子列表的元素合并到一个列表中)。

假设我们有以下嵌套列表:

nested_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

我们希望将其展平为[1, 2, 3, 4, 5, 6, 7, 8, 9]

使用嵌套的for循环可以这样做:

flattened = []
for sublist in nested_list:
    for item in sublist:
        flattened.append(item)
print(flattened)  # 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

我们遍历外层列表得到每个子列表,然后遍历每个子列表中的项目,并将其添加到一个输出列表中。

我们可以通过嵌套两个列表推导式来实现相同的效果:

flattened = [item for sublist in nested_list for item in sublist]
print(flattened)  # 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

外层推导式仍然用方括号括起来。其结构解读如下:

  • item:我们希望返回到输出列表中的最终元素。
  • for sublist in nested_list:外层循环,遍历nested_list中的每个sublist
  • for item in sublist:内层循环,对于每个sublist,遍历其中的每个item

这样就得到了一个展平后的列表。

最佳实践提示:虽然列表推导式功能强大,但通常最佳实践是将其用于简单的循环,这种单行格式更易于阅读。应避免创建过于复杂的列表推导式,以免你或他人难以理解和解析。保持列表推导式的简洁性,但请大胆尝试使用它们。


总结 🎯

本节课中我们一起学习了Python列表推导式。我们了解到列表推导式是一种用于创建列表的简洁语法,可以替代特定的for循环。我们具体学习了:

  1. 基础列表推导式:用于替代简单的遍历和操作循环。
  2. 映射函数:使用列表推导式将函数应用到序列的每个元素上。
  3. 过滤元素:在列表推导式中加入if条件,选择性包含元素。
  4. 嵌套列表推导式:用于处理嵌套数据结构,如展平列表的列表。

记住,列表推导式的核心优势在于使代码更简洁、更易读,但应避免过度复杂化。

026:Python生成器表达式 🧬

在本节课中,我们将要学习如何创建生成器表达式,以及如何将多个生成器表达式链接在一起使用。


概述

我们已经了解了使用 range 对象相比数字列表能显著减少内存占用。生成器表达式是列表推导式的一种内存高效替代方案,其工作原理与 range 对象类似,只在需要时才计算值。

从列表推导式到生成器表达式

上一节我们介绍了列表推导式,本节中我们来看看如何将其转换为更节省内存的生成器表达式。

首先,这是一个我们之前见过的列表推导式。它遍历一个 range 对象,并对每个数字进行平方。我们导入了 sys 模块来获取结果对象的大小。

import sys

# 列表推导式
list_comp = [x**2 for x in range(1000000)]
print(sys.getsizeof(list_comp))  # 输出对象占用的内存大小

为了将其替换为生成器表达式,我们基本上使用相同的内部逻辑,但将方括号 [] 改为圆括号 ()

# 生成器表达式
gen_exp = (x**2 for x in range(1000000))
print(sys.getsizeof(gen_exp))  # 输出对象占用的内存大小

我们可以看到,生成的结果对象在内存占用上要小得多。这是因为生成器表达式(或称生成器)与 range 对象类似,只在被请求时才计算值。

访问生成器表达式的值

如果我们查看这个生成器表达式,会发现它只显示为一个对象,看不到其中的任何值。

要访问生成器表达式中的值,我们可以使用 next() 函数。

gen = (x**2 for x in range(5))
print(next(gen))  # 输出第一个值:0
print(next(gen))  # 输出第二个值:1

通常,我们使用生成器表达式的方式是在循环中。

gen = (x**2 for x in range(1000000))
for value in gen:
    print(value)
    break  # 此处使用break,因为生成器表达式会生成大量值

当我们迭代生成器表达式时,其行为类似于迭代一个序列,会依次返回每个后续值。

链接生成器表达式

生成器表达式的另一个强大功能是可以将它们链接在一起。

以下是两个生成器表达式链接的例子:

# 第一个生成器:获取0到99之间的所有偶数
evens = (x for x in range(100) if x % 2 == 0)

# 第二个生成器:迭代第一个生成器,筛选出能被3整除的数
filtered = (x for x in evens if x % 3 == 0)

print(next(filtered))  # 输出:0

如果我们想查看这个生成器对象会返回的所有值,可以使用列表推导式将其转换为列表。

result_list = [x for x in filtered]
print(result_list)

总结

本节课中我们一起学习了生成器表达式。生成器表达式在语法上与列表推导式非常相似,但具有更小的内存占用,因为它们采用惰性求值的方式。你应该尝试创建一些自己的生成器表达式,并体会其在处理大数据集时的优势。

027:Python生成器函数 🧬

在本节课中,我们将要学习如何使用 yield 语句,以及如何创建无限生成器。生成器是一种特殊的迭代器,它允许你按需生成值,而不是一次性在内存中创建所有值,这对于处理大数据集或无限序列非常有用。


上一节我们介绍了生成器的基本概念,本节中我们来看看如何编写更复杂的生成器函数。

首先,让我们看一个简单的函数示例。这个函数名为 return_number,目前它包含一个 for 循环。但在循环内部,它有一个 return 语句,这意味着它会在第一次循环迭代时就停止并返回值。

def return_number():
    for i in range(3):
        return i

如果我们运行这个函数,它确实会在第一次遇到 return 语句时停止。

现在,如果我们把这个 return 语句改为 yield 语句,情况会发生变化。

def yield_number():
    for i in range(3):
        yield i

此时,yield_number 不再直接返回值,而是返回一个生成器对象。我们可以像之前操作生成器一样,对这个对象调用 next() 函数。

gen = yield_number()
print(next(gen))  # 输出: 0
print(next(gen))  # 输出: 1
print(next(gen))  # 输出: 2

有趣的是,每次我们从生成器对象中获取一个值时,生成器都会记住我们在 for 循环中的位置,即它保存了函数的执行状态。这就是 yield 语句的作用:它返回一个值,但不会重置函数的状态。

利用这个特性,我们可以创建比生成器表达式更复杂的生成器。


接下来,让我们创建一个可以计数的生成器。

以下是该生成器的代码:它初始化一个值为 0,并包含一个永不退出的无限 for 循环。在每次迭代中,它 yield 当前数字,然后将其递增。

def count_up():
    x = 0
    while True:
        yield x
        x += 1

让我们尝试对它调用 next()

counter = count_up()
print(next(counter))  # 输出: 0
print(next(counter))  # 输出: 1
# 可以无限继续下去

由于这个循环永远不会退出,这被称为无限生成器。我们可以从这个生成器中获得无限的计数序列,除了语言本身的数字大小限制外,它永远不会停止。


为了让生成器更有趣,我们还可以在初始化时给它传递参数。

以下是一个改进的计数器生成器:它接受一个起始值作为参数,而不是每次都从 0 开始。

def count_up_from(start):
    x = start
    while True:
        yield x
        x += 1

现在,当我们从这个生成器开始计数时,可以从指定的数字开始。

counter = count_up_from(12)
print(next(counter))  # 输出: 12
print(next(counter))  # 输出: 13

让我们再做一个生成器示例。这次,创建一个生成斐波那契数列的生成器。

斐波那契数列从 0 和 1 开始,之后的每个数字都是前两个数字之和(即 0, 1, 1, 2, 3, 5, 8...)。

以下是该生成器的实现:它首先处理前两个数字(0 和 1),然后进入一个无限循环,不断计算并生成下一个斐波那契数。

def fibonacci():
    last, current = 0, 1
    yield last
    yield current
    while True:
        last, current = current, last + current
        yield current

让我们测试一下:

fib = fibonacci()
print(next(fib))  # 输出: 0
print(next(fib))  # 输出: 1
print(next(fib))  # 输出: 1
print(next(fib))  # 输出: 2
print(next(fib))  # 输出: 3

通过这种方式,你可以创建相当复杂的无限生成器。与列表推导式不同,它们没有固定的截止点,并且可以在保持极小内存占用的同时,实现非常强大的功能。


本节课中我们一起学习了如何使用 yield 语句来编写生成器函数,包括创建简单的生成器、无限计数器以及生成斐波那契数列的复杂生成器。生成器是处理流式数据或大型序列的强大工具,因为它们支持惰性求值,可以高效地管理内存。

现在,你应该尝试使用 yield 语句来编写自己的生成器表达式了。

028:Pandas与替代方案介绍 🐼📊

在本节课中,我们将学习功能强大且流行的Pandas库。我们将了解如何创建Pandas的DataFrame,编写语句从DataFrame中选择列和行,应用比较和布尔运算符作为筛选数据的方法,以及何时使用Pandas DataFrame的替代方案。

让我们开始吧。

创建Pandas DataFrame 🏗️

上一节我们概述了本节课的内容,本节中我们来看看如何创建Pandas的核心数据结构——DataFrame。

DataFrame是一个二维的、大小可变的、具有潜在异构列的表格型数据结构。你可以把它想象成一个Excel表格或SQL表。

以下是创建DataFrame的几种常见方法:

  • 从字典创建:字典的键将成为列名,值将成为列数据。
    import pandas as pd
    data = {'Name': ['Alice', 'Bob', 'Charlie'],
            'Age': [25, 30, 35],
            'City': ['New York', 'London', 'Tokyo']}
    df = pd.DataFrame(data)
    
  • 从列表的列表创建:需要单独指定列名。
    data = [['Alice', 25, 'New York'],
            ['Bob', 30, 'London'],
            ['Charlie', 35, 'Tokyo']]
    df = pd.DataFrame(data, columns=['Name', 'Age', 'City'])
    
  • 从文件读取:Pandas可以轻松读取CSV、Excel等格式文件。
    df = pd.read_csv('data.csv')
    

选择列和行 🔍

现在我们已经有了一个DataFrame,接下来学习如何从中提取特定的数据部分,即选择列和行。

选择数据是数据分析中最常见的操作之一。

选择列

选择单列会返回一个Series(可以看作一维数组),选择多列则返回一个新的DataFrame。

以下是选择列的方法:

  • 使用方括号 [] 选择单列df[‘column_name’]
  • 使用方括号 [] 选择多列df[[‘col1’, ‘col2’]]
  • 使用点号 . 访问属性(仅限列名是有效的Python标识符时)df.column_name

选择行

选择行通常基于索引位置或条件筛选。

以下是选择行的方法:

  • 使用 .iloc[] 按整数位置选择df.iloc[0] 选择第一行。
  • 使用 .loc[] 按索引标签选择:如果索引是自定义标签,使用此方法。
  • 使用切片选择多行df[0:2] 选择前两行。

应用比较与布尔运算符筛选 🎯

上一节我们介绍了如何通过位置选择行,但更强大的方式是通过条件进行筛选。本节中我们来看看如何应用比较和布尔运算符来精确选择数据。

你可以通过将比较操作(如 >==!=)应用于列,生成一个布尔值(True/False)序列(称为布尔掩码),然后用这个掩码来筛选DataFrame。

以下是使用布尔运算符筛选的步骤:

  1. 创建条件:例如,df[‘Age’] > 30 会生成一个布尔Series。
  2. 应用条件:将布尔Series放入方括号中对DataFrame进行筛选。df[df[‘Age’] > 30]
  3. 组合多个条件:使用 & (与)、| (或)、~ (非) 运算符,每个条件需用括号括起来。例如,df[(df[‘Age’] > 25) & (df[‘City’] == ‘London’)]

Pandas的替代方案 ⚖️

虽然Pandas是Python数据分析的事实标准,但在某些场景下,其他工具可能更合适。本节中我们来看看何时应考虑使用Pandas DataFrame的替代方案。

了解替代方案有助于你为特定任务选择最佳工具。

以下是需要考虑替代方案的几种情况及其对应工具:

  • 处理超大规模数据集(超出内存容量):可以考虑 DaskVaex。它们提供了类似Pandas的API,但能进行并行计算或惰性计算,处理比内存大的数据。
  • 需要极高的单机性能:可以考虑 Polars。这是一个用Rust编写的新库,其查询引擎经过高度优化,速度通常远超Pandas。
  • 与大数据生态系统集成(如Spark)PySpark 的DataFrame是分布式计算环境下的标准选择。
  • 进行交互式数据分析与可视化Pandas 本身很强大,但结合 Jupyter Notebook 和可视化库(如Matplotlib, Seaborn)是常见工作流。对于更丰富的交互式可视化,可以考虑 PlotlyTableau 等专业工具。

总结 📝

本节课中我们一起学习了Python数据处理的核心库Pandas。

我们首先介绍了如何创建DataFrame。接着,我们学习了从DataFrame中选择特定列和行的多种方法。然后,我们探讨了如何使用比较和布尔运算符进行条件筛选,这是数据清洗和分析中的关键技能。最后,我们讨论了Pandas的适用场景及其主要替代方案,例如在处理海量数据、追求极致性能或需要分布式计算时,可以考虑Dask、Polars或PySpark等工具。

掌握这些基础将为你后续进行更复杂的数据工程和数据分析任务打下坚实的基础。

029:创建Pandas数据框 📊

在本节课中,我们将要学习如何创建Pandas数据框。数据框是Python数据科学生态系统中最重要的数据结构之一,它以二维表格的形式存储数据。我们将探讨四种创建数据框的方法:创建空数据框、从字典创建、从列表的列表创建,以及从文件数据创建。

什么是Pandas数据框? 🤔

上一节我们介绍了本课的学习目标,本节中我们来看看Pandas数据框的基本概念。

Pandas数据框是Python数据科学生态系统中使用最广泛的数据结构之一。它们以二维表格结构保存数据。该结构由列和行组成,两者都可以被标记。Pandas数据框中的数据可以是混合类型,但每一列都定义为单一类型。数据可以通过列、行或两者组合来访问。你可以将Pandas数据框视为用代码编写的电子表格。

导入Pandas库 📦

在开始创建数据框之前,我们需要导入Pandas库。按照惯例,我们通常使用pd作为Pandas的别名。

import pandas as pd

从字典创建数据框 📖

现在,让我们看看如何从字典创建Pandas数据框。字典的键将成为数据框的列名。

以下是创建数据框的步骤:

  1. 定义一个字典,其中键是列名,值是列数据。
  2. 使用pd.DataFrame()构造函数,并将字典作为参数传入。
# 定义一个字典
data = {
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'City': ['New York', 'Los Angeles', 'Chicago']
}

# 从字典创建数据框
df_from_dict = pd.DataFrame(data)
print(df_from_dict)

执行上述代码将输出一个类似表格的结构,这就是Pandas数据框在Jupyter Notebook中的表示形式。

从列表的列表创建数据框 📋

接下来,我们学习如何从列表的列表创建数据框。在这种方法中,每个子列表代表数据框中的一行数据。

以下是需要注意的要点:

  • 每个子列表对应数据框中的一行。
  • 如果没有指定列名,列将只有索引编号。
# 定义一个列表的列表
data = [
    ['Alice', 25, 'New York'],
    ['Bob', 30, 'Los Angeles'],
    ['Charlie', 35, 'Chicago']
]

# 从列表的列表创建数据框(无列名)
df_from_list = pd.DataFrame(data)
print(df_from_list)

# 从列表的列表创建数据框(指定列名)
df_from_list_with_columns = pd.DataFrame(data, columns=['Name', 'Age', 'City'])
print(df_from_list_with_columns)

从文件数据创建数据框 📁

从列表和字典创建数据框是基础方法,但最常见的方式是从文件加载数据。这通常是你从其他地方获取或数据管道传递过来的文件。

Pandas提供了专门的函数来读取不同格式的数据。以下是读取CSV文件的示例:

# 假设有一个名为‘data.csv’的文件
file_path = 'data.csv'

# 使用read_csv方法从文件创建数据框
df_from_file = pd.read_csv(file_path)
print(df_from_file.head()) # 查看前几行数据

以下是Pandas支持的部分文件读取函数:

  • pd.read_csv(): 用于读取逗号分隔值文件,这是最常见的格式之一。
  • pd.read_json(): 用于读取JSON格式文件。
  • pd.read_excel(): 用于读取Excel文件。
  • pd.read_sql(): 用于从SQL数据库读取数据。

你应该根据数据文件的格式选择合适的读取函数。

创建空数据框 ⬜

最后,我们也可以创建一个完全空的数据框,这在需要后续动态添加数据时很有用。

# 创建一个空数据框
empty_df = pd.DataFrame()
print(empty_df)

总结 🎯

本节课中我们一起学习了创建Pandas数据框的多种方法。我们首先了解了数据框作为二维表格结构的基本概念。然后,我们逐步实践了四种创建方式:创建空数据框、从字典创建(键成为列名)、从列表的列表创建(每个子列表为一行),以及最常用的从文件(如CSV)加载数据。掌握这些方法是使用Pandas进行数据操作和分析的重要第一步。现在,你可以尝试使用不同的数据源来创建自己的数据框了。

030:探查Pandas数据框中的数据 📊

在本节课中,我们将学习如何探查Pandas数据框中的数据。我们将介绍几种查看数据的方法,包括查看数据的前几行和后几行、获取描述性统计信息,以及如何选择特定的列和行。掌握这些基本操作是进行数据分析和处理的第一步。

上一节我们介绍了数据框的基本概念,本节中我们来看看如何具体查看和选择数据框中的数据。

查看数据框的前几行和后几行 👀

加载一个新的数据框或新数据后,首先要做的就是查看数据的前几行,以了解数据的结构和内容。

以下是查看数据的方法:

  • df.head():默认显示数据框的前5行。可以传入一个整数参数来指定显示的行数,例如 df.head(10)
  • df.tail():默认显示数据框的后5行。同样可以传入参数来指定显示的行数,例如 df.tail(3)

获取数据的描述性统计信息 📈

查看了几行数据后,下一步是了解数据的整体形状和分布。

获取描述性统计信息是一个好方法,这可以通过 describe 方法实现。

df.describe() 方法会为所有数值型列计算并显示计数、均值、标准差、最小值、最大值以及25%、50%、75%分位数。

我们也可以直接调用单个统计方法,例如 df.min() 获取最小值,或 df.std() 获取标准差。

选择数据框中的列 🔍

选择特定的列是数据操作中的常见需求。首先,我们可以通过数据框的 columns 属性查看所有列名。

以下是选择列的几种方法:

  • 方括号语法(最常用)df[‘column_name‘]。这与从字典中获取值的方式类似。选择单个列会返回一个Series对象。要选择多个列,需要将列名放入一个列表中:df[[‘col1‘, ‘col2‘]],这将返回一个包含所选列的新数据框。
  • 点语法:如果列名中没有空格或特殊字符,可以直接使用点语法选择列,例如 df.column_name。方括号语法和点语法都非常方便,你会经常看到它们被使用。

使用 ilocloc 选择行和列 🎯

对于处理大量数据或需要更精确选择的情况,ilocloc 操作符是更优化的方法。

以下是它们的使用方式:

  • iloc 操作符:基于整数位置索引进行选择。
    • 选择单行:df.iloc[2] (选择第三行,索引从0开始)。
    • 选择行范围:df.iloc[3:30] (选择第4行到第30行)。
    • 选择特定行和列:df.iloc[2, 2] (选择第三行、第三列)。也可以使用范围:df.iloc[3:30, 1:4]
  • loc 操作符:基于行和列的标签名称进行选择。
    • 选择单行:df.loc[1990] (假设行标签是年份)。
    • 选择行范围:df.loc[1990:2000]
    • 选择特定行和列:df.loc[1990, ‘column_name‘]。也可以选择多列:df.loc[1990:2000, [‘col1‘, ‘col2‘]]

使用这些方法,你可以选择数据框的一个子集,只查看你感兴趣的行和列。

总结 ✨

本节课中我们一起学习了探查Pandas数据框的基本方法。我们首先使用 headtail 方法查看了数据的首尾部分,然后通过 describe 方法获取了数据的描述性统计信息。接着,我们探讨了如何选择特定的列,包括使用方便的方括号语法、点语法,以及更强大的 ilocloc 操作符来选择行和列的组合。现在,你应该尝试使用这些方法来调查你数据框中的数据了。

031:选取Pandas数据框中的数据 📊

在本节课中,我们将学习在Pandas数据框中选取和筛选数据的多种方法。我们将重点介绍如何使用布尔掩码(Boolean Mask)来精确地选择数据行,以及如何通过比较运算符和逻辑运算符来创建这些掩码。此外,我们还将学习如何基于现有列创建新的数据列。


数据框的初始设置

首先,我们像之前一样,从一个CSV文件设置我们的Pandas数据框。

import pandas as pd
df = pd.read_csv('your_data.csv')
df.head(2)

我们调用了head方法来查看前两行数据,以便了解数据的结构。


使用布尔掩码选择数据

上一节我们设置了数据框,本节中我们来看看如何使用布尔值列表来筛选数据。

布尔掩码是一个与数据框行数等长的布尔值(True/False)列表。我们可以手动创建这样一个列表。

例如,我创建了一个列表,其中所有值都是False,除了第3到第6行(索引从0开始)为True

mask = [False, False, False, True, True, True, True] + [False] * (len(df) - 7)

以下是使用此掩码的两种方法:

  1. 使用方括号语法:我们可以将这个布尔列表作为参数传递给数据框的方括号语法。
    filtered_df = df[mask]
    
  2. 使用.loc索引器:我们也可以使用.loc索引器达到同样的效果。
    filtered_df = df.loc[mask]
    

这两种方法都只会返回那些在掩码中对应位置为True的行。这种方法被称为布尔掩码,它是我们接下来要探讨的筛选方法的核心。


使用比较运算符创建掩码

手动指定每个行的布尔值虽然可行,但在分析数据时并不实用。更实用的方法是使用比较运算符来动态生成掩码。

例如,我们可以比较两列的值来创建掩码。假设我们想找出“CG通知后损失的生命”大于“CG通知前损失的生命”的所有行。

mask = df['lives_lost_after_CG_notification'] > df['lives_lost_before_CG_notification']

这个操作会生成一个布尔序列,对于满足条件的行标记为True,否则为False。然后,我们可以像之前一样使用这个掩码来筛选数据框。

result_df = df[mask]

这样,result_df将只包含满足我们设定条件的行。

比较运算符不仅可以在列之间使用,也可以与固定值进行比较。

例如,创建一个掩码,筛选出“CG通知后损失的生命”小于600的所有行:

mask = df['lives_lost_after_CG_notification'] < 600
filtered_df = df[mask]

使用Pandas布尔运算符组合条件

有时我们需要基于多个条件进行筛选。Pandas使用Python的逻辑运算符(并进行了重载)来组合多个布尔掩码。

主要的三个运算符是:与(&)或(|)非(~)

假设我们查看数据框的统计摘要后,想创建一个掩码来筛选同时满足以下两个条件的行:

  1. cases列的值小于60000。
  2. sorties列的值大于4500。

以下是创建此复合掩码的方法:

mask = (df['cases'] < 60000) & (df['sorties'] > 4500)
result_df = df[mask]

在这个例子中:

  • (df[‘cases’] < 60000) 创建了第一个条件掩码。
  • (df[‘sorties’] > 4500) 创建了第二个条件掩码。
  • 使用 & 运算符将两个掩码组合,要求两个条件同时为True
  • 最终,result_df只包含同时满足这两个条件的行。

在数据框中创建新列

除了筛选数据,我们还可以基于现有列进行计算,并将结果添加为数据框的新列。

创建新列使用了与我们查看单个列时相同的选择语法。我们只需指定一个新列名,并为其赋值一个由现有列计算而来的序列。

例如,我们想计算每次出动平均拯救的生命数,可以用“拯救的生命数”除以“出动次数”:

df['saved_per_sortie'] = df['lives_saved'] / df['sorties']

执行这行代码后,我们的数据框df中就会新增一个名为‘saved_per_sortie’的列。该列的数据就是我们指定的两个现有列进行除法运算的结果。


总结

本节课中我们一起学习了在Pandas数据框中选取和操作数据的核心技巧。

我们首先介绍了布尔掩码的概念,它是筛选数据的基石。接着,我们学习了如何使用比较运算符(如><)来动态生成针对列值条件的掩码。然后,我们探讨了如何使用Pandas的布尔运算符&|)来组合多个条件,实现更复杂的筛选逻辑。最后,我们还掌握了如何通过对现有列进行运算来创建新的数据列

掌握这些数据选取和转换的方法,是进行有效数据清洗、分析和工程化的重要一步。现在,你应该尝试在自己的数据框上应用这些技术进行数据选取了。

032:操作Pandas数据框 🐼

在本节课中,我们将学习如何操作Pandas数据框。你将掌握重命名列、删除列与行,以及设置列数据类型的核心方法。


上一节我们介绍了Pandas数据框的基础知识,本节中我们来看看如何对已创建的数据框进行具体操作。

首先,我们创建一个基于客户数据的数据框,命名为clients。这个数据框包含三列和三行。

以下是创建数据框的示例代码:

import pandas as pd

data = {
    'first': ['Alice', 'Bob', 'Charlie'],
    'last': ['Smith', 'Jones', 'Brown'],
    'age': ['25', '30', '35']
}
clients = pd.DataFrame(data)

重命名列

数据框有一个rename方法,可用于重命名列。该方法有一个columns参数,该参数接收一个字典,用于将当前列名映射到我们希望使用的新列名。

例如,我们将first列重命名为first_name

clients_renamed = clients.rename(columns={'first': 'first_name'})

我们也可以使用rename方法为行设置标签。默认情况下,创建数据框时若不指定行标签,Pandas会分配数字标签。通过指定index参数并传入一个字典,我们可以将当前行标签映射到新标签。

例如,将行标签从0, 1, 2更改为A, B, C

clients_relabeled = clients.rename(index={0: 'A', 1: 'B', 2: 'C'})

在上述两种情况下,rename方法返回的是一个新的数据框,原始数据框clients保持不变。若希望直接修改原始数据框,可以使用inplace=True参数。

例如,原地重命名列:

clients.rename(columns={'first': 'first_name'}, inplace=True)

若希望将行标签重置回数字序列,可以使用reset_index方法,同样配合inplace=True参数:

clients.reset_index(inplace=True)

删除列与行

数据框的drop方法可用于删除列或行。columns参数可以接收单个列名或列名列表。

以下是删除first列的示例:

clients_without_first = clients.drop(columns='first')

我们也可以通过指定行索引来删除特定行。

例如,删除索引为0的第一行:

clients_without_row = clients.drop(index=0)

设置列的数据类型

数据框的每一列都有单一的数据类型。从数据创建数据框时,若不指定数据类型,Pandas会尽力推断。例如,我们创建age列时使用了字符串,Pandas会将其类型识别为object

若希望将某列作为整数处理,可以使用astype方法,并指定目标数据类型。

以下是更改age列数据类型的示例:

clients['age'] = clients['age'].astype(int)

总结

本节课中,我们一起学习了操作Pandas数据框的几个核心技巧:使用rename方法重命名列和行标签,使用drop方法删除列与行,以及使用astype方法设置列的数据类型。这些是数据处理中的基础且重要的操作,建议你多加练习以熟练掌握。

033:更新Pandas数据框数据 📝

在本节课中,我们将要学习如何更新Pandas数据框中的数据。具体来说,我们将掌握四种核心操作:添加新行、更新特定值、使用数学运算更新以及使用替换方法更新数据。


概述

数据框中的数据并非一成不变。在实际工作中,我们经常需要根据新信息或计算来修改现有数据。本节课程将详细介绍在Pandas中执行这些更新操作的各种方法。


添加新行

上一节我们介绍了数据框的基本结构,本节中我们来看看如何向现有数据框添加新的数据行。

首先,我们创建一个包含客户数据的示例数据框。当有新数据需要加入时,我们可以通过添加新行来实现。

以下是添加新行的步骤:

  1. 准备一个字典,其键与现有数据框的列名相同,值为要插入的新数据列表。
  2. 使用该字典创建一个新的数据框。
  3. 使用 append 方法将新数据框追加到原数据框。
# 示例:创建新数据并追加
new_data = {'Name': ['Eve'], 'Age': [28], 'City': ['Boston']}
new_clients_df = pd.DataFrame(new_data)
updated_df = clients_df.append(new_clients_df, ignore_index=True)

请注意,append 方法默认会返回一个新的数据框。如果直接追加,索引可能会出现重复(例如两个索引0),此时可以考虑使用 reset_index 方法来重置索引。


更新特定值

了解了如何添加行之后,我们来看看如何修改数据框中某个特定位置的值。

我们可以使用与字典或列表类似的括号语法来定位并修改单个单元格的值。

例如,要将索引为1的行、第一列(‘Name’列)的值改为‘Frankie’,可以这样操作:

# 使用 .loc 更新单个值
df.loc[1, 'Name'] = 'Frankie'

此操作会直接(就地)修改原数据框中的值。我们也可以同时更新一个范围内的多个单元格。使用 .loc 时,范围指定是包含首尾的。

# 更新一个范围的值
df.loc[0:1, 'Age'] = 30

使用数学运算更新

除了直接赋值,我们还可以通过数学运算来批量更新数据,例如为整列数据加1。

以下是为‘Count’列所有值加1的代码:

# 返回一个新的数据框,原数据框不变
new_df = df['Count'] + 1

但请注意,上面的操作会返回一个新的Series,原数据框并未改变。如果希望直接在原数据框上进行运算,需要使用原地赋值操作符,如 +=-= 等。

# 使用原地操作符,直接修改原数据框
df['Count'] -= 3

使用替换方法

最后,我们学习一种更通用的更新方法:replace。当需要将数据框中所有出现的某个特定值替换为另一个值时,这个方法非常有用。

replace 方法接受两个主要参数:要被替换的值和替换后的新值。

例如,将数据框中所有的 -4 替换为 0

# 替换整个数据框中的特定值
df.replace(-4, 0, inplace=True)

该方法会搜索整个数据框,将所有匹配的值进行替换。inplace=True 参数确保更改直接应用于原数据框。


总结

本节课中我们一起学习了更新Pandas数据框数据的四种主要方法:

  1. 使用 append 方法添加新行
  2. 使用 .loc 或括号语法更新特定单元格或范围的值。
  3. 使用数学运算(如 +=批量更新列数据
  4. 使用 replace 方法全局替换特定值

掌握这些方法,你就能灵活地管理和修正数据框中的内容,为后续的数据分析和处理打下坚实基础。请尝试在自己的数据上练习这些操作以加深理解。

034:在Pandas数据框中应用函数 🐼

在本节课中,我们将要学习如何对Pandas数据框应用函数。你将掌握如何跨行、跨列应用函数,如何编写自定义函数,以及如何对单个列应用函数。


创建示例数据框

首先,我们创建一个数据框。我们的数据框有两列:even(偶数)和odd(奇数)。

even列包含从20开始,每次递减2的数字。
odd列包含从1开始,递增到21的奇数。

我们将使用一个名为sum的内置函数作为示例。sum函数接受任何包含数值数据的可迭代对象作为参数,并对其中的项进行求和。例如,对0到9的数字求和,结果是45。

接下来,我们将sum函数应用到我们的数据框上。


应用函数到整个数据框

每个数据框都有一个apply方法。该方法接受一个函数作为参数,并将该函数应用到数据框的每一行或每一列。

以下是使用apply及其默认参数,以及sum作为要应用的函数的示例:

df.apply(sum)

使用默认参数时,apply会将函数应用到每一列的所有值上。因此,它会分别对even列和odd列的所有值求和,并返回结果。

apply方法接受一个可选的axis参数。让我们再次查看数据框。

如果将axis设置为1,函数将应用到每一行

df.apply(sum, axis=1)

我们可以看到,它计算了每一行中所有列值的总和。在这个例子中,每一行的和恰好都是21。


使用自定义函数

我们不仅可以使用内置函数或第三方函数,还可以定义自己的函数。

首先,定义一个应用于列数据的函数:

def column_function(col):
    if sum(col) > 100:
        return "大于100"
    else:
        return "不大于100"

这个函数接受一个列作为参数。它对列调用sum函数,并测试总和是否大于100。根据结果返回相应的字符串。

如果我们把这个函数应用到数据框:

df.apply(column_function)

我们可以看到,函数被分别应用到每一列,并根据各列的值返回结果。

我们也可以定义处理行的函数。对于行函数,其参数是行本身,行包含每一列的值。

def row_function(row):
    if row['even'] % 3 == 0:
        return "偶数可被3整除"
    elif row['odd'] % 3 == 0:
        return "奇数可被3整除"
    else:
        return "均不可被3整除"

在这个函数中,我们检查even值是否能被3整除,同时也检查odd值。

将这个函数应用到数据框,并指定axis=1,使其应用于行(即跨列):

df.apply(row_function, axis=1)

返回可迭代结果的函数

我们还可以定义返回可迭代结果(如列表)的函数。

def list_return_function(row):
    return [row['even'] * 2, row['odd'] * 3]

这个函数对一行数据操作,根据行中每列的值,返回一个包含两个值的列表。

如果我们将其应用到数据框:

df.apply(list_return_function, axis=1)

它会返回一个对象,其中每个行的值都是一个列表。

有时,我们希望设计这样的函数:它从返回的可迭代对象中取出值,并将其展开,使每个值都有自己的列。我们可以使用result_type='expand'参数来实现。

df.apply(list_return_function, axis=1, result_type='expand')

结果是得到一个具有两列的新数据框,每一列对应我们函数返回的一个值。


对单个列应用函数

除了对整个数据框应用函数,我们还可以对单个列应用函数。当我们只想基于单个列的值进行操作,而不是对整列求和或跨行操作时,这非常有用。

这里我们定义了一个函数:

def single_column_function(value):
    if value > 10:
        return "大"
    else:
        return "小"

这个函数接受一个名为value的参数,并将其视为单个值(而不是一个可以索引并获取不同列的对象),然后根据条件返回不同的值。

我们以与完整数据框类似的方式应用它,但指定了要应用到的单个列:

df['even'].apply(single_column_function)

可以看到,函数被应用到了单个列,并为该列中的每个值返回了结果。


总结

本节课中,我们一起学习了在Pandas数据框中应用函数的核心方法。

我们介绍了如何跨行(axis=1)和跨列(默认axis=0)应用函数,如何编写自定义函数来处理行或列数据,以及如何将函数专门应用于单个列。

无论是跨整个数据框(行或列)还是对单个列应用函数,都是处理和转换数据、将其塑造成新值的重要方式。你应该自己尝试这些操作,以加深理解。

035:创建NumPy数组 🧮

在本节课中,我们将介绍NumPy数组。你将学习NumPy数组与Pandas数据框之间的区别,了解如何创建NumPy数组,如何控制数组的维度,以及何时应选择NumPy数组而非Pandas数据框。

概述

NumPy是一个为科学计算设计的包。它围绕一个ND数组对象构建,我们通常简称为数组。它为这些多维数组对象提供了高度优化的计算工具,并且围绕这个数组对象已经发展出了一个科学和数学库的生态系统。

许多机器学习库期望数组对象作为输入。即使不直接期望,它们也会在后台将输入转换为数组对象,并返回数组对象作为结果。

NumPy数组的特点

上一节我们介绍了NumPy的基本概念,本节中我们来看看NumPy数组对象的一些关键特性,这些特性使其区别于Python序列和Pandas数据框。

  • 当你创建一个ND数组对象时,其大小是固定的。不创建新对象就无法调整其大小。
  • ND数组对象包含单一数据类型,这与Pandas数据框不同。
  • 它们不限于二维。它们可以被重塑,返回一个新的数组对象,这是我们控制其维度的方式。

创建NumPy数组

了解了数组的基本特性后,我们来看看如何在Jupyter笔记本中创建NumPy数组。按照惯例,NumPy通常以别名np导入。

我们可以通过多种方式创建数组。一种方式是向np.array函数或构造函数传入一个序列参数。

以下是创建数组的几种方法:

  • 使用列表创建:np.array([1, 2, 3])
  • 使用嵌套列表(列表的列表)创建二维数组:np.array([[1, 2, 3], [4, 5, 6]])
  • 使用np.ones方法创建全1数组:np.ones((3, 3))
  • 使用np.zeros方法创建全0数组:np.zeros((2, 4))
  • 使用np.arange方法创建序列数组,类似于Python的range对象:np.arange(10)

请注意,无论我们如何创建数组,NumPy在显示时都会使其看起来像是通过列表传入的数据。

数组的维度与形状

我们已经学会了创建数组,现在让我们深入了解数组的维度。NumPy对象具有维度的概念。

我们可以查看数组的维度(ndim)、形状(shape)和总大小(size)。例如,一个由np.arange(21)创建的一维数组,其维度为1,形状为(21,),大小为21。

一个由嵌套列表创建的二维数组(例如3x3),其维度为2,形状为(3, 3),大小为9。

重塑数组

我们可以使用reshape方法来返回一个具有新形状的数组。例如,将一个包含12个元素的一维数组重塑为3x4的二维数组:arr.reshape(3, 4)

调用reshape时,参数必须是能乘出数组中正确项目总数的数字。例如,尝试将12个项目的数组重塑为2x3的数组会引发错误,因为2乘以3不等于12。

要创建更高维度的数组,只需为每个新维度提供更多参数。例如,使用参数(2, 2, 3)可以创建一个形状为2x2x3的三维数组。

数据类型

如前所述,NumPy数组与Pandas数据框的一个区别是它们只能包含单一数据类型。

创建数组时,我们可以查看其数据类型(dtype)和占用的内存大小(nbytes)。我们也可以在创建时通过dtype参数指定特定的数据类型,例如np.array([1, 2, 3], dtype=np.int8)。这对于处理大量数据且已知数值范围有限的情况非常重要,因为可以选择更小的数据类型来节省内存。

需要注意的是,一旦数据类型被设定,NumPy不允许添加不同类型的数据项,并且其处理方式有时可能具有误导性。例如,向一个int8类型的数组中添加一个浮点数,NumPy会截断小数点后的部分。因此,在设置数组数据类型时,需要清楚将要放入的数据的预期类型。

广播

广播是NumPy数组维度操作的另一个重要方面。它允许在不同形状的数组之间进行算术运算。

例如,我们可以将一个3x3的数组与另一个3x3的数组相加,也可以与一个单一数值相加,结果数组的维度仍为3x3。我们甚至可以将一个3x1的数组与一个3x3的数组相加,结果仍然是3x3的数组。这就是广播。

广播的规则是:运算结果数组的每个维度大小,是输入数组在该维度上的最大值。让我们看一个更复杂的例子:一个形状为(2, 1, 5)的三维数组与一个形状为(2, 7, 1)的三维数组相加,结果数组的形状将是(2, 7, 5),即每个维度都取了两者中的最大值。

矩阵运算

NumPy为数组提供了大量高效的数学运算,其中一部分是矩阵运算。

以下是部分矩阵运算示例:

  • 转置:使用.T属性或np.transpose函数,例如 m1.T
  • 对角线:使用np.diag函数获取矩阵对角线元素,例如 np.diag(m1)
  • 矩阵乘法:使用@运算符或np.dot函数,例如 m1 @ m2

何时使用NumPy数组 vs Pandas数据框

在学习了数组的创建和操作后,一个重要的问题是:何时应该使用NumPy数组对象,何时应该使用Pandas数据框?

  • 如果你处理的是高维数据任何非二维的、单一数据类型的数据,并且目标是进行复杂的数值计算,那么应该使用NumPy数组
  • 另一方面,如果你处理的是二维数据,尤其是包含多种数据类型,并且希望进行数据分析或数据可视化,那么Pandas数据框是更合适的选择。

最终,你需要根据所要解决的问题和手头的数据来做出选择。

总结

本节课中,我们一起学习了NumPy数组与Pandas数据框的区别,掌握了如何创建数组并了解了一些数组方法,学会了如何操作数组的维度,并讨论了何时使用数组、何时使用数据框。现在,你应该尝试创建和操作一些NumPy数组来巩固所学知识。

036:Spark与PySpark数据框 🔥

在本节课中,我们将要学习Spark与PySpark数据框。我们将探讨处理大数据的分布式解决方案,理解惰性求值与急切求值的区别,并分析在选择Spark/PySpark与pandas时需要考虑的因素。

单机方案:Pandas 🐼

上一节我们介绍了课程概述,本节中我们来看看传统的数据处理库Pandas。

Pandas被设计为在单台机器上运行。这意味着其性能受限于该机器的内存。为了让Pandas高效运行,其数据需要能够放入该内存中。

Pandas提供了一种称为“分块”的功能,即加载数据子集进行处理,然后再加载另一块数据。但这种方法的处理和性能并不理想。

那么,使用Pandas处理的数据可以有多大?这实际上取决于你的机器。你需要根据所使用的硬件来判断何种规模是高效的。但作为一个经验法则,在不使用分块的情况下,上限通常在1到5GB左右;使用分块时,可能达到100GB

大数据与分布式方案 🚀

那么,如果你有TB或PB级别的数据呢?这时你就遇到了所谓的“大数据”,应该考虑其他解决方案。

以下是两种被广泛采用的大数据解决方案:

  • Hadoop
  • Spark

这两种方案都使用分布式计算。这意味着它们不是使用单个节点或单一线程,而是使用多个节点。这些节点可以是不同的计算机、虚拟机或子进程。无论如何,它们都允许你进行水平扩展,即通过添加更多机器来提高性能,而不必非得添加更庞大、更昂贵的机器。

  • Hadoop通过在计算过程中写入中间文件来实现这一点,因此对内存要求不高。
  • Spark是一个较新的解决方案,性能更高,它在内存中工作,但使用的是所有这些节点的内存总和。因此,它可用的内存通常比Pandas大得多。

Spark核心:分布式数据框 ⚙️

接下来,让我们更深入地了解Spark。

Spark的核心使用数据框,并且如我们所说,这些是分布式数据框。它们使用Java虚拟机编写。Spark本身是用Scala编写的,但提供了许多库,包括PySpark库,它允许你在Python环境中使用Spark数据框和转换操作。

Spark可以处理来自广泛数据源的数据,包括:

  • 分布式数据源Hadoop HDFS
  • S3上的文件
  • 流数据

Spark使用一种称为惰性求值的策略。

惰性求值 vs. 急切求值 ⚡

现在,我们来谈谈惰性求值。

Pandas使用的是急切求值。这意味着在Pandas代码中,每一个操作都会在其下一个操作运行之前计算出结果。急切求值在交互式工作时非常有用,并且调试起来更直观。

另一方面,Spark充分利用了惰性求值。在惰性求值中,操作被堆叠起来,Spark在后台计算出一个优化的转换计划。这意味着结果不会在任何单个操作之后立即计算,而只会在你执行一个需要计算该结果的操作时才进行计算。这种机制,结合其分布式特性,正是Spark能够高效计算大数据的原因。

选择Spark/PySpark的考量 🤔

那么,在选择Spark与Pandas时需要考虑哪些因素呢?

以下是主要的考量点:

  • 数据规模:首要且最重要的考量是,你是否真的在处理大数据?如果你处理的CSV文件小于1GB,你可能不需要Spark或其基础设施。但如果你计划处理TB级数据,或者发现你的Pandas实现在尝试进行的转换中性能不足,那么Spark是一个很好的解决方案。
  • 编程策略:我们讨论过的惰性求值优化计划策略意味着,你也需要规划编写转换操作的方式。如果你编写了一个需要计算的操作(例如,类似于Pandas的df.head(),用于打印数据框的前几行),那么就需要运行任何先前的操作,以便在显示结果之前计算出这些结果。这意味着在编写PySpark代码时,你需要理解哪些操作会导致结果被计算,并有策略地决定何时调用这些特定操作。
  • 调试难度:使用PySpark的另一个考量是,因为它是Spark之上的一层,调试有时可能具有挑战性。通常,当你在日志中查看错误时,你会看到Python异常,然后看到一些与转换层相关的代码,再往下是Scala层的堆栈跟踪。当出现问题时,要梳理这么多信息来找出问题所在可能很困难。

总结 📝

本节课中我们一起学习了Spark与PySpark数据框。我们了解到Pandas适用于单机上的中小型数据集,而Spark通过其分布式数据框和惰性求值机制,为处理TB/PB级别的大数据提供了高效的解决方案。在选择工具时,需要综合考虑数据规模、编程模式的变化以及调试复杂度。尽管存在一些挑战,但Spark和PySpark无疑是进行大数据转换的强大工具。

037:创建Dask数据框 📊

在本节课中,我们将要学习Dask数据框。我们将了解如何创建Dask数据框,如何使用Dask的惰性求值特性,以及如何可视化Dask任务图。

概述

上一节我们介绍了PySpark在处理大数据集时的高效性,但它不具备原生Python库的便利性,也不如pandas数据框功能丰富。本节中我们来看看Dask库。Dask是Python原生的库,专为分布式操作设计,它实际上封装了pandas数据框和NumPy数组。它还能够以高效分布式的方式运行任意的Python代码。

创建Dask数据框

让我们来看一个Dask数据框的例子。

在这个Notebook中,我们已经导入了pandas。我们定义了一个包含非常大数字的变量,并用这个数字来填充一个包含数据的字典。基本上,这是为两个不同的键(A和B)生成的大量随机整数。然后,我们获取这些数据并创建了一个名为df的数据框,并查看前五行。

我们对同一个数据框调用标准差计算。注意,计算标准差花费了一点时间。这并非说明pandas不可用,但延迟是明显的。

现在,让我们用相同的数据创建一个Dask数据框。

我们使用了dd.from_pandas方法,并将我们的pandas数据框作为参数传入,同时定义了希望将数据划分和处理的分区数量

import dask.dataframe as dd
dask_df = dd.from_pandas(df, npartitions=4)

如果我们查看这个数据框,会注意到这些单元格中并没有实际的值。如果我们对其调用标准差计算,它同样不会直接给出答案。这是因为Dask使用了惰性求值,我们之前在Spark中也见过。

惰性求值与计算

为了强制Dask进行计算,我们需要使用.compute()方法。

result = dask_df.std().compute()

这样我们就能看到结果了。基本上,使用Dask,我们可以执行许多与pandas数据框非常相似的操作。

例如,我们可以对两列求和,然后计算差值。

sum_diff = (dask_df['A'] + dask_df['B']).sum() - (dask_df['A'] - dask_df['B']).sum()

但同样,结果直到我们调用.compute()时才会被计算。

final_result = sum_diff.compute()

可视化任务图

Dask一个有趣的特点是,当你构建这一系列操作时,它会在后台创建一个任务图。我们可以查看这个图,了解计算最终结果所经历的各个操作步骤。

Dask也提供了可视化这个图的能力。

如何选择合适的数据处理框架

我们已经看了几种作为pandas数据框替代方案的不同选项。问题是如何为你的任务选择合适的框架。

从高层次来看,你可能需要知道是想要类似pandas、NumPy这样易于设置、交互性强的工具,还是像PySpark和Dask这样的工具。如果这是你的选择,那么关键取决于数据的大小

以下是选择框架时需要考虑的几个关键点:

  • 大数据场景:如果你正在处理大数据,或者计划处理大数据,那么应该选择PySpark或Dask。
  • 小数据场景:如果你的数据量不大,不足以对pandas或NumPy的性能产生影响,那么直接使用它们会简单得多。
  • pandas vs NumPy:这取决于数据的性质。如果你的数据都是同一数据类型(例如数值型),并且需要进行高效运算(尤其是需要将数据表示为高维矩阵的运算),那么NumPy是更好的选择。另一方面,如果你处理的是包含不同数据类型的表格数据,那么pandas可能更适合,特别是当你只是进行简单分析或准备可视化时。
  • PySpark vs Dask:这取决于企业环境。Spark是一个更成熟的产品,拥有更大的生态系统、支持和文档。如果你的企业已经在使用Spark,或者非常关注产品的成熟度,那么PySpark可能是更好的选择。另一方面,Dask是Python原生的,允许你充分利用pandas和NumPy数据框及数组的全部功能。如果你正在启动一个新项目,并且希望整个数据管道都使用Python,那么Dask是一个很好的选择。

总结

本节课中,我们一起学习了如何创建和使用Dask数据框。我们了解了Dask的惰性求值机制,它通过.compute()方法触发实际计算。我们还看到了如何可视化Dask在执行过程中生成的任务图。最后,我们讨论了在不同场景(数据大小、数据类型、企业环境)下,如何在pandas、NumPy、PySpark和Dask之间做出合适的选择。理解这些工具的优缺点,将帮助你为数据工程项目构建更高效、更合适的处理流程。

038:Python开发环境介绍 🐍💻

在本节课中,我们将学习代码开发与版本控制的基础知识。你将了解如何使用不同的工具来编写、调试和管理你的代码。


概述

本周我们将涵盖代码开发和版本控制。

你将学习如何在ViIm文本编辑器中开发代码。

如何在Visual Studio Code中开发和调试代码。

以及如何使用Git版本控制来保存修订历史。


ViIm文本编辑器介绍

上一节我们概述了本周的学习内容,本节中我们来看看第一个工具:ViIm文本编辑器。ViIm是一个功能强大的命令行文本编辑器,在服务器和远程开发中非常常用。

以下是使用ViIm进行基本代码开发的几个核心步骤:

  • 打开文件:在终端中输入 vim 文件名.py 即可用ViIm打开或创建一个Python文件。
  • 模式切换:ViIm有多个模式,最常用的是普通模式(用于导航和命令)和插入模式(用于输入文本)。按 i 键进入插入模式,按 Esc 键返回普通模式。
  • 保存与退出:在普通模式下,输入 :w 保存文件,输入 :q 退出,输入 :wq 保存并退出。

Visual Studio Code开发与调试

了解了基础的命令行编辑器后,我们来看看一个更现代、功能更集成的开发环境:Visual Studio Code(简称VS Code)。它是一个免费的源代码编辑器,支持多种编程语言和强大的调试功能。

在VS Code中开发和调试代码通常涉及以下流程:

  1. 安装与配置:从官网下载并安装VS Code,并根据需要安装Python等扩展插件。
  2. 创建与编辑文件:可以直接在图形界面中创建新文件,享受语法高亮、代码自动补全等便利功能。
  3. 运行与调试:使用内置的终端运行脚本,并利用调试面板设置断点、逐行执行代码以查找错误。

一个简单的Python调试配置可能如下所示(在项目 .vscode/launch.json 文件中):

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: 当前文件",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
    ]
}

Git版本控制基础

我们已经学会了如何编写和调试代码,接下来需要学习如何管理代码的变更历史,这就是版本控制的作用。Git是目前最流行的分布式版本控制系统。

使用Git进行版本控制的核心是跟踪文件的更改并保存快照(提交)。以下是几个基本概念和操作:

  • 初始化仓库:在项目目录下执行 git init 命令,将该目录变为Git可管理的仓库。
  • 跟踪与提交:使用 git add 文件名 将文件更改放入暂存区,然后使用 git commit -m “提交信息” 将更改永久保存到仓库历史中。
  • 查看状态与历史git status 命令可以查看当前工作区和暂存区的状态,git log 命令可以查看提交历史。

其基本工作流程可以概括为以下公式:
工作区 -> git add -> 暂存区 -> git commit -> 本地仓库


总结

本节课中我们一起学习了代码开发与版本控制的基础。我们介绍了如何使用ViIm文本编辑器进行基础编码,如何在Visual Studio Code中利用集成环境开发和调试代码,最后学习了使用Git来管理代码的版本历史。掌握这些工具是进行高效、可追溯的数据工程开发的重要第一步。

039:Vim普通模式入门 🖥️

在本节课中,我们将学习Vim文本编辑器,特别是其核心的“普通模式”。我们将了解Vim的基本概念、如何在不同模式间切换,并重点掌握在普通模式下移动光标、复制、粘贴和删除文本的方法。


Vim是什么?📝

Vim是一个文本编辑器,而不是文字处理器。这是一个重要的区别。

文字处理器(如Microsoft Word)以二进制格式存储文档,其中不仅包含您键入的文本,还包含字体、样式、表格、图像等各种非文本信息的数据。

Vim作为一个文本编辑器,只保存纯文本。您在Vim文件中键入的文本,就是保存在该文件中的全部内容,别无其他。


Vim的历史与特点 🕰️

Vim基于VI文本编辑器,后者是早期Unix系统中可用的编辑器。

正因如此,VI的设计早于计算机鼠标或图形用户界面的出现。当时与计算机交互的唯一方式是通过文本终端。因此,VI以及现在的Vim都基于纯键盘交互。

这意味着在Vim文件中移动,与在依赖鼠标的现代图形界面程序中移动有很大不同。

Vim提供了大量的自定义选项。您可以更改许多设置,并且有一个庞大的插件生态系统。那些依赖Vim作为主要编程界面的开发者会定制他们的配置,以至于几乎每个开发者都有独特的设置。

关于Vim,另一点需要知道的是,由于它源于早期Unix系统中的VI,您可以在任何Unix或Linux机器上找到Vim。这意味着如果您需要登录服务器查看日志或进行其他工作,Vim总是可用的。


Vim的模式 🔄

因为Vim使用您输入文本的同一个键盘来执行所有命令,所以它引入了“模式”的概念。

  • 普通模式:这是您启动Vim时进入的模式。在此模式下,您可以在文件中移动并执行许多非文本输入类型的操作。
  • 插入模式:这是您输入文本的模式。
  • 可视模式:用于进行特殊类型的文本选择。
  • 命令行模式:用于执行特殊命令、搜索、查找和替换。

为了在不同模式间切换,我们需要使用特殊的按键组合。

普通模式是您的大本营,是您开始的地方。要从任何其他模式返回普通模式,请按 Esc 键。这是一个需要记住的重要事项。很多时候,Vim新手会不小心进入某个模式而感到困惑,此时始终可以按 Esc 键返回“家”——即普通模式。


在普通模式下导航 🧭

上一节我们介绍了Vim的模式,本节中我们来看看如何在普通模式下移动光标。这可能是Vim学习曲线中最陡峭的部分,即习惯使用键盘导航。

以下是基本的移动键:

  • h:向左移动
  • j:向下移动
  • k:向上移动
  • l:向右移动

您可以在按键前输入一个数字,以移动相应次数的距离。例如,输入 5l 会使光标向右移动5个字符。

掌握这些按键需要大量练习,直到它们成为您手指的本能记忆。


进阶移动方式 🚀

除了被称为“Home键”的基本移动方式外,还有其他在Vim文件中移动的方法。

以下是其他有用的移动命令:

  • w:向前移动一个单词
  • b:向后移动到当前单词的开头
  • e:移动到当前单词的末尾
  • gg:移动到文件顶部
  • G:移动到文件底部

例如,按 w 会跳转到下一个单词的开头,按 e 会跳转到当前单词的末尾。


复制、粘贴与删除 📋

在普通模式下工作时,另一些需要学习的重要按键是用于复制、粘贴和删除文本的键。

以下是相关命令:

  • y:将高亮显示的文本复制到缓冲区(称为“拉取”)。yw 复制一个单词,yy 复制整行。
  • p:粘贴
  • x:删除光标下的一个字符
  • dd:删除整行

例如,执行 yw 复制一个单词,然后移动光标并按 p 即可粘贴。按 x 会删除光标下的字符,按 dd 会删除整行。


总结 📚

本节课中,我们一起学习了Vim的模式,重点掌握了在其普通模式下移动光标、复制、粘贴和删除文本的方法。您现在应该在Vim的普通模式下多加练习,以熟悉这些核心操作。

040:Vim模式切换教程 🖥️

在本节课中,我们将要学习如何在Vim编辑器中从普通模式切换到插入模式和可视模式。掌握这些切换方式是高效使用Vim进行文本编辑的基础。

从普通模式进入插入模式 🔄

上一节我们介绍了Vim的普通模式,本节中我们来看看如何从普通模式进入插入模式以输入文本。

从普通模式切换到插入模式,可以使用 iao 键。它们都以不同的方式进入插入模式,并且都有对应的大写字母变体,提供不同的插入起始位置。

以下是具体的按键及其功能:

  • 小写 i:在光标当前位置之前开始插入。
  • 大写 I:在当前行的行首开始插入。
  • 小写 a:在光标当前位置之后开始插入。
  • 大写 A:在当前行的行尾开始插入。
  • 小写 o:在当前光标所在行的下方创建一个新行,并进入插入模式。
  • 大写 O:在当前光标所在行的上方创建一个新行,并进入插入模式。

从普通模式进入可视模式 👁️

了解了插入模式的进入方式后,我们再来看看如何进入可视模式以选择文本。

从普通模式切换到可视模式,我们有三个选项:vVCtrl+v。它们分别对应不同粒度的文本选择。

以下是三种可视模式的区别:

  • 小写 v:进入字符可视模式,允许你按字符选择文本。
  • 大写 V:进入行可视模式,允许你按整行选择文本。
  • Ctrl + v:进入块可视模式,允许你选择一个矩形的文本块。

在可视模式下,你可以对选中的文本执行复制(y)、删除(d)等操作。例如,在字符可视模式下选中一些文本后,按下 y 可以复制它们,然后移动光标到别处,按下 p 即可粘贴。


本节课中我们一起学习了Vim中从普通模式切换到插入模式和可视模式的各种按键组合及其变体。现在,你应该尝试在Vim中练习这些操作,以熟悉不同模式下的编辑方式。

041:Vim命令行使用教程 📝

在本节课中,我们将要学习如何在Vim编辑器的命令行模式下执行命令。具体内容包括:如何进入命令行模式、执行基本文件操作命令、在文档中进行搜索,以及如何进行文本的查找和替换。

从普通模式进入命令行模式 🔄

上一节我们介绍了Vim的基本模式,本节中我们来看看如何从普通模式切换到命令行模式。

有三种方式可以从普通模式切换到命令行模式,每种方式对应不同的功能:

  • 冒号 (:):允许我们执行命令。
  • 正斜杠 (/):允许我们在文档中向下搜索。
  • 问号 (?):允许我们在文档中向上或向后搜索。

如果我们输入冒号后跟一个感叹号 (:!),这被称为过滤器命令,它是一个特殊的命令子集。

基本命令行操作 💻

当我们处于命令行模式时,有一些基本命令对于使用Vim至关重要。

以下是几个核心命令及其功能:

  • help:Vim有内置文档。在命令行模式下输入 help 加上你想了解的命令或主题名称,就会调出相关文档。
  • q:Vim中你看到的窗口被称为缓冲区q 命令会关闭当前缓冲区,相当于关闭一个文件。
  • w:保存当前缓冲区。
  • e:打开一个文件。输入 e 后跟文件路径,即可打开该文件。
  • new:创建一个新的缓冲区,之后可以将其保存为新文件。
  • sav:保存当前缓冲区,但可以指定一个新名称。如果你想保存缓冲区的副本,可以使用这个方法。

要使用这些命令,我们首先需要输入冒号 (:) 进入命令行模式。

命令实践演示 🎬

让我们尝试使用这些命令。

我们按冒号进入命令行模式,输入 help 和我们想了解的主题。例如,如果想查看关于命令行模式本身的帮助,我们可以输入:

:help command-line-mode

这会打开一个新缓冲区,显示该部分的文档。

要关闭这个缓冲区,我们输入:

:q

要创建一个新缓冲区,我们使用 new 命令:

:new

我们可以看到一个新的空白缓冲区。

要保存这个缓冲区并指定文件名,我们可以使用 save 命令:

:save new_file.txt

然后关闭它。

要打开一个文件,我们可以使用 e 命令:

:e existing_file.txt

文件将被打开。

如果我们进入插入模式添加一些文本,然后返回普通模式,可以使用 :w 来保存更改。

在关闭缓冲区之前保存文件是一种相对常见的操作,因此 :wq(保存并退出)经常被一起使用。

文本搜索功能 🔍

现在让我们看看如何进行搜索。正斜杠 (/) 用于向前搜索,问号 (?) 用于向后搜索。

一旦找到搜索匹配项,你可以使用 n 键移动到下一个匹配项,或使用大写的 N 键移动到上一个匹配项。

例如,如果我按下 /,然后输入我想搜索的单词,按回车后,光标会跳转到该单词的下一个出现位置。如果我按 n,它会跳转到下一个匹配项。如果我按 N,它会回到上一个匹配项。

如果我按 ? 并搜索一个词,它会跳转到光标位置之前的第一个匹配项。在按过 ? 之后,再按 n 会继续在文件中向上搜索,而按 N 则会向下或向前搜索。

查找与替换功能 ✏️

Vim另一个强大的功能是查找和替换。

要进行查找和替换,请按冒号 (:),然后输入 s,接着是一个正斜杠 (/)、要替换的词、另一个斜杠、替换成的词,最后是执行范围的标志。

例如,将当前行中的 “pass” 替换为 “cat”:

:s/pass/cat/

如果我们想替换文件中所有匹配的实例,可以做同样的事情,但在开头加上一个百分号 %,它代表整个文件:

:%s/pass/cat/g

现在,我们已经将文件中所有的 “pass” 实例替换成了 “cat”。

Vim的查找和替换功能非常强大,支持使用正则表达式等更复杂的选项,例如在替换中使用匹配到的部分内容。这其中的功能非常丰富,但已超出本课程的范围。

课程总结 📚

本节课中我们一起学习了可以在Vim命令行中执行的一些基本命令,也了解了如何搜索文本以及如何进行查找和替换操作。

现在,你应该尝试使用一些这些命令行功能来加深理解。

042:Vim基础配置教程 🛠️

在本节课中,我们将学习如何对Vim文本编辑器进行一些基础配置,并了解如何通过.vimrc文件持久化这些设置,以提升你的编码体验。

概述 📋

Vim是一个功能强大的文本编辑器,但其默认配置可能并不符合每个人的使用习惯。通过调整一些基础设置,我们可以让Vim变得更易用、更高效。本节将引导你完成这些基础配置。

默认Vim与配置后的Vim

到目前为止,我们看到的Vim是我个人配置后的版本。如果你使用的是未经配置的“开箱即用”版Vim,它的界面会更简单。

以下是同一文件在未配置Vim中的样子。

基础设置命令

我们可以通过命令行在Vim会话中临时调整一些设置。

以下是几个实用的基础设置命令:

  • 开启语法高亮:输入命令 :syntax on。这会让代码根据其语法以不同颜色显示,使其更易读。在文件中向下滚动时,效果会更明显。
  • 显示行号:输入命令 :set number。这些行号仅在你查看文件时显示,并不会被插入到文件内容中。但在处理代码时,它们非常有用。
  • 启用自动缩进:输入命令 :set autoindent:set ai。启用后,当你换到新行时,Vim会根据代码语法自动为你缩进。请注意,这需要Vim知道文件的语法类型。

文件类型与设置

当我们开始编辑一个新文件时,Vim可能不知道使用哪种语法高亮规则,因为文件尚未保存,没有文件扩展名。

使用保存命令(例如 :w test.py)保存文件。因为我们已经用 .py 扩展名保存了它,所以Vim现在会使用Python的语法高亮规则。

我们还可以设置显示标尺。输入命令 :set ruler。设置后,我们可以在界面底部看到光标的位置信息,包括行号和在该行中的列号。

持久化配置:.vimrc 文件

像上面这样每次打开文件都重新设置会很麻烦。Vim提供了一个名为 .vimrc 的配置文件来解决这个问题。

这个文件通常保存在你的用户主目录下,名为 .vimrc。在这个文件中,你可以写入之前那些相同的设置命令。

让我们看一下我的 .vimrc 文件示例。你可以看到里面有很多不同的配置命令,正是这些命令给了我个性化的Vim配置。

这样,每次我启动Vim时,它都会自动读取这个文件,并按照我喜欢的方式设置好开发环境。

插件生态系统

.vimrc 文件不仅能保存设置,还能管理插件。Vim拥有一个庞大的插件生态系统。

在我的 .vimrc 文件中,你也能看到所有我为自己工作安装的插件列表。

总结 🎯

本节课中,我们一起学习了如何定制Vim编辑器。你看到了一些可以用来个性化Vim会话的基础设置,也了解了 .vimrc 文件的样子,以及如何利用它来持久保存这些配置,让你每次都能在熟悉、高效的环境中工作。

043:Visual Studio Code介绍 🖥️

在本节课中,我们将学习集成开发环境的定义,并对Visual Studio Code进行一个高层次的介绍。

什么是集成开发环境?

上一节我们提到了文本编辑器与IDE的区别,本节中我们来看看什么是集成开发环境。

集成开发环境是一个可以编写代码的地方,它通常拥有远超于编写文本文件的功能。例如,IDE通常具备调试代码的方式,可以在IDE应用程序内运行代码,并且它们常常与版本控制系统集成。

Visual Studio Code简介

现在,让我们来谈谈Visual Studio Code。

Visual Studio Code是一个IDE,即集成开发环境。之前我们讨论过Vim本质上是一个文本编辑器,通过其插件生态系统,你可以添加插件将Vim变得非常类似于一个IDE,但开箱即用,它本身并不是一个IDE。与某些IDE相比,Visual Studio Code是一个非常轻量级的IDE,但它确实拥有一个允许你扩展它的插件环境。

以下是Visual Studio Code的一些核心特性:

  • 代码补全:帮助编辑代码。
  • 交互式调试:当你的代码出现问题时,这非常有用。
  • 内置语言支持:对JavaScript、TypeScript和Node.js提供内置支持。
  • 丰富的插件生态系统:为其他语言提供了庞大的插件生态系统。

Visual Studio Code高层次概览

现在,让我们回顾一些关于Visual Studio Code的高层次概念。

本节课中,我们一起学习了集成开发环境的定义,并对Visual Studio Code这一轻量级但功能强大的IDE进行了介绍,了解了其核心特性,如代码补全、调试支持和可扩展的插件系统。

044:Visual Studio Code设置 🛠️

在本节课中,我们将学习如何设置Visual Studio Code(VS Code)集成开发环境,并掌握在VS Code中浏览文件和使用核心功能的基本方法。

初始界面与侧边栏

当你打开Visual Studio Code时,会看到类似下图的界面。

界面中包含一个“入门”页面,提供了有用信息的链接。左侧是侧边栏,包含以下主要图标:

  • 搜索:用于在项目中搜索文本。
  • 源代码管理:如果你使用Git等版本控制工具,可以在这里管理。
  • 运行和调试:用于运行和调试你的程序。
  • 扩展:用于安装和管理扩展插件。

安装Python扩展

由于我们使用Python,需要安装Python扩展来获得更好的开发支持。

以下是一个流行的Python扩展。安装过程需要一些时间。扩展安装完成后,它可能会询问是否要安装Python解释器。我们已经安装了Python,因此不需要这一步。

打开文件夹与浏览文件

现在,如果我们打开一个文件夹,例如一个包含pipeline项目的文件夹。

我们可以在资源管理器视图中看到该文件夹内的所有文件。

通过点击文件,可以打开它。

请注意,编辑器右侧有一个大纲视图,展示了整个文件的结构概览。你可以点击大纲视图中的条目,快速跳转到文件的特定部分。

文件导航与代码补全

如果我们打开了多个文件,并希望轻松地在它们之间切换,可以使用以下几种方法:

  • 查看编辑器顶部的标签页
  • 使用左侧的资源管理器
  • 按下 Ctrl + Tab 快捷键,会显示所有已打开文件的列表。

让我们向项目中添加一个新文件。现在,我们有了一个未命名的文件。将其设置为Python文件后,当我们输入代码时,编辑器会提供代码补全建议。

我们可以看到从不同的建议中进行选择。当选择一个方法时,它会显示该方法的参数信息。对于这个特定的方法,显示的是它的帮助文档、所接受的参数以及它的返回值。

动手实践

现在,你已经了解了如何设置Visual Studio Code,包括为我们使用的编程语言安装特定扩展,以及如何在编写代码时浏览文件和使用代码补全功能。

让我们开始动手实践吧。


本节课总结

在本节课中,我们一起学习了Visual Studio Code的基本设置与核心操作:

  1. 认识了VS Code的初始界面和主要侧边栏功能。
  2. 学会了如何安装必要的Python扩展来增强开发体验。
  3. 掌握了打开项目文件夹、使用资源管理器浏览文件以及通过大纲视图导航代码结构的方法。
  4. 了解了在多个打开文件之间切换的几种方式。
  5. 体验了强大的代码自动补全功能,它能提示方法参数和返回值,提升编码效率。

045:Visual Studio Code调试 🐛

在本节课中,我们将学习如何在 Visual Studio Code 中进行调试。你将学习如何设置调试环境,包括创建调试配置文件、设置断点,以及如何逐步执行一个正在运行的程序。


上一节我们介绍了 Visual Studio Code 的基本操作,本节中我们来看看如何使用其强大的调试功能。

这里我们有一个简单的 Python 程序示例。我们定义了一个名为 foo 的函数,设置了一个变量,遍历一个范围,然后打印变量并执行一些操作。最后,我们调用了这个函数。

def foo():
    x = 10
    for i in range(5):
        y = i * 2
        print(f"i: {i}, y: {y}")
        x += y
    print(f"Final x: {x}")

foo()

如果我们直接运行它,可以在终端看到输出结果。现在,如果我们想调试这个函数,需要先进行设置。

首先,我们需要为调试器创建一个配置文件。我们想要一个 Python 调试配置。我们可以添加到此配置中,但为了示例的简洁性,我们将保持其默认设置。

接下来,我们回到代码中设置断点。断点是程序执行时会暂停的位置。让我们运行调试。

程序开始运行,但由于我们在循环开始处设置了断点,程序在首次执行到该行时暂停,因此尚未打印任何内容。

在调试侧边栏,我们可以看到变量的当前值。例如,x 是 10,y 是 0。

然后,我们可以在这里控制执行。我们可以选择 继续,这将使程序运行直到下一次遇到断点。它会再执行一次循环。

我们可以看到,终端已经打印出了循环第一次迭代的值。同时,侧边栏更新了变量的当前值。

我们还可以使用 单步跳过,它只向前移动一行,如果遇到嵌套函数调用,不会进入函数内部。

或者使用 单步进入,这将进入被调用的函数内部。

如果我们已经进入了一个函数,可以使用 单步跳出 来退出该函数。

此外,我们可以 重新启动 整个调试过程。

这就是调试的基本工作流程。当你的软件项目出现问题时,这是一种非常重要且实用的方法,可以帮助你查看问题所在。


现在,你已经了解了如何在 Visual Studio Code 中导航、如何通过插件进行设置、如何在文件中导航以及如何设置调试。

让我们尝试将所有这些知识结合起来运用。


本节课中我们一起学习了 Visual Studio Code 的调试功能。我们掌握了如何创建调试配置、设置断点,以及使用继续、单步跳过、单步进入和单步跳出等控制命令来逐步检查程序状态。这是定位和修复代码问题的核心技能。

046:什么是版本控制 🗂️

在本节课中,我们将要学习版本控制的概念。我们将讨论为什么需要版本控制,以及如何保存和在不同版本之间切换。


在项目开发过程中,你会不断进行修改。当你做出更改后,有时可能需要回到之前的某个版本。例如,你可能尝试重写一个函数,但后来发现早期版本的解决方案实际上更好。

如果你只进行了短时间的开发,要回到早期版本相对容易。你的集成开发环境或文本编辑器通常都有“撤销”按钮。然而,对于一个大型项目,或者经过较长时间后,要回到某个早期版本就变得非常复杂。如果有多人同时为同一个项目做贡献,情况会更加棘手。


这正是版本控制发挥作用的地方。版本控制系统会跟踪所有更改,识别是谁做出了更改,并且可以选择记录更改的原因。它还允许你在项目的不同更改和不同版本之间自由切换。


那么,版本控制是如何工作的呢?它的核心思想是将一组更改提交到版本控制系统中。

当你首次提交项目时,意味着你启动项目并到达一个你认为值得保存的节点。此时,你进行一次提交。每次提交都可以附带一条提交信息。你应该包含一条信息,并确保该信息能描述本次提交的内容。

提交进入版本控制系统后,系统会为这次提交生成一个唯一标识符,记录提交信息以及提交者。之后,当你继续开发并准备进行下一次提交时,版本控制系统不会提交整个已更改的文件或整个项目。相反,它会计算当前更改上一次提交之间的差异,只提交这些差异本身,而不是整个项目。

通过这种方式,系统构建了一个更改历史记录。它可以通过按顺序应用所有更改来构建出最新的版本。一旦版本控制系统中有了这一系列提交,你就可以轻松地回到任何一个早期版本进行查看,或者从中提取某些内容,然后再轻松地返回到最新版本。

因此,使用版本控制系统,你可以:

  • 识别是谁进行了提交。如果你想知道某个更改的原因,可以找到对应的人进行沟通。
  • 记录你自己进行更改的原因。
  • 在这些更改的不同版本之间来回切换。

本节课中,我们一起学习了版本控制的基本概念。我们了解到版本控制通过跟踪更改、记录提交者和原因,并管理不同版本之间的差异,来帮助开发者在项目开发过程中高效地协作和回溯。掌握版本控制是进行任何规模软件开发或数据工程项目的基础技能。

047:Git与Git概念介绍 🚀

在本节课中,我们将介绍Git版本控制系统。你将学习如何设置仓库、管理提交以及如何创建和合并分支。


上一节我们介绍了Git的基本概念,本节中我们来看看一些我们将要使用的基础Git命令。

以下是核心的Git命令列表:

  • git init:初始化一个新的Git仓库。
  • git status:查看仓库当前状态。
  • git add:将文件添加到暂存区,准备提交。
  • git commit:将暂存区的更改记录到仓库历史中。
  • git log:查看提交历史记录。
  • git diff:查看工作区与最后一次提交之间的差异。
  • git checkout:切换分支或恢复文件。
  • git stash:将当前未提交的更改临时保存起来。
  • git merge:将指定分支的更改合并到当前分支。


现在,让我们通过一个具体示例来实践这些命令。假设我们有一个目录,里面创建了一个简单的Python脚本。

首先,我们需要初始化一个Git仓库。使用命令:

git init

这个命令会在当前目录创建一个新的Git仓库。

初始化后,我们可以使用git status来查看仓库状态。默认情况下,Git会创建一个名为master的主分支。此时,你会看到还没有任何提交,并且有一个名为hello.py的未跟踪文件。

为了开始跟踪这个文件,我们使用git add命令:

git add hello.py

执行后,该文件已被添加到暂存区,等待下一次提交。

接下来,我们进行提交。提交时附带描述信息是一个好习惯。使用命令:

git commit -m “添加新文件”

现在,文件已被正式提交到仓库。再次运行git status,会显示没有新的更改需要提交。

我们可以使用git log来查看提交历史:

git log

这会显示我们刚刚完成的提交及其信息。


如果我们修改了hello.py文件,然后运行git status,Git会提示该文件已被修改。

在提交之前,我们可以使用git diff来查看具体修改了哪些内容:

git diff

这个命令会显示工作区文件与最后一次提交版本之间的差异。

假设我们暂时不想提交这些修改,但又不想丢失它们,可以使用git stash将更改临时存储起来:

git stash

这会将当前的修改保存到一个临时区域。之后,我们可以使用git stash pop来恢复这些更改。

当我们决定提交时,可以直接在git commit命令中指定文件,这样就无需先执行git add

git commit -m “更新问候语” hello.py


在协作项目中,最佳实践是在独立的分支上进行工作。我们可以使用git checkout命令创建并切换到一个新分支:

git checkout -b new-feature

使用git branch命令可以查看所有分支,并确认当前所在的分支。

在新分支上,我们可以自由地进行修改和提交。这些更改只会影响当前分支。

要查看当前分支与master分支的差异,可以运行:

git diff master

当我们完成新功能开发后,需要切换回master分支:

git checkout master

此时,查看git log,你会发现新分支上的提交在master分支的历史中并不存在。

最后,要将新分支的更改合并到master分支,使用merge命令:

git merge new-feature

合并后,再次查看git log,就能看到来自新分支的更改已经整合到master分支的历史中了。


本节课中我们一起学习了Git版本控制的核心操作。我们回顾了从初始化仓库(git init)、跟踪和提交更改(git add, git commit),到查看状态与历史(git status, git log, git diff)的完整流程。此外,我们还实践了如何使用分支(git checkout, git branch)进行隔离开发,以及如何临时保存更改(git stash)和最终合并分支(git merge)。掌握这些基础命令是进行高效代码管理和团队协作的第一步。

048:使用GitHub进行版本控制 🚀

在本节课中,我们将要学习如何使用GitHub进行版本控制。我们将首先介绍分布式版本控制的概念,然后学习如何在GitHub上创建仓库,以及如何将本地更改推送到GitHub,或从GitHub拉取更改到本地。

分布式版本控制介绍

上一节我们介绍了Git的基本操作,本节中我们来看看分布式版本控制系统的概念。

Git版本控制系统是一个分布式版本控制系统。这意味着你可以拥有同一个仓库的多个副本。这些副本都是完整的,并且可以独立工作。

如果你在一个仓库副本中进行提交,你可以将这些提交分享给另一个仓库副本,反之亦然。Git本身支持在开发者各自的机器之间进行这种操作,但更常用的解决方案是使用一个托管式的集中服务器版本。每个开发者都向该服务器推送或从该服务器拉取更改。

在Git世界中,有两个非常流行的基于服务器的托管解决方案:GitHubGitLab。从开发者的角度来看,它们的工作方式非常相似,因此我们仅以GitHub为例进行讲解。

创建GitHub仓库

理解了分布式版本控制的基本概念后,接下来我们看看如何在GitHub上创建一个新的仓库。

首先,访问 github.com 并注册一个免费账户。

注册并登录GitHub后,你会看到类似下图的界面。这个区域显示的是仓库。在我的GitHub账户中,你可以看到一些我创建并拥有的仓库,以及一些我被授权参与协作的仓库。

要创建一个新项目或新仓库,请点击“New repository”按钮。

以下是创建新仓库时需要填写的几个关键信息:

  • 仓库名称:为仓库命名。
  • 描述:可选的描述信息。
  • 可见性:选择公开(其他人可以发现)或私有(由你控制谁可以查看)。
  • 初始化文件:可以选择用一些项目常用文件来初始化仓库,例如:
    • README文件:用于描述项目如何工作以及如何使用。
    • .gitignore文件:告诉Git忽略某些类型的文件,使它们不会出现在git status的结果中。
    • 许可证:可以选择一个许可证。

在本例中,我们仅选择添加一个README文件。现在,我们已经成功创建了一个仓库。

克隆仓库与本地操作

创建好远程仓库后,我们需要将其复制到本地进行工作。这个过程称为“克隆”。

查看仓库页面时,我们可以看到分支列表(目前只有默认的main分支),以及允许我们“克隆”此仓库的信息。克隆意味着在其他地方创建它的副本。

我们有以下几种克隆选项。如果我们在本地终端中,可以使用git clone命令来下载项目的副本。

git clone <仓库URL>

现在,我们在本地拥有了项目,并且可以看到它已经创建了一个README文件。如果我们修改这个README文件,然后使用git status命令,可以看到文件已被修改。我们可以将其提交到本地仓库。

推送更改到GitHub

在本地完成提交后,我们需要将这些更改同步到远程的GitHub仓库。

如果我们想将本地更改推送到GitHub,我们使用git push命令。

git push origin main


现在,如果我们切换回GitHub页面,可以看到有了一次新的提交,这正是我们在本地终端中进行的提交。我们可以查看文件,确认我们的更改已被添加。

在GitHub上直接修改与拉取

版本控制是双向的,我们也可以在GitHub上直接修改文件。

你也可以直接在GitHub网页上修改文件并提交更改,通常需要为其提供一个提交信息。



现在,如果我们回到本地终端,并希望将这些在GitHub上所做的更改拉取到本地机器,我们使用git pull命令。

git pull origin main

现在,如果我们查看本地的README文件,可以看到我们在GitHub上所做的更改已经被拉取到了本地仓库中。

课程总结

本节课中,我们一起学习了分布式版本控制系统的概念。我们演示了如何使用GitHub创建仓库,以及如何从本地机器向GitHub推送更改,或从GitHub拉取更改到本地。通过git pushgit pull命令,你可以在本地和远程仓库之间轻松同步工作进度。


049:Python与Pandas数据工程总结 🎯

在本节课中,我们将对“Python与Pandas数据工程”部分的学习内容进行总结。我们将回顾从项目环境搭建到核心工具使用的整个学习路径,帮助你巩固所学知识。

恭喜你,你已经完成了数据工程中的Python和Pandas部分的学习。

现在,你已经掌握了如何设置项目环境、Python和Pandas编程的基础知识,以及如何使用Vim和Visual Studio Code进行开发。

上一节我们介绍了具体的开发工具,本节中我们来看看整个课程的收获与总结。

我希望你喜欢这门课程,并祝愿你在未来的数据工程道路上一切顺利。

以下是本课程涵盖的核心技能点回顾:

  • 项目环境设置:学会了为数据工程项目创建和管理独立的Python环境。
  • Python编程基础:掌握了变量、数据类型、控制流和函数等核心语法。
  • Pandas库应用:理解了如何使用Pandas进行数据加载、清洗、转换和分析。核心操作通常涉及类似 df = pd.read_csv(‘data.csv’) 的代码。
  • 开发工具使用:熟悉了在命令行中使用Vim编辑器以及在集成开发环境(IDE)中使用Visual Studio Code进行高效开发。

本节课中我们一起学习了数据工程中Python与Pandas部分的关键技能。你已从零开始,逐步掌握了构建数据工程解决方案所需的基础编程能力和工具链。这些知识是通往更高级数据操作、自动化脚本和数据分析的坚实基石。请继续实践和探索,将这些技能应用于实际项目中。

001:Linux与Bash数据工程入门 🐧

在本课程中,我们将学习如何利用Linux工具构建解决方案,并掌握使用Bash和ZSH配置与语法来交互和控制Linux系统。本课程适合初学者,以及对将Bash脚本应用于数据科学、机器学习和数据工程感兴趣的中级学员。学习本课程前,您需要对Linux有基本的了解。

课程结束时,您将能够达成以下目标:利用Linux工具构建解决方案;开发Bash语法和配置来配置与控制Linux;以及构建Bash脚本。


课程概述 📋

上一节我们介绍了课程的整体目标。本节中,我们将具体了解您在本课程中将掌握的核心技能。

以下是您将学习到的三项关键能力:

  1. 利用Linux工具构建解决方案
  2. 开发Bash语法和配置来配置与控制Linux
  3. 构建Bash脚本

核心技能详解 🔧

1. 利用Linux工具构建解决方案

在数据工程领域,Linux提供了强大的命令行工具集,用于高效地处理数据、管理文件和自动化任务。掌握这些工具是构建稳健数据管道的基础。

2. 开发Bash语法和配置

Bash是Linux系统中最常用的Shell。通过学习和配置Bash(以及功能丰富的ZSH),您可以更高效地与系统交互,定制您的工作环境,并执行复杂的命令序列。

3. 构建Bash脚本

Bash脚本是将一系列命令组合成可执行文件的方式,是实现任务自动化的关键。一个简单的Bash脚本结构如下:

#!/bin/bash
# 这是一个注释
echo "Hello, Data Engineering World!"

这个脚本使用 echo 命令输出一段文本。#!/bin/bash 指明了脚本的解释器。


总结 🎯

本节课中,我们一起学习了《Linux与Bash数据工程入门》课程的核心目标。您了解了本课程将帮助您掌握利用Linux工具、配置Bash/ZSH以及编写Bash脚本这三项重要技能,为后续深入数据工程实践打下坚实基础。

002:认识课程讲师诺亚·吉夫特 👨‍🏫

在本节课中,我们将认识本课程的核心讲师之一——诺亚·吉夫特,了解他的专业背景和行业经验,这有助于我们理解课程内容的设计视角。

大家好,我是诺亚·吉夫特。我是Coursera专项课程“数据工程基础”的讲师之一。

我是杜克大学MIDS项目和电子工程系的兼职教授。

我在软件行业拥有超过20年的经验,涉足领域包括SaaS、社交媒体、电影和游戏。我曾担任过多种角色,例如咨询首席技术官、独立贡献者、首席技术官以及机器学习和人工智能部门负责人。

我撰写了超过八本书籍,其中包括《Python for DevOps》和《Practical MLOps》。此外,我还负责另一个杜克大学的Coursera专项课程“面向数据的云计算”。

我很高兴能与大家分享我的知识。让我们开始学习吧。


本节课中,我们一起认识了讲师诺亚·吉夫特。他丰富的行业经验和学术背景,为我们学习数据工程提供了坚实的实践与理论基础。在接下来的课程中,我们将基于这些宝贵的经验,深入探索Python、Bash和SQL在数据工程中的应用。

003:核心概念概览 🧭

在本节课中,我们将概览课程2的核心概念,包括Bash命令行、脚本编写、配置文件以及文件搜索工具。这些是数据科学家、数据工程师和机器学习工程师高效工作的基础工具。

Bash命令行的重要性 💻

上一节我们介绍了课程的整体框架,本节中我们来看看Bash命令行。Bash命令行之所以重要,是因为两点之间最短的距离是直线。若想在终端中执行任务,命令行界面是实现目标的最快途径。因此,掌握Bash是数据领域从业者最高效的工作方式。

脚本的构成与执行 📜

理解了命令行的效率后,接下来我们学习如何将命令组织成脚本。编写脚本需要了解几个关键组件。

以下是脚本的核心组成部分:

  • Shebang行:Shebang行(例如 #!/bin/bash#!/usr/bin/env python3)告知系统使用何种解释器来执行脚本中的代码。
  • 可执行权限:在Linux系统中,需要使用 chmod +x script_name 命令为脚本文件添加可执行权限。
  • 执行脚本:添加权限后,通过 ./script_name 即可运行脚本。脚本文件甚至可以没有扩展名,Shebang行会确保使用正确的解释器执行代码。

掌握脚本的构建细节是一个非常重要的概念。

配置文件与环境变量 ⚙️

学会了编写基础脚本后,我们常常需要配置环境。这就引出了配置文件和变量,它们在Bash编程中频繁出现。

其中,最重要的配置文件之一是 .bashrc

.bashrc 文件的重要性在于,每个新启动的终端会话都会自动运行该文件中的命令。它的好处是允许你配置诸如别名等功能。例如,你可以为“进入某个目录并激活Python虚拟环境”这一系列命令创建一个简短的别名。同样,人们也常在此导出环境变量,例如项目的API密钥,这样可以避免将其意外提交到GitHub导致泄露。

.profile 文件仅在用户登录时运行一次,通常用于项目的初始设置。

文件搜索工具 🔍

配置好环境后,我们经常需要查找文件。本课程将介绍搜索工具,主要有两种类型。

第一种是 find 命令,它进行实时搜索。在实时项目搜索中,你可以构建搜索结构,查找特定模式。这非常重要,因为它能进行你所需的穷举式搜索。

locate 是另一个有趣的搜索工具,它基于文件系统的元数据先前建立的索引进行搜索,因此能提供极快的速度。例如,在一个TB级别的文件系统中,使用 find 可能耗时过长,而 locate 则能快速返回结果。

再次强调,这里存在权衡:若追求穷举性,使用 find;若追求速度,则使用 locate


本节课中,我们一起学习了Bash命令行的高效性、脚本的基本构成与执行方法、关键的配置文件(.bashrc.profile)及其用途,以及两种主要的文件搜索工具(findlocate)的特点与适用场景。掌握这些核心概念将为后续的数据工程工作打下坚实基础。

004:Linux操作入门

在本周课程中,我们将学习如何使用Linux来解决软件工程、数据工程和机器学习领域中常见的技术问题。我们将了解Linux Shell的核心组成部分,并学习如何在常见场景中应用这些知识。

接下来,让我们明确第一周的学习目标。

🎯 第一周学习目标

在本周学习结束时,你将能够:

  • 识别Linux Shell的关键组成部分。
  • 创建Shell管道。
  • 解释SSH是什么,以及如何使用它来解决问题。


上一节我们明确了本周的学习目标,本节中我们将开始深入探讨Linux Shell的基础知识。

🐚 认识Linux Shell

Linux Shell是一个命令行解释器,它为用户提供了与操作系统内核交互的界面。你可以将它想象成一个强大的控制台,通过输入特定的文本命令,就能指挥计算机完成各种任务。

对于数据工程师而言,熟练使用Shell至关重要,因为它能高效地处理文件、管理进程和自动化任务。


了解了Shell的基本概念后,我们来看看它的几个关键组成部分。

⚙️ Shell的关键组成部分

以下是Linux Shell的几个核心组件:

  • 终端(Terminal):这是一个应用程序,提供了一个输入和显示文本的窗口,让你能够与Shell进行交互。
  • Shell程序:这是运行在终端内的实际程序,负责解析并执行你输入的命令。常见的Shell包括Bash(Bourne Again Shell)和Zsh。
  • 命令(Command):你输入的具体指令,例如 ls(列出文件)、cd(切换目录)或 grep(搜索文本)。
  • 文件系统:Linux以层级目录结构组织所有文件和目录,Shell命令让你能够在这个结构中导航和操作。

掌握了Shell的基本构成,我们就可以学习如何将多个命令组合起来,实现更强大的功能。

🔗 创建Shell管道

在Linux中,管道(Pipeline)是一个强大的功能,它允许你将一个命令的输出直接作为另一个命令的输入。这通过竖线符号 | 实现。

管道的基本语法是:
command1 | command2

这意味着 command1 的输出会传递给 command2 进行处理。

例如,如果你想列出当前目录的所有文件,但只显示其中包含“log”字样的行,可以使用:
ls -la | grep log

这个例子中,ls -la 命令列出了所有文件的详细信息,然后通过管道 | 将结果传递给 grep log 命令进行过滤,最终只显示包含“log”的行。


学会了在本地使用命令和管道后,我们常常需要连接到远程服务器进行操作,这就需要用到SSH。

🔐 理解并使用SSH

SSH(Secure Shell)是一种网络协议,用于在不安全的网络上安全地进行远程登录和其他网络服务。

你可以把它想象成一把加密的钥匙,让你能够从本地计算机安全地访问和控制另一台远程计算机(通常是Linux服务器)。

基本的SSH连接命令是:
ssh username@remote_host_address

例如,要连接到IP地址为 192.168.1.100 的服务器,用户名为 data_engineer,命令如下:
ssh data_engineer@192.168.1.100

输入该命令并验证密码后,你的终端就会连接到远程服务器,之后输入的所有命令都将在那台服务器上执行。这对于管理云服务器、部署应用或访问远程数据至关重要。


📚 本周总结

在本节课中,我们一起学习了Linux操作入门的基础知识。

我们首先明确了本周的目标是掌握Linux Shell的核心用法。接着,我们认识了什么是Linux Shell及其在数据工程中的重要性。然后,我们分解了Shell的关键组成部分:终端、Shell程序、命令和文件系统。在此基础上,我们学习了如何通过管道符号 | 将多个命令串联起来,构建强大的数据处理流程。最后,我们介绍了SSH协议,学会了如何使用 ssh 命令安全地连接到远程服务器进行管理。

掌握这些基础技能,是你迈向高效数据工程实践的重要第一步。

005:Linux Shell简介 🐧

在本节课中,我们将要学习Linux Shell。Linux Shell是现代云计算和机器学习中一个极其重要的核心组件。它是一个如今在云计算领域无处不在的环境。如果你从事数据工程或数据科学工作,几乎不可能不接触到Linux Shell。即使是在Jupyter Notebook或容器中,你也很可能会遇到某种Linux环境。它始于90年代的一个学生项目,如今已发展成为拥有超过2800万行代码的庞大系统。接下来,让我们深入了解Linux Shell的细节,探讨它如何能帮助你。

什么是Linux?如何开始?

首先,我们将介绍什么是Linux以及如何开始使用它。Linux之所以重要,是因为它作为一个执行环境,在你编写的任何代码中都扮演着普遍存在的角色。

介绍Shell环境

上一节我们介绍了Linux的基本概念,本节中我们来看看Shell环境。Shell中有哪些关键特性是重要的?你可以通过自动化或快捷方式来简化哪些操作?

以下是关于Shell的一些关键点:

  • Shell是一个命令行界面,允许用户与操作系统交互。
  • 它支持通过脚本实现任务自动化。
  • 用户可以利用别名和快捷键来提高工作效率。

不同的云Shell环境

了解了Shell的基础后,我们来看看不同的云Shell环境。有趣的是,一旦你熟悉了一种云Shell环境,通常可以很容易地切换到另一种。

以下是几种常见的云Shell环境:

  • AWS Cloud 9:一个基于云的集成开发环境,包含Shell功能。
  • AWS CloudShell:AWS提供的浏览器内Shell环境。
  • Google Cloud Shell:Google Cloud平台提供的浏览器内Shell环境。
  • Azure Cloud Shell:微软Azure平台提供的浏览器内Shell环境。
  • 本地终端:你自己机器上的终端程序。

我们将比较这些不同的环境,看看如何优化各自的工作流程,以获得最高效的体验。

Linux的历史

最后,我们来谈谈Linux的历史。Linux本身已经存在了相当长的时间。它由赫尔辛基大学的Linus Torvalds在1991年创立。当时他对专有软件感到沮丧,于是开发了如今有史以来最成功的开源项目之一。

本节课中,我们一起学习了Linux Shell的基础知识,包括其定义、重要性、Shell环境的关键特性、不同的云Shell实现选项,以及Linux项目的起源和历史。掌握这些内容,是高效进行数据工程和云计算工作的第一步。

006:Linux安装与访问 🐧

在本节课中,我们将学习如何开始使用Linux操作系统。Linux是全球最流行的操作系统之一。如果你要部署代码,它很可能会被部署到云端的Linux环境或你自己的物理数据中心。Ubuntu和Red Hat Linux是两个最流行的版本。此外,还有云服务商特定的Linux版本,例如Amazon Linux。本教程将讨论如何开始使用Linux,如何安装它,以及你可以选择哪些选项,例如在桌面上使用Parallels、在云端启动虚拟机,甚至使用Docker在你的物理工作站上运行Linux。我们将尝试其中几种不同的方式,然后你将能够选择最适合你的Linux发行版和技术。让我们开始吧。

安装Linux的方法 💻

上一节我们介绍了Linux的重要性,本节中我们来看看如何安装Linux。这是使用Linux操作系统的第一步。安装Linux有多种方法,你可以根据自己的需求和环境选择最合适的一种。

以下是几种主要的安装选项:

  • 虚拟机:这是一种非常流行的方式。你可以在云端或本地桌面(使用Parallels、VMware等软件)上运行Linux虚拟机。对于数据工程师来说,这很常见,因为最终你的代码很可能就部署在这样的环境中。
  • 容器:使用Docker等软件可以在你的桌面机器上运行Linux容器。这是开始使用Linux的一个绝佳方式,它为使用容器进行开发提供了非常好的工作流程。
  • 云平台:直接在云平台(如AWS、Google Cloud)上启动Linux虚拟机或使用Kubernetes部署容器。这也是非常普遍的做法,例如使用Amazon EC2实例或Google Kubernetes引擎。
  • 物理硬件:你可以在笔记本电脑或服务器上直接安装Linux。例如,我有一台安装了Linux的戴尔笔记本电脑,用于测试特定的配置。

Linux操作系统的使用方式多种多样,由你选择安装的位置。接下来,我们将逐步展示这些方法。

方法一:使用桌面虚拟机(如Parallels) 🖥️

现在,让我们看看如何使用VMware Parallels Desktop这类软件。Parallels Desktop允许我使用不同的虚拟机。例如,我这里有一个Ubuntu 18.04的虚拟机。如果你刚开始使用Linux,这通常是一个起点。

需要注意几点:在配置中,我可以指定虚拟机使用6个处理器核心和10GB内存。你还可以调整图形设置等,让这个“访客”系统在你的桌面上获得完整的体验。

对于数据工程师,你通常会打开一个终端。例如,我可以搜索并打开终端,然后运行ls命令来列出文件,或者运行top命令查看系统进程。我也可以使用cd命令切换到桌面目录。

cd ~/Desktop

在这里,由于Parallels的共享文件夹功能,如果我创建一个文件:

echo "hello" > test_file.txt

你会发现这个文件也出现在我宿主机的桌面上。通过Ubuntu虚拟机,你可以安装软件、更新系统,完全模拟运行Linux的体验。这是大多数人初次使用Ubuntu的方式。

除了使用预建的虚拟机,你也可以轻松添加自己的。在Parallels中点击“+”号,可以选择从微软获取Windows 10、从DVD安装,或者在“免费系统”中下载Ubuntu Linux。例如,选择下载Ubuntu 20.04.2,点击下载后,软件会解压并引导你完成安装过程。

安装过程中,通常需要你设置一个密码。完成这些步骤后,Ubuntu就设置好了,你可以开始使用了。整个过程非常简单。

方法二:使用Docker容器 🐳

上一节我们体验了桌面虚拟机,本节中我们来看看更轻量级的方法——Docker。Docker是你安装在桌面上的软件。在配置中,你可以设置Docker是否随登录启动,分配多少CPU和内存资源,以及是否启用Kubernetes。

因为Docker正在运行,我可以在终端(这里是macOS的终端)输入命令来查看运行中的容器:

docker ps

目前没有容器在运行。现在,让我们运行一个Ubuntu容器:

docker run -it ubuntu

这个命令会检查本地是否有Ubuntu镜像,如果没有则会拉取。运行后,我就进入了容器的Shell。输入uname -a可以看到,虽然宿主机是macOS,但我已经进入了Linux环境。

uname -a

这可能是使用Ubuntu或其他Linux操作系统最简单的方法之一。再次运行docker ps,现在可以看到一个容器正在运行。Docker方法非常便捷,我经常使用它。

方法三:使用云平台(如AWS) ☁️

现在让我们转向第三种方法,即直接在云端启动Ubuntu虚拟机或其他Linux虚拟机。我将使用亚马逊云控制台。

登录AWS控制台后,我可以快速启动一个虚拟机。这是云计算的常规操作:进入控制台,启动虚拟机。在系统选择界面,我可以选择macOS或其他系统,但作为数据工程师,我会选择Linux,因为这才是代码最终部署的环境。

选择Linux镜像后,点击“审核和启动”,确认设置后即可启动实例。我可以看到实例状态从“pending”变为“running”。然后,我就可以通过SSH协议连接到这台机器。

此外,AWS还提供了Cloud9,这是一种专门为云端开发设计的环境,使用起来更简单。创建环境时,我可以选择Linux,并使用一个微型实例。我推荐使用Amazon Linux,如果你在AWS上工作的话。创建完成后,它会打开一个Shell,这就是一个Linux终端。从这里,我可以开始开发软件、运行命令,进行任何数据工程相关的工作。


总结

本节课中我们一起学习了开始使用Linux的几种主要方法。我们介绍了在桌面使用虚拟机软件(如Parallels)来模拟完整的Linux环境,使用Docker容器快速获得轻量级的Linux Shell,以及直接在云平台(如AWS)上启动Linux虚拟机或使用Cloud9开发环境。每种方法都有其适用场景,你可以根据自己的开发、测试或部署需求选择最合适的一种。掌握这些访问Linux的方式,是成为一名数据工程师的重要基础。

007:GitHub Codespaces入门 🚀

在本节课中,我们将学习如何使用GitHub Codespaces这一基于云的开发环境。我们将了解其核心优势,并通过实际操作演示如何在其中使用Bash终端进行基本的文件操作和环境配置。


概述

GitHub Codespaces提供了一个完全在云端运行的开发环境。它的主要优势在于,你可以直接与代码仓库交互,并使用一个预先配置好的开发环境,无需在本地进行任何复杂的安装和设置。这对于学习Bash、进行软件开发或数据工程项目来说,是一个理想且高效的起点。


云开发环境的优势

上一节我们提到了云开发环境的概念,本节中我们来看看它的具体优势。

使用GitHub Codespaces这类云环境,你可以直接与代码仓库对话,同时也能使用一个功能完整的开发环境。这与AWS、GCP或Azure提供的服务类似,你可以在其中访问它们的服务并使用其开发工具。

这代表了一种软件开发的新范式。我们将直接进入GitHub Codespaces,探索那些能让你高效工作的功能。


创建并进入Codespace

以下是创建GitHub仓库并启动Codespace的步骤。

  1. 在GitHub上,点击“New repository”创建一个新仓库。
  2. 将仓库命名为“GitHub-Codespaces-Demo”,并添加描述。
  3. 勾选“Add a README file”选项。
  4. 在“.gitignore”模板中选择“Python”,以忽略不必要的文件。
  5. 点击“Create repository”完成创建。

创建仓库后,你可以直接在GitHub环境中进行软件开发。这是数据工程、软件工程和机器学习工程的基础。

点击仓库页面的“Code”按钮,你可以选择将代码克隆到本地。但我们选择“Codespaces”选项卡。这里提示:“欢迎使用云端编辑。你无需本地克隆和设置即可编辑、调试和运行你的仓库。”这确实是软件开发的未来。

如果你想学习Bash,这里是最好的起点之一。你可以通过创建一个这样的临时仓库,立即开始在Codespace中构建项目。


探索Bash终端

现在我们已经进入了Codespace,本节中我们来看看如何使用其中的Bash终端。

这个环境已经为你编写代码所需的一切做好了准备。对于软件工程师来说,这是一个开始实践和探索的理想场景。让我们看看在终端里能做什么。

首先,我喜欢输入以下命令来查看操作系统信息:

uname -a

这个命令会显示操作系统详情,例如“Ubuntu 18.04”,以及系统创建时间等信息。

接下来,运行ls命令来列出当前目录下的文件:

ls

这就像打开房间的灯,让你看清里面有什么。同时,输入pwd可以查看当前所在的完整路径。

如果你想创建一个新文件,可以使用touch命令:

touch new_file.txt

执行后,文件管理器中会出现这个新文件。点击它,你可以进行编辑。例如,输入“This is really cool.”并保存。

如果要从终端读取这个文件的内容,可以使用cat命令:

cat new_file.txt

Tab键可以自动补全文件名。这是一个非常常见的工作流程:用uname了解系统,用ls查看目录,用touch创建文件。

注意,touch命令是幂等的。这意味着无论你运行多少次,效果都相同。如果文件不存在,它会创建文件;如果文件已存在,它不会做任何事。Linux的许多工具都采用这种设计,提供稳定可靠的小功能。


目录导航与界面定制

上一节我们介绍了基本的文件操作,本节我们来学习如何切换目录和个性化你的工作区。

使用cd命令可以切换目录。例如,进入临时目录:

cd /tmp

然后使用pwd确认当前位置。如果想返回硬盘的根目录,可以输入:

cd /

如果想回到工作区,可以输入cd /workspaces/GitHub-Codespaces-Demo

在GitHub Codespaces中,你可以通过“View”菜单下的“Appearance”和“Layout”选项来调整界面布局和外观,这是很常见的操作。

你还可以配置终端,例如设置不同的快捷键绑定或为不同语言进行定制。在这个云环境中,你拥有文件资源管理器(可以开关)、文件搜索、代码调试、源代码控制管理等功能。

例如,你可以搜索并安装插件。如果想找一个Bash代码格式化工具,可以搜索“bash beautifier”。你也可以通过命令面板添加各种功能。

核心要点是,使用基于云的开发环境,你可以自由地做任何想做的事。


高级终端操作与环境配置

现在我们已经熟悉了基本操作,本节中我们来看看一些更高级的终端技巧和环境配置方法。

在Bash工作区中,你可以拆分终端,让多个终端并排显示,这是一种常见做法。只需点击终端界面上的拆分图标即可。

你可能会想编辑环境配置。注意到终端提示符有各种炫酷的显示吗?你可以通过编辑~/.bashrc文件来修改提示符的样式,很多人都会这么做。

另一个实用的技巧是设置别名。别名就像是命令的快捷方式或书签。例如,创建一个快速进入/tmp目录的别名:

alias tmptop='cd /tmp'

这样,以后只需输入tmptop就能直接跳转到/tmp目录。

创建别名后,需要让当前环境重新加载配置才能生效。有两种方法:

  1. 打开一个新的终端窗口。
  2. 在当前终端使用source ~/.bashrc命令。

之后,输入alias命令就能看到新设置的tmptop别名了。输入tmptop,你会发现它成功地将工作目录切换到了/tmp

我强烈推荐使用云开发环境,如GitHub Codespaces或AWS Cloud Shell。这能让你放心地动手实践Bash,配置环境,而无需担心搞坏任何东西,因为这些环境都是可以随时丢弃和重建的。


创建并运行你的第一个Bash脚本

最后,让我们以传统的方式,创建一个“Hello World”脚本作为结束。

首先,使用touch命令创建一个脚本文件:

touch hello.sh

然后,编辑这个文件,输入以下内容:

echo "Hello world"

保存后,如何运行这个Bash脚本呢?很简单,使用bash命令:

bash hello.sh

终端会输出:“Hello world”。就这样,你成功在Codespaces开发环境中创建并运行了第一个脚本。

你可以自己多玩玩,尝试使用我们在本演示中展示过的各种命令。


总结

本节课中,我们一起学习了GitHub Codespaces这一强大的云开发环境。我们从创建仓库和启动Codespace开始,逐步探索了Bash终端的基本命令(如uname, ls, pwd, touch, cat, cd)、目录导航、界面定制、终端拆分、环境配置(如编辑.bashrc和设置别名),最后创建并运行了一个简单的Bash脚本。

通过使用这种预先配置好、可随时重置的云环境,你可以无风险地练习和掌握Bash及软件开发的基础技能,这是迈向数据工程和软件工程的重要一步。

008:Linux Shell环境对比 🖥️

在本节课中,我们将学习如何比较不同的终端环境,包括本地终端和基于云的Shell环境。我们将探讨如何根据具体任务选择合适的工具,并了解如何通过安装第三方工具来增强本地Shell的功能。


本地终端环境:macOS

上一节我们介绍了终端环境的基本概念,本节中我们来看看具体的本地环境示例。以macOS(OS X)为例,其自带的终端功能强大,但用户可以通过安装第三方工具来获得更佳的体验。

以下是两个在macOS上非常实用的增强工具:

  1. Homebrew:这是一个macOS上缺失的软件包管理器。开发者常用它来安装开源项目文档中提到的各种工具。其核心作用是补充macOS系统预装软件的不足。

    • 安装命令示例/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    • 使用示例:系统自带的 top 命令可以查看进程,而通过Homebrew安装的 htop 提供了更强大的功能,如彩色显示、树状视图和更直观的交互。
      • 安装:brew install htop
      • 运行:htop
  2. Oh My Zsh:这是一个流行的Zsh(Z Shell)配置框架。Zsh本身提供了强大的自动补全等功能,而Oh My Zsh使其更易于配置和使用。

    • 安装命令示例sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
    • 功能示例:启用auto_cd功能后,无需输入cd命令,直接输入目录名即可切换。输入波浪号~可直接返回用户主目录。

核心思想是:你可以通过安装像Homebrew和Oh My Zsh这样的第三方工具,来显著增强你在macOS上的本地开发体验。


云端Shell环境

了解了本地环境后,我们来看看云端环境。所有主流云服务提供商(如AWS、Google Cloud、Azure)都提供了类似的云端Shell环境。这对于在对应云平台上进行开发和操作非常方便。

以下是各云平台Shell环境的共同特点和使用方法:

  1. 通用模式:点击云控制台中的“Cloud Shell”图标,平台会为你临时配置一个免费的Shell环境。这个环境预装了与该云平台交互所需的所有命令行工具(Bash工具)。

  2. 环境定制:与本地环境一样,你可以通过编辑Shell的配置文件(如 ~/.bashrc)来定制环境,例如设置别名或环境变量。

    • 编辑配置文件nano ~/.bashrcvim ~/.bashrc
    • 添加自定义命令:在文件中添加一行,例如 echo "欢迎使用我的Shell"
    • 使配置生效:保存文件后,运行 source ~/.bashrc 或新开一个终端窗口,即可看到效果。

  1. 平台专用命令示例
    • AWS Cloud Shell:可以使用 aws s3 ls 命令列出账户中的所有S3存储桶。结合 wc -l 命令可以计数。
      • 代码示例aws s3 ls | wc -l
    • Google Cloud Shell:可以使用 gcloud 开头的系列命令管理资源。
    • Azure Cloud Shell:可以使用 az 开头的系列命令与各项服务交互。

关键在于:一旦你掌握了Bash,你就掌握了驾驭多种云环境和本地环境的钥匙。它们虽然预装工具不同,但核心的Shell操作逻辑是相通的。


本节课中我们一起学习了如何对比和选择不同的Linux Shell环境。我们探讨了如何通过Homebrew和Oh My Zsh等工具增强本地macOS终端的功能,也了解了AWS、Google Cloud和Azure等云平台提供的Cloud Shell环境及其基本用法。记住,根据你当前的工作场景(本地开发或特定云平台操作)选择最合适的环境,是提高效率的关键。

009:编写Hello World Bash脚本 🐚

在本节课中,我们将学习如何创建一个最简单的Bash脚本——“Hello World”。我们将从创建一个空文件开始,逐步添加脚本内容,并最终使其成为一个可执行的实用工具。通过这个过程,你将理解Bash脚本的基本结构和运行原理。


创建脚本文件

首先,我们需要创建一个空的脚本文件。这通常可以通过 touch 命令来完成。touch 命令会创建一个指定名称的空文件,如果文件已存在,则更新其时间戳。

以下是创建名为 hello.sh 的脚本文件的命令:

touch hello.sh

执行此命令后,你将在当前目录下看到一个名为 hello.sh 的新文件。你可以使用任何文本编辑器(如Vim或VS Code)来编辑它。


理解脚本结构

一个基本的Bash脚本包含几个关键部分。上一节我们创建了文件,本节中我们来看看脚本内部的结构。

Shebang行

脚本的第一行通常是 Shebang行。它的作用是告诉操作系统使用哪个解释器来执行这个脚本。对于Bash脚本,Shebang行如下:

#!/usr/bin/bash

这行代码以 #! 开头,后面跟着Bash解释器的路径。这样,当你直接运行脚本时,系统就知道应该用Bash来解析它,而无需你在命令行中手动输入 bash 命令。

脚本主体

Shebang行之后,就是脚本要执行的实际命令。在Bash脚本中,你可以写入任何在终端中能直接运行的命令。

例如,一个打印“Hello World”的脚本主体就是:

echo "Hello World"

编写脚本的优势在于,你可以将一系列复杂的终端命令保存到一个文件中,实现自动化。


使脚本可执行

创建并编写好脚本后,它默认是不可执行的。我们需要修改文件的权限,使其可以被运行。

查看当前权限

在修改权限前,可以使用 ls -l 命令查看文件的详细权限信息。

ls -l hello.sh

输出结果中,开头的字符表示文件类型和权限。例如 -rw-r--r-- 表示文件所有者可以读写,但所有人都不能执行(缺少 x 标志)。

添加执行权限

使用 chmod(change mode)命令可以修改文件权限。+x 标志表示为文件添加执行权限。

chmod +x hello.sh

执行此命令后,再次运行 ls -l hello.sh,你会看到权限中已经包含了 x,例如 -rwxr-xr-x。现在,所有用户都可以执行这个脚本了。


运行脚本

完成以上所有步骤后,你就可以运行你的第一个Bash脚本了。

在脚本所在目录,使用以下命令运行它:

./hello.sh

命令开头的 ./ 表示在当前目录下寻找可执行文件 hello.sh 并运行。如果一切正常,终端将输出 “Hello World”。

这种方法的强大之处在于,无论脚本是用Bash、Python还是Perl编写的,只要正确设置了Shebang行和执行权限,都可以用相同的方式运行和集成到更大的工作流中。


总结

本节课中我们一起学习了如何从零开始创建一个Bash脚本。我们首先使用 touch 命令创建了一个脚本文件,然后理解了脚本的核心结构:Shebang行 用于指定解释器,脚本主体用于存放要执行的命令。接着,我们使用 chmod +x 命令赋予了脚本执行权限。最后,我们使用 ./script_name.sh 的方式成功运行了脚本,并看到了输出结果。掌握这些基础步骤,是使用Bash进行自动化任务和数据工程工具开发的第一步。

010:Linux常用终端命令 🐧

在本节课中,我们将学习Linux终端命令的核心结构与常用操作。掌握这些命令是成为Bash专家的基础,能帮助你高效地管理文件、查看系统状态和处理进程。

命令核心结构

Linux命令通常遵循一个核心结构:命令选项目标。有时还会包含目的地。一个通用的表示方式是:

命令 [选项] [目标] [目的地]

一个典型的例子是 ls 命令。你可以输入选项 -lh,并指定目标目录,例如 /tmp

ls -lh /tmp

这是一种极其常见的命令结构。理解它之后,你就能在Linux上运行任何类型的命令。

接下来,让我们通过实践一些基础命令来练习如何成为Bash专家。

常用命令实践

以下是成为Bash专家的最佳方式之一:动手尝试各种命令。

首先,我们从几个时间和日期命令开始。在终端中输入 cal,它会清晰地显示当前月份的日历,并用方框高亮标出今天的日期。输入 date 命令则会显示当前的日期和时间。

许多有用的命令都内置在Bash中。你可以使用 which date 来查看 date 命令的存放位置(通常在 /usr/bin)。我们可以进入该目录查看所有可用的命令:

cd /usr
ls -l

这是学习Bash的好方法:逐一尝试这些命令,通过实践你终将成为专家。

现在,让我们更聚焦一些,回到工作目录。使用 df -h 命令可以查看磁盘使用情况,-h 选项使其以人类可读的格式(如GB、MB)显示。这对于数据工程师来说是一个非常重要的命令。

另一个有用的命令是 du -sh,它可以显示指定目录(如 /usr/bin/*)下各工具的大小,帮助你识别哪些可能是大型工具。同样,你也可以在当前工作目录中使用它来查找大文件。

top 命令在运行程序时非常有用,它能实时显示系统的进程状态。按 q 键可以退出。

探索Shell环境

探索Shell环境同样重要。pwd 命令可以显示当前工作目录的完整路径,这在脚本编写或设置环境变量时非常方便。

ls -lah 命令会以长列表格式显示文件信息,包括Unix权限、所属用户/组以及文件大小,是浏览目录的得力工具。

cd 命令用于在文件系统中导航。例如,cd /tmp 进入 /tmp 目录,cd ~ 则返回用户主目录。

which 命令用于定位可执行文件。例如,which python3 会显示Python3解释器的具体路径。这在系统存在多个版本时(如Python 2和Python 3)尤其有用,可以帮助你确认正在使用的版本。

查看文件内容

查看文件内容是非常常见的操作。less /etc/passwd 命令会分页显示 /etc/passwd 文件的内容,你可以使用上下箭头键滚动浏览。而 cat /etc/passwd 命令则会一次性将整个文件内容打印到终端。对于大文件,less 是更好的选择。

wc -l 命令用于统计文件的行数。例如,wc -l /etc/passwd 会显示该文件有多少行。

修改文件与目录

现在,让我们看看如何修改文件和目录。

touch new_file.txt 命令可以创建一个新的空文件。之后,你可以使用文本编辑器向其中添加内容。

mkdir new_dir 命令用于创建新目录。

mv 命令用于移动或重命名文件。其基本结构是 mv [源] [目标]。例如,mv new_file.txt new_dir/ 将文件移动到新目录中。同样,mv old_name.txt new_name.txt 可以重命名文件。

mkdir -p 命令可以一次性创建多级目录。例如,mkdir -p a/b/c/d 会创建完整的目录树。

最后,rm -rf 命令可以递归地强制删除整个目录及其内容。使用此命令务必小心。

管理进程

进程管理是另一个关键技能。ps 命令可以列出当前运行的进程。例如,在一个终端运行 python 交互界面,在另一个终端使用 ps | grep python 就能找到对应的Python进程。

我们可以创建一个简单的脚本 sleeper.sh,它包含一个无限循环,不断打印“hanging out...”。运行它:

./sleeper.sh

Ctrl+C 可以终止该进程。按 Ctrl+Z 则可以挂起(停止)该进程。之后,使用 jobs 命令可以查看被挂起的作业,使用 fg %1 命令可以将编号为1的作业恢复到前台继续运行。

另一种方式是在启动命令时加上 & 符号,使其在后台运行:

./sleeper.sh &

对于后台进程,你需要先用 fg %1 将其调回前台,然后再用 Ctrl+C 终止。

向专家学习

成为专家的一个好方法是学习优秀开源项目的实践。例如,查看官方Python Docker镜像的Dockerfile,你会发现其中大量使用了我们刚才练习过的命令(如 rm -rf),以及 && 这样的逻辑操作符(表示前一个命令成功后才执行下一个)。通过研究这些成熟项目的脚本,你可以学到很多Bash编程的最佳实践。

总结

本节课我们一起学习了Linux终端命令的核心结构,并实践了日期查看、文件浏览、系统监控、目录导航、文件操作和进程管理等常用命令。记住,熟练使用Bash的关键在于不断练习和探索。通过理解命令的基本模式并模仿专家们的写法,你将能够高效地利用命令行工具来完成数据工程中的各项任务。

011:Shell管道入门 🚀

在本节课中,我们将要学习Shell管道的基础知识。Shell管道是连接多个命令的强大工具,它允许你将一个命令的输出作为另一个命令的输入,从而实现数据的连续处理。我们将从概念入手,逐步深入到实际应用。

什么是Shell管道? 💡

上一节我们介绍了Shell的基本概念,本节中我们来看看Shell管道的核心定义。Shell管道本质上是一系列“管道”,数据从一端流入,经过处理,再从另一端流出。你可以把它想象成家中的水管系统:水(数据)从源头进入,经过洗碗机(处理程序)的处理,然后被送往污水处理厂(输出或下一个处理环节)。

核心概念命令1 | 命令2 | 命令3 ...
在这个公式中,竖线 | 是管道符号,它将左侧命令的标准输出连接到右侧命令的标准输入。

Shell管道的工作原理 ⚙️

理解了基本概念后,我们来探讨其工作机制。Shell管道在实践中是如何运作的呢?它的核心在于输入、处理和输出这三个环节。

以下是其工作流程的关键步骤:

  • 输入:数据从文件、用户输入或上一个命令进入管道。
  • 处理:数据被管道中的命令(如 grepsortawk)进行处理或转换。
  • 输出:处理后的数据被发送到屏幕、文件,或作为输入传递给管道中的下一个命令。

整个过程是流式的,这意味着数据可以立即被处理,而无需等待前一个命令完全结束。

实践:Shell管道示例 🛠️

理论需要实践来巩固。现在,我们通过一系列例子来看看如何在实际中使用Shell管道。这些例子将帮助你掌握如何格式化代码,以便与内置的Unix工具良好协作。

以下是一个经典示例,演示了如何查看文件、筛选内容并进行统计:

# 示例:统计一个日志文件中包含“ERROR”关键词的行数
cat application.log | grep “ERROR” | wc -l

让我们分解这个命令:

  • cat application.log:读取 application.log 文件的内容。
  • grep “ERROR”:从上游输入中筛选出包含“ERROR”字符串的行。
  • wc -l:统计上游输入的行数。

通过这个管道,我们无需创建任何中间临时文件,就高效地完成了数据的读取、过滤和计数。

总结 📝

本节课中我们一起学习了Shell管道。我们从“一系列管道”这个比喻开始,理解了Shell管道是关于输入、处理和输出的流程。接着,我们探讨了其工作原理,并通过一个统计日志错误行数的具体例子,实践了如何将多个命令用管道符 | 连接起来,构建高效的数据处理流程。掌握Shell管道是提升命令行效率和数据处理能力的关键一步。

012:什么是Shell管道 🚰

在本节课中,我们将要学习Shell编程中的一个核心概念——管道。管道是连接不同命令的桥梁,它允许我们将一个命令的输出作为另一个命令的输入,从而构建出强大的数据处理流程。

概述

理解Bash管道的一个好方法是类比其他系统。一个很好的例子就是你家的供水系统。

管道概念类比:家庭供水系统 🏠

上一节我们提到了类比的概念,本节中我们来看看具体的例子。家庭供水系统清晰地展示了管道的三个核心阶段:输入、处理和输出。

输入阶段:城市供水是源头。人们从这里获取饮用水,也为家中的各种电器供水。如果没有这个核心的输入,就无法完成诸如使用洗碗机、洗衣服或洗热水澡等高级功能。同样,在构建Bash脚本时,你也需要输入。这个输入至关重要,因为你需要有东西来进行处理。

处理阶段:在这个例子中,我们以洗碗机为例。它也可以是淋浴、洗衣机、水疗池或游泳池等。这就是处理阶段。Bash管道的关键在于,你通常会将某些东西送入一个进程并进行操作,例如翻译某些内容、反转字符或提取特定的关键短语。

输出阶段:这里是下水道阶段,也就是输出。在这个具体例子中,盘子清洗完毕后,你希望含有食物残渣的水排入污水系统以便处理。这在Bash场景中是一个类似的概念。输出可能不是污水系统,而可能是写入一个输出文件,例如 output.txt。或者,如果我们想要一个更贴切的例子,这可能是类似 /dev/null 的东西,这非常常见,用于将你不希望看到结果的输出导入其中。因此,这与家庭管道和Bash管道非常相似。

从理论到实践:一个Bash管道示例 💻

现在,让我们遵循刚才讨论的房屋和管道范式,来看下面这个具体例子。

这是一个命令:ls | wc -l。请注意,我首先列出了某个目录中的一些文件内容。接下来,我将结果通过管道传输到下一个部分。所以,这里是起点,这是进程一

进程二是:然后我会计算目录中出现的条目数量。想象一下,例如我在 /bin 目录中,该目录下可能有40个不同的实用程序。如果我将所有这些条目通过管道传输到 wc -l 这个命令,它实际上会返回40。

这个结果是一个我可以进一步处理的结果。在示例中,我们看到它可能被输出到一个文件,比如 outfil.txt。在这个特定例子中,这样做的好处是,我可以将这些结果交给其他人,也许我可以通过电子邮件发送这些结果,或者通过短信等方式以某种方式处理它。

Bash中的这种管道概念确实与进入你家的水非常相似:水进入洗碗机,清洗一些东西,然后排到别处。因此,一旦你理解了这个概念,就很容易用Bash管道构建复杂的工具。

总结

本节课中我们一起学习了Shell管道的核心概念。我们通过家庭供水系统的类比,理解了管道在数据流中的三个关键角色:输入处理输出。并通过 ls | wc -l 这个具体命令,看到了如何将一个命令(ls)的输出作为另一个命令(wc -l)的输入,从而完成计算文件数量的任务。掌握管道是编写高效Bash脚本和进行命令行数据处理的基石。

013:Shell管道实例演练 🐚

在本节课中,我们将通过一系列实例,深入探索Shell中管道的强大功能。我们将学习如何将多个命令连接起来,处理数据流,以及利用管道和重定向来高效地完成文件操作和日志分析等常见任务。

探索管道的力量

上一节我们介绍了管道的基本概念,本节中我们来看看如何在实际操作中运用它。一个经典的管道使用例子是结合 lswc 命令。

首先,我们使用 ls 命令列出 /usr/bin 目录下的所有文件。这个目录包含大量文件,手动计数非常困难。

ls /usr/bin

为了统计文件数量,我们可以将 ls 的输出通过管道 | 传递给 wc -l 命令。wc -l 会计算输入的行数。

ls /usr/bin | wc -l

执行后,我们得知该目录下有 1059 个条目。这避免了手动滚动和计数的繁琐。

我们还可以更进一步,利用命令历史记录和输出重定向。使用上箭头键可以调出刚才执行的命令,然后我们可以将其输出重定向到一个文件中。

ls /usr/bin | wc -l > bin_count.txt

> 操作符将命令的标准输出重定向到 bin_count.txt 文件,而不是显示在终端上。查看文件内容,可以看到统计结果已被保存。

cat bin_count.txt

这种模式非常常见:第一步列出文件,第二步通过管道计数,第三步将结果重定向到文件。

条件执行操作符

现在让我们转向另一个操作:条件执行。这在 Dockerfile 或 Makefile 等场景中非常常见。

条件执行使用 && 操作符。它表示只有前一个命令成功执行(返回退出状态码0),后一个命令才会运行。

例如,尝试列出一个不存在的目录会失败:

ls /non_existent_directory

这个命令会报错。如果我们将其与 touch 命令用 && 连接:

ls /non_existent_directory && touch new_file.txt

由于 ls 命令失败,touch 命令不会执行,因此 new_file.txt 文件不会被创建。

现在,我们执行一个会成功的命令:

ls README.md && touch new_file.txt

因为当前目录存在 README.md 文件,ls 命令成功,所以 touch 命令得以执行,创建了 new_file.txt。再次执行(使用上箭头键调出命令)会创建第二个文件 new_file2.txt

ls README.md && touch new_file2.txt
ls

通过 ls 可以看到两个新文件都被创建了。这种链式操作确保了一系列命令必须全部成功,如果中间任何一步失败,则后续步骤不会执行,这对于构建脚本和自动化流程至关重要。

处理多行文本的管道

接下来,我们看一个更复杂的例子:处理多行文本。首先,我们创建一个包含多行文本的变量。

STR='1. This is a line\n2. This is a line\n3. This is a line'

使用 echo 命令并加上 -e 选项(用于解释反斜杠转义字符)来查看变量内容:

echo -e "$STR"

输出为三行独立的文本。

我们可以将此输出重定向到一个文件:

echo -e "$STR" > lines.txt

现在,我们可以对文件内容进行各种管道操作。例如,使用 cat 查看文件,然后通过管道传递给 sort -r 进行反向排序:

cat lines.txt | sort -r

原本以“1.”开头的行现在变成了以“3.”开头。

如果文件内容很多,我们可以再管道传递给 less 命令进行分页查看:

cat lines.txt | sort -r | less

less 视图中,可以使用上下箭头键浏览,按 q 键退出。这是查看和处理大型日志文件的常用模式。

另一个常见操作是使用 grep 进行模式匹配。例如,我们只想查看包含数字“3”的行:

cat lines.txt | grep 3

grep 会高亮显示匹配的行。我们也可以将匹配的结果保存到新文件:

cat lines.txt | grep 3 > matched_line.txt
cat matched_line.txt

这演示了如何从数据流中精确提取所需信息。

文件的重定向与追加

之前我们使用了 > 进行输出重定向,它会覆盖目标文件。有时我们需要追加内容,而不是覆盖。

使用 > 会覆盖文件:

echo “First line” > append_file.txt
cat append_file.txt

文件内容为“First line”。如果再次使用 >

echo “Second line” > append_file.txt
cat append_file.txt

文件内容被替换为“Second line”,“First line”丢失了。

要追加内容,需要使用 >> 操作符:

echo “Third line” >> append_file.txt
cat append_file.txt

现在文件包含了“Second line”和“Third line”两行内容。这在持续记录日志或错误信息时非常有用。

丢弃错误输出

在脚本中,我们有时希望忽略命令的错误信息,避免其干扰正常输出。标准错误流(stderr)的文件描述符是 2

例如,一个失败的命令会产生错误输出:

ls /non_existent_directory

为了丢弃这些错误信息,我们可以将标准错误重定向到 /dev/null(一个特殊的设备文件,会丢弃所有写入的数据):

ls /non_existent_directory 2> /dev/null

现在,终端上不再显示错误信息。这是一种清理脚本输出的有效模式。

查看文件头部与尾部

在数据工程中,经常需要查看日志文件。tailhead 命令是这方面的利器。

tail 命令默认显示文件的最后10行。使用 -f 选项可以实时追踪文件的新增内容(常用于监控日志):

tail -f /var/log/syslog

(按 Ctrl+C 退出追踪模式)

如果只想查看最后几行,可以使用 -n 选项指定行数:

tail -n 2 /var/log/dpkg.log

这会显示该日志文件的最后两行。

相应地,head 命令用于查看文件的开头部分:

head -n 1 /var/log/dpkg.log

这会显示文件的第一行。

高效使用命令历史

Shell 会记录你执行过的命令历史。使用 history 命令可以查看。

history

历史记录可能很长,这时管道就派上用场了。我们可以用 less 来分页查看:

history | less

less 中可上下滚动,按 q 退出。

更常见的是,我们使用 grep 在历史中搜索特定命令:

history | grep “ls”

这会列出所有包含“ls”的历史命令。

找到想重复执行的命令后,有一个快捷方式:使用 ! 加历史行号。
例如,history 显示你想运行的命令在第70行,只需输入:

!70

Shell 就会重新执行该行命令。

还有两个特别有用的快捷方式:

  • !!:重复执行上一条命令。
  • !$:代表上一条命令的最后一个参数。

此外,编辑长命令时,快捷键可以提高效率:

  • Ctrl + A:光标跳到行首。
  • Ctrl + E:光标跳到行尾。
  • Ctrl + L:清屏。

总结

本节课中我们一起学习了 Shell 管道的核心应用。我们掌握了如何用 | 连接命令,用 >>> 重定向输出,用 && 实现条件执行。我们还实践了使用 grepsortheadtailless 等工具来处理文本流,并学习了高效利用命令历史的技巧。这些技能是进行文件操作、日志分析和自动化脚本编写的基础,对于数据工程师至关重要。

014:SSH入门 🔐

在本节课中,我们将要学习SSH(Secure Shell)协议。SSH是连接远程服务器、进行安全通信和隧道传输的核心工具。我们将从了解SSH的基本概念开始,逐步学习如何创建密钥、连接到服务器以及使用端口转发功能。

什么是SSH?

SSH的关键特性之一是它创建了一个加密且安全的隧道。这个隧道可以保护你的数据在传输过程中不被窃听或篡改。

上一节我们介绍了SSH的整体目标,本节中我们来看看SSH协议本身的具体细节和最佳实践。

创建SSH密钥

创建SSH密钥对是与远程服务器或代码托管平台(如GitLab或GitHub)安全通信的常用方法。你需要知道如何生成公钥和私钥对,并将公钥配置到远程服务器上。

以下是创建和使用SSH密钥对的基本步骤:

  1. 生成密钥对:使用 ssh-keygen 命令生成公钥和私钥。
    ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
    
  2. 存储密钥:将生成的私钥(如 id_rsa)安全地保存在本地,将公钥(如 id_rsa.pub)添加到远程服务器的授权列表中。
  3. 配置使用:通过SSH客户端连接时,系统会自动使用本地私钥进行身份验证。

连接到服务器与端口转发

在创建好密钥后,下一步就是学习如何连接到远程服务器。此外,SSH的端口转发功能非常强大,例如,它允许你将远程服务器上正在开发的网站(如WordPress站点)安全地转发到本地机器进行预览,而无需立即公开上线。

以下是连接和隧道操作的核心步骤:

  1. 基本连接:使用 ssh 命令连接远程服务器。
    ssh username@remote_server_ip
    
  2. 本地端口转发:将远程服务器的某个端口映射到本地端口。例如,将远程的MySQL服务(3306端口)转发到本地的3307端口。
    ssh -L 3307:localhost:3306 username@remote_server_ip
    
  3. 远程端口转发:将本地服务的端口暴露给远程服务器访问。

所有这些操作都有一个共同点:你正在创建一个隧道,你的数据就在这条通道的中间。这很像一列火车,你所有的包裹、乘客等一切都在车厢内,你需要安全地将它们从一个地点运送到另一个地点,确保不被他人拦截。本质上,SSH就是一个用于连接服务器、建立隧道以及远程管理源代码的工具箱。


本节课中我们一起学习了SSH的三个主要领域:密钥管理、服务器连接和隧道技术。掌握这些技能对于安全地进行远程操作和数据传输至关重要。

数据工程基础:2-4:什么是SSH 🔐

在本节课中,我们将要学习SSH(Secure Shell)协议。SSH是数据工程和软件开发中一个极其重要的工具箱,它允许你安全地连接到远程服务器、执行命令、管理密钥以及进行端口转发。掌握SSH将极大地提升你在分布式计算和远程开发中的工作效率。


上一节我们介绍了SSH的整体概念,本节中我们来看看SSH最常见的三种用途。

1. 远程登录与命令执行
通过SSH,你可以从本地客户端(如你的笔记本电脑)安全地登录到远程服务器(例如云端的虚拟机)。登录后,你可以在远程服务器的终端中执行任何Bash命令,就像在本地操作一样。这种方式非常适合需要在特定环境(如生产服务器)中运行代码的开发者。

其基本命令格式为:

ssh username@remote_server_ip

2. 端口转发(隧道)
端口转发是SSH一个强大的功能。它允许你将远程服务器上的某个网络端口映射到本地机器的端口上。例如,远程服务器上运行着一个在8080端口的Flask应用,你可以通过SSH隧道将其转发到本地的8080端口。这样,你就能在本地浏览器中访问localhost:8080来调试远程应用,无需在远程服务器上开放公共端口,增强了安全性。

其基本命令格式为:

ssh -L local_port:localhost:remote_port username@remote_server_ip

3. 使用SSH密钥与GitHub等服务通信
在GitHub等代码托管平台使用SSH密钥,可以实现免密码认证。你将本地生成的公钥id_rsa.pub)添加到GitHub账户中。之后,当你执行git clonegit push等操作时,SSH协议会使用你的私钥进行加密认证,无需每次手动输入密码,既安全又便捷。

生成SSH密钥对的命令为:

ssh-keygen -t rsa -b 4096

本节课中我们一起学习了SSH的三大核心功能:远程命令执行、安全的端口隧道以及基于密钥的认证。SSH作为一个多功能的安全协议,是连接本地与远程资源、实现安全自动化工作流的基石工具。在接下来的实操演示中,我们将具体练习这三种方法。

016:创建SSH密钥并与GitHub配合使用 🔑

在本节课中,我们将学习如何通过SSH密钥安全地连接到GitHub仓库。这是数据工程项目中第一天就会遇到的常见任务。我们将创建一个SSH密钥对,并将其添加到GitHub账户,从而无需密码即可与远程仓库进行加密通信。

准备工作:云端开发环境

上一节我们介绍了SSH连接的基本概念,本节中我们来看看具体的操作步骤。首先,我们需要一个可以执行命令的开发环境。这里我们使用AWS Cloud9,它是一个基于云的集成开发环境,功能类似于GitHub Codespaces或Azure Cloud Shell。

我们将这个环境命名为“GitHub Work”,用于编辑GitHub仓库。创建时保留默认设置,例如使用Amazon Linux 2操作系统,并在30分钟无活动后自动超时。环境启动需要几秒钟时间。

虽然这个云端环境功能齐全,但为了与GitHub仓库进行读写交互,我们必须建立安全的通信渠道。

创建SSH密钥对

要与GitHub安全通信,我们需要创建SSH密钥。SSH密钥是一对加密密钥,包括一个私钥(必须保密)和一个公钥(可以共享)。以下是创建密钥的步骤:

  1. 打开终端,输入以下命令生成新的RSA密钥对:
    ssh-keygen -t rsa
    
  2. 系统会提示你选择保存密钥的文件位置。通常按回车键接受默认路径(例如 /home/ec2-user/.ssh/id_rsa)。
  3. 接着,你可以选择为密钥设置一个密码(可选)。直接按两次回车可以跳过,不设置密码。

命令执行成功后,会在 .ssh 目录下生成两个文件:id_rsa(私钥)和 id_rsa.pub(公钥)。

获取并复制公钥

创建密钥后,我们需要获取公钥的内容,以便将其添加到GitHub。公钥可以公开分享,用于验证你的身份。

使用 cat 命令查看并复制公钥内容:

cat ~/.ssh/id_rsa.pub

终端会显示一串以 ssh-rsa 开头的长文本。这就是你的公钥。请完整地复制它。

将公钥添加到GitHub账户

现在,我们需要让GitHub知道这个公钥属于你。以下是添加公钥到GitHub的步骤:

  1. 登录你的GitHub账户。
  2. 点击右上角的个人头像,选择 Settings(设置)。
  3. 在左侧边栏中,找到并点击 SSH and GPG keys
  4. 点击绿色的 New SSH key 按钮。
  5. 在“Title”字段中,为这个密钥起一个易于识别的名字(例如“Bash Duke Coursera SSH”)。
  6. 将刚才复制的公钥内容粘贴到“Key”字段中。
  7. 点击 Add SSH key 按钮。系统可能会要求你输入GitHub密码进行确认。

添加成功后,你的本地环境就获得了访问GitHub仓库的权限。

测试SSH连接

让我们测试一下配置是否成功。回到Cloud9的终端。

你可以使用 history 命令查看之前输入过的命令,这对于重复长命令非常方便。找到之前尝试克隆仓库时使用的 git clone 命令,通常可以通过输入 !1 来执行历史记录中的第一条命令。

现在,再次尝试克隆你的GitHub仓库:

git clone git@github.com:你的用户名/你的仓库名.git

如果一切配置正确,这次命令应该会成功执行,并将仓库的代码克隆到你的本地环境中。这表明SSH密钥已生效,你可以开始读写仓库中的代码了。

总结

本节课中我们一起学习了如何创建和使用SSH密钥连接GitHub。整个过程可以概括为三个核心步骤:使用 ssh-keygen 命令生成密钥对,从 .pub 文件中复制公钥,最后将公钥添加到GitHub账户的设置中。完成这些步骤后,你就能通过加密的SSH通道,无需密码即可安全地与GitHub仓库进行交互,这是进行协作开发和数据工程工作的基础技能。

017:使用SSH连接AWS云远程服务器 🖥️➡️☁️

在本节课中,我们将学习如何使用SSH(Secure Shell)协议,从本地计算机连接到AWS云上的远程服务器。你将了解配置安全组、获取连接信息以及建立SSH连接的全过程。


基于云的开发环境最大的优势之一是,它已经预装了我所需的所有工具,并且位于能够快速访问其他服务器的位置。

事实上,如果我输入一个像 uname -a 这样的命令,可以看到它显示运行的是Amazon Linux。这让我知道我正在一台传统的Linux机器上。

现在,如果我切换到另一个位置,例如我的本地OSX桌面,并输入 uname -a,可以看到这是一个完全不同的操作系统。

那么,我如何让我的本地桌面能够与那台远程服务器通信并运行命令呢?我们可以通过SSH来实现。

配置AWS安全组 🔐

上一节我们确认了本地与远程环境的差异,本节中我们来看看如何为远程服务器打开连接端口。

我需要回到AWS控制台,进入EC2服务。在这里我可以看到所有正在运行的服务器。向下滚动并清除筛选器,我可以看到实例状态。这里有一个是我的Cloud9环境。

点击这个实例,在安全设置下,我可以进行增强配置,使安全组允许我与之通信。为此,我需要编辑入站规则。

请注意,这里的SSH连接规则。我不必添加一条新规则,但需要确保它允许来自我本地机器的连接。一个简单的开始方式是允许来自“任何地方”的连接,即设置源为 0.0.0.0/0。这表示允许我的OSX机器进行通信。我给自己添加一个备注,然后保存更改。

获取连接信息 🔗

仅仅打开端口还不够,我还需要知道一些其他信息才能建立连接。

首先,我需要知道这台远程服务器的公共IP地址,以便与之通信。幸运的是,在实例详情页面可以直接找到它。我将复制这个IP地址。

回到我的本地终端,我可以创建一个变量来存储这个IP地址,这样就不需要每次都记住它。

HOST="你的公共IP地址"

输入 echo $HOST 可以验证变量已设置。

接下来,我需要准备SSH公钥。进入我的 ~/.ssh 目录,查看 id_rsa.pub 文件的内容。这个文件包含我的公钥信息。

cat ~/.ssh/id_rsa.pub

复制显示的公钥内容。

在远程服务器上授权密钥 🔑

现在,我需要回到AWS上的远程服务器,将我的公钥添加到授权列表中,这样我才能无需密码登录。

根据AWS文档,我们需要将公钥内容放入 ~/.ssh/authorized_keys 文件中。这个过程相对直接。

在远程服务器的终端中,使用 vim 编辑器打开(或创建)该文件:

vim ~/.ssh/authorized_keys

将之前复制的公钥内容粘贴到文件末尾。熟悉 vim 编辑器在这里很有帮助。在 vim 中,可以输入 :set paste 来启用粘贴模式,然后粘贴密钥。确保密钥格式正确(以 ssh-rsa 开头)。完成后,保存并退出 vim:wq)。

建立SSH连接 ⚡

现在,所有设置都已完成,理论上我应该能够从本地连接到远程主机了。

我需要知道的最后信息是远程服务器上的用户名。对于AWS EC2上的Amazon Linux,默认用户名通常是 ec2-user

回到我的本地Shell,现在可以尝试连接了。使用以下命令,其中 -v 参数表示详细输出,有助于调试:

ssh -v ec2-user@$HOST

系统可能会询问是否确认连接,输入 yes。如果一切配置正确,我们将成功登录到远程服务器。

登录后,可以再次输入 uname -a 来确认我们确实在Amazon Linux服务器上。我们还可以在远程服务器上创建文件或运行命令,例如创建一个标记连接成功的文件。

完成后,输入 exit 即可退出SSH会话,回到本地终端。

总结 📝

本节课中我们一起学习了如何使用SSH连接AWS远程服务器。我们首先在AWS控制台配置了安全组规则,打开了SSH端口。然后,我们获取了远程服务器的公共IP地址和本地SSH公钥。接着,我们将公钥添加到远程服务器的授权密钥文件中。最后,我们使用 ssh 命令成功建立了安全连接,并验证了操作环境。

使用SSH可以打开一个全新的世界,你可以登录到单台机器,甚至可以登录到机器集群。这是与基于云的机器进行通信非常常见且高级的技术。

018:SSH隧道示例演练 🚀

在本节课中,我们将学习如何使用SSH隧道(也称为端口转发)技术,来安全地访问运行在远程服务器上的本地网络服务。我们将通过一个具体的Flask Web应用示例,演示如何从本地浏览器访问远程服务器上运行的应用程序。


概述

SSH隧道是一种强大的网络技术,它允许你将远程服务器上的特定网络端口“映射”到本地计算机的端口上。这意味着,即使服务(如一个网站或数据库)只在远程服务器的内部网络(如127.0.0.1:8080)上运行,你也可以通过本地计算机直接访问它,就像服务运行在本地一样。这对于远程开发、调试和访问受防火墙保护的内部服务至关重要。

上一节我们介绍了SSH的基础连接,本节中我们来看看如何利用SSH隧道进行端口转发。


准备示例应用

首先,我们来看一个具体的例子。这里有一个来自O‘Reilly书籍《Python EmOs Cookbook》的Flask Web应用示例,该示例是关于机器学习模型部署的。

我已经将这个应用克隆到了当前远程服务器环境中。这个应用的核心是一个简单的Flask服务器。

以下是该Flask应用的关键部分代码:

# app.py 示例代码
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return “这是一个运行在远程服务器上的Flask应用。”

@app.route('/predict')
def predict():
    # 这里是机器学习预测的逻辑
    return “预测结果”

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080)

如代码所示,该应用绑定在本地环回地址127.0.0.18080端口上。这意味着它只能从服务器本机访问。

现在,让我们在服务器上启动这个应用。在终端中运行:

python app.py

应用启动后,在同一个远程服务器上打开另一个终端,我们可以使用curl命令测试应用是否正常运行:

curl 127.0.0.1:8080

如果看到返回的网页信息,则证明应用在服务器本地运行成功。然而,我们无法从自己的个人电脑直接访问这个地址。


建立SSH隧道

为了从我们的本地计算机访问这个远程服务,我们需要建立一条SSH隧道。这通过SSH的-L参数实现。

以下是建立隧道的命令格式:

ssh -L [本地IP]:[本地端口]:[远程服务地址]:[远程服务端口] [用户名]@[远程服务器地址]

在我们的例子中,命令具体如下:

ssh -L 127.0.0.1:8080:127.0.0.1:8080 your_username@remote_server.com

命令解析:

  • -L: 表示进行本地端口转发。
  • 127.0.0.1:8080(第一个):指定在本地计算机上监听的地址和端口。
  • 127.0.0.1:8080(第二个):指定远程服务器上目标服务的地址和端口。
  • your_username@remote_server.com: 你的SSH登录信息。

执行此命令并输入密码后,SSH连接会建立,同时隧道也开始工作。


通过隧道访问远程应用

隧道建立后,神奇的事情发生了。你无需对远程应用做任何修改。

现在,请在你的本地计算机上打开一个网页浏览器,在地址栏输入:

http://127.0.0.1:8080

或者

http://localhost:8080

你会发现,浏览器中显示的内容正是远程服务器上那个Flask应用的主页。你每刷新一次页面,实际上都是通过加密的SSH隧道向远程服务器的8080端口发送了一次请求。

如果你此时查看运行Flask应用的远程终端,可以看到类似以下的访问日志,证明请求确实到达了远程服务器:

127.0.0.1 - - [日期时间] “GET / HTTP/1.1” 200 -


SSH隧道的强大用途

SSH隧道不仅限于转发Web服务。它是一种通用技术,可以转发任何基于TCP协议的服务。

以下是SSH隧道的一些常见应用场景:

  • 访问内部数据库:将远程服务器上的MySQL(3306端口)、PostgreSQL(5432端口)等数据库服务转发到本地,方便用本地工具(如DBeaver, TablePlus)进行连接和管理。
  • 远程开发与调试:就像本例一样,你可以将测试环境或预生产环境的Web服务映射到本地,使用熟悉的本地浏览器和开发者工具进行调试。
  • 绕过网络限制:访问某些仅限内网访问的Wiki、监控面板或管理后台。
  • 安全连接:所有通过隧道传输的数据都经过SSH协议加密,即使在不安全的网络中也能保证通信安全。

掌握SSH隧道技术,能让你更灵活、更安全地在分布式环境中工作和开发。


总结

本节课中我们一起学习了SSH隧道的原理与实战应用。我们首先在远程服务器上启动了一个本地Flask应用,然后通过ssh -L命令建立了一条从本地端口到远程端口的加密隧道。最终,我们成功通过本地浏览器访问了原本只能在远程服务器内部访问的Web服务。

记住核心命令格式:ssh -L [本地端口]:[远程地址]:[远程端口] [用户]@[主机]。这项技术是数据工程师和开发者在处理远程资源、进行安全调试时的必备技能。

019:Bash操作入门 🐚

在本节课中,我们将学习如何配置Bash脚本,以增强和控制你的Linux开发环境及生产系统。我们还将了解Shell变量,并学习如何有效地使用标准输入和标准输出。

学习目标 🎯

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

  • 你将能够配置Shell环境。
  • 你将能够利用Shell变量来解决常见的Linux工作流问题。
  • 你将学会解释标准输入和标准输出的用途,并利用它们有效地进行数据流处理。

配置Shell环境 ⚙️

Shell环境是用户与操作系统内核交互的界面。通过配置它,我们可以定制自己的工作环境,提高效率。

以下是配置Shell环境的关键步骤:

  1. 了解配置文件:Shell在启动时会读取特定的配置文件,如 ~/.bashrc(针对交互式非登录Shell)和 ~/.bash_profile(针对登录Shell)。这些文件用于设置环境变量和别名。
  2. 设置环境变量:环境变量是存储在系统中的动态值,可以被运行在Shell中的程序访问。例如,PATH 变量决定了Shell到哪些目录中查找命令。
    • 代码示例:export PATH=$PATH:/your/custom/path
  3. 使用别名:别名可以将长的命令序列简化为一个短的命令。这能极大提升命令行操作的速度。
    • 代码示例:alias ll='ls -la'
  4. 自定义提示符:通过修改 PS1 变量,可以改变命令行提示符的显示样式,使其包含更多有用信息,如当前目录、Git分支等。

使用Shell变量 💾

Shell变量是存储数据的容器,对于编写脚本和自动化任务至关重要。它们分为局部变量和环境变量。

以下是关于Shell变量的核心概念:

  • 定义变量:直接使用等号赋值,等号两边不能有空格。
    • 代码示例:my_var="Hello World"
  • 引用变量:使用美元符号 $ 来获取变量的值。
    • 代码示例:echo $my_var
  • 环境变量:使用 export 命令可以将局部变量提升为环境变量,使其对子进程可见。
    • 代码示例:export MY_ENV_VAR="SomeValue"
  • 特殊变量:Shell提供了一些预定义的特殊变量,例如:
    • $0:脚本名称。
    • $1, $2...:脚本的参数。
    • $?:上一个命令的退出状态。

理解标准输入、输出与流处理 🌊

在Linux中,每个程序在运行时都会打开三个标准文件描述符:标准输入、标准输出和标准错误输出。掌握它们对于数据管道操作非常重要。

上一节我们介绍了Shell变量,本节中我们来看看如何利用标准流进行数据传递。

  • 标准输入:文件描述符为0,是程序读取输入数据的默认来源(通常是键盘)。
  • 标准输出:文件描述符为1,是程序输出结果的默认目的地(通常是终端屏幕)。
  • 标准错误输出:文件描述符为2,是程序输出错误信息的默认目的地。

以下是利用这些概念进行流处理的关键操作:

  1. 重定向输出:使用 > 将命令的输出重定向到文件(覆盖),使用 >> 进行追加。
    • 代码示例:ls > file_list.txt
  2. 重定向输入:使用 < 将文件内容作为命令的输入。
    • 代码示例:sort < unsorted_data.txt
  3. 管道:使用 | 将一个命令的标准输出连接到另一个命令的标准输入。这是构建强大命令行工具链的基础。
    • 代码示例:cat log.txt | grep "ERROR" | wc -l
    • 这个命令组合的含义是:先显示 log.txt 的内容,然后筛选出包含“ERROR”的行,最后统计这些行的数量。


总结 📝

本节课中我们一起学习了Bash操作的核心基础。我们首先了解了如何通过配置文件、环境变量和别名来定制Shell环境。接着,我们探讨了Shell变量的定义和使用方法,包括局部变量与环境变量的区别。最后,我们深入讲解了标准输入、标准输出和标准错误输出的概念,并学习了如何通过重定向和管道操作来构建高效的数据处理流程。掌握这些知识是成为一名高效的数据工程师或开发者的重要一步。

020:Bash Shell环境配置简介 🐚

在本节课中,我们将要学习如何配置你的Shell环境。掌握环境配置是成为数据工程、MLOps和云计算领域专家的关键技能。我们将探讨Shell配置文件的重要性、最常见的.bashrc文件,以及一些新兴的配置技术。

为什么需要配置Shell环境? ⚙️

上一节我们介绍了课程概述,本节中我们来看看为什么需要关心Shell配置。这很像定制一台新笔记本电脑:你会选择内存大小、GPU型号和屏幕尺寸,以打造最适合自己需求的工具。配置Shell环境也是如此,目的是构建一个能让你在开发项目时最高效工作的环境。

核心配置文件 📁

理解了配置的重要性后,接下来我们介绍两个最关键的配置文件。

.bashrc 文件

.bashrc是你在编辑环境时最常更改的文件。它几乎存在于所有你可能登录的云系统、Docker环境或虚拟机中。该文件在每次启动新的交互式Bash shell时被执行,用于设置环境变量、别名和函数。

例如,你可以在其中添加一个别名来简化常用命令:

alias ll='ls -la'

.zshrc 文件

最后,我们谈谈一些正在发展的技术,例如通过Oh My Zsh等第三方库使用的.zshrc文件。对于希望完全自定义并优化工作流程的开发者来说,这是一种新兴趋势。

总结 📝

本节课中我们一起学习了Shell环境配置的基础知识。我们了解了像定制硬件一样定制Shell环境的重要性,认识了.bashrc这一在各类系统中通用的核心配置文件,并简要了解了像.zshrc这样用于深度定制的新兴技术。正确配置这些文件,能确保你每次打开终端时,环境都处于最佳工作状态。

021:Shell配置文件解析 🐚

在本节课中,我们将要学习Shell配置文件的概念、作用及其核心组成部分。Shell配置文件是自动化Shell环境设置的关键,对于高效使用命令行和数据工程工作至关重要。


什么是Shell配置文件?

Shell配置文件是自动化的一种形式。没有配置文件,在Linux环境中需要手动执行许多步骤,这些步骤可能非常复杂。配置文件的核心目的是简化并自动化这些过程。

配置文件的位置与命名

一个常见的配置文件位于用户的主目录下,路径是 ~/.bashrc

  • ~ 代表用户的主目录。
  • / 表示文件位于该目录内。
  • .bashrc 是一个以点开头的隐藏文件。

在Shell环境中,以点开头的文件默认是隐藏的。使用 ls 命令或某些GUI文件管理工具时,默认不会显示它们。

配置文件的核心组成部分

上一节我们介绍了配置文件的基本概念,本节中我们来看看它的几个核心组成部分。

1. 别名

别名类似于网页浏览器中的书签。它允许你输入一个快捷命令,该命令会指向某个路径、命令或其他资源。

以下是定义别名的基本语法:

alias ll='ls -la'

随着你在某个公司或个人项目中不断使用Shell,你可能会开发出数百个自定义别名。这是一种高效的自动化形式。

2. 集中式挂载点与共享配置

在大型或分布式环境中,确保所有用户拥有一致的体验至关重要。这可以通过集中式文件系统实现。

  • 亚马逊 有弹性文件系统。
  • 谷歌 有网络文件系统。
  • 每个云平台和纯Linux环境都支持某种形式的集中式文件系统。

其理念是,在网络中的任何机器上登录,所有机器都能提供完全相同的用户体验。这意味着你无需反复进行繁琐的设置。将配置文件放在集中式挂载点,是实现完全自动化的有效方法之一。

3. 文件嵌套与来源

配置文件本身可以“引入”其他文件。这是通过 source 命令实现的。

例如,在AWS等云环境中,登录时 .bashrc 文件可能会引入大量自定义命令和环境设置。Python虚拟环境也是如此,它会引入一个bash脚本来设置库路径,确保所有包都安装到特定位置。

4. 自定义Bash函数

随着技能提升,你可能会开始编写小的Bash函数。这些自定义代码价值巨大。

它们可以执行诸如挂载文件系统、进行深度学习或数据清洗等操作。自定义代码的潜力几乎是无限的,你可以将这些函数引入到你的 .bashrc 文件中。


总结

本节课中我们一起学习了Shell配置文件。我们了解到,配置文件是Shell环境中自动化的基础,它允许你执行非常高级的操作。任何使用数据或机器学习的开发者,都应具备配置 .bashrc 文件的基本能力。它通过别名、共享环境设置和自定义代码,极大地提升了工作效率和一致性。

022:配置Bashrc文件 🛠️

在本节课中,我们将学习.bashrc文件的概念及其在配置Shell环境中的核心作用。我们将了解如何通过编辑此文件来创建命令别名、设置环境变量以及自动加载脚本,从而提升在不同计算环境(如AWS、GitHub Codespaces等)中的工作效率。

理解.bashrc文件 🌳

上一节我们介绍了Shell环境的基础知识,本节中我们来看看.bashrc文件的核心概念。.bashrc文件是Bash Shell的配置文件,它就像一棵树的根基。在不同的云Shell环境中,例如AWS CloudShell、GitHub Codespaces、Azure Cloud Shell和Google Cloud Shell,都使用.bashrc文件来配置环境。每个环境还会在此基础上添加自己特有的“秘方”以适应其独特需求。我们将主要关注AWS CloudShell环境,但也会简要展示其他环境,以说明一旦掌握.bashrc文件的配置,就能将其应用于所有环境。此外,如果你使用的是macOS等操作系统,同样可以应用这些技巧。

在AWS CloudShell中开始配置 🚀

现在,让我们进入AWS CloudShell环境开始实践。当你首次打开任何CloudShell环境时,建议先执行以下命令来确认当前使用的Shell。

echo $SHELL

这个命令会告诉我们正在使用Bash Shell。确认后,我们可以列出主目录的内容。

ls -la ~/

此命令显示主目录下的文件,你会看到诸如.bash_history.bash_logout.bash_profile等文件。虽然这些文件都可以编辑,但大多数人选择编辑.bashrc文件,因为它是存储交互式Shell常用设置的集中位置。

接下来,我们使用vim编辑器打开并查看.bashrc文件。

vim ~/.bashrc

打开后,你可以看到AWS环境已经预置了一些配置,例如aws_completer路径、用户特定的别名和函数。我们的目标是在此基础上进行自定义。

创建命令别名 ⚡

命令别名是命令的快捷方式,可以显著减少输入。让我们在.bashrc文件中添加一个别名。

以下是添加别名的步骤:

  1. .bashrc文件末尾添加一行:alias srccode='cd ~/src'
  2. 保存并关闭文件。
  3. 为了使更改生效,你需要打开一个新的终端标签页(这会启动一个新的交互式Shell并读取配置文件),或者执行source ~/.bashrc命令。

现在,输入alias命令,你会看到新添加的srccode别名。尝试运行srccode,如果提示目录不存在,只需先创建它:mkdir ~/src。之后,srccode命令就能成功将你切换到~/src目录。

管理环境变量与API密钥 🔑

在数据工程项目中,配置API密钥等环境变量非常常见。我们可以通过.bashrc文件自动加载这些变量。

首先,创建一个存储API密钥的脚本文件。

vim ~/api_keys.sh

在该文件中,我们可以设置一些环境变量。

export SECRET_1="123"
export SECRET_2="abc"

保存文件后,我们需要让.bashrc文件在启动时自动加载这个脚本。编辑.bashrc文件,添加以下内容。

# 设置API密钥文件路径
export PATH_TO_API_KEYS="$HOME/api_keys.sh"
echo "I am sourcing this path: $PATH_TO_API_KEYS"
source $PATH_TO_API_KEYS

保存.bashrc后,打开一个新的终端标签页。你会看到提示信息,并且环境变量SECRET_1SECRET_2已经可用,可以通过echo $SECRET_1进行验证。这样,Python或Bash脚本就可以直接使用这些密钥了。

自动激活Python虚拟环境 🐍

对于数据工程,Python虚拟环境至关重要。我们可以配置.bashrc在启动时自动激活特定的虚拟环境。

首先,创建一个Python虚拟环境。

python3 -m venv ~/venv

然后,手动激活它以确认其工作正常。

source ~/venv/bin/activate

激活后,Shell提示符通常会发生变化。为了每次打开Shell都自动激活,我们将激活命令添加到.bashrc中。

echo "I am sourcing a Python virtual environment"
source ~/venv/bin/activate

保存.bashrc后,打开新的终端标签页。提示符会改变,并且使用which python命令可以确认当前Python解释器来自虚拟环境。

跨云环境的一致性验证 ☁️

掌握了.bashrc的配置后,这些技巧可以无缝应用到其他云Shell环境。无论在Google Cloud Shell、GitHub Codespaces还是Azure Cloud Shell中,你都可以使用相同的echo $SHELL命令来确认环境,并应用我们学到的.bashrc配置方法。这让你能在任何新环境中快速建立熟悉、高效的工作流。

本节课中我们一起学习了.bashrc文件的强大功能。通过配置别名、环境变量和自动加载脚本,你可以个性化Shell环境,大幅提升工作效率。掌握.bashrc是每位数据工程师都应该具备的一项基础且高效的技能。

023:配置Zshrc与第三方工具Oh My Zsh 🛠️

在本节课中,我们将学习如何在本地机器上配置Zsh环境,并利用强大的第三方工具Oh My Zsh来增强终端的功能,从而提高数据工程工作的效率。


概述

作为macOS开发者,配置开发环境以使其功能更强大、更美观是一种常见做法。一个非常流行的第三方工具是Oh My Zsh。它通过提供丰富的插件和主题,帮助用户更高效地使用Z shell。

安装与初始配置

首先,我们需要安装Oh My Zsh。安装过程非常简单,只需在终端中运行一条命令。

以下是安装Oh My Zsh的命令:

sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

运行此命令后,如果尚未安装,系统将开始安装过程。安装完成后,你的Zsh环境将得到增强,并生成一个配置文件 ~/.zshrc

探索Oh My Zsh目录

安装成功后,我们可以查看Oh My Zsh的目录结构,其中包含了各种插件和主题。

使用以下命令列出Oh My Zsh目录的内容:

ls -la ~/.oh-my-zsh/

进入插件目录,可以看到大量可用的插件:

ls -la ~/.oh-my-zsh/plugins/

这些插件为开发环境提供了丰富的扩展功能。

Oh My Zsh的默认增强功能

使用Oh My Zsh后,Zsh环境会获得一些非常实用的默认增强功能。

例如,你可以直接输入路径来切换目录,而无需使用 cd 命令。只需输入路径并按回车,即可切换到该目录。

此外,Oh My Zsh默认提供了更长的命令历史记录。你可以使用 history 命令查看,与普通的Bash shell相比,历史记录容量显著增加。

配置.zshrc文件

要自定义和进一步配置环境,我们需要编辑 ~/.zshrc 文件。

使用以下命令打开并编辑该文件:

vim ~/.zshrc

在这个文件中,你可以设置主题、启用插件以及定义别名等。

主题与插件配置

.zshrc 文件中,你可以找到设置主题和插件的部分。

例如,设置主题的配置行可能如下:

ZSH_THEME="robbyrussell"

启用插件的配置行如下。以下示例启用了git、pip、python和docker插件:

plugins=(git pip python docker)

这些插件能提供智能的自动补全功能。例如,进入一个Git项目目录后,终端提示符会自动显示Git分支状态等信息。

添加更多插件

Oh My Zsh社区提供了海量的插件。你可以根据需求轻松添加。

例如,要添加AWS命令行工具的自动补全插件,只需在 plugins 列表中加上 aws

plugins=(git pip python docker aws)

添加后,需要重新加载配置文件以使更改生效:

source ~/.zshrc

现在,输入 aws 后按Tab键,就会看到所有AWS相关命令的自动补全提示,这极大地提升了使用效率。

使用别名提升效率

.zshrc 文件中,另一个强大的功能是定义别名(alias)。别名可以将长的、复杂的命令简化为短的快捷命令。

例如,你可以添加如下别名:

alias ll='ls -la'
alias gs='git status'
alias activateenv='source venv/bin/activate'

通过积累和定制别名,可以让你在终端中的操作变得极其迅速。

在macOS上更改默认Shell

你的macOS系统可能默认使用Bash。如果你想将默认登录Shell改为Zsh,可以按以下步骤操作。

  1. 打开“系统偏好设置” > “用户与群组”。
  2. 解锁后,右键点击当前用户,选择“高级选项”。
  3. 在“登录Shell”下拉菜单中,选择 /bin/zsh
  4. 点击“好”保存更改。

此外,也可以在“终端”应用的偏好设置中,直接修改某个配置文件的“Shell”路径来更改。

macOS专属实用命令

在macOS环境下,除了通用Unix命令,还有一些系统专属的实用工具。

例如,mdfind 命令可以利用Spotlight索引快速搜索文件,速度远超传统的 find 命令。

mdfind -name "*.txt"

另一个强大的命令是 diskutil,用于管理磁盘和分区。

diskutil list

掌握这些macOS特有的命令,能让你在本地开发环境中更加得心应手。

总结

本节课中,我们一起学习了如何配置Zsh环境并利用Oh My Zsh这一强大工具。我们涵盖了从安装、探索目录结构、配置主题插件,到添加自定义插件和别名等一系列步骤。同时,也了解了如何在macOS上更改默认Shell,并介绍了一些系统专属的实用命令。通过合理配置你的Shell环境,可以显著提升作为数据工程师的工作效率和舒适度。

024:Shell变量操作入门 🐚

在本节课中,我们将学习Shell变量的基本概念和操作方法。Shell变量是自动化脚本和安全管理API密钥等敏感信息的重要工具。通过学习如何定义、导出和在脚本中使用变量,你将能更安全、高效地编写Bash脚本。

什么是Shell变量? 🤔

上一节我们介绍了课程目标,本节中我们来看看Shell变量的本质。

Shell变量是存储在Shell环境中的命名值。你可以把它们想象成脚本中的临时标签或容器,用于存储文本、数字或文件路径等信息。使用变量能让脚本更灵活、更易读,也便于管理敏感数据。

在交互式提示符中使用与导出变量 💻

理解了基本概念后,我们来看看如何在Bash终端中实际操作变量。

你可以在终端中直接创建和使用变量。变量名通常使用大写字母,赋值时等号两边不能有空格。要使用变量的值,需要在变量名前加上美元符号$

以下是定义和使用变量的基本步骤:

  1. 定义变量:使用等号进行赋值。

    MY_VARIABLE="这是一个值"
    
  2. 引用变量:使用$符号获取变量的值。

    echo $MY_VARIABLE
    
  3. 导出变量:使用export命令使变量在当前Shell及其子进程中可用。

    export MY_VARIABLE
    

注意:在交互式Shell中定义的变量默认只在当前会话中有效。关闭终端后,这些变量就会消失。

在脚本中使用Shell变量 📜

我们已经学会了在终端中操作变量,现在来看如何将它们应用到脚本中,这是实际开发中最常见的场景。

在脚本中使用外部定义的变量(如API密钥)是一种最佳实践。这能确保敏感信息不会硬编码在脚本里,从而避免意外泄露。通常的做法是将变量定义在一个单独的、不被提交到代码仓库的配置文件中。

以下是在脚本中使用环境变量的方法:

  1. 在一个外部文件(如.env)中定义变量。

    # .env 文件内容示例
    export API_KEY="your_secret_key_here"
    export DATABASE_URL="postgresql://user:pass@localhost/db"
    
  2. 在运行脚本前,先加载这个配置文件。

    source .env
    
  3. 在脚本中直接引用这些已导出的变量。

    # 你的脚本内容
    echo "使用密钥:$API_KEY"
    curl -H "Authorization: Bearer $API_KEY" https://api.example.com/data
    

这种方法将配置与代码分离,极大地提高了安全性和可维护性。

总结 🎯

本节课中我们一起学习了Shell变量的核心操作。我们首先了解了Shell变量是什么,然后实践了在交互式终端中定义、引用和导出变量的方法。最后,我们探讨了如何在脚本中安全地使用环境变量来管理API密钥等敏感信息,这是数据工程和自动化脚本开发中的一项关键技能。掌握这些知识,将帮助你编写出更健壮、更安全的Bash脚本。

025:什么是Shell变量 🐚

在本节课中,我们将要学习Shell变量的概念、作用以及基本使用方法。Shell变量是存储数据的一种方式,这些数据可以在脚本或其他进程中使用,尤其在处理API密钥和敏感信息时非常有用。


上一节我们介绍了Shell的基本概念,本节中我们来看看Shell变量的具体定义和用途。

Shell变量本质上是一种存储数据的方式,以便在其他脚本或进程中使用。当您使用API构建项目时,它们会频繁出现。关键原因在于,您希望将这些特殊的密钥存储在某个地方,以免它们被意外提交到代码仓库中。

以下是常见的做法:

  • 您会使用一个 .gitignore 文件,它告诉源代码控制系统忽略某些特定文件。
  • 在这个文件中,您可能会指定忽略一个名为 .env 的文件。
  • 在这个 .env 文件中,您会存放云服务提供商、第三方服务的API密钥,或者数据库的认证凭证。
  • 所有这些信息都存储在仅对您本地机器可见的文件中。

这样,当您与团队中的其他开发者协作时,只需告诉他们需要创建自己的 .env 文件,并将所需内容放入其中。然后,在处理特定项目时,您需要“获取”(source)这个 .env 文件。

您甚至可以更进一步,将其作为 .bashrc 文件的一部分,这样每次切换到特定目录时,它都会自动获取该项目的环境变量。仅通过使用Shell变量,就可以实现很多自动化操作。


了解了Shell变量的用途后,让我们来看看其背后的基本原理和语法。理论部分并不复杂。

首先,您需要在Bash中使用 export 命令创建一个变量。

这个 export 命令的好处在于,您可以清楚地看到 food 是变量名,而 Apple 是变量的值。

接下来,如果您想使用这个变量,只需在运行第一条命令后,在 echo 命令中引用 $food 即可。

因此,使用Shell变量构建内容的关键在于,必须通过在变量名前加美元符号 $ 来访问它。这意味着Shell知道如何解释这是一个变量。

如果您想使用稍微复杂一点的语句(其实也并不复杂),可以将其放入一个句子中。如下例所示,您可以说 echo "I love $food"。这在调试时非常常见。我经常这样做,例如,当我有一个很长的路径、数据库凭证或某个服务的URL时,我会将其放入变量中,然后用 echo 打印出来,以确保我理解当前的情况。


现在,让我们通过实际操作来巩固对理论的理解。我们将演示刚才提到的例子。

首先,我输入 export food=Apple 来创建变量。

现在,我该如何访问这个值呢?输入 echo $food。这里有一个很棒的技巧:如果我只输入部分内容(比如 echo $f),然后按 Tab 键,它会自动补全剩余部分。这是一个非常推荐的专业技巧,使用 Tab 键来探索,它就像在谷歌中进行自动搜索一样,可以让您在输入更少的情况下发现正确的值。这不仅节省时间,还能让您在构建内容时更高效,避免出错。

看,我得到了那个值。接下来,让我们把它放到一个句子里。我可以输入 echo "I love $food"

这本质上就是在Bash提示符下编写变量的“Hello World”。在Z shell中操作是完全相同的,开始编写Shell变量非常容易。


本节课中我们一起学习了Shell变量的核心概念。我们了解到,Shell变量是存储数据(尤其是敏感信息如API密钥)的重要工具,通过 export 命令定义,并通过 $变量名 的方式引用。我们还实践了变量的创建、访问以及在字符串中的使用,并掌握了使用 Tab 键自动补全以提高效率的技巧。理解Shell变量是自动化脚本和安全管理项目配置的基础。

026:在交互式提示符中导出Shell变量

在本节课中,我们将要学习Shell变量在父进程与子进程之间的作用域关系。理解export命令的用法对于确保变量能在子Shell中被访问至关重要。

父进程与子进程中的变量作用域

上一节我们介绍了Shell变量的基本概念,本节中我们来看看变量在不同Shell进程间的传递规则。Shell变量根据其定义方式,作用域会有所不同。

本地变量与导出变量

以下是两种定义变量的方式及其区别:

  • 本地变量:使用 VAR=value 格式定义。这种变量仅在当前Shell进程中有效。
    • 示例my_var="local_value"
  • 导出变量:使用 export VAR=value 格式定义。这种变量会被“导出”到当前进程的环境变量中,从而可以被该进程启动的任何子进程(子Shell)继承。
    • 示例export my_exported_var="global_value"

实践演示:变量作用域对比

让我们通过一个实验来直观地理解这两种变量的区别。假设我们打开一个终端(父Shell),并在其中进行以下操作。

首先,我们创建一个本地变量:

VAR_LOCAL="I_am_local"

然后,我们创建一个导出变量:

export VAR_EXPORTED="I_am_exported"

现在,我们在当前Shell中验证这两个变量:

echo $VAR_LOCAL      # 输出:I_am_local
echo $VAR_EXPORTED   # 输出:I_am_exported

接下来,我们启动一个新的子Shell进程:

bash

在这个新的子Shell中,再次尝试访问这两个变量:

echo $VAR_LOCAL      # 输出为空(变量不存在)
echo $VAR_EXPORTED   # 输出:I_am_exported

可以看到,只有使用export定义的变量VAR_EXPORTED被传递到了子Shell中。

在配置文件中正确使用Export

这个知识点在配置Shell环境时尤为重要,例如在编辑~/.bashrc文件时。

~/.bashrc文件中的命令会在每次启动新的交互式Shell时执行。为了确保你在此文件中设置的变量对所有子Shell和程序都可用,必须使用export命令。

以下是~/.bashrc中设置变量的推荐方式:

# 正确:使用export,变量可被子进程继承
export API_KEY="your_api_key_here"
export CUSTOM_PATH="$HOME/my_tools/bin"

# 错误:不使用export,变量仅对当前Shell有效(通常不是你想要的)
TEMP_VAR="will_not_be_passed_down"

通过这种方式,当你打开新的终端窗口、标签页,或在脚本中启动子Shell时,这些重要的环境变量都将可用。

总结

本节课中我们一起学习了Shell变量的作用域机制。核心要点是:使用VAR=value定义的变量是本地变量,仅存在于当前Shell;而使用export VAR=value定义的变量是导出变量,会被添加到环境变量中,从而可以被所有子Shell进程继承。在配置~/.bashrc等启动文件时,务必对需要全局使用的变量使用export命令,以确保你的自定义设置在整个Shell会话树中生效。

027:Bash脚本中的Shell变量使用 🐚

在本节课中,我们将学习在Bash环境中执行代码的两种核心方式:source命令与直接运行脚本。我们将重点探讨它们如何影响Shell变量的作用域,并学习如何利用这些特性来高效地管理项目环境。

两种执行方式的对比

上一节我们介绍了Shell变量的基本概念,本节中我们来看看如何让这些变量在不同的上下文中生效。关键在于理解代码的执行方式。

source命令(或简写为.)用于在当前Shell环境中执行脚本文件。这意味着脚本中定义的变量、别名和函数会直接加载到您当前的终端会话中,并在此后持续可用。其核心行为可以用以下命令表示:

source script.sh
# 或
. script.sh

直接运行脚本(通常使用./script.sh)则会创建一个新的子Shell来执行脚本。脚本中定义的任何变量都只存在于这个临时的子Shell中,当脚本运行结束,子Shell及其所有变量都会被销毁,不会影响父Shell环境。其核心行为可以用以下命令表示:

chmod +x script.sh  # 确保脚本有执行权限
./script.sh

以下是两种方式的核心区别总结:

  • source:旨在改进当前环境。它会添加上下文、变量和别名,效果是持久的。
  • 运行脚本:旨在运行并遗忘。它适合执行独立任务,其内部变量不会污染父Shell环境。

实践:变量作用域演示

理解了理论,让我们通过一个简单的例子来直观感受这两种方式的区别。

假设我们有两个脚本文件:

  1. env.sh:用于定义环境变量。
  2. run.sh:一个可执行脚本,它会调用env.sh并尝试使用其中定义的变量。

首先,我们创建一个定义变量的环境文件。以下是env.sh的内容:

#!/bin/bash
export FOOD="pizza"
export ANIMAL="cat"

接着,我们创建一个可执行脚本run.sh,它会输出这些变量。以下是run.sh的内容:

#!/bin/bash
source env.sh  # 在子Shell中加载变量
echo "My favorite animal is: $ANIMAL"
echo "Inside the script, food is: $FOOD"

现在,让我们在终端中观察不同执行方式的结果。

场景一:直接运行脚本

  1. run.sh添加执行权限:chmod +x run.sh
  2. 运行脚本:./run.sh
  3. 脚本会成功打印出动物和食物的信息。
  4. 然而,在脚本运行结束后,在终端中尝试回显变量:echo $FOOD
  5. 你会发现终端中没有任何输出,因为$FOOD变量只存在于运行脚本时创建的子Shell中,父Shell无法访问。

场景二:使用source命令

  1. 在终端中直接加载环境文件:source env.sh
  2. 现在,尝试回显变量:echo $FOOD
  3. 终端会成功输出“pizza”。因为source命令将env.sh中的变量直接导入了当前的Shell会话,使其立即可用。

这个对比清晰地展示了:直接运行脚本时,变量是隔离的;使用source时,变量会融入当前环境。

应用:创建项目环境上下文

那么,如何利用这个特性来优化我们的工作流呢?一个常见的实践是为不同的项目创建独立的环境上下文。

我们可以这样做:

  1. 为每个项目创建一个环境变量文件(例如project_env.sh),其中定义项目专用的变量。
  2. ~/.bashrc文件中设置一个别名(alias)。
  3. 这个别名能实现两个功能:首先切换到项目目录,然后自动source该项目的环境变量文件。

以下是具体步骤:

第一步,创建项目目录和环境变量文件。

mkdir my_project
cd my_project
echo 'export PROJECT_SECRET="ABCD1234"' > project_env.sh

第二步,编辑~/.bashrc文件,添加一个智能别名。

alias myproject='cd /path/to/my_project && source project_env.sh'

请将/path/to/my_project替换为你实际的项目绝对路径。你可以通过在项目目录下运行pwd命令来获取这个路径。

第三步,使别名生效。

source ~/.bashrc

现在,每当你想要进入my_project的工作环境时,只需在终端输入myproject。这个命令会:

  1. 将你的工作目录切换到项目文件夹。
  2. 自动加载project_env.sh中定义的所有变量(例如$PROJECT_SECRET)。

这样,你的主Shell环境就不会被所有项目的变量所“污染”。变量只在需要它们的项目上下文中被激活,实现了环境的清晰隔离和快速切换。

总结

本节课中我们一起学习了Bash中管理Shell变量的两种关键方法。

  • 我们明确了source命令与直接运行脚本的本质区别:前者将变量导入当前Shell,后者则在子Shell中运行且变量随之消亡。
  • 我们通过实践演示,验证了变量作用域的不同。
  • 最后,我们应用这些知识,构建了一个实用的工作流——通过结合aliascdsource命令,创建了能够一键切换并加载特定环境变量的项目上下文快捷方式。

掌握这些技巧,能帮助你更干净、更高效地组织和管理命令行环境,尤其是在处理多个不同配置的项目时。

028:标准流入门 🚀

在本节课中,我们将学习标准流的概念,包括标准输入、标准输出和标准错误。我们将探讨它们各自的作用,以及如何从Bash脚本的角度来理解和使用它们,以提升编程效率。


什么是标准流? 💡

标准流是操作系统为程序提供的预定义通信通道。每个程序启动时都会自动打开三个流:标准输入标准输出标准错误。它们分别服务于不同的目的,是程序与外部环境交互的基础。

上一节我们介绍了课程概述,本节中我们来看看标准流的具体定义。

  • 标准输入:程序读取数据的来源,通常来自键盘。
  • 标准输出:程序正常输出结果的去向,通常显示在终端。
  • 标准错误:程序输出错误信息的去向,通常也显示在终端。

在Bash中,它们分别对应文件描述符 012


使用标准输入、输出和错误 🔧

理解了基本概念后,本节我们将深入探讨如何具体使用这三个标准流。

标准输入

标准输入是程序获取数据的主要方式。数据可以来自用户的键盘输入,也可以来自其他程序或文件。

以下是使用标准输入的常见方式:

  • 键盘输入:程序运行后,等待用户在终端键入内容。
  • 重定向:使用 < 符号将一个文件的内容作为程序的输入。
    # 将 file.txt 的内容作为 cat 命令的输入
    cat < file.txt
    
  • 管道:使用 | 符号将一个命令的输出作为另一个命令的输入。
    # 将 ls 命令的输出作为 grep 命令的输入
    ls -l | grep ".txt"
    

标准输出

标准输出用于打印程序的常规运行结果。

以下是处理标准输出的方法:

  • 终端显示:默认情况下,标准输出会显示在终端屏幕上。
  • 重定向:使用 >>> 符号将输出保存到文件。
    # 将输出覆盖写入 file.txt
    echo "Hello" > file.txt
    # 将输出追加到 file.txt 末尾
    echo "World" >> file.txt
    
  • 管道:如前所述,可以通过管道将输出传递给下一个命令。

标准错误

标准错误专门用于输出错误和诊断信息,这有助于将正常输出与错误信息分开处理。

以下是处理标准错误的方法:

  • 终端显示:默认情况下,错误信息也会显示在终端上,与标准输出混合。
  • 重定向:可以单独重定向错误流。2> 表示重定向文件描述符2(即标准错误)。
    # 将错误信息写入 error.log 文件
    command_that_might_fail 2> error.log
    
  • 合并重定向:使用 2>&1 将标准错误合并到标准输出。
    # 将标准输出和标准错误都写入 output.log
    command > output.log 2>&1
    

如何运用标准流? 🎯

我们已经分别了解了三个标准流。在实际的Bash编程中,灵活运用它们是关键。

核心在于理解数据流向。你可以通过重定向和管道,精确控制信息的来源和去向。例如,你可以让一个脚本从文件读取输入,将正常结果写入一个日志文件,同时将错误信息发送到另一个错误日志文件,甚至通过邮件通知管理员。

这种分离控制使得脚本更健壮,也便于调试和自动化。


总结 📝

本节课中我们一起学习了标准流的核心概念。我们明确了标准输入、标准输出和标准错误各自的作用,并掌握了通过重定向和管道来操作它们的基本方法。熟练运用这些概念是成为一名高效Bash程序员的基础,它能帮助你更好地构建、调试和集成数据处理的脚本与流程。

029:什么是标准流

在本节课中,我们将要学习Unix系统中的标准流概念。标准流是程序与外部环境(如终端、文件或其他程序)进行输入输出通信的核心机制。理解标准流对于高效地使用命令行处理数据至关重要。

什么是标准流?💡

标准流是一种捕获数据输入并对其进行过滤的方式。这在数据科学中非常常见。例如,你会通过SQL查询来过滤数据,或者使用Pandas库遍历并选取数据的某个子集,以便在后续流程中进行处理。同样地,当你有输出数据时,你会将这些数据通过管道传输到其他位置。因此,你可以将标准流想象成水管中的水流。如果你把水引入房屋,你会处理这些水,例如通过污水系统、洗碗机以及房屋内的各种电器。Unix系统中的数据处理、过滤和流传输概念与此非常相似,存在多种方法。

标准流图解与分析 🔍

上一节我们介绍了标准流的基本概念,本节中我们来看看一个具体的Unix标准流示意图。通过分析这个图表,我们可以更直观地理解数据是如何流动的。

请看这个Unix标准流图表。在输入侧,这是你将数据传递给Unix实用程序的地方。这方面有很多例子。例如,如果你想通过管道将数据传递给grep命令,你可以搜索某个关键词以在日志文件中查找模式。同样,很多时候你会将数据通过管道输出到一个文件,而在输出之前,你可能会进行一些处理,比如截断数据、打乱顺序或进行某种过滤操作。这是非常常见的做法。

因此,当你思考在使用标准流时“我在做什么”以及“为什么这么做”时,答案通常是:你将要过滤数据和处
理数据。这些是最常见的操作。如果你在处理数据,命令行是更强大、更高效的方式之一。

图表中还有“错误”流。很多时候,你要么捕获错误并将其放入一个文件,要么将错误转移到可以丢弃的位置。在某些情况下,你不希望这些错误消息污染你的文件系统并使其被填满,你会将其通过管道传输到类似/dev/null这样的位置。

总结 📝

本节课中我们一起学习了Unix标准流。我们了解到,标准流是程序进行输入、输出和错误信息通信的三种预定义数据流。它们使得数据过滤、处理和重定向变得非常高效,是命令行数据操作的核心。通过将程序像水管一样连接起来,我们可以构建强大的数据处理流水线。

030:标准输出使用 📤

在本节课中,我们将学习如何在命令行中使用标准输出。标准输出是程序将其结果信息打印到终端屏幕的默认方式。通过管道和重定向,我们可以将这些输出保存到文件,或者传递给其他命令进行进一步处理,这是数据工程中处理文本和数据流的基础技能。

标准输出的基础:echo 命令

上一节我们介绍了标准输出的概念,本节中我们来看看最基础的命令:echoecho 命令的作用是将其接收的参数原样打印到标准输出。

例如,以下命令会输出三行文本:

echo -e "apple\ncarrot\nbanana"

这里的 -e 选项允许解释反斜杠转义字符,\n 代表换行。执行后,终端会显示:

apple
carrot
banana

重定向输出到文件

echo 命令的输出不仅可以显示在屏幕上,还可以通过重定向操作符保存到文件中。

以下是两种主要的输出重定向方式:

  • >:将命令的标准输出重定向到一个文件。如果文件已存在,则会覆盖原有内容。
  • >>:将命令的标准输出追加到一个文件的末尾。如果文件不存在,则会创建它。

例如,将水果列表保存到 fruit.txt 文件:

echo -e "apple\ncarrot\nbanana" > fruit.txt

要查看文件内容,可以使用 cat 命令:

cat fruit.txt

使用管道连接命令

标准输出最强大的功能之一是管道,它允许我们将一个命令的输出直接作为另一个命令的输入。管道操作符是 |

例如,我们可以将 echo 的输出通过管道传递给 sort 命令进行排序:

echo -e "apple\ncarrot\nbanana" | sort

执行后,输出会按字母顺序排列:

apple
banana
carrot

我们还可以将排序后的结果再次重定向回文件,覆盖原内容:

echo -e "apple\ncarrot\nbanana" | sort > fruit.txt

组合使用管道与过滤器

通过管道,我们可以将多个命令串联起来,形成强大的数据处理流水线。

以下是几个有用的文本处理命令,常与管道配合使用:

  • sort:对输入的行进行排序。
  • uniq -c:报告或忽略重复的行。-c 选项会在每行前加上该行重复出现的次数。
  • grep:用于搜索文本,输出包含指定模式的行。

让我们看一个组合示例。首先,创建一个包含重复项的列表:

echo -e "apple\ncarrot\nbanana\napple" > fruits_with_dup.txt

然后,使用管道链进行排序、去重并计数:

cat fruits_with_dup.txt | sort | uniq -c

输出结果会显示每个唯一水果及其出现次数:

      2 apple
      1 banana
      1 carrot

如果只想查看“apple”的统计,可以在管道链末尾加入 grep

cat fruits_with_dup.txt | sort | uniq -c | grep apple

实际应用示例

管道和过滤在系统管理中非常实用。例如,ps ef 命令会列出系统所有进程,但输出通常很长。

如果我们只想查看与Python相关的进程,可以使用 grep 进行过滤:

ps ef | grep python

这样,终端就只显示包含“python”关键词的进程行,便于我们快速定位。

另一个有趣的例子是反转文本。rev 命令可以反转每一行的字符顺序:

echo "1993" | rev

输出结果为:

3991

本节课中我们一起学习了标准输出的核心操作。我们掌握了使用 echo 生成输出,使用 >>> 重定向到文件,以及使用 | 管道连接多个命令(如 sortuniqgrep)来构建数据处理流程。熟练运用这些技巧,能极大地提升你在命令行中处理和转换数据文本的效率。

031:标准输入的使用 📥

在本节课中,我们将学习如何在Bash脚本中使用标准输入。标准输入是Shell与用户或程序交互的一种基本方式,掌握它对于编写自动化脚本至关重要。我们将通过具体的例子,学习如何提示用户输入、如何将文件内容作为输入传递给命令。

从用户获取输入

上一节我们介绍了标准输入的基本概念,本节中我们来看看如何从用户那里获取输入。read命令是实现这一功能的核心工具。

read命令会等待用户输入,并将输入的内容存储到一个变量中。其基本语法是:

read -p "提示信息" 变量名

其中,-p选项用于指定提示用户的信息。

例如,以下命令会提示用户输入一个文件名,并将输入的值存储在filename变量中:

read -p "请输入文件名: " filename

执行后,用户输入的内容(例如fruit.txt)就会被赋值给变量filename。之后,你可以通过echo $filename来使用这个变量。

一个实用的脚本示例

理解了基础用法后,我们可以将其应用到实际的脚本中。以下是一个完整的脚本示例,它演示了如何结合read命令和其他命令完成一个实用任务。

以下是input.sh脚本的内容:

#!/bin/bash
# 提示用户输入文件名
read -p "请输入要处理的文件名: " filename
# 使用readlink命令获取文件的完整绝对路径
fpath=$(readlink -f "$filename")
# 输出完整路径
echo "文件的完整路径是: $fpath"

使用这个脚本前,需要先赋予其执行权限:

chmod +x input.sh

然后运行脚本:

./input.sh

脚本会提示你输入一个文件名(例如fruit.txt),然后它会计算出该文件的完整系统路径并显示出来。这种方式在需要处理文件绝对路径的自动化任务中非常有用。

将文件内容作为命令输入

除了从用户获取输入,标准输入的另一个强大功能是将一个命令或文件的内容直接作为另一个命令的输入。这可以通过管道|或输入重定向<来实现。

例如,我们有一个名为fruit.txt的文件。如果想用less命令分页查看其内容,通常的做法是:

less fruit.txt

另一种等价的写法是使用输入重定向,将文件内容“喂给”less命令:

less < fruit.txt

这两种方式都使得fruit.txt的内容成为了less命令的标准输入,从而实现了分页浏览。

课程总结

本节课中我们一起学习了Bash中标准输入的核心用法。我们首先介绍了如何使用read -p命令与用户交互,获取输入并存入变量。然后,我们通过一个获取文件绝对路径的脚本实例,展示了如何将用户输入应用到实际任务中。最后,我们了解了如何将文件内容作为另一个命令的输入,这体现了Linux“一切皆文件”和管道思想的灵活性。掌握这些标准输入的操作,是编写高效、交互式Bash脚本的基础。

032:标准错误的使用 📝

在本节课中,我们将学习标准错误(Standard Error)的概念及其在脚本编写中的重要性。标准错误是程序输出错误信息的通道,理解如何管理它对于编写健壮的脚本至关重要。

理解标准错误

上一节我们介绍了标准输出,本节中我们来看看标准错误。观察标准错误行为的一个有效方法是运行一个必定会失败的假命令。

例如,运行 ls 命令并指定一个不存在的路径:

ls /some/nonexistent/path

命令执行后会显示错误信息:“ls: cannot access '/some/nonexistent/path': No such file or directory”。

如果你运行的脚本会输出许多路径,其中一些有效,一些无效,你可能不希望所有这些错误信息都混杂在标准输出中。此时,你可以将标准错误重定向到另一个位置。

重定向标准错误到文件

以下是重定向标准错误的方法。使用 2> 来引用标准错误,然后可以将其管道传输到一个文件中。

例如,将错误信息重定向到 errors.txt 文件:

ls /some/nonexistent/path 2> errors.txt

执行后,错误信息不再显示在终端,而是被写入 errors.txt 文件。查看该文件内容,可以看到具体的错误信息。

如果希望追加错误信息而不是覆盖文件,可以使用 2>>

ls /another/fake/path 2>> errors.txt

每次运行此命令,错误信息都会追加到 errors.txt 文件中。这在编写需要检查多个路径有效性的脚本时非常有用,你可以将所有错误收集到一个文件中,便于后续分析和调试。

丢弃标准错误

在某些情况下,例如处理服务器任务时,你可能出于多种原因(如避免磁盘空间被无关日志占满)不希望显示任何错误信息。此时,可以将标准错误重定向到 /dev/null

/dev/null 是一个特殊的设备文件,它会丢弃所有写入其中的数据。这是一种常见的模式,用于忽略脚本运行过程中的所有错误输出。

执行以下命令:

ls /some/nonexistent/path 2> /dev/null

错误信息将完全消失,被系统丢弃。这在你确信某些错误无关紧要,或者不希望它们干扰主要输出时非常有用。

核心技巧总结

在处理标准错误时,有几个关键技巧需要掌握:

以下是两种主要的处理方法:

  1. 捕获并记录:使用 2>2>> 将错误重定向到文件,便于后续审查和调试。
  2. 丢弃忽略:使用 2> /dev/null 将错误输出丢弃,保持输出界面的整洁或避免无关日志累积。

掌握这些处理标准错误的简单技巧,你就能有效地应对脚本运行中出现的意外情况,提升脚本的健壮性和可维护性。

本节课中我们一起学习了标准错误的概念,以及如何通过重定向来捕获或丢弃错误信息。这些技能是编写可靠数据工程脚本的基础。

033:Bash脚本构建入门 🚀

在本节课中,我们将学习如何构建Bash脚本,以解决数据工程中的常见问题。课程内容包括Shell逻辑、数据处理以及如何使用Bash构建命令行工具。掌握这些技能后,你将能够创建实用的Bash命令行工具。

本周学习目标 📋

完成本周学习后,你将能够执行以下操作:

  • 使用Shell逻辑与控制流。
  • 编写语句以在Shell中处理数据。
  • 构建Bash脚本和命令行工具。

上一节我们明确了本周的学习目标,本节中我们将开始探索Bash脚本的基础构建方法。

核心概念与实践

Bash脚本的本质是将一系列Shell命令组织在一个文件中,使其能够按顺序或根据条件自动执行。一个简单的脚本以指定解释器开始:

#!/bin/bash

以下是构建Bash脚本的基本步骤:

  1. 创建脚本文件:使用文本编辑器创建一个新文件,通常以 .sh 作为扩展名。
  2. 添加执行权限:通过 chmod +x script_name.sh 命令使脚本可执行。
  3. 运行脚本:在终端中,使用 ./script_name.sh 来执行你的脚本。

在脚本中,你可以使用变量存储数据,其基本格式为 variable_name=value。例如:

name="DataEngineer"
echo "Hello, $name!"

了解基础后,我们来看看如何让脚本具备逻辑判断能力。Shell逻辑与控制流是脚本智能化的关键。

使用Shell逻辑与控制流

为了使脚本能够根据不同条件执行不同操作,我们需要使用控制流语句。最常用的是 if 条件判断,其基本结构如下:

if [ condition ]
then
    # 条件为真时执行的命令
else
    # 条件为假时执行的命令
fi

例如,检查一个文件是否存在:

if [ -f "data.csv" ]
then
    echo "文件存在。"
else
    echo "文件不存在。"
fi

此外,for 循环可以用于遍历列表或重复执行任务:

for file in *.csv
do
    echo "正在处理文件: $file"
done

掌握了逻辑控制,我们就可以在Shell中有效地处理和操作数据了。

在Shell中处理数据

Shell提供了强大的文本处理工具。例如,grep 命令用于搜索文本,awksed 用于更复杂的文本提取与转换。以下是一个组合使用管道 (|) 处理数据的例子:

# 查找包含"error"的行,并提取第一列
cat logfile.txt | grep "error" | awk '{print $1}'

你还可以在脚本中读取文件内容、将命令输出重定向到文件,这些都是数据工程中常见的操作。


最后,我们将所学知识整合起来,构建实用的命令行工具。

构建Bash命令行工具

一个优秀的命令行工具通常接受参数,使其更加灵活。在Bash脚本中,可以使用 $1, $2 等来获取传递给脚本的参数。例如:

#!/bin/bash
# 用法: ./tool.sh <文件名>
echo "你输入的文件是: $1"

结合之前所学的逻辑、循环和数据处理命令,你可以创建用于文件批量处理、日志分析或数据提取的自动化工具。记住,清晰的用法说明(通常通过检查参数数量来实现)和错误处理会让你的工具更专业。


本节课中我们一起学习了Bash脚本构建的核心知识。我们从学习目标出发,逐步掌握了创建Bash脚本的基础、使用逻辑控制流实现条件判断、利用Shell命令处理文本数据,最终将这些技能综合应用于构建自定义的命令行工具。通过实践这些内容,你将能够自动化完成数据工程中的许多重复性任务。

034:Shell逻辑与控制流简介 🐚

在本节课中,我们将学习如何在Shell脚本中使用逻辑判断和控制流结构。这些工具能让我们编写出能够自动决策、重复执行任务的脚本,从而高效地解决实际问题。

上一节我们介绍了Shell脚本的基础知识,本节中我们来看看如何让脚本变得更“智能”。

什么是Shell控制流?

Shell控制流指的是在脚本中控制命令执行顺序的结构。它主要包括条件判断循环。通过它们,脚本可以根据不同的情况执行不同的命令,或者重复执行某些命令直到满足特定条件。

控制流操作

以下是Shell脚本中常用的控制流操作,它们构成了脚本逻辑的基石。

条件判断:if 语句

if语句允许脚本根据条件测试的结果来决定执行哪部分代码。其基本语法结构如下:

if [ condition ]
then
    # 条件为真时执行的命令
else
    # 条件为假时执行的命令
fi

条件测试使用方括号 [ ],内部可以比较数字、字符串或检查文件状态。例如,[ $a -eq $b ] 检查两个变量是否相等。

循环结构

循环用于重复执行一系列命令。Shell中主要有两种循环:for循环和while循环。

for 循环
for循环会遍历一个列表中的每个项目,并对每个项目执行相同的命令块。

for variable in list
do
    # 使用 $variable 执行命令
done

while 循环
while循环会一直执行,只要给定的条件保持为真。

while [ condition ]
do
    # 要重复执行的命令
done

在脚本中实现控制流

理解了基本结构后,关键在于将它们组合到脚本中。一个典型的脚本可能以#!/bin/bash开头,然后定义变量,接着使用if语句做检查,最后用循环来处理多个项目。

记住,脚本中的命令是从上到下顺序执行的,但控制流结构可以改变这个顺序。

使用Shell控制流解决问题

学习语法的最终目的是应用。Shell控制流的核心价值在于解决重复性或条件性的任务。

例如,你可以编写一个脚本:

  1. 检查某个目录是否存在(使用if-d测试)。
  2. 如果不存在则创建它。
  3. 使用for循环遍历当前目录下的所有.txt文件。
  4. 对每个文件进行备份(例如,复制并重命名)。

这只是一个简单例子,结合管道、命令替换和变量,你可以构建出非常强大的自动化工具。

使用Shell循环

让我们更具体地看看循环的威力。for循环特别适合处理已知集合,比如文件列表或数字序列。

以下是一个遍历数字序列的示例:

for i in {1..5}
do
    echo "迭代次数: $i"
done

while循环则更适用于条件未知、需要持续监控的场景,比如读取文件直到末尾,或等待某个进程完成。


本节课中我们一起学习了Shell逻辑与控制流。我们介绍了if条件判断和forwhile循环的基本语法,探讨了如何将它们融入脚本以实现自动化决策和重复任务。掌握这些控制流结构是编写高效、智能Shell脚本的关键一步。

035:什么是Shell控制流 🧭

在本节课中,我们将要学习Shell脚本中的控制流。控制流本质上是一种逻辑形式,它允许脚本根据不同的条件执行不同的命令,从而实现自动化决策。

概述

在现实世界中,我们时刻都在运用逻辑。一个很好的例子是去杂货店购物。你可能想要某种特定的牛奶,比如有机牛奶。如果这种牛奶缺货,你的脑海中就会形成一个条件判断:如果这种牛奶没有,那么我就选另一种牛奶。如果第二种选择也没有,你或许还能接受第三种选项。Shell脚本中的控制流与此非常相似。你使用工具并编写这些条件语句来处理各种情况,从而完全自动化一个流程或工作流。

控制流详解

让我们更详细地了解一下。以下是一个控制流示意图,它展示了一个典型的if-elif-else结构。

图中展示了一个控制流程图。你可以看到有一个if语句和一个elif语句。在任何一种情况下,都会根据匹配到的条件运行相应的命令。

请注意,在第一个if语句中,如果你找不到某个文件(例如),那么就会注册一个false结果,程序将跳转到下一个代码块,即elif

你可能会尝试寻找第二个文件。如果找到了那个文件,你就会运行一个命令,比如进行归档操作。最后,你会看到这里的false语句实际上是触发第二个循环的条件。

最终,当所有操作完成后,脚本会执行fi,这标志着脚本的结束。

因此,具体执行哪个命令,完全取决于你处于代码的哪个分支。在第一个if语句中,如果你能找到一个MP4文件并归档它,那么流程就直接通过。如果那个MP4文件不存在,那么就会进入第二个场景,即false语句。

所以,这本质上是一种基于条件匹配的流程。脚本会遍历并确定根据代码块的命中情况,接下来应该执行哪部分,然后继续传递到其他条件。

现在请注意,如果它不满足前两个条件(即ifelif),结果将再次为false,然后你会运行其他命令。这可能是类似这样的操作:如果这两个文件都不存在,也许向标准输出发送一条打印语句,让用户知道“我无法操作,我需要这些输入”。

因此,控制流与现实世界中的逻辑决策非常相似,它只是一系列允许你逻辑性地得出结论的步骤。

核心概念与结构

在Shell脚本中,控制流的基本结构通常如下所示:

if [ condition1 ]; then
    # 如果条件1为真,执行这里的命令
elif [ condition2 ]; then
    # 如果条件2为真,执行这里的命令
else
    # 如果以上条件都不为真,执行这里的命令
fi

以下是控制流中几个关键组成部分的说明:

  • if:用于检查第一个条件。
  • elif:是“else if”的缩写,用于检查额外的条件(可以有多个)。
  • else:当前面所有条件都不满足时,执行此分支。
  • fi:标志着整个if语句块的结束。
  • 条件判断:通常使用方括号 [ ][[ ]] 来包裹测试表达式。

总结

本节课中,我们一起学习了Shell脚本中的控制流。我们了解到,控制流是一种逻辑结构,它通过ifelifelse等语句,让脚本能够根据不同的条件自动做出决策并执行相应的命令。这就像为脚本赋予了“思考”能力,使其能够处理各种可能的情况,从而构建出强大且灵活的自动化工作流。掌握控制流是编写有效Shell脚本的关键一步。

036:使用Shell控制流解决Bash问题 🐚

在本节课中,我们将学习如何在Bash脚本中使用if-else条件语句。这是一种根据不同的输入或条件执行不同操作的核心编程概念。我们将通过一个简单的食物选择示例,来演示其工作原理。

上一节我们介绍了Bash脚本的基础知识,本节中我们来看看如何使用控制流来让脚本做出决策。

代码解析与执行

首先,让我们逐步分析示例代码,然后运行它来观察效果。

脚本以一个echo命令开始,它会向用户打印问题:“what food do you choose”。接着,read命令会等待并读取用户的输入,并将其存储在一个名为food的变量中。

现在,条件判断部分开始了。方括号 [ ] 用于进行字符串匹配。其逻辑是:如果food变量的值等于字符串“apple”,则执行对应的条件语句。

以下是脚本中完整的条件判断结构:

if [ "$food" = "apple" ]; then
    echo "Eat yogurt with your apple."
elif [ "$food" = "milk" ]; then
    echo "Eat cereal with your milk."
else
    echo "Eat your $food by itself."
fi
  • if:开始一个条件判断块。
  • [ "$food" = "apple" ]:这是测试条件。它检查变量$food的值是否等于“apple”。注意,在Bash中,字符串比较使用单个等号=,并且变量和等号两边都需要有空格。
  • then:如果上述条件为真,则执行后面的语句。
  • elif:代表“else if”。如果第一个条件不满足,则检查这个条件($food是否等于“milk”)。
  • else:如果以上所有条件都不满足,则执行这部分语句。
  • fi:非常重要,它用于关闭整个if条件语句块。

此外,脚本开头有 #!/bin/bash(shebang)行,它指定了用哪个解释器来执行脚本。我们还需要使用 chmod +x 命令赋予脚本可执行权限。

运行示例

现在,让我们运行这个脚本,看看不同输入下的结果。

首先,输入 apple。脚本必须精确匹配“apple”这个单词。

Eat yogurt with your apple.

这部分运行成功。

接下来,输入 milk

Eat cereal with your milk.

运行良好。

最后,我们输入一个既不是“apple”也不是“milk”的食物,例如 steak

Eat your steak by itself.

脚本正确地执行了else分支。

总结与练习

本节课中我们一起学习了Bash中if-else条件语句的基本用法。可以看到,Bash的条件判断结构相当直观。

这是一个供你练习的绝佳例子。本示例代码已托管在GitHub上,仓库名为 “bash-control-flow”。你可以访问该仓库链接,获取代码并在自己的环境中进行修改和实验,以加深理解。

通过这个简单的食物选择器,你已经掌握了使用Shell控制流来构建能够响应不同输入的Bash脚本的基础。

037:Shell循环的使用 🔄

在本节课中,我们将要学习Bash脚本中两种核心的循环结构:for循环和while循环。循环是自动化处理重复任务的关键,例如遍历文件列表或执行特定次数的操作。我们将通过简单的例子来理解它们的工作原理。

理解循环的概念

循环的概念类似于去杂货店购物。你拿一个购物篮,把商品放进去,然后在结账时,你需要把篮子里的东西一件件拿出来。循环就是这样一个过程:从一个“篮子”(例如数组或计数器)中逐个“取出”项目并进行处理。

使用 for 循环遍历数组

以下是使用for循环遍历数组的典型方法。首先,我们需要创建一个“篮子”——也就是数组,然后使用循环来逐个处理其中的元素。

代码示例:

#!/bin/bash
# 声明一个数组
declare -a array=("apple" "pear" "cherry")

# 使用for循环遍历数组
for item in "${array[@]}"
do
    echo "This $item is delicious"
done

执行结果:

This apple is delicious
This pear is delicious
This cherry is delicious

工作原理:

  1. #!/bin/bash 指定脚本使用Bash解释器执行。
  2. declare -a array=(...) 声明并初始化一个名为array的数组,其中包含三个水果。
  3. for item in "${array[@]}" 开始一个for循环。"${array[@]}"表示展开数组的所有元素。循环会依次将每个元素赋值给临时变量item
  4. dodone之间的代码是循环体,对每个item执行一次。这里只是简单地打印一条消息。
  5. 你可以轻松地向数组中添加更多元素(如“strawberry”),循环会自动处理所有项。

for循环非常适合处理已知的、离散的项目集合,例如文件列表或配置项。

使用 while 循环与计数器

上一节我们介绍了用于遍历集合的for循环。本节中我们来看看另一种循环——while循环,它特别适合在满足某个条件时重复执行操作,通常与计数器结合使用来控制循环次数。

代码示例:

#!/bin/bash
echo "How many loops do you want?"
read loops

# 初始化计数器
count=1

# while循环:当 count <= loops 时执行
while [ $count -le $loops ]
do
    echo "Loop $count"
    # 递增计数器
    ((count++))
done

执行结果(用户输入4):

How many loops do you want?
4
Loop 1
Loop 2
Loop 3
Loop 4

工作原理:

  1. read loops 从用户输入获取希望循环的次数,并存储在变量loops中。
  2. count=1 初始化计数器变量count
  3. while [ $count -le $loops ] 是循环条件。-le表示“小于等于”。只要count的值小于等于loops的值,循环就会继续。
  4. 在循环体内,echo "Loop $count"打印当前循环次数。
  5. ((count++)) 是关键步骤,它使用算术运算将计数器count的值增加1。没有这一步,count永远不会改变,循环将无限进行下去。
  6. 每次循环结束后,都会重新检查while条件。当count递增到大于loops时,条件不再满足,循环结束。

为了更清晰地理解执行顺序,可以添加调试信息:

带调试信息的代码:

#!/bin/bash
echo "How many loops do you want?"
read loops

count=1
while [ $count -le $loops ]
do
    echo "Count before increment: $count"
    echo "Loop $count"
    ((count++))
    echo "Count after increment: $count"
done

执行结果(用户输入2):

How many loops do you want?
2
Count before increment: 1
Loop 1
Count after increment: 2
Count before increment: 2
Loop 2
Count after increment: 3

通过输出,你可以清晰地看到变量count在循环开始、打印信息后以及递增后的值变化过程。

掌握while循环和计数器对于实现重试机制(如尝试连接网络)、统计匹配项数量(如查找重复文件)等场景至关重要。

课程总结

本节课中我们一起学习了Bash脚本中两种强大的循环结构:

  • for 循环:用于遍历一个已知的项目列表(如数组中的元素、目录中的文件)。它简洁明了,是处理集合的首选。
  • while 循环:只要给定条件为真,就会持续执行。当与计数器结合时,它可以精确控制循环执行的次数,适用于需要动态控制或条件判断的重复任务。

理解并熟练运用这两种循环,将极大提升你使用Bash进行数据工程和自动化任务的能力。

038:Bash中的条件判断

在本节课中,我们将要学习Bash脚本中两个非常重要的逻辑运算符:&&(逻辑与)和 ||(逻辑或)。它们允许你根据前一个命令的执行结果来决定是否执行后续的命令,这在编写复杂的脚本、Dockerfile或Makefile时非常有用。

逻辑与运算符(&&)

上一节我们介绍了课程主题,本节中我们来看看第一个核心概念:逻辑与运算符。

在Bash中,双与符号 && 代表“逻辑与”。它的核心思想是:只有当前一个命令成功执行(返回退出状态码0)时,才会执行 && 后面的命令。这是一种将多个命令链接在一起执行的方式,常用于确保一系列操作按顺序且无错误地执行。

以下是 && 运算符的基本语法:

command1 && command2

在这个结构中,只有当 command1 成功执行后,command2 才会运行。

这种模式在Dockerfile和Makefile中非常常见。例如,在Dockerfile中,你可能会看到这样的结构,它确保了一系列安装步骤只有在前一步成功后才继续。

逻辑或运算符(||)

了解了“与”操作后,我们再来看看它的互补操作:逻辑或运算符。

双竖线符号 || 代表“逻辑或”。它的行为与 && 相反:只有当前一个命令执行失败(返回非零退出状态码)时,才会执行 || 后面的命令。这可以用来提供备选方案或错误处理。

以下是 || 运算符的基本语法:

command1 || command2

在这个结构中,只有当 command1 执行失败时,command2 才会运行。如果 command1 成功,则 command2 会被跳过。

实践与应用

理论需要结合实践来巩固。让我们通过一些具体的例子来看看这两个运算符是如何工作的。

查看Dockerfile中的实例

学习 &&|| 的一个好方法是研究真实的Dockerfile。例如,在Docker Hub的官方Python镜像页面,你可以看到大量使用 && 的范例。它们通常的格式是使用反斜杠 \ 将长命令换行,并在每一行的末尾使用 &&,确保所有步骤顺序执行且任何一步失败都会停止构建。这种风格保证了整个安装过程的原子性。

在脚本中实践

我们创建一个简单的脚本来演示。假设我们想连续创建三个文件,然后列出它们。

以下是使用 && 链式执行命令的示例:

touch file1.txt && touch file2.txt && touch file3.txt && ls *.txt

这段脚本会依次创建 file1.txtfile2.txtfile3.txt,只有前一个 touch 命令成功,下一个才会执行。最后,如果所有创建都成功,就会执行 ls 命令列出所有文本文件。

现在,让我们看看失败的情况。如果我们尝试列出一个不存在的文件:

ls non_existent_file.txt && echo “This will not print”

由于 ls 命令会失败(文件不存在),&& 后面的 echo 命令将不会执行。

对于 || 运算符,我们可以这样测试:

false || echo “This will print because the first command failed”

false 命令总是返回失败状态,因此 || 后面的 echo 命令会执行。

true || echo “This will NOT print because the first command succeeded”

true 命令总是返回成功状态,因此 || 后面的 echo 命令不会执行。

总结

本节课中我们一起学习了Bash中两个关键的条件判断运算符。

  • &&(逻辑与):用于命令链,只有前一个命令成功,才执行下一个命令。这是构建可靠、顺序操作序列的基础。
  • ||(逻辑或):用于提供备选路径,只有前一个命令失败,才执行下一个命令。这常用于错误处理或提供默认行为。

掌握这两个运算符对于编写健壮的Bash脚本、理解Dockerfile和Makefile至关重要。通过观察官方镜像的Dockerfile并自己动手实践,你将能快速掌握它们的用法,并应用到自己的自动化任务中。

039:Bash Shell 数据操作入门 🐚

在本节课中,我们将学习如何在 Bash Shell 环境中操作数据。Shell 提供了许多经过长时间发展的强大工具,能让你非常方便地对数据进行各种操作。掌握这些基础技能,是构建更复杂数据处理流程的第一步。

概述

本节课将分为三个主要部分。首先,我们将介绍用于数据操作的基础 Shell 技巧。然后,我们会学习如何截断、搜索和过滤数据。最后,我们将把这些技巧结合起来,在 Shell 中构建实际的数据处理流程。


Shell 数据操作技巧

首先,我们来看看能立即用于解决问题的基本 Shell 技巧。这些命令是处理文本和数据文件的核心。

以下是几个最常用和强大的命令:

  • cat:用于查看文件内容。
    • 示例:cat data.txt
  • headtail:分别用于查看文件的开头或结尾部分。
    • 示例:head -n 10 data.txt (查看前10行)
  • wc:用于统计文件的行数、单词数和字符数。
    • 示例:wc -l data.txt (统计行数)
  • sort:用于对文件内容进行排序。
    • 示例:sort data.txt
  • uniq:用于报告或忽略重复的行,通常与 sort 结合使用。
    • 示例:sort data.txt | uniq

数据的截断、搜索与过滤

上一节我们介绍了一些基础命令,本节中我们来看看如何对数据进行更精确的控制,包括截取特定部分、搜索内容以及按条件过滤行。

一旦掌握了这三项核心操作,你就能轻松创建更复杂的数据处理流程了。

以下是实现这些功能的关键命令:

  • 截断数据:使用 cut 命令可以从每行中提取特定的字段或字符。
    • 示例:cut -d‘,’ -f1,3 file.csv (以逗号为分隔符,提取第1和第3列)
  • 搜索数据:使用 grep 命令可以搜索包含特定模式的行。
    • 示例:grep “error” logfile.txt (搜索包含“error”的行)
    • 示例:grep -v “debug” logfile.txt (搜索包含“debug”的行)
  • 过滤数据:使用 awk 命令可以进行更复杂的文本分析和处理,它是一种强大的编程语言。
    • 示例:awk ‘$3 > 100 {print $1}’ data.txt (如果第三列值大于100,则打印第一列)

在 Shell 中进行数据处理

最后,我们将学习如何组合使用这些工具,通过管道(|)构建数据流水线,以解决实际问题。管道能将一个命令的输出直接作为另一个命令的输入。

例如,要找出一个日志文件中出现频率最高的错误信息,你可以组合多个命令:

grep “ERROR” application.log | cut -d‘ ’ -f4- | sort | uniq -c | sort -nr | head -5

这个流水线的步骤如下:

  1. grep “ERROR” application.log:过滤出所有包含“ERROR”的行。
  2. cut -d‘ ’ -f4-:从每行中提取出从第四个字段开始的内容(即错误信息本身)。
  3. sort:对错误信息进行排序,为下一步去重做准备。
  4. uniq -c:统计每条唯一错误信息出现的次数。
  5. sort -nr:按出现次数进行数字降序排序。
  6. head -5:显示出现次数最多的前5条错误信息。

通过这种方式,你可以将简单的命令串联起来,执行非常强大的数据分析任务。


总结

本节课中我们一起学习了 Bash Shell 环境下的数据操作。我们从基础的 catheadsort 等命令开始,然后深入学习了如何使用 cutgrepawk 来截断、搜索和过滤数据。最后,我们看到了如何通过管道将这些工具组合成强大的数据处理流水线来解决实际问题。掌握这些技能,将为你在命令行中高效处理数据打下坚实的基础。

040:Bash Shell数据处理技巧 🛠️

在本节课中,我们将学习一些Bash Shell的数据处理技巧。这些技巧能帮助数据工程师更高效地处理文件和数据,就像使用工具箱中的不同工具一样。我们将介绍截取、过滤、提取和搜索等核心操作,并通过具体示例展示如何组合使用这些工具来解决实际问题。


截取文件内容

上一节我们介绍了本课程的目标,本节中我们来看看第一个常用技巧:截取文件内容。headtail命令是你的得力助手,它们分别用于查看文件的开头或结尾部分。默认情况下,它们会显示文件的前10行或后10行。

如果你想指定查看的行数,可以使用-n参数。例如,要查看文件的前3行,命令如下:

head -n3 filename.txt

同样,要查看文件的后5行,命令如下:

tail -n5 filename.txt

过滤文件内容

接下来,我们看看如何过滤文件内容。grep命令是一个非常强大的工具,用于在文件中搜索特定的文本模式。

例如,如果你想在一个文件中查找单词“bird”,只需运行:

grep "bird" filename.txt

该命令会输出所有包含“bird”的行。grep支持正则表达式,可以进行更复杂的模式匹配。


提取特定列

现在,我们来学习如何从文件中提取特定的列。cut命令非常适合处理具有多列数据的文件,例如CSV或TSV文件。

假设你有一个以逗号分隔的文件,并且只想查看第一列,可以使用以下命令:

cut -d',' -f1 filename.csv

这里,-d','指定分隔符为逗号,-f1表示提取第一列。cut命令功能丰富,但提取列是其最常见的用途之一。


搜索文件系统

最后,我们探讨在文件系统中搜索文件的技巧。find命令允许你实时搜索文件系统,功能非常强大。

例如,要在当前目录及其子目录中查找所有扩展名为.txt的文件,可以运行:

find . -name "*.txt"

另一个有用的命令是locate,它基于预先构建的索引进行搜索,因此速度非常快。例如:

locate filename.txt

在某些系统(如OS X)上,还有一个名为mdfind的命令,可以搜索文件的元数据。这些搜索工具各有优势,在构建复杂解决方案时都非常有帮助。


总结

本节课中,我们一起学习了Bash Shell中几种核心的数据处理技巧。我们介绍了如何使用headtail截取文件内容,使用grep过滤特定模式,使用cut提取数据列,以及使用findlocate搜索文件系统。掌握这些工具并能灵活组合它们,将极大提升你作为数据工程师处理日常任务的能力。

041:Bash数据截断

在本节课中,我们将学习如何使用Bash命令来处理大型数据文件,特别是如何查看文件的开头、结尾以及进行随机采样。这些技巧对于高效地预览和操作大数据集至关重要。

截断的概念

上一节我们介绍了Bash脚本的基础。本节中,我们来看看数据截断。截断是处理大数据文件时的一个重要概念。它允许我们只查看或处理文件的特定部分,而不是整个文件,这在处理无法一次性加载到内存或屏幕的大型文件时非常有用。

生成示例文件

为了演示截断操作,我们首先需要一个示例文件。以下是一个名为populate_file的脚本,它可以生成一个包含多行数据的文件,以便我们练习如何获取文件的顶部、底部以及随机行。

# 这是一个生成示例文件的脚本
# 它会询问用户想要创建多少行数据

运行此脚本,并创建一个包含200行的文件。使用wc -l命令可以验证文件确实有200行。

查看文件内容

如果直接使用cat命令查看整个文件,由于输出内容过多,我们无法在屏幕上看到所有信息。因此,我们需要使用截断技术。

使用head命令

head命令允许我们查看文件的开头部分。默认情况下,它会显示文件的前10行。

head filename.txt

如果只想查看前几行,可以使用-n选项指定行数。

head -n 5 filename.txt

以下是使用head命令查看文件前几行的步骤:

  1. 打开终端。
  2. 导航到文件所在目录。
  3. 输入head filename.txt查看前10行。
  4. 或输入head -n [行数] filename.txt查看指定行数。

使用tail命令

tail命令与head相反,它用于查看文件的末尾部分。默认显示最后10行。

tail filename.txt

同样,可以使用-n选项来指定要查看的行数,例如只查看最后一行。

tail -n 1 filename.txt

tail命令在需要快速检查日志文件最新记录或文件结尾内容时非常有用。

随机采样数据

在处理超大型数据集(例如TB级数据)时,我们可能没有足够的资源用特定工具处理全部数据,或者需要先创建一个较小的样本来测试流程。这时可以使用shuffle命令(在Linux系统中通常是shuf)。

shuffle命令可以随机打乱文件中的行,并允许我们选择其中的一部分。这对于创建机器学习训练数据的随机样本或初步测试数据处理管道非常有效。

# 随机打乱文件行并输出前N行作为样本
shuf -n [样本行数] filename.txt

例如,从大文件中随机选取5行并保存到一个小文件中:

shuf -n 5 large_data.txt > sample.txt

然后可以使用cat sample.txt来查看这个小型样本文件的内容。

命令总结

本节课中我们一起学习了三个用于数据截断和采样的核心Bash命令。

  • head:用于查看文件的开头部分。
  • tail:用于查看文件的结尾部分。
  • shuf:用于对文件行进行随机打乱和采样。

掌握这些命令能帮助你在命令行中高效地预览和初步处理大型数据文件,为后续的数据工程任务打下基础。

042:Bash数据过滤 🔍

在本节课中,我们将要学习如何使用Bash命令grep来过滤和分析文本文件中的数据。这对于处理大型日志文件、数据集或任何需要快速查找特定信息的文本场景非常有用。


上一节我们介绍了Bash的基础操作,本节中我们来看看如何使用grep命令进行数据过滤。首先,我们有一个脚本,它能生成一个包含随机字符串的文件。这些字符串是“apple”、“pear”或“cherry”中的一个。

运行脚本后,我们得到一个名为filter_file的文件,其中包含250行数据。每行有一个行号和一个随机选择的字符串。

现在,我们可以开始过滤这些数据。这在数据科学中很常见,例如当你有一个巨大的文件并想初步了解其内容时。

以下是使用grep进行过滤的基本操作:

查找特定字符串
要查找文件中所有包含“apple”的行,可以使用命令:

grep apple filter_file

这个命令会输出所有包含“apple”的行及其行号。

统计出现次数
如果你想知道“apple”出现了多少次,可以加上-c选项:

grep -c apple filter_file

例如,结果显示“apple”出现了76次。这在快速估算数据集中某个类别的数量时非常方便。

查找多个模式
有时,你可能想同时查找多个模式。使用-e选项可以指定多个模式:

grep -e apple -e pear filter_file

这个命令会找出所有包含“apple”或“pear”的行。

统计多个模式的出现次数
要统计多个模式总共出现了多少次,可以结合使用-e-c

grep -c -e apple -e pear filter_file

结果显示,在250行中,共有169行包含“apple”或“pear”。

反向过滤(排除特定模式)
在某些情况下,你可能希望排除包含特定字符串的行。例如,日志文件中可能有很多重复的无关信息,你想忽略它们。这时可以使用-v选项:

grep -v apple filter_file

这个命令会显示所有不包含“apple”的行。

组合使用过滤
你还可以将多个grep命令组合起来,进行更复杂的过滤。例如,先排除“apple”,再在结果中查找“pear”:

grep -v apple filter_file | grep -c pear

这个管道命令先过滤掉所有含“apple”的行,然后统计剩余行中“pear”的出现次数。


本节课中我们一起学习了grep命令在Bash中的数据过滤功能。我们掌握了如何查找特定字符串、统计出现次数、同时匹配多个模式、排除不需要的内容,以及通过命令组合进行复杂过滤。这些技巧是处理和分析文本数据的强大工具,能帮助你在海量信息中快速定位关键内容。

043:Bash文件系统搜索 🔍

在本节课中,我们将学习如何在Bash终端中使用 find 命令来搜索文件系统。这对于处理包含大量文件和目录的数据工程或机器学习项目至关重要,它能帮助你快速定位所需文件。


概述

当你在处理一个庞大的数据目录树时,例如来自机器学习或数据工程项目的文件,一个常见的问题是如何找到特定文件的位置。幸运的是,Bash终端提供了一个名为 find 的命令,它能让搜索变得非常简单。本节我们将深入探讨 find 命令的基本用法和一些高级技巧。


按名称和扩展名搜索文件

首先,我们来看一个基本用法:搜索当前工作目录中所有具有特定扩展名的文件。

命令示例:

find . -name "*.sh"

在这个命令中:

  • find 是命令本身。
  • . 指定了搜索的起始位置,即当前工作目录。
  • -name "*.sh" 是搜索条件,表示查找所有以 .sh 结尾的文件。

运行此命令后,你可能会看到类似以下的输出:

./truncate_script.sh
./filter_script.sh
./populate_file_script.sh
./pop_filter_script.sh

这表明命令成功找到了四个匹配的脚本文件。这是一个非常有用的模式:指定搜索位置和文件名(或扩展名)模式。


递归搜索与模式匹配

那么,如果你想递归地搜索所有CSV文件呢?方法非常相似。

命令示例:

find . -name "*.csv"

通过使用 . 作为起始点,find 命令会从当前位置开始,递归地向下搜索所有子目录。你会看到它遍历了当前目录下的所有文件夹,最终找到并列出所有包含 .csv 扩展名的文件。这是递归遍历文件系统并查找特定模式的绝佳方式。


按文件类型和权限搜索

另一个常见需求,尤其是在编写脚本时,是专门查找具有特定属性的文件,例如可执行文件。

命令示例:

find . -type f -executable ! -path '*/.*'

让我们分解这个命令:

  • find .:从当前目录开始搜索。
  • -type f:只查找文件(f 代表 file),排除目录。
  • -executable:查找设置了可执行权限的文件。
  • ! -path '*/.*':这是一个排除条件。! 表示“非”,-path '*/.*' 匹配所有以点开头的隐藏文件或目录路径。因此,这部分的作用是不搜索隐藏文件/目录。

运行此命令,你将只得到你真正关心的那些显式的可执行文件,而不会看到像 .git/ 这样的版本控制系统的隐藏目录,从而减少了输出中的“噪音”。


总结

本节课我们一起学习了 find 命令的强大功能。我们掌握了如何:

  1. 按名称搜索:使用 -name 参数和通配符来查找特定文件。
  2. 递归搜索:通过指定目录起点(如 .),find 可以遍历整个目录树。
  3. 按类型和权限过滤:结合 -type-executable 等参数精确查找目标。
  4. 排除干扰项:使用 ! -path 来忽略不需要搜索的路径(如隐藏目录)。

find 是处理数据工程项目时一个非常重要且实用的工具,熟练使用它能极大提升你在文件系统中定位和管理数据的效率。

044:Bash脚本与命令行工具编写入门 🐚

在本节课中,我们将学习Bash脚本和命令行工具的基础知识,并深入探讨如何构建它们。我们将了解脚本和命令行工具的定义,学习如何构建一个Bash脚本,并最终创建一个实用的Bash命令行工具来解决实际问题。


什么是脚本与命令行工具?

上一节我们介绍了Bash的基本操作。本节中,我们来看看脚本和命令行工具的具体定义。

  • 脚本:脚本是一个包含一系列命令的文本文件。当执行脚本时,系统会按顺序运行文件中的命令。脚本通常用于自动化重复性任务。
  • 命令行工具:命令行工具是可以在终端中直接调用的程序或脚本。它们通常接受输入(参数),执行特定功能,并产生输出。一个编写良好的脚本可以被配置为方便的命令行工具。


如何构建一个Bash脚本

理解了基本概念后,我们现在开始学习如何构建一个Bash脚本。

构建Bash脚本有几个关键步骤,以下是详细说明:

  1. 创建脚本文件:使用文本编辑器创建一个新文件,通常以 .sh 作为扩展名,例如 myscript.sh
  2. 指定解释器(Shebang):在脚本文件的第一行,需要指定用于执行脚本的解释器。对于Bash脚本,使用:
    #!/bin/bash
    
    这行代码告诉系统使用 /bin/bash 来运行此脚本。
  3. 编写命令:在Shebang行之后,可以按顺序写入需要执行的Bash命令。
  4. 赋予执行权限:创建文件后,它默认没有执行权限。需要使用 chmod 命令添加权限:
    chmod +x myscript.sh
    
  5. 运行脚本:现在可以通过以下方式运行脚本:
    ./myscript.sh
    

构建一个Bash命令行工具

我们已经学会了创建基础脚本。本节中,我们来看看如何将脚本升级为一个更实用、更像标准命令行的工具。

一个有用的命令行工具通常能处理用户输入。在Bash脚本中,可以使用特殊变量来获取传递给脚本的参数。

  • $1$2, ...: 代表第一个、第二个参数。
  • $0: 代表脚本本身的名称。
  • $#: 代表传递给脚本的参数个数。
  • $@: 代表所有参数的一个列表。

例如,我们可以创建一个名为 greet.sh 的工具,它接受一个名字作为参数并打招呼。

#!/bin/bash
# 这是一个简单的命令行工具示例
echo "Hello, $1! Welcome to the command line."

保存并赋予权限后,可以这样使用:

./greet.sh Alice

输出结果为:

Hello, Alice! Welcome to the command line.

通过使用条件判断(如 if 语句)、循环和参数检查,可以让脚本变得更健壮和实用,从而解决更复杂的问题。


本节课中我们一起学习了Bash脚本与命令行工具的基础。我们明确了脚本与命令行工具的定义,逐步实践了从创建脚本文件、添加Shebang、编写命令到赋予权限和运行的完整流程。最后,我们通过使用特殊变量处理参数,将一个简单的脚本转化为一个可交互的命令行工具。掌握这些技能是自动化任务和构建高效数据工程流水线的重要一步。

045:脚本与命令行工具解析 🐚

在本节课中,我们将学习Bash编程中的三种核心操作模式:语句、脚本和命令行工具。我们将探讨它们各自解决的问题、适用场景以及如何在实际工作中选择和使用它们。


什么是语句?

上一节我们介绍了课程概述,本节中我们来看看第一种操作模式:语句。

语句是你在Bash命令行中交互式执行的单条命令。它通常是临时性的,用于快速尝试或探索某些操作,执行后通常不会保存或复用。

一个典型的例子是使用 grep 命令搜索文件中的内容:

grep "error" logfile.txt

执行这条语句后,你看到了结果,任务就完成了。你可能会通过命令历史来重新运行它,但语句本身并非为长期复用而设计。它的核心价值在于交互式探索


什么是脚本?

理解了临时性的语句后,我们来看看更持久的解决方案:脚本。

脚本是将一系列命令或函数组织在一个文件中的程序。它用于创建可重复执行的自动化工作流,比单条语句更正式和结构化。

以下是创建一个简单备份脚本的步骤:

  1. 创建脚本文件:使用文本编辑器创建一个新文件,例如 backup.sh
  2. 添加Shebang行:在文件第一行指定解释器,例如 #!/bin/bash
  3. 编写命令:在文件中添加要执行的命令序列。
  4. 赋予执行权限:使用 chmod +x backup.sh 命令使脚本可执行。
  5. 运行脚本:通过 ./backup.sh 执行脚本。

脚本的优势在于可以保存、版本控制,并定期运行(例如通过cron作业),非常适合自动化重复性任务。


什么是命令行工具?

在掌握了脚本的基础上,我们来看看功能更强大、更灵活的形式:命令行工具。

命令行工具是一种特殊的脚本或程序,它接受用户输入(如参数和标志)来定制其行为。几乎所有的Unix工具(如 ls, cat, grep)都是命令行工具。

命令行工具的关键特性是可配置性。例如:

ls -l /home  # `-l` 是一个标志(flag),用于改变`ls`命令的输出格式
grep -i "warning" system.log  # `-i` 标志使搜索不区分大小写

以下是命令行工具的核心组成部分:

  • 参数:命令作用的对象,如文件名。
  • 标志(Flags/Options):以 --- 开头的选项,用于修改命令行为,例如 -h(帮助)、-l(长格式列表)。
  • 输入/输出:可以从标准输入读取数据,或将结果输出到标准输出或文件。

命令行工具结合了语句的交互性和脚本的自动化能力,通过参数化实现了高度的灵活性,是自动化的终极形式。在构建可复用的工具时,应优先考虑为其设计清晰的命令行接口。


总结

本节课中我们一起学习了Bash中三种重要的操作模式。

  • 语句是用于即时探索的临时性交互命令。
  • 脚本是保存在文件中的命令集合,用于实现可重复的自动化任务。
  • 命令行工具是带参数和标志的脚本或程序,提供了最高级别的灵活性和可配置性,是构建强大自动化工作流的基石。

理解这三者的区别和联系,能帮助你在不同场景下选择最合适的工具,从而更高效地使用命令行完成数据工程任务。

046:构建Bash脚本 🐚

在本节课中,我们将要学习如何构建一个基础的Bash脚本。我们将了解脚本的核心组成部分,包括Shebang行、变量、语句以及一些用于调试和增强脚本健壮性的设置。


脚本的核心组件

让我们深入了解Bash脚本的一些关键组成部分。

首先,脚本的最开头是Shebang行。Shebang行的作用是告诉系统应该使用哪个解释器来执行这个脚本。例如,#!/bin/bash 表示我们希望使用Bash来运行它。它也可以是Python、Z shell或其他任何shell。这一点很重要,因为有了它,我们就不必在运行脚本前手动输入 bash 命令。

另一个需要注意的点是,你需要使用 chmod +x 命令使脚本文件可执行。这样,任何人都可以执行这个脚本,你也能在自动化流程中重复使用它。

接下来要指出的是变量。如果你想创建一个变量,只需要通过等号语句进行赋值即可。例如,variable="1" 将一个字符串赋值给变量。之后,你可以在代码的其他部分使用这个变量。

最后是语句。在这一部分,你可以将语句串联起来。例如,我打印出“word”这个词加上我之前设置的变量。实际上,要构建你的第一个Bash脚本,只需要了解这几样东西。一旦你掌握了它,它就是一种自动化形式,你可以将一系列步骤或一个待办事项列表放入脚本中,然后让它们按顺序运行。


一个基础的Bash脚本示例

现在,我们来看一个基础的Bash脚本示例。脚本的核心组件包括Shebang行、调试模式、语句和变量。如果我们想最简略地理解如何编写Bash脚本,我建议关注以下三点。

以下是这个基础Bash脚本的内容。首先,请注意这里的Shebang行。其次,一个很好的做法是在脚本开头添加注释。在这里,你可以说明你的代码具体是做什么的,这样即使十年后其他人看到你的脚本,也能明白发生了什么。

接下来重要的是,通常建议设置严格模式。你可以通过 set -e 来实现。这会使shell在命令执行失败时立即退出。这非常有用,因为它能让你的代码对潜在错误更具鲁棒性。

你还可以启用打印shell输入行的功能,这对于调试很有帮助。另一个可以做的操作是启用在执行命令前打印命令追踪信息的功能。这很好,因为它会告诉你它将要做什么,然后再去执行。

最后,核心组件是变量和语句。在这里,我创建了一个变量,然后在语句中使用了它。让我们运行一下这个脚本。如果我输入 bash script_basic,它会执行并显示调试信息。现在,我可以注释掉这些调试设置,再次运行。你会发现输出变得简洁了,只显示“这是一个带有变量1的脚本”。通常,你可以来回切换这些设置,决定你希望在代码中看到多少详细信息。

Bash还有一个巧妙的小功能,你可以在执行时设置详细的调试模式。如果我将Shebang行改为 #!/bin/bash -xv,让我们看看会发生什么。运行后,它会向标准输出打印大量信息。实际上,它在这里展示了我代码执行的所有细节,包括运行的环境、执行前和执行过程中发生的情况,甚至在执行前打印出整个脚本本身。如果你在代码调试中遇到困难,想进行详细调试,这是一个值得了解的好选项。


总结

本节课中,我们一起学习了构建Bash脚本的基础知识。我们了解了脚本的三大核心组件:Shebang行、变量和语句。我们还探讨了如何通过 chmod 命令使脚本可执行,以及如何使用 set -e 等选项来设置严格模式和调试信息,从而使脚本更健壮、更易于调试。掌握这些基础后,你就可以开始创建自己的自动化脚本了。

047:构建Bash函数 🛠️

在本节课中,我们将要学习Bash脚本中一个非常强大的概念:函数。我们将了解函数是什么、为什么需要它们,以及如何在Bash中创建和使用函数。

概述

Bash函数就像一台小型机器,它接收输入,执行一系列操作,然后产生输出。理解这个概念是编写高效、可重用脚本的关键。本节我们将通过具体示例,学习如何构建和使用Bash函数。

理解Bash函数:一台小型机器

让我们来看看Bash函数。一个很好的理解方式是,把它们想象成你家中的小机器。

例如,你有一台洗碗机。什么东西会进入洗碗机呢?我们有水,还有碗碟。这些就是进入机器的输入。机器运转,清洗所有东西,然后你得到一个输出,那就是干净的碗碟,你可以把它放进碗柜。

同样地,在Bash或任何编程语言中,你也有类似的东西。例如,你有一个数字1,也许还有一个数字2,它们进入机器,被加在一起。这里与其他编程语言的区别在于,Bash中没有显式的return语句。你不能直接返回例如1和2相加的值。不过,你可以做的是,将结果echo出来,然后捕获这个echo。这与其他编程语言略有不同。

但总的来说,核心概念是把函数看作一台机器。那么,你为什么想要一台洗碗机呢?因为你可以反复使用它。同样地,你为什么想要一个Bash函数呢?因为你可以反复使用它,这允许你重用你的代码。

构建你的第一个Bash函数

让我们来看一个例子。

以下是构建一个简单Bash函数的基本结构:

#!/bin/bash

# 这是一个简单的函数示例
function mimic() {
    echo $1
    echo $2
}

在这个脚本的开头,有一个Shebang行(#!/bin/bash),它告诉系统应该用Bash来运行这个脚本。然后我写了一些注释。我所做的就是放置了一个函数的基本结构:函数名、括号,然后是花括号,花括号内部是你运行代码的地方。因此,在Bash中构建一个函数真的非常简单。

这是一个你能构建的最简单的函数之一,一个模仿函数。它所做的就是回显你传入的第一个参数和第二个参数。

那么它是如何工作的呢?如果我们在这里调用它,传入mimic one two,它就会在这个语句内部打印出onetwo

类似地,在Bash中构建函数的整个要点在于,当你往下看,想要第二次调用同一个函数时,注意我不需要重写代码。这就是可重用性,这是在Bash中编写函数的核心概念。再次调用时,它会回显99100

现在让我们运行这个脚本,看看它是如何工作的。运行bash functions.sh,我们可以看到它首先打印了firstsecond,第二次调用时,它做了同样的事情。

所以,如果你要构建一个备份脚本,你肯定不希望每次备份时都重写所有代码。你只想传入,比如说,一个要备份的路径。这就是构建逻辑的核心思想:你可以重用代码。

处理函数的输出:捕获与传递

现在,让我们看一个更复杂的函数,并了解Bash的一些独特特性,因为Bash没有return的概念。

获取结果的唯一方法要么是捕获echo的输出,要么是做一些更复杂的操作。

在这个例子中,我将定义一个名为add的函数。在函数内部,我将第一个参数赋值给一个变量,将第二个参数赋值给另一个变量。然后,我将这两个东西相加。这是一个非常简单的计算,将两个数字相加。接着,我在这里回显结果。

所以,基本上,在我完成计算后,获取这个值的唯一方法就是查看函数的标准输出。因此,如果我们这样做,它将回显单词three

这里需要注意的一点是,我不必显式地让结果输出到标准输出。实际上,我可以捕获它并将其赋值给另一个变量。

以下是一个很好的示例,说明我如何做到这一点:

output=$(add 4 9)

我运行那个add函数,传入49。将要发生的是,结果会被捕获到这个变量output中。然后我就可以用它做任何我想做的事情。比如说,我需要把它传递给另一个函数,这完全没问题。

事实上,这样做可能很有趣。我们为什么不这样做呢?我们为什么不把这个输出拿过来,再次调用add呢?不过,这次我将把output变量本身作为两个参数传入。

那么会发生什么呢?这个output值(即13)会再次进入函数。然后这个函数应该返回13 + 13,也就是26

让我们运行这个脚本。运行bash functions.sh,我们可以看到,事实上我成功地重用了那个值。它没有打印出13,因为我没有回显它。所以,这不会回显13,因为我捕获了它。相反,我所做的是,我把那个值再次发送给了add函数。

所以,这就是Bash函数的核心概念:可重用性。你甚至可以捕获输出,如果你对echo做一些巧妙处理的话,然后你可以再次将它发送给同一个函数或另一个函数。

总结

本节课中我们一起学习了Bash函数。我们了解到Bash函数就像可重复使用的机器,它接收输入并产生输出。我们学习了如何定义函数的基本结构,如何向函数传递参数,以及如何通过echo和命令替换$()来捕获和处理函数的输出。关键在于,函数的核心价值在于代码的可重用性,这能帮助我们编写更简洁、更易维护的脚本。

048:构建Bash命令行工具 🛠️

在本节课中,我们将学习如何构建一个Bash命令行工具。我们将了解命令行工具的基本结构,并学习一个通用的构建公式,该公式包含定义功能、解析输入以及将两者整合三个核心步骤。

什么是Bash命令行工具?

一个Bash命令行工具通常包含几个部分。首先是脚本的名称。其次是传递给脚本的选项,用于收集值。例如,我们可能会看到 -count 这样的选项及其关联的值,或者 -phrase 这样的选项。因此,我的代码需要能够解析这些选项。

构建命令行工具的通用公式

我总结了一个公式,你可以将其应用于几乎任何Bash命令行工具的构建。这个公式分为三个清晰的步骤。

以下是构建Bash命令行工具的三个核心步骤:

  1. 构建代码功能:首先,你需要构建代码的核心功能。这可能是一个加法函数、计数函数,或者你正在构建的任何功能,例如一个备份脚本。你需要将其构建成一个Bash函数。
  2. 解析输入:其次,你需要编写能够解析输入的代码。具体来说,你需要能够解析从命令行传入的参数和选项。
  3. 整合与执行:一旦你完成了前两步,就准备就绪了。你只需要将所有部分整合在一起,并将收集到的输入传递给第一步中定义的函数。

这个公式是构建命令行工具的一个绝佳方法。接下来,让我们通过一个实例来观察它是如何运作的。

实例解析:一个短语生成器工具

让我们仔细看看我之前展示的那个命令行工具,并对照我们的公式进行分析。首先,我们再次看到脚本的名称,这里是 ci.sh。我希望它能解析一些选项,并且能够运行某些逻辑代码。

第一步:定义功能(A部分)

这里是A部分,其功能是多次生成短语。让我们逐步分解这一步。generate_phrases 是函数的名称。实际的工作是一个 for 循环,我根据用户传入的值计数循环次数,然后将其回显输出。

generate_phrases() {
    for ((i=0; i<$1; i++)); do
        echo "$2"
    done
}

第二步:解析输入(B部分)

现在来到B部分,这里将设置选项解析。这部分可以相当公式化。这里使用了一个 case 语句,允许我在不同的值之间进行切换。它被包含在一个 while 循环中,基本逻辑是:只要还有一个或多个参数,就进入循环,捕获 -count-phrase 选项的值,完成后结束整个循环。

while [[ $# -gt 0 ]]; do
    case $1 in
        -count)
            COUNT="$2"
            shift 2
            ;;
        -phrase)
            PHRASE="$2"
            shift 2
            ;;
        *)
            echo "未知选项: $1"
            exit 1
            ;;
    esac
done

第三步:整合与执行(C部分)

最后,既然我已经获取了所有输入,我只需要将它们传递给我之前定义的函数。在这个例子中,我只是打印出计数和短语。

generate_phrases "$COUNT" "$PHRASE"

工具运行演示

那么这个工具如何工作呢?我们可以直接从命令行运行它。我们输入 ci(脚本名称),然后有一个选项 -count 5,以及另一个选项 -phrase Hello

我们可以改变这些值,例如将计数改为2,并使用不同的短语,比如 I love coding。运行后,工具应该能给我们输出两次“I love coding”。

这确实是一个我强烈推荐的优秀公式。当你构建命令行工具时,请思考A、B、C三个部分:A部分是核心功能,B部分是输入解析,C部分则是将所有部分整合在一起并运行你的代码。

总结

本节课中,我们一起学习了构建Bash命令行工具的完整流程。我们首先了解了命令行工具的基本构成,然后掌握了一个包含三个步骤的通用构建公式:定义功能函数编写输入解析逻辑以及最终整合执行。通过一个生成短语的实例,我们逐步实践了这个公式。记住这个结构,你就能更有条理地创建出功能清晰、易于使用的Bash脚本工具。

049:Makefile与Dockerfile 🔧🐳

在本节课中,我们将要学习两个在数据工程和软件开发中至关重要的自动化工具:Makefile 和 Dockerfile。它们都深受 Bash 脚本的影响,是构建、测试和部署工作流的核心。理解它们将帮助你更高效地封装和交付你的数据工程解决方案。

Makefile:自动化构建的配方 📜

上一节我们介绍了Bash脚本的基础,本节中我们来看看如何利用Makefile将一系列命令组织成可复用的自动化任务。Makefile的核心思想非常简单:它由一系列“目标”和对应的“配方”组成。

基本公式如下:

目标: 依赖项
    [Tab]配方命令1
    [Tab]配方命令2

以下是Makefile的一个简单示例:

all: hello goodbye

hello:
    echo “Hello, World!”

goodbye:
    echo “Goodbye for now!”

在这个例子中,hellogoodbye 是两个目标(或称为“配方”)。每个目标下面的行(必须以Tab键开头)定义了要执行的Bash命令。all 是一个特殊目标,它依赖于 hellogoodbye,运行 make all 或直接 make 会依次执行这两个目标。

在实际操作中,你只需在包含 Makefile 的目录下运行 make 命令。系统会列出所有可用的目标。运行 make hello 会执行 echo “Hello, World!” 命令。Makefile的威力在于,你可以将复杂的构建、测试、清理和部署步骤封装成简单的命令,例如 make testmake deploy。对于数据科学家和工程师来说,使用Makefile管理项目流程是提升效率的关键一步。

Dockerfile:容器化应用的蓝图 🐳

理解了基于配方的自动化后,我们来看看如何将整个应用环境打包。Dockerfile 是用于构建 Docker 镜像的文本文件,它定义了一个可重复、可移植的容器环境。它的许多指令本质上就是在运行Bash命令。

Dockerfile 的基本结构是一系列指令,每条指令都会在镜像中创建一层。一个典型的Dockerfile可能如下所示:

# 使用官方Python镜像作为基础
FROM python:3.10-slim

# 设置工作目录
WORKDIR /app

# 将依赖文件复制到容器中
COPY requirements.txt .

# 安装Python依赖(这里运行的是Bash命令)
RUN pip install --upgrade pip && \
    pip install -r requirements.txt

# 复制应用代码
COPY . .

# 定义容器启动时执行的命令
CMD [“python”, “app.py”]

在这个Dockerfile中,关键指令是 RUNRUN pip install … 就是在容器内执行Bash命令来安装软件包。&& 符号是Bash中的逻辑与操作符,确保前一个命令成功后才执行下一个。FROM 指令指定了基础镜像(如某个Linux发行版),这意味着你继承了该镜像的所有功能和Bash环境。

要使用这个Dockerfile,你需要在同一目录下运行构建命令:

docker build -t my-python-app .

此命令会读取Dockerfile,逐步执行指令,最终生成一个名为 my-python-app 的Docker镜像。之后,你可以使用 docker run 命令来启动一个基于该镜像的容器,并进入其Bash终端进行交互或运行应用。

总结 📝

本节课中我们一起学习了Makefile和Dockerfile这两个强大的工具。

  • Makefile 是一个自动化任务运行器,它使用目标-配方的模式来组织Bash命令,极大地简化了复杂的项目工作流,如编译、测试和部署。
  • Dockerfile 是一个容器镜像构建蓝图,它通过一系列指令(特别是 RUN 指令执行Bash命令)来定义一个完整、一致的应用运行环境,实现了应用及其依赖的完美封装与移植。

它们的共同点在于都深深植根于Bash和Unix哲学。一旦你掌握了Bash,理解和编写Makefile与Dockerfile就会变得容易得多。熟练运用这两个工具,将能帮助你高效地构建、打包和交付可靠的数据工程解决方案。

050:Bash数据结构 📚

在本节课中,我们将学习Bash脚本中两种用于存储信息的数据结构:数组和哈希。理解这两种结构是编写高效、强大脚本的基础。

概述

在编程中,数据结构是组织和存储数据的方式。Bash提供了两种核心数据结构:数组和哈希。数组在其他语言(如Python)中常被称为列表,而哈希则类似于字典。掌握它们能帮助你更有效地处理数据集合。

数组:简单的数据序列 🧺

数组是一种序列结构,你可以将一系列项目放入其中。当需要处理这些项目时,可以编写一个循环,逐个取出并执行操作。这种方法非常适用于需要批量处理多个项目的场景,例如备份或处理一系列文件。

以下是数组的基本概念和用法。

数组示例与循环

让我们看一个数组的简单例子。以下代码声明了一个包含几种水果的数组,并使用for循环遍历它们。

#!/bin/bash
# 声明一个数组
fruits=("apple" "pear" "cherry")

# 使用for循环遍历数组
for fruit in "${fruits[@]}"
do
  echo "This $fruit is delicious."
done

执行这段脚本,输出结果如下:

This apple is delicious.
This pear is delicious.
This cherry is delicious.

这个过程就像从购物篮中一次取出一个物品。数组结构简单直观,是处理有序项目集的理想选择。

哈希:强大的键值对结构 🛒

哈希(也称为关联数组或字典)比数组更复杂,但也更强大。在哈希中,你需要通过一个唯一的“键”来访问对应的“值”。这就像在超市使用购物车:虽然比篮子复杂,但容量更大,功能更强,使用起来也更方便。

Bash从4.0版本开始支持哈希。大多数云系统都具备这个功能。哈希在需要建立映射关系或确保唯一性的场景中非常有用。

哈希示例与循环

以下是如何声明和使用哈希的示例。我们创建一个表示三餐的哈希,然后遍历它的所有键值对。

#!/bin/bash
# 声明一个关联数组(哈希)。注意‘-A’选项。
declare -A meal_hash

# 为哈希赋值
meal_hash["dinner"]="steak"
meal_hash["lunch"]="salad"
meal_hash["breakfast"]="fruit"

# 遍历哈希的所有键
for meal in "${!meal_hash[@]}"
do
  # 通过键获取对应的值
  food="${meal_hash[$meal]}"
  echo "For $meal, I like to eat $food."
done

执行这段脚本,输出结果可能如下(顺序可能不同):

For breakfast, I like to eat fruit.
For dinner, I like to eat steak.
For lunch, I like to eat salad.

在这个循环中,每次迭代都会处理一个键值对。这种结构功能强大,例如,你可以用它来存储网络中的所有主机名并对其执行操作,或者检查某个键是否存在以决定后续逻辑。

总结

本节课我们一起学习了Bash中的两种核心数据结构。

  • 数组:一种简单的序列结构,适用于存储和顺序处理项目列表。其语法简单,是基础任务的好帮手。
  • 哈希:一种基于键值对的关联数组,功能更强大。它允许你通过唯一的键来高效地存储和检索数据,适用于需要映射关系或确保数据唯一性的复杂场景。

理解数组的简易性与哈希的强大功能之间的权衡,将帮助你在不同的脚本编写任务中选择最合适的数据结构。

051:Linux文件与数据管理方案构建入门 🐧

在本节课中,我们将学习如何使用Linux系统来构建文件和数据管理方案。我们将探索文件系统的搜索、文件和目录的修改、权限控制,以及如何在Bash中处理文本,并最终将这些知识应用于创建一个Bash搜索解决方案。


文件系统搜索 🔍

上一节我们介绍了课程的整体目标,本节中我们来看看如何在Linux文件系统中进行搜索。这是管理大量数据的基础技能。

Linux提供了强大的命令行工具来查找文件和目录。最常用的工具是 find 命令。

以下是 find 命令的基本用法示例:

find /path/to/search -name "filename.txt"
  • -name:按名称搜索。
  • -type:按类型搜索(如 f 代表文件,d 代表目录)。
  • -size:按文件大小搜索。

另一个有用的工具是 grep,它用于在文件内容中搜索文本模式。

grep "search_pattern" file.txt


修改文件与目录 📁

学会了如何找到文件后,接下来我们需要掌握如何修改它们。这包括创建、删除、移动和复制文件及目录。

以下是管理文件和目录的核心命令:

  • 创建目录mkdir directory_name
  • 创建文件touch file_name.txt
  • 复制文件或目录cp source destination
  • 移动/重命名文件或目录mv source destination
  • 删除文件rm file_name.txt
  • 删除空目录rmdir directory_name
  • 递归删除目录及其内容rm -r directory_name

注意:使用 rm -r 命令时要格外小心,因为删除的文件通常无法恢复。


控制权限 🔐

在多人使用或管理敏感数据的系统中,控制谁可以访问或修改文件至关重要。Linux使用一套权限系统来管理这一点。

每个文件和目录都有三组权限:所有者所属组其他用户。每组权限又包括执行

权限可以用符号表示(如 rwxr-xr--),也可以用数字表示(如 754)。

修改权限的命令是 chmod

以下是权限管理的示例:

# 符号表示法:给所有者添加执行权限
chmod u+x script.sh

# 数字表示法:设置权限为 rwxr-xr-- (754)
chmod 754 file.txt
  • u 代表所有者,g 代表组,o 代表其他用户。
  • + 添加权限,- 移除权限,= 设置精确权限。
  • 数字表示法中:4=读,2=写,1=执行。三组数字分别对应所有者、组和其他用户。

Bash中的文本处理 📝

Linux的强大之处在于能够通过管道(|)将多个简单的命令组合起来,完成复杂的文本处理任务。这是构建数据管理方案的核心。

以下是几个关键的文本处理命令:

  • cat:显示整个文件内容。
  • head / tail:显示文件的开头或结尾部分。
  • sort:对文本行进行排序。
  • uniq:报告或省略重复的行。
  • cut:从每行中删除部分内容。
  • awk:一种强大的文本分析工具。
  • sed:流编辑器,用于过滤和转换文本。

例如,要统计一个日志文件中出现频率最高的错误代码,可以组合多个命令:

cat error.log | grep "Error Code" | cut -d' ' -f3 | sort | uniq -c | sort -nr

构建Bash搜索方案 🛠️

现在,让我们综合运用以上所有知识,创建一个实用的Bash脚本,用于在项目目录中搜索特定的代码模式,并列出包含该模式的文件。

假设我们想在所有 .py 文件中搜索函数定义 def calculate

#!/bin/bash

# 这是一个简单的搜索脚本
SEARCH_TERM="def calculate"
SEARCH_DIR="/path/to/project"

echo "正在搜索 '${SEARCH_TERM}' 在 ${SEARCH_DIR} 中..."
echo "----------------------------------------"

find "$SEARCH_DIR" -name "*.py" -type f -exec grep -l "$SEARCH_TERM" {} \;

脚本解释

  1. #!/bin/bash 指定脚本解释器。
  2. find 命令在指定目录中查找所有 .py 文件。
  3. -exec 参数对找到的每个文件执行 grep -l 命令。
  4. grep -l 只打印包含搜索模式的文件名,而不是匹配的行。

你可以将此脚本保存为 search_code.sh,并通过 chmod +x search_code.sh 赋予其执行权限,然后运行 ./search_code.sh 来使用它。


总结 📚

本节课中我们一起学习了Linux文件与数据管理的基础。我们从文件系统搜索开始,掌握了 findgrep 的用法。接着,我们学习了如何修改文件和目录,以及如何通过 chmod 命令控制访问权限。然后,我们探索了Bash中的文本处理工具链,这是实现自动化的关键。最后,我们综合这些技能,构建了一个简单的Bash搜索方案。这些是数据工程师在日常工作中管理和处理数据文件的基础能力。

052:Linux文件系统搜索简介 🔍

在本节课中,我们将学习如何在Linux文件系统中进行高效搜索。搜索是系统管理和数据工程中的一项核心技能,它能帮助我们快速定位文件、管理数据以及执行自动化任务。

概述

搜索文件系统有很多实际原因,例如查找重复文件、归档特定数据或定位配置文件。掌握有效的搜索工具可以极大提升工作效率。本节将介绍两个强大的命令行工具:locatefind,并探讨如何使用正则表达式进行高级搜索。最后,我们将了解xargs命令,它能增强命令行参数的处理能力。

为何需要搜索文件系统?

搜索文件系统的原因多种多样。例如,你可能需要查找重复文件以释放磁盘空间,或者需要归档特定日期创建的所有日志文件。有效的搜索能帮助你快速定位所需文件,从而进行后续的管理或分析操作。

使用 locatefind 命令

locatefind是Linux中用于定位和搜索文件的两个主要命令。它们各有特点,适用于不同的场景。

locate 命令

locate命令基于一个预先构建的数据库来快速查找文件,因此速度非常快。但它可能无法找到刚刚创建的文件,因为数据库需要定期更新。

基本语法

locate [选项] 文件名

find 命令

locate不同,find命令会实时遍历文件系统来查找文件,因此功能更强大、更灵活,但速度可能稍慢。它支持根据名称、类型、大小、修改时间等多种条件进行搜索。

基本语法

find [路径] [选项] [操作]

使用正则表达式进行搜索

正则表达式是一种强大的模式匹配工具,可以与find等命令结合使用,进行更复杂、更精确的搜索。

例如,以下命令使用find和正则表达式在当前目录下查找所有以.txt.log结尾的文件:

find . -regex ".*\.\(txt\|log\)$"

利用 xargs 增强命令行

xargs命令用于从标准输入构建并执行命令行。它通常与管道(|)结合使用,将上一个命令的输出作为参数传递给下一个命令,从而处理大量文件或参数。

例如,以下命令查找所有.tmp文件并将其删除:

find /path/to/search -name "*.tmp" | xargs rm

总结

本节课我们一起学习了Linux文件系统搜索的基础知识。我们了解了搜索文件系统的常见原因,掌握了使用locatefind命令进行文件定位的方法,并探讨了如何结合正则表达式进行高级搜索。最后,我们介绍了xargs命令,它能有效地处理命令行参数,将搜索与其他操作串联起来。掌握这些工具将帮助你更高效地管理和处理文件系统中的数据。

053:Linux文件系统搜索方法 🔍

在本节课中,我们将要学习在Linux文件系统中搜索文件和数据的三种主要方法。理解这些方法及其适用场景,对于高效地管理和定位数据至关重要。

概述

为什么需要搜索文件系统?原因很简单:为了找到东西。当文件数量很少时,例如在桌面上,查找很容易。但当文件数量达到数百甚至更多时,定位特定文件就变得困难。在数据工程中,面对庞大的文件系统,掌握有效的搜索方法是一项核心技能。

三种文件系统搜索方法

接下来,我们将逐一探讨这三种搜索模式。每种方法都有其独特的优势和适用场景。

1. 可视化搜索 👁️

上一节我们介绍了搜索的基本需求,本节中我们来看看第一种方法:可视化搜索。

这种方法不仅限于桌面环境。文件系统通常采用分层结构(即树状结构),数据被逻辑地组织到树的特定分支中。许多组织会创建这种逻辑结构,例如将图片放在一个目录,按日期分类的文件放在另一个目录。你可以通过视觉浏览这个结构来查找文件。

这通常是你在文件系统中寻找东西时首先会使用的方法。

2. 实时搜索(线性搜索) ⏳

当数据量增长时,显然搜索文件系统所需的时间会越来越长。这是一个 O(n) 复杂度的问题:可用数据越多,搜索耗时越长。

以下是防止搜索时间过长的几种策略:

  • 限制搜索范围:可以只搜索数据的一个子集,而无需遍历整个大数据集。
  • 按文件类型筛选:同时结合数据子集进行搜索。

find 命令在此类搜索中极其有用。所有数据工程师和数据科学家都应该理解如何使用它。最重要的是要明白,这种搜索是实时的,并且需要时间来完成。

3. 元数据库搜索 🗃️

最后要提到的是元数据库搜索的概念。这是笔者个人最喜欢的方法,因为甚至无需触及实际文件系统,只需查询一个包含了文件系统上所有项目信息的文件即可。

对于 locate 命令,你需要运行一个名为 updatedb 的命令来更新这个数据库。通常,人们会通过定时任务(如每晚)自动运行此命令来扫描文件系统并查找更新。但有时此功能不可用,需要手动更新。

在更复杂的文件系统如 macOS 中,有一个名为 Spotlight 的功能,它使用类似的实用程序 mdfind。这个命令能够搜索整个文件系统并基于查询返回结果。

总结

本节课中我们一起学习了查看和搜索文件系统的三种不同方法。了解并掌握在何种问题场景下使用何种方法,对于高效的数据管理工作至关重要。

054:使用 locate 命令查找文件 🔍

在本节课中,我们将学习如何在 Linux 系统中使用 locate 命令来快速查找文件。locate 命令通过查询一个预建的数据库来工作,因此速度非常快,但需要了解其更新机制。

安装与更新数据库

首先,我们需要确保系统上安装了 locate 命令。安装方法因 Linux 发行版而异。在基于 Debian 的系统(如 Ubuntu)上,可以使用以下命令安装:

sudo apt install locate

安装完成后,locate 命令需要一个数据库来索引文件系统。这个数据库不会自动实时更新。为了获得最新的搜索结果,我们需要手动更新它:

sudo updatedb

这个命令会扫描文件系统,并将最新的文件信息更新到 locate 使用的数据库中。

基本文件查找

上一节我们介绍了如何安装和更新数据库,本节中我们来看看如何使用 locate 进行基本搜索。

使用 locate 命令非常简单,只需在命令后跟上要查找的文件名或模式即可。例如,查找所有名为 .zshrc 的配置文件:

locate .zshrc

执行后,命令会返回系统中所有匹配该名称的文件路径。

统计查找结果数量

有时我们不仅想找到文件,还想知道有多少个匹配项。以下是结合 wc 命令统计结果数量的方法:

我们可以通过管道将 locate 的输出传递给 wc -l 命令来统计行数,从而知道找到了多少个匹配的文件。

locate .zshrc | wc -l

这个命令会先执行 locate .zshrc 查找所有 .zshrc 文件,然后通过管道 | 将结果传递给 wc -l 命令进行行数统计,最终输出一个数字。

进行不区分大小写的搜索

默认情况下,locate 命令是区分大小写的。这意味着搜索 .zshrc.ZSHRC 会得到不同的结果。

如果希望进行不区分大小写的搜索,可以使用 -i 选项。

locate -i .ZSHRC

使用 -i 选项后,locate 将忽略大小写差异,返回所有匹配 zshrc 的文件,无论其字母大小写如何。

理解 locate 命令的局限性

locate 命令非常高效,但它有一个重要的限制:它只能找到在最后一次更新数据库时已存在的文件。

例如,如果你在更新数据库后创建了一个新文件,locate 将无法立即找到它。你需要再次运行 sudo updatedb 来更新数据库,新文件的信息才会被纳入索引。

因此,在查找最新创建的文件时,可能需要先更新数据库。

总结

本节课中我们一起学习了 locate 命令的核心用法。我们首先介绍了如何安装和初始化其依赖的数据库。接着,我们探索了基本文件查找、统计结果数量以及进行不区分大小写搜索的方法。最后,我们了解了 locate 命令基于数据库工作的原理及其局限性:它无法实时反映文件系统的变化,需要定期手动更新数据库才能包含最新文件。尽管有此限制,locate 在已知文件系统中快速定位文件方面依然是一个极其强大和常用的工具。

055:使用 find 命令查找文件 🔍

在本节课中,我们将要学习 Linux 系统中一个强大的实时文件搜索工具——find 命令。我们将了解它的基本用法、优势、局限性,以及如何根据场景选择它或 locate 命令。


认识 find 命令

find 命令允许你实时搜索文件系统。因此,对于需要在文件系统中查找特定模式或文件的用户来说,它成为了最实用的工具之一。

要开始使用,你可以输入 find,后跟要搜索的位置。例如,如果我输入 find .,表示我想查看当前工作目录及其子目录。

按名称查找文件

接下来,我们可以指定查找条件。例如,使用 -name 选项按文件名查找。假设我想查找所有名为 .zshrc 的文件,命令如下:

find . -name .zshrc

执行后,它会找到当前目录及子目录中所有名为 .zshrc 的文件。我们可以看到它确实找到了一个。

搜索整个文件系统

然而,我的文件系统上可能不止一个 .zshrc 文件。如何找到它们呢?需要注意的是,要搜索整个文件系统,我可能需要使用 sudo 命令,因为它能赋予我更高的权限,访问用户可能无权访问的所有位置。

为此,我会输入:

sudo find / -name .zshrc

这里,/ 代表文件系统的根目录。执行后,我们找到了第二个 .zshrc 文件。这正是 find 命令的强大之处——它能实时查找文件。

find 命令的性能考量

不过,这种强大并非没有代价。find 命令存在一些问题,其中之一是性能。为了直观感受,我们可以使用 time 命令来测量其执行时间。

运行以下命令:

time sudo find / -name .zshrc

你会发现,搜索整个文件系统大约需要一秒钟。这是一个相当耗时的命令。

与 locate 命令对比

现在,让我们将其与 locate 命令进行对比。locate 命令通过查询预构建的数据库来查找文件,速度通常快得多。

运行以下命令:

time locate .zshrc

你会发现,这种方式的速度快了上百倍,大约只需 0.03 秒。因为它能够查询数据库来快速定位文件。

如何选择使用哪个命令

因此,你必须根据情况选择使用哪一个命令。

以下是选择建议:

  • 当你需要实时精确地搜索文件,或者需要基于文件属性(如修改时间、大小、权限)进行复杂查找时,使用 find 命令。
  • 当你只需要快速根据文件名进行查找,并且可以接受数据库可能不是最新(新创建的文件可能暂时查不到)时,使用 locate 命令。

find 命令是我推荐大家掌握的工具。除了需要注意其搜索速度,也要留意你指定的搜索范围。除此之外,它是一个非常出色的工具,应该成为你技能工具箱的一部分。


本节课中,我们一起学习了 find 命令的基本用法,了解了它实时搜索的强大功能,也认识了其相对较慢的缺点。通过将其与 locate 命令对比,我们明白了在不同场景下如何选择合适的文件搜索工具:追求实时性和灵活性时用 find,追求速度时用 locate

056:使用xargs扩展Linux搜索能力

在本节课中,我们将学习如何将find命令与xargs命令结合使用,以对搜索到的文件执行批量操作。我们将通过创建和删除一系列示例文件来演示这一强大功能。

上一节我们介绍了如何使用find命令在文件系统中定位文件。本节中我们来看看,找到文件后,如何高效地对它们执行操作。

创建示例文件

首先,我们需要一些用于演示的文件。我们将在temp目录下创建一系列文件。

以下是创建文件的命令:

for i in {0..10}; do touch temp/F$i; done

执行后,temp目录下会生成11个名为F0F10的文件。

使用find命令定位文件

现在,假设这些文件分散在庞大的文件系统中,手动管理它们(如移动、清理)会非常麻烦。这正是find命令发挥作用的地方。

让我们使用find命令来定位这些文件:

find temp -name "F*" -type f

此命令会搜索temp目录下所有名称以“F”开头的文件(-type f确保只列出文件,而非目录),并将它们打印出来。

为了增加复杂性,我们可以在temp目录下再创建一个名为Ffolder的目录。此时再次运行上述find命令,你会发现该目录不会被列出,因为我们使用了-type f过滤器。

结合xargs执行批量操作

找到文件后,我们可能希望对它们执行操作,例如删除。这时就需要xargs命令。xargs可以从标准输入接收参数,并将其传递给指定的命令执行。

以下是删除所有找到的文件的命令:

find temp -name "F*" -type f | xargs /bin/rm -f

命令解析:

  • find temp -name "F*" -type f:查找所有目标文件。
  • |:管道符,将find命令的输出作为下一个命令的输入。
  • xargs /bin/rm -fxargs接收文件列表,并为每个文件执行/bin/rm -f命令进行强制删除。

执行此命令后,再次运行find命令进行验证,你会发现所有F*文件都已被成功删除。xargs的强大之处在于,它不仅能用于删除,还可以与任何命令结合,例如查看元数据、打包归档等,从而基于搜索结果执行复杂的批处理任务。

总结

本节课中我们一起学习了如何利用xargs命令来扩展find命令的功能。核心流程是:首先使用find精确地定位文件,然后通过管道|将结果传递给xargs,最后由xargs驱动另一个命令(如rmcpmv等)对这批文件执行批量操作。这种方法极大地提升了在Linux文件系统中进行批量文件管理的效率和灵活性。

057:在 macOS 上使用 mdfind 命令 🔍

在本节课中,我们将学习 macOS 操作系统独有的一个强大工具——mdfind 命令。它基于 Spotlight 元数据搜索引擎,能够快速、高效地搜索文件和目录,并支持实时更新和复杂的查询条件。


了解 mdfind 命令

上一节我们介绍了通用的 find 命令,本节中我们来看看 macOS 系统特有的 mdfind。首先,我们可以通过 man 命令查看其详细手册。

man mdfind

通过手册,我们可以发现 mdfind 提供了许多专为 macOS 设计的功能。例如,-live 选项允许进行实时元数据搜索,搜索结果会随文件系统的变化而动态更新。-count 选项可以统计匹配项的数量。我们还可以使用 -onlyin 选项将搜索范围限制在特定目录内。最强大的是,它能执行基于文件元数据的复杂查询。


执行基础搜索

以下是 mdfind 的一个基础应用示例。假设我们想在当前工作目录及其子目录中,搜索所有名为 “readme” 的文件。

mdfind -name readme -onlyin .

执行后,命令会列出该目录下所有包含 “readme” 名称的项目文件。这个搜索非常迅速,因为它利用了系统预建的元数据索引。


体验实时搜索

mdfind 的一个独特优势是实时性。如果我们在执行搜索后,立刻新建一个符合条件的文件,传统的 locate 命令可能无法立即发现它,因为其数据库并非实时更新。

然而,mdfind 配合 -live 选项,可以持续监控并输出新的匹配结果。我们可以打开一个终端标签页,运行以下命令进行监听:

mdfind -live -name readme -onlyin .

此时,命令会进入等待状态。在另一个标签页中,进入 source 目录并创建一个新的 readme.txt 文件:

cd ~/source
mkdir foo
touch foo/readme.txt

一旦文件创建完成,实时搜索的窗口会立即更新,显示新匹配到的文件路径。这证明了 mdfind 在获取最新文件信息方面的强大能力。


使用日期范围筛选

除了名称,mdfind 还能根据文件的创建或修改日期进行筛选。例如,我们可以搜索过去十年内修改过的 readme.txt 文件:

mdfind -name readme.txt -onlyin . -mtime -3650

如果我们想搜索未来某个日期(例如 2022 年之后)的文件,可以调整日期参数。这样,刚刚创建的新文件也会被包含在结果中,因为它符合“晚于某个过去时间点”的条件。日期筛选功能使得清理旧文件或查找特定时间段内的项目变得非常方便。


按文件大小搜索

一个非常实用但常被忽视的功能是按文件大小搜索。这对于磁盘空间管理至关重要。例如,我们可以在整个文件系统中搜索大于 10GB 的文件:

mdfind 'kMDItemFSSize >= 1e10'

这个查询几乎是瞬间完成的,它会列出系统中所有超过 10GB 的文件,例如一些庞大的 Xcode 模拟器或缓存文件。


结合 xargs 获取详细信息

上面的命令只告诉我们文件“大于某个尺寸”。如果我们想精确知道每个文件的具体大小,可以将 mdfind 的结果通过管道传递给 xargsdu 命令。

mdfind 'kMDItemFSSize >= 1e10' | xargs -I {} du -sh {}

xargs 会将 mdfind 找到的每一个文件路径,作为参数传递给 du -sh 命令。执行后,我们会得到一个人类可读的文件大小列表,例如 “25G”、“12G” 等,从而能更准确地判断哪些文件占用了大量空间。

我们还可以进一步过滤,例如只查找包含 “Xcode” 关键字的大文件:

mdfind 'kMDItemFSSize >= 1e10 && kMDItemFSName == "*Xcode*"c' | xargs -I {} du -sh {}

这样,输出结果会更加精确和整洁,直接显示出所有名称中包含 “Xcode” 且体积庞大的文件。


本节课中我们一起学习了 macOS 上 mdfind 命令的核心用法。我们了解了它基于元数据快速搜索的原理,实践了基础文件名搜索、体验了其独特的实时搜索能力,并学习了如何使用日期和文件大小进行高级筛选,最后还结合 xargs 命令来获取更详细的信息。mdfind 是一个功能强大、速度极快的工具,特别适合在 macOS 系统上进行高效的文件管理和检索。

058:Linux文件、目录、权限修改与归档入门 🐧

在本节课中,我们将学习在Linux终端中操作文件系统的核心技能。具体包括修改文件与目录、调整权限以及打包归档数据。这些是高效使用命令行进行数据管理的基础。

上一节我们介绍了Linux的基本命令,本节中我们来看看如何对文件系统进行更深入的操作。

修改文件系统

修改文件系统涉及创建、删除、重命名文件和目录。以下是几个关键命令:

  • touch:用于创建新的空文件或更新现有文件的时间戳。
    touch new_file.txt
    
  • mkdir:用于创建新的目录。
    mkdir new_directory
    
  • rm:用于删除文件或目录。使用 -r 选项可以递归删除目录及其内容。
    rm file_to_delete.txt
    rm -r directory_to_delete
    
  • mv:用于移动或重命名文件和目录。
    mv old_name.txt new_name.txt  # 重命名
    mv file.txt /path/to/destination/  # 移动文件
    

移动文件与目录

移动文件和目录是日常操作。mv 命令是完成这项任务的主要工具。它的基本语法是 mv [源] [目标]。如果目标是目录,则源文件或目录会被移动到该目录下;如果目标是文件名,则执行重命名操作。

归档数据

归档是将多个文件或目录打包成单个文件的过程,常用于备份或传输。以下是核心的归档与压缩命令:

  • tar:是Linux中最常用的归档工具。
    • -c:创建新的归档文件。
    • -x:从归档文件中提取文件。
    • -v:显示处理过程的详细信息。
    • -f:指定归档文件名。
    • -z:通过gzip压缩或解压归档文件。
    tar -czvf archive.tar.gz /path/to/folder  # 创建压缩归档
    tar -xzvf archive.tar.gz  # 解压归档文件
    

本节课中我们一起学习了Linux文件系统操作的核心命令。我们掌握了如何使用 touchmkdirrmmv 来管理文件和目录,并了解了如何使用 tar 命令进行数据的打包与压缩。这些技能是进行有效数据工程工作的基石。

059:Linux文件系统操作概览 🗂️

在本节课中,我们将要学习数据工程中一个至关重要的基础技能:在Linux文件系统上进行数据操作。随着集中式文件系统在云平台和数据工程工作流中的回归,掌握如何高效地创建、读取、更新和删除文件与目录,变得尤为重要。

数据工程中的文件系统角色

我们正处在一个数据工程的新时代,集中式文件系统正在回归。许多云平台和数据工程工作流都涉及基于磁盘的层次化结构数据处理。这与我在电影行业的工作类似,我们会编写数据迁移脚本,查看数据树状结构并移动数据,因为我们能假定磁盘上存在某种约定。

虽然并非总能假定磁盘上存在某种结构,但可以确定的是,你总需要进行某种类型的数据操作,例如归档数据、将同类型文件集中处理,然后进行大数据处理。因此,特别是在集中式文件系统环境下,掌握操作文件系统上数据的能力,是数据工程师的一项重要技能。

接下来,我将介绍一些在磁盘上进行处理的重要技术。

从CRUD到文件系统操作

让我们看一个在软件工程中常见的缩写:CRUD。在开发Web或移动应用时,你常会听到这个缩写,它代表Create(创建)、Read(读取)、Update(更新)、Delete(删除)。当操作数据库系统时,你会创建、读取、更新和删除记录,这非常普遍。这里存在一个操作事物的反馈循环,你需要构建与数据库交互的服务。

在文件系统中,存在类似类型的操作,但它们有不同的名称和细微差别。

以下是文件系统中与CRUD对应的常见操作:

  • 创建 (Create):你可以使用 touch 命令创建一个空文件(如果文件已存在,则不会执行任何操作)。你可以使用 mkdir 命令创建目录。Linux文件系统的美妙之处在于,你可以创建多种不同的东西。
  • 读取 (Read):你可以运行 cat 命令将文件内容输出到标准输出。你也可以使用 less 命令来分页浏览文件内容。这些都是基于读取的操作。
  • 更新 (Update):你可以在文件系统上更新许多东西。例如,你可以使用 mv 命令将文件从一个位置移动到另一个位置,或重命名文件(移动和重命名实际上是同义词)。你可以使用 tarzip 命令来压缩、归档文件,将其打包以便上传到邮件系统等。你还可以使用 chmod 命令更改文件的权限。
  • 删除 (Delete):类似地,你可以使用 rm 命令删除文件,或使用 rmdir 命令删除目录。

可以看到,数据库和文件系统在操作类型上非常相似,但使用的具体命令不同。

总结

本节课中,我们一起学习了Linux文件系统操作在数据工程中的重要性。我们了解了集中式文件系统的回归背景,并将熟悉的数据库CRUD概念(创建、读取、更新、删除)映射到了文件系统的具体命令上,例如 touch/mkdircat/lessmv/tar/chmod 以及 rm/rmdir。掌握这些基础操作是高效管理和处理数据文件的第一步。

060:Linux文件与目录移动

在本节课中,我们将学习如何在Linux环境中操作文件系统。具体内容包括创建目录和文件、移动与复制文件及目录,以及使用强大的rsync命令同步两个目录。这些是数据工程师日常工作中管理文件和数据的核心技能。

创建目录与文件

首先,我们来学习如何创建目录。在Linux中,使用mkdir命令可以创建新目录。

例如,创建一个名为food的目录:

mkdir food

执行ls命令,可以看到food目录已被创建。

mkdir命令还有一个非常实用的-p选项。它可以自动创建路径中所有不存在的中间目录。

例如,创建嵌套目录结构bar/bam/bz

mkdir -p bar/bam/bz

使用ls -R命令递归查看,可以看到barbambz这一整个目录树都被成功创建了。这个功能在需要搭建复杂目录结构时非常方便。

接下来,我们学习如何创建文件。一个常用的方法是使用touch命令。

例如,在bar目录下创建一个文件:

touch bar/1.txt

进入该目录查看,文件1.txt已经存在。touch命令是幂等的,这意味着你可以多次运行同一命令,而不会产生额外影响,它只会确保文件存在。这对于搭建文件系统框架非常有用。

我们还可以在更深的子目录中创建文件:

touch bar/bam/2.txt
touch bar/bam/bz/3.txt

现在,使用ls -lR命令递归查看当前目录结构,可以看到完整的文件和目录布局。

移动与复制操作

上一节我们创建了目录和文件,本节中我们来看看如何移动和复制它们。

移动操作使用mv命令。它可以将文件或目录从一个位置物理移动到另一个位置。

例如,将整个bar目录移动到temp目录中:

mv bar temp/

执行后,原来的bar目录消失了。进入temp目录查看,会发现bar目录及其内部的全部文件都已被移动过来。

与移动不同,复制操作使用cp命令。它会在新位置创建副本,而原始文件保持不变。结合-r选项,可以递归复制整个目录。

例如,将bam目录复制到当前目录:

cp -r bar/bam .

现在,当前目录下会有一个bam目录的副本。进入其中查看,其子目录和文件都被完整复制了。cp -r命令在需要备份或复制数据时非常常用。

使用rsync同步目录

对于数据工程师而言,管理集中式文件系统时,rsync是一个极其重要和有用的命令。它擅长高效地同步两个目录,尤其适用于文件数量庞大的场景。

假设我们有两个目录:foodnewspot/food。使用ls命令查看它们的内容:

ls food
ls newspot/food

我们发现food目录下有1.txt3.txt,但缺少2.txt;而newspot/food目录下可能文件不全或版本不同。我们希望只同步有变动的文件。

以下是使用rsync同步food目录到newspot/food目录的命令:

rsync -av food/ newspot/food/
  • -a:归档模式,保持文件属性并递归同步。
  • -v:详细模式,显示同步过程。
  • 命令末尾的/很关键,它确保同步的是food目录内的内容,而不是food目录本身。

运行命令后,rsync会智能地只传输发生变化的文件(例如缺失的2.txt)。再次检查目标目录,确认文件已同步完成。

rsync是处理大量文件同步的理想工具。最好的学习方式就是多动手实践,体验它高效同步目录的强大功能。

总结

本节课中我们一起学习了Linux文件系统的基本操作。我们掌握了使用mkdir创建目录(包括-p选项创建目录树),使用touch创建文件。接着,我们学习了使用mv命令移动文件/目录,以及使用cp -r命令复制目录。最后,我们重点介绍了数据工程师的核心工具rsync命令,它能够高效、增量地同步两个目录,是管理大型数据集的必备技能。

061:Linux文件与目录权限设置 🔐

在本节课中,我们将要学习Linux系统中一个核心但常令人困惑的概念:文件与目录的权限设置。我们将详细解释权限的表示方法、如何解读以及如何修改它们,确保您能安全有效地管理您的文件系统。


权限模式解读

上一节我们介绍了Linux的基本命令,本节中我们来看看如何解读ls -l命令输出的权限信息。

执行ls -l命令后,您会看到类似-rwxr-xr--的字符串,这被称为文件的“模式”。它代表了三组不同的权限:

  • 用户权限:文件所有者的权限。
  • 组权限:文件所属用户组成员的权限。
  • 其他权限:系统中所有其他用户的权限。

紧随权限之后的两个名称,分别是该文件的所有者所属组


权限的数字表示法

为了更精确地设置权限,Linux使用数字来表示权限。每个权限对应一个二进制值:

  • 权限用数字 4 表示。
  • 权限用数字 2 表示。
  • 执行权限用数字 1 表示。

通过将这些数字相加,我们可以为每一组用户(用户、组、其他)定义一个权限值。例如,权限rwx(读、写、执行)的计算方式是 4 + 2 + 1 = 7

因此,一个常见的权限数字754可以解读为:

  • 用户(所有者):7 -> 可读、可写、可执行。
  • 组:5 -> 可读(4)、可执行(1),但不可写(因为5不包含2)。
  • 其他:4 -> 仅可读。

修改文件权限 (chmod)

理解了权限的数字表示法后,我们就可以使用chmod命令来修改文件权限了。

以下是修改权限的几种常用方法:

  • 使用数字模式:直接指定代表三组权限的三个数字。

    chmod 754 script.sh
    
  • 使用符号模式:通过+(添加)、-(移除)、=(设定)来操作特定权限。

    chmod +x script.sh  # 为所有用户添加执行权限
    chmod u=rw,go=r script.sh  # 设置用户可读写,组和其他仅可读
    

权限实践与root用户

让我们通过一个例子来实践。假设我们有一个文件script.sh,其初始权限为400(仅所有者可读)。

  1. 此时,即使是文件所有者也无法执行它。

    ./script.sh  # 会提示“权限被拒绝”
    
  2. 我们可以为所有者添加执行权限。

    chmod u+x script.sh
    ./script.sh  # 现在可以成功执行
    
  3. root用户的特殊权限root用户(超级管理员)几乎不受普通权限规则限制。即使一个文件权限设置为400且所有者是其他用户,root用户依然可以强制修改其权限。

    sudo chmod +x script.sh  # 以root权限为文件添加执行权限
    

    这展示了系统管理中权限层级的概念,root用户拥有最高控制权。


总结

本节课中我们一起学习了Linux文件权限的核心知识。我们了解了如何解读ls -l命令输出的权限字符串,掌握了用数字(如754)表示执行权限的方法,并学会了使用chmod命令来修改文件权限。最后,我们还看到了root用户超越普通权限规则的特殊能力。建议您在自己的环境中创建文件,反复练习这些chmod命令,这是掌握Linux系统安全管理的基石。

062:Linux数据归档 📦

在本节课中,我们将要学习如何在Linux命令行中进行数据归档。归档是将多个文件或目录打包成一个文件的过程,常用于备份或传输数据。我们将重点介绍两种最常用的归档工具:ziptar


归档工具简介

上一节我们介绍了数据归档的概念,本节中我们来看看两种具体的工具。在Linux系统中,ziptar是两种最常用的归档命令。它们都能将多个文件打包并压缩,但语法和特性略有不同。

以下是两种命令的基本操作对比:

  • zip命令:用于创建.zip格式的压缩文件。基本语法是 zip -R [目标文件.zip] [源文件或目录]。其中,-R参数表示递归地包含子目录中的所有内容。
  • tar命令:通常用于创建.tar.gz.tgz格式的压缩包。一个常见的用法是 tar -zcvf [目标文件.tar.gz] [源文件或目录]。这里的-z表示使用gzip压缩,-c表示创建归档,-v显示过程,-f指定文件名。

使用 zip 进行归档与解档

现在,让我们通过一个具体例子来实践zip命令。假设我们有一个名为food的目录需要备份。

首先,我们可以创建一个专门的archives目录来存放备份文件,这是一个良好的习惯。

mkdir archives

接下来,使用zip命令进行归档。命令格式为:先指定目标压缩包路径,再指定要压缩的源目录。

zip -R archives/food_backup.zip food/

执行后,food目录下的所有内容都被打包进了archives/food_backup.zip文件中。这种.zip格式文件便于通过邮件等方式分享,因为它是一个通用的压缩包。

如果需要解压这个归档文件,恢复原始内容,可以使用unzip命令。

unzip archives/food_backup.zip

解压后,原始的文件结构就会被还原到当前目录。为了进行下一个演示,我们可以清理掉刚解压出来的文件。

rm -rf food/

使用 tar 进行归档与解档

清理了环境后,我们来看看功能强大的tar命令。它的操作逻辑与zip类似。

要使用tar创建压缩归档,我们使用-zcvf参数组合。同样,我们先指定目标文件名,再指定源目录。

tar -zcvf archives/food_backup.tar.gz food/

命令执行后,会在archives目录下生成一个food_backup.tar.gz文件。要解压并提取这个归档文件的内容,我们使用-zxvf参数组合。

tar -zxvf archives/food_backup.tar.gz

执行解压命令后,food目录及其文件就被完整地提取到了当前路径下。


课程总结 🎯

本节课中我们一起学习了Linux命令行下的数据归档。我们介绍了两种核心工具:ziptarzip命令简单直观,适合创建通用的.zip压缩包;而tar命令参数丰富,是Linux系统中最经典的归档工具,常与gzip结合使用。掌握归档(zip -R, tar -zcvf)与解档(unzip, tar -zxvf)的基本操作,能帮助你有效地备份、打包和传输数据文件。

063:Linux文本处理入门

在本节课中,我们将要学习Linux命令行中处理文本的基础知识。文本处理是使用Bash和终端最核心的方面之一,它涉及操作和分析文本流。我们将从概述开始,然后学习几个强大的命令,最后探讨如何使用正则表达式查找文本模式。

什么是文本处理?

文本处理指的是使用命令行工具来操作、分析和转换文本数据。这些工具通常通过管道(|)连接,将一个命令的输出作为另一个命令的输入,从而构建强大的数据处理流程。

上一节我们介绍了文本处理的概念,本节中我们来看看几个关键的命令行工具。

常用文本处理命令

以下是几个在文本处理中极其常用且强大的命令:grepcutsortuniq

1. grep:搜索文本

grep命令用于在文件或输入流中搜索匹配指定模式的行。其基本语法是:

grep [选项] ‘模式’ 文件名

例如,grep ‘error’ log.txt会在log.txt文件中查找所有包含“error”的行。

2. cut:提取字段

cut命令用于从文件的每一行中截取特定的部分(如列)。它通常与分隔符一起使用。基本语法是:

cut -d‘分隔符’ -f字段编号 文件名

例如,cut -d‘,’ -f1,3 data.csv会使用逗号作为分隔符,提取data.csv文件中每行的第1和第3个字段。

3. sort:排序文本

sort命令用于对文本文件的行进行排序。默认按字典序排序。基本语法是:

sort [选项] 文件名

例如,sort names.txt会对names.txt文件中的行进行升序排序。

4. uniq:报告或忽略重复行

uniq命令用于过滤或报告相邻的重复行。它常与sort命令结合使用,因为uniq只检查相邻行。基本语法是:

uniq [选项] 文件名

例如,sort file.txt | uniq会先排序文件,然后去除所有重复行。

掌握了这些基础命令后,我们可以将它们组合起来解决更复杂的问题。接下来,我们将深入一个更强大的工具:正则表达式。

使用正则表达式(Regex)查找模式

正则表达式是一种用于描述字符串模式的强大语言。在Linux命令行中,grep等工具支持使用正则表达式进行更灵活和精确的文本搜索。

一个简单的正则表达式示例是:

^Hello.*world$

这个模式匹配以“Hello”开头、以“world”结尾的任何行。

你可以使用grep -E来启用扩展正则表达式。例如:

grep -E ‘^[0-9]{3}-[0-9]{2}-[0-9]{4}’ data.txt

这个命令会在data.txt中查找符合美国社会安全号码格式(如123-45-6789)的行。


本节课中我们一起学习了Linux文本处理的基础。我们从文本处理的概述开始,然后详细介绍了grepcutsortuniq这几个核心命令的用法,最后探讨了如何使用正则表达式进行模式匹配。掌握这些工具是构建高效命令行工作流和数据处理管道的关键第一步。

064:Linux文本处理优势 🚀

在本节课中,我们将探讨在Linux环境下进行文本处理的核心优势。我们将了解为何命令行工具是数据预处理和快速任务的理想选择,并介绍其背后的关键理念。


无处不在的Linux与文本

Linux系统及其文本处理能力无处不在。如果你需要进行数据预处理,或者时间紧迫,必须在30秒内完成某项任务,命令行文本处理是首选方案。接下来,我们将详细讨论其重要性以及可用的技术。

简而言之,选择Shell进行文本处理的核心原因是:Linux和文本无处不在


简单易用与快速启动

如果你在使用Linux,开始进行文本处理非常简单。这通常是第一步:选择最简单的方案来观察结果。由于运行Linux的服务器存在于每一个云环境中,你可以确信,可以使用这个Linux操作系统来完成一些小任务。

另一个关键优势是速度快。很多时候,Shell本身是构建解决方案的一种非常直观的方式。由于这些解决方案的体量小,并且通常用高性能语言编写,因此它们的性能表现非常出色,同时也非常直观。


直观的管道操作

如果我们深入探究,看看一个处理管道(pipeline)是什么样子,它可能类似于以下流程:一个命令的输出通过管道(|)传递给另一个命令进行某种处理,然后你继续处理并整理结果。

这种将操作通过管道连接在一起的思想,使得解决问题变得非常直观。一个基础的管道示例如下:

command1 | command2 | sort

因此,我认为最好的问题应该是:你为什么不使用Shell呢? 如果你遵循这种启发式方法,这将是开始理解如何进行高性能数据工程的最佳途径之一。


总结

本节课中,我们一起学习了Linux文本处理的几大核心优势:其普遍性确保了工具的可用性;其简单性降低了入门门槛;其高性能直观的管道模型使得快速构建和组合解决方案成为可能。掌握这些命令行工具,是迈向高效数据工程实践的重要一步。

065:Linux中grep、cut、sort与unique使用 🛠️

在本节课中,我们将学习如何使用Linux命令行中自带的几个强大文本处理工具:grepcutsortuniq。这些工具能帮助我们快速探索、分析和处理数据文件,而无需依赖复杂的图形界面或编程环境。

探索文件内容

在开始处理数据之前,我们首先需要了解文件的基本结构和内容。Linux提供了简单的命令来查看文件的头部和尾部。

使用 head 命令可以查看文件的开头几行。例如,要查看名为 Amazon_review 的文件的前几行,可以输入:

head Amazon_review

同样,使用 tail 命令可以查看文件的末尾几行:

tail Amazon_review

这两个命令能让我们对文件内容有一个快速的概览。

使用grep进行文本搜索与统计

grep 是一个强大的文本搜索工具。我们可以用它来查找文件中包含特定单词或模式的行。

如果我们想统计单词“bad”在文件中出现的次数,可以使用 -c 选项进行计数:

grep -c bad Amazon_review

命令会返回一个数字,表示“bad”出现的次数。例如,它可能返回 154

如果我们不仅想计数,还想查看所有匹配的行(其中“bad”会被高亮显示),可以省略 -c 选项:

grep bad Amazon_review

我们可以用同样的方法探索其他词汇,例如“plastic”:

grep -c plastic Amazon_review

分析数据分布

首先,我们可以使用 wc -l 命令来确认文件的总行数:

wc -l Amazon_review

假设文件有5000行。接下来,我们可以利用 grep 来分析评论的情感分布。文件中可能包含“negative”、“mixed”、“positive”等状态。

要统计负面评论的数量,可以执行:

grep -c negative Amazon_review

结果显示有1175条负面评论。

统计混合情感评论的数量:

grep -c mixed Amazon_review

结果显示有241条混合评论。

最后,统计正面评论的数量:

grep -c positive Amazon_review

通过简单的减法或直接计数,我们可以验证数据的一致性。这展示了命令行的强大之处——无需额外软件即可完成基础数据分析。

使用diff比较文件差异

当我们需要比较两个文件的内容差异时,可以使用 diff 命令。

假设我们有两个文件:fruit1.txtfruit2.txt
fruit1.txt 的内容是:

cherry
pear
strawberry
apple
apple

fruit2.txt 的内容是:

cherry
pear
strawberry
apple

要比较这两个文件,可以运行:

diff fruit1.txt fruit2.txt

或者使用通配符一次性比较所有匹配的文件:

diff fruit*.txt

输出会清晰地指出两个文件之间的差异,例如,一个文件比另一个多了一行“apple”。

使用uniq识别与统计唯一值

uniq 命令用于报告或忽略文件中的重复行。在处理可能包含重复项的数据(如客户ID列表)时非常有用。

fruit1.txt 为例,它包含两个“apple”。如果我们只想查看唯一的行,可以运行:

uniq fruit1.txt

输出将只显示 cherrypearstrawberryapple 各一次。

如果我们想统计每个唯一项出现的次数,可以加上 -c 选项:

uniq -c fruit1.txt

输出会显示每个水果及其出现的次数。简而言之,diff 用于找不同,而 uniq 用于找唯一和计数。

使用sort对数据进行排序

sort 命令可以对文本文件的行进行排序,类似于SQL中的 ORDER BY

fruit1.txt 进行默认的字母顺序排序:

sort fruit1.txt

要进行反向排序(降序),可以添加 -r 选项:

sort -r fruit1.txt

这在需要快速查看按特定字段(如客户名、日期)排列的数据时非常方便。

使用cut截取文件字段

cut 命令允许我们从文件的每一行中提取特定部分(字段),类似于在电子表格中操作列。

假设我们的数据文件 data.csv 是以逗号分隔的,并且我们想提取最后一列(情感状态)。由于 cut 通常从行首开始计数,一种技巧是先反转每一行的字段顺序。

以下是一个组合命令的示例,它提取逗号分隔文件的最后一列:

cat data.csv | rev | cut -d',' -f1 | rev

让我们分解这个命令:

  1. cat data.csv:输出文件内容。
  2. rev:反转每一行字符串的顺序(这样最后一列就变成了第一列)。
  3. cut -d‘,’ -f1:以逗号(,)为分隔符,提取第一个字段(即原最后一列)。
  4. rev:再次反转结果,使其恢复原始顺序。

我们可以将结果保存到新文件:

cat data.csv | rev | cut -d',' -f1 | rev > status.txt

现在,status.txt 文件就只包含了原始数据的情感状态列。这个结果可以进一步清理或导入到像Python Pandas这样的工具中进行高级处理。

总结

本节课我们一起学习了Linux命令行中几个核心的文本处理工具。我们使用 headtail 探索文件,用 grep 搜索和统计文本,用 diff 比较文件差异,用 uniq 找出唯一值并计数,用 sort 对数据进行排序,并用 cut 提取特定的数据列。掌握这些工具能让你在初次接触Linux环境下的数据时,高效地进行初步的探查和清洗,为后续更复杂的数据工程任务打下坚实基础。最好的学习方式就是多动手尝试。

066:Linux中的截断、awk与sed编辑技术

在本节课中,我们将学习Linux命令行中几个强大的流处理工具:shuftrsedawk。这些工具能帮助我们高效地处理和分析文本数据,是构建数据预处理管道的基础。

使用 shuf 命令进行随机采样

首先,我们来看看如何使用 shuf 命令。这是一个非常有用但常被低估的命令,数据科学家尤其需要了解它。shuf 命令可以从输入中随机选择行。

以下是其基本用法:

shuf -n 10 input_file.txt

这个命令会从 input_file.txt 中随机选取10行输出。如果你有一个巨大的文件(例如PB级别),而你的Python或R程序对内存敏感,使用 shuf 抽取一个小样本进行测试就非常方便。

要将随机采样的结果保存到新文件,可以使用重定向操作符 >

shuf -n 10 input_file.txt > sample_10_lines.txt

然后,你可以使用 wc -l 命令验证新文件的行数。与按顺序选取的 headtail 命令不同,shuf 提供了非确定性的随机采样。

使用 tr 命令进行字符转换

接下来,我们看看 tr(translate)命令,它用于转换或删除字符。

一个简单的例子是将小写字母转换为大写:

echo “hello world” | tr ‘a-z’ ‘A-Z’

执行结果是 HELLO WORLD。这在数据预处理中很有用,例如,在将数据输入数据库或进行机器学习之前,可能需要统一文本的大小写格式。

你可以将此功能与其他命令结合。例如,先随机采样,再转换大小写:

shuf -n 5 reviews.txt | tr ‘a-z’ ‘A-Z’

这构成了一个简单的数据转换管道。

使用 sed 命令进行搜索与替换

sed(stream editor)是一个功能强大的流编辑器,常用于文本替换。其最常见的用途是搜索并替换文本。

基本语法是:

sed ‘s/pattern/replacement/’

例如,将文本中的 “mixed” 替换为 “negative”:

echo “This is a mixed review.” | sed ‘s/mixed/negative/’

输出为 This is a negative review.

假设你正在处理产品评论,希望将所有情感为 “mixed” 的标记改为 “negative”,以便进行二元分类。你可以轻松地将 sedshuf 结合:

shuf -n 200 reviews.txt | sed ‘s/mixed/negative/’

为了验证替换是否成功,可以结合 grep 命令进行计数检查。sed 的这种简单替换功能,在构建数据清洗管道时非常实用。

使用 awk 命令进行模式扫描与处理

最后,我们介绍 awk,这是一种诞生于1970年代贝尔实验室的编程语言,专为文本处理设计。awk 的基本结构是“模式 {动作}”,即对匹配模式的行执行相应动作。

一个典型应用是根据字段数量过滤行。假设我们有一个用制表符分隔的评论文件,我们想找出字段数少于10的简短评论:

awk -F‘\t’ ‘NF < 10’ reviews.txt

其中,-F‘\t’ 指定字段分隔符为制表符,NF 代表当前行的字段数量。这个命令会打印出所有字段数小于10的行。

反之,如果你想找出非常冗长、字段数大于300的评论,可以这样做:

awk -F‘\t’ ‘NF > 300’ reviews.txt

awk 的功能远不止于此,但它基于条件和动作的处理逻辑,使其成为过滤和转换结构化文本数据的利器。

总结

本节课我们一起学习了Linux中四个核心的流处理工具:

  • shuf:用于从文件中进行随机采样
  • tr:用于字符转换或删除,如大小写转换。
  • sed:用于基础的文本搜索与替换
  • awk:一种功能强大的文本处理编程语言,常用于基于模式的过滤和报告生成。

这些工具的威力在于它们可以像管道一样组合使用。通过灵活结合 shuftrsedawk,你可以构建出强大而高效的数据预处理流程,轻松应对各种文本数据处理任务。掌握这些命令行工具,将极大提升你在数据工程领域的工作效率。

067:Linux正则表达式使用 🧠

在本节课中,我们将要学习Linux中一个强大的文本处理工具——正则表达式。我们将通过具体的例子,了解如何使用正则表达式来匹配、筛选和处理文本数据,例如从混乱的文档中提取有效的电话号码,或者从大量文件中快速找到特定类型的文件。


正则表达式的超能力 💪

从事文本处理工作的人,其一项超能力就是掌握正则表达式。一个经典的例子是:你有一个包含大量电话号码的文档,其中一些号码格式混乱,你希望只提取出格式正确的有效号码。

例如,观察以下号码:

  • 919-555-1234 看起来格式正确。
  • 919-555-123 看起来格式错误。
  • 919-555-1234 看起来格式正确。
  • 919-555-123 看起来格式错误。

即使对于有几年工作经验的人来说,手动完成这项任务也非常困难且繁琐,容易出错。幸运的是,我们可以利用Linux系统中的工具轻松地自行处理。


使用 grep 匹配电话号码 📞

接下来,我们将通过一个命令来演示。我将使用 echo 命令输出一个有效的电话号码,并通过 grep 命令来匹配它。

命令的核心是使用正则表达式模式:[0-9]{3}-[0-9]{3}-[0-9]{4}。这个模式的含义是:

  • [0-9]{3}:匹配一个由三位数字(0到9)组成的字符串。
  • -:匹配一个连字符。
  • 整体模式要求匹配“三位数字-三位数字-四位数字”的格式。

让我们执行这个命令:

echo "919-555-1234" | grep -E "[0-9]{3}-[0-9]{3}-[0-9]{4}"

运行后,它会成功匹配并输出有效的电话号码。

如果我们输入一个无效的号码:

echo "919-555-123" | grep -E "[0-9]{3}-[0-9]{3}-[0-9]{4}"

它将不会产生任何输出,因为模式不匹配。这正是我们想要的:只保留有效的条目,忽略无效的。


处理文件中的电话号码列表 📄

上一节我们介绍了如何匹配单个字符串,本节中我们来看看如何将这个方法应用于整个文件。我们可以将 grep 命令与文件读取命令 cat 结合使用。

假设所有电话号码都存储在一个名为 phone_numbers.txt 的文件中。以下是处理该文件的命令:

cat phone_numbers.txt | grep -E "[0-9]{3}-[0-9]{3}-[0-9]{4}"

执行后,命令会从文件中筛选出仅有的两个格式正确的电话号码。这个例子展示了如何将正则表达式集成到Shell脚本中,从而轻松处理格式混乱的文档。

需要注意的是,正则表达式可能非常复杂,需要花费时间确保其正确性。但一旦掌握,它能以极快的速度清理文本。


使用 grep 筛选特定文件 📁

除了处理文件内容,正则表达式还可以用于筛选文件名。例如,使用 ls 命令列出目录中的文件时,如果文件数量庞大,手动查找会非常麻烦。

我们可以使用 grep 来过滤 ls 命令的输出。以下命令可以找出文件名中包含“phone”或“fruit”的文件:

ls | grep -E "phone|fruit"

执行后,我们只看到包含这两个关键词的文件。这是一个将正则表达式与其他工具(如 ls)的输出流结合使用的绝佳例子。


在大型文档中搜索与统计 📊

现在,让我们看一个更实际的数据预处理例子。假设我们有一个包含数千行文本的评论文档 reviews.txt

我们可以使用相同的 grep 模式来查找包含特定品牌(如“Avante”或“Samsung”)的评论,并进行计数。命令如下:

grep -E "Avante|Samsung" reviews.txt | wc -l

对于一个有5000行的文档,此命令可能会输出309,表示找到了309条相关评论。我们还可以进一步验证结果的准确性。

以下是验证步骤:

  1. 统计只包含“Avante”的评论数:
    grep "Avante" reviews.txt | wc -l
    
    (假设输出82)
  2. 统计只包含“Samsung”的评论数:
    grep "Samsung" reviews.txt | wc -l
    
    (假设输出227)
  3. 将两个数字相加:82 + 227 = 309。这与我们组合查询的结果一致,验证了命令的正确性。

这展示了在数据进行其他复杂处理之前,使用 grep 进行快速筛选和预处理是一种简单有效的方法。


总结 🎯

本节课中我们一起学习了Linux正则表达式的基础应用。我们了解到:

  1. 正则表达式是强大的文本模式匹配工具,尤其适合从混乱数据中提取规整信息(如电话号码)。
  2. 使用 grep -E 命令可以执行扩展正则表达式匹配。
  3. 正则表达式可以轻松地与管道(|)结合,处理命令输出流或文件内容。
  4. 应用场景广泛,包括验证数据格式、筛选特定文件、在大型文档中搜索和统计关键词等。

花时间学习正则表达式,未来在处理文本和数据时将会获得丰厚的回报。电话号码匹配是绝佳的练习起点,而快速筛选文件则是正则表达式一个非常实用的用例。

068:欢迎来到Python与SQL数据工程脚本编程 🐍

在本课程中,我们将学习如何高效地运用Python和SQL进行数据工程工作。

我们将探讨Python脚本编程中有用的数据结构,并学习如何连接到MySQL等数据库。你将学会使用现代文本编辑器来连接真实的数据库并运行SQL查询,执行数据加载和提取操作。最后,你将运用网络抓取技术从网站中提取数据。掌握这些知识将使你能够在数据不易获取时,或需要执行特殊查询从数据库中提取有用信息时,都能有效地开展工作。


上一节我们概述了本课程的整体目标,本节中我们将具体了解课程的核心内容模块。

以下是本课程将涵盖的主要技术要点:

  • Python脚本与数据结构:学习在数据工程任务中常用的Python数据结构和脚本编写技巧。
  • 数据库连接与操作:掌握使用Python连接MySQL数据库,并执行SQL查询的方法。
  • 数据加载与提取:实践从数据库加载数据以及将处理后的数据提取出来的完整操作流程。
  • 网络数据抓取:运用Python库从网站上抓取所需数据,扩展数据来源。

本节课中我们一起学习了Python与SQL在数据工程中的核心应用场景,包括脚本编写、数据库交互以及数据获取技术。这些技能是构建有效数据管道和处理多样化数据源的基础。

069:认识你的课程讲师

概述

在本节课中,我们将认识本课程的讲师,了解他的专业背景和教学经验,这有助于我们理解课程内容的来源和侧重点。

讲师介绍

我的名字是Salreesa,我曾是一名奥林匹克运动员,目前担任云技术布道师,专注于Python和机器学习领域。

我的专业背景主要集中在系统管理、Python和DevOps方面。

我撰写了许多关于Python和机器学习的书籍。

我很高兴能在这里,教授大家如何在Python中结合SQL和数据库来使用数据的强大技术。

总结

本节课我们一起认识了本课程的讲师Salreesa。我们了解到他拥有奥林匹克运动员的经历,现在是专注于Python和机器学习的云技术布道师,具备深厚的系统管理、Python和DevOps背景,并且著有多本相关书籍。他将指导我们学习在Python中有效运用SQL和数据库处理数据的核心技巧。

070:Python、Bash和SQL核心概念概览 🎯

在本节课中,我们将要学习数据工程中的几个核心概念,包括在Python中持久化数据的不同方法、SQL的重要性以及网络数据抓取(Scraping)的应用场景。理解这些概念将帮助你为不同的任务选择正确的工具。


Python中的数据持久化 💾

上一节我们介绍了课程的整体框架,本节中我们来看看如何在Python中保存数据。将数据保存到磁盘是数据工程中的常见操作,Python提供了多种格式来实现这一目标。

使用 with 语句写入文件

一种非常基础且常见的方法是直接将数据写入文本文件。在这个过程中,with 语句是一个关键工具。它的作用是为代码块创建一个上下文环境,确保文件在使用后被正确关闭,而无需手动调用关闭方法。这可以防止数据丢失或文件损坏。

以下是使用 with 语句写入文件的示例代码:

with open('data.txt', 'w') as file:
    file.write('Hello, World!')

使用 pickle 序列化对象

当你需要保存复杂的Python数据结构(如字典、列表或自定义对象)时,pickle 模块非常有用。它可以将对象序列化为字节流并保存到文件,之后可以完整地还原。这在机器学习中尤其常见,用于保存训练好的模型。

以下是使用 pickle 保存和加载数据的示例代码:

import pickle

# 保存数据
data = {'key': 'value'}
with open('data.pkl', 'wb') as file:
    pickle.dump(data, file)

# 加载数据
with open('data.pkl', 'rb') as file:
    loaded_data = pickle.load(file)

JSON与YAML格式

除了上述方法,JSON和YAML是两种在软件开发中广泛使用的数据交换格式。

JSON 全称为JavaScript Object Notation。它结构清晰,易于人阅读和机器解析,因此常被用于构建API接口。你可以将Python字典轻松转换为JSON字符串。

以下是使用 json 模块的示例代码:

import json

data = {'name': 'Alice', 'age': 30}
json_string = json.dumps(data)  # 转换为JSON字符串

YAML 格式与JSON类似,但语法更注重人类可读性,常用于配置文件,特别是在DevOps流程(如GitHub Actions)或云服务配置中。

以下是使用 yaml 模块的示例代码(需安装PyYAML库):

import yaml

data = {'server': {'host': '127.0.0.1', 'port': 8080}}
yaml_string = yaml.dump(data)  # 转换为YAML字符串

选择哪种格式取决于你手头要解决的具体问题:pickle 适合保存Python特有对象,JSON适合数据交换,YAML适合编写配置文件。


SQL:久经考验的数据查询语言 🗃️

现在,让我们从数据存储转向数据查询。SQL(结构化查询语言)是数据领域不可或缺的工具。

SQL诞生于1970年代,历经数十年考验,至今仍是查询和处理数据的黄金标准。它的强大之处在于其通用性,既能用于小型数据库,也能作为许多大数据系统的查询接口。

例如,Apache Spark和Amazon Athena这样的大数据处理引擎都支持使用SQL进行查询。这意味着,掌握SQL基础能让你在各类数据平台上都能有效地工作。


网络数据抓取(Scraping)🌐

最后,我们探讨网络数据抓取。当目标网站没有提供官方API接口时,抓取技术就变得至关重要。

这种情况常出现在一些老旧的数据集、政府网站或最初并非为程序化访问而设计的页面上。通过抓取,你可以自动访问网页,提取所需的信息,并将其保存到关系型数据库或其他存储系统中,从而获得有价值的数据集。

掌握网络抓取技能,能让你成为一名更全面、更高效的数据科学家或工程师,能够从更广泛的数据源中获取信息。


总结 📝

本节课中我们一起学习了数据工程的三个核心概念:

  1. Python数据持久化:了解了使用普通文件、pickle、JSON和YAML保存数据的不同场景与方法。
  2. SQL的重要性:认识到SQL作为一种通用、稳定且支持大小数据系统的查询语言的核心地位。
  3. 网络数据抓取的应用:明白了在缺乏API时,如何通过抓取技术从网页中获取所需数据。

理解这些概念并知道何时应用它们,是构建有效数据解决方案的关键第一步。

071:Python数据处理入门 🐍

在本节课中,我们将学习如何有效地使用Python的数据结构来加载、持久化和遍历数据。你将学会将这些数据结构应用于处理流行数据格式(如JSON)时遇到的不同问题。最终,你将能够从不同来源提取数据,并将其映射到Python的数据结构中。


数据结构基础

上一节我们概述了课程目标,本节中我们来看看Python中用于数据处理的核心数据结构。Python提供了几种内置的数据结构,它们是组织和存储数据的基石。

以下是三种最常用的数据结构:

  • 列表:一种有序、可变的元素集合。使用方括号 [] 定义。
    • 示例:my_list = [1, 2, 3, ‘hello’]
  • 元组:一种有序、不可变的元素集合。使用圆括号 () 定义。
    • 示例:my_tuple = (1, 2, ‘world’)
  • 字典:一种无序的键值对集合。使用花括号 {} 定义,键和值之间用冒号 : 分隔。
    • 示例:my_dict = {‘name’: ‘Alice’, ‘age’: 25}


数据加载与遍历

理解了基本数据结构后,我们来看看如何将数据加载到这些结构中并进行遍历。这是数据处理的第一步。

以下是加载和遍历数据的常用方法:

  • 从文件加载:使用 open() 函数和 read() 方法读取文本文件内容。
    • 示例:with open(‘data.txt’, ‘r’) as file: data = file.read()
  • 遍历列表:使用 for 循环可以轻松访问列表中的每个元素。
    • 示例:for item in my_list: print(item)
  • 遍历字典:使用 .items() 方法可以同时获取字典的键和值。
    • 示例:for key, value in my_dict.items(): print(key, value)

处理JSON数据

在实际工作中,JSON是一种极其常见的数据交换格式。Python的 json 模块使得处理JSON数据变得非常简单。

以下是处理JSON数据的关键步骤:

  • 解析JSON字符串:使用 json.loads() 函数将JSON格式的字符串转换为Python字典或列表。
    • 示例:import json; python_data = json.loads(‘{“name”: “Bob”}’)
  • 读取JSON文件:使用 json.load() 函数直接从JSON文件中读取数据并转换为Python对象。
    • 示例:with open(‘data.json’, ‘r’) as f: data = json.load(f)
  • 将Python对象转为JSON:使用 json.dumps() 函数将Python字典或列表转换回JSON格式的字符串。
    • 示例:json_string = json.dumps(my_dict)

数据提取与映射

最后,我们将综合运用以上知识,学习如何从不同来源(如API、数据库或CSV文件)提取数据,并将其映射到合适的Python数据结构中,以便进行进一步的分析和处理。

以下是数据提取与映射的基本思路:

  • 确定数据源:明确数据来自哪里(例如,一个网络API的URL,一个本地数据库,或一个CSV文件)。
  • 提取原始数据:使用相应的方法获取原始数据(如 requests.get() 用于API,pandas.read_csv() 用于CSV文件)。
  • 清洗与转换:将原始数据(通常是字符串或特定格式)转换为Python数据结构(如列表、字典)。这可能涉及解析JSON、分割字符串等操作。
  • 结构化存储:将转换后的数据存储到设计好的数据结构中,为后续操作做好准备。


本节课中我们一起学习了Python数据处理的核心流程。我们从列表、元组和字典这些基础数据结构开始,掌握了如何加载和遍历数据。接着,我们深入探讨了如何处理常见的JSON格式数据。最后,我们了解了从多种数据源提取信息并将其映射到Python对象中的完整思路。掌握这些技能是成为一名高效数据工程师的重要第一步。

072:Python数据结构入门 🐍

在本节课中,我们将要学习Python中几种核心的数据结构。不同的数据需要不同的数据结构来处理,这样才能更高效地工作。虽然每次都使用Python中一些更常见的数据结构听起来很诱人,但那些不那么常见的数据结构也自有其用武之地。本节课我们将介绍一些更常见的数据结构,如列表字典,同时也会涵盖一些不那么常见但同样重要的结构,如集合元组。理解它们的工作原理,将帮助你在处理数据时做出更明智的决策。

列表(Lists)

上一节我们概述了数据结构的重要性,本节中我们来看看最灵活、最常用的数据结构之一:列表。

列表是一个有序的元素集合,用方括号 [] 表示。列表中的元素可以是任何数据类型,并且列表是可变的,这意味着创建后可以修改其内容。

以下是列表的基本操作:

  • 创建列表my_list = [1, 2, 3, “hello”]
  • 访问元素:使用索引,例如 my_list[0] 返回 1
  • 修改元素my_list[1] = “world” 将列表的第二个元素改为 “world”
  • 添加元素:使用 append() 方法在末尾添加,例如 my_list.append(4)
  • 删除元素:使用 remove() 方法删除特定值,或使用 pop() 方法删除指定索引的元素。

元组(Tuples)

了解了可变的列表后,我们来看看它的不可变版本:元组。

元组与列表类似,也是一个有序的集合,但它是不可变的,用圆括号 () 表示。一旦创建,其内容就不能被修改。这使得元组在某些场景下(如作为字典的键或确保数据不被意外更改)非常有用。

以下是元组的基本操作:

  • 创建元组my_tuple = (1, 2, 3, “apple”)
  • 访问元素:与列表相同,使用索引,例如 my_tuple[2] 返回 3
  • 尝试修改会报错my_tuple[0] = 10 会导致 TypeError

字典(Dictionaries)

接下来,我们学习一种通过键(key)来存取值(value)的高效数据结构:字典。

字典是一种无序的键值对集合,用花括号 {} 表示。每个键都必须是唯一的,并且与一个值相关联。字典非常适合用于存储和快速查找具有标签的数据。

以下是字典的基本操作:

  • 创建字典my_dict = {“name”: “Alice”, “age”: 30, “city”: “New York”}
  • 访问值:通过键来访问,例如 my_dict[“name”] 返回 “Alice”
  • 添加或修改键值对my_dict[“job”] = “Engineer” 会添加新条目。
  • 删除键值对:使用 del 关键字,例如 del my_dict[“city”]

集合(Sets)

最后,我们来探讨一种用于存储唯一元素的无序集合:集合。

集合是一个无序的、不重复元素的集,用花括号 {} 表示(但创建空集合必须使用 set() 函数)。集合支持数学上的集合操作,如并集、交集和差集,常用于成员测试和消除重复项。

以下是集合的基本操作:

  • 创建集合my_set = {1, 2, 3, 3, 4} 结果会是 {1, 2, 3, 4}
  • 添加元素:使用 add() 方法,例如 my_set.add(5)
  • 删除元素:使用 remove() 方法。
  • 集合运算
    • 并集:set_a | set_bset_a.union(set_b)
    • 交集:set_a & set_bset_a.intersection(set_b)
    • 差集:set_a - set_bset_a.difference(set_b)

本节课中我们一起学习了Python的四种核心数据结构:列表(有序、可变)、元组(有序、不可变)、字典(键值对映射)和集合(无序、唯一)。每种结构都有其特定的用途和优势,理解它们之间的区别是编写高效、清晰Python代码的基础。在实际的数据工程任务中,根据数据的特点和操作需求选择合适的数据结构至关重要。

073:使用列表在Python中保存与检索数据 📚

在本节课中,我们将要学习Python中一个非常基础且重要的数据结构——列表。列表就像一个容器,我们可以向其中放入各种数据,也可以从中取出数据。我们将学习如何创建列表、向列表中添加元素、修改列表以及如何根据位置(索引)检索列表中的元素。


什么是列表? 📦

列表是Python中用于存储一系列元素的数据结构。列表中的元素可以是任何数据类型,例如数字、字符串、布尔值,甚至是其他列表。列表使用方括号 [] 来定义。

创建列表

我们可以创建一个空列表,也可以创建一个包含初始元素的列表。

创建一个空列表:

things = []

创建一个包含不同类型元素的列表:

things_one = [True, 10, "hello"]

在上面的例子中,things_one 列表包含了一个布尔值 True、一个整数 10 和一个字符串 "hello"


向列表中添加元素 ➕

上一节我们介绍了如何创建列表,本节中我们来看看如何向一个已存在的列表中添加新元素。Python提供了几种方法来修改列表。

为了演示,我们先导入 os 模块,并获取一个目录列表。

import os
directories = os.listdir('./code')
print(directories)

这段代码会列出 ./code 路径下的所有文件和目录。

使用 append() 方法

append() 方法用于在列表的末尾添加一个新元素。

以下是使用 append() 方法的示例:

directories.append('example')
print(directories)

执行后,字符串 'example' 会被添加到 directories 列表的最后。

使用 insert() 方法

如果你想在列表的特定位置插入一个元素,可以使用 insert() 方法。该方法需要两个参数:要插入的位置(索引)和要插入的元素。

以下是使用 insert() 方法的示例:

directories.insert(0, 'example')
print(directories)

这段代码会在列表的开头(索引0的位置)插入 'example'


从列表中检索元素 🔍

现在我们已经知道如何向列表中添加元素,接下来学习如何从列表中取出我们需要的元素。这主要通过索引来实现。

理解索引

在Python中,列表的索引从 0 开始计数。这意味着:

  • 第一个元素的索引是 0
  • 第二个元素的索引是 1
  • 以此类推

此外,Python支持使用负数索引来从列表末尾开始计数:

  • 索引 -1 代表最后一个元素
  • 索引 -2 代表倒数第二个元素

以下是检索元素的示例:

# 获取第一个元素
first_item = directories[0]
print(first_item)  # 例如:'.DS_Store'

# 获取第二个元素
second_item = directories[1]
print(second_item) # 例如:'OReilly_videos'

# 获取最后一个元素
last_item = directories[-1]
print(last_item)   # 例如:'scraping_demo'

使用 index() 方法查找位置

有时,我们知道列表中的某个值,但想知道它所在的位置(索引)。这时可以使用 index() 方法。

以下是使用 index() 方法的示例:

position = directories.index('scraping_demo')
print(f"'scraping_demo' 的索引是:{position}")

运行后,我们会得到 'scraping_demo' 在列表中的索引号(例如 16)。之后,我们就可以用这个索引号来获取该元素:

item = directories[position]
print(item) # 输出:'scraping_demo'

需要注意的是:如果你尝试查找一个列表中不存在的值,index() 方法会引发一个 ValueError 错误。

# 这行代码会报错,因为 'non_existent' 不在列表中
# directories.index('non_existent')

总结 ✨

本节课中我们一起学习了Python列表的基本操作。

我们首先了解了列表就像一个可以存放各种数据类型的容器。然后,我们学习了两种向列表添加元素的方法:使用 append() 在末尾添加,以及使用 insert() 在指定位置插入。

最后,我们重点学习了如何从列表中检索数据。关键点在于理解索引从0开始的规则,并掌握了使用正数索引、负数索引来获取元素,以及使用 index() 方法根据值来查找其对应位置的方法。

掌握列表的创建、修改和检索,是进行Python数据处理的基石。

074:使用字典在Python中保存与检索数据 📚

在本节课中,我们将要学习Python中一个非常重要的数据结构——字典。字典可以帮助我们将一个事物映射到另一个事物,这种映射关系在数据处理中非常有用。

概述

字典通过“键-值对”的形式组织数据。我们可以将“键”想象成标签,而“值”就是该标签对应的具体信息。接下来,我们将从创建一个空字典开始,逐步学习如何声明、检索和修改字典中的数据。

声明一个字典

让我们从一个简单的例子开始:创建一个存储联系人信息的字典。联系人是理解字典的绝佳例子,因为在手机通讯录中,我们正是通过姓名(键)来查找电话号码(值)的。

以下是声明一个字典的方法:

contacts = {
    “name”: “Alfredo”,
    “last_name”: “Deza”
}

在这段代码中,我们使用花括号 {} 创建了一个字典。“name”“last_name” 是键,它们分别被映射到值 “Alfredo”“Deza”。运行这段代码,字典 contacts 就包含了这两条信息。

从字典中检索数据

上一节我们介绍了如何创建字典,本节中我们来看看如何从中获取信息。当我们知道一个键的名称时,可以使用方括号来请求其对应的值。

以下是请求信息的示例:

name = contacts[“name”]
print(name)  # 输出:Alfredo

这种方法同样适用于字典中的任何其他键。

last_name = contacts[“last_name”]
print(last_name)  # 输出:Deza

处理不存在的键

直接使用方括号检索有一个问题:如果请求的键不存在,Python会抛出一个 KeyError 异常,导致程序中断。

例如,尝试获取不存在的 “phone” 键:

# 这行代码会引发 KeyError: ‘phone’
phone = contacts[“phone”]

为了避免程序因键不存在而崩溃,我们可以使用 .get() 方法。这个方法允许我们安全地检索值,并在键不存在时返回一个默认值(或 None)。

以下是使用 .get() 方法的示例:

# 键不存在时返回 None
phone = contacts.get(“phone”)
print(phone)  # 输出:None

# 键不存在时返回自定义的默认值
phone = contacts.get(“phone”, “Unknown”)
print(phone)  # 输出:Unknown

使用 .get() 方法是处理可能缺失数据的一种优雅方式,它避免了异常,让程序更加健壮。

探索字典的键和值

有时我们需要查看字典中所有的键或值。Python为此提供了方便的方法。

以下是获取所有键和值的示例:

# 获取所有键
all_keys = contacts.keys()
print(all_keys)  # 输出类似:dict_keys([‘name‘, ‘last_name‘])

# 获取所有值
all_values = contacts.values()
print(all_values)  # 输出类似:dict_values([‘Alfredo‘, ‘Deza‘])

返回的 dict_keysdict_values 对象类似于列表,你可以遍历它们来查看所有内容。

向字典中添加或修改数据

字典不是一成不变的。我们可以轻松地向其中添加新的键值对,或者修改现有键对应的值。

以下是添加新信息的示例:

# 添加电话号码
contacts[“phone”] = “123-456-7890”
print(contacts)
# 输出:{‘name‘: ‘Alfredo‘, ‘last_name‘: ‘Deza‘, ‘phone‘: ‘123-456-7890‘}

在这段代码中,我们通过 contacts[“phone”] = “123-456-7890” 这行语句,在 contacts 字典中创建了一个新键 “phone” 并赋予了它一个值。现在字典中就包含了三条信息。

总结

本节课中我们一起学习了Python字典的核心操作。我们首先了解了字典是通过键-值对存储数据的映射结构。然后,我们学习了如何声明一个字典,如何使用方括号或 .get() 方法安全地检索数据。接着,我们探索了如何查看字典的所有。最后,我们掌握了如何添加或修改字典中的数据。字典是Python中用于组织和管理关联数据的强大工具,在数据工程中应用广泛。

075:Python非常见数据结构概览 🐍

在本节课中,我们将学习Python中两种相对不那么常见但非常有用的数据结构:元组(Tuple)和集合(Set)。我们将了解它们的特点、创建方式以及适用场景。


元组:不可变的序列 📦

上一节我们介绍了列表等常见数据结构,本节中我们来看看元组。元组类似于一个只读的列表。一旦创建,其内容就无法被修改。

元组的声明方式是使用圆括号 (),而不是列表的方括号 [] 或字典的花括号 {}

# 创建一个元组
read_only = ("Item 1", "Item 2")
print(read_only)  # 输出:('Item 1', 'Item 2')

以下是关于元组的一些关键操作:

  • 访问元素:你可以像列表一样通过索引来检索元素。
    print(read_only[0])  # 输出:Item 1
    print(read_only[1])  # 输出:Item 2
    
  • 不可修改性:尝试修改元组(例如添加、删除或更改元素)会导致错误。
    read_only.append("Item 3")  # 这会引发 AttributeError
    

元组适用于存储不应被程序意外更改的数据集合。


集合:唯一元素的容器 🧺

接下来,我们探讨集合。集合的主要特性是它只包含唯一的元素,会自动去除所有重复项。

集合的声明方式是使用花括号 {},这与字典相同,但集合内部是单个元素,而不是键值对。

# 创建一个集合
unique = {1, 2, 3, 1, 1}
print(unique)  # 输出:{1, 2, 3}
print(type(unique))  # 输出:<class 'set'>

以下是集合的一些基本操作:

  • 添加元素:使用 .add() 方法向集合中添加新元素。如果元素已存在,则不会有任何效果。
    unique.add(1)  # 集合不变,因为1已存在
    unique.add(4)  # 添加新元素4
    print(unique)  # 输出:{1, 2, 3, 4}
    
  • 创建空集合:要创建一个空集合,必须使用 set() 函数。直接使用 {} 创建的是空字典。
    empty_set = set()
    print(empty_set)  # 输出:set()
    

当你需要确保容器中元素的唯一性,或者需要进行集合运算(如交集、并集)时,集合是一个理想的选择。


总结 📝

本节课中我们一起学习了Python的两种非常见数据结构:

  1. 元组:使用圆括号 () 定义,是一种不可变的序列,适用于存储不应更改的数据。
  2. 集合:使用花括号 {} 定义或 set() 函数创建,是一种元素唯一的无序集合,能自动去重。

理解这些数据结构的特点,能帮助你在数据工程任务中选择最合适的工具来高效处理数据。

076:Python数据结构回顾 🐍

在本节课中,我们将回顾Python中一些更常见的数据结构。你将学习到列表、字典,以及根据数据处理工作的类型可能用到的元组和集合等不太常见的数据结构。

概述 📋

在Python中处理数据时,选择合适的数据结构至关重要。如果你需要存储一系列有序的元素,你可能会使用列表;如果你需要映射键和值,你肯定会用到字典;如果你追求元素的唯一性,集合可能更合适;而如果你需要一个类似列表的只读容器,那么元组或许是你的选择。掌握这些数据结构将帮助你更有效地管理Python中的工作负载,提升数据处理效率。

核心数据结构详解

上一节我们概述了Python中几种主要的数据结构,本节中我们将逐一深入探讨它们的特点和用法。

列表

列表是Python中最基础、最常用的数据结构之一。它是一个有序、可变的集合,可以包含不同类型的元素。

以下是列表的基本操作示例:

# 创建一个列表
my_list = [1, 2, 3, ‘hello‘, 5.0]

# 访问元素(索引从0开始)
first_element = my_list[0]  # 结果为 1

# 修改元素
my_list[1] = ‘world‘

# 添加元素
my_list.append(‘new item‘)

# 删除元素
del my_list[2]

字典

字典用于存储键值对,它基于键来快速检索值,是无序但可变的集合。

以下是字典的基本操作示例:

# 创建一个字典
my_dict = {‘name‘: ‘Alice‘, ‘age‘: 30, ‘city‘: ‘New York‘}

# 通过键访问值
name = my_dict[‘name‘]  # 结果为 ‘Alice‘

# 添加或修改键值对
my_dict[‘job‘] = ‘Engineer‘
my_dict[‘age‘] = 31

# 删除键值对
del my_dict[‘city‘]

元组

元组与列表类似,都是有序的集合。但关键区别在于,元组是不可变的,创建后不能修改其内容。这使其适合用作只读的数据容器。

以下是元组的基本操作示例:

# 创建一个元组
my_tuple = (1, 2, 3, ‘apple‘)

# 访问元素(与列表相同)
second_element = my_tuple[1]  # 结果为 2

# 尝试修改元组会导致错误
# my_tuple[1] = ‘banana‘  # 这行代码会引发 TypeError

集合

集合是一个无序的、不重复元素的集合。它的主要用途是进行成员关系测试和消除重复项,并支持数学上的集合运算,如并集、交集等。

以下是集合的基本操作示例:

# 创建一个集合
my_set = {1, 2, 3, 3, 4}  # 重复的3会被自动去除,结果为 {1, 2, 3, 4}

# 添加元素
my_set.add(5)

# 移除元素
my_set.remove(2)

# 集合运算
set_a = {1, 2, 3}
set_b = {3, 4, 5}
intersection = set_a & set_b  # 交集,结果为 {3}
union = set_a | set_b         # 并集,结果为 {1, 2, 3, 4, 5}

如何选择合适的数据结构

了解了每种数据结构的特点后,选择哪一个取决于你的具体需求。以下是选择时的关键考量点:

  • 需要有序且可变的序列吗? -> 使用 列表
  • 需要通过唯一的键来快速查找关联的值吗? -> 使用 字典
  • 需要一个保证元素唯一性的无序集合吗?需要进行集合运算(如求交集、并集)吗? -> 使用 集合
  • 需要一个有序但创建后内容不可变的轻量级容器吗? -> 使用 元组

总结 🎯

本节课中我们一起学习了Python的四种核心数据结构:列表字典元组集合。我们明确了列表的有序可变性、字典的键值对映射能力、元组的不可变性以及集合的元素唯一性和集合运算功能。理解并熟练运用这些数据结构,是使用Python高效管理和处理数据的基石。

077:Python数据结构选择入门 🐍

在本节课中,我们将学习如何从Python的数据结构中提取数据。具体来说,我们将探讨如何遍历列表、字典等数据结构,提取其中的信息,并可能创建新的数据结构来组织内容。掌握这些技能对于高效处理数据至关重要,它使您能够在分析数据内容的同时,对数据进行筛选、区分和操作,并最终为后续的数据持久化存储做好准备。


从数据结构中提取数据

上一节我们介绍了Python的基本数据结构。本节中,我们来看看如何从这些结构中提取信息。

提取数据通常涉及遍历数据结构中的元素。无论是处理列表中的多个项目,还是分析字典中的键值对,遍历都是核心操作。这个过程使您能够与数据进行有效交互,在浏览数据内容的同时,对数据进行分离或区分,以便进一步处理。

遍历与数据操作

以下是遍历数据结构并操作数据的一些基本方法:

  • for循环遍历列表:使用for循环可以依次访问列表中的每个元素。

    my_list = [1, 2, 3, 4, 5]
    for item in my_list:
        print(item * 2)  # 对每个元素进行操作
    
  • 遍历字典的键值对:使用.items()方法可以同时获取字典的键和值。

    my_dict = {'a': 1, 'b': 2, 'c': 3}
    for key, value in my_dict.items():
        print(f"Key: {key}, Value: {value}")
    
  • 列表推导式创建新列表:这是一种从现有列表快速生成新列表的简洁方法。

    original_list = [1, 2, 3, 4, 5]
    squared_list = [x**2 for x in original_list]  # 创建平方数新列表
    
  • 条件筛选数据:在遍历时结合if语句,可以筛选出符合特定条件的数据。

    numbers = [10, 15, 20, 25, 30]
    even_numbers = [num for num in numbers if num % 2 == 0]  # 筛选偶数
    

数据提取的实际意义

通过遍历提取数据后,您可以进行多种操作。例如,您可以计算统计信息、过滤掉不需要的数据点、将数据转换为新的格式,或者为存入数据库或文件做好准备。当您完成对列表中多个项目或字典内容的迭代处理后,便能够创建或持久化最终的数据结果。


本节课中我们一起学习了从Python数据结构中提取数据的关键技术。我们了解了如何使用循环遍历列表和字典,如何利用列表推导式高效地创建新列表,以及如何在过程中筛选数据。这些是数据处理的基础,将使您能够有效地操作、分析和准备数据,为后续更复杂的数据工程任务打下坚实的基础。

078:Python中列表与字典的迭代操作 🐍

在本节课中,我们将要学习如何在Python中对两种核心数据结构——列表和字典——进行迭代操作。迭代是遍历集合中每个元素并执行操作的过程,是数据处理的基础。

列表的迭代操作

上一节我们介绍了数据结构,本节中我们来看看如何遍历列表。列表是一种有序的元素集合,我们可以使用for循环来逐个访问其中的元素。

以下是一个简单的列表迭代示例:

names = ['Alfredo', 'Kennedy', 'Jim']
for name in names:
    print(name)

这段代码会依次打印出列表中的每个名字。for循环的语法清晰易读,表明我们正在处理一组名字中的每一个。

在循环中添加条件

有时我们只想处理列表中满足特定条件的元素。这可以通过在for循环中加入if语句来实现。

以下是添加条件判断的示例:

for name in names:
    if len(name) > 5:
        print(name)

这段代码只会打印出长度大于5个字符的名字,即“Alfredo”和“Kennedy”。

列表推导式

Python提供了一种更简洁的创建新列表的方法,称为列表推导式。它允许我们将循环和条件判断内联在一行代码中。

以下是使用列表推导式的示例:

long_names = [name for name in names if len(name) > 5]
print(long_names)

这段代码会创建一个名为long_names的新列表,其中只包含原列表中长度大于5的名字。列表推导式非常简洁,但当逻辑复杂或存在嵌套时,可能会影响代码的可读性。

字典的迭代操作

现在,让我们将注意力转向字典。字典是一种键值对集合,其迭代方式与列表略有不同,取决于我们想要访问的是键、值还是两者。

遍历字典的键

默认情况下,直接对字典进行for循环会遍历其所有的键。

以下是遍历字典键的示例:

contacts = {
    'Alfredo': 'alfredo@example.org',
    'Kennedy': 'kennedy@example.org',
    'Noah': 'noah@example.org'
}
for contact in contacts:
    print(contact)

这段代码会打印出字典中所有的键,即人名。

遍历字典的值

如果我们只想获取字典中的值,可以使用.values()方法。

以下是遍历字典值的示例:

for contact in contacts.values():
    print(contact)

这段代码会打印出字典中所有的值,即电子邮件地址。

同时遍历键和值

为了同时获取字典的键和值,我们需要使用.items()方法,并在循环中“解包”这两个元素。

以下是同时遍历键和值的示例:

for name, email in contacts.items():
    print(name, email)

在这段代码中,name变量接收键(人名),email变量接收值(电子邮件)。这样我们就可以在循环中同时使用两者。

总结

本节课中我们一起学习了Python中对列表和字典进行迭代的核心方法。

  • 对于列表,我们掌握了使用基本的for循环、在循环中添加条件判断,以及使用简洁的列表推导式来创建新列表。
  • 对于字典,我们学会了如何分别遍历其(直接迭代或使用.keys())、(使用.values())以及键值对(使用.items()并解包)。

理解这些迭代技巧是进行有效数据操作和处理的基石。

079:Python中其他数据结构的迭代操作 🔄

在本节课中,我们将要学习如何在Python中迭代遍历除列表和字典之外的其他数据结构,特别是元组(tuple)和集合(set)。掌握循环结构后,这些操作会变得非常简单。

概述

上一节我们介绍了列表和字典的迭代操作。本节中我们来看看如何对元组和集合进行类似的遍历。我们将通过定义示例变量并编写循环代码来演示其用法。

迭代遍历元组

首先,我们创建一个只读的元组变量,它包含四个项目:1、2、3和4。迭代遍历元组的构造与遍历列表非常相似,甚至完全相同。

以下是遍历元组的代码示例:

read_only = (1, 2, 3, 4)
for ro_item in read_only:
    print(ro_item)

这段代码会依次打印出1、2、3和4。操作直接且简单。

迭代遍历集合

接下来,我们创建一个集合。集合的特点是它会自动去除重复项。我们定义一个包含重复数字1和2的集合,然后遍历它。

以下是遍历集合的代码示例:

unique = {1, 1, 2, 2, 3}
for item in unique:
    print(item)

运行这段代码,输出结果是1、2和3。因为集合中重复的1和2只被保留了一份,所以只打印出这三个唯一的项目。

核心要点总结

本节课中我们一起学习了如何迭代遍历Python中的元组和集合。

  • 元组的迭代:与列表迭代方式完全相同,使用 for item in tuple: 结构。
  • 集合的迭代:同样使用 for item in set: 结构,但遍历的是集合中的唯一元素。

如果你已经掌握了如何处理Python中最常见的数据结构——列表,那么你就能轻松地遍历和操作像元组和集合这样不太常见的数据结构了。

080:Python数据结构间的数据存储 📂

在本节课中,我们将学习如何遍历数据结构,并根据特定条件创建新的数据结构或将数据从一个结构移动到另一个结构。具体来说,我们将通过一个实际例子来演示:如何遍历用户主目录的内容,并将文件和目录清晰地分离开来。

概述

我们将从列出主目录的内容开始,然后使用Python的os模块来区分文件和目录。最终,我们会将这些信息存储在一个字典中,以便后续操作。

实现步骤

首先,我们需要导入os模块,并列出主目录的内容。

import os

home_items = os.listdir('/users/Alfredo')

home_items现在是一个包含主目录下所有文件和目录名称的列表。然而,这些只是字符串,并非绝对路径。为了后续能准确判断它们是文件还是目录,我们需要将其转换为绝对路径。

以下是转换方法:

home_paths = [os.path.join('/users/Alfredo', item) for item in home_items]

现在,home_paths列表包含了所有项目的完整绝对路径。

接下来,我们将创建一个字典来分别存储文件和目录。我们初始化一个名为home_content的字典,其中包含两个键:filesdirectories,它们的初始值都是空列表。

home_content = {
    'files': [],
    'directories': []
}

现在,我们需要遍历home_paths列表中的每个路径,并根据其类型(文件或目录)将其添加到home_content字典的相应列表中。

以下是实现这一逻辑的代码:

for path in home_paths:
    if os.path.isdir(path):
        home_content['directories'].append(path)
    elif os.path.isfile(path):
        home_content['files'].append(path)

通过这个循环,我们成功地将所有目录路径添加到了home_content['directories']列表中,将所有文件路径添加到了home_content['files']列表中。

结果验证

现在,我们可以分别访问和操作这些分组后的数据。例如,我们可以打印出所有的文件路径:

for item in home_content['files']:
    print(item)

总结

本节课中,我们一起学习了如何在Python数据结构之间存储和转移数据。我们从使用os.listdir获取的一个简单列表开始,通过列表推导式将其转换为绝对路径列表。然后,我们创建了一个字典来清晰地分离文件和目录,并使用一个for循环和os.path模块的条件判断来填充这个字典。最终,我们得到了一个结构化的数据容器,可以方便地对文件和目录进行单独处理。这个过程展示了如何根据条件筛选数据并构建新的、更有组织的数据结构。

081:Python数据结构映射回顾 🗺️

在本节课中,我们将回顾Python中映射数据结构(主要是字典)的核心概念,并学习如何通过循环遍历数据来提取信息、进行分组和做出决策。这对于数据工程任务至关重要。

概述

我们已经学习了如何循环遍历数据、不同类型的数据结构,以及如何有效地创建新的分组和新的数据结构类型,以便在处理数据时以有意义的方式存储内容。这是必不可少的,因为在处理数据的过程中,你经常会遇到需要深入数据内部、确定其某些特征并尝试据此做出决策的情况。因此,能够通过循环遍历来提取数据,是你数据工程技能中的关键部分。

映射数据结构:字典

在Python中,字典是实现映射的主要数据结构。它将映射到,允许你通过唯一的键来快速访问、插入或删除相关联的值。

字典的创建和使用非常简单。以下是其核心语法:

# 创建一个字典
my_dict = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
}

# 通过键访问值
print(my_dict["key1"])  # 输出: value1

# 添加或修改键值对
my_dict["key4"] = "value4"

循环遍历字典

为了检查或处理字典中的数据,我们需要遍历它。Python提供了几种遍历字典的方法。

以下是遍历字典的几种主要方式:

  • 遍历所有键:使用 for key in my_dict:for key in my_dict.keys():
  • 遍历所有值:使用 for value in my_dict.values():
  • 同时遍历键和值:使用 for key, value in my_dict.items():

数据提取与决策

上一节我们介绍了如何遍历字典。本节中我们来看看如何在实际场景中应用这些循环来提取数据并基于数据特征做出决策。

例如,你可能需要检查一个字典中的所有值是否都符合某个条件(如都是正数),或者根据某些规则对数据进行分组。

以下是一个简单的示例,演示了如何遍历列表中的字典,并提取满足特定条件的数据:

# 假设我们有一个包含用户信息的字典列表
users = [
    {"name": "Alice", "age": 30, "active": True},
    {"name": "Bob", "age": 25, "active": False},
    {"name": "Charlie", "age": 35, "active": True}
]

# 提取所有活跃用户的姓名
active_users = []
for user in users:
    if user["active"]:  # 检查“active”键的值是否为True
        active_users.append(user["name"])

print(active_users)  # 输出: ['Alice', 'Charlie']

数据规范化任务

在处理数据时,经常需要进行规范化操作,例如修改数据格式,或者验证数据结构是否符合要求。

例如,你可能需要确保某个列表中的所有元素都是字符串类型,或者确保一个字典包含所有必需的键。

以下是一个检查并修正数据的例子:

# 一个可能包含非字符串年龄值的列表
data_list = ["25", 30, "thirty-five", "40"]

# 目标:将所有元素转换为整数,无法转换的标记为None
normalized_list = []
for item in data_list:
    try:
        normalized_list.append(int(item))
    except (ValueError, TypeError):
        normalized_list.append(None)  # 无法转换为整数,存入None

print(normalized_list)  # 输出: [25, 30, None, 40]

总结

本节课中我们一起学习了Python映射数据结构(字典)的回顾与应用。我们重点掌握了如何通过循环遍历字典来提取关键信息,以及如何利用这些技术进行数据检查和基本的规范化任务,例如筛选特定条目和转换数据类型。这项技能是数据工程的基础,能帮助你在深入探索数据集、清洗数据并使其满足分析需求方面取得进展。

082:Python数据源与格式入门 📚

在本节课中,我们将学习如何使用Python从不同格式的文件中读取和加载数据。我们将重点介绍处理文本文件、CSV文件以及JSON和YAML格式数据的基本方法。

从文件中读取数据

读取文件并将数据加载到程序中,其具体操作方式取决于你的目标以及可用的库。

根据你的需求,你可以选择将文件作为一个整体在Python中读取,或者使用特定的库。例如,对于CSV文件,你可能会使用pandas库;对于JSON文件,则可以使用json模块直接读取并将其加载到Python中。

处理JSON和YAML数据

上一节我们提到了文件读取的基本概念,本节中我们来看看如何处理结构化的数据格式。

我们将介绍如何将JSON和YAML数据加载到Python的数据结构中,以便你能够对这些结构进行操作。同时,我们也会学习如何将Python数据结构中的数据转换为YAML格式,并保存到文件中。

以下是处理这些格式的核心步骤:

  • 加载JSON:使用json.load()json.loads()函数。
  • 加载YAML:通常需要使用第三方库,如PyYAMLyaml.safe_load()函数。
  • Python转YAML:使用yaml.dump()函数将Python对象序列化为YAML字符串。
  • 保存到文件:使用标准的文件写入操作,将序列化后的字符串写入文件。

总结

本节课中我们一起学习了Python处理不同数据源和格式的基础知识。我们了解了读取文本文件的通用思路,并掌握了使用特定库(如pandas处理CSV,json模块处理JSON)来加载数据的方法。最后,我们还探讨了如何在Python数据结构与YAML格式之间进行转换和保存。掌握这些技能是进行数据操作和分析的重要第一步。

083:从文件与文件路径加载数据到Python 📂

在本节课中,我们将要学习如何在Python中从文件和文件路径加载数据。根据您要处理的数据类型以及Python或其库的支持情况,这个过程可能会有些技巧性。

概述

我们将从最简单的纯文本文件读取开始,逐步介绍不同文件格式(如SQL、CSV)的加载方法,并比较手动管理与使用上下文管理器管理文件资源的区别。最后,我们会了解如何使用像Pandas这样的库来高效处理结构化数据文件。

从纯文本文件读取数据

在Python中读取常规的纯文本文件是最基础的起点。您可以使用相对路径或绝对路径来指定文件。为了确保打开正确的文件,使用绝对路径是更可靠的做法。

以下是一个简单的示例,展示了如何打开并读取一个名为populate.sql的SQL文件。

# 打开文件
sql_file = open('populate.sql')
# 读取整个文件内容到一个字符串
sql_contents = sql_file.read()
# 处理完毕后,必须关闭文件
sql_file.close()

这种方法的问题在于,您必须记住在处理完文件后手动关闭它。如果不关闭,文件句柄会一直占用系统资源。

逐行读取文件内容

如果您需要逐行处理文件内容,使用read()方法将整个内容作为一个字符串返回可能不太方便。这时,readlines()方法是一个更好的选择。

以下是使用readlines()方法的示例:

# 打开文件
sql_file = open('populate.sql')
# 读取所有行到一个列表中
sql_lines = sql_file.readlines()
# 关闭文件
sql_file.close()

readlines()方法返回一个列表,其中每个元素都是文件中的一行(以换行符分隔)。这使得逐行处理数据变得非常简单。

使用上下文管理器管理文件

为了避免忘记关闭文件,Python提供了上下文管理器,它可以通过with语句自动管理资源的打开和关闭。

以下是使用上下文管理器读取文件的示例:

# 使用with语句自动管理文件
with open('populate.sql') as sql_file:
    sql_contents = sql_file.readlines()
# 当退出with代码块时,文件会自动关闭

使用with open(...) as ...:的语法,可以确保文件在代码块执行完毕后被正确关闭,无需手动调用close()方法。这是一种更安全、更简洁的文件操作方式。

使用Pandas库读取CSV文件

对于特定格式的文件,如CSV(逗号分隔值),使用专门的库(如Pandas)通常更加高效和方便。Pandas是一个强大的数据分析库,可以轻松处理大型数据集。

首先,需要导入Pandas库。通常我们使用pd作为其别名。

import pandas as pd

导入库后,可以使用read_csv()函数来读取CSV文件。

以下是读取名为wine_rating_small.csv的CSV文件的示例:

# 使用Pandas读取CSV文件
df = pd.read_csv('wine_rating_small.csv')

读取后,数据被加载到一个称为DataFrame的Pandas数据结构中。您可以使用head()方法快速查看数据的前几行。

# 查看数据的前几行
print(df.head())

这种方法不仅代码简洁,而且Pandas提供了丰富的数据操作功能,非常适合处理表格型数据。

总结

本节课中,我们一起学习了在Python中从文件和文件路径加载数据的几种核心方法:

  1. 读取纯文本文件:使用open()read()readlines()函数,并注意手动关闭文件或使用上下文管理器自动管理。
  2. 使用上下文管理器:通过with语句安全地处理文件,避免资源泄漏。
  3. 利用专业库处理特定格式:例如使用Pandas的read_csv()函数高效读取和处理CSV文件。

选择哪种方法取决于您的具体需求:处理通用文本文件,还是处理具有特定结构的格式化数据。掌握这些基础技能,是进行有效数据工程处理的第一步。

084:Python中的JSON数据处理 🐍📄

在本节课中,我们将学习如何在Python中处理JSON数据。JSON是一种常见的数据交换格式,Python内置的json模块可以方便地在JSON字符串和Python数据结构之间进行转换。我们将学习如何将Python对象序列化为JSON字符串,以及如何将JSON字符串反序列化为Python对象。


Python数据结构与JSON的相似性

在Python中处理JSON时,你可能会发现许多JSON数据结构与Python的数据结构看起来非常相似。例如,一个Python字典与JSON对象在结构上基本相同,只有一些细微的差别。

接下来,我们将使用Python的json模块来探索这些操作。


将Python对象转换为JSON字符串

首先,我们需要导入json模块。假设我们有一个简单的Python字典,我们想将其转换为一个有效的JSON字符串。

以下是转换的步骤:

  1. 导入json模块。
  2. 创建一个Python字典。
  3. 使用json.dumps()函数将字典序列化为JSON格式的字符串。

代码示例:

import json

# 创建一个Python字典
data = {
    "name": "Alfredo",
    "other_data": []  # 一个空列表
}

# 使用json.dumps()将字典转换为JSON字符串
json_string = json.dumps(data)
print(json_string)

运行上述代码,你将得到一个JSON格式的字符串。这个字符串看起来与我们创建的Python字典非常相似,这正是因为两者的结构高度一致。


注意JSON与Python的细微差别

虽然JSON和Python数据结构很相似,但存在一些关键区别。例如,Python中的布尔值TrueFalse在JSON中会转换为小写的truefalse

让我们通过一个例子来观察这个区别:

代码示例:

import json

data_with_bool = {
    "name": "Test",
    "valid": True  # Python中的布尔值
}

json_output = json.dumps(data_with_bool)
print(json_output)  # 输出中"valid"的值将是小写的"true"

在这个例子中,Python的True在生成的JSON字符串中变成了true。了解这些差异对于正确处理数据至关重要。


将JSON字符串转换为Python对象

上一节我们介绍了如何将Python对象转换为JSON字符串。本节中,我们来看看反向操作:如何将JSON字符串加载回Python数据结构。

假设我们收到了一个JSON格式的字符串,并希望在Python程序中使用它。我们可以使用json.loads()函数(注意是loads,代表“load string”,而不是复数形式的loads)来实现。

以下是转换的步骤:

  1. 准备一个JSON格式的字符串。
  2. 使用json.loads()函数将其解析为Python对象。

代码示例:

import json

# 一个JSON格式的字符串
json_output = '{"name": "Alfredo", "valid": true, "count": 1}'

# 使用json.loads()将字符串转换为Python字典
loaded_json = json.loads(json_output)

# 检查类型,确认它是一个字典
print(type(loaded_json))

# 现在可以像操作普通字典一样操作它
print(loaded_json["valid"])  # 输出:True

现在,原本是字符串的json_output已经变成了一个有效的Python字典,我们可以像访问普通字典一样访问其中的数据。注意,JSON中的true被转换回了Python的True


从JSON文件加载数据

在实际工作中,JSON数据通常存储在文件中。接下来,我们学习如何直接从.json文件中读取数据并将其加载到Python中。

我们将使用json.load()函数(注意是load,不是loads),它可以直接从文件对象中读取并解析JSON数据。

以下是操作步骤:

  1. 使用open()函数以读取模式打开JSON文件。
  2. 将打开的文件对象传递给json.load()函数。
  3. 函数会返回解析后的Python对象(通常是字典或列表)。

代码示例:

import json

# 假设当前目录下有一个名为 ‘wine-ratings.json‘ 的文件
with open(‘wine-ratings.json‘, ‘r‘) as f:  # ‘f‘ 是文件对象的简写
    loaded_json = json.load(f)  # 注意是 json.load(),不是 loads

# 现在 loaded_json 包含了文件中的所有数据
print(type(loaded_json))
print(len(loaded_json))  # 查看数据量,例如可能包含774条记录

通过这段代码,wine-ratings.json文件中的所有内容都被加载到了变量loaded_json中,并转换成了对应的Python数据结构(如字典的列表),方便你进行进一步的分析和处理。


总结

本节课中我们一起学习了Python中处理JSON数据的核心方法。我们了解到JSON结构与Python字典、列表的相似性,并掌握了两个关键操作:使用json.dumps()将Python对象序列化为JSON字符串,以及使用json.loads()json.load()将JSON字符串或文件反序列化为Python对象。记住,JSON中的布尔值(true/false)和null与Python的True/False/None存在大小写区别。掌握这些知识,你就能在数据工程任务中自如地处理JSON格式的数据了。

085:将Python数据保存到磁盘 💾

在本节课中,我们将学习如何使用Python的json模块,将Python数据结构(如字典)保存到磁盘上的JSON文件中。这是一个将程序中的数据持久化存储的基础操作。


上一节我们介绍了如何从文件中读取JSON数据。本节中我们来看看如何将Python数据写入文件。

首先,我们需要导入json模块。

import json

接下来,我们创建一个Python字典作为示例数据。

data = {}

以下是写入JSON文件的核心步骤:

我们将使用open()函数打开一个文件用于写入。这需要一个特殊的标志'w',它代表“写入”模式。然后,我们使用json.dump()方法将数据对象写入这个已打开的文件。

with open('sample_data.json', 'w') as f:
    json.dump(data, f)

让我们详细解释一下这段代码:

  1. with open('sample_data.json', 'w') as f: 这行代码打开(或创建)一个名为sample_data.json的文件。'w'标志表示我们将写入这个文件。f是分配给这个已打开文件的变量名。
  2. json.dump(data, f): 这行代码调用json.dump()函数。它接收两个参数:第一个是要保存的Python数据对象(这里是data字典),第二个是已打开的文件对象(f)。dump()函数负责将数据转换为JSON格式并写入文件。

运行这段代码后,会在当前目录生成一个sample_data.json文件。目前,由于data字典是空的,文件内容将是一个空对象{}

现在,让我们用一些实际数据来填充字典,并再次保存。

data = {
    "name": "Alfredo",
    "last_name": "Deza"
}

再次运行写入代码。请注意,由于我们使用了'w'模式,它会覆盖之前文件的内容。新的sample_data.json文件将包含转换后的JSON数据。

JSON格式与Python格式有一些区别。例如,Python中的布尔值True在JSON中会转换为小写的truejson.dump()方法会自动处理这些转换。

data_with_bool = {"active": True}
# 保存后,JSON文件中的内容将是 {"active": true}

本节课中我们一起学习了如何使用json.dump()方法将Python数据结构保存到JSON文件。你只需要提供一个数据对象和一个以写入模式打开的文件对象即可。这是实现数据持久化、在不同程序间共享数据或为后续处理保存中间结果的关键技能。

086:Python数据持久化与加载回顾 📚

在本节课中,我们将回顾Python中数据持久化与加载的核心概念。我们将学习如何从文件读取数据到Python,以及如何将Python中的数据保存回文件。这包括处理JSON格式和CSV格式的数据,这些是数据工程中非常常见的任务。

从文件加载数据到Python

上一节我们介绍了JSON的基本操作。本节中我们来看看如何将文件中的数据加载到Python程序中。这是数据处理的基础,因为您需要有能力从外部文件读取信息。

  • 您可以将文件中的数据读取到Python中。
  • 您可以在Python中处理这些来自文件的数据。

处理JSON数据

我们已经学习了如何操作JSON。这包括从JSON文件加载数据,以及将Python中的数据保存到JSON文件。

以下是处理JSON数据的关键步骤:

  • 从JSON文件加载数据。
  • 将来自Python的数据保存到JSON文件。
  • 在Python中将相邻的数据结构(如字典、列表)转换为JSON字符串。

使用Pandas库处理CSV数据

最后,我们还简要介绍了如何使用Pandas库来处理CSV文件。Pandas是Python中用于数据分析和操作的一个强大库。

本节课中我们一起学习了Python数据持久化的核心流程:从文件(特别是JSON和CSV格式)读取数据到Python环境,在Python中进行处理,以及将处理后的数据保存回文件。掌握这些技能是进行有效数据工程工作的基础。

087:Python脚本与SQL入门 🐍💾

在本节课中,我们将学习如何结合使用Python脚本技术与SQL。具体内容包括:创建一个数据库、存储数据,以及从Python中执行SQL来查询这些数据。我们将使用小巧但功能强大的SQLite数据库,并通过Python与其建立连接。最后,我们将介绍一些关键的SQL语句和命令,以便高效地从数据库中筛选和提取数据。


使用Python连接SQLite数据库

上一节我们概述了课程目标,本节中我们来看看如何从Python连接到SQLite数据库。SQLite是一个轻量级的数据库引擎,非常适合学习和原型开发。Python标准库中的 sqlite3 模块提供了与SQLite数据库交互的接口。

以下是建立数据库连接并创建一个游标对象的基本步骤:

import sqlite3

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/duke-py-bash-sql/img/e99b3406bea788e8c878c2decb8fb789_0.png)

# 连接到数据库(如果不存在则会自动创建)
connection = sqlite3.connect('my_database.db')

# 创建一个游标对象,用于执行SQL语句
cursor = connection.cursor()

创建表与插入数据

成功连接数据库后,下一步是创建表结构并向其中插入数据。表是数据库中存储数据的基本单元,类似于Excel中的一个工作表。

以下是创建表和插入数据的示例:

# 创建一个名为‘users’的表
create_table_sql = """
CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER
);
"""
cursor.execute(create_table_sql)

# 向‘users’表中插入一行数据
insert_sql = "INSERT INTO users (name, age) VALUES (?, ?)"
cursor.execute(insert_sql, ('Alice', 30))

# 提交更改,使插入操作生效
connection.commit()

使用SQL查询数据

表中有数据后,我们就可以使用SQL查询语句来检索信息。SQL的 SELECT 语句是用于从数据库获取数据的主要工具。

以下是一些基本的查询示例:

# 查询‘users’表中的所有数据
cursor.execute("SELECT * FROM users")
all_users = cursor.fetchall()
print(all_users)

# 查询特定条件的数据,例如年龄大于25的用户
cursor.execute("SELECT * FROM users WHERE age > ?", (25,))
users_over_25 = cursor.fetchall()
print(users_over_25)

核心SQL语句与命令

为了更高效地操作数据库,我们需要掌握几个核心的SQL命令。这些命令构成了与数据库交互的基础。

以下是几个必备的SQL语句:

  1. CREATE TABLE:用于创建新表。

    CREATE TABLE table_name (column1 datatype, column2 datatype, ...);
    
  2. INSERT INTO:用于向表中插入新行。

    INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...);
    
  3. SELECT:用于从表中查询数据。

    SELECT column1, column2 FROM table_name WHERE condition;
    
  4. UPDATE:用于修改表中现有的记录。

    UPDATE table_name SET column1 = value1 WHERE condition;
    
  5. DELETE:用于从表中删除记录。

    DELETE FROM table_name WHERE condition;
    

关闭数据库连接

完成所有数据库操作后,务必正确关闭连接。这是一个重要的好习惯,可以确保所有数据被正确保存并释放资源。

关闭连接的代码如下:

# 关闭游标和数据库连接
cursor.close()
connection.close()

本节课中我们一起学习了如何使用Python脚本操作SQLite数据库。我们从建立连接开始,逐步完成了创建表、插入数据、执行查询等核心操作,并介绍了关键的SQL语句。最后,我们强调了在操作完成后关闭连接的重要性。掌握这些基础技能,是迈向更复杂数据工程任务的重要一步。

088:Python脚本编程入门 🐍

在本节课中,我们将学习如何将Python知识整合到脚本中,以便在命令行中执行数据收集和处理等实用任务。我们将介绍创建Python脚本的基础知识和关键注意事项。


一旦你开始熟悉Python,你可能会希望利用它的脚本功能。

Python被称为脚本语言,因此你将要做的事情之一就是整合所有学到的知识。

将这些知识放入一个脚本中,并开始在命令行中执行数据收集和数据处理等有用的任务。

在本节中,我们将介绍一些基础知识。

以下是使用Python和创建Python脚本时需要了解和注意的一些事项。


上一节我们介绍了Python脚本编程的概述,本节中我们来看看创建脚本的具体步骤和核心概念。

以下是创建和运行Python脚本的基本步骤:

  1. 创建脚本文件:使用任何文本编辑器创建一个以 .py 为扩展名的文件,例如 myscript.py
  2. 编写代码:在文件中编写你的Python代码。
  3. 添加Shebang(可选但推荐):在脚本文件的第一行,可以添加一行特殊的注释,告诉系统使用哪个解释器来执行这个脚本。这对于在Unix/Linux/Mac系统上直接运行脚本很有用。
    #!/usr/bin/env python3
    
  4. 使脚本可执行(Unix/Linux/Mac):在终端中,使用 chmod 命令为脚本文件添加执行权限。
    chmod +x myscript.py
    
  5. 运行脚本:有多种方式可以运行脚本。
    • 在命令行中直接使用Python解释器:
      python3 myscript.py
      
    • 如果已经添加了Shebang并赋予了执行权限,在Unix/Linux/Mac上可以直接运行:
      ./myscript.py
      

上一节我们了解了如何创建和运行脚本,本节中我们来看看编写脚本时的一些核心概念和最佳实践。

以下是编写有效Python脚本时需要掌握的几个关键点:

  • 命令行参数:脚本通常需要接收外部输入。可以使用 sys.argv 列表来获取在命令行中传递给脚本的参数。
    import sys
    # sys.argv[0] 是脚本名称
    # sys.argv[1] 是第一个参数,依此类推
    
  • 标准输入/输出:使用 input() 函数接收用户输入,使用 print() 函数输出结果。这对于与命令行交互非常有用。
  • 文件操作:脚本经常需要读取或写入文件。使用 open() 函数和文件对象的方法(如 .read().write().readlines())来处理文件。
    with open('data.txt', 'r') as file:
        content = file.read()
    
  • 错误处理:使用 try...except 块来捕获和处理运行时可能出现的错误,使脚本更加健壮。
    try:
        # 可能出错的代码
        result = 10 / 0
    except ZeroDivisionError:
        print("不能除以零。")
    
  • 模块化:将代码组织成函数和类。这不仅使代码更清晰、更易维护,也方便在其他脚本中复用代码。
  • 使用标准库和第三方库:Python拥有丰富的标准库(如 ossysjsoncsv)和第三方库(如 requestspandas)。在脚本中导入和使用这些库可以极大地扩展脚本的功能。

本节课中我们一起学习了Python脚本编程的基础。我们了解了如何创建、运行Python脚本,并探讨了编写脚本时涉及的核心概念,如处理命令行参数、文件操作和错误处理。掌握这些技能将使你能够利用Python自动化任务并高效地处理数据。

089:在Python中创建模块化脚本 🐍

在本节课中,我们将要学习如何在Python中创建模块化脚本。我们将重点理解 __name__ 变量的作用,以及如何利用它来编写既能作为独立脚本运行,又能作为模块被其他文件导入的Python代码。

理解 __name__ 变量

在Python中,每个模块都有一个内置的变量 __name__。这个变量的值取决于你如何运行这个Python文件。

  • 当你直接运行一个Python文件时,该文件中的 __name__ 变量会被设置为字符串 "__main__"
  • 当一个Python文件被作为模块导入到另一个文件中时,该文件中的 __name__ 变量会被设置为该模块的名字(通常是文件名去掉 .py 后缀)。

这个机制是编写模块化代码的关键。

一个简单的示例

让我们通过一个简单的例子来观察 __name__ 的行为。假设我们有一个名为 main.py 的文件,其内容如下:

print(f"The name of this module is {__name__}")

如果我们直接在终端运行这个文件:

python main.py

输出将会是:

The name of this module is __main__

这是因为我们直接运行了 main.py,所以它的 __name__ 被设置为 "__main__"

现在,我们创建另一个名为 other.py 的文件,它导入了 main 模块:

import main

当我们运行 other.py 时:

python other.py

输出将会是:

The name of this module is main

此时,main.py 是被导入的模块,因此它的 __name__ 是其模块名 "main",而不是 "__main__"

编写模块化脚本的标准模式

理解了 __name__ 的差异后,我们就可以编写更健壮的代码。我们不希望模块在被导入时,其顶层的代码(如打印语句、数据处理)自动执行,产生“副作用”。我们通常希望这些代码只在文件被直接运行时才执行。

以下是实现这一点的标准模式:

  1. 将主要逻辑封装在一个函数中(通常命名为 mainprincipal)。
  2. 使用 if __name__ == "__main__": 条件判断,来确保这个函数只在文件被直接运行时才被调用。

让我们重构 main.py 文件:

def principal():
    """这是主要的执行函数。"""
    print(f"The name of this module is {__name__}")
    # 在这里编写你的主要处理逻辑
    # 例如:预处理数据、调用其他函数等

if __name__ == "__main__":
    principal()

现在,当我们直接运行 main.py 时,principal() 函数会被调用,输出和之前一样。但是,当 main.pyother.py 导入时,principal() 函数不会自动执行,因为我们没有在 other.py 中调用它。这避免了意外的副作用。

总结

本节课中我们一起学习了Python模块化脚本的核心概念。

  • 我们了解了 __name__ 这个特殊变量,它根据文件是直接运行还是被导入而具有不同的值("__main__" 或模块名)。
  • 我们学习了编写模块化脚本的标准模式:将主要逻辑放入一个函数(如 main),并使用 if __name__ == "__main__": 这个条件判断来调用它。
  • 这种模式使得你的Python文件既可以作为一个独立的脚本运行,也可以安全地作为一个功能模块被其他代码导入和使用,是编写可复用、结构清晰的Python代码的基础。

090:使用Python脚本遍历文件系统

在本节课中,我们将学习如何使用Python的os模块来编写一个脚本,该脚本能够遍历计算机的文件系统,列出目录和文件。这是数据工程中处理本地数据文件的常见任务。

概述

上一节我们介绍了如何编写一个基本的Python脚本。本节中,我们来看看如何利用Python内置的os模块,特别是其中的os.walk()函数,来遍历文件系统中的目录和文件。

导入模块

首先,我们需要导入os模块,它提供了与操作系统交互的实用工具。

import os

使用 os.walk() 函数

os.walk()函数是遍历目录树的核心工具。它生成一个三元组(root, directories, files),分别代表当前目录路径、该目录下的子目录列表和文件列表。

以下是使用os.walk()的基本结构:

for root, directories, files in os.walk('/path/to/start'):
    # 处理逻辑

遍历并打印文件

为了理解os.walk()的输出,我们可以先编写一个简单的脚本来打印遍历过程中发现的所有文件及其完整路径。

import os

def main():
    start_path = '/users/Alfredo/.files'  # 请替换为你的实际路径
    for root, directories, files in os.walk(start_path):
        for file in files:
            absolute_path = os.path.join(root, file)
            print(f"文件路径: {absolute_path}")

if __name__ == "__main__":
    main()

重要提示:必须使用绝对路径(如/users/Alfredo/.files),而不能使用缩写路径(如~/.files),否则脚本可能无法正确运行。

遍历并打印目录

如果你想专门处理目录,可以循环遍历directories列表。

import os

def main():
    start_path = '/users/Alfredo/.files'  # 请替换为你的实际路径
    for root, directories, files in os.walk(start_path):
        for directory in directories:
            dir_path = os.path.join(root, directory)
            print(f"目录路径: {dir_path}")

if __name__ == "__main__":
    main()

总结

本节课中我们一起学习了如何使用Python的os.walk()函数来遍历文件系统。我们掌握了:

  1. 导入os模块。
  2. 使用os.walk()循环获取目录树的根路径、子目录和文件。
  3. 通过os.path.join()组合出文件或目录的绝对路径。
  4. 分别编写了遍历文件和遍历目录的脚本。

记住,始终使用绝对路径是确保脚本在不同环境下都能正确工作的关键。

091:Python脚本语言核心要点

在本节课中,我们将回顾Python脚本语言的一些关键事实和特性。这些知识对于创建脚本模块至关重要,特别是当您希望在数据工程流程中编写强大脚本时。

脚本模块的命名约定

上一节我们介绍了脚本的基本概念,本节中我们来看看Python在幕后使用的一些命名约定。理解这些约定有助于我们在从命令行调用特定函数时利用这些规则,并避免一些副作用。

以下是Python中与脚本执行相关的几个核心命名约定:

  • __name__: 这是一个特殊的Python变量。当Python文件作为脚本直接运行时,__name__的值会被设置为"__main__"
  • if __name__ == "__main__":: 这是一个常见的代码块。它用于判断当前文件是作为主程序运行,还是被导入为模块。这可以防止模块中的代码在被导入时自动执行。
# 示例:使用 __name__ 判断执行上下文
def main():
    print("这是主函数")

if __name__ == "__main__":
    # 只有当此文件被直接运行时,下面的代码才会执行
    main()

利用命名约定控制脚本行为

了解了这些命名规则后,我们可以利用它们来优化脚本。主要目的是将脚本的“可执行”部分与“可导入”的功能部分分离。

以下是实现这种分离的典型步骤:

  1. 将核心逻辑封装在函数或类中。
  2. 在文件末尾使用if __name__ == "__main__":条件块。
  3. 在该条件块内调用主函数,以启动脚本执行。

这种模式确保了您的代码既可以被其他脚本作为模块导入和复用,也可以独立运行。

命令行执行与脚本优化

当从命令行运行Python脚本时,上述模式显得尤为重要。它允许您清晰地定义脚本的入口点。

例如,一个数据处理脚本可以这样组织:

# data_processor.py
import sys

def process_data(input_file):
    # 这里是数据处理的核心逻辑
    with open(input_file, 'r') as f:
        data = f.read()
    processed_data = data.upper()  # 假设的处理步骤
    return processed_data

def main():
    # 从命令行参数获取输入文件
    if len(sys.argv) < 2:
        print("请提供输入文件路径")
        sys.exit(1)
    input_file = sys.argv[1]
    result = process_data(input_file)
    print(result)

if __name__ == "__main__":
    main()

您可以在命令行中通过以下方式运行它:

python data_processor.py my_input.txt

总结

本节课中我们一起学习了Python脚本编程的几个核心要点。我们探讨了__name__变量的作用以及if __name__ == "__main__":这一惯用法的意义。通过将代码组织为可导入的模块和可执行的脚本,您可以创建出更清晰、更强大、更易于维护的数据工程脚本。现在,您已经掌握了开始构建高效数据流程脚本所需的基础知识。

092:嵌入式数据库入门 🗄️

在本节课中,我们将学习嵌入式数据库的概念,并重点介绍如何使用 SQLite。SQLite 是一个轻量级、可靠且功能强大的数据库,其操作方式与常规数据库类似,但有一些细微差别。我们将学习如何通过命令行,特别是通过 Python 来操作 SQLite 数据库,以便您能轻松地在实际项目中使用数据库,而无需配置更复杂的大型数据库系统。

什么是嵌入式数据库?

上一节我们明确了本课的学习目标,本节中我们来看看嵌入式数据库的核心定义。嵌入式数据库是一种集成在应用程序内部的数据库系统。它不需要独立的服务器进程,所有数据库操作都在应用程序进程中完成。SQLite 是嵌入式数据库的典型代表。

以下是嵌入式数据库的几个关键特点:

  • 无服务器:无需安装、配置或管理独立的数据库服务器。
  • 零配置:开箱即用,数据库以单个文件形式存储在磁盘上。
  • 事务性:支持 ACID 事务,确保数据的一致性和可靠性。
  • 跨平台:数据库文件可在不同操作系统间自由移动和使用。

为什么选择 SQLite?

了解了嵌入式数据库的基本概念后,我们自然会问:为什么选择 SQLite 作为学习对象?SQLite 因其极简的设计和广泛的适用性而备受青睐。它被广泛应用于移动应用、桌面软件、乃至某些特定场景的 Web 后端,是初学数据库操作的理想选择。

以下是 SQLite 的一些主要优势:

  • 轻量级:整个库体积小巧,易于集成。
  • 自包含:几乎不需要外部依赖。
  • 公共领域:代码开源,可免费用于任何目的。
  • 标准兼容:支持大多数 SQL 标准,学习成本低。

通过 Python 连接 SQLite

现在,我们已经知道了 SQLite 是什么以及为何要使用它。接下来,我们将进入实践环节,学习如何使用 Python 来连接和操作 SQLite 数据库。Python 的标准库 sqlite3 提供了与 SQLite 交互的全部功能。

以下是通过 Python 使用 SQLite 的基本步骤:

  1. 导入模块:首先需要导入 sqlite3 库。

    import sqlite3
    
  2. 建立连接:连接到数据库文件。如果文件不存在,SQLite 会自动创建它。

    conn = sqlite3.connect('my_database.db')
    
  3. 创建游标:游标对象用于执行 SQL 语句并获取结果。

    cursor = conn.cursor()
    
  4. 执行 SQL:使用游标执行创建表、插入数据、查询等操作。

    cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
    cursor.execute("INSERT INTO users (name) VALUES ('Alice')")
    
  5. 提交与关闭:完成数据修改后,必须提交事务。最后,关闭连接。

    conn.commit()
    conn.close()
    

核心操作示例

掌握了连接数据库的方法后,我们通过一个简单的例子来演示完整的操作流程。这将帮助您理解如何将各个步骤组合起来,完成一个基本的数据持久化任务。

假设我们要创建一个存储用户信息的表,并插入一条数据,然后查询它。

import sqlite3

# 1. 连接到数据库(如果不存在则创建)
conn = sqlite3.connect('example.db')

# 2. 创建一个游标对象
cur = conn.cursor()

# 3. 创建一个表
cur.execute('''CREATE TABLE IF NOT EXISTS users
               (id INTEGER PRIMARY KEY, username TEXT, email TEXT)''')

# 4. 插入一条数据
cur.execute("INSERT INTO users (username, email) VALUES (?, ?)", ('john_doe', 'john@example.com'))

# 5. 提交事务,保存更改
conn.commit()

# 6. 查询所有用户
cur.execute("SELECT * FROM users")
rows = cur.fetchall()
for row in rows:
    print(row)

# 7. 关闭连接
conn.close()

总结

本节课中,我们一起学习了嵌入式数据库的基础知识。我们从嵌入式数据库的概念入手,探讨了 SQLite 作为其经典实现的优势。随后,我们重点讲解了如何通过 Python 的 sqlite3 模块来连接 SQLite 数据库、执行基本的 SQL 操作(包括创建表、插入数据和查询数据),并完成了一个完整的代码示例。通过本课的学习,您已经掌握了在 Python 项目中使用轻量级数据库进行数据存储和检索的基本技能,这为处理更复杂的数据工程任务奠定了坚实的基础。

093:什么是SQLite 🗄️

在本节课中,我们将要学习SQLite数据库的核心概念。我们将探讨SQLite与其他主流数据库(如PostgreSQL或MySQL)的关键区别,并理解其作为“嵌入式数据库”的含义。

SQLite的本质

上一节我们介绍了数据工程中数据库的基本角色,本节中我们来看看SQLite的具体特点。

SQLite被称为嵌入式数据库,它与PostgreSQL或MySQL等主流数据库存在一些关键差异。

以下是SQLite的几个核心特征:

  • 数据库即文件:一个SQLite数据库在文件系统中直接体现为一个单一的文件。这是一个显著区别,因为像PostgreSQL这样的大型数据库,你无法在文件系统中找到一个直接对应的单一数据库文件。
  • 无客户端-服务器架构:当你使用PostgreSQL或MySQL时,你需要一个数据库服务器,然后通过客户端(通常通过网络)连接到它。SQLite没有服务器进程,你直接连接到一个数据库文件。
  • 零配置:像SQLite这样的嵌入式数据库无需任何配置。你不需要进行任何特定的设置。正如我们将在本课中看到的,从Python连接、设置以及与SQLite的交互都非常直接。

使用SQLite的优势

了解了SQLite的基本特性后,我们来看看它带来的便利。

从Python与SQLite的交互非常直接。虽然有一些需要注意的事项,我们接下来会看到,但总体上一切都很简单,你无需担心太多。这使得它成为处理数据、使用SQL、存储和检索数据、运行查询以及尝试使用数据库的绝佳选择。

本节课中我们一起学习了SQLite作为嵌入式数据库的核心概念,包括其以文件形式存在、无服务器架构和零配置的特点。这些特性使其成为学习和进行轻量级数据操作的理想工具。

094:在Python中创建与连接SQLite数据库

在本节课中,我们将学习如何使用Python创建和连接SQLite数据库。SQLite是一个轻量级的嵌入式数据库,非常适合在Python项目中进行数据存储和管理。我们将从导入必要的模块开始,逐步完成创建数据库、建立连接、定义表结构以及执行SQL命令的完整流程。

导入SQLite模块

首先,我们需要导入Python内置的sqlite3模块。这个模块是Python标准库的一部分,因此你无需安装任何额外的库。

import sqlite3

sqlite3模块在大多数Python版本中都可用,这使得跨环境工作变得非常方便。

建立数据库连接

导入模块后,下一步是建立与数据库的连接。我们可以选择创建内存数据库或文件数据库。

创建内存数据库

内存数据库是一种临时数据库,程序运行结束后数据不会持久保存。创建方法如下:

connection = sqlite3.connect(':memory:')

使用:memory:作为参数可以创建一个内存数据库。这在需要快速测试或处理临时数据时非常有用。

创建文件数据库

如果你希望数据持久化保存到文件系统中,可以指定一个文件名来创建数据库文件。

connection = sqlite3.connect('sample.db')

以上代码会创建一个名为sample.db的数据库文件。你可以根据需要为数据库文件命名。

无论是内存数据库还是文件数据库,后续的操作步骤都是相同的。唯一的区别在于数据是否持久化存储。

定义表结构

建立连接后,我们可以定义数据库中的表结构。这需要通过编写SQL的CREATE TABLE语句来完成。

以下是一个创建people表的示例SQL语句。该表包含idnamesurname三个字段。

CREATE TABLE people (
    id INTEGER PRIMARY KEY,
    name TEXT,
    surname TEXT
)

在这个语句中:

  • id字段被定义为整数类型(INTEGER)并设置为主键(PRIMARY KEY)。主键能确保每条记录的唯一性,虽然这不是强制要求,但通常是个好习惯。
  • namesurname字段都被定义为文本类型(TEXT),用于存储字符串数据。

执行SQL命令

定义了SQL语句后,我们需要通过Python来执行它。这涉及到两个关键对象:游标(Cursor)提交(Commit)

创建游标

游标是数据库编程中的一个核心概念,它像一个指针,允许我们遍历和执行SQL命令。

cursor = connection.cursor()

我们通过连接对象的.cursor()方法来创建一个游标。

执行查询

创建游标后,使用它的.execute()方法来运行我们之前写好的SQL语句。

cursor.execute("""
    CREATE TABLE people (
        id INTEGER PRIMARY KEY,
        name TEXT,
        surname TEXT
    )
""")

这里,我们将创建people表的SQL语句传递给.execute()方法。

提交更改

执行SQL命令后,必须调用连接对象的.commit()方法,才能将更改(例如创建新表)永久保存到数据库中。

connection.commit()

如果不执行提交操作,当程序结束时,所有未提交的更改都会丢失。

验证数据库创建

现在,我们可以运行整个Python脚本。脚本本身可能不会在控制台输出任何内容,但它的作用是创建数据库文件。

运行脚本后,你会在项目目录中看到一个名为sample.db的新文件。为了验证数据库和表是否成功创建,我们可以使用SQLite的命令行工具。

打开终端,导航到数据库文件所在的目录,然后输入以下命令:

sqlite3 sample.db

这将启动SQLite命令行界面并连接到sample.db数据库。连接成功后,你可以输入SQL命令来查看或操作数据。例如,输入.tables命令可以列出数据库中的所有表,你应该能看到刚刚创建的people表。

通过命令行工具,你可以直接与数据库交互,执行查询、插入数据等操作,这为调试和验证提供了便利。

总结

本节课中,我们一起学习了在Python中操作SQLite数据库的基础知识。我们首先导入了sqlite3模块,然后建立了与数据库的连接,并区分了内存数据库和文件数据库。接着,我们编写了SQL语句来定义表结构,并通过创建游标、执行查询和提交更改这一系列步骤,成功在数据库中创建了表。最后,我们使用SQLite命令行工具验证了数据库的创建结果。掌握这些步骤是使用Python进行数据存储和管理的坚实基础。

095:在Python中保存与查询SQLite数据库

概述

在本节课中,我们将学习如何使用Python与SQLite数据库进行交互。具体内容包括:如何生成模拟数据,如何将数据插入到SQLite数据库表中,以及如何从数据库中查询数据。我们将使用Python的sqlite3库和Faker库来完成这些任务。

生成模拟数据

上一节我们介绍了如何创建SQLite数据库和表。本节中,我们来看看如何向表中填充数据。首先,我们需要一些数据。为了生成随机的姓名数据,我们将使用一个名为Faker的第三方库。

以下是安装和初始化Faker库的步骤:

  1. 在命令行中运行 pip install faker 来安装库。
  2. 在Python代码中导入并初始化Faker
from faker import Faker
fake = Faker()

接下来,我们生成100个随机的全名。由于我们的数据库表设计为存储“名”和“姓”两个字段,我们需要确保生成的数据符合这个格式。

# 生成100个随机全名
names = [fake.name().split() for i in range(100)]
# 过滤出恰好包含两个部分(名和姓)的姓名
names = [name for name in names if len(name) == 2]

连接数据库并插入数据

现在我们已经有了数据,下一步是将其插入到之前创建的数据库表中。首先,我们需要建立与SQLite数据库文件的连接。

以下是建立数据库连接和定义插入查询的代码:

import sqlite3
# 连接到已存在的数据库文件
connection = sqlite3.connect('sample.db')
cursor = connection.cursor()

# 定义插入数据的SQL语句,使用问号作为占位符
insert_query = "INSERT INTO people (name, surname) VALUES (?, ?)"

为了将数据插入数据库,我们需要遍历姓名列表,并使用游标执行插入操作。最后,必须提交事务以使更改生效。

以下是执行数据插入的循环代码:

for name in names:
    cursor.execute(insert_query, name)
# 提交事务,保存所有插入的数据
connection.commit()

查询数据库验证结果

数据插入完成后,我们需要验证操作是否成功。最好的方法是从数据库中查询数据。

我们可以执行一个简单的SELECT查询来获取表中的记录。为了避免输出过多数据,我们使用LIMIT子句限制返回的行数。

以下是执行查询并打印结果的代码:

# 定义查询语句
select_query = "SELECT * FROM people LIMIT 10"
# 执行查询
cursor.execute(select_query)
# 获取并打印所有结果
results = cursor.fetchall()
for row in results:
    print(row)

运行此代码后,你将看到类似 (1, ‘John’, ‘Doe’) 的输出,其中第一个数字是SQLite自动生成的ID,后面两列是我们插入的名和姓。

在命令行中验证

为了进一步确认数据已正确存储,我们可以在系统命令行中直接使用sqlite3工具打开数据库文件并执行相同的查询。

操作步骤如下:

  1. 打开终端或命令行。
  2. 导航到数据库文件所在的目录。
  3. 运行命令 sqlite3 sample.db
  4. 在SQLite提示符下,输入 SELECT * FROM people LIMIT 10;

你应该能看到与Python中查询结果一致的数据,这证明了我们的Python代码成功地与SQLite数据库进行了交互。

总结

本节课中我们一起学习了使用Python操作SQLite数据库的核心流程。我们首先使用Faker库生成了模拟数据,然后通过sqlite3库连接到数据库,使用参数化查询安全地插入了数据,并通过查询验证了插入结果。最后,我们还对比了在命令行中直接查询数据库的结果,确保操作的一致性。掌握这些步骤是进行数据存储和检索的基础。

096:SQLite与Python回顾 🗂️

在本节课中,我们将回顾如何使用Python与SQLite数据库进行交互。我们将学习连接数据库、执行查询以及管理数据的基本操作。


上一节我们介绍了SQLite的基本概念,本节中我们来看看如何在实际的Python代码中运用这些知识。

SQLite是一个轻量级的嵌入式数据库,它不需要独立的服务器进程。这意味着你可以将整个数据库存储在一个单一的文件中。使用Python的sqlite3库,操作这些数据库变得非常简单直接。

我非常喜欢这种方式。它是我开始学习数据库、深入理解数据库的起点,因为我当时已经熟悉了Python。学习如何连接到一个新数据库、提取数据、添加更多数据并执行查询,让我在处理数据时成为了一个更出色的程序员。

SQLite是一个优秀的数据库,非常稳定且应用广泛。它几乎可以在任何地方轻松使用。如果某个系统没有预装SQLite,通常也能找到相应的软件包或安装程序。

现在,你已经准备好使用一个真正的数据库来更多地实践SQL和数据操作了。请注意,说它是“嵌入式”数据库并不意味着它不是“真正的”数据库。它是一个完全合规的数据库,你可以对其运行标准的SQL查询,它的行为与其他任何真正的数据库一样。


以下是使用Python操作SQLite的核心步骤:

  1. 导入库:首先需要导入Python内置的sqlite3模块。

    import sqlite3
    
  2. 连接数据库:使用connect()函数连接到一个数据库文件。如果文件不存在,它会被自动创建。

    conn = sqlite3.connect('example.db')
    
  3. 创建游标:游标对象用于执行SQL语句并获取结果。

    cursor = conn.cursor()
    
  4. 执行SQL命令:使用游标的execute()方法运行SQL语句,例如创建表或插入数据。

    cursor.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)''')
    
  5. 提交更改:对于修改数据库的操作(如INSERT, UPDATE, DELETE),需要调用连接对象的commit()方法来保存更改。

    conn.commit()
    
  6. 查询数据:使用execute()执行SELECT语句,然后通过fetchall()fetchone()等方法获取结果。

    cursor.execute("SELECT * FROM users")
    rows = cursor.fetchall()
    for row in rows:
        print(row)
    
  7. 关闭连接:完成所有操作后,记得关闭数据库连接。

    conn.close()
    

本节课中我们一起学习了如何利用Python的sqlite3库与SQLite数据库进行交互。我们回顾了从连接数据库、执行SQL命令到查询和提交数据的完整流程。SQLite作为一个强大而简单的工具,是初学者理解和实践数据库操作的绝佳起点。

097:Python中使用SQL查询入门 🐍

在本节课中,我们将学习如何在Python中执行基本的SQL查询。我们将从连接到数据库开始,然后学习如何运行查询、提取数据,并在Python端处理这些数据。课程还将探讨何时使用SQL语句更高效,以及何时在Python中进行处理更具可读性和可维护性。最后,我们会介绍SQL中的通配符搜索,以便根据已知的数据属性来提取匹配的信息。

连接到数据库与执行查询

上一节我们介绍了数据库连接的基础。本节中,我们来看看如何执行基本的SQL查询并提取数据。

连接到数据库后,你可以执行查询来提取所需的数据。以下是一个基本步骤示例:

import sqlite3

# 连接到数据库(示例中使用SQLite)
connection = sqlite3.connect('example.db')
cursor = connection.cursor()

# 执行一个简单的SELECT查询
cursor.execute("SELECT * FROM users")
results = cursor.fetchall()

for row in results:
    print(row)

# 关闭连接
cursor.close()
connection.close()

SQL查询与Python处理的权衡

有时查询会变得非常复杂。你需要学习在何时以及如何使用SQL或Python处理数据。

这主要归结为效率与可维护性之间的权衡。使用SQL语句通常效率更高,但代码的可读性和可维护性可能会稍差。其效率优势可以用一个简单的公式来理解:数据库处理量 ≈ 数据量 × 查询复杂度。在数据库服务器上执行复杂过滤和聚合,通常比将大量数据拉取到Python中处理要快得多。

另一方面,在Python中进行处理可能速度较慢,但后续的维护和过滤操作会容易得多。例如,如果你需要对结果进行复杂的、动态的逻辑判断,Python可能更灵活。

使用SQL通配符进行搜索

最后,我们将了解如何执行搜索,以及SQL中可用的通配符,以便提取与我们已知数据属性相匹配的数据。

SQL提供了LIKE操作符和通配符(如%_)来进行模式匹配。以下是通配符的用法:

  • %:匹配任意数量的字符(包括零个字符)。
  • _:匹配单个字符。

以下是一个使用通配符进行搜索的示例:

# 假设我们要查找名字以'J'开头的所有用户
cursor.execute("SELECT * FROM users WHERE name LIKE 'J%'")
j_names = cursor.fetchall()

# 查找名字第二个字母是'o'的所有用户
cursor.execute("SELECT * FROM users WHERE name LIKE '_o%'")
second_o_names = cursor.fetchall()

本节课中我们一起学习了在Python中执行SQL查询的基础。我们掌握了连接数据库、执行查询以及提取结果的方法。我们探讨了在复杂数据处理场景下,选择使用高效SQL语句与选择可维护性更强的Python代码之间的权衡。最后,我们介绍了如何使用SQL通配符进行灵活的数据搜索。理解这些核心概念,将帮助你在数据工程项目中更有效地操作数据库。

098:Python中的基础SQL命令 🐍

在本节课中,我们将学习如何在Python环境中使用基础的SQL命令与数据库进行交互。我们将从建立连接开始,逐步探索SELECTLIMIT等核心语句,并了解何时可以借助Python来处理复杂的查询逻辑。


我们已经初步使用过一些基础的SQL命令来与数据交互。同样地,我们将在Python环境中运行代码,导入SQLite3模块,创建一个数据库连接,并设置一个游标来与数据库进行交互。我们使用的是同一个示例数据库,它已预先填充了姓名数据。

现在我们已经有了一个游标。SQL语句中有哪些基础元素呢?我们已经从SELECT开始了。我们将重用这里的query变量,以便尝试不同的查询,并重新分配其内容。

我们将执行一个简单的SELECT查询,就像之前做过的那样。SELECT意味着我们要选择什么,*代表所有字段,FROM则指定数据来自哪个表。在我们的例子中,表名为people。因此,最基本的查询是SELECT * FROM people,这将获取表中的所有数据。

为了执行这个查询,我们使用最基本的方式:cursor.execute(query)。然后,我们可以遍历结果。例如,使用for result in cursor.execute(query): print(result)。运行后,我们会得到数据库中当前的所有条目。Jupyter Notebook会非常友好地显示所有输出,即使数据量很大。

这种方法的一个强大之处在于,你现在可以通过Python来控制数据处理。例如,如果你想限制输出数量,虽然用SQL查询通常更高效,但当查询变得复杂时,你可以依赖Python。你可以设置一个计数器,当打印的条目超过一定数量(比如10个)时,就中断循环。这展示了将部分处理逻辑放在Python端的能力,而不是完全依赖SQL语句。

除了SELECT * FROM people,我们当然可以在SQL查询中直接使用LIMIT。例如,SELECT * FROM people LIMIT 10会产生相同的效果。这无疑是更高效的做法。这是一个非常简单的查询示例。当情况变得复杂时,你可能需要退回到Python并进行一些处理。

除了SELECT *,我们还可以进行更精确的查询。请记住,我们的表中有三列:idnamesurname。你可以构建更贴近你所需数据的查询。例如,如果你只关心名字,可以执行SELECT name FROM people LIMIT 10。同样地,如果你只想选择姓氏,查询将是SELECT surname FROM people LIMIT 10。运行这些查询,你将分别获得名字或姓氏的数据。

这些就是你可以用来与数据库交互的一些基础SQL命令。你可以执行SELECT查询,指定表和字段,并肯定可以使用LIMIT。当事情开始变得过于复杂时,你可以依靠Python来进行一些处理,就像我们之前尝试用Python限制输出而不是在SQL查询中使用LIMIT时所展示的那样。


本节课中,我们一起学习了在Python中使用基础SQL命令与数据库交互。我们掌握了如何执行SELECT查询、使用LIMIT子句以及选择特定字段。更重要的是,我们理解了在SQL查询效率不足或过于复杂时,如何灵活地借助Python进行数据处理。这为后续处理更复杂的数据工程任务奠定了基础。

099:使用SQL在Python中提取唯一数据

在本节课中,我们将学习如何结合使用SQL和Python来提取和处理数据库中的唯一数据。我们将重点介绍如何构建SQL查询来获取特定数据,以及如何在Python中处理和呈现这些查询结果。

概述

SQL语句的强大之处在于能够精确地获取你所需的数据。在构建查询时,有多种方法可以实现这一目标。本节将通过具体的代码示例,展示如何使用SQL的SELECTORDER BYWHERE子句来提取数据,并演示如何在Python中执行这些查询并处理返回的结果。

构建基础查询

首先,我们定义一个基本的SQL查询,从people表中选择所有数据,并按姓名降序排列。这有助于我们以字母顺序查看数据。

query = "SELECT * FROM people ORDER BY name DESC"

接下来,我们执行这个查询并遍历结果。

for result in cursor.execute(query):
    print(result)

执行上述代码后,输出会从以“Z”开头的姓名开始,一直显示到以“A”开头的姓名。这是一种数据排序方式。

提取特定字段

如果我们只对特定的列感兴趣,可以在SELECT语句中明确指定字段名,而不是使用*

query = "SELECT name, surname FROM people ORDER BY name DESC"

运行修改后的查询,我们将只获得姓名和姓氏两列的数据。

使用WHERE子句过滤数据

为了获取更具体的数据,我们可以使用WHERE子句来添加过滤条件。例如,我们只想获取名为“James”的记录。

query = 'SELECT name, surname FROM people WHERE name = "James"'

注意,在Python字符串中定义SQL查询时,如果外层使用了单引号,内层应使用双引号来避免转义问题。执行此查询将返回所有名为“James”的条目。

处理查询结果

查询结果返回后,我们可以在Python端进行进一步处理。例如,我们可以更友好地格式化输出,而不是直接打印整个元组。

以下是处理单个结果记录的示例。我们假设查询只返回一条记录,并分别提取姓名和姓氏。

# 假设 ‘result‘ 是包含单条记录的元组,例如 (‘James‘, ‘Hopkins‘)
first_name = result[0]  # 索引0对应名字
last_name = result[1]   # 索引1对应姓氏
print(f"First name: {first_name}, Last name: {last_name}")

需要特别注意,我们应该对查询返回的result对象进行操作,而不是原始的cursor对象。result包含了从数据库获取的实际数据。

性能考量

在Python端处理数据(如循环、格式化)通常比在SQL查询中完成相同操作要慢。这是因为数据库引擎在处理和过滤数据方面经过了高度优化。

因此,一个重要的原则是:尽可能利用SQL查询的力量来完成数据筛选和聚合,将结果集最小化后再传递给Python。这样可以显著提高效率。

然而,有时为了代码的可读性可维护性,在Python中进行一些简单的后处理也是可以接受的,尤其是在对性能要求不高的场景下。

总结

本节课我们一起学习了如何使用SQL在Python中提取唯一数据。我们介绍了如何通过SELECT语句选择特定字段,使用ORDER BY进行排序,以及利用WHERE子句进行条件过滤。同时,我们也探讨了如何在Python中接收和处理SQL查询返回的结果,并比较了在SQL端与Python端处理数据的性能差异。记住,为了效率,应优先在SQL查询中完成复杂的数据操作。

100:在Python中使用SQL进行搜索 🔍

在本节课中,我们将学习如何在Python中执行SQL查询时,使用LIKE语句和通配符进行灵活的文本搜索。这对于处理非确定性数据或进行模糊匹配非常有用。


上一节我们介绍了基本的SQL查询操作,本节中我们来看看如何使用LIKE语句进行更灵活的搜索。

LIKE语句是SQL中用于在WHERE子句中进行模式匹配的关键字。它通常与两个通配符配合使用:

  • %:代表零个、一个或多个字符。
  • _:代表一个单一的字符。

我们将使用一个名为people的表进行演示,该表包含namesurname字段。

使用 % 通配符

%通配符可以匹配任意长度的字符序列。以下是几种常见用法。

匹配以特定字符结尾的名字

如果我们想查找所有以“es”结尾的名字,可以使用以下查询:

SELECT name, surname FROM people WHERE name LIKE '%es'

此查询中,%匹配“es”之前的任何字符,因此会返回所有以“es”结尾的名字。

匹配包含特定字符序列的名字

如果想查找名字中包含“es”的记录,无论“es”出现在什么位置,可以在其前后都使用%

SELECT name, surname FROM people WHERE name LIKE '%es%'

这个模式会匹配任何位置包含“es”的名字,例如“Wesley”(es在中间)也会被找到。

匹配以特定字符开头的名字

类似地,我们可以查找所有以“J”开头的名字:

SELECT name, surname FROM people WHERE name LIKE 'J%'

此查询返回所有首字母为“J”的名字。

使用 _ 通配符

_通配符用于精确匹配一个字符的位置。这在需要特定格式的搜索时非常有用。

匹配特定长度的结尾

例如,查找以任意两个字符加上“s”结尾的名字:

SELECT name, surname FROM people WHERE name LIKE '%__s'

这里,两个_代表两个任意字符,再加上尾部的“s”。例如,“Jones”(on+s)符合此模式。

组合使用进行精确匹配

我们可以组合使用%_进行更精确的搜索。例如,查找名字中在“s”后面紧跟三个字符的记录:

SELECT name, surname FROM people WHERE name LIKE '%s___'

这个模式会匹配如“Joseph”(s+eph)这样的名字。

应用场景与总结

本节课中我们一起学习了SQL LIKE语句与%_通配符的用法。这些工具在以下场景中尤其强大:

  • 在数据不完全确定时进行模糊搜索。
  • 需要根据数据的部分特征(如开头、结尾或包含的字符)来筛选记录。
  • 处理海量数据时,利用数据库的查询优化能力进行高效过滤。

掌握这些搜索技巧,能让你在Python中操作SQL数据库时更加得心应手,灵活地应对各种数据查询需求。


101:Python中SQL查询回顾 🐍

在本节课中,我们将回顾如何在Python环境中执行SQL查询,并理解在数据库端与Python端进行数据处理的性能权衡。

概述

上一节我们介绍了如何连接SQLite数据库。本节中,我们将回顾执行基本SQL查询的方法,并讨论在SQL查询与Python代码中进行数据处理的效率差异。

SQL查询执行

以下是使用Python的sqlite3模块执行SQL查询的基本步骤。

  1. 首先,需要导入sqlite3模块并连接到数据库文件。

    import sqlite3
    connection = sqlite3.connect('example.db')
    
  2. 接着,创建一个游标对象,它是执行SQL命令的主要接口。

    cursor = connection.cursor()
    
  3. 然后,使用游标的.execute()方法运行SQL查询语句。

    cursor.execute("SELECT * FROM users WHERE age > 20")
    
  4. 最后,获取查询结果并关闭连接。

    results = cursor.fetchall()
    connection.close()
    

性能考量:SQL vs. Python

我们了解到,虽然数据处理可以在Python端完成,但这会带来性能成本。

  • 高效方式:尽可能使用SQL语句在数据库端完成筛选、聚合等操作。例如,使用WHERE子句过滤数据比将所有数据取到Python中再过滤要快得多。
    -- 在数据库端高效过滤
    SELECT name, salary FROM employees WHERE department = 'Sales' AND salary > 50000;
    
  • Python处理:对于复杂的、难以用单一SQL表达的逻辑,可以在获取数据后在Python中进行处理。但这通常意味着需要传输更多数据,可能影响性能。

知识的扩展与应用

尽管本节课使用的是嵌入式数据库SQLite,但所学的SQL查询概念可以无缝应用到其他数据库系统,如PostgreSQL和MySQL。关键在于构建高效的SQL查询,并明智地决定在何处进行数据处理。

总结

本节课中我们一起学习了在Python中执行SQL查询的流程,并重点探讨了数据处理策略。核心要点是:优先利用SQL查询在数据库内部完成数据操作以获得最佳性能,同时保留在Python端进行必要后处理的灵活性。现在,你可以基于这些SQL知识,开始更高效地处理数据了。

102:使用Python进行网络爬虫入门 🕸️

在本节课中,我们将学习如何从互联网上高效地提取数据。你将了解如何使用爬虫库从网站读取数据,并从中识别和提取特定的信息。接着,我们将学习从HTML中定位和分离关键数据的技术。最后,我们会将解析后的数据使用SQL持久化存储到数据库中。

网络爬虫概述

网络爬虫是一种自动从万维网上收集信息的程序。它模拟人类浏览网页的行为,但以更快的速度和更大的规模进行。对于数据工程而言,爬虫是获取外部数据源的重要工具。

核心工具与库

要进行网络爬虫,我们需要借助一些Python库。以下是主要的工具:

  • Requests库:用于向网站发送HTTP请求并获取网页的HTML内容。其基本用法是 requests.get(‘url’)
  • Beautiful Soup库:用于解析HTML和XML文档,并从中提取数据。它能将复杂的HTML文档转换成一个树形结构,便于我们导航和搜索。

上一节我们介绍了爬虫的基本概念,本节中我们来看看如何实际获取网页内容。

获取网页内容

使用Requests库可以轻松地获取网页的原始数据。以下是基本步骤:

  1. 导入requests库。
  2. 使用get方法向目标URL发送请求。
  3. 检查响应状态码(如200表示成功)。
  4. 通过response.text属性获取网页的HTML内容。
import requests

url = ‘https://example.com‘
response = requests.get(url)

if response.status_code == 200:
    html_content = response.text
    print(html_content[:500]) # 打印前500个字符
else:
    print(‘Failed to retrieve the webpage‘)

成功获取HTML内容后,下一步就是从这些结构化文本中提取我们需要的信息。

解析与提取数据

Beautiful Soup库是解析HTML和提取数据的利器。它提供了简单的方法来搜索文档树。以下是使用Beautiful Soup的基本流程:

  1. 使用Beautiful Soup解析获取到的HTML内容。
  2. 利用其查找方法(如find, find_all)或CSS选择器(select)定位特定元素。
  3. 从元素中获取文本、属性等信息。
from bs4 import BeautifulSoup

# 假设html_content是上一步获取的HTML字符串
soup = BeautifulSoup(html_content, ‘html.parser‘)

# 查找所有的段落标签
all_paragraphs = soup.find_all(‘p‘)
for p in all_paragraphs:
    print(p.get_text())

# 使用CSS选择器查找特定class的元素
specific_elements = soup.select(‘.article-title‘)
for elem in specific_elements:
    print(elem.text)

我们已经学会了如何定位和提取数据,接下来需要将这些零散的数据点系统地保存起来。

数据持久化

将爬取的数据保存下来至关重要,通常我们会选择存入数据库以便后续分析。SQLite是一个轻量级的数据库,非常适合初学者和快速原型开发。以下是基本步骤:

  1. 连接到SQLite数据库(或创建一个新库)。
  2. 创建一个表来定义数据存储的结构。
  3. 将解析后的数据插入到表中。
  4. 提交更改并关闭连接。
import sqlite3

# 连接到数据库(如果不存在则创建)
conn = sqlite3.connect(‘scraped_data.db‘)
cursor = conn.cursor()

# 创建表
cursor.execute(‘‘‘
CREATE TABLE IF NOT EXISTS articles (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    content TEXT
)
‘‘‘)

# 假设我们有一个提取好的数据列表 data_to_save
data_to_save = [(‘标题1‘, ‘内容1‘), (‘标题2‘, ‘内容2‘)]

# 插入数据
cursor.executemany(‘INSERT INTO articles (title, content) VALUES (?, ?)‘, data_to_save)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/duke-py-bash-sql/img/b13544181383a1515ddc8def9caf7afc_3.png)

# 提交并关闭
conn.commit()
conn.close()

总结

本节课中我们一起学习了网络爬虫的完整流程。我们从使用Requests库获取网页原始HTML开始,然后利用Beautiful Soup库解析文档并提取出有价值的结构化数据,最后通过sqlite3库将这些数据持久化存储到SQLite数据库中。掌握这些技能,你就能自动化地从互联网上收集所需的数据,为数据工程项目提供丰富的外部数据源。

103:从非结构化HTML提取数据入门 🕸️

在本节课中,我们将要学习如何从非结构化HTML中提取数据。这是数据工程中处理网络数据的一项关键技能,通常在其他结构化数据源(如API)不可用时使用。

概述

从网络上抓取数据应被视为最后的手段。你应始终优先考虑使用API,或寻找从网络服务中提取数据的更好方法。然而,有时这是不可能的。我们可能会遇到大量非结构化的HTML,除了使用网络爬虫和抓取技术来读取数据外,别无他法。一旦成功提取数据,你就可以将其规范化并转换为其他格式,如JSON或CSV。随后,你可以对这些从非结构化HTML中有效提取的数据进行进一步的数据工程处理,这本身可能非常具有挑战性。接下来,我们将探讨如何应对这些困难,以提取并处理这些数据。

核心概念与挑战

上一节我们概述了网络抓取的必要性,本节中我们来看看处理非结构化HTML的核心挑战。非结构化HTML缺乏固定的数据模式,使得提取变得复杂。

以下是处理非结构化HTML数据时面临的主要困难:

  • 结构不一致:不同网页的HTML标签和布局可能千差万别。
  • 数据嵌套:所需信息可能深藏在多层嵌套的<div>或其他标签中。
  • 内容动态加载:部分数据可能由JavaScript动态生成,初始HTML中并不存在。
  • 反爬虫机制:网站可能采取技术手段阻止自动化抓取。

为了应对这些挑战,我们需要使用专门的工具和技术来定位和提取数据。

基本工作流程

了解了挑战之后,我们来看看从HTML提取数据的基本步骤。这个过程可以系统化为几个关键阶段。

以下是提取数据的基本工作流程:

  1. 获取HTML内容:使用如requests(Python库)等工具向目标网页发送HTTP请求,获取原始的HTML代码。
    import requests
    response = requests.get('https://example.com')
    html_content = response.text
    
  2. 解析HTML:使用解析库(如BeautifulSoup)将获取的字符串HTML转换为可遍历和搜索的树形结构对象。
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html_content, 'html.parser')
    
  3. 定位数据:通过分析HTML结构,使用标签名、CSS选择器或XPath等方法精确定位到包含目标数据的HTML元素。
    # 使用CSS选择器查找所有类名为‘title’的<h1>标签
    titles = soup.select('h1.title')
    
  4. 提取与清洗:从定位到的元素中提取文本、属性(如链接的href),并进行必要的清洗(如去除空白字符)。
    data_list = []
    for title in titles:
        clean_text = title.get_text().strip()
        data_list.append(clean_text)
    
  5. 存储数据:将提取出的结构化数据保存为便于后续分析的格式,如CSV或JSON。
    import pandas as pd
    df = pd.DataFrame(data_list, columns=['Title'])
    df.to_csv('extracted_data.csv', index=False)
    

总结

本节课中我们一起学习了从非结构化HTML提取数据的入门知识。我们首先明确了网络抓取是获取数据的最后选择,并讨论了处理非结构化HTML时的主要挑战。接着,我们介绍了一个标准的工作流程,包括获取、解析、定位、提取清洗和存储数据。掌握这些基础是进行更高级网络数据工程的第一步。

104:网络数据采集面临的挑战

在本节课中,我们将探讨从网络获取数据时,特别是在没有官方API(应用程序编程接口)的情况下,所面临的主要挑战。我们将了解为什么直接从网页提取数据比使用API更复杂,以及这个过程涉及哪些步骤。

网络数据采集的典型场景

上一节我们介绍了通过网络获取数据的基本概念。本节中我们来看看一个常见但复杂的场景:从没有提供API的网站提取数据。

想象一个网站,其数据以非结构化的HTML格式呈现。这种HTML可能难以直接提取和利用。此时,你无法使用API。API本质上是一种不可行或不可用的方案。理想情况下,你希望拥有一个API,它能提供一个简洁的URL,例如 users/{id},然后你可以通过这个接口检索数据。

API的优势

以下是API在数据交换中的主要优势:

  • 结构化数据:API通常以格式良好的JSON或XML格式暴露数据。
  • 易于处理:你可以直接提取JSON数据,并对其进行操作。XML是另一种用于暴露数据的流行格式。

无API时的数据获取挑战

但是,如果你无法访问API,该如何获取网络上那些有趣的数据呢?

如果有一个客户端(例如你的计算机),它可以像访问API一样访问网页。但你需要从网站的HTML中读取全部数据。这个过程相当复杂,它要求客户端执行以下步骤:

  1. 联系网络并读取:客户端需要访问网页并读取其100%的内容。
  2. 进行深度解析:对获取的HTML进行繁重的解析工作。
  3. 理解与重构:理解HTML的含义,并以一种有意义的方式对其进行重构和规范化,使其结构化。
  4. 生成可用数据:最终,才能生成实际的JSON数据返回给客户端。
  5. 持久化存储:将数据持久化保存到文件或数据库中。
  6. 数据转换:甚至可能进一步将数据转换为CSV等格式,再传回客户端。

核心挑战总结

本节课中我们一起学习了网络数据采集的核心挑战。

本质上,这种方法试图摆脱对暴露数据的API的依赖。但正如我们所见,这带来了非常高的复杂度。这些就是从客户端向不提供API的网站进行网络数据采集时面临的一些主要挑战。

105:使用HTMLParser在Python中解析HTML 🕸️

在本节课中,我们将要学习如何使用Python内置的HTMLParser模块来解析HTML文档。我们将通过一个具体的例子,提取HTML中特定标签(如<a>标签)的内容。


概述

解析HTML是从网页中提取结构化数据的关键步骤。Python标准库中的html.parser模块提供了一个简单但强大的工具来完成这项任务。本节我们将创建一个自定义的解析器类,并学习如何精确地捕获我们所需的数据。

创建自定义HTML解析器类

首先,我们需要从html.parser模块导入HTMLParser类,并创建一个继承自它的自定义类。

from html.parser import HTMLParser

class MyHTMLParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.recording = False

在上面的代码中,我们初始化了父类HTMLParser,并定义了一个self.recording属性。这个布尔值标志将帮助我们控制何时开始和停止记录数据。

处理开始标签

解析器在遇到每一个开始标签时,都会调用handle_starttag方法。我们需要在这个方法中检查是否遇到了我们感兴趣的标签。

    def handle_starttag(self, tag, attrs):
        if tag == 'a':
            self.recording = True
        else:
            self.recording = False

这段代码的逻辑是:当解析器遇到一个<a>标签时,将self.recording设置为True,表示我们准备记录接下来的数据。对于其他所有标签,则将其设置为False

处理标签内的数据

当解析器读取到标签之间的文本数据时,会调用handle_data方法。我们在这里检查self.recording标志,如果为True,则打印或处理这些数据。

    def handle_data(self, data):
        if self.recording:
            print(f"Found A tag info: {data}")
            self.recording = False

注意,在打印出数据后,我们立即将self.recording重置为False。这是关键的一步,确保我们只捕获目标<a>标签内的文本,而不会错误地捕获后续其他标签的内容。

使用解析器解析HTML内容

现在我们已经定义了解析器类,接下来看看如何使用它。假设我们有一段HTML内容存储在变量content中。

以下是使用解析器的步骤:

  1. 创建解析器类的一个实例。
  2. 调用实例的feed()方法,并传入要解析的HTML字符串。
# 假设 `content` 变量包含了你的HTML字符串
parser = MyHTMLParser()
parser.feed(content)

执行这段代码后,解析器会遍历整个HTML内容。每当它找到一个<a>标签并读取其内部的文本时,就会按照我们在handle_data方法中的定义,将文本打印出来。

总结

本节课中我们一起学习了使用Python内置的HTMLParser模块解析HTML的基本方法。我们创建了一个自定义解析器类,通过重写handle_starttaghandle_data方法,成功地定位并提取了HTML文档中所有<a>标签内的文本信息。关键点在于使用一个recording标志来精确控制数据捕获的时机。这种方法虽然基础,但对于许多简单的网页抓取任务来说已经足够有效。

106:Python网络爬虫技术回顾 🕸️

在本节课中,我们将回顾如何使用Python从非结构化的HTML中抓取数据。我们将学习利用Python标准库和HTML解析器来完成这项任务,无需安装任何外部库。这是一种在找到具有特定结构的HTML后,提取数据的直接方法。同时,我们也会探讨使用HTML解析器和基于类的方法时可能遇到的一些困难。

使用Python标准库解析HTML

上一节我们介绍了网络爬虫的基本概念。本节中,我们来看看如何利用Python内置的工具来解析HTML文档。

Python的标准库提供了处理HTML的模块,例如 html.parser。通过它,我们可以将HTML字符串转换为一个可遍历的树状结构对象,从而定位和提取所需的数据。

以下是使用 HTMLParser 类的基本步骤:

from html.parser import HTMLParser

class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print(f"遇到开始标签: {tag}")
    def handle_data(self, data):
        print(f"遇到数据: {data}")

parser = MyHTMLParser()
parser.feed('<html><body><h1>标题</h1></body></html>')

基于类的方法与解析难点

虽然使用 HTMLParser 类提供了很大的灵活性,但它也带来了一些挑战。这种方法需要我们手动定义处理不同HTML元素(如开始标签、结束标签、数据)的回调函数,逻辑可能变得复杂。

以下是使用基于类的方法时常见的几个难点:

  1. 状态管理复杂:当需要根据嵌套的标签结构来提取信息时,需要在类中维护状态变量,代码容易出错。
  2. 对不规范HTML容错性差:如果网页HTML结构不规范(如标签未闭合),html.parser 可能无法正确解析。
  3. 代码冗长:对于简单的数据提取任务,编写一个完整的解析器类可能显得过于繁琐。

因此,对于复杂的网页抓取任务,开发者通常会转向更强大、更易用的第三方库,如 BeautifulSouplxml。这些库提供了更简洁的API来查询和遍历HTML文档。

总结

本节课中我们一起学习了使用Python标准库进行网络爬虫的核心技术。我们了解到,通过 html.parser 模块可以解析HTML并提取数据,这是一种无需额外依赖的方法。同时,我们也认识到,基于 HTMLParser 类的方法在应对复杂结构或需要精细控制时,会面临状态管理和代码复杂度增加的挑战。这为我们在后续课程中选择更合适的爬虫工具提供了重要的参考依据。

107:Scrapy与XPath入门指南

在本节课中,我们将学习如何使用Scrapy框架和XPath语言从网页中提取数据。这两种工具协同工作,能帮助我们高效地从结构可能不完善的HTML文档中定位并抓取特定信息。

概述:Scrapy与XPath简介

Scrapy是一个用Python编写的开源网络爬虫框架。XPath则是一种在XML和HTML文档中查找信息的语言。当网页的HTML结构不够清晰或规整时,结合使用Scrapy和XPath能让我们从原始HTML中有效地检测和提取目标数据。这个过程并非总是轻而易举,但这两个工具的结合使我们能够从网站的大量HTML中精准地抓取特定数据,以便后续进行持久化存储。

核心概念解析

Scrapy框架

Scrapy是一个为爬取网站和提取结构化数据而构建的Python项目。它提供了一个完整的框架来处理请求、解析响应、提取数据和存储结果。

XPath语言

XPath使用路径表达式在HTML/XML文档中进行导航和节点选择。它遵循一套规则,使我们能够定位文档中的特定部分。

以下是XPath的一个基本代码示例,用于选择HTML中所有的段落(<p>)元素:

//p

工具协同工作流程

上一节我们介绍了Scrapy和XPath各自的基本概念。本节中我们来看看它们如何协同工作。

Scrapy负责处理网络请求和下载网页,而XPath则用于在下载的HTML内容中精确定位我们想要的数据。以下是典型的协同工作步骤:

  1. 发送请求:Scrapy向目标网址发送HTTP请求。
  2. 获取响应:接收服务器返回的HTML内容。
  3. 解析内容:使用XPath表达式在HTML文档树中定位目标数据节点。
  4. 提取数据:根据XPath定位的结果,提取出文本、属性等信息。
  5. 处理数据:对提取的数据进行清洗、转换或存储。

实践应用要点

以下是开始使用Scrapy和XPath时需要注意的几个关键点:

  • 安装Scrapy:首先需要通过pip安装Scrapy库,命令为 pip install scrapy
  • 理解HTML结构:使用浏览器的开发者工具(Inspect)查看网页的HTML结构,是编写正确XPath的基础。
  • 编写XPath:根据HTML结构,编写XPath表达式来匹配目标元素。例如,提取所有具有特定class的div标签://div[@class="product-name"]
  • 在Scrapy中使用:在Scrapy的Spider解析函数中,使用 response.xpath() 方法并传入你的XPath表达式来提取数据。

总结

本节课中我们一起学习了Scrapy框架和XPath语言的基础知识。我们了解到Scrapy是一个强大的Python爬虫框架,而XPath是用于在HTML中导航和选择节点的查询语言。两者结合,能够有效地解决从结构复杂的网页中提取特定数据的问题,为后续的数据处理和分析工作奠定基础。

108:使用Scrapy在Python中创建网络爬虫项目

在本节课中,我们将学习如何使用Scrapy框架来创建一个网络爬虫项目。Scrapy是一个功能强大的Python框架,专门用于高效地从网站提取结构化数据。我们将从零开始,逐步完成项目的初始搭建。

概述

要使用Scrapy开始爬取网站并创建项目,需要几个关键步骤。本节将引导你完成从创建虚拟环境到生成第一个爬虫的完整过程。

创建虚拟环境

首先,我们需要创建一个独立的Python虚拟环境。这能确保项目依赖与其他项目隔离。

以下是具体步骤:

  1. 打开终端,进入一个空目录。
  2. 使用以下命令创建名为 venv 的虚拟环境:
    python3 -m venv venv
    
  3. 激活虚拟环境:
    source venv/bin/activate
    
  4. 验证 pip 命令是否来自虚拟环境:
    which pip
    
    确认输出路径包含 venv/bin/pip

安装Scrapy

虚拟环境激活后,我们可以在其中安装Scrapy框架。

使用 pip 安装Scrapy:

pip install scrapy

此命令会下载并安装Scrapy及其所有必要的依赖库(如 lxmlTwisted)。Scrapy是一个功能完备的框架,因此依赖项比Python标准库中的 HTMLParser 等工具更多。

创建Scrapy项目

安装完成后,我们可以开始创建Scrapy项目。Scrapy提供了便捷的命令来生成项目脚手架。

首先,确认 scrapy 命令可用:

which scrapy
scrapy --help

接下来,使用 startproject 命令创建新项目。该命令需要一个项目名称作为参数。

例如,创建一个名为 vulnerabilities 的项目:

scrapy startproject vulnerabilities

命令执行后,会生成一个名为 vulnerabilities 的目录,其中包含项目的标准结构和配置文件(如 scrapy.cfg)。

生成爬虫(Spider)

上一节我们创建了项目框架,本节中我们来看看如何生成一个具体的爬虫。爬虫是定义如何爬取网站并解析数据的核心组件。

项目创建成功后,终端会提示你可以使用 genspider 命令来创建第一个爬虫。我们需要进入项目目录并执行此命令。

以下是生成爬虫的步骤:

  1. 进入项目目录:
    cd vulnerabilities
    
  2. 使用 genspider 命令生成爬虫。此命令需要两个参数:爬虫名称和允许爬取的域名。
    scrapy genspider cve cve.mitre.org
    
    本例中,我们创建了一个名为 cve 的爬虫,并限制其爬取 cve.mitre.org 域名下的内容。域名限制有助于在发起多个请求时控制爬取范围。

命令执行成功后,会在 vulnerabilities/spiders/ 目录下生成一个名为 cve.py 的文件。这个文件包含一个基本的爬虫类,其中定义了一个名为 parse 的方法。这就是使用Scrapy库开始解析项目所需的最初脚手架。

总结

本节课中我们一起学习了使用Scrapy创建网络爬虫项目的完整初始流程。我们首先创建并激活了虚拟环境,然后安装了Scrapy框架。接着,我们使用 startproject 命令搭建了项目结构,最后通过 genspider 命令生成了第一个包含基础解析方法的爬虫文件。至此,项目的基础设置已经完成,为后续编写具体的爬取和解析逻辑做好了准备。

109:使用XPath与Scrapy Shell解析数据

在本节课中,我们将学习如何从没有API的网站上解析和提取数据。我们将以一个政府漏洞披露网站为例,使用Scrapy Shell和XPath技术来定位并提取我们所需的漏洞编号(CVE)及其对应的漏洞利用数据库ID(Exploit DB ID)。

概述与目标网站

从网站解析或抓取数据时,一个非常常见的场景是目标网站没有提供API来获取信息。本次教程就基于这样一个场景。我们使用的目标网站是 cve.mitre.org,这是一个政府网站,用于公开已报告的常见漏洞。

该网站上有一个参考映射,意味着每个CVE条目都关联着一个Exploit DB ID。我们的业务需求就是将这些数据解析并提取出来。

定位目标数据

首先,我们需要确定目标URL。接下来,我们需要查看网页源代码。源代码是高度非结构化的HTML,非常粗糙。我们的任务是识别出我们想要的数据所在的位置。

向上滚动页面,可以看到大量我们不关心的额外信息。数据从表格部分开始变得有意义。这里有一个表格,它没有classid属性,这会在我们后续尝试定位目标表格时带来一些麻烦。

我们的目标是遍历每一行,并提取行内的数据,即Exploit DB ID和CVE编号。这让我们对需要做什么有了一个初步的了解。

启动Scrapy Shell

现在,我们复制URL,打开终端,使用Scrapy的一个功能——Scrapy Shell。Scrapy Shell是一个交互式Shell,允许你以交互方式尝试对网站进行抓取操作。

使用Scrapy Shell的一个要求是传入完整的URL。输入命令后,我们会得到大量输出,并且可以使用一些可用功能,如fetch、查看帮助和构建响应对象。输入response,可以看到一个HTTP 200响应,说明我们已经准备好开始处理数据。

使用XPath探索页面结构

我们将使用XPath来探索页面结构。通过向.xpath()方法传入字符串,我们可以访问XPath属性。我们知道要处理表格,所以使用//table来获取文档中的所有表格。

执行后,响应是一个包含多个元素的列表。我们检查这个列表的长度,发现返回了5个元素,这意味着文档中有5个表格。我们需要从中提取出包含多行数据的那个表格。

识别目标表格

有几种方法可以做到这一点。一种方法是逐个检查这些表格,看哪个包含大量行项。我们可以这样做:response.xpath('//table')[0],这是第一个表格,但它没有我们需要的长度。

我们可以尝试:response.xpath('//table')[0].xpath('.//tr'),这是第一个表格的所有行。我们可以看到这里有10851项,但这是所有表格的总和。

因此,我们需要循环检查。我们可以尝试检索第二个表格,依此类推。当我们检查第三个表格(索引为2)时,发现它有10839行数据。这告诉我们,response.xpath('//table')[2]就是包含所有目标数据的表格。

提取并处理表格数据

我将这个表格赋值给一个变量,以便后续操作:table = response.xpath('//table')[2]。通过检查table.xpath('.//tr')的长度,可以确认我们确实选中了正确的表格,它有10839行。

接下来,我们需要遍历这个表格的每一行。在开始循环之前,我们需要识别集合中的一些项。如果我们输入table,只会得到一个选择器对象。

让我们执行table.xpath('.//tr'),这会生成大量项。我们随机选取一项,比如第110项(索引109),并将其赋值给一个变量child_item

现在,我们可以在这个子项上运行另一个XPath调用:child_item.xpath('.//td/text()')。这会提取出该行所有单元格的文本内容,返回一个列表。

我们可以获取列表的第一项并调用.extract()(一个Scrapy辅助函数),得到Exploit DB ID,例如“Exploit-DB: 10102”。这非常有用。我们还需要找到CVE编号。尝试获取列表的第二项没有结果,但第三项给出了CVE编号。

构建数据提取循环

如果我们想遍历所有项(记住我刚才选的是第11项),可以在交互式Shell中创建一个循环。代码如下:

for row in table.xpath('.//tr'):
    try:
        print(row.xpath('.//td/text()')[0].extract())
    except IndexError:
        pass

在Shell中操作时需要注意正确闭合括号,否则会遇到语法错误。执行上述循环后,我们可能会遇到“list index out of range”的错误。这是因为有些行可能没有我们预期的数据。

为了解决这个问题,我们使用try...except块。当索引错误发生时,我们选择忽略(pass)该行。这样,循环就会输出所有我们想要的Exploit DB ID。你可以看到终端中输出了数千个ID,这个过程非常彻底,甚至超出了我们最初的预期。

总结

本节课中,我们一起学习了如何在没有API的情况下从网页中解析数据。我们使用了Scrapy Shell进行交互式探索,利用XPath定位目标表格和行,并通过编写循环和异常处理逻辑,成功提取了Exploit DB ID和CVE编号。这个过程涵盖了从识别目标、探索结构到最终提取数据的完整工作流,是网页抓取中的一个实用技能。

110:使用Scrapy Spider进行网络爬虫

在本节课中,我们将学习如何创建一个Scrapy Spider,用于从网页中提取结构化数据。我们将通过一个具体的例子,从目标网站抓取CVE漏洞信息,并详细讲解代码的编写和执行过程。


为什么Scrapy Spider有用

Scrapy Spider之所以有用,是因为它允许我们编写结构化的代码来提取网页数据。现在,我们可以退出当前环境,进入CVE Spider文件进行编辑。


编辑CVE Spider文件

接下来,我们将编辑CVE Spider文件。在parse方法中,response对象与我们之前在Shell中使用的对象功能相同。

因此,我们可以将之前编写的同步代码复制到这里。例如,我们可以编写以下代码:

for child in response.xpath('//table'):
    if len(child.xpath('tr')) > 100:
        table = child
        break

现在,我们有了目标表格table,可以遍历其中的每一行:

for row in table.xpath('tr'):
    try:
        print(row.xpath('td/text()').extract_first())
    except IndexError:
        pass

这段代码将提取并打印表格中每个单元格的文本内容。如果遇到索引错误(例如单元格为空),则跳过。


运行Scrapy Spider

现在,CVE Spider已经准备就绪,我们可以尝试运行它。这本质上与我们在Shell中使用的命令相同。

首先,我们需要返回上一级目录,然后调用scrapy命令。在项目目录中,scrapy命令会提供更多可用选项。

我们需要指定起始URL。在代码的第7行,start_urls应该包含目标网站的完整URL。因此,我们需要从网站复制URL,并粘贴到start_urls列表中。在这个例子中,我们只有一个URL。

以下是所有可用的Scrapy命令。现在,我们已经编辑了文件并设置了目标URL,可以运行Spider了。Spider的名称是CVE,因此我们使用crawl命令:

scrapy crawl CVE

命令执行后,会有大量输出,并且运行速度很快。我们需要向上滚动查看输出内容。


查看输出结果

在输出中,可以看到打印的exploit_db_id信息。虽然初始的终端滚动可能无法显示所有内容,但命令scrapy crawl CVE确实向目标网站exploit-db.com发送了请求。

一旦数据返回,Scrapy就会使用我们定义的CVE Spider进行解析。为什么是CVE Spider?因为我们在spiders目录下的CVE.py文件中定义了Spider的名称为CVE

parse方法被执行,并打印每一个exploit_db_id。这就是使用XPath解析方法提取数据的方式。


总结

本节课中,我们一起学习了如何创建和运行一个Scrapy Spider来提取网页数据。我们编辑了Spider文件,使用XPath定位和提取表格数据,并通过scrapy crawl命令执行了爬虫任务。通过这个过程,我们掌握了使用Scrapy进行网络爬虫的基本步骤和方法。

111:Scrapy与XPath在Python中的回顾 🕷️🔍

在本节课中,我们将回顾如何使用Scrapy库和XPath表达式从网页中抓取数据。我们将学习如何设置环境、创建项目,并利用XPath精准定位和提取网页中的特定信息。


项目创建与环境设置

上一节我们介绍了数据抓取的基本概念,本节中我们来看看如何开始一个实际的Scrapy项目。

首先,我们创建了一个项目并安装了Scrapy。Scrapy是一个基于Python的强大库,专门用于从网站抓取数据。

以下是创建Scrapy项目的基本步骤:

  1. 使用命令 scrapy startproject project_name 初始化一个新项目。
  2. 进入项目目录。
  3. 使用 scrapy genspider spider_name example.com 命令生成一个爬虫。

使用XPath提取数据

在成功设置项目后,我们利用XPath的能力来查找并提取特定的数据。XPath是一种在XML和HTML文档中导航并选择节点的语言。

XPath的核心功能是通过路径表达式来定位文档中的节点。以下是一些基本但强大的XPath用法示例:

  • 选择所有节点:使用双斜杠 // 可以从任意位置开始选择。例如,//div 会选择文档中所有的 <div> 元素。
  • 根据属性选择:使用方括号 [@属性名='值'] 可以进行过滤。例如,//a[@class='link'] 会选择所有 class 属性为 “link” 的链接。
  • 提取文本或属性
    • 获取元素文本://h1/text()
    • 获取元素属性://img/@src

通过组合这些表达式,我们可以精确地定位到网页中我们感兴趣的数据块,并将其提取出来。

总结

本节课中我们一起学习了Scrapy与XPath的基础应用。我们首先完成了Scrapy项目的创建与环境设置,然后深入探讨了如何使用XPath表达式在网页文档中导航并提取目标数据。这套方法在数据无法通过API便捷获取时尤为有用,为你开启了从网页直接采集数据的大门。

112:网络爬虫挑战概览 🕸️

在本节课中,我们将要学习网络爬虫过程中可能遇到的主要挑战。即使你已经制定了一个良好的爬取策略,在实际操作中仍然会面临一些复杂问题。了解这些挑战是构建健壮、高效数据管道的第一步。

动态网站结构带来的挑战

上一节我们介绍了网络爬虫的基本概念,本节中我们来看看实际操作中的第一个主要挑战:目标网站的动态变化。

假设你正在从云端访问一个远程网站,其内容由HTML构成。你可能认为已经准备好开始爬取数据了。然而,网站的结构和内容并非一成不变。我们可以将初始的网站结构称为 版本1

# 假设的爬虫代码,针对网站版本1
response = requests.get('https://example.com/v1')
soup = BeautifulSoup(response.content, 'html.parser')
data = soup.find('div', class_='target-data')  # 依赖于版本1的特定结构

问题在于,网站可能决定发布版本2。而你部署在云端的代码是为解析版本1的HTML结构而编写的。这会导致你的爬虫立即失效,因为代码无法在新结构中找到预期的数据。此时,你需要分析新版本,并重新实现一套爬取策略。

这个过程成本高昂。试想,如果问题规模扩大,挑战将变得更加严峻。

规模扩展带来的复杂性与成本

以下是当爬虫任务规模增长时,随之而来的几个核心挑战:

  • 目标数量增长:如果从爬取一个网站变为需要解析数百或数千个不同网站,维护每个网站的特定解析逻辑将变得极其复杂。
  • 数据量激增:如果每次爬取返回的数据量从1兆字节(MB) 增长到1太字节(TB),这将带来巨大的网络传输成本、存储成本和数据处理压力。
  • 操作成本高昂:频繁地重新编写爬虫以适应网站变化,以及处理海量数据,会成为一项非常昂贵且耗时的操作。

我们的目标是避免这种复杂性,并尽可能降低成本。因此,我们需要采用一些技术来更高效、更直接地解决这些问题。

应对策略与目标

面对上述挑战,关键在于构建一个更具适应性和效率的爬虫系统。

无论目标网站存在多少个版本,或者其结构如何频繁变化,一个优秀的爬虫系统都应做好准备来解析和获取数据。在后续课程中,我们将探讨一些能够帮助你更好地完成这项任务、提升效率的技术,例如:

  • 使用更健壮的解析方法(如CSS选择器、XPath结合正则表达式)。
  • 实现错误处理和重试机制。
  • 设计可扩展的架构来处理大规模数据抓取。
  • 设置监控告警,及时发现网站结构变更。

本节课中我们一起学习了网络爬虫面临的两大核心挑战:网站动态变化规模扩展带来的成本问题。理解这些挑战是设计出稳定、可维护且经济高效的数据获取流程的基础。在接下来的内容中,我们将深入探讨解决这些挑战的具体方法和技术。

113:本地爬取实践 🕷️💾

在本节课中,我们将学习如何通过下载网页的HTML文件到本地,来优化和测试网络爬虫。这种方法可以避免频繁请求在线服务器,提高开发效率,并允许我们更安全地进行代码调试。

概述

上一节我们创建了一个漏洞扫描项目。现在,我们将回顾用于从目标网站抓取漏洞信息的爬虫。我们将重点介绍如何通过本地处理HTML文件来改进爬虫,使其运行更快、更高效,并便于测试。

回顾现有爬虫

我们的爬虫项目位于 vulnerabilities 目录下,核心文件是 cve.py。在这个文件中,start_urls 列表定义了爬虫开始工作的起点。

start_urls = ['https://www.exploit-db.com/html']

这意味着每当爬虫启动时,它都会访问这个在线网址。如果只是抓取少量信息,这没有问题。但设想一下,如果需要测试多个不同网站,或提取数百GB的数据,频繁的网络请求将变得非常消耗资源和时间。

本地处理的优势

一种缓解方法是预先下载HTML文件。这样做有几个好处:

  • 可以进行离线测试。
  • 可以自由修改本地文件以测试代码的健壮性。
  • 可以避免因频繁请求而给目标服务器带来负担,做一个“友好的”数据抓取者。

接下来,我们将实践这一方法。

下载HTML文件

我们将使用命令行工具 wget 来下载目标网页。首先,打开终端并导航到爬虫项目所在的目录。

cd path/to/vulnerabilities/spiders
wget https://www.exploit-db.com/html -O exploit-db.html

命令执行后,一个约1.56MB的 exploit-db.html 文件会被下载到当前目录。如果需要对几十个网站进行类似操作,本地处理的效率优势将非常明显。

作为对比,我们可以用 time 命令计时运行一次原始爬虫,看看在线抓取需要多长时间。

time scrapy crawl cve

执行结果显示,整个过程(包括网络请求、数据抓取和解析)大约需要2秒。其中,网络请求和加载到内存占用了大部分时间。

修改爬虫以读取本地文件

现在,我们修改 cve.py 文件,让爬虫从刚下载的本地HTML文件读取数据,而不是访问在线网址。

首先,我们需要导入 os 模块来构建文件路径。

import os
from os.path import dirname, join

接着,我们定义当前文件所在的目录,并拼接出HTML文件的绝对路径。

# 获取当前脚本文件(cve.py)所在的目录
current_dir = dirname(__file__)
# 构建本地HTML文件的完整路径
html_file_path = join(current_dir, 'exploit-db.html')

最后,修改 start_urls。Scrapy框架支持使用 file:// 协议来加载本地文件。

# 注释掉原来的在线URL
# start_urls = ['https://www.exploit-db.com/html']
# 改为使用本地文件路径
start_urls = [f'file://{html_file_path}']

测试与对比

完成修改后,我们再次运行爬虫并计时。

time scrapy crawl cve

现在,执行时间缩短到不足1秒。因为所有处理都在内存和本地文件中进行,消除了网络延迟。

为了感受差异,我们可以将 start_urls 改回在线网址再运行一次,会发现速度明显变慢(尽管可能受到缓存影响)。这个练习的核心在于,通过本地处理HTML,可以极大地提升效率。

本地文件的更多用途

使用本地HTML文件的另一个重要优势是便于防御性编程调试

我们可以直接打开 exploit-db.html 文件,观察其结构。例如,检查表格行(<tr>)的排列。如果我们想在代码中添加逻辑来防止因页面结构意外变化而导致的索引错误(IndexError),就可以在本地文件上直接进行测试。

例如,在解析代码中,可以添加判断:

# 假设 rows 是我们选取的所有表格行
if len(rows) > expected_count:
    # 安全地处理数据
    process_data(rows[expected_index])
else:
    # 处理结构不符的情况
    logging.warning("HTML structure changed.")

如果直接对在线网站进行测试,我们无法控制其HTML结构,也无法快速验证错误处理逻辑。而使用本地文件,我们可以随意添加或删除HTML标签,模拟各种页面结构变化,从而确保爬虫代码更加健壮。

总结

本节课我们一起学习了网络爬虫的优化技巧——本地爬取实践。我们了解到,通过 wget 等工具将目标网页下载到本地,并修改爬虫的 start_urls 指向本地文件(使用 file:// 协议),可以带来多重好处:

  1. 大幅提升爬取速度,消除网络请求开销。
  2. 减轻目标服务器压力,遵守良好的网络礼仪。
  3. 便于代码测试与调试,可以在本地文件上模拟各种页面结构,实践防御性编程,使爬虫更健壮。

虽然本例只使用了一个HTML文件,但在处理大量页面时,这种先下载后解析的模式能显著提高开发效率和程序性能。这是一个非常实用且值得掌握的数据工程技巧。

114:数据持久化为CSV与JSON格式 📁

在本节课中,我们将学习如何将从网站解析或抓取到的数据持久化保存。我们将重点介绍两种非常流行的数据格式:CSV文件和JSON文件。你将学会如何使用Python标准库来创建和写入这两种格式的文件,以便后续进行数据分析或可视化。

概述

从网页获取数据后,我们需要将其保存下来以供后续使用。CSV(逗号分隔值)和JSON(JavaScript对象表示法)是数据科学和工程中两种极为常见的格式。本节我们将通过一个具体的例子,演示如何将抓取到的漏洞数据分别保存为CSV和JSON文件。

保存为CSV文件

CSV文件是一种以纯文本形式存储表格数据的简单格式。我们将使用Python内置的csv模块来创建和写入文件。

首先,我们需要在代码顶部导入csv模块。

import csv

接下来,我们需要确定在代码的哪个位置打开文件并写入数据。这里有一个关键点:我们有一个for循环来处理每一条数据。如果在循环内部打开文件,文件会在每次迭代时被覆盖。因此,我们必须在循环之外打开文件。

以下是具体步骤:

  1. 打开文件:在循环开始前,我们使用open()函数以写入模式('w')打开一个CSV文件。
  2. 创建写入器:使用csv.writer()函数创建一个写入器对象,它将负责将数据格式化为CSV格式并写入文件。
  3. 写入表头:在写入具体数据行之前,我们先写入一行作为列标题。
  4. 写入数据行:在循环内部,对于每一条处理好的数据,我们使用写入器将其作为一行写入文件。
  5. 关闭文件:循环结束后,记得手动关闭文件。如果使用with open(...) as ...的上下文管理器语法,这一步会自动完成,但本例中我们使用传统方式。

以下是实现代码的核心部分:

# 在循环外打开文件
csv_file = open('vulnerabilities.csv', 'w')
writer = csv.writer(csv_file)

# 写入表头
writer.writerow(['exploit_ID', 'CVE_ID'])

# 在循环内部写入每一行数据
for item in data_items:
    writer.writerow([exploit_id, cve_id])

# 循环结束后关闭文件
csv_file.close()

运行代码后,我们会得到一个名为vulnerabilities.csv的文件。打开文件,第一行是表头exploit_ID, CVE_ID,后面跟着我们抓取到的近100条数据记录。这个过程中,我们也可能发现一些数据清洗的问题(例如存在空行),这有助于我们在后续处理中改进。

保存为JSON文件

JSON是一种轻量级的数据交换格式,易于人阅读和编写,也易于机器解析和生成。它使用键值对的结构来组织数据。

现在,让我们看看如何将相同的数据保存为JSON格式。首先,在代码顶部导入json模块。

import json

保存为JSON的步骤与CSV有所不同:

  1. 准备数据结构:我们不再逐行写入文件,而是先在内存中构建一个Python字典(或列表)来存储所有数据。
  2. 打开文件:同样,在循环外以写入模式打开一个.json文件。
  3. 填充数据:在循环内部,将每一条数据作为键值对添加到之前创建的字典中。
  4. 序列化并写入:循环结束后,使用json.dump()方法将整个字典对象序列化为JSON格式的字符串,并一次性写入文件。
  5. 关闭文件:完成写入后关闭文件。

以下是实现代码的核心部分:

import json

# 创建一个空字典来存储数据
data = {}

# 在循环外打开文件
json_file = open('vulnerabilities.json', 'w')

# 在循环内部将数据添加到字典中
for item in data_items:
    data[exploit_id] = cve_id  # 以exploit_id为键,cve_id为值

# 循环结束后,将整个字典写入JSON文件
json.dump(data, json_file)

# 关闭文件
json_file.close()

运行代码后,我们会得到一个vulnerabilities.json文件。打开文件,可以看到数据以{"exploit_id_1": "cve_id_1", "exploit_id_2": "cve_id_2", ...}的格式存储。同样,我们可能发现一些数据问题(如换行符被当作数据),这提示我们需要在数据处理阶段进行更严格的清洗。

总结

本节课我们一起学习了两种将数据持久化的常用方法。

  • 保存为CSV:适合表格型数据,可以使用Python的csv模块逐行写入,便于用电子表格或pandas库打开和分析。
  • 保存为JSON:适合结构化、嵌套的数据,使用json模块将整个数据结构序列化后写入文件,便于在Web应用或不同编程语言间交换数据。

两种方法都能有效地将内存中的数据保存到磁盘,并且生成的文件可以轻松导入到Jupyter Notebook等工具中进行进一步的分析、清洗和可视化。通过实践,你也能直观地看到原始数据中可能存在的问题,从而不断优化你的数据抓取和处理流程。

115:将数据持久化到SQLite数据库

在本节课中,我们将学习如何将数据持久化存储到SQLite数据库中。我们将了解为什么数据库存储比CSV或JSON文件更高效,并逐步实践如何创建数据库连接、定义表结构以及插入数据。

概述

将数据持久化到数据库是一种高效保存工作进度的方法。与CSV和JSON文件相比,数据库能更好地支持持续的数据添加和进度保存。本节我们将使用SQLite3来解决数据持久化问题。

创建数据库连接

首先,我们需要建立与SQLite数据库的连接。连接是后续所有数据库操作的基础。

以下是创建连接的代码:

import sqlite3
connection = sqlite3.connect('bo.db')

这段代码使用sqlite3模块的connect方法。如果当前目录下存在名为bo.db的文件,它将打开该数据库文件;如果不存在,则会创建一个新的数据库文件。

定义数据表结构

建立连接后,下一步是定义存储数据的表结构。我们需要明确表中包含哪些列以及它们的数据类型。

以下是创建表的SQL语句:

CREATE TABLE booms (
    exploit TEXT,
    cve TEXT
)

我们创建了一个名为booms的表,它包含两个文本类型的列:exploitcve。为了示例的简洁,我们暂时没有定义主键。

执行表创建操作

定义了SQL语句后,我们需要通过数据库游标来执行它,并将更改提交到数据库。

以下是执行和提交的代码:

cursor = connection.cursor()
table_sql = \"CREATE TABLE booms (exploit TEXT, cve TEXT)\"
cursor.execute(table_sql)
connection.commit()

我们首先从连接对象创建了一个游标。游标是用于执行SQL命令的接口。然后,我们执行了创建表的SQL语句,并通过connection.commit()将创建表的操作永久保存到数据库中。

向表中插入数据

表创建完成后,我们就可以开始向其中插入数据了。这通常在数据处理的循环中进行。

以下是插入数据的代码示例:

exploit_id = \"sample_exploit\"
cve_id = \"CVE-2023-XXXXX\"
cursor.execute(\"INSERT INTO booms (exploit, cve) VALUES (?, ?)\", (exploit_id, cve_id))
connection.commit()

我们使用INSERT INTO语句向booms表添加数据。SQLite的API推荐使用问号?作为占位符来安全地传递值,这可以防止SQL注入攻击。execute方法的第二个参数是一个元组,其中包含了要插入的实际值。每次插入后,都需要调用connection.commit()来提交事务,确保数据被写入磁盘。

验证数据持久化

操作完成后,我们可以验证数据是否已成功保存到数据库中。

我们可以使用SQLite命令行工具或直接在Python中查询来检查数据:

SELECT * FROM booms;

运行此查询将显示booms表中所有已插入的记录,确认我们的数据已被持久化保存。

总结

本节课我们一起学习了如何将数据持久化到SQLite数据库。我们首先创建了数据库连接,然后定义了表结构并执行创建,最后在循环中向表中插入了数据并提交了更改。与文件存储相比,数据库存储提供了更可靠、更高效的方式来保存和处理大量数据,特别是在需要持续追加数据和跟踪进度的场景中。你现在已经掌握了使用SQLite进行基本数据持久化的方法。

116:网络爬虫持久化与效能回顾

在本节课中,我们将学习网络爬虫开发中的两个核心主题:如何通过本地持久化数据来提升爬虫的稳定性和测试效率,以及如何将爬取到的数据保存到不同的存储介质中。

上一节我们介绍了网络爬虫的基础知识,本节中我们来看看如何让爬虫更健壮、更高效。

提升爬虫的稳定性与测试效率

在爬取不受你控制的网站时,网站内容可能随时变化。为了应对这种不确定性,你需要编写防御性代码并进行有效测试。一个关键策略是将网页数据持久化到本地文件中。

核心概念:将远程的、易变的网页数据保存为本地静态文件,以便进行快速、稳定的解析测试。

以下是实现此策略的主要优势:

  • 提升解析速度:直接从本地硬盘读取文件比反复发起网络请求快得多。
  • 增强测试稳定性:使用固定的本地文件进行测试,可以确保你的解析逻辑在网站内容变化前后保持一致,便于调试。
  • 实现离线开发:你可以在没有网络连接的情况下,继续开发和测试数据解析代码。

数据持久化的多种方式

在解析网站和提取数据的过程中,你需要将结果保存下来。有多种方式可以实现数据持久化。

以下是三种常见的数据持久化方法:

  1. 保存到CSV文件:这是一种简单的表格格式,适合存储结构化的数据。你可以使用Python内置的csv库来实现。

    import csv
    with open('data.csv', 'w', newline='') as file:
        writer = csv.writer(file)
        writer.writerow(['列1', '列2'])
        writer.writerow(['数据A', '数据B'])
    
  2. 保存到JSON文件:这是一种轻量级的数据交换格式,非常适合存储嵌套的、半结构化的数据。Python的json库让读写JSON文件变得非常简单。

    import json
    data = {"key": "value", "list": [1, 2, 3]}
    with open('data.json', 'w') as file:
        json.dump(data, file)
    
  3. 保存到数据库:对于大量或需要复杂查询的数据,数据库是更优的选择。本节课我们以SQLite为例,其原理同样适用于其他主流数据库(如MySQL、PostgreSQL)。

    import sqlite3
    conn = sqlite3.connect('mydatabase.db')
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS mytable (id INTEGER, name TEXT)''')
    cursor.execute("INSERT INTO mytable VALUES (1, 'Alice')")
    conn.commit()
    conn.close()
    

渐进式保存的重要性

在爬虫运行时进行渐进式数据保存至关重要。这意味着在解析和提取数据的过程中,定期将已获取的数据片段保存起来,而不是等到整个爬取任务全部完成后再一次性保存。

核心优势

  • 防止数据丢失:如果程序中途崩溃或网络中断,你已经保存了部分成果,无需从头开始。
  • 提升处理效率:你可以边爬取边处理已保存的数据,实现流水线作业,这比一次性处理所有数据后再保存要高效得多。

本节课中我们一起学习了提升网络爬虫效能的关键技术。我们探讨了如何通过将网页数据本地化来增强代码的防御性和测试的稳定性。同时,我们也介绍了多种数据持久化方法,包括保存到CSV、JSON文件以及SQLite数据库,并强调了在爬取过程中进行渐进式保存的重要性。掌握这些技巧,能帮助你构建出更可靠、更高效的数据采集程序。

117:MySQL数据库操作入门 🗄️

在本节课中,我们将学习如何使用现代开发环境VS Code来连接和操作MySQL数据库。你将掌握从建立连接到执行查询,再到数据迁移的全过程。

概述

本周,你将学习如何使用现代开发环境VS Code。我将介绍从VS Code连接到MySQL数据库服务器所需了解的所有知识。你将能够执行查询和进行数据库操作。最后,我将介绍一些可用于从MySQL等数据库加载、提取和迁移数据的策略。

使用VS Code连接MySQL

上一节我们概述了课程目标,本节中我们来看看如何具体设置开发环境。首先,你需要确保已在计算机上安装VS Code和MySQL服务器。

以下是连接MySQL数据库的基本步骤:

  1. 在VS Code中安装MySQL扩展。
  2. 使用扩展面板创建新的数据库连接。
  3. 输入主机名、用户名、密码和数据库名称等连接信息。
  4. 测试连接以确保配置正确。

执行查询与数据库操作

成功连接数据库后,我们就可以开始与之交互了。在VS Code中,你可以直接编写并运行SQL语句。

以下是一些基础的数据库操作示例:

  • 查询数据:使用 SELECT * FROM table_name; 语句检索数据。
  • 插入数据:使用 INSERT INTO table_name (column1, column2) VALUES (value1, value2); 语句添加新记录。
  • 更新数据:使用 UPDATE table_name SET column1 = value1 WHERE condition; 语句修改现有记录。
  • 删除数据:使用 DELETE FROM table_name WHERE condition; 语句删除记录。

数据加载、提取与迁移策略

掌握了基本操作后,我们进一步探讨更实际的数据工程任务。从数据库中高效地移动数据是数据工程的核心工作之一。

以下是几种常用的数据迁移策略:

  • 使用SQL导出/导入:通过 SELECT ... INTO OUTFILELOAD DATA INFILE 命令进行数据的导出和导入。
  • 编写Python脚本:利用 pymysqlsqlalchemy 库连接数据库,用 pandas 库处理数据,实现复杂的ETL(提取、转换、加载)流程。
    import pandas as pd
    import pymysql
    # 建立数据库连接
    connection = pymysql.connect(host='localhost', user='root', password='password', database='your_db')
    # 读取数据到DataFrame
    df = pd.read_sql('SELECT * FROM your_table', con=connection)
    # 处理数据...
    # 将数据写入新表或文件
    df.to_sql('new_table', con=connection, if_exists='replace', index=False)
    
  • 使用专业ETL工具:对于大型或定期的数据迁移任务,可以考虑使用Apache Airflow、Talend等工具来编排和管理工作流。

总结

本节课中,我们一起学习了如何在VS Code环境中连接MySQL数据库、执行基本的SQL查询与操作,并了解了数据加载、提取和迁移的几种实用策略。这些技能是进行数据工程工作的基础,帮助你有效地管理和移动数据。

118:在VSCode中集成MySQL入门 🚀

在本节课中,我们将学习如何在强大的文本编辑器Visual Studio Code中连接并管理MySQL数据库。这能让你在编写代码的同一环境中直接操作数据库,极大提升工作效率。

连接MySQL的多种方式

有多种方法可以连接和管理MySQL数据库。你可以使用命令行工具,即所谓的MySQL客户端。也可以使用功能更全面的管理工具,例如历史悠久、提供图形用户界面的PhpMyAdmin,它允许你添加数据并与MySQL内的数据进行交互。

上一节我们介绍了连接MySQL的通用方法,本节中我们来看看如何在VSCode中实现这一目标。

选择VSCode的优势

在本例中,我们将使用Visual Studio Code。它是一个功能强大的文本编辑器,具备连接MySQL、并与MySQL及其内部数据进行交互的能力。

这种方法非常实用,因为它将数据库操作工具直接集成到了你的工作环境中。如果你能使用同一个文本编辑器来操作数据,那将事半功倍。接下来,我们将具体看看如何实现。

核心步骤概述

以下是配置VSCode连接MySQL的核心步骤:

  1. 安装扩展:在VSCode的扩展市场中搜索并安装MySQL管理扩展。
  2. 建立连接:在扩展面板中,点击“+”号,输入你的MySQL服务器地址、用户名、密码和端口号。
  3. 浏览与操作:连接成功后,你可以在侧边栏浏览数据库、表,并执行SQL查询。

具体操作流程

以下是详细的操作流程:

  • 第一步:安装MySQL扩展
    打开VSCode,进入扩展视图(Ctrl+Shift+X),搜索“MySQL”,选择由Microsoft或cweijan发布的官方扩展进行安装。

  • 第二步:配置数据库连接
    安装后,左侧活动栏会出现数据库图标。点击它,然后点击“+”号添加连接。你需要填写主机名(通常是localhost)、用户名、密码和端口(默认3306)。

  • 第三步:执行SQL查询
    连接建立后,你可以展开数据库和表。右键点击数据库或表,选择“新建查询”,即可在编辑器中编写并运行SQL命令,例如:

    SELECT * FROM your_table_name;
    

总结

本节课中,我们一起学习了在VSCode编辑器中集成MySQL数据库的方法。我们了解了这种方式的便利性,并逐步实践了从安装扩展、建立连接到执行查询的完整流程。掌握这一技能,能让你在数据工程工作中更加得心应手。

119:连接MySQL服务器 🗄️

在本节课中,我们将学习如何在VS Code环境中连接到一个MySQL数据库。我们将使用一个浏览器内的VS Code实例,通过MySQL管理扩展来建立连接、浏览数据库结构并执行简单的SQL查询。

概述

上一节我们介绍了数据工程的基本工具。本节中,我们来看看如何具体连接到一个MySQL服务器。我们将从检查扩展开始,逐步完成连接配置,并最终运行一个查询来验证连接。

连接步骤

以下是连接MySQL服务器的详细步骤。

1. 检查并确认MySQL扩展

首先,我们需要确保VS Code中安装了MySQL管理扩展。这个扩展允许我们列出服务器、数据库、表、列以及运行查询。

  • 在VS Code左侧活动栏中,点击“扩展”图标。
  • 在搜索框中输入“MySQL”。
  • 确认名为“MySQL”的管理工具扩展已安装。如果未安装,你需要在此处点击安装。

2. 打开MySQL视图

扩展安装后,我们需要找到并使用它。

  • 回到“资源管理器”视图。
  • 在视图顶部,你会发现一个“MySQL”选项。点击它。
  • 此时,MySQL视图面板会打开,但里面是空的,因为我们尚未连接到任何数据库。

3. 创建新连接

要连接到数据库,我们需要创建一个新的连接配置。

  • 在MySQL视图面板的顶部,点击加号(+)按钮来添加连接。
  • 系统会提示你输入连接信息。

4. 输入连接参数

创建连接需要提供以下几项信息,请按顺序填写:

  • 主机名:数据库所在服务器的地址。对于本地数据库,输入 localhost。对于远程数据库,则需要输入IP地址或完整的域名。
  • 用户名:用于认证的MySQL用户。在本示例中,我们使用拥有全部权限的 root 用户。在实际生产环境中,建议使用权限受限的特定用户。
  • 密码:对应用户的密码。本例中的测试数据库root用户没有密码,因此直接按回车键跳过。生产环境中必须设置强密码。
  • 端口:MySQL服务监听的端口。默认是 3306,如果使用默认端口,直接按回车确认即可。
  • SSL证书路径:如果数据库连接启用了SSL加密,则需要提供证书路径。本例未使用SSL,因此留空并按回车跳过。

5. 验证连接

完成上述步骤后,连接 localhost 应该会出现在MySQL视图面板中。点击其左侧的展开箭头,你应该能看到四个默认的数据库(如 mysql, information_schema 等)。这表示连接成功。

执行SQL查询

成功连接后,我们可以尝试执行一个SQL查询。

  1. 在MySQL视图面板中,展开 localhost 连接,再展开 mysql 数据库以查看其下的表。
  2. user 表上点击右键,在上下文菜单中选择“新建查询”。这会在编辑器区域创建一个新文件。
  3. 在新文件中输入SQL语句:
    SELECT * FROM user;
    
  4. 重要:不要点击编辑器右上角的“运行”按钮来执行此查询,这会导致错误。
  5. 正确的方法是使用命令面板。通过菜单 查看 -> 命令面板 或快捷键(Mac上是 Shift + Command + P,Windows/Linux上是 Ctrl + Shift + P)打开命令面板。
  6. 在命令面板中输入并选择“运行 MySQL 查询”。
  7. 查询结果将显示在编辑器下方的“输出”面板中。

总结

本节课中我们一起学习了在VS Code中连接MySQL数据库的完整流程。关键步骤包括:确认MySQL扩展、通过MySQL视图添加新连接、填写必要的主机、用户、端口等连接参数,以及通过命令面板正确执行SQL查询。请记住,在生产环境中,务必使用强密码并遵循最小权限原则来管理数据库用户。

120:VSCode与MySQL集成回顾 🧑‍💻

在本节课中,我们将回顾如何将VSCode与一个正在运行的MySQL数据库连接起来,并了解建立连接时需要注意的关键事项。

连接配置要点回顾

上一节我们介绍了MySQL的基本概念,本节中我们来看看建立连接的具体配置项。在创建新连接时,有几个关键参数需要设置。

以下是创建连接时必须注意的几个方面:

  • SSL加密:连接是否需要加密?如果需要,你必须提供SSL证书路径,并通过VSCode中的相应扩展使其可用。
  • 用户名与密码:我们使用的root用户密码为空。root是MySQL数据库中权限最高的用户。我们现已了解,这在生产环境中并非理想做法,但对于测试数据库而言完全可行。
  • 地址与端口:我们使用了完整的端口地址。如果你的环境或生产数据库使用了不同的端口,可以在此进行更改。

查询执行与数据操作

完成连接配置后,我们便可以通过VSCode高效地操作数据库了。

我们运行了一些查询,并利用已设置的文本数据和连接字符串查看了结果。现在,你应该能够熟练地使用文本数据连接任何MySQL数据库,并通过VSCode检索数据、操作数据以及有效地管理数据库。

本节课中我们一起学习了在VSCode中配置与连接MySQL数据库的完整流程,包括SSL、认证信息和网络端口的设置,以及最终执行查询和操作数据的方法。

121:执行MySQL查询的挑战与解决方案

在本节课中,我们将探讨在编写和执行MySQL查询时可能遇到的挑战,并学习如何利用现代工具来提高效率和准确性。

概述

对MySQL数据库执行查询有时会有些棘手。特别是当你尝试创建复杂的SQL查询时,如果不完全清楚自己在做什么,可能会遇到麻烦。即使你经验丰富、精通SQL,也可能因为拼写错误或语法错误而陷入困境。因此,拥有一个能帮助你自动填充或自动完成SQL查询的工具非常有价值。

自动完成功能的价值

上一节我们提到了执行查询的潜在困难,本节中我们来看看一个具体的解决方案:自动完成功能。

例如,当数据库中有多个表,而你又不完全记得所有表名时,像VS Code这样的文本编辑器就能帮助你预填充和自动完成这些表名,确保你的SQL查询语法正确。这个功能非常宝贵。

以下是自动完成功能带来的主要好处:

  • 减少拼写错误:自动建议表名、列名和关键字。
  • 提高编写速度:无需完全记忆或手动输入冗长的名称。
  • 确保语法正确:通过提示正确的SQL结构。

并行查询与结果比较

除了自动完成,另一个非常有用的功能是能够同时运行多个查询并比较结果。

有时你需要对SQL查询进行一些调整,例如不仅限制返回的条目数量,还包括进行过滤、选择特定数据或以特定方式排序。能够比较两次查询的结果是非常宝贵的。

在VS Code中,你可以轻松实现这一点。以下是进行操作的基本步骤:

  1. 在编辑器中打开两个SQL查询文件或分栏。
  2. 分别执行每个查询。
  3. 并排查看或导出结果集,进行直观对比。

这种方法能帮助你快速验证查询修改是否产生了预期的效果。

总结

本节课中我们一起学习了执行MySQL查询的两个关键挑战:复杂查询的编写错误和结果验证。我们探讨了如何利用VS Code等工具的自动完成功能来避免语法和拼写错误,以及如何通过并行运行和比较查询结果来高效调试和优化SQL语句。掌握这些工具技巧能显著提升数据查询工作的准确性和效率。

122:使用VSCode执行MySQL查询 🛠️

在本节课中,我们将学习如何使用VSCode连接并操作MySQL数据库。我们将涵盖创建数据库、执行SQL脚本、填充数据以及运行查询等核心操作。


上一节我们介绍了如何连接到MySQL数据库。本节中,我们来看看如何在VSCode中与数据库进行更深入的交互。

我们已通过VSCode连接到本地MySQL服务器。在界面中可以看到四个系统自带的内部数据库。接下来,我们将创建一个新的数据库并运行一些查询。

创建新数据库

以下是创建新数据库的步骤。

首先,我们需要运行一个SQL脚本来创建数据库和表。在VSCode的资源管理器中,打开一个名为 setup.sql 的脚本文件。该脚本内容如下:

CREATE DATABASE IF NOT EXISTS ratings;
USE ratings;
CREATE TABLE ratings (
    name TEXT(556),
    rating TEXT(120),
    region TEXT(256)
);

这段代码执行三个操作:

  1. 创建一个名为 ratings 的数据库(如果它尚不存在)。
  2. 切换到 ratings 数据库。
  3. ratings 数据库中创建一个名为 ratings 的表,该表包含三个文本类型的列:nameratingregion

要执行此脚本:

  1. 确保在VSCode的MySQL扩展中已选中 localhost 连接。
  2. 打开 setup.sql 文件。
  3. 使用命令面板(Ctrl+Shift+P 或 Cmd+Shift+P),搜索并选择 “Run MySQL Query”

执行后,输出面板会显示操作结果,如“affected rows”。此时,左侧的数据库列表可能不会立即刷新。只需在“MySQL”视图中的 localhost 上右键点击,选择 “Refresh”,即可看到新创建的 ratings 数据库及其内部的表结构。

向数据库填充数据

数据库创建好后,下一步是向表中插入数据。

我们打开另一个SQL脚本文件,例如 populate_data.sql。该文件的第一行通常是 USE ratings;,用于确保在正确的数据库中操作,其后包含大量的 INSERT 语句。

再次使用命令面板的 “Run MySQL Query” 来执行这个脚本。脚本运行后,数据便被插入到 ratings 表中。

为了验证数据是否成功插入,我们可以在 ratings 数据库上右键点击,选择 “New Query”。这会在编辑器中打开一个新的SQL文件。

执行查询与验证数据

现在,我们可以编写SQL查询来检查数据。

在新打开的查询编辑器中,输入以下查询语句,用于查看表中的前100条记录:

SELECT * FROM ratings LIMIT 100;

同样,通过命令面板执行 “Run MySQL Query”。结果面板会显示查询到的数据,包括 nameratingregion 字段。

我们也可以执行更具体的查询。例如,只想查看 region 字段的前10个结果:

SELECT region FROM ratings LIMIT 10;

执行后,输出将只包含地区信息。输出面板不仅显示数据,还能在SQL语法出现错误时提供详细的错误信息,这对于调试非常有帮助。

处理错误与使用技巧

VSCode的MySQL扩展提供了便捷的查询历史对比功能。你可以通过结果面板的选项卡在不同查询结果之间切换。

如果查询语句存在语法错误,扩展会明确提示。例如,在LIMIT子句中错误地添加了括号(某些SQL方言支持,但MySQL不支持):

SELECT * FROM ratings LIMIT(10);

执行此错误查询后,输出面板会显示具体的错误信息,帮助你快速定位和修正问题。


本节课中我们一起学习了使用VSCode操作MySQL数据库的完整流程。我们掌握了如何执行SQL脚本来创建数据库和表,如何向表中填充数据,以及如何运行查询来验证和探索数据。同时,我们也了解了如何利用VSCode的输出面板来查看结果和调试SQL语句错误。这些技能是进行数据操作和分析的基础。

123:MySQL查询执行回顾 🗄️

在本节课中,我们将回顾如何连接到正在运行的MySQL数据库,并了解在建立新连接时需要注意的关键事项。我们将涵盖连接参数、安全性考量以及如何通过VS Code扩展执行查询。

概述

本节课程将引导你完成连接到MySQL数据库的步骤,并解释连接过程中涉及的各个组件。我们将从连接参数开始,然后讨论安全设置,最后演示如何执行查询。

连接参数详解

上一节我们介绍了数据库的基本概念,本节中我们来看看建立连接所需的具体参数。连接到MySQL数据库需要提供几个关键信息。

以下是建立连接时必须配置的核心参数:

  • 主机地址与端口:你需要知道数据库服务器的地址(如 localhost 或一个IP地址)和它监听的端口号。默认的MySQL端口是 3306
  • 用户名与密码:用于身份验证。我们示例中使用了 root 用户且密码为空。
  • SSL证书路径:为了确保连接安全加密,你可能需要配置SSL证书的路径。

安全性与用户权限

了解了连接参数后,我们需要关注连接的安全性。我们之前使用 root 用户且未设置密码进行连接。

root 是MySQL中权限最高的用户。在生产环境中,使用空密码的 root 用户极不安全。这仅适用于本地测试环境。对于生产系统,应创建具有最小必要权限的专用用户,并设置强密码。

在VS Code中执行查询

配置好连接并理解安全考量后,我们就可以与数据库交互了。我们通过VS Code中的相应扩展,使用已设置的连接字符串来执行SQL命令。

我们运行了一些查询,并通过文本数据看到了返回的结果。你现在应该能够熟练地使用文本数据连接任何MySQL数据库,并通过VS Code有效地检索数据、操作数据以及管理数据库。

总结

本节课中我们一起学习了连接到MySQL数据库的完整流程。我们明确了连接所需的地址、端口、用户名和密码等参数,讨论了使用 root 用户和空密码的安全风险,并最终在VS Code环境中成功执行了SQL查询,验证了连接的有效性。

124:数据库导入的挑战 🧩

在本节课中,我们将要学习数据库导入过程中可能遇到的主要挑战。理解这些挑战是确保数据在不同环境(如从本地迁移到生产环境)间安全、准确转移的基础。

概述

数据库导入是将一个数据库的结构、配置或内容迁移到另一个数据库的过程。这在数据工程中是一项常见且关键的任务,无论是为了设置生产环境、实现高可用性,还是为了数据复用。

数据库迁移的常见场景

上一节我们概述了数据库导入的重要性,本节中我们来看看它通常发生在哪些具体场景中。

你可能会在本地使用一个数据库,并对其状态感到满意,准备开始定义生产环境的配置。这时,你需要将本地数据库的定义、设置和配置迁移到生产环境中。

即使不涉及生产环境迁移,将数据从一个数据库转移到另一个数据库本身也是一项复杂且必要的操作。这是实现高可用性、数据复用和多数据库协作的基础。

面临的挑战与必要性

了解了常见场景后,我们来看看为什么这个过程充满挑战且至关重要。

将数据库内容从一个库迁移到另一个库非常棘手,但这又是数据工程中的基础技能。你无疑会经常进行此类操作。

因此,不仅需要知道如何操作,还需要知道如何高效地操作。这不仅仅涉及你可能已经熟悉的SQL,还包括处理其他数据格式,如CSV和JSON。

总结

本节课中我们一起学习了数据库导入的核心挑战与场景。我们了解到,无论是从本地环境迁移到生产环境,还是在不同数据库间转移数据,都会面临复杂性。掌握高效完成这些任务的方法,包括使用SQL及处理CSV、JSON等格式,是构建可靠数据管道和实现系统高可用性的关键基础。

125:将CSV数据导入MySQL 📊

概述

在本节课中,我们将学习如何将CSV格式的数据导入到MySQL数据库中,以及如何将MySQL中的数据导出为CSV文件。这是数据工程中常见的数据交换操作。

导入CSV数据到MySQL

将数据导入MySQL通常通过SQL文件完成。然而,使用其他格式(如CSV)导入数据也是完全可行的。本节中,我们来看看如何将CSV文件加载到MySQL中。

这个过程涉及几个步骤,可能会有些复杂。我们有一个名为 load_csv.sql 的SQL文件。在第一行,它这样写道:

LOAD DATA LOCAL INFILE 'ratings.csv' INTO TABLE ratings

这条语句意味着它将读取 ratings.csv 文件,并将其数据加载到名为 ratings 的表中。

以下是定义数据格式的选项:

  • 字段分隔符:对于CSV文件,字段通常由逗号分隔。你可以根据实际分隔符进行调整。
  • 行终止符:每条记录(行)由换行符终止。
  • 变量映射:一个有趣的特性是,你可以为数据的每个部分命名并设置变量。例如,使用 @name@rating@region 这样的变量来捕获CSV中的列,然后在语句的右侧将它们映射到数据库表的对应列。这不是一个直观的语法,但这是实现此功能的方式。

现在,如果我尝试运行这个查询,可能会遇到问题。执行 run mysql query 命令后,系统可能会报错:“Loading local data is disabled; this must be enabled both on the client and server sides”。

这个错误意味着客户端和服务器都必须启用从本地文件加载数据的功能,此操作才能成功。如果你的环境已启用此功能,那么上述方法就是导入CSV的标准流程。

从MySQL导出数据到CSV

上一节我们介绍了如何导入,本节中我们来看看如何从MySQL数据库导出数据为CSV格式。假设我们的 ratings 表中已有数百条记录,我们希望将其导出。

我们来看一个名为 export_csv.sql 的导出文件示例。在这个文件中,我们执行以下操作:

首先,我们编写一个查询,从 ratings 表中选择 nameratingregion 字段。然后,我们使用 UNION 语句(如果需要合并初始标题行和实际数据)。最后,我们使用 INTO OUTFILE 子句将结果输出到指定路径。

以下是导出时可以定义的格式参数:

  • 输出路径:需要提供一个绝对路径来指定CSV文件的生成位置。
  • 字段分隔符:使用 FIELDS TERMINATED BY 指定,对于CSV,通常是逗号 ,
  • 值包围符:使用 ENCLOSED BY 指定,例如双引号 "。这提供了灵活性,如果你的数据本身包含双引号,可以选择其他字符以避免转义问题。
  • 行终止符:使用 LINES TERMINATED BY 指定,通常是换行符。

现在,我们执行这个导出查询。运行 run mysql query 命令后,你可以在左侧文件列表中看到新生成的 table.csv 文件。点击查看,你会发现它包含了数据库中 ratings 表的所有数据。

这是一种从MySQL导出数据的非常有效且便捷的方法。导出的CSV文件可以很方便地加载到Python中(例如使用pandas库)进行进一步的数据处理,使得数据在不同工具间的流转更加顺畅。

总结

本节课中,我们一起学习了在MySQL中进行CSV格式数据导入和导出的方法。虽然SQL文件仍是MySQL数据迁移更常见的方式,但掌握CSV的导入导出能让你在处理需要与其他数据分析工具(如Python/pandas)交互的数据时更加得心应手。我们了解了导入时可能遇到的本地文件加载权限问题,以及导出时如何灵活定义字段分隔符、文本包围符等格式细节。

126:从MySQL导出数据 📤

在本节课中,我们将学习如何从MySQL数据库中导出数据。导出数据是数据工程中的一项重要技能,它允许我们备份数据、迁移数据或在不同的系统之间共享数据。我们将重点介绍使用 mysqldump 命令行工具来完成这项任务。

上一节我们介绍了如何向MySQL导入数据,本节中我们来看看如何将数据从MySQL中导出。

准备工作

首先,你需要确保你的系统上安装了 mysqldump 工具。在Linux或类Unix系统中,你可以使用 which 命令来检查。

which mysqldump

如果该工具已安装,此命令将返回 mysqldump 的安装路径。

使用 mysqldump 导出数据

mysqldump 是一个功能强大的工具,它可以生成一个包含数据库结构和数据的SQL脚本文件。以下是导出数据库的基本步骤。

以下是使用 mysqldump 导出名为 ratings 的数据库的命令示例:

mysqldump -u root -p ratings > export.sql

在这个命令中:

  • -u root 指定用户名为 root
  • -p 表示稍后会提示你输入密码。为了安全,不建议在命令行中直接输入密码。
  • ratings 是要导出的数据库名称。
  • > export.sql 使用重定向操作符 > 将命令的输出(即SQL语句)保存到名为 export.sql 的文件中。

执行命令后,系统会提示你输入密码。输入正确密码后,export.sql 文件就会被创建在当前目录下。

理解导出文件

生成的 export.sql 文件包含了重建整个数据库所需的所有SQL命令。它主要包含以下内容:

  1. 删除和创建表的语句:首先会删除已存在的表(如果存在),然后重新创建它们,并指定正确的存储引擎和字符集。
  2. 插入数据的语句:文件会将所有数据打包成一个或多个 INSERT 语句。为了提高效率,它通常会将大量数据合并到一个大型的 INSERT 语句中,而不是分成许多小语句。

实践:还原导出的数据

为了验证导出文件的有效性,我们可以模拟一个数据恢复的场景:先删除原数据库,然后利用导出的SQL文件重建它。

以下是操作步骤:

  1. 删除原数据库:在MySQL客户端或管理工具中执行以下命令。

    DROP DATABASE ratings;
    

    执行后,ratings 数据库将被移除。

  2. 创建空数据库:我们需要先创建一个同名的空数据库。

    CREATE DATABASE ratings;
    

  1. 还原数据:现在,使用我们之前导出的 export.sql 文件来填充这个新数据库。首先,需要确保SQL文件在操作前选择了正确的数据库。你可以在文件开头添加 USE ratings; 语句,或者在使用 mysql 命令还原时指定数据库。
    使用 mysql 命令行还原:

    mysql -u root -p ratings < export.sql
    

    或者在MySQL客户端中直接运行 export.sql 文件中的SQL语句。

  2. 验证数据:数据还原后,可以运行一个简单的查询来验证。

    SELECT * FROM ratings LIMIT 10;
    

    如果查询成功返回之前的数据行,说明导出和还原过程成功。

总结

本节课中我们一起学习了如何使用 mysqldump 工具从MySQL数据库导出数据。我们了解了导出命令的基本语法,生成了一个包含数据库结构和数据的SQL文件,并通过一个完整的删除与重建的流程,实践了如何利用这个导出文件来恢复数据库。这种方法是一种非常直接且有效的数据库备份和迁移方式,mysqldump 工具会处理服务器配置等细节,确保数据库能被无缝重建。

127:MySQL数据导入导出回顾 🗃️

在本节课中,我们将回顾使用MySQL进行数据导入和导出的核心技术与场景。你将学习到如何通过“数据库转储”来备份、恢复或迁移数据,这是数据工程中一项至关重要的技能。

概述

在数据库管理中,数据迁移可能非常棘手。即使在灾难恢复场景下,例如数据库完全崩溃或数据遭到破坏时,你依然可以有效地使用MySQL及其工具,以一致的方式将所有数据恢复到位。本节课我们将回顾一些用于提取MySQL信息的技术。

数据库转储:核心备份技术

在MySQL领域,提取所有数据库信息的过程被称为“转储”。一个数据库转储能获取数据库内所有数据的完整快照。

我们利用这个转储文件来重建数据库。这是一种非常高效的技术。

核心命令示例:

# 导出整个数据库
mysqldump -u username -p database_name > backup_file.sql

# 导入数据库
mysql -u username -p database_name < backup_file.sql

迁移场景与应用

毫无疑问,你将来会发现自己处于需要将一个数据库迁移到另一个数据库的情况。这时,你就可以运用我们今天所看到的工具和技术来尝试管理这些数据,并在数据库之间移动它们。

以下是常见的迁移步骤:

  1. 使用 mysqldump 命令从源数据库导出数据。
  2. 将生成的SQL文件传输到目标服务器。
  3. 在目标服务器上使用 mysql 命令导入数据。

总结

本节课我们一起学习了MySQL数据导入与导出的关键方法。我们了解了“数据库转储”的概念及其在数据备份、恢复和迁移中的核心作用。掌握这些工具和技术,将使你能够从容应对数据管理中的各种挑战,确保数据在不同环境间安全、一致地移动。

128:MySQL高级操作概览 🛠️

在本节课中,我们将学习几种与MySQL交互的“黑客式”高效方法。这些方法不依赖于图形界面或复杂的SDK,而是利用Python、Bash等系统自带工具,帮助你在资源有限或需要快速原型开发的环境中,灵活地处理数据。

核心概念:命令行与标准库的力量 💪

上一节我们介绍了数据库的基本操作,本节中我们来看看如何利用系统原生工具进行高效的数据交互。其核心优势在于避免陷入复杂的包管理,并能快速构建解决方案

方法一:查询结果导出到文件 📁

你可以将MySQL的查询结果直接导出到磁盘文件,例如CSV格式。之后,便能使用Linux命令行工具或Python标准库对其进行处理。

以下是使用mysql命令行客户端导出查询结果到CSV文件的基本命令:

mysql -u [用户名] -p[密码] -h [主机] [数据库名] -e "SELECT * FROM [表名];" | sed 's/\t/,/g' > output.csv

代码解释

  • mysql -e 执行后面的SQL语句。
  • sed 's/\t/,/g' 将制表符(默认分隔符)替换为逗号,以生成CSV格式。
  • > output.csv 将输出重定向到文件。

方法二:使用Python标准库构建简易Web服务 🌐

Python标准库功能强大,无需安装第三方库即可完成许多任务,例如快速搭建一个临时的Web服务来提供数据。

以下是使用Python标准库http.server模块快速启动一个Web服务,并读取CSV文件的基本思路:

  1. 首先,将MySQL数据导出为CSV文件(如上述方法)。
  2. 编写一个简单的Python脚本,使用http.server模块创建服务,并在请求时读取并返回CSV文件内容。
# 这是一个简化的示例逻辑
import http.server
import socketserver

PORT = 8000

class MyHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        # 在此处添加读取CSV文件并返回内容的逻辑
        self.send_response(200)
        self.end_headers()
        # 示例:返回文件内容
        with open('output.csv', 'r') as f:
            self.wfile.write(f.read().encode())

with socketserver.TCPServer(("", PORT), MyHandler) as httpd:
    print(f"服务启动于端口 {PORT}")
    httpd.serve_forever()

应用场景:这种方法适用于黑客松、实验室快速原型验证等需要极速搭建数据接口的场景。

方法三:使用mysqldump与Linux工具链 🔧

另一种强大的方式是使用mysqldump工具导出整个数据库或特定表的SQL格式备份,然后结合Linux文本处理工具进行解析和操作。

以下是相关命令示例:

# 导出整个数据库的结构和数据
mysqldump -u [用户名] -p[密码] [数据库名] > dump.sql

# 结合grep、sed等工具处理导出的SQL文件
# 例如:查找包含特定关键词的INSERT语句
grep "特定关键词" dump.sql | head -n 20

优势:你可以利用grepsortawksed等强大的Linux命令行工具,对导出的结构化SQL数据进行灵活的过滤、分析和转换,无需启动完整的数据库服务。

总结 📝

本节课中我们一起学习了三种与MySQL交互的高级操作方法:

  1. 导出查询结果到文件:便于使用通用工具进行后续处理。
  2. 利用Python标准库构建Web服务:快速创建临时的数据API接口。
  3. 结合mysqldump与Linux工具链:对数据库转储文件进行灵活的文本级操作。

这些“黑客风格”的技巧强调利用现有环境工具,以最小依赖和最快速度解决数据提取、共享和初步处理的问题。在考虑引入复杂的工业级框架或库之前,尝试这些基础方法往往能更高效地应对临时性需求或完成概念验证。

129:终端操作MySQL 🖥️

在本节课中,我们将学习如何在终端环境中操作MySQL数据库。我们将从登录开始,逐步完成创建数据库、定义表结构、插入数据以及查询数据等核心操作。通过终端直接与数据库交互,可以帮助你更清晰地理解每个步骤背后的原理。

登录MySQL

首先,我们需要登录到MySQL服务器。我们将以root用户身份登录,这是新安装MySQL后的默认管理员账户。

以下是登录命令:

mysql -u root

执行此命令后,系统会提示你输入密码(如果已设置)。对于新安装,初始密码可能为空。

修改用户密码

成功登录后,一个常见的后续步骤是修改root用户的密码,以增强安全性。

我们可以使用ALTER USER语句来完成此操作。以下是修改密码的命令:

ALTER USER 'root'@'localhost' IDENTIFIED BY 'DB_password';

请将'DB_password'替换为你想要设置的新密码。

创建数据库

修改密码后,下一步是创建一个新的数据库。默认情况下,MySQL安装后没有用户数据库。

以下是创建名为my_demo的数据库的命令:

CREATE DATABASE my_demo;

选择数据库

创建数据库后,我们需要明确指定要使用哪个数据库来进行后续操作。

使用USE语句来选择我们刚刚创建的数据库:

USE my_demo;

执行此命令后,终端提示符可能会发生变化,表明当前会话已切换到my_demo数据库。

创建数据表

选择了数据库,我们就可以在其中创建存储数据的表。可以将表想象成电子表格,它由行和列组成,但每一列都需要预先定义数据类型。

以下是创建一个简单用户表的命令。该表包含idnameemail三列:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100)
);

在这个例子中:

  • id 是整数类型,并设置为自动递增的主键。
  • nameemail 都是可变长度的字符串类型,最大长度为100个字符。

向表中插入数据

表结构定义好后,我们就可以向其中添加数据了。使用INSERT INTO语句来添加新记录。

以下是向users表中插入两条用户记录的示例:

INSERT INTO users (name, email) VALUES
('John', 'john@gmail.com'),
('Jane', 'jane@yahoo.com');

这条命令会向表中添加两行数据,分别对应John和Jane的用户信息。

查询数据

数据插入后,我们就可以使用SELECT语句来查询和查看表中的数据了。这是与数据库交互中最常用的操作之一。

要查看users表中的所有数据,可以使用以下命令:

SELECT * FROM users;

执行后,终端会返回一个结果集,显示表中所有的行和列,即我们刚刚插入的John和Jane的信息。

总结

本节课中,我们一起学习了在终端中操作MySQL数据库的完整基础工作流程。我们从登录MySQL开始,依次完成了修改密码、创建数据库、选择数据库、定义表结构、插入数据以及最终查询数据。这个流程是理解数据库操作的核心,通过在终端中直接实践,你能更牢固地掌握每个命令的作用和数据库的基本概念。

130:数据库归档与删除

在本节课中,我们将学习MySQL数据库的两个核心操作:备份(归档)与删除。你将掌握如何安全地备份整个数据库,如何在不同数据库间复制数据,以及如何谨慎地执行删除操作。这些是数据工程师在管理和维护数据库时必须掌握的基本技能。

数据库备份

上一节我们介绍了数据库的基本操作,本节中我们来看看如何为数据库创建安全备份。在执行任何重大变更(如数据迁移或结构修改)之前,备份数据库是一项至关重要的最佳实践。

以下是使用 mysqldump 命令进行完整备份的步骤:

  1. 打开终端,输入备份命令。该命令将连接MySQL并导出指定数据库的所有内容。
    mysqldump -u root -p my_demo > backup_my_demo.sql
    
  2. 系统会提示输入 root 用户的密码,输入后命令开始执行。
  3. 备份完成后,建议立即检查备份文件以确保其有效。首先,可以查看文件大小,确认其不为空。
    du -h backup_my_demo.sql
    
  4. 接着,可以查看备份文件的开头部分,确认其中包含正确的SQL语句(如 CREATE TABLE)和数据。
    head -n 100 backup_my_demo.sql
    

通过以上步骤,我们就获得了一个包含数据库完整结构和原始数据的备份文件。这是一个非常实用的技术。

数据库间复制数据

现在我们已经有了一个安全的备份,接下来可以尝试对数据库进行一些操作。本节我们将学习如何在不同的数据库之间复制数据。

首先,我们需要连接到MySQL服务器:

mysql -u root -p

输入密码后,我们便进入了MySQL交互界面。

我们可以先列出所有数据库,确认目标数据库存在:

SHOW DATABASES;

假设我们想创建一个新数据库 my_demo_2,并将原数据库 my_demo 中的 users 表复制过来。以下是操作流程:

  1. 创建新数据库。
    CREATE DATABASE my_demo_2;
    
  2. 切换到新数据库。
    USE my_demo_2;
    
  3. 通过 CREATE TABLE ... AS SELECT ... 语句,从原数据库复制表结构和数据。
    CREATE TABLE users_2 AS SELECT * FROM my_demo.users;
    
  4. 操作完成后,可以退出MySQL,然后重新连接并列出数据库,验证 my_demomy_demo_2 都已存在。

这个技巧非常有用,无论是用于数据归档、创建测试环境,还是迁移部分数据。

删除表与数据库

最后,我们来学习如何清理不再需要的实验性数据。删除操作需要格外谨慎,因为一旦执行,数据可能无法恢复。这正是我们在第一步进行备份的原因。

假设我们要清理刚才创建的 my_demo_2 数据库:

  1. 确保当前使用的是目标数据库。
    USE my_demo_2;
    
  2. 首先删除其中的表。
    DROP TABLE users_2;
    
  3. 最后,删除整个数据库。
    DROP DATABASE my_demo_2;
    
  4. 执行 SHOW DATABASES; 命令,可以看到 my_demo_2 已不存在。

注意DROP DATABASE 是一个极其危险的命令,它会永久删除数据库及其所有内容。务必在确认无误并拥有备份的情况下执行。

本节课中我们一起学习了MySQL数据库管理的三个关键操作:使用 mysqldump 进行备份、在不同数据库间复制数据、以及安全地执行删除。请始终牢记,在执行任何可能影响数据的操作前进行备份,是保障数据安全的最重要原则。

131:导入并使用Sakila示例数据库

在本节课中,我们将学习如何下载、导入并使用MySQL官方提供的Sakila示例数据库。这是一个经典的电影租赁数据库,非常适合用于练习SQL查询和数据库操作。

获取Sakila数据库

首先,我们需要获取Sakila数据库。这是一个很好的参考数据库,可以帮助你理解特定类型数据库的结构和操作。

你可以从MySQL官方网站获取该数据库的文档和下载文件。访问以下链接:
dev.mysql.com/doc/index-other.html

下载包中通常包含三个文件:

  • Schema文件:用于创建数据库表结构。
  • Data文件:包含填充表的数据。
  • .MWB文件:用于MySQL Workbench,我们暂时不需要关注。

在终端中导入数据库

上一节我们介绍了如何获取数据库文件,本节中我们来看看如何在MySQL命令行中导入它们。

首先,我们可以在终端中查看一下下载的文件。使用ls命令可以看到文件列表,其中数据文件通常较大,而结构文件较小。你也可以用tail命令预览一下数据文件的内容,感受一下数据的格式。

接下来,我们需要登录MySQL并导入文件。具体步骤如下:

  1. 登录MySQL服务器:
    mysql -u root -p
    
    输入密码后进入MySQL命令行界面。

  1. 导入数据库结构文件(schema):
    SOURCE /完整路径/sakila-schema.sql;
    
    这个过程可能需要一些时间来创建所有表。

  1. 导入数据文件(data):
    SOURCE /完整路径/sakila-data.sql;
    
    这个过程会将示例数据填充到刚创建的表中。

探索数据库结构

成功导入数据库后,下一步就是探索它的结构。参考官方文档总是一个好主意。

首先,切换到sakila数据库:

USE sakila;

然后,查看数据库中的所有表:

SHOW FULL TABLES;

你会看到数据库中包含actorfilmlanguagecategory等表。

官方文档的“结构”部分非常有用,它可以展示每张表的详细信息。例如,如果你想查看actor表的结构,可以查看其列定义。此外,文档还提供了数据库的模式图,展示了表与表之间的关系。

对于数据库工程师或数据工程师来说,在开始构建结构化数据库之前,花时间理解模式图是至关重要的。你需要理清数据的连接关系以及如何构建查询。

执行基础查询操作

了解了数据库结构后,我们现在可以开始执行一些查询来熟悉数据。

探索一张新表时,通常的第一步是查看其结构:

DESCRIBE actor;

这能显示actor表包含哪些列及其数据类型。

接下来,我们可以进行简单的数据查询。在不确定查询结果大小时,使用LIMIT子句是个好习惯:

SELECT * FROM actor LIMIT 5;

这将只返回前5行数据。你可以将LIMIT 5改为LIMIT 20来获取更多记录。

实践复杂查询:表连接

为了获取更有价值的信息,我们经常需要连接多张表。以下是一个连接查询的例子,它从多张表中组合信息:

SELECT f.title, f.rental_rate, a.first_name, a.last_name
FROM film f
JOIN film_actor fa ON f.film_id = fa.film_id
JOIN actor a ON fa.actor_id = a.actor_id
LIMIT 10;

这个查询将电影标题、租金、以及出演该电影的演员姓名组合在一起。这类查询可用于构建数据看板或报告。

使用聚合函数

除了连接,聚合函数也是数据分析的利器。例如,如果你想统计数据库中有多少部电影:

SELECT COUNT(*) AS total_films FROM film;

查询结果显示共有1000部电影。

我们也可以结合连接进行稍复杂的聚合查询。例如,一个更简单的连接查询可以获取电影及其对应的语言信息:

SELECT f.title, l.name AS language
FROM film f
JOIN language l ON f.language_id = l.language_id
LIMIT 10;

通过这个查询,我们可以看到每部电影对应的语言。

总结

本节课中我们一起学习了如何导入并使用Sakila示例数据库。我们首先从MySQL官网下载了数据库文件,然后在终端中通过SOURCE命令将其导入。接着,我们使用USESHOW TABLESDESCRIBE等命令探索了数据库结构。最后,我们实践了基础查询、表连接(JOIN)和聚合函数(如COUNT)等核心SQL操作。

Sakila数据库因其完善的官方文档而成为提升SQL技能的绝佳工具。通过反复查询和探索这个结构清晰的示例数据库,你可以深入理解数据库设计的细节和复杂查询的构建方法。

132:修改Sakila数据库 🗄️

在本节课中,我们将学习如何扩展一个已有的MySQL示例数据库。我们将通过创建一个新表、向其中插入数据、进行关联查询,最后清理实验性数据,来模拟一个电影导演为影片添加后期制作笔记的场景。

概述

我们将使用MySQL自带的sakila示例数据库,它模拟了一个电影租赁商店的数据。我们的目标是扩展这个数据库,为其添加一个用于存储导演笔记的新表,并进行一系列操作来演示基本的数据库修改流程。

连接到数据库并查看现有结构

首先,我们需要登录到MySQL服务器并查看现有的数据库和表结构。

以下是通过命令行登录MySQL的示例代码:

mysql -u root -p

登录后,我们可以查看所有数据库,并选择使用sakila数据库。

SHOW DATABASES;
USE sakila;

选择了数据库后,查看其中的所有表是一个好的开始,这能让我们了解现有的数据结构。

以下是查看sakila数据库中所有表的SQL语句:

SHOW TABLES;

执行后,我们可以看到诸如actorcategoryfilm等表。

创建新表以扩展数据模型

上一节我们查看了数据库的现有结构,本节中我们来看看如何扩展它。假设我们是一名导演,希望在数据库中添加一个表来记录影片的后期制作笔记。

我们需要创建一个名为director_notes的新表。这个表将包含笔记ID、对应的影片ID、笔记内容以及编辑日期。

以下是创建director_notes表的SQL语句:

CREATE TABLE director_notes (
    note_id INT AUTO_INCREMENT PRIMARY KEY,
    film_id INT,
    director_note TEXT,
    edit_date DATE
);

在这条语句中:

  • note_id 是自动递增的主键。
  • film_id 是整数,用于关联film表。
  • director_note 是文本类型,用于存储自由格式的笔记。
  • edit_date 是日期类型。

执行此语句后,新表就创建好了。

向新表中插入数据

表创建完成后,我们需要向其中添加一些示例数据,使其变得有用。我们将使用INSERT语句来添加两条导演笔记。

以下是向director_notes表插入数据的SQL语句:

INSERT INTO director_notes (film_id, director_note, edit_date)
VALUES
    (1, 'Fix the sound in minute 20.', '2023-10-26'),
    (2, 'Shorten the battle scene.', '2023-10-27');

这两条记录模拟了导演对ID为1和2的影片提出的修改意见。

关联查询以获取有用视图

仅仅查看笔记表本身信息有限。通常,我们需要将新表与原有数据关联起来,以获得更有意义的视图。例如,将导演笔记与影片名称一起显示。

以下是通过JOIN关联film表和director_notes表进行查询的SQL语句:

SELECT f.title, dn.director_note, dn.edit_date
FROM film f
LEFT JOIN director_notes dn ON f.film_id = dn.film_id
ORDER BY dn.edit_date DESC;

这条查询语句:

  • film表中选择影片标题(title)。
  • director_notes表中选择笔记内容(director_note)和编辑日期(edit_date)。
  • 使用LEFT JOIN将两个表通过film_id连接起来,确保即使某些影片没有笔记也会被列出。
  • 结果按编辑日期降序排列。

执行后,我们可以看到所有影片的列表,但只有Academy DinosaurAce Goldfinger这两部影片旁边显示了导演笔记。这个查询结果本身就可以作为一个非常有用的视图,供导演浏览所有影片及其待办事项。

清理实验性数据

在完成实验或测试后,清理创建的实验性表是一个好习惯。这可以通过DROP TABLE语句轻松完成。

以下是删除director_notes表的SQL语句:

DROP TABLE director_notes;

执行后,我们可以再次运行SHOW TABLES;来确认director_notes表已被成功移除。

总结

本节课中我们一起学习了如何修改一个现有的MySQL数据库。我们完成了从登录数据库、查看结构,到创建新表、插入数据、进行关联查询,最后清理临时表的完整流程。这个过程是数据库开发和数据工程中常见的操作模式。尝试在自己的环境中对示例数据库进行类似的扩展练习,是熟悉这些基本概念的好方法。

133:使用Bash管道与MySQL交互 🛠️💾

在本节课中,我们将学习如何结合使用Bash Shell和MySQL数据库。这种技术能让数据工程师在不依赖特定编程语言的情况下,高效地执行SQL查询、处理数据并实现自动化工作流。


上一节我们介绍了Bash和MySQL的基础操作,本节中我们来看看如何将两者结合,通过管道(Pipe)实现数据交互。

连接数据库与初始查询

首先,我们需要连接到MySQL数据库。使用以下命令,以root用户身份登录:

mysql -u root -p

连接成功后,我们可以查看当前服务器上有哪些数据库:

SHOW DATABASES;

假设我们有一个名为 sakila 的数据库(这是一个示例演员-电影数据库)。我们选择使用这个数据库:

USE sakila;

现在,我们可以执行一个简单的查询来验证连接和数据。例如,从 actor 表中查询前5条记录:

SELECT * FROM actor LIMIT 5;

将查询结果导出到文件

直接在MySQL客户端查看结果很有用,但更强大的功能是将数据导出到文件,以便后续在Bash中进行处理。

我们可以修改上面的查询,使用 INTO OUTFILE 语句将结果直接保存到磁盘。以下是具体步骤:

  1. 执行一个查询,并将其结果输出到指定路径的文件中。
  2. 我们将文件保存在一个名为 coder/project 的目录下,文件名为 actors.txt

对应的SQL命令如下:

SELECT * FROM actor INTO OUTFILE '/path/to/coder/project/actors.txt';

注意:你需要将 /path/to/coder/project/ 替换为你系统上的实际路径。执行此命令后,查询结果将保存到该文本文件中。

在Bash中处理导出的数据

将数据导出为文件后,我们可以退出MySQL,回到Bash Shell环境进行进一步操作。

exit

然后,切换到文件所在的目录:

cd /path/to/coder/project

现在,我们可以使用各种Bash命令来处理 actors.txt 文件。以下是几个例子:

  • 查看文件内容:使用 cat 命令。
  • 过滤数据:使用 grep 命令查找包含特定字符串的行。例如,查找姓氏为“Penn”的演员:
cat actors.txt | grep Penn

执行后,你可能会看到类似“Gary Penn”和“Richard Penn”的结果。

  • 将过滤结果保存到新文件:我们可以将上一步的过滤结果重定向到一个新文件。
cat actors.txt | grep Penn > penn_actors.txt

现在,penn_actors.txt 文件只包含了姓氏为“Penn”的演员数据。这个新文件可以被加载到另一个数据库表,或用于其他分析。

不登录MySQL直接导出数据

除了在MySQL内部操作,我们还可以完全不进入MySQL客户端,直接使用命令行工具导出数据。mysqldump 是一个强大的工具,用于备份或导出数据库和表。

例如,我们可以直接导出整个 sakila 数据库中的 actor 表的结构和数据:

mysqldump -u root -p sakila actor > full_actors_dump.sql

系统会提示你输入密码。执行成功后,full_actors_dump.sql 文件将包含创建 actor 表及插入所有数据的SQL语句。

对比与分析数据文件

回到Bash,我们可以轻松对比不同方式生成的数据文件大小,以验证我们的操作。

使用 ls -lh 命令查看目录下文件的详细信息:

ls -lh

输出可能显示:

  • actors.txt:8KB(直接从 SELECT ... INTO OUTFILE 导出的纯数据)
  • full_actors_dump.sql:12KB(包含SQL语句的完整转储)
  • penn_actors.txt:4KB(我们过滤后的子集)

这直观地展示了我们如何通过不同方法获取和筛选数据。

我们还可以使用 wc(word count)命令来统计文件的行数,这对于快速了解数据规模非常有用:

wc -l penn_actors.txt  # 统计penn_actors.txt的行数
wc -l actors.txt       # 统计原始actors.txt的行数

第一个命令可能输出 2,表示有2位姓“Penn”的演员。第二个命令可能输出 200,表示原表中共有200位演员。


本节课中我们一起学习了如何将Bash Shell与MySQL数据库协同工作。核心在于利用管道和重定向,将SQL的查询能力与Bash的文件处理、文本过滤能力结合起来。你掌握了:

  1. 在MySQL中使用 INTO OUTFILE 导出数据。
  2. 在Bash中使用 grep> 等命令处理和分析导出的数据。
  3. 使用 mysqldump 工具直接从命令行备份数据库表。
  4. 通过 lswc 命令快速检查文件属性和数据规模。

这种方法避免了为了简单数据操作而编写Python或Ruby脚本的复杂性,让数据工程师能够快速、灵活地完成许多常见任务。

134:通过Python标准库Web服务器连接MySQL 🐍

在本节课中,我们将学习如何结合使用MySQL、Bash和Python标准库,快速构建一个简单的数据服务。具体流程是:从MySQL数据库中提取数据,将其保存为CSV文件,然后使用Python内置的HTTP服务器创建一个可通过网络访问该数据的Web服务。


上一节我们介绍了数据提取的基本方法,本节中我们来看看如何将数据发布为Web服务。

首先,我们需要从MySQL数据库中查询数据。以下步骤在终端中完成。

  1. 连接到MySQL服务器:
    mysql -u root -p
    
  2. 查看并选择包含目标数据的数据库:
    SHOW DATABASES;
    USE sequila;
    
  3. 确认目标表存在:
    SHOW TABLES;
    
  4. 执行查询并将结果导出到CSV文件。此查询会进行一些数据清洗以确保格式正确:
    SELECT * FROM actor INTO OUTFILE '/tmp/actors.csv'
    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
    LINES TERMINATED BY '\n';
    
  5. 完成操作后,退出MySQL:
    EXIT;
    

现在,我们已经得到了数据文件 actors.csv。接下来,我们将使用Python标准库创建一个Web服务器来提供这些数据。

以下是实现此功能的核心Python代码 server.py

import csv
from http.server import HTTPServer, BaseHTTPRequestHandler

class SimpleHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # 设置响应头,表明返回的是CSV格式数据
        self.send_response(200)
        self.send_header('Content-type', 'text/csv')
        self.end_headers()

        # 打开CSV文件并将其内容写入HTTP响应体
        with open('/tmp/actors.csv', 'r') as csvfile:
            reader = csv.reader(csvfile)
            writer = csv.writer(self.wfile)
            for row in reader:
                writer.writerow(row)

# 在8081端口启动HTTP服务器
if __name__ == '__main__':
    server_address = ('', 8081)
    httpd = HTTPServer(server_address, SimpleHandler)
    print("Server started on port 8081...")
    httpd.serve_forever()

代码解析:

  • SimpleHandler 类继承自 BaseHTTPRequestHandler,用于处理HTTP GET请求。
  • do_GET 方法定义了服务器收到GET请求时的行为:设置正确的HTTP头,然后读取CSV文件的内容并流式传输回客户端。
  • 脚本最后部分创建了一个 HTTPServer 实例,并使其在端口8081上永久运行。

运行服务器非常简单。在终端中执行以下命令:

python server.py

服务器启动后,您会看到提示信息。现在,可以通过多种方式访问数据服务。

使用 curl 命令在终端中获取数据:

curl http://localhost:8081

您也可以在浏览器中直接访问 http://localhost:8081,浏览器会提示下载或显示CSV文件内容。


本节课中我们一起学习了如何利用现有的基础工具链构建快速数据解决方案。我们首先使用MySQL和SQL语句查询并导出了数据,然后仅用Python标准库中的 http.servercsv 模块,就创建了一个轻量级的Web数据服务。

这个过程展示了Python标准库的强大功能。很多时候我们过于关注复杂的第三方库,但对于构建快速、简单的原型或工具,熟练运用Bash、SQL和Python标准库,就能高效地开发出实用的解决方案。

135:Web应用程序与数据工程命令行工具导论

在本节课中,我们将学习如何将之前课程的知识整合起来,构建实用的命令行工具来解决数据工程问题。课程还将邀请客座讲师,分享进阶技巧。

大家好,我是诺亚·吉夫特,欢迎来到第四门课程。我们将在这门课程中涵盖数据工程的许多应用层面,重点整合第一门、第二门和第三门课程的内容,构建命令行工具来创建解决方案。我们还将与一些客座讲师交流,他们将展示如何提升到更高水平。让我们开始吧。

课程概述 🗺️

本课程是数据工程系列课程的第四部分。在前三门课程中,我们学习了Python、Bash和SQL的基础知识。现在,我们将把这些知识结合起来,专注于构建实际可用的命令行工具。这些工具是数据工程师日常工作中自动化任务、处理数据和构建解决方案的核心。

核心目标 🎯

本课程的核心目标是应用所学知识。我们将不再停留在理论层面,而是动手创建能够解决真实数据问题的工具。这意味着你需要准备好编写代码、调试脚本,并将不同的技术栈(如Python与Bash的交互)融合在一起。

你将学到什么 📚

以下是本课程将涵盖的主要内容:

  • 命令行工具开发:学习如何设计、编写和打包功能强大的命令行界面工具。
  • 技术整合:实践如何将Python脚本、Bash命令以及SQL查询有效地组合在一个工作流中。
  • 解决实际问题:通过项目案例,学习如何应对数据提取、转换、加载以及系统自动化等常见工程挑战。
  • 专家见解:聆听行业专家的经验分享,了解高级技巧和最佳实践。

学习准备 💻

为了跟上本课程的节奏,你需要:

  1. 熟练掌握前三门课程中关于Python编程、Bash shell和SQL数据库的基础内容。
  2. 准备好一个代码编辑器或集成开发环境。
  3. 在本地或云端配置好Python和数据库环境。

总结 ✨

本节课我们一起了解了第四门课程的总体框架和学习目标。我们明确了本课程的重点是应用与实践,即将分散的知识点整合起来,构建实用的数据工程命令行工具。从下一节开始,我们将深入命令行工具的设计理念,并动手创建我们的第一个工具。

准备好了吗?让我们进入构建环节。

136:核心概念概览 🧠

在本节课中,我们将要学习数据工程领域的几个核心概念。这些概念对于理解整个专业领域的知识体系至关重要,它们包括:Notebook的作用、命令行工具、DevOps理念、项目脚手架以及微服务。我们将逐一探讨它们的重要性及其在实践中的应用。

Notebook:冰山一角 🧊

上一节我们介绍了课程的整体框架,本节中我们来看看Notebook。为什么Notebook如此重要,值得我们详细讨论?

一个理解Notebook的好方法是将其视为“冰山一角”。它是你代码的执行界面,但在幕后,有许多复杂的过程正在发生。以SageMaker这样的环境为例,它利用数据湖的能力,提供近乎无限的存储和计算资源,并在机器学习或数据工程项目中反复移动和处理数据。Spark或SageMaker等工具都由Notebook来编排,但在后台,进行的是大规模的数据操作,例如启动计算节点来训练机器学习模型或进行预测推理。

因此,了解Notebook不仅要知道其表面功能,更要理解其底层运行机制。

命令行工具:高效原型设计的关键 🔧

接下来,我们将探讨命令行工具界面。对于数据和工程领域的原型设计而言,这是最重要、最需要了解的工具。

命令行工具之所以关键,是因为你可以获取一个输入,应用一个工作单元(可以简单到一个Python函数),然后获取结果并输出。接着,你可以通过管道操作将这个输出传递给另一个工作单元。这个工作单元可以是另一个命令行工具,它可以再次通过管道传递数据并执行任务。

因此,命令行界面对于构建快速、高效、单一用途的工具至关重要。

DevOps:持续改进与自动化 🤖

另一个至关重要但常被忽视的概念是DevOps。你可能听说过这个术语,让我们来定义一下它。

DevOps的核心是持续改进。其理念是通过将自动化应用于你的流程,来不断优化工作。这意味着,假设我们使用一个常见的构建系统(例如Github Actions),你会构建代码、测试代码,然后将其部署到生产环境。整个过程会持续优化,这是一个高效且完全自动化的流程。

一个检验是否实践了DevOps的试金石是:如果你的代码在推送到生产环境的过程中没有实现完全自动化,那么你做的就不是DevOps。DevOps是关于高质量代码的完全自动化,并持续使其变得更好,这也就是“改善”的概念。

项目脚手架:稳固的基石 🏗️

那么,如何实践DevOps呢?其中的一些细节我称之为“脚手架”。就像在海滩社区,房屋会建在桩基上以防洪水一样,Python项目也需要构建这样的结构。

以下是Python项目中最重要的三个组成部分:

  • Makefile:它允许你在其中定义一系列“配方”,以抽象出非常复杂的代码执行序列。这能为你节省时间,而不是耗费时间。在项目生命周期中,我建议所有Python开发者都使用Makefile来简化操作。在后续的部署过程中,你只需输入 make installmake testmake lint 等命令即可。
  • requirements.txt 文件:这个文件至关重要,因为它不仅记录了项目依赖的包,还记录了这些包的具体版本。动态拉取包的最新版本很容易导致项目崩溃,这在现实场景中屡见不鲜。通过使用requirements文件,你可以锁定版本号来避免这个问题。
  • Dockerfile:这个文件非常有用,因为它定义了项目的运行时环境,使你可以将项目容器化。然后,你可以将这个容器推送到容器注册表(例如亚马逊的ECR,或公共的Docker Hub)。其核心思想是,通过Dockerfile将你的代码和容器环境打包在一起。

因此,我认为大多数Python项目,特别是在数据工程和机器学习工程领域,都应该具备Makefile、requirements.txt文件和Dockerfile。

微服务:数据工程的网络接口 🌐

最后,我们将深入探讨微服务,这对于掌握数据工程至关重要。

微服务允许你将网络作为输入。这可以是一个HTTP端点:你访问一个网站,执行一些工作。这项工作可能小到只有五行Python代码,文件大小并不重要,重要的是它解决了什么问题。然后,大多数情况下,你会将输出以JSON数据结构的形式返回。这样,你就可以将这些数据发送给其他人。

在微服务部分,我们将讨论Flask、FastAPI,以及你可以构建的数据可视化服务。这些是掌握第四门课程内容的关键最后步骤。

总结 📝

本节课中,我们一起学习了数据工程的五个核心概念。我们了解了Notebook作为执行界面和编排工具的双重角色;认识了命令行工具在构建高效、模块化工具中的价值;深入理解了DevOps所倡导的自动化与持续改进理念;探讨了使用Makefile、requirements.txt和Dockerfile构建稳健Python项目的“脚手架”方法;最后,我们预览了微服务如何利用网络接口(HTTP/JSON)来构建灵活、可交互的数据应用。掌握这些概念,将为你在数据工程领域的深入学习和实践打下坚实的基础。

137:Jupyter Notebooks导论 🧪

在本节课中,我们将要学习如何使用Jupyter Notebooks。Jupyter Notebook是一个强大的交互式开发环境,广泛应用于数据分析、机器学习和科学计算。我们将从Jupyter Notebook的基础开始,逐步探索其高级功能和云端版本。

概述

首先,我们将介绍Jupyter Notebook及其增强版本JupyterLab。接下来,我们会深入了解CoLab,这是一个由Google托管的Jupyter Notebook服务。最后,我们将介绍AWS机器学习套件中的SageMaker服务。让我们开始吧。


Jupyter与JupyterLab

上一节我们概述了课程内容,本节中我们来看看Jupyter Notebook的核心组件。Jupyter Notebook是一个开源的Web应用程序,允许你创建和共享包含实时代码、公式、可视化和叙述性文本的文档。

JupyterLab是Jupyter Notebook的下一代用户界面。它提供了更灵活的工作区,允许你在一个标签页中并排排列Notebook、终端、文本编辑器和控制台。

以下是Jupyter Notebook的一些核心功能:

  • 交互式代码执行:你可以逐个单元地运行代码,并立即看到结果。
  • 支持多种语言:虽然最初是为Python(IPython)设计的,但现在通过内核支持Julia、R等语言。
  • 数据可视化:可以直接在Notebook中生成图表和图形。
  • Markdown支持:可以使用Markdown语法编写格式化的文本、添加标题、列表和链接。
  • 易于分享:Notebook文件(.ipynb)可以轻松导出为HTML、PDF等多种格式,便于分享和展示。

探索CoLab

在了解了本地Jupyter环境后,本节我们来看看一个流行的云端解决方案。CoLab(Colaboratory)是由Google提供的免费Jupyter Notebook服务。它完全在云端运行,无需任何设置,并且提供了免费的GPU和TPU资源,非常适合机器学习和深度学习项目。

CoLab的核心优势在于其便捷性和强大的计算资源。你只需要一个Google账户,就可以通过浏览器直接访问和运行Notebook。所有的代码执行都在Google的服务器上完成。

以下是使用CoLab的主要步骤:

  1. 访问 colab.research.google.com
  2. 点击“新建笔记本”或上传已有的.ipynb文件。
  3. 在代码单元中编写Python代码,例如:print("Hello, Colab!")
  4. 使用快捷键 Shift + Enter 或点击单元左侧的播放按钮来运行代码。
  5. CoLab会自动将你的工作保存到Google Drive。

AWS SageMaker简介

最后,我们将介绍一个企业级的机器学习平台。Amazon SageMaker是亚马逊云科技(AWS)提供的一项完全托管的服务,它包含了构建、训练和部署机器学习模型的完整工具链。SageMaker Studio提供了一个基于Jupyter的集成开发环境(IDE)。

SageMaker的优势在于它简化了机器学习工作流的每一步,从数据准备到模型部署,并且可以轻松地与其他AWS服务集成,实现大规模、生产级的机器学习应用。

SageMaker Notebook实例是一个完全托管的ML计算实例,预装了Jupyter和常用的数据科学库。你可以通过以下步骤启动一个Notebook实例:

  1. 登录AWS管理控制台,导航到SageMaker服务。
  2. 在左侧导航栏中,选择“Notebook实例”。
  3. 点击“创建Notebook实例”。
  4. 配置实例名称、实例类型(如ml.t3.medium)、IAM角色等设置。
  5. 点击“创建实例”,等待状态变为“InService”。
  6. 点击“打开JupyterLab”或“打开Jupyter”,开始使用。

总结

本节课中我们一起学习了Jupyter Notebooks生态系统。我们从基础的Jupyter和JupyterLab开始,了解了它们作为交互式计算环境的核心价值。接着,我们探索了Google CoLab,这是一个免配置、提供免费计算资源的云端Notebook服务,极大地方便了协作与实验。最后,我们介绍了AWS SageMaker,这是一个为企业级机器学习项目设计的强大平台,它将Jupyter环境与完整的ML工作流工具深度集成。

掌握这些工具,将帮助你在数据工程和机器学习项目中更高效地进行探索、开发和协作。

138:Jupyter入门指南 🚀

在本节课中,我们将学习Jupyter Notebook的核心组成部分,包括代码单元格、文本单元格、魔法命令的使用以及Jupyter Lab界面的基本操作。Jupyter Notebook是一个强大的交互式文档工具,广泛应用于数据科学和工程领域。

从IPython到Jupyter

Jupyter项目源于2006年开始的IPython项目。IPython是一个增强的交互式Python shell。2014年,Jupyter项目从IPython项目中分离出来,成为该项目中与编程语言无关的部分。

Jupyter Notebook是一种可执行文档。它包含多个单元格,这些单元格可以容纳代码、文本或图像。代码单元格在“内核”中运行。官方为Python、R和Julia语言提供了内核,其他内核则由更广泛的社区维护。

安装与启动Jupyter

我们可以使用Python的包管理工具pip来安装Jupyter。

pip install jupyter

安装过程需要一些时间。安装完成后,我们可以使用以下命令启动Jupyter Notebook。

jupyter notebook

此命令通常会自动在您的浏览器中打开一个Notebook窗口。如果浏览器没有自动打开,您可以查看命令行终端的输出。输出信息中会提供一个URL,您可以将其复制到浏览器地址栏中手动打开。

Jupyter Notebook界面初探

启动后,我们进入了Jupyter的界面,但此时还没有任何Notebook文件。我们可以通过选择内核来创建一个新的Notebook。

这是Notebook的主界面。顶部的“Untitled”是Notebook的名称,它也将作为保存时的文件名。Jupyter会定期自动保存您的工作。

界面上方有一系列控制按钮,用于执行各种操作,如运行单元格、保存文件等。您也可以按下 Esc 键,然后按 H 键,来调出键盘快捷键列表,这能极大提高操作效率。

总结

本节课我们一起学习了Jupyter Notebook的起源、基本概念以及如何安装和启动它。我们了解到Jupyter Notebook由代码、文本和图像单元格构成,并在特定的内核中运行代码。掌握这些基础知识是后续进行高效数据工程和科学计算的第一步。

139:Jupyter中的代码单元格

在本节课中,我们将学习Jupyter Notebook中代码单元格的核心功能与操作。你将了解如何编写、运行代码,使用代码补全和查看文档,以及如何在Python环境中执行Shell命令。

概述

Jupyter Notebook的默认单元格类型是代码单元格。我们可以在其中编写和执行Python代码。代码单元格支持语法高亮、代码补全和即时文档查看。运行代码后,结果会直接显示在单元格下方。此外,我们还可以在Notebook中直接执行Shell命令,并将命令的输出捕获为Python变量,实现Python与系统环境的交互。

代码单元格基础

当你首次打开或创建一个新的Notebook时,默认创建的单元格就是代码单元格。

我们可以在代码单元格中编写代码。请注意,代码具有语法高亮功能。

我们可以通过点击运行按钮来执行代码。

请注意,如果当前单元格下方已存在其他单元格,点击运行按钮会选中下方的单元格。我们也可以使用键盘快捷键 Shift + Enter 来达到同样的效果。

代码辅助功能

如果我们输入一个点号,然后按下 Tab 键,会触发代码补全功能。

开始输入时,可以看到系统会显示该模块下所有可用的函数。

如果我们在一个函数名后输入一个问号,然后按下 Shift + Enter,它会打开该函数的文档。

代码输出

我们调用的函数或方法的结果,会显示在单元格正下方的输出区域。

需要注意两点:首先,如果我们在一个单元格中定义了变量,这个变量的状态会被创建并保存。其次,该变量将在后续的单元格中持续存在。

输出可以是数值或文本,但也可以是更复杂的形式。例如,如果我们导入pandas库并打开一个CSV文件,我们刚刚加载的DataFrame的输出实际上是HTML格式的。你可以看到它是交互式的,并且内容比纯文本更丰富。

执行Shell命令

我们可以在Notebook内部运行Shell命令,方法是在命令前加上感叹号 !

这里,我们在运行此Notebook的当前目录上执行了ls命令,可以看到该目录中的所有文件。

我们还可以通过将Shell命令的输出赋值给一个变量来捕获它。

我们可以看到,该变量现在作为一个Python对象存在,我们可以用普通的Python代码来使用它。

总结

本节课我们一起学习了Jupyter Notebook中代码单元格的基本操作。我们掌握了如何运行代码、使用代码补全和查阅文档。我们还了解了代码输出的多样性,以及如何在Notebook中执行并捕获Shell命令的结果,从而将Python代码与操作系统环境无缝结合。这些技能是进行高效数据分析和工程的基础。

140:Jupyter Notebook中的文本单元格 📝

在本节课中,我们将学习如何在Jupyter Notebook中使用Markdown语言创建格式丰富的文本单元格。这能帮助我们更好地记录代码、解释分析过程并创建结构清晰的文档。

概述

Jupyter Notebook不仅是一个代码执行环境,也是一个强大的文档工具。通过将单元格转换为文本模式并使用Markdown语法,我们可以创建标题、列表、链接、图片等多种富文本内容,使我们的分析报告和笔记更具可读性和专业性。

创建文本单元格

首先,我们需要将单元格从代码模式转换为文本模式。

使用菜单栏中的选项,选择“Markdown”,即可将当前单元格转换为文本单元格。

标题的创建

要创建一个标题,在文本单元格的开头使用井号#

  • 一级标题使用一个井号:# 标题
  • 二级标题使用两个井号:## 标题
  • 以此类推,可以创建更多层级的标题。

引用块

我们可以创建引用块。

在段落开头添加右尖括号>即可。

当然,首先需要将单元格转换为Markdown模式。

文本强调

我们可以为单词添加强调效果。

用单个星号*包裹单词,会使其变为斜体

用两个星号**包裹单词,会使其变为粗体

或者,我们可以用两个波浪线~~包裹单词来删除它。

创建列表

我们可以创建两种类型的列表。

以下是用短横线-创建的项目符号列表:

  • 项目一
  • 项目二

我们也可以创建编号列表。

实际上,你输入什么数字并不重要。

列表的编号会自动生成。

  1. 第一项
  2. 第二项

添加链接

我们可以添加URL链接。

将链接文本放在方括号[]内,紧接着将URL地址放在圆括号()内,格式为:[链接文本](URL地址)

你可以点击这个URL来访问网站,即跟随链接。

嵌入代码

如果我们想在文本中嵌入代码。

我们可以用反引号`将其包裹起来,例如:print(“Hello”)

或者,如果我们想创建一个代码块。

我们可以用三个反引号```将其包裹起来。

print(“这是一个代码块”)

或者,直接通过缩进也可以创建代码块。

添加图片

要添加一张图片。

在方括号[]前添加一个感叹号!,方括号内填写图片的替代文本,紧接着在圆括号()内填写图片的路径地址,格式为:![替代文本](图片路径)

可以看到,图片已经显示出来。

总结

本节课我们一起学习了在Jupyter Notebook中使用Markdown语法创建文本单元格的核心方法。我们掌握了如何设置标题、引用、强调文本、创建列表、插入链接与图片,以及嵌入代码。熟练运用这些技巧,能极大地提升Notebook文档的可读性和组织性,是进行数据分析和撰写报告的重要基础。

141:Jupyter中的魔法命令 🪄

在本节课中,我们将要学习Jupyter Notebook中的“魔法命令”。魔法命令是Jupyter从IPython内核继承的一种特殊指令,可以极大地提升我们的工作效率和代码灵活性。

什么是魔法命令?

魔法命令是一种以特殊符号开头的命令,Jupyter从IPython内核获取这些命令。它们主要分为两类:

  • 单行魔法命令:以单个百分号 % 开头。
  • 单元格魔法命令:以两个百分号 %% 开头。

接下来,我们通过一些具体例子来了解它们的用法。

探索魔法命令

首先,我们可以使用一个魔法命令来列出所有可用的魔法命令。

以下是查看所有可用魔法命令的方法:

  • %lsmagic:此命令会列出所有可用的单行魔法命令和单元格魔法命令。

与Python中的其他函数类似,我们也可以通过添加问号 ? 来获取魔法命令的帮助文档。例如,输入 %alias? 可以查看 %alias 命令的详细说明。

计时与性能分析

在编写和优化代码时,了解代码的执行时间非常重要。Jupyter提供了方便的计时魔法命令。

最受欢迎的魔法命令之一是计时魔法 %timeit,它用于测量一条语句的运行时间。

以下是 %timeit 的两种用法:

  • 单行模式%timeit 会计算同一行中紧随其后的语句的执行时间。例如,%timeit sum(range(1000))。默认情况下,它会运行7次,每次循环执行语句10000遍,并返回7次运行中的最佳平均时间。
  • 单元格模式%%timeit 会计算当前单元格内剩余所有代码的执行时间。在这种模式下,它通常运行7次,但每次只循环1000遍。

文件操作魔法命令

Jupyter魔法命令还能方便地与文件系统进行交互,例如读写文件。

一个有用的单元格魔法是 %%writefile,它允许我们将单元格中的内容写入磁盘文件。

例如,我们可以在单元格中定义一个函数,然后使用 %%writefile hello.py 将该单元格的所有内容(包括函数定义)保存到名为 hello.py 的文件中。

写入文件后,我们可以使用 %run 这个单行魔法命令来执行磁盘上的文件。例如,%run hello.py 会在Jupyter环境中运行该脚本并显示输出。

另一个处理文件的有用命令是 %load。这个命令可以将磁盘上文件的内容加载到当前单元格中。例如,%load hello.py 会将 hello.py 文件的内容插入到当前单元格,之后我们就可以直接调用其中定义的函数了。

环境变量管理

有时我们需要在Notebook中查看或设置系统环境变量,魔法命令也能轻松完成这个任务。

我们可以使用 %env 命令来管理环境变量。

以下是 %env 命令的常见用法:

  • 查看所有环境变量:直接输入 %env 会列出当前环境中定义的所有环境变量。
  • 设置新环境变量:我们可以像在命令行中一样设置变量,例如 %env USER_KEY=my_secret_value
  • 查看特定环境变量:使用 %env USER_KEY 可以检查该变量的值。

多语言支持

Jupyter的魔法命令还有一个强大的功能,就是能在同一个Notebook中运行不同编程语言的代码。

我们可以使用 %%script 这个单元格魔法来指定用另一种语言解释器运行代码。

例如,如果我们想用Ruby语法打印一些内容,可以这样写:

%%script ruby
puts "Hello from Ruby!"

Jupyter会使用Ruby解释器执行这段代码,并将输出结果显示在单元格下方。

总结

本节课中,我们一起学习了Jupyter Notebook中强大的魔法命令。我们了解了单行魔法(%)和单元格魔法(%%)的区别,并实践了如何列出所有命令、获取帮助、为代码计时、读写文件、管理环境变量,甚至运行其他语言的代码。掌握这些魔法命令,能让你的数据分析和工程工作流程更加高效和灵活。

142:Jupyter Lab概览 🧪

在本节课中,我们将要学习Jupyter Lab,这是一个功能强大的交互式开发环境,特别适合数据科学和工程任务。我们将介绍如何安装、启动Jupyter Lab,并探索其核心界面和功能。

安装与启动

首先,我们可以通过pip安装Jupyter Lab。

pip install jupyterlab

安装完成后,使用以下命令启动Jupyter Lab。

jupyter lab

启动后,我们将看到Jupyter Lab的界面。

界面概览

Jupyter Lab的界面中央是一个启动器。启动器提供了多种创建新项目的选项。

以下是启动器中的主要选项列表:

  • 创建一个新的Notebook。
  • 开启一个交互式Python会话。
  • 打开一个终端会话。
  • 创建一个文本文件。
  • 创建一个Markdown文件。
  • 创建一个Python文件。
  • 显示上下文帮助。

如果我们打开一个Python文件,可以看到侧边栏会提供我们正在使用的函数的文档。这在我们输入代码时会实时显示。

在界面左侧,我们有一个导航栏,其中包含一个文件浏览器,用于显示本地网络中的文件。我们可以右键点击文件或Notebook进行重命名,并且重命名操作会同步反映在文件浏览器中。

导航栏还显示了当前正在运行的内核和终端。此外,这里还列出了已安装到Jupyter Lab中的各种扩展和插件。Jupyter Lab是一个完整的生态系统,尤其对于Jupyter Notebook和这些扩展的支持非常出色。

多文档与布局

现在,让我们打开另一个Notebook。这样我们就同时拥有了两个Notebook。实际上,你可以通过拖放来自由地布局这些文档,按照你喜欢的方式排列它们。

文本单元格与导航

如果我们在Notebook中输入一些文本单元格,可以在侧边栏看到一个生成的大纲。这个大纲可以帮助我们导航,直接跳转到特定的单元格。对于大型、复杂的文档来说,这个功能使得浏览变得非常容易。

右键点击一个单元格,我们可以看到所有可以对该单元格执行的操作命令。

内置调试器

Jupyter Lab还内置了一个调试器。让我们输入一些代码来演示。

这里有一个简单的函数,它遍历一个数字范围并设置一些变量。

def example_function():
    total = 0
    for i in range(5):
        total += i
    return total

我们可以在这里打开调试器,然后设置一个断点。设置后,我们可以在侧边看到调试信息面板,它提供了单步执行代码、查看局部变量值以及步入/步出函数的能力。

总结

本节课中,我们一起学习了Jupyter Notebook是一个由代码单元格和文本单元格组成的可执行文档。代码单元格由内核执行,这些内核可以是Python、Julia或R。此外,为了获得更接近集成开发环境的体验,我们可以使用Jupyter Lab。

143:Google Colab导论 🚀

在本节课中,我们将学习Google Colab。我们将讨论托管笔记本环境,查看文本单元格预览,学习如何在Colab笔记本中挂载Google Drive,以及如何管理Colab笔记本。


什么是Google Colab? ☁️

Google Colab是一个托管环境。它托管在云端。它是一个Jupyter笔记本,但带有额外的功能。它预装了数据科学包。因此,对于你将进行的大部分工作,你无需担心环境或任何类型的设置。

Google Colab与普通的Jupyter Notebooks不同,它仅支持Python。使用Google账户可以免费使用。由于它在云端,文档天生就是可共享的,就像你共享其他类型的Google文档一样。它拥有一个庞大的共享集合生态系统。


探索共享生态系统:AI Hub(原Seed Bank) 📚

接下来,我们来看看AI Hub,它以前被称为Seed Bank。在AI Hub中,有大量示例笔记本。你可以通过这些笔记本学习与机器学习和数据科学相关的不同主题。

如果我们选择一个笔记本,可以在这里阅读它。或者,我们可以在Google Colab中自己打开这个笔记本。这为我们提供了一个可以随意修改的笔记本副本。


核心功能与操作

上一节我们介绍了Colab的基本概念和共享资源。本节中,我们来看看它的核心功能与具体操作。

文本单元格预览

Colab笔记本包含文本单元格和代码单元格。文本单元格支持Markdown格式,可以用于编写说明文档。

挂载Google Drive

你可以在Colab笔记本中挂载你的Google Drive,以便直接访问其中的文件。这通过以下代码实现:

from google.colab import drive
drive.mount('/content/drive')

执行此代码后,按照提示完成授权,你的Google Drive就会被挂载到指定路径。

管理Colab笔记本

以下是管理Colab笔记本的几个关键点:

  • 创建与保存:你可以创建新的笔记本,或从GitHub、Google Drive导入。笔记本会自动保存到你的Google Drive。
  • 运行环境:Colab提供免费的GPU和TPU资源,可以在“运行时”菜单中更改硬件加速器类型。
  • 共享与合作:像共享Google文档一样,你可以通过链接邀请他人查看或编辑你的Colab笔记本。

总结

本节课中我们一起学习了Google Colab。我们了解到它是一个基于云端的、免费的Python托管笔记本环境,预装了数据科学包,简化了设置流程。我们探索了AI Hub上的共享笔记本资源,并学习了如何挂载Google Drive以及管理笔记本的基本操作。这些功能使得Colab成为进行数据科学和机器学习实验的强大且便捷的工具。

144:Google Colab 功能导览 🧭

在本节课中,我们将一起探索 Google Colab 的主要界面和功能。Colab 是一个基于云端的 Python 开发环境,尤其适合数据科学和机器学习项目。我们将学习如何访问它、熟悉其布局,并了解一些能提升效率的特色工具。

访问与初始界面

要访问 Google Colab,请打开浏览器并前往 colab.research.google.com

进入网站后,您将看到初始界面。这个界面提供了几个主要选项:

  • 最近打开的笔记本:如果您之前使用过 Colab,可以在这里快速打开。
  • 示例笔记本:Google 提供的一系列教程和示例项目。
  • Google 云端硬盘中的笔记本:打开您存储在 Google Drive 上的笔记本。
  • GitHub:从 GitHub 仓库直接打开笔记本。
  • 上传:从您的本地计算机上传一个 Jupyter Notebook 文件(.ipynb)。
  • 新建笔记本:创建一个全新的 Colab 笔记本。

由于 Colab 笔记本仅基于 Python 内核,因此创建时没有选择其他内核的选项。

界面布局概览

现在,让我们看看 Colab 笔记本的界面布局。它的设计与 Jupyter Lab 非常相似。

在顶部菜单栏,您可以点击此处来更改笔记本的名称。这个操作也会同步改变保存时的文件名。与 Jupyter 一样,Colab 笔记本会定期自动保存,防止数据丢失。

在界面左侧,有一个侧边栏,包含多个实用面板:

  • 目录:如果您的笔记本使用了标题,它们会在这里显示,方便快速导航。
  • 查找和替换:在整个笔记本中搜索和替换文本。
  • 代码片段:这是一个非常有用的功能,我们稍后会详细介绍。
  • 变量管理器:跟踪代码中变量的当前值。
  • 文件和数据:管理项目文件和数据集,我们将在后续课程中深入探讨。

编辑与预览功能

在编辑方面,Colab 提供了一些便利功能。当您添加新单元格时,可以直接选择添加一个文本单元格,而无需像在经典 Jupyter 中那样先添加代码单元格再转换类型。

更重要的是,在您编写 Markdown 文本时,Colab 会实时显示预览效果。这意味着您无需运行单元格就能看到最终的渲染样式,这大大提升了文档编写的效率。

使用代码片段

上一节我们介绍了界面布局,本节中我们重点来看看“代码片段”这个强大工具。代码片段是预先编写好的示例代码块,涵盖了数据加载、可视化、机器学习等多种常见任务。

它们是非常有用的学习参考和效率工具。您可以通过搜索框快速查找需要的片段。

以下是使用代码片段的步骤:

  1. 在左侧边栏打开“代码片段”面板。
  2. 浏览或搜索您需要的功能(例如“加载数据”)。
  3. 选中一个片段后,下方会显示该片段的说明、解释和部分代码预览。
  4. 点击“插入”按钮,该段代码就会被添加到您当前光标所在的单元格中。
  5. 插入后,像运行普通代码一样运行该单元格即可。

例如,插入一个展示表格的代码片段并运行后,它可能会加载一个数据集并以交互式表格的形式呈现。您可以直接在表格界面中进行排序、筛选等操作,体验类似于使用电子表格软件。


本节课中我们一起学习了 Google Colab 的基本使用方法。我们了解了如何访问 Colab、认识了其核心界面布局,并掌握了使用代码片段来加速开发的重要技巧。这些功能使得 Colab 成为一个强大且用户友好的云端 Python 编程环境。

145:Colab中的数据与文档管理 📁

在本节课中,我们将学习如何在Google Colab环境中处理文档和文件。我们将了解如何访问预加载的示例数据、连接个人Google Drive、上传本地文件以及管理笔记本的版本。


文件管理界面

上一节我们介绍了Colab的基本界面,本节中我们来看看其左侧的文件管理功能。

在左侧标签页中,有一个“文件”区域。该区域预加载了Google提供的一些示例数据,供用户试用。

例如,双击“README.md”文件,右侧窗格会打开并预览该文件内容。这允许我们阅读描述示例数据的说明文档。同样,打开任何一个示例文件,它也会在右侧显示预览。

导入外部数据

除了系统提供的示例数据,我们还有其他方式可以将数据导入到Colab文档中。

以下是几种主要的数据导入方式:

  • 挂载Google Drive:点击“Mount Drive”按钮,可以将你的Google Drive连接到Colab。连接完成后,你便可以访问Drive中的所有文件,并拥有将输出文件写入Drive的权限。
  • 上传本地文件:你可以从个人电脑硬盘上传单个文件。例如,上传一个CSV文件后,它会出现在文件列表中,从而允许我们在代码中访问它。需要注意的是,在不同会话之间,上传的文件不会被保留。如果你想再次使用,必须重新上传。
  • 文件保存位置:默认情况下,Google Colab文档会自动保存在Google Drive中。你可以在Drive中找到一个由Colab自动创建的“Colab Notebooks”目录,你的笔记本文件就保存在那里。

笔记本的版本管理与共享

除了将笔记本保存在Google Drive,我们还有其他管理选项。

以下是可用的操作:

  • 保存到GitHub:可以将笔记本的副本保存到GitHub,进行版本控制。
  • 下载副本:可以将笔记本下载为Jupyter Notebook文件(.ipynb),或下载为可在命令行执行的Python文件(.py)。
  • 版本历史:文件会自动保存,但我们也可以随时手动保存并创建一个修订版本。之后可以查看修订历史,比较不同版本之间的更改。
  • 上传现有笔记本:可以从本地磁盘上传一个在标准Jupyter环境中创建的笔记本文件,并将其导入到Google Colab的云端环境中。

本节课中我们一起学习了Google Colab中的数据与文档管理。我们了解到Colab是一种托管的、基于云端的笔记本服务,它扩展了Jupyter Notebook的功能。通过其文件系统,我们可以方便地访问示例数据、个人云端存储和本地文件,并有效地管理笔记本的版本。Colab还拥有一个包含大量数据科学信息的笔记本生态系统,可供学习和研究特定主题。

146:AWS SageMaker 导论 🚀

在本节课中,我们将学习AWS SageMaker。我们将创建一个SageMaker笔记本实例,概览SageMaker Studio,并了解如何使用流水线进行数据和特征工程。AWS SageMaker是AWS托管的笔记本环境。与Google Colab不同,它提供多种可用的内核。同时,它也不是免费的。SageMaker专为在AWS基础设施上构建机器学习流水线而设计。


访问与设置SageMaker

上一节我们介绍了SageMaker的基本概念,本节中我们来看看如何访问和设置它。

登录AWS控制台后,可以通过搜索找到SageMaker服务。

使用SageMaker的第一步是设置一个SageMaker域。你可以看到该域正在创建的状态。这需要一些时间。

现在我们的域已准备就绪,我们可以创建一个笔记本实例。


创建笔记本实例

以下是创建新笔记本实例的步骤:

  1. 在界面中选择“笔记本”选项。
  2. 你可以搜索现有的笔记本或创建一个新的。
  3. 为你的笔记本命名。
  4. 在此处选择你希望笔记本运行的实例类型。

需要谨慎选择实例类型,不要选择比你所需更大或更强大的实例,因为你将为此付费。向下滚动,你可以看到T系列、M系列、计算优化型、加速计算型和内存优化型等选项。你还有其他选择。

为了本示例的简洁性,我们将使用默认设置创建一个笔记本。

可以看到这里的状态是“待处理”。笔记本创建需要一些时间。


使用SageMaker笔记本

现在我们的笔记本已创建,我们可以打开它。要打开它,选择Jupyter风格的单笔记本界面。

在这里打开Jupyter,你可以看到我们拥有正常的Jupyter界面。Amazon提供了相当多的内核,包括R和Python。在这里,我们有一个在AWS环境中运行的Jupyter笔记本。

通常,这些笔记本的运行方式与你期望的正常Jupyter笔记本一样。Amazon提供的一个特别之处是一个特殊的SDK。它让你能够访问一整套用于机器学习的工具,包括用于训练和构建模型的工具、用于访问AWS资源的工具以及执行许多其他操作的工具。

例如,如果我们想查找运行笔记本的环境,可以通过调用 boto3.session.Session().region_name 来获取。你可以看到我运行在 us-east-1 区域。

当我们完成笔记本工作并关闭它时,请务必也停止它。


总结

本节课中我们一起学习了AWS SageMaker的基础知识。我们了解了如何访问SageMaker服务、设置SageMaker域、创建并配置一个笔记本实例,以及如何在其中使用Jupyter环境和AWS提供的特殊SDK进行工作。记住,使用完毕后及时停止实例以避免不必要的费用。

147:SageMaker Studio导览 🧭

在本节课中,我们将学习如何启动和导航Amazon SageMaker Studio。这是一个基于JupyterLab的云端集成开发环境,专为机器学习工作流设计,并集成了许多AWS的特定功能。

启动SageMaker Studio

要启动SageMaker Studio,我们需要回到SageMaker控制台中的“域”页面。在那里,我们可以找到并点击“启动Studio”的选项。

当你的Amazon SageMaker Studio实例创建完成后,你将看到一个类似下图的界面。

界面概览

这个界面看起来应该很熟悉。它非常像JupyterLab。实际上,它就是在JupyterLab的基础上,通过一些扩展添加了新功能和能力。

如果我们观察左侧,会看到一些在JupyterLab中见过的熟悉控件,例如文件浏览器。

核心功能面板

以下是SageMaker Studio左侧面板的主要功能区域:

  • 文件浏览器:用于浏览和管理项目文件。
  • Git仓库:如果我们将域连接到了Git仓库,可以在这里与仓库进行交互。
  • 运行中的应用/内核:可以在这里查看任何正在运行的应用程序、实例和内核会话。在SageMaker Studio中,我们可以同时运行多个笔记本和多个内核。
  • 命令面板:这里提供了一个有用的界面,用于查找在SageMaker Studio中可用的命令。其中很多命令与我们之前见过的相同,但这个面板让查找变得更容易。

启动器与资源

当前我们正在查看的是“启动器”页面。它提供了创建新笔记本、终端、控制台等资源的快捷入口。

启动器旁边是SageMaker JumpStart的接口。此外,这里还列出了其他SageMaker资源。

探索SageMaker JumpStart

现在,让我们来看看SageMaker JumpStart。JumpStart包含了一系列示例模型、笔记本、视频和博客,能让你快速上手。

在这里,我们可以浏览这些模型。我们可以选择一个模型,查看如何部署它,并了解如何训练它。每个模型都附有描述,帮助我们理解其功能。这是一个快速入门的便捷功能。

例如,我们可以打开一个笔记本。如果我们想使用这个笔记本,可以将其导入到我们的工作区,以便进行修改。这个笔记本将教会我们如何使用XGBoost,这是一个在机器学习中常用的模型。

总结

本节课中,我们一起学习了SageMaker Studio的基本界面和核心功能。除了上述这些新增功能,你完全可以将SageMaker Studio视为运行在AWS托管云端的、功能增强版的JupyterLab。它集成了模型开发、训练和部署的工具,为数据科学和机器学习工程提供了完整的工作环境。

148:SageMaker Pipelines概览 🚀

在本节课中,我们将学习AWS SageMaker Pipelines的基本概念和工作流程。我们将了解如何在SageMaker Studio中创建项目、定义管道,以及如何通过管道将数据科学操作步骤连接起来,形成一个可重复、可部署的自动化流程。


现在,让我们来看看SageMaker Pipelines。我们回到了SageMaker Studio环境中。要使用管道,我们需要先创建一个项目。SageMaker Studio提供了模板项目,可以作为许多常见场景的基础。

我们将选择一个用于构建和训练机器学习模型的项目。为项目指定一个名称。项目创建需要几分钟时间。

现在,我们的项目已经创建完成。这是我们项目的管道。我们可以检查管道是否正在执行,这在创建新管道时会自动进行。

在这里,我们可以看到管道的图形化表示,其中包含了管道中定义的所有步骤。这些步骤以节点的形式呈现在图中。我们可以点击一个节点,获取关于该节点的信息,包括发送给节点的输入、节点产生的任何输出,以及节点可能产生的日志。

如果我们返回,可以在这里看到代码仓库,其中存放着定义此项目的代码。我们可以复制这份代码,以便查看并为我们自己的项目进行修改。

以下是此项目中的代码和文件。有一个Jupyter笔记本,我们可以打开它。笔记本中解释了项目的功能,并提供了关于SageMaker Pipelines的信息。

如果我们进入pipelines目录,管道本身定义在这个文件中。可以看到,它是使用Python定义的。每个步骤都有自己的类,并在这里进行定义。

这就是SageMaker Pipelines如何为您提供一种方式,将数据科学操作封装成可重复、适合生产代码的可部署形式。


总结 📝

本节课中,我们一起学习了SageMaker Notebooks,这是AWS托管的Notebook解决方案。我们了解了SageMaker Studio,它本质上是一个用于创建和管理Notebook的集成开发环境。我们还引入了SageMaker Pipelines的概念,它能够连接数据科学解决方案中的不同步骤。

149:Python微服务构建导论 🚀

在本节课中,我们将学习如何使用Python构建HTTP API微服务。我们将涵盖如何打包Python项目、将其部署为API,以及如何应用持续集成/持续交付和DevOps最佳实践来构建健壮的Web服务。


项目打包与API构建 📦

上一节我们介绍了微服务的基本概念,本节中我们来看看如何将Python项目打包并构建成API。

我们将学习如何将Python项目打包,并将其部署为一个API。

以下是构建API的关键步骤:

  1. 使用setuptoolspoetry等工具定义项目依赖和元数据。
  2. 创建setup.pypyproject.toml文件来管理项目配置。
  3. 使用Web框架(如Flask或FastAPI)编写API端点。
  4. 将业务逻辑封装在独立的模块中,并通过API暴露功能。

例如,一个简单的Flask API结构如下:

# app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/data', methods=['GET'])
def get_data():
    # 业务逻辑
    data = {"message": "Hello from the API!"}
    return jsonify(data)

if __name__ == '__main__':
    app.run(debug=True)

应用CI/CD与DevOps最佳实践 ⚙️

在将项目打包为API后,我们需要确保其能够可靠地构建、测试和部署。本节我们将探讨如何应用持续集成和持续交付。

我们将应用持续集成和持续交付,并使用DevOps实践来构建Web服务的最佳实践。

以下是实施CI/CD的关键环节:

  1. 在代码仓库中设置自动化构建和测试流程。
  2. 配置CI服务器(如Jenkins、GitHub Actions)在每次代码提交时运行测试。
  3. 使用Docker等容器化技术确保环境一致性。
  4. 设置自动化部署流水线,将通过测试的代码部署到生产或预生产环境。

一个基础的GitHub Actions工作流配置示例如下:

# .github/workflows/ci.yml
name: Python CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
    - name: Run tests
      run: |
        python -m pytest


总结 📝

本节课中我们一起学习了Python微服务构建的核心内容。我们首先了解了如何将Python项目打包并构建成HTTP API。接着,我们探讨了如何通过应用持续集成、持续交付和DevOps最佳实践,来确保Web服务的质量、可靠性和高效部署。掌握这些技能是构建现代化、可维护数据工程应用的基础。

150:微服务的优势解析 🚀

在本节课中,我们将探讨微服务的优势,并理解它们在现代软件工程,尤其是数据工程和机器学习工程领域中的重要性。我们将学习如何创建项目结构以实现持续集成和持续交付,如何将功能映射到服务,以及如何部署该服务。


微服务的核心特征

上一节我们介绍了微服务的重要性,本节中我们来看看它的两个核心特征:独立性和单一职责。

独立性

微服务架构允许每个服务独立开发、部署和扩展。这意味着开发团队可以为不同的服务选择最适合的技术栈,而无需在整个组织中强制统一。

以下是独立性的关键体现:

  • 技术栈自由:一个服务可以用C#编写,另一个可以用Python编写。在大型组织中,这允许充分利用拥有不同技术背景(如C++、Python、Java、Haskell、Swift)的开发人员。
  • 解耦开发:团队可以独立工作,无需过度担心服务间的紧密集成,因为每个服务都通过定义良好的API进行通信。

单一职责

这是微服务架构另一个强大的特征。每个微服务都应专注于完成一项特定的业务功能。

以下是单一职责的示例:

  • 机器学习预测服务:一个服务只做一件事,例如接收一张猫或狗的图片,并正确分类。
  • 数据工程ETL管道:一个服务专门负责完成数据抽取、转换和加载的整个流程。

独立性与单一职责的结合,是微服务架构最关键的方面之一,它带来了诸多显著优势。


微服务的核心优势

理解了微服务的特征后,我们来看看这些特征带来的具体好处。

以下是微服务的主要优势:

  • 快速开发:这是最直接的优势之一。你可以采用“函数即服务”(Function as a Service, FaaS)的理念,将单一功能快速封装并部署到生产环境。例如,在AWS Lambda或FastAPI框架中构建机器学习预测接口,都能在微服务框架下实现快速开发和原型构建。
  • 弹性与韧性:微服务提供了隔离的环境。当系统的其他部分出现故障时,不会波及到你构建的微服务,因为每个服务只负责自己的单一职责。这提高了整个系统的容错能力。
  • 容器化部署:通过容器技术(如Docker),你可以将代码及其运行环境打包在一起。这种 代码 + 运行时 的打包方式是确保软件在不同环境中一致、正确部署的关键。
  • 多语言支持:你可以在构建不同微服务时自由选择C#、Ruby、Python、Swift等任何编程语言。这是一个关键优势,它意味着企业无需只雇佣掌握某一特定语言的开发人员,可以更灵活地组建团队。

总结

本节课中,我们一起学习了微服务的核心特征与优势。我们了解到,微服务通过 独立性单一职责 的设计,实现了快速开发系统弹性容器化部署多语言支持等关键优势。这些特性使得微服务架构特别适合构建复杂、需要快速迭代和高度可扩展的现代应用,尤其是在数据工程和机器学习工程领域。在接下来的课程中,我们将深入探讨如何具体实现和部署微服务。

151:为持续集成设置Python项目结构 🏗️

在本节课中,我们将学习如何为Python项目设置一个持续集成(CI)生态系统。核心目标是建立一个项目结构,使得每次开发者向Git仓库推送更改时,都能自动进行代码测试、代码规范检查和代码格式化。这是确保项目质量、为后续部署做好准备的关键一步。

项目结构与核心概念

上一节我们介绍了持续集成的目标,本节中我们来看看一个为持续集成和持续交付(CI/CD)准备好的项目具体包含哪些关键组件。

一个典型的项目结构包含以下核心部分:

  • GitHub工作流:这是自动化构建系统的配置区域。在本例中,我们使用GitHub Actions,但你也可以使用Jenkins、AWS CodeBuild等其他工具。它定义了在每次代码推送时自动执行的任务。
  • Makefile:这是一个包含一系列“配方”的文件,用于在本地或构建服务器上执行标准化命令,例如安装依赖、代码检查、运行测试和格式化代码。
  • 项目代码与配置文件:包括源代码、测试文件、依赖清单(如requirements.txt)和说明文档(如README.md)。

这种结构的核心优势在于,它通过一个Makefile将关键操作封装成简单的命令,例如make lint(代码检查)、make test(运行测试)和make format(格式化代码)。开发者可以在本地执行这些命令,而构建服务器(即“机器人”)会在每次代码提交后自动执行相同的流程,从而持续保证代码质量。

实战演练:剖析一个CI项目

接下来,让我们通过一个实际的GitHub项目,看看上述概念是如何具体实现的。

1. 自动化工作流配置

以下是项目自动化流程的核心配置,它定义在.github/workflows/main.yml文件中:

name: CI Pipeline
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
        with:
          python-version: '3.8'
      - run: make install
      - run: make lint
      - run: make test
      - run: make format

这个配置意味着:每当有代码推送(push)事件时,GitHub Actions会启动一个Ubuntu虚拟机,安装Python 3.8,并依次执行make install(安装依赖)、make lint(代码检查)、make test(运行测试)和make format(代码格式化)命令。

2. 项目文件组成

现在,我们来看看项目仓库中包含的其他关键文件:

  • README.md:项目说明文档,通常包含构建状态徽章。
  • main.py:项目源代码文件,例如一个简单的加法函数。
  • test_main.py:对应的单元测试文件,用于验证代码功能。
  • requirements.txt:项目依赖包及其精确版本的清单。这确保了开发环境和构建环境的一致性,是实现可重现性的关键。
  • Makefile:本地和CI流程的指挥中心,定义了所有标准化操作命令。

3. Makefile详解

Makefile是连接本地开发与自动化流程的桥梁。其基本结构如下:

install:
    pip install -r requirements.txt

lint:
    pylint --disable=R,C main.py

test:
    python -m pytest -v test_main.py

format:
    black main.py test_main.py

all: install lint test format

通过运行make all,你可以一次性执行安装、检查、测试和格式化所有步骤,快速获得项目状态的反馈。

在开发环境中实践

我们可以在一个云开发环境(如GitHub Codespaces)中打开这个项目,并实际操作这些组件,观察整个流程如何工作,以及当代码出现问题时如何快速发现。

  1. 在终端中运行 make all 命令。这将启动完整的本地CI流程:安装依赖、进行代码规范检查、运行测试并格式化代码。如果一切正常,你会看到所有步骤都成功的反馈。
  2. 模拟一个错误:在main.py中引入一个代码问题,例如将一个变量赋值给它自身(x = x),这是一个无意义且可能导致错误的操作。
  3. 再次运行 make lintmake all。此时,代码检查工具(如pylint)会捕获这个“bug”,并在终端输出错误或警告信息,明确指出问题所在的行和原因。
  4. 修复代码问题后,将更改提交并推送到Git仓库。

观察自动化构建流程

当你将包含问题的代码推送到GitHub后,持续集成的威力就显现出来了。

  1. 前往GitHub仓库的“Actions”选项卡,你可以看到一个自动触发的构建任务正在运行。
  2. 由于我们推送的代码存在规范问题,构建任务会在执行到 make lint 步骤时失败。构建服务器会清晰地标记出失败的任务,并展示详细的错误日志。
  3. 此时,你甚至不需要打开本地开发环境。可以直接在GitHub的网页界面上编辑有问题的文件,删除或修复错误的代码行,然后再次提交。
  4. 新的提交会再次触发自动化构建。这一次,所有步骤(安装、检查、测试、格式化)都会顺利通过,构建状态将显示为成功的绿色对勾。

这个过程完美展示了持续集成如何作为一个“机器人质检员”,持续监控代码质量,并在问题引入时立即给出反馈,从而确保主分支的代码始终处于健康状态。

课程总结

本节课中,我们一起学习了为Python项目设置持续集成(CI)的完整流程。

我们首先理解了CI的核心价值:通过自动化测试、代码检查和格式化,在代码合并前持续保证质量。接着,我们剖析了一个标准的CI项目结构,其核心是Makefile自动化工作流配置(如GitHub Actions)。通过实战,我们在开发环境中运行了CI步骤,并故意引入错误以观察自动化流程如何捕获问题。最后,我们见证了代码推送后,构建服务器如何自动执行“质检”任务,并在发现问题时快速反馈。

对于任何进行微服务开发、工具开发或软件开发的Python项目,建立这样一个持续集成环境都是至关重要的最佳实践。它不仅能及早发现缺陷,还能统一团队代码风格,为代码的可靠部署打下坚实基础。

152:使用Python构建随机水果Web应用 🍇

在本节课中,我们将学习如何使用Flask框架构建一个能够随机生成水果名称的微型Web应用。我们将从项目结构开始,逐步讲解核心代码、模板、测试以及如何利用持续集成(CI)工具来自动化开发流程。


项目结构概览

首先,我们需要为项目建立一个清晰的结构。一个典型的Flask微服务项目可能包含以下文件:

  • app.py: 这是主要的应用文件,包含了Web服务的核心逻辑。
  • requirements.txt: 列出了项目运行所依赖的Python包。
  • test_app.py: 包含了用于测试应用功能的代码。
  • Makefile: 一个包含常用命令(如安装、测试、代码检查)的脚本文件,用于简化开发流程。
  • 构建系统配置文件(如GitHub Actions的.yml文件):用于在持续集成环境中自动执行任务。

这些文件共同构成了一个既适合本地开发,也适合在持续集成环境中运行的项目基础。


核心应用代码解析

上一节我们介绍了项目的整体结构,本节中我们来看看构成应用核心的app.py文件是如何工作的。

import random
from flask import Flask, render_template

app = Flask(__name__)

def random_fruit():
    """
    此函数从预定义的列表中随机选择一个水果并返回。
    """
    fruits = ['Apple', 'Cherry', 'Orange']
    return random.choice(fruits)

@app.route("/")
def home():
    """
    定义Web应用的根路由(主页)。
    它调用random_fruit函数,并将结果传递给HTML模板进行渲染。
    """
    fruit = random_fruit()
    return render_template("index.html", fruit=fruit)

代码的核心逻辑如下:

  1. random_fruit函数:它创建了一个包含三种水果的列表,并使用random.choice()方法随机返回其中一个。
  2. 路由@app.route(“/”):这定义了当用户访问网站根目录(主页)时,Flask应该执行home()函数。
  3. render_template:这个Flask函数用于渲染一个HTML模板(index.html),并将random_fruit()函数生成的结果(fruit变量)传递给它。

HTML模板与数据渲染

我们已经看到Python代码如何生成数据,接下来需要了解如何将这些数据展示给用户。这通过HTML模板实现。

以下是index.html模板的内容:

<!DOCTYPE html>
<html>
<head>
    <title>Random Fruit Generator</title>
</head>
<body>
    <h1>Random Fruit: {{ fruit }}</h1>
    <p>Refresh the page for more random fruit.</p>
</body>
</html>

这个模板与普通HTML的唯一区别在于 {{ fruit }} 部分。这是Jinja2(Flask使用的模板引擎)的语法。当Flask渲染这个页面时,它会用我们从Python代码中传递过来的fruit变量的实际值(例如“Apple”)替换掉 {{ fruit }}


使用Makefile自动化任务

手动运行安装、测试、代码检查等命令很繁琐。我们可以使用Makefile来定义和自动化这些任务。

以下是一个典型的Makefile示例,它定义了四个常用命令:

install:
    pip install --upgrade pip &&\
        pip install -r requirements.txt

lint:
    pylint --disable=R,C,W1203 app.py test_app.py

test:
    python -m pytest -vv test_app.py

format:
    black *.py

all: install lint test format

以下是每个命令的作用:

  • make install: 安装项目所需的所有Python依赖包。
  • make lint: 使用pylint工具检查代码风格和质量。
  • make test: 使用pytest运行test_app.py文件中所有的测试。
  • make format: 使用black工具自动格式化Python代码,使其风格统一。
  • make all: 按顺序执行以上所有任务,这是进行本地持续集成检查的快捷方式。

在终端中运行 make all,即可一键完成安装、代码检查、测试和格式化。


编写与运行测试

为了保证应用功能的可靠性,我们需要为它编写测试。以下是test_app.py文件中的一个简单测试示例:

import app

def test_random_fruit():
    """
    测试random_fruit函数是否返回预期列表中的水果。
    """
    fruit = app.random_fruit()
    assert fruit in [‘Apple‘, ‘Cherry‘, ‘Orange‘]

这个测试导入了我们的主应用app,然后调用random_fruit()函数。断言(assert) 语句会检查函数的返回值是否在 [‘Apple‘, ‘Cherry‘, ‘Orange‘] 这个列表中。如果返回值不在列表中,测试就会失败。

运行 make testpytest 命令即可执行这个测试。


运行应用与持续集成

最后,让我们启动应用并理解持续集成的价值。

在终端运行 python app.py 启动Flask开发服务器。在类似GitHub Codespaces的环境中,你可以直接打开浏览器预览应用。访问主页,你会看到类似“Random Fruit: Orange”的文字,刷新页面,水果名称会随机变化。

现在,如果我们修改了代码,如何确保没有引入错误呢?这就是持续集成系统的用途。我们配置的构建系统文件(如GitHub Actions工作流)会在每次代码变更时自动执行 make all 中的任务(安装、检查、测试、格式化)。如果任何一步失败,系统会发出通知。这为微服务的开发建立了质量保障,确保代码始终处于可测试、可部署的状态。


总结

本节课中我们一起学习了构建一个Flask随机水果Web应用的完整流程。我们从搭建项目结构开始,编写了生成随机数据的核心函数和Web路由,利用HTML模板渲染页面,并通过Makefile自动化开发任务。我们还为应用编写了简单的功能测试,并了解了如何通过持续集成系统自动化执行这些测试,从而保障代码质量。这个工作流是构建小型、健壮微服务的优秀实践。

Python微服务基础:2-4:Python微服务导论与最佳实践

在本节课中,我们将学习构建Python微服务的最佳实践,探讨Python为何是快速构建和部署微服务的优秀语言,并介绍FastAPI、Flask以及AWS Lambda等现代框架。


为什么选择Python构建微服务?

Python因其简洁的语法和丰富的生态系统,成为快速构建微服务的理想选择。开发者可以快速将业务逻辑映射到服务框架(如FastAPI或Flask),并高效地投入生产环境。

微服务架构的核心优势

微服务架构将应用程序拆分为一组小型、独立的服务。每个服务运行在自己的进程中,并通过轻量级机制(通常是HTTP API)进行通信。这种架构的主要优势包括:

  • 独立部署:每个服务可以独立更新和部署。
  • 技术异构性:不同服务可以使用不同的技术栈。
  • 可扩展性:可以针对特定服务进行扩展。

构建Python微服务的最佳实践

以下是构建健壮、可维护的Python微服务的一些关键实践:

  1. 单一职责原则:每个微服务应专注于一个特定的业务功能或领域。
  2. 定义清晰的API:服务间通过定义良好的API(如RESTful API或gRPC)进行通信。
  3. 实现弹性设计:服务应能处理上游服务的故障,例如通过实现重试机制和断路器模式。
  4. 集中化配置管理:使用配置服务器或环境变量来管理不同环境的配置。
  5. 全面的日志记录与监控:集中收集日志和指标,以便于调试和性能监控。

现代Python微服务框架

上一节我们介绍了最佳实践,本节中我们来看看实现这些实践的具体工具。Python社区提供了多个优秀的框架来简化微服务的开发。

  • FastAPI:一个现代、快速(高性能)的Web框架,用于基于标准Python类型提示构建API。它自动生成交互式API文档。
    from fastapi import FastAPI
    app = FastAPI()
    @app.get("/")
    def read_root():
        return {"Hello": "World"}
    
  • Flask:一个轻量级的WSGI Web应用框架,以其简单性和灵活性著称,适合构建小型到中型的微服务。
  • AWS Lambda(无服务器计算):这是一种事件驱动的计算服务,让你无需预置或管理服务器即可运行代码。你可以将业务逻辑封装为函数,由特定事件(如HTTP请求、文件上传)触发执行,是实现微服务架构的另一种高效范式。

总结

本节课中我们一起学习了使用Python构建微服务的核心概念。我们了解了Python在此领域的优势,探讨了微服务架构的最佳实践,并介绍了FastAPI、Flask和AWS Lambda等关键框架与平台。掌握这些知识,你将能够开始设计和实现自己的高效、可扩展的Python微服务。

154:构建用于机器学习预测的FastAPI微服务 🚀

在本节课中,我们将学习如何使用FastAPI框架构建一个简单的微服务,并将其部署到AWS App Runner云平台上。整个过程将涵盖从本地开发环境设置到云端部署的完整流程。


项目与环境准备

首先,我们有一个名为“NOAA Gi fast API”的代码仓库,它基于FastAPI框架。FastAPI是一个流行的Python Web框架,它支持类型注解、集成了Swagger API文档,并且使用简单。以下是它的一些关键特性。

接下来,我将使用AWS Cloud9作为开发环境。选择它的原因是,如果计划将微服务部署到AWS云平台,它会非常方便。我们最终将使用AWS App Runner进行部署,这是一个新兴的、优秀的AWS微服务部署托管服务。

关于App Runner,可以看到我这里已经运行了多个应用程序。现在,让我们开始搭建环境。

我的Cloud9环境已经设置好,并且创建了一个Python虚拟环境。我喜欢为项目建立一个脚手架结构。让我们看一下这个Makefile,我设置了installtestformatlint等命令。我认为这是一个很好的实践。

此外,我还使用了GitHub Actions来自动测试代码,这被称为持续集成。我强烈建议你也这样做。你可以在“NoAgift fast API”仓库中查看具体配置。


编写并运行微服务

下一步,我将运行这个微服务。首先,让我们查看一下代码。

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def hello_world():
    return {"message": "hello Duke"}

@app.get("/add")
def add_numbers(a: int, b: int):
    total = a + b
    return {"total": total}

代码解析如下:

  • fastapi导入FastAPI
  • 定义了一个根路由/,返回“hello Duke”信息。
  • 定义了一个/add路由,它接收两个整数参数ab,并返回它们的和。
  • 服务将运行在外部接口的8080端口上。

运行这个服务非常简单。进入微服务目录后,只需执行命令python main.py。现在服务已经运行起来了。

在基于终端的环境中,一个很实用的技巧是,我可以打开一个新的终端窗口,然后使用curl命令测试服务。例如,测试根路由:

curl http://localhost:8080/

返回结果正常。接着测试/add端点:

curl “http://localhost:8080/add?a=2&b=2”

可以看到加法功能也正常工作。


部署到AWS App Runner

现在服务已在本地运行,接下来我们将其部署到AWS App Runner环境。这个过程非常直接。

首先,在AWS控制台创建App Runner服务,选择“源代码仓库”作为部署源。然后,选择我们的“NOAA giftft Fast API”仓库。

加载仓库后,有几个配置选项。有“自动部署”选项,这对应着持续交付的理念,即代码会自动推送到生产或原型环境。目前,为了演示清晰,我选择“手动”部署。

运行时选择Python 3。启动命令设置为python install -r requirements.txt,以安装依赖包。然后运行命令设置为python main.py,这与我们在Cloud9环境中做的完全一样。

接着,为服务命名,例如“microservice-2”。最后,点击“创建并部署”即可。

部署过程大约需要五分钟。我之前已经部署过一个服务,现在可以访问它的默认域名(一个完全加密的URL)。FastAPI的一个优点是内置了Swagger文档。访问/docs路径,就可以看到一个交互式API文档界面。

在文档界面中,可以点击“Try it out”来测试/add端点,例如输入2和2,然后执行。它会显示对应的curl命令、请求URL和响应详情等信息,这对于构建数据工程或机器学习API非常有用。同样,也可以直接测试根路由/


总结

本节课我们一起学习了如何构建一个基础的FastAPI微服务,包括编写API端点、在本地运行测试,以及最终将其部署到AWS App Runner云平台。整个过程展示了从开发到部署的现代微服务工作流。

155:部署Python Lambda微服务 🚀

在本节课中,我们将学习如何将一个在Databricks中训练并注册的机器学习模型,下载到本地开发环境,并构建成一个独立的、可部署的微服务。我们将使用FastAPI创建一个简单的Web服务,该服务能够接收文本输入并返回模型预测结果(例如,判断是否为假新闻)。整个过程将演示如何将云端模型“本地化”,并为其构建一个轻量级的API接口。


概述

上一节我们介绍了在Databricks中注册和管理模型。本节中,我们来看看如何将这个已注册的模型下载到本地环境,并封装成一个独立的Python微服务。我们将使用GitHub Codespaces作为云开发环境,并最终通过FastAPI框架暴露一个预测端点。

这个代码仓库包含了一个高层架构图。

具体来说,在这个高层架构中,你可以看到我们之前的工作流程:我们访问了Cagle,创建了文件系统,执行了自动化流程,注册了最佳模型,并点击了“服务”按钮。

但现在,我打算在一个基于云的开发环境中进行交互。

首先,我可能会在GitHub Codespaces中尝试操作,这可能是一个适合实验的良好环境。之后,我们会通过容器化的微服务将其部署出去。这样,我们几乎可以假装Databricks不存在,只需下载模型并开始使用它。

让我们开始实验如何做到这一点。

首先,我将点击上方名为“Code”的按钮。

我将进入一个代码空间,并打开我之前设置好的现有代码空间。

这个环境类似于AWS Cloud9,它允许我在这个代码空间内测试想法和运行代码。

让我展示几件我们可以做的事情。

我们可以列出这个追踪系统中所有可用的模型。

这实际上非常有趣,因为在一个传统的独立scikit-learn类型环境中...

实际上,让我先改变一下背景色,让眼睛更舒服些。

我将把颜色主题改为Visual Studio Dark。

好了,这样对我来说容易多了。

这里的好处是,注意这段特定的代码会连接到Databricks系统,然后要求“显示所有已注册的不同模型”。让我们试试看。

运行 python list_models.py

注意,它会发起一个API调用,因为我之前创建了一个Databricks令牌。这是一个环境变量,现在我可以连接到Databricks文件系统了。

看,我们应该能...我甚至可以抓取那个模型。我可以执行类似这样的命令,或者用grep。

我需要用grep。像这样。

好了。看,这就是我刚创建的“text-only-fake-news”模型。实际上这很简洁,我已经注册了模型,现在几乎可以假装Databricks不存在。我可以开始将其视为一个独立的项目。

另一件我们可以做的事情是,我也可以下载这个模型。

看看这个。如果我到这里。

注意,这是我下载的另一个不同类型的模型。

但我实际上可以创建这个脚本的另一个版本,或者制作一个类似Chameleon的工具,把这个模型放到不同的地方。

所以,我打算这样做。我想我会像这样替换它。使用新模型的令牌或ID,也就是那个假新闻模型。

我想如果我查看这里的内容。是这个,68AFF...,基本上就是这个载荷。我准备把它替换掉。看看会发生什么。

让我们开始做吧。保存文件。

首先,让我们看看我们使用的Python版本。是的,Python 3.8,我认为没问题。

我认为这应该能工作。因为模型必须以特定的Python版本序列化,有时Python 3.8和3.7之间可能会有问题,但目前我认为这会奏效。

我将运行 python download_model.py

好了,我们成功下载了这个模型。看,它把模型放到了这个目录里。我们可以查看它。我们可以这样做:ls -dl

我想查看模型的位置。

我们进入这里并运行。啊哈,我们看到它有一个requirements.txt文件,模型就在这里。所以,实际上我真正关心的是这两段代码。

现在,我注意到这里有一个model目录,里面是我之前的另一个模型。我可以直接覆盖它,我认为这是个好主意。

我准备这样做。我将复制这个数据到这里。

覆盖我现有的模型。

让我们试试看会发生什么。使用通配符 *,复制到model目录。

好了。那么如果我运行get_status,它应该是一个不同的模型了,因为我更改了下载的模型,更改了这里,还更改了input_example。因为现在的输入示例会不同,它应该非常简单,只有一个列,这很简洁,因为我几乎不需要做任何处理。

从这里,我可以直接运行get_status。我会提交这个更改。

使用命令 git commit -m “添加新模型”

然后推送。我们先拉取一下。好了,合并。然后执行git push

我让这个运行起来了。那么,我如何构建一个能与之交互的脚本呢?

之前我必须处理一个非常复杂的示例,实际上我可以去掉所有这些。我要更改这段代码,因为我只需要创建一个包含text字段的载荷。仅此而已。

所以,我将在这里创建一个数据框,放入一些看起来像假新闻的内容。事实上,我可以参考这里的输入示例。

看看数据的样子。事实上,我可以直接复制这个载荷作为起点。我相信我可以直接把它导入pandas,让我们试试,因为这样很好,它给了我们结构。

让我们在这里输入ipython。然后说 import pandas as pd。然后我可以执行 pd.DataFrame(...)。我原以为可以直接扔进去...不,那文本太大了。也许我不想用那个。

这里的文本。让我们试试这段文本。“Aliens are coming...”,我将用它创建一个数据框,然后我可以基于之前的代码进行修改。

我们可以选择远程调用这个模型,或者也应该能在本地运行预测。我认为这会奏效。所以,我们基本上可以本地加载这个东西,或者我们可以做一个更小的版本。

让我看看这个,我们可能想用这个。我认为实际上我们想做的是,在这一点上,我可以直接构建一个Flask应用,那可能更容易。

让我们试试这个。我改错了文件,让我改这个,也就是这里的文本。我们保留这个文本。我们继续这样。

这样我们就有了一个非常简单的数据框。注意,这就是我需要做的全部:从磁盘加载我刚下载的模型,然后这段代码将进行预测,它会使用加载的模型。在Flask中,我创建一个端点,它将接收这个基于文本的输入进行预测,然后我基本上将其转换为JSON并返回一个JSON响应。

让我们运行这个,这应该是个好例子。我输入 python main.py,这应该能工作。我们马上就能知道。

好的,看起来它正在运行,很好。在这种情况下,我们需要... 我想我需要打开这个浏览器窗口。

并将这个地址粘贴回我们的窗口。这样我们可以在两者之间切换。

这确实让事情复杂了一点,但让我们试试。这样我就能看到它了。现在,为了与这个模型交互...

我可以访问/docs。这里的好处是,现在我有了一个Web服务,它再次与Databricks没有接口。在这一点上,我们只有一个模型,它使用FastAPI(一个Python Web服务),我们可以直接使用这段代码。这真的很棒,因为我现在只需要把这个东西打包并导入。

所以,如果我点击POST,我应该能测试这个端点。然后输入我需要的内容。比如,“aliens are coming to invade Earth”,某种假新闻故事。

让我们看看它是否预测那是假新闻。

看,它返回了“fake news”,返回值是1,我相信这代表“真”(是假新闻)。我们能够从这里获得整个输入返回,所以这个端点工作正常。

然后,如果我愿意,我可以用这种风格构建Chameleon工具,我可以用这种风格构建许多不同的东西。




接下来尝试的好事情可能是,看看我们是否也能构建一个不错的小Chameleon工具,也能导入并使用这个库。这将为我们接下来容器化这两个系统并建立持续交付流水线做好准备。


核心步骤与代码

以下是实现上述过程的核心步骤和代码片段。

1. 列出已注册模型

首先,我们需要一段代码来从Databricks模型注册表中列出所有模型。这需要一个有效的Databricks个人访问令牌(作为环境变量DATABRICKS_TOKEN)和工作区地址。

# list_models.py
import os
from databricks_registry_client import list_registered_models

# 从环境变量获取令牌和工作区地址
token = os.environ.get('DATABRICKS_TOKEN')
workspace_url = os.environ.get('DATABRICKS_HOST')

models = list_registered_models(workspace_url, token)
for model in models:
    print(f"Model Name: {model['name']}, Version: {model['latest_version']}")

2. 下载特定模型

接着,下载我们需要的模型(例如“text-only-fake-news”)到本地目录。

# download_model.py
import os
import mlflow

# 设置Databricks追踪URI和令牌
os.environ['DATABRICKS_HOST'] = 'https://your-workspace.cloud.databricks.com'
os.environ['DATABRICKS_TOKEN'] = 'your-personal-access-token'

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/duke-py-bash-sql/img/1f13c5381a687bb7f0ec0f29820f4097_8.png)

model_name = "text-only-fake-news"
model_version = "1" # 或 "latest"

# 下载模型到本地目录 ./model
model_uri = f"models:/{model_name}/{model_version}"
local_path = mlflow.artifacts.download_artifacts(artifact_uri=model_uri, dst_path='./model')
print(f"Model downloaded to: {local_path}")

3. 构建FastAPI微服务

下载模型后,我们创建一个简单的FastAPI应用来提供预测服务。

# main.py
import pandas as pd
import mlflow.pyfunc
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

# 定义请求体结构
class PredictionRequest(BaseModel):
    text: str

# 加载本地模型
model_path = "./model"
loaded_model = mlflow.pyfunc.load_model(model_path)

# 创建FastAPI应用
app = FastAPI(title="Fake News Predictor")

@app.post("/predict", summary="预测新闻真伪")
async def predict(request: PredictionRequest):
    """
    接收一段文本,预测其是否为假新闻。
    - **text**: 需要预测的新闻文本。
    """
    try:
        # 将输入文本转换为模型期望的DataFrame格式
        input_data = pd.DataFrame([{"text": request.text}])
        # 进行预测
        prediction = loaded_model.predict(input_data)
        # 假设模型输出1表示假新闻,0表示真新闻
        result = "fake news" if prediction[0] == 1 else "real news"
        return {"input_text": request.text, "prediction": result, "prediction_value": int(prediction[0])}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/duke-py-bash-sql/img/1f13c5381a687bb7f0ec0f29820f4097_10.png)

@app.get("/")
async def root():
    return {"message": "Fake News Prediction API is running. Go to /docs for interactive API documentation."}

# 运行命令: uvicorn main:app --reload

4. 测试API

运行服务后(例如使用 uvicorn main:app --reload),可以访问 http://localhost:8000/docs 进入交互式API文档页面。在 /predict 端点中,尝试发送以下JSON请求体:

{
  "text": "Aliens are coming to invade Earth next Tuesday."
}

服务将返回类似这样的预测结果:

{
  "input_text": "Aliens are coming to invade Earth next Tuesday.",
  "prediction": "fake news",
  "prediction_value": 1
}


总结

本节课中,我们一起学习了如何将一个在云端Databricks平台训练并注册的机器学习模型,转化为一个独立的、本地可运行的Python微服务。关键步骤包括:

  1. 环境准备:在GitHub Codespaces等云开发环境中设置项目。
  2. 模型获取:使用MLflow API从Databricks模型注册表列出并下载目标模型。
  3. 服务构建:利用FastAPI框架,创建一个接收文本输入并返回模型预测结果的Web API。
  4. 本地测试:运行服务并通过交互式文档测试其功能。

这个过程的核心价值在于实现了模型与训练平台的解耦。下载的模型可以被打包、容器化,并部署到任何支持Python的环境(如AWS Lambda、Azure Functions、Kubernetes等),为构建可扩展的数据工程和机器学习流水线奠定了基础。下一步,你可以探索如何将此FastAPI应用Docker化,并集成到CI/CD流程中实现自动部署。

156:容器化微服务构建导论 🐳

在本节课中,我们将要学习微服务和容器的核心概念。我们将探讨如何将业务逻辑与运行时环境打包在一起,形成一个可复制的产品,并了解如何将其部署到不同的环境中,包括无服务器环境。我们还将简要介绍使用C#和Python两种语言来部署微服务。


微服务与容器

上一节我们介绍了数据工程的基础工具,本节中我们来看看如何构建和部署应用。微服务是一种将应用程序构建为一套小型、独立服务的方法。容器技术则为这些服务提供了轻量级、可移植的运行环境。

将一段逻辑(功能)应用到某个端点(endpoint)时,微服务与容器是绝佳的搭配。关键在于,你需要将这段逻辑及其源代码,连同其运行时环境一起打包。

核心概念公式
可部署产品 = 业务逻辑 + 源代码 + 运行时环境

当你将这些元素打包在一起时,你就创建了一个可复制的产品。这个产品可以部署到许多不同的环境中,包括像AWS Lambda这样的无服务器环境。此外,你还可以使用多种编程语言,例如C#或Python。

在本教程的后续部分,我们将实际查看C#和Python的示例,并使用这些技术来部署微服务。


核心优势

以下是容器化微服务带来的主要优势:

  • 环境一致性:容器确保了应用在开发、测试和生产环境中的运行行为完全一致。
  • 可移植性:打包好的容器镜像可以在任何支持容器运行时的系统上运行。
  • 资源高效:容器共享主机操作系统内核,比传统虚拟机更轻量,启动更快。
  • 易于扩展:每个微服务可以独立地进行横向扩展。

总结

本节课中我们一起学习了微服务和容器的基本概念。我们理解了将应用逻辑、代码和运行时打包成容器镜像的重要性,这创造了一个可重复部署的单元。我们还了解到,这种架构支持多语言开发并能灵活部署在各种云环境中,为构建现代、可扩展的数据工程应用奠定了基础。

157:微服务为何采用容器技术 🐳

在本节课中,我们将探讨容器技术在微服务架构中的核心价值。我们将了解容器如何借鉴物理世界的运输模式,以及它相较于传统虚拟化技术的优势。


你可能会问,为什么我要使用容器?这有什么大不了的?

这确实是一个常见的问题,并且存在一些很好的解答方案。

首先,如果你观察全球的航运集装箱行业,你会发现能够将集装箱装载到船上,运到陆地港口,然后将其转移到半挂卡车或铁路上,通过某种运输机制将其运送到目的地。这对全球供应链的改进是一个巨大的好处。因此,物理集装箱在航运、运输乃至整个供应链方面都是一个巨大的进步。

同样地,当你在软件环境中考虑使用容器构建应用时,这些容器能够将交付产品所需的一切打包在一起。如果你考虑微服务的概念,你不仅拥有重要的代码,还拥有运行时本身。这意味着你拥有库、Linux操作系统,所有这些都被封装在容器内部。因此,当你将一个容器交付到生产环境时,你确切地知道你将得到什么。

同样重要的是,容器的另一个关键概念是响应时间。对于基于虚拟机的虚拟化技术,部署一个服务可能需要数分钟,这在需要快速响应时间的场景(如机器学习)中可能造成问题。因此,当容器与某种容器编排系统结合时,它们表现卓越。

一个非常流行的编排系统是 Kubernetes。也存在其他竞争方案,如亚马逊的 ECS(弹性容器服务)。简而言之,它允许你对事件做出非常快速的响应。

因此,容器真正封装了软件开发的最佳实践。它们也从运输行业借鉴了许多理念,并且确实是未来的方向。我认为在即将到来的时代,几乎所有的软件项目都将利用容器技术。


在本节课中,我们一起学习了容器技术对于微服务架构的重要性。我们了解到容器如何通过封装一切依赖来确保环境一致性,如何借鉴物理集装箱的理念实现高效“运输”,以及其快速的启动时间如何满足了现代应用(如机器学习)对敏捷性的需求。最后,我们认识到容器编排系统(如Kubernetes)是发挥容器潜力的关键,容器技术已成为现代软件工程不可或缺的一部分。

158:部署容器化.NET 6 API

在本节课中,我们将学习如何为C#开发者构建一个完全云原生的、基于容器的工作流,并部署一个微服务。我们将使用AWS的一系列服务,从开发到部署,全程在云端完成。

我们将启动AWS Cloud9,构建一个包含.NET 6 Web服务的Docker容器,将其推送到Amazon Elastic Container Registry (ECR),然后通过AWS App Runner将其部署为微服务并进行测试。整个过程可以在几分钟内完成。

🚀 云原生容器化工作流概述

上一节我们介绍了课程目标,本节中我们来看看实现这一目标的完整工作流架构。

如果你观察上图所示的四个服务:AWS Cloud9、Amazon ECR、AWS App Runner和CloudShell,你就拥有了一个纯粹的云端开发环境。

这意味着你无需接触任何本地重型开发环境。即使你只有一台性能很弱的设备,比如一台Chromebook,通过Cloud9,你也能在一个与最终部署环境高度相似的、隔离的环境中进行开发。由于Cloud9与ECR之间有高速网络连接,一旦你将.NET应用容器化并推送到ECR,就基本准备就绪了。

当镜像存在于ECR后,你可以将其连接到生产部署流程。App Runner允许你自动部署推送到ECR的新镜像。部署完成后,服务将通过HTTPS完全加密。你可以打开CloudShell,通过curl命令调用并测试它。这代表了.NET和C#开发的下一代工作方式,是值得你掌握的工具。

🛠️ 准备开发环境与代码

上一节我们了解了工作流,本节中我们来看看具体的代码仓库和环境设置。

让我们看一下这个代码仓库:Noagift/donet-6-AWs。在Cloud9中启动后,你需要安装一些.NET 6的包,之后就可以开始部署了。

首先,我已经设置好了一个Cloud9环境。我建议你设置一个性能较强的实例。需要指出的是,安装好.NET后,我可以运行dotnet命令。这里有一个非常简单的程序,它接受一个URL参数/hello?name=,并替换字符串输出。我可以直接运行它。

如果你想将其作为容器化应用运行,这也很简单,因为环境已经设置好了。当然,我们也可以先将其作为常规的.NET应用运行。

以下是运行常规应用的步骤:

  1. 在项目目录下,运行命令:dotnet run
  2. 程序启动后,控制台会显示服务监听的URL(例如 http://localhost:5000)。
  3. 打开另一个终端,使用curl命令测试:curl “http://localhost:5000/hello?name=Bob”

你应该能看到返回结果:Hello Bob。这表明应用程序正在正常工作。

🐳 构建与运行Docker容器

上一节我们成功运行了常规应用,本节中我们来看看如何将其容器化。

如果我想运行容器化版本,同样相当简单。我只需要查看这个Dockerfile。这个Dockerfile基于.NET 6 SDK构建。它查看CSproj文件,进行构建,并设置一个发布版本,暴露端口8080。WebService.dll会在容器化应用中被暴露。

这个环境的优点在于构建非常直接。实际上,我可以在本地直接使用Docker命令构建。

以下是构建和运行Docker镜像的步骤:

  1. 确保你在包含Dockerfile的项目目录中。
  2. 运行构建命令,为镜像打上标签:docker build -t web-service:latest .
  3. 构建完成后,运行容器:docker run -p 8080:8080 web-service:latest

现在,我们可以使用相同的curl命令进行测试,但端口改为8080:curl “http://localhost:8080/hello?name=Bob”。你将再次看到Hello Bob,证明容器化应用运行成功。

📦 推送镜像到Amazon ECR

上一节我们完成了本地容器化,本节中我们将把镜像推送到云端仓库以便部署。

接下来,我们可以将镜像推送到ECR环境。ECR代表Elastic Container Registry。

操作步骤如下:

  1. 在AWS控制台进入ECR服务。
  2. 创建一个新的存储库(Repository),例如命名为test
  3. 创建完成后,点击存储库,选择“查看推送命令”。
  4. 系统会给出完整的推送步骤,包括认证、打标签和推送。

由于我已经认证过,我可以直接复制最后的推送命令并执行:docker push <你的账户ID>.dkr.ecr.<区域>.amazonaws.com/web-service:latest

推送速度会非常快,因为我们在同一网络内。镜像进入ECR后,你可以与团队共享,或使用ECS、App Runner等服务进行部署。接下来我们将使用App Runner。

⚡ 使用AWS App Runner部署服务

上一节我们将镜像推送到了ECR,本节中我们使用App Runner来部署它。

在App Runner中设置一个新的.NET服务非常简单。

部署步骤如下:

  1. 在App Runner控制台点击“创建服务”。
  2. 选择“容器注册表”作为源,并选择“Amazon ECR”。
  3. 浏览并选择你刚刚推送的容器镜像(例如 web-service)。
  4. 在配置页面,可以设置服务名称(如 deploy-dotnet),并启用“自动部署”。这意味着当ECR中有新镜像被推送时,App Runner会自动部署新版本。
  5. 点击“创建并部署”。

部署是微不足道的。App Runner集成了软件开发的最佳实践,如日志、活动指标和配置管理。部署完成后,服务会获得一个可访问的URL。

✅ 通过CloudShell测试部署的服务

上一节我们完成了服务部署,本节中我们进行最终测试。

现在,我可以访问这个URL。我将利用另一个云服务——CloudShell来进行测试。

测试步骤如下:

  1. 在AWS控制台打开CloudShell。
  2. 在CloudShell中,运行curl命令测试我们部署的服务:curl “https://<你的App-Runner-URL>/hello?name=Bob”

你将再次看到返回的Hello Bob。这表明服务正在云端正常运行。

📝 课程总结

本节课中,我们一起学习了如何利用Cloud9、ECR、App Runner和CloudShell这一系列云原生服务,为C#/.NET开发者构建一个完整的端到端开发与部署工作流。

我们实现了从代码编写、本地容器化测试、镜像推送、云端自动化部署到最终测试的完整流程,全程无需中断,且能获得极快的性能。对于.NET开发者来说,尝试这套工作流是很有价值的。

159:部署容器化机器学习微服务 🚀

在本节课中,我们将学习如何将一个机器学习模型打包成Docker容器,并将其部署为可扩展的微服务。我们将使用AWS的Elastic Container Registry (ECR) 来存储容器镜像,并使用AWS App Runner来自动化部署和持续交付流程。


概述

我们将通过一个具体的例子,演示如何将两个不同的机器学习项目容器化并部署到云端。第一个项目是一个简单的身高体重预测模型,第二个是一个更复杂的文本分类(假新闻检测)模型。整个过程涵盖了从本地构建、测试到云端推送和自动化部署的关键步骤。


创建容器仓库

上一节我们介绍了容器化的概念,本节中我们来看看如何为我们的模型创建存储位置。

首先,我们需要在AWS Elastic Container Registry (ECR) 中创建一个仓库来存储我们的容器镜像。这个仓库将存放我们的微服务及其关联的模型。

以下是创建仓库的步骤:

  1. 登录AWS管理控制台,导航到ECR服务。
  2. 点击“创建仓库”。
  3. 为仓库命名,例如 emlops-cookbook(用于存放身高体重模型)。
  4. 点击“创建仓库”完成操作。

创建完成后,ECR会提供一组用于推送镜像的命令。我们需要复制这些命令,稍后在本地环境中使用。


构建并测试本地微服务

在将应用推送到云端之前,我们需要在本地环境中构建Docker镜像并确保微服务能够正常运行。

我们回到Cloud9开发环境。首先,需要登录到ECR。使用ECR提供的登录命令进行身份验证:

aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com

接下来,我们查看项目目录中的Dockerfile。它定义了如何构建镜像,通常包括从基础镜像(如Python 3.8)开始、设置工作目录、安装依赖包以及配置启动命令。

在构建之前,最好先在本地运行微服务进行测试。使用命令启动应用:

python app.py

如果遇到依赖包版本冲突(例如Flask版本问题),可以通过更新requirements.txt文件来解决。使用pip freeze命令查看当前安装的包版本,并固定Flask到兼容的版本(例如Flask==1.1.2)。

解决依赖问题后,再次测试,确保服务可以正常启动。


构建并推送Docker镜像

确认微服务在本地运行无误后,我们就可以开始构建Docker镜像了。

使用以下命令在本地构建镜像,并为其打上标签:

docker build -t emlops-cookbook .
docker tag emlops-cookbook:latest <account-id>.dkr.ecr.<region>.amazonaws.com/emlops-cookbook:latest

构建完成后,使用ECR提供的推送命令将镜像上传到我们之前创建的仓库中:

docker push <account-id>.dkr.ecr.<region>.amazonaws.com/emlops-cookbook:latest

至此,我们的第一个模型镜像已经成功推送到了容器仓库。容器化的优势在于,任何更新只需要构建并推送新的镜像到仓库,部署流程会自动处理后续步骤。


使用AWS App Runner部署服务

现在,我们的镜像已经存储在ECR中,接下来可以将其部署为一个可公开访问的微服务。

我们使用AWS App Runner来完成部署。以下是部署步骤:

  1. 在AWS控制台导航到App Runner服务。
  2. 点击“创建服务”,选择“容器注册表”作为源。
  3. 选择我们刚刚推送镜像的ECR仓库(emlops-cookbook)。
  4. 配置服务名称、实例大小等设置。关键一步是启用“持续部署”,这样每次向ECR推送新镜像时,App Runner会自动更新服务。
  5. 为服务分配合适的IAM角色以访问ECR。
  6. 点击“创建并部署”。

部署过程需要几分钟。完成后,App Runner会提供一个HTTPS端点URL,这就是我们机器学习API的生产级访问地址。我们可以在服务详情中查看监控指标、日志和活动记录。


部署第二个模型服务

为了展示流程的通用性,我们现在来部署第二个机器学习服务。这是一个基于FastAPI的、用于假新闻检测的文本分类模型。

首先,为这个新项目在ECR中创建另一个仓库,例如命名为 fastapi-mlops

然后,在新的项目目录中,使用其Dockerfile构建镜像。这个Dockerfile可能使用了不同的基础镜像(如AWS Lambda基础镜像),但核心步骤相似:复制代码、模型文件和依赖,然后安装。

docker build -t fastapi-mlops .

在推送之前,可以在本地运行容器进行快速测试,确保API能够响应请求。

docker run -p 8080:8080 <image-id>

测试无误后,为镜像打标签并推送到第二个ECR仓库。

docker tag fastapi-mlops:latest <account-id>.dkr.ecr.<region>.amazonaws.com/fastapi-mlops:latest
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/fastapi-mlops:latest

最后,在App Runner中创建第二个服务,指向 fastapi-mlops 仓库,并同样启用持续部署。部署完成后,我们就拥有了两个独立运行的机器学习微服务端点。


测试与调用已部署的服务

服务部署成功后,我们需要验证它们是否工作正常。

对于第一个服务(身高体重预测),我们可以使用浏览器或命令行工具(如curl)调用其端点。例如,使用项目自带的CLI工具进行调用:

./cli predict --host https://your-apprunner-url.com --height 155

对于第二个服务(假新闻检测),FastAPI通常会生成交互式API文档(Swagger UI)。我们直接访问服务URL的 /docs 路径,在网页界面中输入示例文本(如“aliens are coming”)并执行,即可看到预测结果(如“Fake news”)。

在App Runner的控制台,我们可以查看每个服务的调用指标(如请求次数、延迟)和CloudWatch日志,确认服务正在被成功调用并运行。


模型来源与MLOps工作流总结

本节课中我们一起学习了完整的容器化部署流程。最后,我们来总结一下模型微服务可能的来源,这构成了灵活MLOps工作流的基础:

一个容器化的机器学习微服务,其核心模型可以来自以下几种途径:

  1. 下载预训练模型:从模型库(如Hugging Face、TensorFlow Hub)下载现有模型,可能进行微调(迁移学习)。
  2. 自动化机器学习(AutoML):使用平台(如Databricks、Amazon SageMaker)的AutoML功能自动生成模型。
  3. 从零开始训练:传统方式,完全由自己的数据和算法从头训练模型。
  4. 调用外部端点:模型本身托管在第三方服务,微服务仅作为代理调用其API。

无论模型来自何处,一旦将其与微服务代码一起容器化,后续的部署和更新流程就变得统一且简单。通过将镜像推送到容器注册表,并利用像AWS App Runner这样的服务,我们可以轻松实现持续部署,确保每次模型或代码的更新都能自动、可靠地到达生产环境。


总结

在本节课中,我们完整实践了将机器学习模型容器化并部署为云微服务的流程。我们学习了如何创建ECR仓库、在本地构建和测试Docker镜像、将镜像推送到云端仓库,以及最终使用AWS App Runner配置自动化部署。我们还探讨了模型的不同来源,强调了容器化在实现标准化、可重复的MLOps工作流中的关键作用。通过这种方法,机器学习应用的部署和维护变得高效且易于管理。

160:Python打包与命令行工具导论

在本节课中,我们将学习一个非常流行的命令行工具框架——click。我们将首先了解click的一些高级功能,然后深入探讨如何使用click进行测试。最后,我们也会介绍click的入门技巧,这些技巧可以帮助你从一个简单的函数开始构建命令行工具。

什么是Click?

click是一个Python包,用于以可组合的方式创建美观的命令行界面。它的设计目标是让编写命令行工具的过程变得快速而有趣,同时又能确保工具具有一致性且功能完备。

Click的核心概念

click的核心是使用装饰器来将普通Python函数转换为命令行接口。最基本的元素是命令和参数。

  • 命令:通过@click.command()装饰器定义,它标记一个函数作为命令行命令的入口点。
  • 参数:通过@click.argument()@click.option()装饰器定义,用于从命令行接收输入。

以下是一个简单的代码示例:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name', help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for _ in range(count):
        click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    hello()

Click的高级功能

上一节我们介绍了click的基本用法,本节中我们来看看它的一些高级功能,这些功能能让你的命令行工具更加强大和用户友好。

以下是click提供的一些高级特性:

  1. 参数类型click支持多种参数类型,如click.INTclick.FLOATclick.File等,能自动验证和转换用户输入。
  2. 命令组:使用@click.group()可以创建一组子命令,这对于组织复杂的工具非常有用。
  3. 上下文click提供了上下文对象(ctx),允许在不同命令和回调函数之间传递数据。
  4. 自定义帮助:你可以轻松地自定义命令的帮助信息格式和内容。
  5. Shell补全click支持为Bash、Zsh和Fish等shell生成自动补全脚本。

使用Click进行测试

为命令行工具编写测试是保证其可靠性的关键。click提供了CliRunner实用程序,可以方便地在隔离环境中调用和测试你的命令。

以下是使用CliRunner进行测试的基本步骤:

  1. click.testing导入CliRunner
  2. 创建一个CliRunner实例。
  3. 使用runner.invoke()方法调用你的命令,并传入参数。
  4. 检查返回结果,如退出代码、输出内容或可能出现的异常。
from click.testing import CliRunner
from your_module import hello

def test_hello():
    runner = CliRunner()
    result = runner.invoke(hello, ['--count', '3', '--name', 'World'])
    assert result.exit_code == 0
    assert 'Hello, World!' in result.output
    assert result.output.count('Hello, World!') == 3

Click入门技巧

对于初学者,从简单开始往往是最好的方式。click允许你从一个没有任何参数的简单函数快速构建出可用的命令行工具。

你只需要做两件事:

  1. 导入click
  2. @click.command()装饰你的函数。

这样,你的函数就变成了一个可以通过命令行直接调用的命令。随着需求的增加,你可以逐步添加选项(@click.option)和参数(@click.argument)来丰富其功能。

总结

本节课中我们一起学习了click框架。我们从其核心概念——使用装饰器定义命令和参数开始,然后探索了诸如参数类型、命令组等高级功能。我们还了解了如何使用CliRunnerclick工具编写自动化测试。最后,我们回顾了从单个函数起步的简单入门方法。掌握click能帮助你高效地构建出既专业又易用的命令行工具,这对于数据工程中的任务自动化和脚本管理至关重要。

161:使用Python构建健壮的命令行工具

在本节课中,我们将学习如何使用Python构建一个健壮的命令行工具。我们将从最简单的场景开始,逐步深入到更复杂的应用,确保你能掌握创建强大命令行工具所需的核心知识。

构建命令行工具的关键要素

为了创建一个健壮的命令行工具,你需要了解许多要素。本节将逐一介绍这些核心概念。

解析命令行参数

上一节我们介绍了命令行工具的基本概念,本节中我们来看看如何让程序接收用户输入。这是通过解析命令行参数实现的。

以下是解析命令行参数的两种主要方法:

  1. 使用 sys.argv:这是Python内置模块,用于获取命令行输入的原始参数列表。

    import sys
    # sys.argv[0] 是脚本名,后续元素是传入的参数
    arguments = sys.argv[1:]
    
  2. 使用 argparse 模块:这是更强大、更专业的参数解析库,能自动生成帮助信息并处理复杂参数。

    import argparse
    parser = argparse.ArgumentParser(description='描述你的工具')
    parser.add_argument('filename', help='输入文件名')
    args = parser.parse_args()
    

处理输入与输出

一个有用的工具需要能够读取输入并产生输出。这通常涉及文件操作和数据流处理。

以下是处理输入输出的常见操作:

  • 读取标准输入:使用 sys.stdin 读取管道传递的数据。
  • 写入标准输出/错误:使用 print()sys.stdout/sys.stderr 输出结果或错误信息。
  • 文件操作:使用 open() 函数和文件对象的 read/write 方法处理文件。

实现核心逻辑

工具的核心是其要完成的具体任务。这部分代码应保持模块化,与参数解析和输入输出处理分离。

核心逻辑的公式可以概括为:
输出 = 核心函数(输入, 参数)

添加错误处理

健壮的工具必须能妥善处理意外情况,如文件不存在、参数无效等,避免程序崩溃。

以下是实现错误处理的关键步骤:

  1. 使用 try...except 块捕获可能发生的异常。
  2. 向用户提供清晰、有用的错误信息。
  3. 根据错误类型,返回不同的退出码(通过 sys.exit(code))。

设置适当的退出码

命令行工具通过退出码向操作系统或调用它的脚本报告执行状态。惯例是:0表示成功,非0表示错误

从简单到复杂的构建流程

我们将遵循一个清晰的流程来构建工具,确保结构良好。

以下是构建命令行工具的标准步骤:

  1. 定义需求:明确工具要做什么,需要哪些参数。
  2. 搭建框架:使用 argparse 创建解析器,定义参数。
  3. 实现功能:编写完成实际任务的函数。
  4. 集成组装:在 main() 函数中连接参数解析、核心逻辑和错误处理。
  5. 测试优化:使用不同参数和输入测试工具,优化用户体验。

一个基础的脚本结构如下所示:

#!/usr/bin/env python3
import argparse
import sys

def main():
    parser = argparse.ArgumentParser()
    # ... 添加参数定义 ...
    args = parser.parse_args()
    # ... 调用核心逻辑 ...
    return 0

if __name__ == '__main__':
    sys.exit(main())

总结

本节课中我们一起学习了使用Python构建健壮命令行工具的核心要素。我们从解析命令行参数开始,探讨了如何处理输入输出、实现核心业务逻辑、添加必要的错误处理,并最终确保工具能返回正确的退出状态码。遵循从定义需求到测试优化的流程,你将能够创建出结构清晰、用户友好且稳定可靠的命令行工具。

162:Python项目初始设置 🐍

在本节课中,我们将要学习如何为一个Python项目进行初始设置。一个结构清晰的项目是代码可维护性和可扩展性的基础。

概述:为什么需要项目结构?

Python项目之所以有用,是因为它提供了结构,而结构带来了秩序。秩序之所以重要,是因为它为项目的成长奠定了基础。

虽然Python作为一种脚本语言,从单个文件开始非常容易,但当需要更多文件、支持文件时,情况会迅速变得复杂。例如,许可证、自述文件、文档、测试、工具脚本或依赖声明,以及打包等问题,这些都是一个项目需要涵盖的内容。

接下来,我们将快速浏览一个项目,并了解其中一些你可能感兴趣的组成部分。

项目结构详解

以下是一个名为 J format 的简单Python命令行工具项目在Github上的结构。我们将逐一介绍其中的关键目录和文件。

目录与文件说明

  • .github/ 目录:当你需要进行一些自动化操作时,这个目录很有用。例如,它包含一个YAML工作流文件,这是Github用于在拉取请求时进行自动化操作(如测试)所必需的。你可能在项目初期不需要它,但了解其存在的原因是有益的。
  • bin/ 目录:这是可选的,因为在Python中有多种实现方式。它包含一个脚本,允许我们在打包和分发项目时安装这个特定的脚本。我们稍后也会看到通过 setup.py 和Python打包工具来实现的另一种方式。
  • examples/ 目录:你可能希望在项目中包含示例文件,用于展示基本用法。虽然这里的示例文档化程度不高,但这是你可能会看到的一种做法。
  • .gitignore 文件:这个文件允许你更有效地使用Git,忽略某些不应纳入版本控制的文件。特别是在Python项目中,有许多文件(如缓存、虚拟环境)需要被忽略。
  • LICENSE 文件:包含项目许可证总是一个好主意。在这个案例中,我们使用的是MIT许可证。
  • .tox.ini 文件:用于配置Tox测试工具。这在本地终端运行测试,尤其是需要测试多个Python版本时非常有用。
  • setup.py 文件:允许你进行一些基本的打包配置。我们将在后续课程中详细介绍如何进行打包。
  • tests/ 目录:这是你存放测试文件的地方。有时测试目录会放在项目根目录下,与 examples.github 并列;有时则会放在项目内部。这没有严格规定。放在内部可能意味着你会将测试文件与项目代码一同分发。
  • _init_.py 文件:这非常常见,尤其是当你打算将项目创建为一个可导入的模块时。它标志着该目录是一个Python包。
  • main.py 文件:对于命令行工具,这是你放置主要代码或脚本入口点的地方。例如,你可以在这里定义一个 main 函数。

核心代码示例

main.py 文件中,通常可以找到一个主函数作为程序的入口点。例如,在代码的第42行附近,你可能会看到类似的结构:

def main():
    # 这里是程序的主要逻辑
    pass

if __name__ == "__main__":
    main()

同时,在文件的顶部,通常会从项目中的其他模块导入功能。例如:

from . import reformat  # 从当前包的顶层目录导入reformat模块

这种结构定义了一种秩序,让你知道将不同的内容放在何处。如果你想添加更多测试,就放到 tests/ 目录;如果需要更多支持文件或子模块,也可以在相应位置添加。

总结

本节课中,我们一起学习了Python项目初始设置的基本要素。我们看到了一个典型项目包含的目录和文件,如 .github/bin/examples/.gitignoreLICENSEsetup.pytests/ 以及核心的 __init__.pymain.py 文件。这种结构化的方法为代码组织、测试、文档化和分发奠定了基础,在后续的课程中我们将会持续应用这些概念。

163:Python命令行工具框架概览 🛠️

在本节课中,我们将要学习使用Python构建命令行工具的几种不同方法。我们将从最基础的方式开始,逐步介绍更高级、功能更完善的框架,帮助你理解如何选择适合自己项目的工具。

概述

命令行工具是数据工程师日常工作中不可或缺的一部分。Python提供了多种方式来创建这些工具,从直接使用内置模块到借助功能强大的第三方库。本节我们将对比三种主要方法:直接使用 sys 模块、使用Python标准库中的 argparse,以及使用第三方库 click。我们将分析每种方法的优缺点,并理解它们各自的适用场景。

命令行工具的三种构建方式

1. 使用 sys 模块(最基础的方法)

上一节我们介绍了命令行工具的基本概念,本节中我们首先来看看最基础、不依赖任何框架的构建方法:直接使用Python内置的 sys 模块。

这种方法的核心是手动解析通过 sys.argv 获取的命令行参数。sys.argv 是一个列表,其中包含了命令行输入的所有参数。

import sys
import json

def main():
    # 手动检查参数数量
    if len(sys.argv) == 1:
        print("Usage: python tool.py <json_string>")
        sys.exit(0) # 退出程序

    # 获取输入参数
    json_string = sys.argv[1]
    data = json.loads(json_string)
    print(json.dumps(data, indent=2))

if __name__ == "__main__":
    main()

以下是使用 sys 模块的主要特点:

  • 完全手动:你需要自己检查参数数量(len(sys.argv)),并据此打印帮助信息或处理逻辑。
  • 灵活性高但代码复杂:对于极其简单的工具(只有一个固定参数)来说,这种方法足够直接。但一旦需要添加可选标志(如 --sort)或处理多个参数,代码会迅速变得复杂且容易出错。
  • 无自动帮助:框架不会自动为你生成 --help 菜单,所有帮助文本都需要手动编写和触发。

当工具的逻辑非常简单时,这种方法完全可行。但当需求增长时,维护这种手动解析的代码会变得困难。

2. 使用 argparse 框架(Python标准库)

既然手动解析参数在复杂场景下很麻烦,那么让我们来看看Python标准库中自带的解决方案:argparse 框架。它提供了结构化的参数定义和自动生成的帮助菜单。

argparse 的核心是创建一个 ArgumentParser 对象,然后通过 add_argument 方法定义工具接受的参数和选项。

import argparse
import json

def main():
    # 1. 创建解析器
    parser = argparse.ArgumentParser(description="格式化JSON字符串。")
    # 2. 添加参数定义
    parser.add_argument("json_string", help="需要格式化的JSON字符串")
    parser.add_argument("--sort", action="store_true", help="是否按键排序输出")
    # 3. 解析参数
    args = parser.parse_args()

    # 使用解析后的参数
    data = json.loads(args.json_string)
    if args.sort:
        data = dict(sorted(data.items()))
    print(json.dumps(data, indent=2))

if __name__ == "__main__":
    main()

以下是 argparse 的主要工作流程:

  • 定义解析器:创建 ArgumentParser 对象,并可添加工具描述。
  • 声明参数:使用 add_argument 定义位置参数(如 json_string)和可选标志(如 --sort)。你可以指定参数类型、帮助文本和动作(如 store_true 表示布尔标志)。
  • 解析与使用:调用 parse_args() 后,所有参数会被解析并存储在一个命名空间对象(args)中。通过访问该对象的属性(如 args.json_string, args.sort)来使用这些值。

argparse 自动生成了专业的帮助信息(通过 --help 调用),极大地减轻了开发负担。它是Python内置的,无需安装任何额外依赖。

3. 使用 click 框架(第三方库)

最后,我们来看一个更现代、声明式的第三方库:click。它通过使用装饰器(@)让命令行工具的定义变得非常简洁和直观。

click 使用装饰器将普通Python函数直接转换为命令行接口。@click.command() 装饰器声明这是一个命令行命令,@click.argument()@click.option() 则分别用于定义参数和选项。

import click
import json

@click.command()
@click.argument("json_string")
@click.option("--sort", is_flag=True, help="是否按键排序输出")
def format_json(json_string, sort):
    """格式化JSON字符串的工具。"""
    data = json.loads(json_string)
    if sort:
        data = dict(sorted(data.items()))
    click.echo(json.dumps(data, indent=2))

if __name__ == "__main__":
    format_json()

以下是 click 框架的突出特点:

  • 装饰器语法:使用 @click.command() 等装饰器来定义命令、参数和选项,代码清晰且贴近函数本身的功能。
  • 参数自动注入:定义的参数和选项会自动作为参数传递给被装饰的函数,无需手动从某个对象中提取。
  • 功能丰富:内置了对环境变量、提示输入、子命令等高级功能的支持。
  • 需要安装click 是一个第三方库,需要使用 pip install click 安装,并需要在项目依赖中声明。

click 的语法让代码更易读、易写,特别适合构建具有多个命令和复杂选项的CLI工具。

总结

本节课中我们一起学习了使用Python构建命令行工具的三种主要方法:

  1. sys 模块:最基础,适合极其简单的场景,但需要完全手动处理参数解析和帮助信息,复杂度随功能增加而急剧上升。
  2. argparse 框架:Python标准库的一部分,无需额外安装。它提供了结构化的参数定义和自动帮助生成,是构建中等复杂度命令行工具的标准选择。
  3. click 框架:一个强大的第三方库,通过装饰器提供了一种声明式、直观的API。它让代码更简洁,并支持许多高级功能,是构建复杂、用户友好型命令行工具的绝佳选择。

选择哪种方式取决于你的具体需求:对于快速脚本可用 sys;对于需要分发、且希望无额外依赖的工具,argparse 是可靠选择;而对于追求最佳开发体验和强大功能的项目,click 非常值得引入。

164:使用Click构建命令行工具 🛠️

在本节课中,我们将学习如何使用Python的Click库来构建一个命令行工具。我们将从一个简单的脚本开始,逐步将其改造为一个功能更完善、用户友好的命令行应用程序。

概述

我们将分析一个名为“Lter”的简单命令行工具示例。该工具的主要功能是加载一个CSV文件,并使用pandas库在后台检测文件中的一些潜在问题。通过本教程,你将学会如何使用Click库来装饰你的Python函数,使其能够接收命令行参数,并自动获得帮助菜单和错误报告等功能。

代码结构与初始状态

首先,我们来看一下示例代码的初始结构。代码中包含一些辅助函数,但目前它们不是重点。

最重要的部分是第47行的 if __name__ == "__main__":。这行代码使得脚本可以在终端中作为命令行工具直接执行。如果没有这行代码,main 函数就不会被自动调用。

当前,脚本使用了一个技巧:它将命令行的最后一个参数(即文件名)传递给 main 函数(第31行)。目前,代码中虽然导入了 click 库,但尚未使用它。

引入Click库进行改造

现在,我们将使用Click库来改进这个命令行工具。首先,确保已经正确导入Click。

import click
import pandas as pd

接下来,我们需要改造 main 函数。使用Click的第一步是使用一个 装饰器。装饰器是一种特殊的语法,用于修改函数的行为。

我们将使用 @click.command() 这个装饰器来声明 main 函数是一个命令行工具的一部分。

@click.command()
def main():
    # ... 原有的函数逻辑

定义命令行参数

我们的工具需要接收一个参数:要处理的CSV文件路径。在Click中,我们可以使用 @click.argument() 装饰器来定义参数。

我们在 main 函数上方添加这个装饰器,并指定参数名为 filename

@click.command()
@click.argument('filename')
def main(filename):
    # 现在,filename参数会从命令行自动传入这个函数
    # ... 原有的函数逻辑

这段代码的意思是:工具将接受一个名为 filename 的单一参数,并将其传递给 main 函数。

运行改造后的工具

保存修改后,让我们在终端中再次运行这个工具。

python lter.py examples/carriage.csv

你会发现工具的行为和之前一样,成功加载并处理了文件。但现在,我们获得了一些“免费”的新功能。

Click带来的优势

1. 自动生成的帮助菜单
现在,如果你运行工具时加上 --help 标志,就会看到一个清晰的帮助菜单。

python lter.py --help

帮助菜单会显示工具的使用方法(Usage)和可用的选项(Options)。虽然目前选项不多,但这个框架允许你轻松地添加更复杂的标志和选项。

2. 自动错误报告
如果你忘记提供必需的 filename 参数,Click会自动给出清晰的错误提示。

python lter.py

输出会类似于:Error: Missing argument ‘FILENAME’.

这提供了即时的、用户友好的错误反馈。

3. 强大的扩展基础
通过这个简单的起点,你可以继续添加更多功能。例如,你可以添加可选标志来控制程序的行为,或者添加多个参数来处理不同的输入。

总结

本节课中,我们一起学习了使用Click库构建Python命令行工具的基础步骤。

  1. 导入Click库import click
  2. 使用装饰器:用 @click.command() 装饰主函数,使其成为命令行接口。
  3. 定义参数:用 @click.argument(‘arg_name’) 装饰器来定义命令行参数,这些参数会自动注入到被装饰的函数中。

通过这简单的三步,你的脚本就从一个普通的Python程序,转变为一个拥有自动帮助文档、参数解析和错误报告功能的专业命令行工具。你可以在此基础上,继续探索Click库的更多功能,如添加选项(@click.option)、命令组等,来构建更复杂的应用程序。

165:探索高级命令行工具特性

在本节课中,我们将学习如何增强一个基于Click库构建的Python命令行工具,使其功能更强大、对用户更友好。我们将重点介绍如何添加参数类型检查、可选标志以及确保跨平台输出一致性的方法。

上一节我们介绍了如何使用Click库创建一个基础的命令行工具。本节中,我们来看看如何为其添加一些高级特性,以提升工具的健壮性和用户体验。

添加参数类型检查

以下是增强工具的第一步:为命令行参数添加类型检查。这能提供更精确的错误报告。

import click

@click.command()
@click.argument('filename', type=click.Path(exists=True))
def main(filename):
    print(f"Processing file: {filename}")

在上面的代码中,type=click.Path(exists=True) 告诉Click,filename 参数必须是一个存在的文件路径。如果用户提供了一个不存在的路径,Click会自动生成一个清晰的错误信息。

例如,在终端中执行 python my_tool.py /some/nonexistent/path,工具会报错:“Invalid value for ‘FILENAME’: Path ‘/some/nonexistent/path’ does not exist.”。这比让程序在后续步骤中崩溃要友好得多。

添加可选标志(Options)

接下来,我们为工具添加一个可选标志,例如一个控制输出详细程度的 --verbose 标志。

import click

@click.command()
@click.argument('filename', type=click.Path(exists=True))
@click.option('-v', '--verbose', is_flag=True, help='Enable verbose output.')
def main(filename, verbose):
    if verbose:
        click.echo("Running in verbose mode.")
    click.echo(f"Processing file: {filename}")

在上面的代码中:

  • @click.option() 装饰器用于定义可选标志。
  • -v--verbose 是标志的别名,用户使用任意一个都可以。
  • is_flag=True 表示这是一个布尔标志,出现即为 True
  • help 参数用于在帮助信息中描述此选项。

现在,运行 python my_tool.py --help,你会在帮助菜单中看到新增的 -v, --verbose 选项。执行 python my_tool.py existing_file.txt --verbose 会触发详细模式下的输出。

使用 click.echo 确保输出一致性

最后,我们介绍一个确保工具在不同操作系统上都能正确输出的最佳实践:使用 click.echo 代替内置的 print 函数。

# 使用 click.echo 替代 print
click.echo(f"Processing file: {filename}")

click.echo 的功能与 print 类似,但它能更好地处理不同平台(如Windows、Linux、macOS)的终端差异,并妥善处理Unicode字符。这能确保你的工具无论在哪里运行,输出都是稳定和一致的。

本节课中我们一起学习了如何为Click命令行工具添加高级特性。我们首先通过 click.Path 为参数添加了存在性检查,以提供清晰的错误报告。然后,我们使用 @click.option 添加了可别名化的布尔标志,以增加功能选项。最后,我们介绍了使用 click.echo 来替代 print,以确保工具跨平台的输出一致性。这些改进使得命令行工具更加健壮和用户友好。

166:命令行工具构建要点回顾 🛠️

在本节课中,我们将回顾构建命令行工具的一些基础知识。我们将从项目结构入手,然后学习如何使用 click 库来增强一个简单的Python脚本,为其添加选项和参数,并自动生成帮助菜单和错误报告功能。

项目结构基础

上一节我们介绍了命令行工具的概念,本节中我们来看看一个典型的命令行工具项目应具备怎样的目录结构。合理的项目结构有助于代码的组织和维护。

以下是项目目录可能包含的内容及其作用:

  • src/ 目录:通常用于存放项目的主要源代码。
  • tests/ 目录:用于存放所有测试代码,确保工具功能的正确性。
  • docs/ 目录:存放项目相关的文档。
  • setup.pypyproject.toml 文件:用于定义项目的元数据和依赖,方便打包和安装。
  • requirements.txt 文件:列出项目运行所需的所有Python包。

使用Click库增强脚本

了解了基本结构后,我们将动手实践。本节将演示如何利用 click 库,将一个非常简单的单文件Python脚本改造为功能丰富的命令行工具。

我们首先从一个基础脚本开始,然后使用 click 为其添加命令行选项和参数。click 库能让我们轻松地为工具创建帮助菜单,并提供有用的错误信息,从而提升用户体验。

以下是使用 click 的核心步骤:

  1. 安装Click库:首先需要通过包管理器安装 click

    pip install click
    
  2. 导入与装饰:在Python脚本中导入 click,并使用 @click.command() 等装饰器来定义命令行接口。

    import click
    
    @click.command()
    @click.option('--count', default=1, help='Number of greetings.')
    @click.argument('name')
    def hello(count, name):
        for _ in range(count):
            click.echo(f"Hello, {name}!")
    
    if __name__ == '__main__':
        hello()
    
  3. 定义参数与选项:使用 @click.option() 定义可选参数,使用 @click.argument() 定义必需参数。我们可以为它们设置默认值、帮助文本和类型。

  4. 生成帮助与错误处理:完成上述步骤后,click 会自动为你的工具生成帮助菜单(使用 --help 选项调用)。它还能在用户输入无效时提供清晰的错误提示。

总结

本节课中我们一起学习了构建命令行工具的两个关键方面。首先,我们探讨了基本的项目目录结构,了解了各组成部分的用途。接着,我们深入使用了 click 库,将一个简单的脚本增强为具有参数解析、帮助菜单和错误报告功能的专业命令行工具。掌握这些要点,是创建易于使用和维护的命令行程序的基础。

167:Python项目打包与分发导论 🚀

在本节课中,我们将学习Python项目打包与分发的基础知识。了解如何将你的代码项目进行封装,并使其能够被他人轻松安装和使用,是分享和复用代码的关键步骤。

概述

Python打包在两种情况下对你的项目非常有用。

第一种情况是声明依赖项。我们之前见过安装click这样的工具,它是一个命令行工具框架。你可能希望安装这个特定的库,并将其声明为你的用户需要安装的外部依赖项。这是原因之一。

第二个原因当然是分发你的项目。当你不再满足于单个文件来完成某些自动化或有用的任务,并希望将其分发给其他系统或可能觉得它有用的用户时,就到了进行打包的时候。

在本课中,我们将利用Python的工具集和其他库,帮助你不仅打包项目,还能将项目注册到Python包索引上,以便任何人轻松安装和分发。

打包的核心概念

上一节我们介绍了打包的两个主要目的。本节中,我们来看看实现打包需要理解的核心概念。

以下是打包过程中涉及的关键组件:

  • setup.py:这是打包的“配方”文件。它包含了关于你项目的元数据,如名称、版本、作者、依赖项等。它使用setuptools库来定义如何构建和安装你的包。
  • pyproject.toml:一个较新的配置文件,用于声明项目构建系统和依赖项。它正逐渐成为现代Python项目的标准配置方式。
  • 依赖项管理:明确列出你的项目运行所需的外部库。这确保了其他用户能在他们的环境中复现项目运行所需的条件。
  • Python包索引:一个公共的软件仓库,开发者可以上传他们的Python包,供全世界的用户下载和安装。

总结

本节课中,我们一起学习了Python项目打包与分发的基本概念。我们了解到,打包不仅是为了管理项目依赖,确保环境一致性,更是为了将你的代码成果有效地分享给他人。通过使用setup.pypyproject.toml等工具,并利用Python包索引,你可以使自己的项目变得易于安装和使用。掌握这些基础知识,是迈向创建可复用、可分发软件库的第一步。

168:Python打包技术入门 🐍📦

在本节课中,我们将要学习Python项目打包的基础知识。打包是将你的代码、依赖和配置组织起来,以便于分发、安装和运行的过程。我们将从创建虚拟环境开始,逐步了解打包所需的核心文件。

虚拟环境:项目隔离的第一步

上一节我们介绍了打包的概念,本节中我们来看看如何为项目创建一个独立的运行环境。虚拟环境允许你将项目所需的库与系统全局安装的库隔离开来,便于管理和清理。

我们有一个小型项目,但编辑器提示click库无法导入。首先需要安装click作为项目依赖。处理Python项目打包时,你首先会遇到的就是虚拟环境。

创建虚拟环境的一种方法是使用python -m venv命令。我喜欢使用.venv作为目录名,这会创建一个隐藏的虚拟环境目录。

python -m venv .venv

编辑器此时可能检测到虚拟环境已创建,并提示你激活它。在某些情况下,编辑器会自动刷新并激活环境。但了解如何手动激活虚拟环境也很有用。

你可以使用source命令来激活虚拟环境。

source .venv/bin/activate

激活后,你会看到终端提示符发生了变化,前面显示了(.venv)。现在我们可以开始安装项目依赖了。

验证虚拟环境是否激活的一个好方法是检查python命令的路径。

which python

该命令应指向.venv目录下的路径,而不是系统的Python路径。这是判断虚拟环境是否激活的标志之一,除了提示符的变化。

现在,我们可以使用pip来安装click库。

python -m pip install click

click库已通过pip安装完成。之后,你需要重新加载编辑器窗口,以便编辑器识别到新激活的虚拟环境和已安装的库。编辑器需要知道当前激活的是哪个虚拟环境。

如果你在VS Code中打开一个新的终端,它会自动为你激活该虚拟环境。这是Python打包,特别是VS Code编辑器的一个实用功能。

打包项目:核心文件解析

虚拟环境是打包需要了解的第一件事,但让我们进一步了解你将与之交互的几个核心文件。

首先,我们创建了.venv目录,项目所有依赖都将安装在这里。我们的项目名为jformat,包含几个文件。

以下是项目打包中几个特定的文件:

  • LICENSE文件:包含项目的许可证信息。
  • README.md文件:你可以在这里放置项目文档。
  • MANIFEST.in文件:这个文件允许你在打包时,指定包含或排除某些文件模式。例如,如果你有辅助图片但不想分发它们,可以在这里添加排除路径。在本例中,我们选择包含LICENSE文件,因为它通常不会自动包含在Python项目中。
  • setup.py文件:这是另一个你将打交道的重要文件。它是Python中的一个约定,用于配置如何打包和分发你的工具、程序、库或项目,以便将其上传到像PyPI这样的平台。

综上所述,拥有setup.pyMANIFEST.in和一个虚拟环境,你就具备了使用Python打包技术的基本条件。

总结

本节课中我们一起学习了Python打包的入门知识。我们从创建和激活虚拟环境开始,它确保了项目的依赖隔离。接着,我们探讨了打包项目的核心文件:setup.py用于配置项目打包信息,MANIFEST.in用于控制哪些文件被包含在分发包中。掌握这些基础是分发和共享Python项目的重要第一步。

169:Python Setuptools实战 🛠️

在本节课中,我们将学习如何使用Python的setuptools库来打包和分发你的Python项目。我们将通过一个名为demo-jformat的命令行工具示例,一步步了解setup.py文件的构成,以及如何利用setuptools在本地安装和开发你的工具。

概述 📋

setuptools是Python生态中用于构建和分发包的核心工具。通过定义一个setup.py文件,你可以指定项目的元数据、依赖关系以及如何将你的代码转换为可安装的包或命令行工具。本节将引导你完成一个基本的setup.py配置,并演示其核心功能。

定义setup.py文件

要使用setuptools,首先需要在项目中创建一个setup.py文件。这个文件定义了项目的所有配置信息。

以下是setup.py文件的一个示例,我们将逐行解析其关键部分:

from setuptools import setup, find_packages

setup(
    name='demo-jformat',
    version='0.1.0',
    description='A little command line tool for JSON formatting.',
    install_requires=[
        'click>=7.1',
        'colorama'
    ],
    entry_points={
        'console_scripts': [
            'jformat=jformat.main:main',
        ],
    },
    author='Your Name',
    author_email='your.email@example.com',
    packages=find_packages(),
)

现在,让我们逐一解释上述代码中每个参数的作用。

参数详解

以下是setup函数中各个关键参数的说明:

  • name: 这是项目的名称,用户将使用这个名字来安装你的包。例如,用户会运行 pip install demo-jformat
  • version: 项目的版本号,用于跟踪发布和变更。
  • description: 项目的简短描述。
  • install_requires: 项目运行所依赖的其他Python包列表。你可以指定版本约束,例如 'click>=7.1'
  • entry_points: 这是一个重要的配置项,用于创建命令行工具。console_scripts部分定义了一个名为jformat的终端命令。当用户输入jformat时,它会执行jformat模块内main.py文件中的main函数。
  • authorauthor_email: 用于标识项目作者信息。
  • packages: 使用find_packages()辅助函数,可以自动发现项目中的所有包,无需手动列出。

本地安装与开发模式

理解了setup.py的构成后,接下来我们看看如何利用它来安装和测试我们的工具。

在终端中,确保已激活项目的虚拟环境,然后运行以下命令进行本地开发安装:

python setup.py develop

运行此命令后,setuptools会将你的项目以“开发模式”安装到当前环境中。这意味着它不会将文件复制到Python的site-packages目录,而是创建一个链接指向你的源代码目录。

为什么使用develop模式? 这允许你在修改源代码后,无需重新安装包,更改就能立即在命令行工具中生效,非常适合开发阶段。

安装成功后,你可以通过which jformat命令来验证工具是否已加入系统路径。现在,运行jformat --help就可以看到我们工具的帮助信息了。

构建与打包

除了开发安装,setuptools还能将你的项目构建成可分发的包。

要构建一个可分发的包(如源码包或wheel包),可以运行:

python setup.py build

这个命令会在项目目录下创建一个build文件夹,里面包含了打包所需的所有文件结构。这是将项目上传到PyPI(Python包索引)或其他仓库进行分发前的必要步骤。

你还可以通过python setup.py --help-commands查看setuptools支持的所有命令和选项,功能非常丰富。

总结 🎯

本节课我们一起学习了Python setuptools的基础实战。我们首先了解了如何编写一个setup.py文件来定义项目的元数据、依赖和命令行入口点。接着,我们使用python setup.py develop命令在本地以开发模式安装项目,实现了代码修改的即时生效。最后,我们提到了使用python setup.py build命令来构建可分发的包。

虽然Python打包生态非常复杂,但掌握setuptools的这些基础知识,足以让你开始构建、分享自己的命令行工具和Python项目。在接下来的课程中,我们将探索如何将打包好的项目发布到公共仓库。

170:将Python包上传至注册中心 📦

在本节课中,我们将学习如何将开发完成的Python项目打包,并上传到Python包索引(PyPI)等公共注册中心,以便全球用户都能方便地安装和使用你的工具。

上一节我们介绍了如何构建和测试Python项目。本节中,我们来看看如何将项目发布到线上。

准备上传

项目开发完成后,下一步是将其分发并推送到一个注册中心。理想情况下,这个注册中心是Python包索引(PyPI),网址是 https://pypi.org。你需要提前准备好一个账户。

例如,我创建了一个名为“Alfredodessa”的账户。登录后,你可以看到自己所有的项目。以我一直在测试的 demojformat 项目为例,它最近已经发布。点击“管理”按钮,可以看到该项目的多个发布版本,这让你能很好地控制上传的内容。

创建分发包

那么具体如何上传呢?目前推荐的做法是创建一个源码分发包。你也可以创建轮子分发包并上传。

以下是创建源码分发包的步骤:

  1. 使用 python setup.py sdist 命令创建源码分发包。这个命令会在 dist 目录下生成一个 .tar.gz 文件(即源码分发包)。
  2. 源码分发包不包含任何编译内容。如果你的项目需要编译,可能需要使用轮子或其它编译系统。源码分发包兼容性较好,能在多种系统上工作。

使用Twine上传

创建好分发包后,需要使用一个名为 Twine 的工具来上传。首先需要安装Twine。

以下是安装和上传的步骤:

  1. 使用 python -m pip install twine 命令安装Twine。
  2. Twine安装完成后,就可以上传包了。它会提示你输入PyPI的用户名和密码,除非你已经在主目录的 .pypirc 配置文件中设置了凭证。
  3. 使用 twine upload dist/<包文件名> 命令上传。如果尝试上传一个已存在相同版本号的包,系统会报错提示“文件已存在”。
  4. 此时,你需要更新项目中的版本号(例如在 setup.py 文件中),然后重新创建分发包,再使用Twine上传新版本。

上传成功后,你可以立即返回PyPI网站刷新页面,看到新版本(例如 0.0.3)已经显示在项目发布列表中。

发布的意义

上传成功意味着什么?这意味着世界上的任何用户现在都可以通过Python包索引安装你的 demojformat 项目。

用户只需运行 python -m pip install demojformat 命令即可安装。虽然我们本地已经安装,所以不会重复获取,但这演示了从PyPI安装的流程。

这个过程不仅让你学会了如何上传包,还能验证包是否已成功发布到Python包索引,并允许其他人从该索引安装你的工具。


本节课中我们一起学习了将Python项目打包、使用Twine工具上传至PyPI注册中心,以及管理项目版本的完整流程。这是分享你的代码和工具给全球Python社区的关键一步。

171:Python项目打包与分发总结 🚀

在本节课中,我们将要学习如何将一个Python项目打包并分发到全球。我们将从理解打包所需的关键文件开始,逐步学习如何配置项目、构建包,并最终将其上传到PyPI,以便其他用户能够轻松安装。


项目打包基础 📦

上一节我们介绍了数据工程中Python的基本应用,本节中我们来看看如何将你的代码打包成一个可分享的库。

打包一个Python项目涉及几个核心文件。这些文件告诉打包工具如何组织你的代码、管理依赖以及包含哪些资源。

以下是打包时你可能会看到并需要配置的重要文件:

  • setup.py:这是打包过程的中心配置文件。它使用setuptools库来定义项目的元数据(如名称、版本、作者)和依赖项。
  • setup.cfg:一个配置文件,可以用来声明setup.py中的一些设置,使配置更清晰、更静态。
  • pyproject.toml:一个较新的标准文件,用于声明项目构建系统的要求和配置,可以被pip和现代构建工具识别。
  • MANIFEST.in:这个文件用于指定除了Python源代码文件外,哪些额外的文件(如数据文件、文档)应该被包含在分发包中,或者哪些文件应该被排除。

配置与构建 🔧

理解了核心文件后,接下来我们需要配置它们并实际构建出可分发的包。

我们使用setuptools库中的函数和setup()函数来配置项目。MANIFEST.in文件则用于精确控制包含或排除某些特定路径的文件。

以下是构建和测试包的基本步骤:

  1. 在项目根目录下,运行 python setup.py sdist bdist_wheel 命令。这会生成源代码包(sdist)和预构建的wheel包(bdist_wheel)。
  2. 为了隔离环境,建议使用虚拟环境。你可以使用 python -m venv venv 创建,然后激活它来安装和测试你的包。
  3. 在虚拟环境中,使用 pip install . 安装你刚刚构建的本地包,以验证其能否正常工作。

分发与发布 🌍

项目打包并测试无误后,就可以将其发布到公共仓库,供全世界的用户使用了。

我们将使用Twine工具来上传和注册你的项目到PyPI(Python包索引)。这允许你注册一个新版本,其他用户就可以从任何地方通过pip install your-package-name来安装它。

通过利用这些分发渠道,你可以有效地分享你的Python项目。这为你如何获取任何项目、打包它并分发它奠定了良好的基础。


本节课中我们一起学习了Python项目打包与分发的完整流程。我们从认识setup.pyMANIFEST.in等关键文件开始,学习了如何配置和构建分发包,最后掌握了使用Twine将包发布到PyPI的方法。现在,你应该具备了将你的代码项目打包并分享给全球社区的良好基础。

172:命令行工具持续集成导论

在本节课中,我们将学习持续集成的基本概念,特别是如何将其应用于命令行工具的开发。持续集成是构建健壮软件的关键环节,它能帮助你更轻松地维护项目,并在发布或修改应用时增强你的信心。

上一节我们介绍了命令行工具开发的基础,本节中我们来看看如何通过持续集成来提升其质量和可靠性。

什么是持续集成?

持续集成是一种软件开发实践,要求开发人员频繁地将代码变更合并到共享的主干分支中。每次合并都会触发一个自动化的构建和测试流程,以便尽早发现集成错误。

对于命令行工具项目而言,持续集成意味着每次你推送代码到版本库时,都会自动运行一系列检查,确保你的工具依然按预期工作。

持续集成的核心优势

以下是采用持续集成的主要好处:

  1. 及早发现问题:自动化测试能在代码合并后立即运行,快速定位错误。
  2. 简化集成过程:频繁的集成避免了在项目后期处理大量冲突。
  3. 提升发布信心:通过了一系列自动化测试的代码,让你对发布新版本更有把握。
  4. 改善代码质量:强制运行测试和代码风格检查,有助于维持代码库的整洁。

如何为命令行工具设置持续集成

为命令行工具配置持续集成通常涉及以下几个关键步骤。以下是具体操作流程:

  1. 编写自动化测试:为你的工具功能创建测试脚本。例如,一个简单的Python测试可能如下所示:

    import subprocess
    def test_tool_output():
        result = subprocess.run([‘my_tool’, ‘--help’], capture_output=True, text=True)
        assert ‘usage’ in result.stdout.lower()
    
  2. 选择CI/CD平台:选择一个持续集成服务,如GitHub Actions、GitLab CI或Jenkins。

  3. 创建配置文件:在项目根目录创建CI配置文件(如 .github/workflows/ci.yml),定义构建和测试步骤。

  4. 定义工作流程:在配置文件中,指定触发条件(如 pushpull_request)以及要执行的作业,例如安装依赖和运行测试。

  5. 集成代码质量工具:在CI流程中加入代码风格检查(如 flake8)、类型检查(如 mypy)或安全扫描。

一个简单的CI工作流示例

以下是一个使用GitHub Actions的简化配置示例,它展示了如何为Python命令行工具设置基本的CI流程。

name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: ‘3.9’
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run tests
        run: pytest

总结

本节课中我们一起学习了持续集成在命令行工具开发中的重要性。我们了解到,通过自动化测试和构建流程,持续集成可以帮助我们及早发现问题、简化集成工作并最终交付更可靠的软件。将这套实践应用到你的项目中,是迈向专业软件开发的重要一步。

173:Python代码检查工具入门 🛠️

在本节课中,我们将要学习代码检查工具(Linter)的基本概念及其在Python开发中的应用。代码检查工具能自动分析代码,发现潜在的错误、未使用的变量或不符合编码规范的问题,帮助开发者编写更清晰、更健壮的代码。

什么是代码检查工具?

代码检查工具(Linter)是一种静态代码分析工具。如果你不了解它,它将会彻底改变你的开发方式。Linter适用于多种编程语言和不同的编码场景,在专业的软件开发中尤其重要。本节我们专注于在Python项目中使用Linter。

在命令行中使用代码检查工具

上一节我们介绍了代码检查工具的概念,本节中我们来看看如何在实践中使用它。首先,我们将通过命令行工具来体验其功能。

我使用的是VS Code编辑器,但让我们暂时忘记具体的编辑器。我将打开终端。

在终端中,你首先会注意到我正在使用一个已激活的虚拟环境。我个人喜欢使用的Linter工具叫做Flake8。接下来我将安装它,并解释为什么它很重要。

我将执行 pip install flake8 命令来安装Flake8。

现在,在这个项目目录中,我有 main.pyreformat.py 和一些测试文件。如果我在没有任何参数的情况下运行 flake8 命令,你会看到一些警告信息。

该工具会检查每一个Python文件,并找出我编码时可能遗漏的问题。例如,在 main.py 中,它提示“F401: ‘os’ imported but unused”(导入了‘os’模块但未使用)。类似地,它还指出了其他一些导入但未使用的模块,这些都需要清理。

默认情况下,VS Code已经安装了一个Linter。稍后我会展示它在文本编辑器中的样子。但了解这是一个可以在终端运行的工具很有用。通常,文本编辑器中的这些Linter工具都依赖于你可以安装的命令行工具。

在编辑器中集成代码检查

现在你已经看到了命令行的输出,让我们关闭终端,看看它在编辑器中的表现。首先,我们检查“os imported but unused”这个警告。

如果我转到第3行,你会看到os的导入语句颜色有点暗淡,而re的导入看起来正常。这表示os的导入有问题。当我将鼠标悬停在上面时,会看到提示信息。

点击出现的“灯泡”图标,它会建议“Remove unused import”(移除未使用的导入)。我们可以开始清理这些未使用的导入,对re模块也进行同样的操作。

Linter的功能不仅限于检测未使用的导入,它还能捕获其他更严重、你可能遗漏的问题。

例如,在这个prompt_bool函数中,有一行代码下面有黄色的波浪线。如果你花点时间思考为什么这里会被高亮标记,原因是:函数参数中有一个response_input,它在第37行被使用了。然而,变量question并没有在任何地方定义。

如果我悬停查看,它会提示“question is not defined”(question未定义)。这是编辑器背后的Linter(在这里是VS Code使用的Pylance)发出的警告。我们可以尝试点击“快速修复”,但在这个案例中,它提供的可能不是最佳方案。

正确的解决方案是去修改代码,确保这里使用的是同一个变量response_input,而不是question。这样,函数内部就统一使用了相同的变量。

代码检查工具的配置与选择

如果你在文本编辑器(如本例的VS Code)中安装了Python扩展,Linter功能就会自动工作。当然,你也可以使用像flake8这样的工具,或者启用任何其他你喜欢的Linter。

不同的Linter工具会产生不同类型的警告、错误,其严格程度也可能不完全符合你的偏好。例如,对于某些代码风格问题,我当前的编辑器(Pylance)可能不会抱怨,但flake8或其他遵循PEP 8规范的Linter很可能会提出警告。

总结

本节课中我们一起学习了代码检查工具(Linter)。我们了解了它的基本用途,并实践了如何在命令行使用flake8工具,以及如何在VS Code编辑器中集成和使用Linter功能。Linter非常有用,它能帮助我们保持代码整洁,并让我们意识到那些可能被忽略的问题。掌握使用Linter是迈向编写高质量、可维护代码的重要一步。

174:使用GitHub Actions自动化测试 🚀

在本节课中,我们将学习如何为托管在GitHub上的Python项目设置自动化测试流程。通过GitHub Actions,我们可以确保每次代码变更(如推送或合并请求)时,都能自动运行测试,从而保障代码质量。


上一节我们介绍了如何将项目推送到GitHub。本节中,我们来看看如何利用GitHub Actions为项目配置自动化测试。

我的代码仓库名为 Python CI tools course,其中包含了所有示例,包括我们已有的J格式命令行工具。该项目已配置 setup.py 文件并声明了依赖项,我们也知道如何手动运行测试。但为了提升协作效率,我们需要将测试自动化。例如,当合作者提交一个合并请求时,自动运行测试,而不是手动操作。

虽然可以手动添加自动化流程,但GitHub提供了更简便的方式。以下是具体步骤。

以下是配置GitHub Actions工作流的核心步骤:

  1. 进入Actions菜单:在GitHub仓库页面上方,点击 “Actions” 选项卡。
  2. 选择工作流模板:点击“新建工作流”后,搜索“Python”。选择名为“Python package”的模板,然后点击“Configure”。
  3. 理解预置配置:模板会预生成一个配置文件,位于 .github/workflows/ 目录下,通常命名为 python-package.yml。该配置定义了工作流将执行的操作。
  4. 自定义配置:预置配置会安装依赖并运行 pytest,同时会在多个Python版本(如3.8, 3.9, 3.10)下进行测试。你需要根据项目实际情况调整。例如,如果项目没有 requirements.txt 文件,而是通过 setup.py 管理依赖,则需要修改安装步骤。
  5. 提交配置文件:完成配置后,通过GitHub网页界面提交该文件到仓库。提交信息可以是“Create automation with GitHub actions”。
  6. 触发与监控:提交后,工作流会自动触发。你可以在“Actions”选项卡中查看运行状态和详细日志。

现在,让我们快速解析一下这个预置配置文件的核心部分。它主要完成以下任务:

  • 设置环境:配置运行器(容器)并指定要测试的Python版本。
  • 安装依赖:默认会安装最新版的 pip,以及 flake8(代码风格检查工具)和 pytest(测试运行器)。
  • 运行测试:执行 pytest 命令来运行项目中的测试。

对于我们的项目,由于依赖是通过 setup.py 管理的,我们需要将默认的 pip install -r requirements.txt 步骤,修改为通过开发模式安装当前包:

- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install flake8 pytest
    pip install -e .  # 这行替代了安装requirements.txt

此工作流配置了触发条件:当有代码推送到 main 分支,或有指向 main 分支的合并请求时,就会自动运行。提交配置文件后,你可以在“Actions”页面看到一个新的任务开始执行。点击进入可以查看每个Python版本下的测试详情,例如检查 pytest 的运行日志,确认它是否正确地找到了你的测试文件(如 tests/test_*.py)。


本节课中,我们一起学习了如何为GitHub上的Python项目配置自动化测试。通过创建GitHub Actions工作流,我们实现了在代码推送或合并请求时,自动在多个Python版本环境下运行测试,这极大地提高了代码的可靠性和协作开发的效率。

175:Python项目自动化发布 🚀

在本节课中,我们将学习如何为Python项目设置自动化发布流程。我们将使用GitHub Actions,在项目创建新版本(Release)时,自动将包构建并发布到Python包索引(PyPI)。整个过程涉及创建PyPI API令牌、在GitHub仓库中配置密钥以及设置自动化工作流。


准备工作

上一节我们介绍了项目的基本结构。本节中,我们来看看如何为自动化发布做准备。首先,你需要一个PyPI账户。

以下是必要的准备工作步骤:

  1. 访问PyPI网站:确保你已登录PyPI(pypi.org)。如果还没有账户,请先注册。
  2. 创建API令牌:在PyPI账户设置中,找到“API令牌”部分。创建一个新令牌。
    • 为令牌起一个有意义的名字,例如“GitHub项目自动化”。
    • 选择令牌的作用域。如果是首次发布该项目,通常需要选择“整个账户”作用域。如果项目已发布过,则可以选择仅针对该特定项目的作用域。
  3. 复制令牌:创建成功后,系统会生成一个令牌字符串。请立即复制并妥善保存,因为它只会显示一次。

在GitHub仓库中配置密钥

现在,我们需要让GitHub Actions能够安全地使用这个PyPI令牌。

以下是配置GitHub仓库密钥的步骤:

  1. 进入你的GitHub项目仓库。
  2. 点击顶部的“Settings”(设置)选项卡。
  3. 在左侧边栏中,找到“Secrets and variables”(密钥与变量)下的“Actions”(操作)。
  4. 点击“New repository secret”(新建仓库密钥)。
  5. 在“Name”(名称)字段中输入 PYPI_API_TOKEN
  6. 在“Value”(值)字段中,粘贴你从PyPI复制的API令牌。
  7. 点击“Add secret”(添加密钥)完成设置。

这样,我们就创建了一个名为 PYPI_API_TOKEN 的密钥,后续的GitHub Actions工作流可以安全地引用它。

创建GitHub Actions发布工作流

接下来,我们将在仓库中创建一个自动化工作流。这个工作流会在你创建新的GitHub Release时自动触发。

以下是创建发布工作流的步骤:

  1. 在项目仓库中,点击顶部的“Actions”(操作)选项卡。
  2. 点击“New workflow”(新建工作流)。
  3. 在搜索框中搜索“Python Package”,选择名为“Publish Python Package”的官方工作流。
  4. 点击“Configure”(配置)。这个工作流模板已经预配置了构建和发布Python包到PyPI所需的主要步骤。
  5. 关键配置点:工作流文件(通常是 .github/workflows/python-publish.yml)中已经包含了对 PYPI_API_TOKEN 密钥的引用。这正是我们上一步设置的密钥名称,因此无需修改。
  6. 点击“Start commit”(开始提交),填写提交信息(例如“添加PyPI发布工作流”),然后提交到仓库的主分支。

提交后,这个工作流文件就会保存在你的仓库中。但请注意,这个工作流被配置为仅在“发布”(Release)事件时触发,目前还不会运行。

创建GitHub Release以触发发布

为了触发自动化发布流程,我们需要在GitHub上创建一个正式的发布版本。

以下是创建GitHub Release的步骤:

  1. 在项目仓库的主页,点击右侧“Releases”(发布)区域附近的“Create a new release”(创建新发布)按钮。
  2. 在“Tag version”(标签版本)字段中,输入你的版本号,例如 v0.0.1。这将在仓库中创建一个新的Git标签。
  3. 为发布填写一个标题和描述,例如“demojformat项目的初始版本”。
  4. 点击“Publish release”(发布版本)按钮。

验证自动化发布流程

创建Release后,GitHub Actions会自动开始运行发布工作流。

以下是验证流程的步骤:

  1. 返回仓库的“Actions”(操作)选项卡。
  2. 你应该能看到一个名为“Publish Python Package”的工作流正在运行或已经完成。
  3. 点击该运行记录,可以查看详细的构建日志。工作流会依次执行安装依赖、构建包(生成.whl.tar.gz文件)、使用PYPI_API_TOKEN向PyPI上传包等步骤。
  4. 如果所有步骤都显示绿色对勾,则表示发布成功。

发布成功后,你可以访问 pypi.org,搜索你的包名(例如 demojformat),应该能看到刚刚发布的新版本及其详细信息。


本节课中我们一起学习了如何为Python项目搭建完整的自动化发布管道。我们首先在PyPI创建了API令牌,然后在GitHub仓库中将其设置为安全密钥。接着,我们利用GitHub Actions的预置模板创建了发布工作流。最后,通过创建一个GitHub Release触发了整个自动化流程,成功将Python包发布到了PyPI。这套流程极大地简化了软件分发的步骤,确保了发布的一致性和可靠性。

176:命令行工具持续集成要点回顾 🛠️

在本节课中,我们将学习如何自动化持续集成流程,涵盖从发布到链接、再到测试不同Python版本的各个方面。这些方法将帮助你构建一个非常健壮的命令行工具。

概述

上一节我们介绍了持续集成的基本概念,本节中我们来看看实现自动化的具体方式。自动化是软件开发实践的基石,它能让你在进行修改或发布新版本时感到自信和安全。

自动化持续集成的方法

以下是几种可以自动化持续集成流程的关键方法。

  • 自动化发布流程:将软件发布到包管理器(如PyPI)或代码仓库(如GitHub)的过程可以实现自动化,减少人为错误。
  • 自动化链接管理:确保项目依赖(如通过requirements.txtpyproject.toml定义)的版本被正确管理和更新。
  • 自动化多版本测试:在多个Python版本(例如3.8, 3.9, 3.10)上自动运行测试套件,以保证代码的广泛兼容性。

核心实践要点

这些自动化实践不仅适用于命令行工具项目,也适用于任何Python软件项目,乃至任何类型的软件项目。

  • 以自动化为开发基础:将自动化脚本和流程(如使用GitHub Actions、GitLab CI/CD或Jenkins)集成到开发工作流中。
  • 提升变更信心:当代码库的每次提交都能触发自动化的构建、测试和检查时,开发者可以更自信地进行代码修改。
  • 保障发布安全:自动化的发布流水线能确保每次版本发布都经过一致且可靠的步骤,降低了发布风险。

总结

本节课中我们一起学习了自动化持续集成的核心要点。通过实现发布、链接管理和多环境测试的自动化,你可以为任何软件项目建立一个健壮、可靠且高效的开发基础,从而在项目的整个生命周期中保持代码质量和开发效率。

177:使用Rust构建命令行工具

在本节课程中,我们将学习如何使用Rust语言构建一个命令行工具。我们将从一个简单的示例场景出发,逐步探索Rust开发命令行程序的基础知识、依赖管理、生态系统,并与Python进行简要对比。

课程概述 🎯

我们将从最基础的概念开始,理解在Rust中开发命令行工具意味着什么,以及其基本形态。随后,我们将循序渐进,从最简单的实现方法开始,逐步过渡到使用成熟的框架。在此过程中,我们会学习如何安装依赖、了解Rust的生态系统,并分析Rust相较于Python的一些显著优势和不同之处。最终,你将构建出一个高性能的命令行工具,它能与系统工具交互、解析输出并以不同方式呈现结果。


从基础开始 🛠️

上一节我们介绍了使用其他语言构建工具的思路,本节中我们来看看如何在Rust中实现。

首先,我们需要理解在Rust中开发命令行工具的基本概念和初始步骤。Rust提供了强大的类型系统和内存安全保证,这使得构建可靠且高效的系统工具成为可能。


循序渐进:从简单到框架 🧗

我们将采用由浅入深的方法来学习。

第一步是创建一个最简单的Rust命令行程序。 以下是创建一个基本“Hello World”命令行程序的步骤:

  1. 使用 cargo new 命令创建一个新的Rust项目。
  2. main.rs 文件中编写处理命令行参数的简单逻辑。
  3. 使用 cargo build 编译项目。
  4. 运行生成的可执行文件。

接下来,我们将引入依赖和框架以增强功能。 Rust拥有丰富的生态系统,crates.io 是其官方的包仓库。我们将学习如何使用 Cargo.toml 文件来管理项目依赖。

例如,添加一个流行的命令行解析库 clap 作为依赖:

[dependencies]
clap = { version = "4.0", features = ["derive"] }

最后,我们将构建一个完整的实用工具。 这个工具将模拟一个真实的系统工程或DevOps场景:与一个系统工具交互,解析该工具的输出,并以格式化方式(如JSON、表格)重新呈现结果。


Rust与Python的对比 🤔

在构建工具的过程中,理解Rust与Python的差异至关重要。Rust在某些方面提供了超越Python的优势,这也是学习Rust的意义所在。

以下是Rust的一些关键优势:

  • 性能:Rust编译为本地机器码,无需解释器或虚拟机,通常比Python执行速度更快,内存效率更高。
  • 内存安全:Rust的所有权系统在编译时保证内存安全和线程安全,避免了空指针、数据竞争等常见问题。
  • 零成本抽象:Rust的高级特性(如泛型、特质)在运行时几乎没有性能开销。
  • 强大的工具链:Cargo作为集成的包管理器和构建工具,极大地简化了依赖管理、编译、测试和文档生成。

总结 📚

本节课中,我们一起学习了使用Rust构建命令行工具的完整路径。我们从基础概念入手,逐步实践了从简单程序到利用框架和生态系统构建复杂工具的过程。我们还探讨了Rust在性能、内存安全和工具链方面相较于Python的优势。最终,我们的目标是能够开发出高性能、可靠且能与系统深度集成的命令行应用程序。

178:命令行开发环境配置 🛠️

在本节课中,我们将学习如何为构建命令行工具配置一个 Rust 开发环境。我们将介绍 Rust 的安装、推荐的开发工具(如 VS Code 扩展),以及如何使用 Cargo 工具链来格式化代码、检查错误和优化代码质量。


概述

与 Python 不同,Rust 需要特定的安装和配置步骤来搭建开发环境。本节将指导你完成这些步骤,并展示一个高效的工作流程。一旦你构建并发布了二进制文件,目标系统就不再需要安装 Rust,这是 Rust 在构建命令行工具时的一个显著优势。

安装 Rust

首先,我们需要安装 Rust。推荐使用 rustup 工具进行安装,而不是通过 Linux 的包管理器。rustup 会处理 Rust 工具链的安装和更新。

以下是安装命令:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

运行此命令后,它会创建几个目录,并将 cargo(Rust 的包管理器和构建工具)的 bin 目录添加到你的系统路径中。你的 Shell 环境也会相应更新,以便访问这些路径。

注意:虽然演示中使用了 macOS,但 rustup 在 Linux 系统上的安装过程类似。请避免使用系统自带的包管理器安装 Rust。

配置开发工具

上一节我们介绍了如何安装 Rust 本身,本节中我们来看看如何配置一个高效的代码编辑环境。

Visual Studio Code 扩展

推荐使用 Visual Studio Code 作为 Rust 的开发环境。你需要安装以下扩展来获得更好的开发体验:

  • Rust Analyzer:这是核心扩展。它提供代码补全、类型提示、悬停文档、查找引用和错误检查等功能,远超基础的代码补全。
  • GitHub Copilot(可选):如果你已拥有许可证,这个 AI 代码助手可以为 Rust 开发提供代码建议,帮助你更快上手,尤其适合从 Python 转向 Rust 的开发者。

安装 Rust Analyzer 后,你会在编辑器底部看到相关状态指示。GitHub Copilot 安装并登录后,其图标也会出现在底部状态栏。

认识项目结构

现在,让我们快速浏览一个典型的 Rust 命令行项目结构,了解我们将要处理的主要组件。

以下是一个由 cargo 生成的典型项目文件列表:

  • Cargo.toml:这是项目的配置文件,类似于 Python 的 setup.pypyproject.toml。它定义了项目的元数据、依赖项等信息。
  • Cargo.lock:此文件由 cargo 自动生成,用于锁定项目依赖的确切版本,确保构建的可重现性。
  • .gitignore:版本控制系统忽略文件,通常也由 cargo 自动生成,会忽略 target/ 等目录。
  • src/ 目录:这是存放 Rust 源代码文件(.rs 后缀)的主要目录。例如,src/main.rs 是二进制程序的入口点。
  • target/ 目录:当你编译或构建项目时,生成的所有文件(包括最终的二进制文件)都会放在这个目录里。
  • examples/ 目录(可选):你可以在这里放置示例代码,用于展示你的命令行工具如何使用。

使用 Cargo 工具链

了解了项目结构后,我们来看看如何使用 cargo 命令来保证代码质量和正确性。cargo 是 Rust 的核心工具,我们主要使用它的三个子命令。

以下是三个关键的 cargo 工作流命令:

  1. cargo fmt:此命令用于格式化代码。无论你的代码格式多么混乱,运行 cargo fmt 后,它都会按照 Rust 社区的通用风格规范重新格式化,确保代码风格一致。

    cargo fmt
    
  2. cargo clippy:这是一个代码 linting 工具。它会分析你的代码,并给出改进建议(例如,更简洁的写法、潜在的陷阱等)。它不报错,但提供优化提示。

    cargo clippy
    
  3. cargo check:此命令会快速检查代码能否通过编译,而无需真正生成可执行文件。它能非常迅速地反馈语法或类型错误,是高效的错误检查工具。

    cargo check
    

提示rustup 在安装 Rust 时,已经包含了 cargo fmtcargo clippy 等工具。此外,之前安装的 Rust Analyzer 扩展会在你编写代码时,在后台实时运行类似 cargo check 的检查,并用红色波浪线直接在编辑器中标出错误,同时提供修复建议。

总结

本节课中我们一起学习了如何配置一个完整的 Rust 命令行开发环境。我们从使用 rustup 安装 Rust 开始,然后配置了 Visual Studio Code 及其关键扩展(Rust Analyzer 和可选的 GitHub Copilot)。接着,我们熟悉了由 cargo 生成的典型项目结构。最后,我们实践了使用 cargo fmtcargo clippycargo check 来格式化、优化和检查代码的核心工作流程。这个环境能帮助你高效、规范地开发 Rust 命令行工具。

强烈建议通过 rustup.rs 网站或上述命令行来安装和更新 Rust。

179:首个命令行工具实战 🛠️

在本节课中,我们将学习如何使用 Rust 构建第一个命令行工具。我们将创建一个 lsblk 命令的替代工具,用于在容器化环境中列出块设备信息。我们将涵盖 Rust 环境的设置、代码结构解析、编译与运行,以及初步的错误处理概念。


环境准备与工具安装

在开始编写代码之前,我们需要确保 Rust 开发环境已正确安装并配置。

首先,我们确认当前的操作系统环境。我们使用的是 Ubuntu 20.04 LTS 版本。

接下来,我们运行 rustup 命令来安装 Rust 工具链。这个过程会下载并安装包括 cargoclippy 在内的必要组件。

安装完成后,我们需要执行 source $HOME/.cargo/env 命令。这确保了在终端中可以直接使用 rustccargo 命令。

我们可以通过以下命令验证安装是否成功:

which rustc
which cargo
rustc --version

如果一切正常,rustc --version 应显示类似 1.68.2 的版本号。至此,Rust 环境已准备就绪。


问题定义与业务需求

上一节我们完成了环境搭建,本节中我们来看看要解决的具体问题。

我们的业务需求是创建一个能替代 lsblk 命令的工具。在标准的容器化环境中,直接运行 lsblk 命令可能会失败,并提示“不是块设备”。这是因为容器环境中的设备并非真实的物理块设备。

为了解决这个问题,我们的策略是使用 lsblk -J 命令。该命令会以 JSON 格式输出设备信息。我们的 Rust 工具将解析这个 JSON 输出,并提取出用户指定的特定设备信息。


代码结构解析

现在,让我们深入查看实现这个工具的 Rust 代码。代码的主要逻辑位于 main.rs 文件中。在 Rust 中,程序默认从 main 函数开始执行。

以下是代码的核心组成部分:

  1. run_command 函数:此函数负责执行外部系统命令。

    • 它接收一个命令字符串。
    • 使用 split_whitespace().collect() 将命令按空格分割,并收集到一个字符串向量 (Vec<String>) 中。这类似于 Python 中的列表。
    • 使用 std::process::Command 来执行命令,并将命令的输出解析为字符串。
    • 如果命令执行失败,它会引发一个错误。
  2. run_lsblk 函数:此函数封装了获取块设备信息的逻辑。

    • 它定义了要执行的命令:lsblk -J
    • 调用 run_command 函数来获取 JSON 格式的输出。
    • 使用 serde_json 库来反序列化 JSON 数据。
    • 遍历 JSON 数据中的块设备列表,寻找与给定设备名(如 sdb)匹配的项。
    • 如果找到匹配项,则返回该设备的信息;否则,目前代码会直接 panic(崩溃)。请注意,在生产代码中应更优雅地处理错误。
  3. main 函数:这是程序的入口点。

    • 它从命令行参数中获取用户输入的设备名称(例如 sdb1)。
    • 调用 run_lsblk 函数来查找设备信息。
    • 将找到的信息打印到控制台。

这个简单的工具尚未使用任何命令行参数解析框架,因此缺少帮助菜单和更健壮的错误处理,但这对于初步理解已足够。


编译与运行工作流

理解了代码结构后,我们来看看如何构建和运行这个工具。Rust 使用 cargo 作为包管理和构建工具。

以下是开发过程中常用的命令:

  • cargo check:快速检查代码语法和类型是否正确,而不进行编译。这是一个快速的验证步骤。
  • cargo build:编译项目并生成可执行文件。可执行文件默认位于 target/debug/ 目录下,并以项目名(在 Cargo.toml 中定义,例如 block-rs)命名。
  • cargo run <arguments>:这是一个组合命令,它会先编译项目(如果代码有变动),然后立即运行生成的可执行文件,并传递指定的参数。这是最常用的开发命令。

例如,要查找设备 sdb1 的信息,我们可以运行:

cargo run sdb1

如果代码没有错误,你将看到工具输出该设备的 JSON 信息。


当前局限与改进方向

在本节中,我们将审视当前工具的不足之处,并探讨未来的改进空间。

目前这个工具是一个功能性的原型,但存在一些明显的限制:

  • 错误处理不完善:当设备未找到或 lsblk 命令执行失败时,程序会直接 panic,这对用户不友好。
  • 缺少命令行功能:没有帮助信息 (--help),也没有对输入参数进行验证或灵活解析。
  • 代码可维护性:所有逻辑都集中在 main.rs 中,随着功能增加会变得难以管理。

在后续的课程中,我们将学习如何使用像 clap 这样的命令行解析库来增强工具,并引入更系统的错误处理机制(例如使用 Result 类型和 ? 操作符),以及如何更好地组织代码模块。


总结

本节课中我们一起学习了使用 Rust 构建首个命令行工具的完整流程。

我们从设置 Rust 开发环境开始,明确了要解决的业务问题——创建一个容器环境下可用的 lsblk 替代工具。我们逐行分析了工具的核心代码,包括命令执行、JSON 解析和设备查找逻辑。接着,我们实践了使用 cargo buildcargo run 来编译和运行工具。最后,我们讨论了当前版本的局限性,为后续的优化和改进指明了方向。

通过这个实战练习,你已经掌握了 Rust 命令行工具开发的基础工作流,为学习更复杂的项目打下了坚实的基础。

180:用户输入参数与选项处理 🛠️

在本节课中,我们将学习如何使用 Rust 语言中的 clap 框架,为命令行工具添加参数解析、标志和帮助菜单生成功能,从而构建一个更健壮的工具。

概述

上一节我们创建了一个简单的命令行工具。本节中,我们将在此基础上进行升级,使其能够处理用户输入的参数和选项。我们将从一个空项目开始,使用 cargo 初始化,并引入 clap 框架来管理命令行界面。

从零开始创建项目

首先,我们需要创建一个新的 Rust 项目。以下是具体步骤。

以下是使用 cargo 初始化项目的命令:

cargo init --name block_rs .

此命令会在当前目录创建一个新的二进制应用包。它会自动生成 src 目录、Cargo.toml 文件并更新 .gitignore 文件。

查看生成的 Cargo.toml 文件,其初始内容如下:

[package]
name = "block_rs"
version = "0.1.0"
edition = "2021"

[dependencies]

添加项目依赖

接下来,我们需要为项目添加必要的依赖库。我们将使用 clap 框架来处理命令行参数,并保留之前使用的 serde_json

以下是更新后的 Cargo.toml 文件依赖部分:

[dependencies]
clap = "2.33.3"
serde_json = "1.0"

clap 是我们新添加的框架,它将为我们提供构建命令行工具所需的所有功能。

构建主函数

项目初始化后,src/main.rs 文件会包含一个基础的 main 函数。我们需要将之前工具的逻辑复制到这个新项目中。

以下是整合后 main.rs 文件的初始代码结构:

fn main() {
    // 旧的工具逻辑将放在这里
}

复制旧代码后,我们可以在终端中运行 cargo build 来确保一切都能正确编译和构建。

引入 Clap 框架

现在,我们将开始修改 main 函数,使用 clap 来解析命令行参数,替换之前简单获取最后一个参数的方法。

以下是使用 clap 定义应用基本信息的方法:

use clap::App;

let matches = App::new("block_rs")
    .version("0.0.1")
    .author("Alfredo Deza")
    .about("Lists block devices in Rust")
    .get_matches();

这段代码创建了一个新的命令行应用,并设置了版本、作者和描述信息。运行 cargo run -- --help 现在可以显示一个基本的帮助菜单。

添加命令行参数

我们的工具需要一个位置参数来指定设备名。使用 clap 可以方便地定义这个参数。

以下是定义必需的位置参数 device 的代码:

let matches = App::new("block_rs")
    .version("0.0.1")
    .author("Alfredo Deza")
    .about("Lists block devices in Rust")
    .arg(
        Arg::with_name("device")
            .index(1)
            .required(true)
            .help("The block device to list (e.g., sda)"),
    )
    .get_matches();

参数 device 被定义为第一个位置参数(index(1)),并且是必需的(required(true))。现在运行帮助命令,可以看到 device 参数已出现在说明中。

使用解析后的参数

定义了参数之后,我们需要在代码中获取并使用它的值,替换之前硬编码的逻辑。

以下是如何从解析结果中获取 device 参数值的代码:

let device = matches.value_of("device").unwrap();
// 后续使用 device 变量进行操作

现在,运行工具时必须提供设备参数,例如 cargo run -- sdb。如果不提供参数,clap 会自动显示错误信息,提示缺少必需参数。

总结

本节课中我们一起学习了如何为一个 Rust 命令行工具添加专业的参数处理功能。我们从使用 cargo init 创建新项目开始,添加了 clap 依赖库,并利用该框架定义了应用的元信息和一个必需的位置参数。这使得我们的工具更加健壮,具备了自动生成的帮助菜单、版本信息以及基本的错误处理能力。下一步,我们可以在此基础上进一步改进错误处理机制。

181:通过模块与库扩展工具功能

在本节课中,我们将学习如何通过模块化来组织Rust代码。我们将把一个单文件项目拆分为多个模块,以提高代码的可读性和可维护性。我们将从创建一个库文件开始,并学习如何在不同模块间共享函数。

到目前为止,我们的工具使用Rust语言实现了一个lsblk命令的功能。我们尚未使用任何模块,仅使用了通过cargo init命令自动生成的main.rs文件。

将所有代码放在单个文件中是可以的,直到你希望更好地组织代码。这正是我们现在要尝试做的事情。我们将使代码更加模块化,并从Rust应用程序中一个常见的做法开始:向src目录添加更多内容。首先,我们将添加一个lib.rs文件。

通常,你至少会有一个main.rs和一个lib.rs。根据你希望的组织方式,你还可以拥有更多文件,甚至包含更多模块和文件的子目录。我们将探索一下,对于我们当前要实现的目标,模块化的Rust代码会是什么样子。有很多不同的选项,但我们将从最简单的开始,即在此处添加一个lib.rs文件。

创建库模块

首先,我们回到main.rs文件,查看已有的内容。我们有run_command函数、run_lsblk函数以及main函数。我们将保持main函数不变,选择除main函数外的所有内容,并将其剪切。

现在,Rust分析器会报错。这是因为我们无法再访问run_lsblk函数。接下来,我们转到lib.rs文件,将刚才剪切的所有内容粘贴进去,并保存。

我们仍然看到一些下划线警告。这是因为这些函数未被使用。例如,分析器提示“function run_command is never used”。这表明Rust分析器工作正常。

从主模块调用库函数

现在,我们回到main.rs文件,看看有哪些错误。首先,错误提示“cannot find run_lsblk in this scope”。我们知道run_lsblk函数存在,但它位于不同的模块中。那么,如何让它工作呢?

如果你记得我们应用程序的名称,可以打开Cargo.toml文件查看。你会看到我们的包名是block_rs。我们将使用这个包名来调用函数。我们在main.rs中通过block_rs::run_lsblk()来调用它。

保存后,我们仍然看到一些波浪下划线。这是因为run_lsblk函数是私有的。当所有代码都在单个main.rs文件中时,函数默认是私有的。现在我们需要将其公开。

公开库函数

我们转到lib.rs文件,找到run_lsblk函数,并在其前面加上pub关键字,使其变为公有函数。

pub fn run_lsblk() -> Result<(), Box<dyn std::error::Error>> {
    // ... 函数实现
}

完成修改后,回到main.rs文件,之前的错误提示消失了。

测试模块化代码

现在,让我们打开终端,运行cargo run命令来测试我们的修改。

cargo run

程序成功运行,输出了预期的结果。首次编译可能会稍慢,因为我们做了一些更改。再次运行则会很快,因为Rust知道无需重新编译所有内容。

使用use声明提高代码清晰度

目前我们处于一个良好的状态,我们有两个模块。你可以继续模块化,使代码随着项目增长而更加有序。目前这样已经足够,并为我们提供了继续扩展的能力。

这种最小化的模块化方式是一个良好的起点。在后续课程中,我们将看到如何进一步推进。现在,你看到了如何实际使用block_rs这个包名来调用run_lsblk函数。

我们还可以在文件顶部使用use声明来引入run_lsblk函数,这样做可以使代码更清晰,尤其是当你想明确知道从另一个模块使用了哪些内容时。

以下是具体做法:在main.rs文件顶部,添加use block_rs::run_lsblk;。然后,在调用函数时,可以直接使用run_lsblk(),而无需前缀。

use block_rs::run_lsblk;

fn main() {
    // ... 其他代码
    if let Err(e) = run_lsblk() {
        eprintln!("Application error: {}", e);
        std::process::exit(1);
    }
}

我们也可以对clap库做同样的处理,以更清晰地表明我们使用了哪些外部内容。我个人倾向于在文件顶部先声明使用的外部内容,然后是内部内容,这有助于保持组织性。

use clap::{App, Arg};
use block_rs::run_lsblk;

这样,代码顶部更明确地展示了我们使用了哪些内容,而实际的函数调用保持不变。我们的代码因为有了lib.rs模块而变得更加有序。

总结

本节课中,我们一起学习了如何通过模块化来组织Rust代码。我们从创建lib.rs库文件开始,将函数从main.rs移动到库中,并通过pub关键字公开它们。然后,我们学习了如何在主模块中通过包名调用库函数,以及如何使用use声明来提高代码的清晰度和组织性。这种模块化的方法是构建更复杂Rust命令行工具的良好基石。

182:输出管理、日志记录与错误处理 🛠️

在本节课中,我们将学习如何改进命令行工具,使其能够优雅地处理错误,而不是在遇到问题时直接崩溃。我们将重点学习如何管理输出、记录日志以及进行错误处理,使工具更加健壮和用户友好。


到目前为止,我们的命令行工具假设一切都会顺利进行。如果事情不顺利,这实际上会导致很多问题。

我们可以看到这里有几个问题。这里的 expect 会导致程序恐慌(panic),并且执行失败时会显示类似的信息。如果我继续往下看,这个 JSON 序列化在序列化出现问题时也会导致恐慌。

继续往下看。实际上,这里有一个 unwrap。所以,如果输出中不存在 block devices,它也会在这里的 unwrap 处导致恐慌。如果我滚动到这里的最下方,又会遇到另一个恐慌,并显示“device not found”消息。

因此有很多恐慌。正如我提到的,如果你只是想写一些演示代码或示例,恐慌是可以接受的。但对于生产级代码来说就不太合适了。我的意思是,你仍然可以在生产级代码中使用恐慌,当我们遇到完全无法恢复的错误时,我们绝对希望 100% 恐慌并停止一切执行,这没问题,但这并不常见,而且肯定不是我们在这里想做的。我们希望继续执行,我们希望处理这些错误,并提供一些消息输出。

现在,如果我们用一个不存在的设备运行 cargo run,我们会得到很多不一定对用户有用的信息,比如“thread main panicked”、“fail to execute command”、“OS code”等等。这里有很多信息对用户来说不是很有用。

所以,让我们开始让它变得更好一点。我将移除这个无效的命令,然后再次运行 clearrun,因为这里有一个 unwrap

在最后,如果我运行这个,“device not found”也会导致恐慌。所以,这是我们想要改进的两种情况。


修复“device not found”恐慌

让我们开始修复它们。我们要做的第一件事是停止最后那个恐慌消息。我们不想那样做。我认为在这里返回一个空的 JSON 是完全可接受的。

我们将不得不传入或从 third_json 返回一个值。所以,我们将移除这个恐慌,当我们使用 third_json 库时,我们不会返回 null。我们将调用这个宏并返回一个 JSON 中的空对象。

现在,我们这样做,而不是说“device not found”(这可能对也可能不对),我们将对可能使用这个库的任何其他工具保持友好。

我将重置并运行 sdb33。现在我们将得到的输出是一个空对象。没有找到 sdb3。所以这个结果意味着没有找到任何东西。因此没有必要恐慌。这已经友好多了。如果你在一个 CD 系统中使用这个工具,你不需要解析错误,但你想有一些东西来处理。

所以,现在一些依赖这个结果的代码可以说:“好吧,我要查看所有的块设备,但因为返回了一个空对象,或者没有设备,这隐式地表示在运行该命令后没有找到任何东西。”

好的,这是我们修复的一个恐慌。


处理命令执行失败

让我们移除这个 file expect,滚动并处理这个 run_command。错误的命令肯定会导致一些问题。

所以,如果我回去把这个命令改成不存在的命令,然后清除并运行,它会导致我们必须处理的问题。我们不打算在这里改变返回值,它仍然是一个字符串。我们将保持它为字符串。我想做的是,当这个输出在这里失败时,捕获那个失败命令的输出,捕获错误(如果失败),并将其打印到终端,这样我们就可以在这里看到它,但要继续执行,这样我们就不需要处理从 run_commandrun_lsblk 函数再到 main 函数的错误传递,也不需要到处更改这些定义,因为我们不得不返回一个 Result 枚举,其中包含一个值或一个错误。

让我们开始工作。我们将通过创建一个对输出的匹配(match)来做到这一点。我将移除 expect,去掉 expect,这会导致各种问题,因为 expect 不在了。

现在我将说,输出将在那里。然后我将……实际上,让我注释掉这部分代码,把它移开,但我把它留在那里作为参考。这是我编写代码时经常使用的方法。我只是注释掉代码,这样我就可以把它作为参考。

所以,我将对输出进行匹配。这当然是我们在这里可以做的。编译器的这个建议看起来几乎正是我想做的。所以,我将接受这个建议。我会做一些修改。

让我们看看这里。所以我们有 let stdout = String::from_utf8_lossy。这一行没问题。我们将 stdout 转换为字符串是可以的。错误条件……不一定要使用这个。我们要打印,我们想准确打印发生了什么。所以我要说 println!,或者实际上,我们可以说 eprintln!。我要说“有一个错误”,错误到底是什么。所以我要保存这个。最后,我将创建一个空字符串,如果发生错误就返回它。

好的,我们这里有一个错误条件,我们漏掉了一个括号。现在我们可以移除这个注释了。

让我们回顾一下这里的一些更改,看看实际发生了什么变化。现在编辑器没有抱怨。一切看起来都很好。所以我们保留了返回值,它是一个字符串。参数是这些,然后我们说:“嘿,运行这个命令,当你完成后,让我们看看我们得到了什么。如果我们得到了正确的执行,没有错误,那么就进行解析,将其转换为 UTF-8 字符串,然后返回那个字符串,因为这里没有分号,这将被返回。否则,如果是一个错误,捕获实际的错误,将其打印到终端,然后返回一个空字符串。”

让我们再次运行这个,看看 run sdb33 会发生什么。好的,我们取得了一点进展。我们在这里打印出了我们的字符串或错误,比如“No such file or directory (os error 2)”,这没问题。但然后我们又遇到了另一个恐慌。那么,发生了什么?我们在这里遇到了麻烦,因为输出现在是一个空字符串,它不是有效的 JSON。所以当 block_devices……让我们看看,这应该是第 24 行。所以这一行会遇到麻烦,因为没有有效的 JSON。

我们在这里可以做的操作是,做一个 if 条件判断:如果输出是空的,那么我们肯定可以返回一个空的 JSON,然后继续。

现在让我们再次清除并运行 cargo run sdb33。我们越来越接近我期望的结果了。我们打印出了实际的错误。当你这样做时,你实际上可以更进一步,将某些内容发送到标准错误,将某些内容发送到标准输出。这样,当你从像这样的命令行工具捕获输出时,就不会遇到麻烦。


添加上下文信息

最后一件不一定非常棒,但仍然有用的事情是,我不想在这里添加打印语句或 println! 语句,但我还是要添加。我将在这里添加一个“Command failed”,并在这里添加实际的命令。我们实际上可以这样做,并将其包含在 println! 中。我要在那里添加一个分号。现在当我运行它时,我会得到更多关于发生了什么的信息。

好了,它告诉我命令失败了。所以现在我有了更多的上下文信息。这是失败的命令,错误是这个,输出将是一个空对象。


总结

在本节课中,我们一起学习了如何改进命令行工具的错误处理。我们修复了因 unwrapexpect 导致的程序恐慌,改为返回空 JSON 对象或空字符串来优雅地表示“未找到”。我们学习了使用 match 语句来捕获和处理命令执行失败,并将错误信息打印到标准错误流,同时继续程序执行。我们还为错误信息添加上下文(如失败的命令本身),使其对用户和开发者都更有用。这些技术对于构建健壮、用户友好的生产级工具至关重要,避免了因未处理的错误而导致程序意外崩溃。

183:Rust命令行工具性能优化与最佳实践 🚀

在本节课中,我们将探讨使用Rust构建命令行工具时的一些关键模式、最佳实践和推荐做法。我们将对比Rust与Python的差异,并深入了解如何利用Rust生态系统的优势来创建高效、健壮的工具。


Rust与Python的差异

上一节我们介绍了命令行工具的基础概念,本节中我们来看看Rust与Python在构建命令行工具时的根本区别。

Rust是一种静态类型、编译型语言,而Python是一种解释型语言。这种差异带来了不同的开发与分发模式。

  • Rust:代码被编译成独立的二进制可执行文件。
    • 公式/代码cargo build --release 生成一个独立的二进制文件。
  • Python:用户需要安装Python解释器和相关依赖才能运行脚本。
    • 公式/代码python your_script.py 需要系统已安装Python。

Rust的主要优势在于其极致的运行速度便捷的分发方式。生成的二进制文件可以在任何兼容的系统上运行,无需用户安装Rust工具链,这解决了Python在部署时对环境依赖的严格要求。


命令行框架的选择

在Rust生态中,有多种框架可用于构建命令行工具。虽然我们从最简单的直接解析参数开始,但使用成熟的框架能带来显著好处。

以下是Rust中一些流行的命令行框架:

  • clap:功能强大、社区活跃,是本教程推荐的选择。
  • structopt:基于宏的声明式参数解析,现已整合到clap中。
  • quick-cli / quicli:旨在提供更简洁、快速的开发体验。

使用像clap这样的框架,可以自动获得参数解析、输入验证、标志设置、错误报告以及帮助菜单生成等功能,极大地减轻了开发负担。


依赖管理

Rust的依赖管理与Python有显著不同,这通常是Rust的一个优势。

在Rust中,通过Cargo.toml文件声明依赖。这些依赖在编译时会被静态链接到最终的二进制文件中。

公式/代码

[dependencies]
clap = { version = "4.0", features = ["derive"] }
serde_json = "1.0"

这意味着:

  1. 最终用户无需单独安装这些库。
  2. 避免了Python中可能出现的依赖冲突、版本不匹配或跨平台编译问题。
  3. 分发时只有一个二进制文件,干净且简单。

错误处理最佳实践

错误处理是构建健壮命令行工具的关键。Rust提供了强大的ResultOption类型来显式处理错误,这与许多其他语言中抛出异常的方式不同。

核心原则是:避免滥用panic!

  • panic!适用于不可恢复的错误或快速原型开发,但不适合生产环境工具。
  • 应使用Result<T, E>类型来封装可能失败的操作,并通过?操作符或match表达式优雅地处理错误。

这样做的好处是能为用户提供清晰的错误上下文,说明操作失败的原因,而不是让程序突然崩溃。


开发工作流与工具

高效的开发离不开好的工具链。在Rust中,以下工具应集成到你的日常开发流程中:

以下是推荐的Rust开发工具:

  • cargo fmt:自动格式化代码,保持风格一致。
    • 公式/代码cargo fmt
  • cargo clippy:代码静态分析,提供改进建议。
    • 公式/代码cargo clippy
  • cargo check:快速检查代码能否编译,不生成可执行文件。
    • 公式/代码cargo check

此外,为你的代码编辑器(如VS Code)安装rust-analyzer扩展,它能提供实时的语法检查、类型提示和自动补全,显著提升开发效率。


工具的可扩展性

从一个简单的单参数工具开始,到使用框架支持多参数、标志和子命令,这大大增强了工具的灵活性和健壮性。

随着功能增长,良好的代码组织至关重要。你可以将代码拆分为不同的模块。

公式/代码

// 在 src/lib.rs 中定义库功能
pub mod utils;
pub mod processor;

// 在 src/main.rs 中调用
use my_tool::processor::process_data;

这种结构允许你:

  1. 分离核心逻辑与命令行接口。
  2. 提高代码的可测试性和可重用性。
  3. 更轻松地添加新功能。

使用外部库

不要害怕使用外部库来实现复杂功能。Rust的包管理器Cargo使得添加和管理依赖非常简单可靠。

例如,虽然clap内置了对彩色输出的支持,但如果你需要更复杂的终端交互、进度条或特殊格式输出,可以轻松引入像indicatifcoloredtui这样的库。

需要权衡的是,添加的依赖越多,项目的初始编译时间可能会越长。但最终的二进制文件大小通常仍然可控,且由于静态链接,分发和运行时的性能与可靠性是其主要优势。


总结

本节课中我们一起学习了使用Rust构建命令行工具的核心最佳实践。我们了解了Rust编译型语言带来的性能与分发优势,探讨了clap等框架如何简化开发,掌握了通过Result进行健壮错误处理的方法,并熟悉了格式化、检查等必备开发工具。记住,良好的模块化设计和对可靠外部库的利用,是构建功能丰富且易于维护的命令行工具的关键。

184:大O表示法最终挑战详解 📊

在本节课中,我们将通过一个出版公司支付版税的模拟案例,深入理解大O表示法的重要性。我们将看到不同的算法效率如何直接影响公司的盈亏,甚至决定其存亡。


概述:为什么大O表示法至关重要

我们常听到编程新手说大O表示法无关紧要,不理解为何人们如此关注它。本节将通过一个具体的商业模拟来展示,算法效率的差异确实会产生巨大影响。效率低下可能导致公司破产,而高效算法则能确保公司持续盈利。

上一节我们介绍了算法复杂度的基本概念,本节中我们来看看如何用具体的商业场景来理解它。


场景设定:三家出版公司

假设有三家出版公司,它们使用不同的系统来处理作者版税支付。它们的效率分别对应三种经典的大O复杂度。

公司一:O(1) - 恒定时间

这家公司拥有自动化版税系统。无论系统中有100名、1000名还是10000名作者,处理时间都是固定的。这通常通过一个高效的SQL查询或自动化流程实现。

核心特征:处理时间与作者数量无关。
公式表示T(n) = c (c为常数)

公司二:O(n) - 线性时间

这家公司利润为零。问题在于,每当新增一名作者或一部作品,他们就需要雇佣更多员工。尽管收入随着作者和作品增加而增长,但所有利润都支付给了员工工资。

核心特征:处理时间与作者数量成线性正比。
公式表示T(n) = k * n (k为常数)

公司三:O(n²) - 平方时间(指数级)

这家公司的系统效率极低。每增加一名作者或一部作品,所需的工作量呈指数级增长(例如,每名作者需要两名员工处理)。工作量增长过快,导致公司迅速破产。

核心特征:处理时间与作者数量的平方成正比。
公式表示T(n) = k * n²


效率对比可视化

下图展示了三种复杂度随作者数量增长的趋势:

  • O(1):一条水平线,时间恒定。
  • O(n):一条向上倾斜的直线,时间线性增长。
  • O(n²):一条急剧上升的曲线,时间呈指数级爆炸增长。

可以清晰地看到,O(n²) 的效率在数据量增大时会带来灾难性后果。


Python代码模拟

接下来,我们通过Python代码来模拟这三家公司的版税支付过程,直观感受时间消耗的差异。

首先,我们导入必要的库并设置基础参数。

import random
import time
import matplotlib.pyplot as plt

# 假设的作者数量范围
num_authors_list = [1, 5, 10, 50, 100]

以下是模拟三种不同效率系统的核心函数:

1. 模拟 O(1) - 恒定时间系统

无论有多少作者,处理时间固定为1秒。

def pay_royalties_o1(num_authors):
    """O(1) 复杂度:固定时间支付版税"""
    time.sleep(1)  # 模拟固定处理时间,例如一个自动化SQL查询
    return 1

2. 模拟 O(n) - 线性时间系统

处理时间随作者数量线性增加。

def pay_royalties_on(num_authors):
    """O(n) 复杂度:时间随作者数量线性增长"""
    total_time = 0
    for _ in range(num_authors):
        time.sleep(0.1)  # 模拟为每位作者处理工作
        total_time += 0.1
    return total_time

3. 模拟 O(n²) - 平方时间系统

处理时间随作者数量呈平方级增长,效率极低。

def pay_royalties_on2(num_authors):
    """O(n²) 复杂度:时间随作者数量平方增长"""
    total_time = 0
    for i in range(num_authors):
        for j in range(num_authors):  # 嵌套循环是 O(n²) 的典型特征
            time.sleep(0.001)  # 模拟每项微小但重复的工作
            total_time += 0.001
    return total_time

运行模拟并绘制图表

现在,我们运行模拟并收集数据,最后用图表展示结果。

# 存储结果的列表
times_o1 = []
times_on = []
times_on2 = []

# 对不同作者数量进行模拟
for n in num_authors_list:
    times_o1.append(pay_royalties_o1(n))
    times_on.append(pay_royalties_on(n))
    times_on2.append(pay_royalties_on2(n))

# 绘制对比图
plt.figure(figsize=(10, 6))
plt.plot(num_authors_list, times_o1, label='O(1) - 公司一 (自动化)', marker='o')
plt.plot(num_authors_list, times_on, label='O(n) - 公司二 (线性)', marker='s')
plt.plot(num_authors_list, times_on2, label='O(n²) - 公司三 (平方)', marker='^')

plt.xlabel('作者数量')
plt.ylabel('处理时间 (秒)')
plt.title('大O表示法缩放对比:三家出版公司支付版税')
plt.legend()
plt.grid(True)
plt.show()

运行上述代码后,你将得到一张清晰的图表。图表显示:

  • O(1) 线保持水平,代表高效稳定。
  • O(n) 线平稳上升,代表成本可控但随规模增长。
  • O(n²) 线急速攀升,仅需少量作者增长,处理时间就会变得不可接受,这模拟了“末日场景”——低效的系统会迅速拖垮公司。


核心结论与总结

本节课我们一起学习了如何用大O表示法分析实际业务场景中的算法效率。

  1. O(1) 恒定复杂度是理想状态,代表高度自动化的高效系统,是工程设计的追求目标。
  2. O(n) 线性复杂度在数据量不大时可以接受,但必须意识到其成本会随规模线性增长,需要管理预期。
  3. O(n²) 平方复杂度(或更糟的指数复杂度) 是必须避免的。在涉及大规模数据的系统中,这类算法会导致性能灾难,正如模拟中第三家公司迅速破产所揭示的。

因此,认为“数学或指数关系不重要”的想法是危险的。在数据工程和软件开发中,对算法复杂度的理解直接关系到系统的可扩展性、成本控制乃至商业成败。请务必重视大O表示法,它不仅是面试知识点,更是构建健壮、高效系统的基石。

posted @ 2026-03-26 12:26  布客飞龙III  阅读(2)  评论(0)    收藏  举报