杜克大学-MLOps-笔记-全-

杜克大学 MLOps 笔记(全)

1:认识你的课程导师 👨‍🏫

在本节课中,我们将认识本课程的导师,并了解课程的整体目标与内容结构。

欢迎来到这门关于MLOps的Python课程。我们将学习Python中最基础的内容,包括变量、赋值、循环、以及像ifelse这样的逻辑条件。

接着,我们会稍微深入函数,然后是类。之后,我们将进行一些数据探索,使用pandasNumPy进行数据分析。

最后,我们将进入构建命令行工具的环节,并将所有这些Python基础知识整合起来,用于构建强大的命令行工具。

那么,为什么要学习Python,为什么要学习所有这些基础知识呢?因为它是至关重要的一环,能为你提供足够的知识去与API交互,完成各种其他与MLOps相关的任务。

在本课程中,我们将学习创建一些命令行工具,以构建自动化流程。你可以自动化那些你或你团队中其他工程师可能正在手动执行的过程、检查、步骤、流水线、部署,甚至是机器学习模型的打包。掌握Python,你可以自动化其中的许多环节。

因此,本质上,在本课程结束时,你将拥有足够的Python知识,可以开始构建强大的自动化工具,这将更好地为你服务。

很高兴你来到这里。让我们来看看你将如何应用所有这些知识来构建这些强大的自动化工具。到课程结束时,你应该有信心在日常运维工作中应用这些概念。


本节课中我们一起认识了课程导师,并了解了本课程将涵盖从Python基础到构建自动化工具的全路径学习目标。

2:变量、类型与流程控制 🐍

在本节课中,我们将学习Python编程的基础核心概念。我们将从变量赋值和数据类型开始,逐步深入到如何使用条件语句(如ifelifelse)来处理程序逻辑,最后探讨异常处理机制,学习如何捕获和处理程序运行中的错误。

变量赋值与数据类型 📦

上一节我们了解了课程的整体框架,本节中我们来看看编程的基石:变量与类型。变量是存储数据的容器,而类型则定义了数据的种类和可执行的操作。

在Python中,变量赋值非常直接。你只需使用等号(=)即可。Python会自动推断变量的类型,这称为动态类型。

以下是变量赋值的基本语法:

variable_name = value

例如:

name = "Alice"  # 这是一个字符串(str)类型变量
age = 30        # 这是一个整数(int)类型变量
height = 1.75   # 这是一个浮点数(float)类型变量
is_student = True # 这是一个布尔值(bool)类型变量

Python中常见的基本数据类型包括:

  • 整数(int): 如 1, -5, 1000
  • 浮点数(float): 如 3.14, 2.0, -0.001
  • 字符串(str): 如 "hello", 'Python'
  • 布尔值(bool): 只有 TrueFalse 两个值

条件逻辑控制 🚦

掌握了如何存储数据后,我们需要让程序能够根据不同的情况做出决策。这就是条件语句的用武之地。使用 ifelifelse 语句,你可以控制代码的执行路径。

条件语句的基本结构如下:

if condition1:
    # 如果condition1为True,执行这里的代码
elif condition2:
    # 如果condition1为False但condition2为True,执行这里的代码
else:
    # 如果以上条件都为False,执行这里的代码

以下是使用条件语句的一个简单示例:

temperature = 25

if temperature > 30:
    print("天气很热。")
elif temperature > 20:
    print("天气温暖。")
else:
    print("天气凉爽。")

异常处理 🛡️

在程序执行过程中,可能会遇到各种意外错误,例如打开不存在的文件,或者进行无效的数学运算。这些错误在Python中被称为“异常”。如果不处理,程序会崩溃。因此,我们需要学会使用 tryexceptfinally 等语句来优雅地捕获和处理异常。

异常处理的基本结构是:

try:
    # 尝试执行可能会出错的代码
    risky_operation()
except SomeSpecificError:
    # 如果发生了SomeSpecificError类型的错误,执行这里的代码进行处理
    handle_the_error()
finally:
    # 无论是否发生异常,最后都会执行的代码(可选)
    cleanup()

让我们看一个处理除零错误的例子:

try:
    result = 10 / 0
    print(f"结果是:{result}")
except ZeroDivisionError:
    print("错误:不能除以零!")

在本节课中,我们一起学习了Python编程的三个基础模块。我们了解了如何使用变量存储不同类型的数据,如何通过条件语句 if/elif/else 让程序具备基本的逻辑判断能力,以及如何使用 try/except 块来捕获和处理运行时异常,使程序更加健壮。这些是构建更复杂程序不可或缺的基石。

3:03_01_03_变量与赋值 📝

在本节课中,我们将学习 Python 中的变量与赋值操作。变量是存储数据的基本单元,而赋值则是将数据与变量名关联起来的过程。我们将通过简单的示例来理解这些概念,并注意一些常见的注意事项。


概述

在本节中,我们将介绍如何创建变量、为变量赋值,以及如何操作这些变量。我们还会探讨 Python 作为非强类型语言的特点,以及这在实际编程中可能带来的影响。


变量赋值

在 Python 中,变量赋值使用等号 = 完成。等号左边是变量名,右边是要赋的值。例如:

name = "Alre"

这里,我们创建了一个名为 name 的变量,并将其赋值为字符串 "Alre"。Python 不要求预先声明变量类型,因此我们可以直接赋值。


数据类型

Python 支持多种数据类型,包括字符串、整数和浮点数。赋值时无需指定类型,解释器会自动推断。例如:

distance = 10.5  # 浮点数
height = 10000   # 整数

需要注意的是,虽然 Python 不强制类型,但赋值时仍需确保值的类型符合后续操作的要求。例如,将字符串赋值给本应进行数值运算的变量可能导致错误。


变量重赋值

变量可以被重新赋值,新值会覆盖旧值。例如:

name = "Alre"
name = "James"

执行上述代码后,变量 name 的值将从 "Alre" 变为 "James"


使用 print 输出变量

print 函数用于输出变量的值。可以通过逗号分隔多个变量,输出时会自动添加空格。例如:

name = "James"
last_name = "Dea"
print(name, last_name)  # 输出: James Dea

如果不使用逗号,而是使用加号连接字符串,则不会自动添加空格:

print(name + last_name)  # 输出: JamesDea

从现有变量创建新变量

我们可以基于现有变量创建新变量。例如,结合 namelast_name 创建 full_name

name = "James"
last_name = "Dea"
full_name = f"{name} {last_name}"
print(full_name)  # 输出: James Dea

这里使用了 f-string 来格式化字符串,我们将在后续课程中详细介绍。


变量复制与引用

在 Python 中,将变量赋值给另一个变量时,实际复制的是值的引用,而不是值本身。这意味着修改原变量不会影响新变量,除非重新赋值。例如:

name = "James"
new_name = name
name = "Alfredo"
print(new_name)  # 输出: James
print(name)      # 输出: Alfredo

这里,new_name 在赋值时保存了 name 的当前值("James"),后续对 name 的修改不会影响 new_name


总结

本节课中,我们一起学习了 Python 中变量的创建、赋值、重赋值以及输出方法。我们还探讨了如何从现有变量创建新变量,并理解了变量复制与引用的行为。掌握这些基础概念是进一步学习 Python 和机器学习运维的重要一步。


4:04_01_04_处理不同数据类型 📚

在本节课中,我们将要学习Python中几种最常见的数据类型。我们将了解字符串、整数、浮点数、布尔值和None类型的基本概念、用法以及它们之间的区别和交互方式。


字符串(Strings)🔤

上一节我们介绍了课程概述,本节中我们来看看第一种数据类型:字符串。字符串本质上就是文本值,在Python中非常常见。

字符串可以用单引号、双引号或三引号来定义。使用三引号的主要好处是,当字符串内部包含单引号或双引号时,可以避免使用转义字符。

# 使用三引号避免转义问题
text = """这是一个'包含'单引号和"双引号"的字符串。"""

以下是字符串的一些基本操作:

  • 字符串拼接:你可以使用加号 + 将多个字符串组合成一个新的字符串。
  • f-string格式化:在字符串前加上前缀 fF,可以在字符串内部直接使用变量,这是一种便捷的字符串格式化方法。
name = "Alfredo"
# 字符串拼接
greeting = "Hello, " + name + "!"
# f-string格式化
greeting_f = f"Hello, {name}!"

整数(Integers)🔢

字符串允许我们处理文本,本节中我们来看看用于处理数字的整数类型。整数(int)代表没有小数部分的整数。

你可以使用内置的 type() 函数来检查任何值或变量的数据类型。

# 检查数据类型
number = 15
print(type(number))  # 输出: <class 'int'>

整数支持标准的数学运算,如加、减、乘、除。但需要注意,某些操作可能导致错误。

以下是使用整数时需要注意的几点:

  • 数学运算:整数可以进行加减乘除等运算。
  • 类型错误:不能直接将整数与字符串相加,否则会引发 TypeError
  • 运算导致类型变化:两个整数相除,即使能整除,结果也会变成浮点数(float)。
# 整数除法得到浮点数
result = 3 / 2  # 结果是 1.5
print(type(result))  # 输出: <class 'float'>

浮点数(Floats)⚖️

我们刚刚看到整数除法可能产生浮点数,本节我们来专门了解浮点数。浮点数(float)代表带有小数点的数字。

任何包含小数点的数字在Python中都会被识别为浮点数。

pi_approx = 3.14159
print(type(pi_approx))  # 输出: <class 'float'>

布尔值(Booleans)✅❌

除了数字和文本,编程中经常需要表示“是”或“否”的逻辑状态,这就是布尔值。布尔类型(bool)只有两个值:TrueFalse(注意首字母大写)。

bool() 函数可以将其它类型的值转换为布尔值。在Python中,通常 0、空字符串、空列表等会被视为 False,而非零值、非空序列等被视为 True

# 布尔值本身
is_active = True
is_complete = False

# 使用bool()函数进行转换
print(bool(1))   # 输出: True
print(bool(0))   # 输出: False
print(bool("Hello"))  # 输出: True
print(bool(""))       # 输出: False

空值(None)🚫

最后,我们来认识一个特殊的类型:NoneNone 在Python中表示“空”或“无”,它是一个独立的数据类型(NoneType)。

它通常用于表示函数没有返回值,或者变量尚未被赋予任何有效值。

# 将None赋值给变量
result = None
print(type(result))  # 输出: <class 'NoneType'>

# 函数默认返回None
def do_nothing():
    pass

print(do_nothing())  # 输出: None

本节课中我们一起学习了Python的五种基本数据类型:用于处理文本的字符串、代表整数的int、代表小数的float、表示真假的布尔值以及表示空值的None。理解这些类型的特性和它们之间的区别,是编写正确、高效Python代码的基础。在后续课程中,我们将学习如何更灵活地运用这些类型进行数据操作和逻辑判断。

5:05_01_05_条件判断与求值 🐍

在本节课中,我们将要学习Python中条件判断的基础知识。条件判断是编程中实现逻辑控制的核心,它允许程序根据不同的情况执行不同的代码块。我们将从最简单的if语句开始,逐步介绍elseelif以及如何组合多个条件。

概述

条件判断是编程的基石。它让程序能够“做决定”,根据数据的状态执行相应的操作。本节将详细介绍Python中ifelseelif关键字的使用,以及如何利用逻辑运算符构建复杂的条件。

if 语句

if是Python中的一个关键字,它允许我们设置一个条件,评估该条件,并根据结果执行相应的操作。其基本逻辑是:如果条件成立(评估为True),则执行紧随其后的代码块。

以下是if语句的基本结构:

if condition:
    # 如果条件为真,则执行这里的代码

例如,在下面的代码中,变量condition被设置为True。当条件满足时,程序会打印出“condition was met”。

condition = True
if condition:
    print("condition was met")

运行这段代码,它会正确执行打印语句。这就是Python中逻辑处理的基础:条件需要评估为True,其后的代码块才会被执行。

条件为 False 的情况

如果条件评估为False,会发生什么?答案是:什么也不会发生。Python看到条件为False,就会跳过if块内的代码,继续执行后面的程序。

condition = False
if condition:
    print("This will not print.")

数据结构的真值评估

之前我们提到过,某些数据结构在“空”的状态下会评估为False,而在包含元素时评估为True。这一点对于Python中几乎所有内置数据结构都适用。

例如,一个空列表评估为False,有元素的列表评估为True

groceries = []  # 空列表,评估为 False
if groceries:
    print("We have some groceries.")

invites = ["Alice", "Bob"]  # 非空列表,评估为 True
if invites:
    print("We have some invites.")

运行上述代码,只会打印“We have some invites.”,因为groceries是空的。

其他数据类型也是如此:

  • 整数0评估为False
  • 任何非零整数(正数或负数)评估为True
properties = 0  # 评估为 False
if properties:
    print("We have properties.")

parents = 2  # 评估为 True
if parents:
    print("We have parents.")

比较运算符

在条件判断中,我们经常需要比较值。Python支持所有常见的比较运算符。

以下是常用的比较运算符示例:

properties = 0
parents = 2

if properties == 0:
    print("I have no properties.")  # 会执行

if parents > 1:
    print("There is more than one parent.")  # 会执行

else 语句

if语句处理了条件为真的情况。那么条件为假时,我们想执行另一段代码该怎么办?这时就需要else语句。else就像一个“否则”,当if条件不满足时,程序会执行else块中的代码。

properties = 0
if properties:
    print("We have properties.")
else:
    print("We don‘t have any properties.")  # 会执行

elif 语句

有时我们不止有两种情况。elif(是“else if”的缩写)允许我们在if条件不满足后,继续检查另一个条件。你可以将其理解为“否则,如果...”。

properties = 0
parents = 2

if properties:
    print("We have properties.")
elif parents:  # 如果 properties 为 False,则检查 parents 是否为 True
    print("We don‘t have properties, but we have parents.")  # 会执行

否定条件 (not)

我们也可以检查条件的反面,即“如果不满足某条件”。这通过not关键字实现,它会将评估结果取反。

name = None  # None 在条件判断中评估为 False
if not name:  # not False 即为 True
    print("Didn‘t get a name.")  # 会执行

not也可以和elif一起使用:

first_name = "Alfredo"
last_name = None

if not first_name:
    print("No first name.")
elif not last_name:  # 检查 last_name 是否为 False
    print("No last name.")  # 会执行

组合条件 (and, or)

我们可以使用and(与)和or(或)来组合多个条件,构建更复杂的逻辑。但要注意,过度组合会让代码难以阅读。

  • and:所有条件都为True时,整个表达式才为True
  • or:至少一个条件为True时,整个表达式就为True

以下是使用and的例子:

has_kids = True
is_married = True

if has_kids and is_married:
    print("This person is married and has kids.")  # 会执行

以下是使用ornot的例子:

likes_books = False
is_logged_in = False

if not likes_books or not is_logged_in:
    print("User not logged in or doesn‘t like books.")  # 会执行
# 注意:`not False` 是 `True`,所以 `not likes_books` 为 True,整个条件为 True。

总结

本节课我们一起学习了Python中条件判断的核心知识。我们从基础的if语句开始,了解了如何根据条件的真值执行代码。接着,我们探讨了当条件不满足时如何使用else,以及如何通过elif处理多个分支条件。我们还学习了如何使用not关键字来检查否定条件,以及如何用andor组合多个条件来构建复杂逻辑。掌握这些基础,你将能够处理Python中大量的基础逻辑控制任务。

6:捕获与处理异常 🐍

在本节课中,我们将要学习Python中异常(Exceptions)的工作原理。异常是Python程序中传播或报告错误的主要机制。我们将了解如何触发异常、如何捕获并处理它们,以及如何利用异常信息进行调试。


异常是什么?🚨

上一节我们介绍了课程概述,本节中我们来看看异常的基本概念。

异常是Python用来“冒泡”或通告程序中错误的方式。在之前的视频中,我们已经见过一两个异常的例子,这里我们将进行更详细的探讨。

例如,运行以下代码会导致一个描述清晰的错误,这实际上就是一个名为 ZeroDivisionError 的异常。

14 / 0

你必须注意所执行的操作,因为它们有时可能会通过引发异常来报告或显示错误。异常通常包含一条消息,例如本例中的“division by zero”,而 ZeroDivisionError 是异常的名称。这非常有用,我们稍后会看到原因,因为你也可以处理这些异常。


如何触发异常?🎯

现在,你也可以自己触发异常。在下面的例子中,我明确地使用 raise 关键字来引发一个内置的 RuntimeError 异常。

raise RuntimeError("This is a problem")

这会正确地引发问题,将其“冒泡”并显示为 RuntimeError: This is a problem。这就是你生成异常的方式。你可能有一个函数或其他代码片段,想要中断并“冒泡”一个问题,那么你就可以使用 raise 关键字来实现。


如何捕获异常?🛡️

现在,你也可以捕获异常。但请注意一个警告:只捕获你打算处理、并且你知道在某些情况下能够处理的异常

对于Python初学者来说,尝试忽略并丢弃异常是很常见的做法。但异常是有用的,因此有充分的理由去处理它们。

以下是捕获异常的基本结构:

try:
    result = 14 / 0
except ZeroDivisionError:
    result = 14 / 2
    print(result)

运行这段代码会得到 7.0。那么这里发生了什么?

try 块会执行下一行的操作:result = 14 / 0try 块会尝试执行这个操作,但正如我们所见,14 / 0 会导致 ZeroDivisionError。接下来,我使用了一个 except 块。try...except 结构就是用来捕获异常的方式:你尝试(try)做某事,如果发生与你捕获的异常类型匹配的异常,你就会进入 except 块。在本例中,我捕获的是 ZeroDivisionError,并执行了备用操作:result = 14 / 2,然后打印结果,最终得到 7.0


避免捕获所有异常 ⚠️

不要被诱惑去捕获所有异常。你可以看到我在这里使用了 except Exception:,这是一种非常通用、非常宽泛的异常捕获方式。这意味着捕获Python中几乎所有的异常。

try:
    result = 14 / 0
    raise RuntimeError("Another problem")
except Exception:
    result = 14 / 2
    print(result)

运行这段代码也会得到 7.0。但问题在于,这里 result 的除零问题和 RuntimeError 是两个可能引发异常的不同源头。由于我使用了 except Exception:,我无法区分异常的具体来源和类型。因此,正确的方法是捕获多个特定的异常。


捕获多个异常 🎭

以下是捕获多个异常的方法。这里我有两个操作:14 / 2 和一个可能导致 TypeError 的字符串加法。

try:
    result = 14 / 2
    result = result + "string"  # 这将导致 TypeError
except (ZeroDivisionError, TypeError):
    print("Caught either a division by zero or a type error!")

我通过将异常类型放在一个元组中来添加它们:(ZeroDivisionError, TypeError)。你可以添加任意多种异常类型。如果我这样做,我就能同时防范除零错误和类型错误这两种问题。如果我运行这段代码,它会完美工作,因为我现在防范了这两个问题。同样,如果我尝试 14 / 0,它也会被捕获。


将异常赋值给变量 📝

最后,你还可以将引发的异常赋值给一个变量,以便进一步检查或记录。

try:
    result = 14 / 0
except ZeroDivisionError as error:
    print(f"Got an error: {error}")

在这里,except ZeroDivisionError as error: 语句将捕获的 ZeroDivisionError 异常对象赋值给变量 error。然后,我可以在 print 语句中使用这个变量来输出异常信息。运行这段代码会打印出 Got an error: division by zero,这正是我们之前看到的 ZeroDivisionError 所携带的消息。


总结 📚

本节课中我们一起学习了Python异常处理的核心知识。我们了解了异常是程序错误传播的机制,学习了如何使用 raise 关键字主动触发异常。更重要的是,我们掌握了使用 try...except 结构来捕获和处理异常,包括如何捕获多个特定异常以及如何将异常对象赋值给变量以获取详细信息。请记住,良好的异常处理实践是只捕获你预期并能处理的异常,这有助于编写更健壮、更易调试的代码。

7:07_01_07_课程回顾-变量与类型 📚

在本节课中,我们将回顾关于变量、数据类型、逻辑运算以及异常处理的核心概念。这些是构建任何Python程序的基础。

变量与赋值 🔢

上一节我们介绍了Python的基本语法,本节中我们来看看如何存储和操作数据。变量是存储数据的容器,你可以通过赋值操作符 = 来为变量赋值。

以下是变量赋值的基本语法:

variable_name = value

例如,x = 5 将整数值 5 存储在名为 x 的变量中。

数据类型 📊

变量可以存储不同类型的数据。Python有几种内置的基本数据类型。

以下是几种常见的数据类型:

  • 整数(int):表示没有小数部分的数字,如 10
  • 浮点数(float):表示带有小数部分的数字,如 3.14
  • 字符串(str):表示文本,需要用引号括起来,如 "Hello"
  • 布尔值(bool):表示逻辑值,只有 TrueFalse

逻辑运算与条件判断 ⚖️

了解了如何存储数据后,我们来看看如何根据数据做出决策。逻辑运算和条件判断允许程序根据特定条件执行不同的代码路径。

以下是逻辑运算的关键部分:

  • 比较运算符:如 ==(等于)、!=(不等于)、>(大于)、<(小于),用于比较两个值。
  • 逻辑运算符:如 andornot,用于组合多个条件。
  • 条件语句:使用 ifelifelse 关键字来构建分支逻辑。

异常处理 🛡️

程序在运行时可能会遇到意外错误,例如尝试打开一个不存在的文件。异常处理机制允许我们优雅地捕获和处理这些错误,防止程序崩溃。

以下是异常处理的核心概念:

  • 异常(Exception):程序执行过程中发生的错误。
  • try-except 块:用于捕获和处理异常的代码结构。将可能出错的代码放在 try 块中,将处理错误的代码放在 except 块中。
  • 基本语法
    try:
        # 可能引发异常的代码
        result = 10 / 0
    except ZeroDivisionError:
        # 处理特定异常
        print("不能除以零")
    


本节课中我们一起学习了Python编程的几个基础但至关重要的部分:如何通过变量存储数据,认识了不同的数据类型,掌握了使用逻辑运算进行条件判断的方法,并了解了如何使用 try-except 结构来捕获和处理程序运行中的异常。这些是编写健壮、可读Python代码的基石。

8:Python数据结构 🐍

在本节课中,我们将要学习Python中一些最流行、最常用的数据结构。理解这些结构是编写、存储和检索数据的基础。我们将了解如何创建它们、它们的行为方式以及彼此之间的差异。掌握这些知识后,你将能够在不同的Python程序中识别这些数据结构,并理解在何种场景下使用它们最为合适。


什么是数据结构?🧱

上一节我们介绍了课程的目标,本节中我们来看看数据结构的核心概念。数据结构是一种组织和存储数据的方式,它决定了数据如何被访问、操作和关联。选择合适的数据结构可以极大地提高程序的效率和可读性。

在Python中,数据结构主要分为两类:可变不可变。可变对象在创建后可以被修改,而不可变对象则不能。


核心数据结构介绍 📚

以下是Python中最常见的几种数据结构,我们将逐一进行探讨。

1. 列表(List)

列表是Python中最基本、最灵活的数据结构之一。它是一个有序、可变的集合,可以包含不同类型的元素。

创建与示例:

my_list = [1, 2, 3, ‘hello‘, True]

特点:

  • 使用方括号 [] 定义。
  • 元素有序,可通过索引(从0开始)访问,例如 my_list[0]
  • 内容可变,可以添加、删除或修改元素。

2. 元组(Tuple)

元组与列表类似,但它是不可变的。一旦创建,其内容就不能被更改。

创建与示例:

my_tuple = (1, 2, ‘world‘)

特点:

  • 使用圆括号 () 定义。
  • 元素有序且可通过索引访问。
  • 不可变性使其更安全,通常用于表示不应被修改的数据集合。

3. 字典(Dictionary)

字典用于存储键值对(key-value pairs)。它通过唯一的键来快速检索对应的值,是无序的(在Python 3.7+中,插入顺序被保留)。

创建与示例:

my_dict = {‘name‘: ‘Alice‘, ‘age‘: 25, ‘city‘: ‘New York‘}

特点:

  • 使用花括号 {} 定义,键值对用冒号 : 分隔。
  • 键必须是不可变类型(如字符串、数字、元组)。
  • 通过键来访问值,例如 my_dict[‘name‘]

4. 集合(Set)

集合是一个无序、不重复元素的集合。它主要用于成员关系测试和消除重复项。

创建与示例:

my_set = {1, 2, 3, 3, 4}  # 结果将是 {1, 2, 3, 4}

特点:

  • 使用花括号 {} 定义(但内部是单个元素,不是键值对)。
  • 元素无序,不支持索引。
  • 自动去除重复元素。
  • 支持数学上的集合运算,如并集、交集。

如何选择合适的数据结构? 🤔

上一节我们介绍了四种核心数据结构,本节中我们来看看如何根据需求做出选择。理解它们的行为差异是关键。

以下是选择数据结构时需要考虑的几个关键因素:

  • 是否需要修改数据? 如果需要频繁增删改,选择列表字典。如果数据是固定的,选择元组
  • 是否需要通过键快速查找值? 如果需要,字典是最佳选择。
  • 是否需要保证元素的唯一性? 如果需要去重或进行集合运算,使用集合
  • 数据是否有顺序要求? 列表元组维护插入顺序,而集合字典(在旧版本中)不保证顺序。


总结 📝

本节课中我们一起学习了Python的四种核心数据结构:列表元组字典集合。我们了解了它们的创建方式、可变性、有序性以及核心用途。列表灵活可变,元组安全不可变,字典便于键值查找,集合擅长处理唯一性。理解这些结构的特性和适用场景,是编写高效、清晰Python代码的重要一步。在后续的编程实践中,你可以根据具体的数据操作需求,自信地选择最合适的数据结构。

9:09_01_03_列表简介 📚

在本节课中,我们将要学习Python中一种最常用的数据结构——列表。我们将了解列表是什么、如何创建列表、如何访问列表中的元素,以及列表的一些基本特性。


列表是Python中最常见的数据结构之一。本质上,列表就是一个项目的容器。如下图所示,列表由方括号定义,其中可以包含各种项目。

列表可以包含任何数据类型。例如,一个列表中可以同时包含整数、字符串、布尔值和浮点数。这意味着对可以放入这种容器中的内容没有限制。

虽然它被称为列表,但我更倾向于将其视为一个可以存放数据并对其进行操作的“容器”。


列表的创建与查看 🛠️

上一节我们介绍了列表的基本概念,本节中我们来看看如何创建和查看一个列表。

你可以通过将列表赋值给一个变量来定义它。我们之前已经学习过如何将项目赋值给变量。

# 定义一个包含字符串的列表
items = [“carrot”, “piece”, “salary”]

要了解列表的更多信息,你可以随时调用内置的 len() 函数。例如,将一个字典传递给 len() 函数,你可以看到其中包含四个项目。


通过索引访问列表元素 🔢

列表中的每个项目都有一个索引(即位置)。在Python中,第一个索引从数字0开始。因此,索引0对应列表中的第一个项目。

当你想要检索列表中的某个项目时,需要使用方括号和索引整数。例如,items[0] 将获取列表中的第一个项目。

# 获取列表中的第一个元素
first_item = items[0]  # 结果为 “carrot”

列表会保持项目的顺序,这一点非常重要。索引是访问元素的关键。如果想获取第一个项目,就使用 items[0]。如果将索引改为1,则会得到第二个项目。


使用负索引 📍

除了正索引,列表还支持负整数索引,这将从列表末尾开始访问项目。

例如,索引 -1 会获取列表中的最后一个项目。如果想获取倒数第二个项目,则使用索引 -2

# 获取列表中的最后一个元素
last_item = items[-1]  # 结果为 “salary”
# 获取倒数第二个元素
second_last_item = items[-2]  # 结果为 “piece”

这种索引表示法起初可能令人困惑,特别是因为它从0开始计数,而有些人可能期望从1开始。但只要记住这个规则,你就能很好地与列表交互,提取所需的项目。



总结 ✨

本节课中我们一起学习了Python列表的基础知识。我们了解到列表是一种有序的、可以容纳各种数据类型的容器。我们学习了如何使用方括号创建列表,如何通过正索引(从0开始)和负索引(从-1开始)来访问列表中的元素。掌握列表的索引是有效使用这种数据结构的关键第一步。

10:Python基础 - 列表的创建与遍历 📝

在本节课中,我们将学习如何在Python中创建列表,以及如何遍历(即循环访问)列表中的每一个元素。列表是Python中最基本且最常用的数据结构之一,掌握其创建和遍历方法是后续学习的基础。


列表的创建

上一节我们介绍了列表的基本概念,本节中我们来看看如何具体创建一个列表。

创建列表主要有两种方式:

  1. 使用方括号 []
  2. 使用内置的 list() 函数。

以下是一个创建列表的示例:

# 使用方括号创建列表
colors = ['red', 'blue', 'brown']
print(colors)

# 使用list()函数创建列表
numbers = list((1, 2, 3, 4, 5))
print(numbers)

执行以上代码,两种方式输出的结果相同。列表中可以包含任意数量、任意类型的元素,例如数字、字符串等。


列表的遍历

创建列表后,最常见的操作就是遍历其中的所有元素。遍历意味着循环访问列表中的每一项。

以下是遍历列表的两种常用方法,它们使用 forin 关键字。

方法一:使用临时变量遍历

在这种方法中,我们为列表中的每个元素指定一个临时变量名。

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

运行这段代码,会依次输出:redbluebrown。列表的顺序总是被保留的,这也是之前我们可以使用索引访问元素的原因。

方法二:直接遍历列表

你也可以不通过临时变量,直接遍历列表本身。

for item in ['red', 'blue', 'brown']:
    print(item)

这种方法的结果与第一种完全相同。通常,为列表和临时变量起有意义的名称(如 colorscolor)会使代码更易读。


列表推导式

接下来,让我们看看列表推导式。这是一种强大但需要谨慎使用的语法,它允许你在一行代码内创建并筛选列表。

列表推导式本质上是一个写在方括号 [] 内的循环。以下是它的基本结构:

[expression for item in iterable if condition]

请看下面的例子:

numbers = [1, 3, 5, 7, 9, 12]
low_numbers = [n for n in numbers if n < 6]
print(low_numbers)  # 输出: [1, 3, 5]

这段代码会遍历 numbers 列表,只将小于6的数字添加到新的 low_numbers 列表中。你也可以修改条件,例如 if n > 6,那么结果就会只包含数字 12

列表推导式非常简洁高效,但如果逻辑过于复杂或代码行过长,会降低代码的可读性。编写易于阅读和维护的代码至关重要。


总结

本节课中我们一起学习了Python列表的核心操作:

  1. 创建列表:可以使用方括号 []list() 函数。
  2. 遍历列表:使用 for item in list: 循环结构,可以方便地访问每个元素。
  3. 列表推导式:这是一种紧凑的创建新列表的方法,语法为 [表达式 for 变量 in 列表 if 条件],但需注意保持代码的简洁与可读性。

掌握这些基础操作,是高效使用Python进行数据处理和机器学习运维的重要一步。

11:Python 字典简介 📚

在本节课中,我们将要学习 Python 中一个非常重要的数据结构——字典。我们将了解字典的基本概念、如何创建字典,以及使用字典时需要注意的一些关键规则。


字典是什么?🗺️

上一节我们介绍了列表,它是一种存储多个项目的容器。本节中我们来看看字典。字典与列表不同,它是一种映射结构。你可以把字典想象成一个电话簿:每个名字(键)都对应一个电话号码(值)。字典就是这种键值对的集合。

创建字典需要使用花括号 {}。键和值之间用冒号 : 分隔。以下是一个简单的例子:

phone_book = {"Alfredo": "555-1234"}

运行这段代码,你会得到一个包含一个键值对的字典。键 "Alfredo" 和值 "555-1234" 都是字符串,但字典也支持其他数据类型。


字典的键与值 🔑

字典的核心是键值对映射。键必须是唯一的,而值可以是任何数据类型,包括其他数据结构。

以下是创建字典时的一些要点:

  • 键值对:每个条目由一个键和一个值组成,格式为 key: value
  • 多个条目:如果需要多个键值对,使用逗号 , 分隔,就像在列表中分隔多个项目一样。
  • 键的唯一性:字典中的键必须是唯一的。如果出现重复的键,后面的键值对会覆盖前面的。

例如,下面的字典中,第二个 "name" 键会覆盖第一个:

my_dict = {"name": "Alfredo", "name": "Another Name"}
# 运行后,my_dict 将是 {"name": "Another Name"}

字典值的多样性 📦

字典的值可以是多种类型,这为存储复杂数据提供了便利。

以下是值类型的一些示例:

  • 布尔值:值可以是 TrueFalse
    {"is_student": True}
    
  • 列表:值可以是一个列表,用于存储多个相关项目。
    {"items": ["lumber", "concrete", "nails"]}
    

重要限制:虽然值可以是列表,但键不能是列表。尝试使用列表作为键会导致 TypeError,提示列表是“不可哈希的”类型。这是因为 Python 字典内部需要快速定位键,而列表是可变的,无法满足这一要求。

# 这将导致错误!
# wrong_dict = {["a", "list"]: "value"}  # TypeError: unhashable type: 'list'

总结 📝

本节课中我们一起学习了 Python 字典的基础知识。我们了解到字典是一种通过键来映射值的数据结构,非常适合用于表示像电话簿这样的关系。我们学会了如何使用花括号和冒号创建字典,明白了键必须是唯一的,并且键不能使用列表等可变类型。字典就像是一个内存中的微型数据库,能够高效地存储和检索数据。

掌握了这些基础后,你就可以开始在你的 Python 程序中使用字典来组织和管理更复杂的数据了。

12:创建与遍历字典 📚

在本节课中,我们将要学习 Python 中字典的创建与遍历。字典是一种非常实用的数据结构,它允许我们以键值对的形式存储和访问数据。我们将探讨创建字典的多种方法,并详细讲解如何遍历字典以获取其中的键、值或两者。


创建字典的多种方式 🔧

上一节我们介绍了字典的基本概念,本节中我们来看看创建字典的几种不同方法。

首先,我们可以使用花括号 {} 直接创建字典。

contact_information = {'name': 'Alfredo', 'last_name': 'Deza'}

其次,我们可以使用内置的 dict() 函数来创建字典。

contact_information = dict()

此外,还有两种更灵活的方式。以下是其他创建字典的方法:

  • 传递键值对列表:你可以向 dict() 函数传递一个包含元组的列表,每个元组是一个键值对。
    contact_information = dict([('name', 'Alfredo'), ('last_name', 'Deza')])
    
  • 使用关键字参数:你可以直接使用关键字参数来创建字典。
    contact_information = dict(name='Alfredo', last_name='Deza')
    

遍历字典 🔄

现在我们已经知道如何创建字典,接下来让我们看看如何遍历字典中的数据。我们将使用一个包含更多信息的字典作为示例。

contact_information = {'name': 'Alfredo', 'last_name': 'Deza', 'age': 49, 'height': 190}

遍历字典的键

如果你只想获取字典中的所有键,可以使用 .keys() 方法。

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

实际上,Python 默认就会遍历字典的键。因此,以下代码会产生相同的结果:

for key in contact_information:
    print(key)

遍历字典的值

如果你想获取字典中的所有值,可以使用 .values() 方法。

for value in contact_information.values():
    print(value)

同时遍历键和值

如果你想同时获取字典的键和值,需要使用 .items() 方法,并结合 Python 的解包功能。

for key, value in contact_information.items():
    print(f"{key}: {value}")

.items() 方法返回一个类似列表的对象,其中每个元素都是一个包含键和值的元组。在循环中,我们使用两个变量(keyvalue)来接收每个元组中的两个值。


总结 📝

本节课中我们一起学习了 Python 字典的创建与遍历。我们掌握了四种创建字典的方法:使用花括号、dict() 函数、传递键值对列表以及使用关键字参数。更重要的是,我们学会了如何通过 .keys().values().items() 方法来遍历字典,从而灵活地访问其中的键、值或两者。这些操作是处理字典数据的基础,在未来的数据管理和机器学习任务中会经常用到。

13:其他数据结构-元组与集合 📚

在本节课中,我们将要学习Python中两种重要的数据结构:元组(Tuple)和集合(Set)。这两种结构虽然不如列表和字典常用,但在特定场景下非常有用。我们将分别介绍它们的特点、创建方式、基本操作以及它们与列表和字典的主要区别。


元组:不可变的序列 📦

上一节我们介绍了列表,它是一种可变的有序集合。本节中我们来看看元组。元组与列表非常相似,但关键区别在于元组是不可变的,你可以将其视为一个“只读”列表。

创建一个元组使用圆括号 (),元素之间用逗号分隔。

ro_items = ('first', 'second', 'third')

访问元组元素的方式与列表完全相同,使用索引。

print(ro_items[0])  # 输出:first
print(ro_items[-1]) # 输出:third

以下是元组的一些基本操作示例:

  • 遍历元组:可以像遍历列表一样使用 for 循环。
  • 查找索引:使用 .index() 方法查找元素的索引位置。
  • 统计次数:使用 .count() 方法统计某个元素出现的次数。

尝试修改元组(例如添加或删除元素)会导致错误,因为元组对象没有 appendpop 等方法。

# 以下操作会引发 AttributeError 错误
# ro_items.append('fourth')
# ro_items.pop()

总之,元组适用于存储不应被程序修改的数据集合,例如配置项、常量集合或函数返回多个值时。


集合:无序的唯一元素集 🧮

接下来,我们探讨集合。集合也是一种存储多个元素的数据结构,但其核心特性是元素唯一无序

创建一个集合可以使用花括号 {}set() 函数。

my_set = {'one'}

以下是集合的核心操作:

  • 添加元素:使用 .add() 方法向集合中添加元素。如果添加已存在的元素,集合不会有任何变化。
    my_set.add('one')  # 集合不变
    my_set.add('two')  # 集合变为 {'one', 'two'}
    
  • 移除元素:使用 .pop() 方法会随机移除并返回一个元素。注意,集合的 .pop() 方法不接受参数。
    popped_item = my_set.pop() # 随机移除一个元素,例如 ‘one’
    
  • 元素唯一性:这是集合最重要的特性。无论你添加多少次重复元素,集合中只会保留一个。
    unique_set = set()
    unique_set.add(1)
    unique_set.add(1)
    unique_set.add(2)
    print(unique_set) # 输出:{1, 2}, 重复的1只出现一次
    

集合的语法 {} 与字典相似,但集合内部没有键值对映射关系,只有独立的值。


总结 🎯

本节课中我们一起学习了Python中两种重要的数据结构。

  • 元组 是一种不可变的有序序列,类似于只读列表,适用于存储不应更改的数据。
  • 集合 是一种无序的容器,其核心是保证元素的唯一性,常用于去重或成员关系测试。

理解元组和集合的特性,能帮助你在未来编写Python程序时,根据数据是否可变、是否需要唯一性等需求,选择最合适的数据结构。

14:Python数据结构回顾 🐍

在本节课中,我们将回顾Python中的核心数据结构。我们将学习如何识别它们,理解不同数据结构之间的语法差异,并探讨在何种场景下应选择何种数据结构来存储数据。最后,我们也会了解一些虽不常用但依然有用的数据结构及其独特之处。

数据结构概述 📊

上一节我们介绍了Python编程的基础概念,本节中我们来看看Python中用于组织和存储数据的核心工具——数据结构。

数据结构是存储、组织和管理数据的特定方式。Python内置了多种数据结构,每种都有其独特的特性和适用场景。

常见数据结构及其识别 🔍

Python中几种常见的数据结构在语法上略有不同。识别它们的关键在于观察其定义时使用的符号。

以下是几种主要数据结构的定义示例:

  • 列表:使用方括号 [] 定义,元素可重复,有序。
    my_list = [1, 2, 3, 'hello']
    
  • 元组:使用圆括号 () 定义,元素不可修改,有序。
    my_tuple = (1, 2, 'world')
    
  • 集合:使用花括号 {} 定义,元素唯一且无序。
    my_set = {1, 2, 3, 3}  # 实际存储为 {1, 2, 3}
    
  • 字典:使用花括号 {} 定义,包含键值对,键唯一。
    my_dict = {'name': 'Alice', 'age': 25}
    

数据结构的选择与应用 ⚖️

理解了不同数据结构的语法后,我们需要掌握如何根据需求选择合适的数据结构。选择的关键在于考虑数据的性质(是否需要有序、是否允许重复)以及需要执行的操作(查找、添加、删除的频率和方式)。

以下是选择数据结构时的一些指导原则:

  • 需要维护元素顺序时,使用列表元组
  • 需要快速检查一个元素是否存在,且不关心顺序和重复时,使用集合
  • 需要将信息(值)与唯一标识符(键)关联时,使用字典
  • 当数据在程序运行过程中不应被改变时,使用元组而非列表。

其他有用的数据结构 🧰

除了上述四种最常用的数据结构,Python还提供了一些其他有用的数据结构,它们在某些特定场景下能提供更优的性能或更方便的接口。

以下是一些值得了解的数据结构:

  • collections.deque:一种双向队列,从两端添加或删除元素的速度很快。
  • collections.defaultdict:一种字典,当访问不存在的键时会返回一个默认值。
  • collections.Counter:用于计数可哈希对象的字典子类。

总结 📝

本节课中我们一起学习了Python的核心数据结构。我们首先学会了如何通过语法识别列表、元组、集合和字典。然后,我们探讨了如何根据数据存储和操作的需求来选择最合适的数据结构。最后,我们简要了解了一些标准库中提供的、用于特殊场景的实用数据结构。掌握这些知识是有效进行数据组织和后续机器学习运维工作的基础。

15:列表与字典的数据操作 📚

在本节课中,我们将学习如何使用Python中的两种核心数据结构——列表和字典,进行数据的添加与提取操作。理解这两种结构的不同行为以及操作它们的方法,是有效管理和处理数据的基础。


列表与字典的差异 🔄

上一节我们介绍了课程的目标,本节中我们来看看列表和字典这两种数据结构的基本差异。它们在Python中的行为有所不同,但掌握如何向其中添加数据、提取数据以及通过各种方式操作其中的数据至关重要。

列表是一个有序的元素集合,而字典则是键值对的集合。这种根本区别决定了我们操作它们的方式。


列表的数据操作 📝

列表使用方括号 [] 定义,并通过索引(从0开始)来访问元素。

向列表添加数据

以下是向列表添加数据的几种常见方法:

  • 使用 append() 方法:在列表末尾添加一个元素。

    my_list = [1, 2, 3]
    my_list.append(4)
    # 结果: [1, 2, 3, 4]
    
  • 使用 insert() 方法:在指定索引位置插入一个元素。

    my_list.insert(1, 'a')
    # 结果: [1, 'a', 2, 3, 4]
    
  • 使用 + 运算符或 extend() 方法:合并两个列表。

    my_list = my_list + [5, 6]
    # 或 my_list.extend([5, 6])
    # 结果: [1, 'a', 2, 3, 4, 5, 6]
    

从列表提取数据

以下是从列表提取数据的主要方式:

  • 通过索引访问:使用 list[index] 获取特定位置的元素。

    first_item = my_list[0]  # 获取第一个元素
    
  • 使用切片:使用 list[start:end] 获取一个子列表。

    sub_list = my_list[1:4]  # 获取索引1到3的元素
    
  • 使用 pop() 方法:移除并返回指定索引的元素(默认为最后一个)。

    last_item = my_list.pop()  # 移除并返回最后一个元素
    

字典的数据操作 🗂️

字典使用花括号 {} 定义,并通过唯一的键来访问对应的值。

向字典添加数据

向字典添加数据本质上是添加新的键值对。

  • 直接赋值:通过 dict[key] = value 添加或修改键值对。

    my_dict = {'name': 'Alice', 'age': 25}
    my_dict['city'] = 'New York'  # 添加新键值对
    my_dict['age'] = 26           # 修改已有键的值
    
  • 使用 update() 方法:用另一个字典的键值对更新当前字典。

    my_dict.update({'job': 'Engineer', 'age': 27})
    

从字典提取数据

从字典提取数据主要通过键来进行。

  • 通过键访问:使用 dict[key] 获取对应键的值。如果键不存在会引发 KeyError

    name = my_dict['name']
    
  • 使用 get() 方法:安全地获取值。如果键不存在,可以返回一个默认值(默认为 None)。

    age = my_dict.get('age')        # 键存在则返回值
    salary = my_dict.get('salary', 0) # 键不存在则返回0
    
  • 获取所有键、值或键值对

    keys = my_dict.keys()    # 获取所有键
    values = my_dict.values() # 获取所有值
    items = my_dict.items()  # 获取所有键值对
    

总结 🎯

本节课中我们一起学习了Python列表和字典的数据添加与提取操作。

我们了解到,列表作为有序序列,主要通过索引和切片进行访问,并使用 append(), insert(), pop() 等方法修改。而字典作为键值映射,则通过唯一的键来操作数据,使用赋值、update()get() 等方法更为常见。

理解这两种结构的特性和操作方法,是后续进行更复杂数据处理的基石。

16:向列表添加数据 📝

在本节课中,我们将学习如何向Python列表中添加数据。我们将探讨多种方法,包括appendinsert、列表相加以及extend,并理解它们之间的区别。


列表基础回顾

上一节我们介绍了如何定义列表。列表使用方括号 [] 来创建。

fruits = ['apple', 'orange']

使用 append 方法添加数据

我们可以使用 append 方法向列表末尾添加新的项目。列表会保持项目的顺序。

以下是向 fruits 列表添加项目的示例:

fruits.append('banana')
# 现在 fruits 是 ['apple', 'orange', 'banana']

使用 insert 方法在指定位置添加数据

append 只能在末尾添加。如果我们想在特定位置插入数据,可以使用 insert 方法。这个方法需要两个参数:索引位置和要插入的值。

例如,我们想在索引 0 的位置(即列表开头)插入 'melon'

fruits.insert(0, 'melon')
# 现在 fruits 是 ['melon', 'apple', 'orange', 'banana']

注意insert 方法的索引参数是必需的。如果省略,会导致 TypeError

使用加法运算符合并列表

两个列表可以使用加法运算符 + 合并,生成一个包含两个列表所有元素的新列表。

假设我们有两个列表:

vegetables = ['cucumber', 'carrots']

我们可以将 fruitsvegetables 相加:

shopping_list = fruits + vegetables
# shopping_list 是 ['melon', 'apple', 'orange', 'banana', 'cucumber', 'carrots']

appendextend 的区别

如果我们想将一个列表添加到另一个现有列表的末尾,直接使用 append 会产生嵌套列表,这可能不是我们想要的结果。

例如:

shopping_list.append(['sugar', 'salt'])
# 现在 shopping_list 末尾是一个嵌套列表: [..., ['sugar', 'salt']]

为了避免嵌套,我们应该使用 extend 方法。extend 会将另一个列表中的所有元素逐个添加到当前列表的末尾。

以下是使用 extend 的正确方式:

shopping_list.extend(['sugar', 'salt'])
# 现在 shopping_list 是平铺的: [..., 'cucumber', 'carrots', 'sugar', 'salt']

总结

本节课中我们一起学习了向Python列表添加数据的几种方法:

  1. append(item):在列表末尾添加单个元素。
  2. insert(index, item):在指定索引位置插入单个元素。
  3. 列表相加 (list1 + list2):合并两个列表,生成一个新列表。
  4. extend(another_list):将另一个列表的所有元素添加到当前列表末尾,避免产生嵌套。

理解这些方法的区别,能帮助你在处理数据时更灵活地操作列表。

17:从列表提取数据 📋

在本节课中,我们将学习如何从Python列表中提取数据。我们将介绍几种不同的方法,包括使用索引、切片操作、pop方法和remove方法,来获取或移除列表中的元素。


从列表提取数据

在前几节课中,我们已经看到可以通过索引从列表中检索值。例如,我们有一个颜色列表:['red', 'yellow', 'green', 'blue']。我们可以使用索引来获取特定项,例如使用索引0会得到'red'

但是,如果我们想获取一个范围或一个子集,仅使用索引就无法实现了。这时,切片操作就派上了用场。


使用切片操作提取子集

切片操作使用冒号:在方括号内进行。例如,colors[:3]会返回列表的前三个元素:'red''yellow''green'

代码示例:

colors = ['red', 'yellow', 'green', 'blue']
subset = colors[:3]
print(subset)  # 输出: ['red', 'yellow', 'green']

如果想获取前两个元素,可以将参数改为2,即colors[:2],这会得到['red', 'yellow']。这是一种非常方便的方法,用于获取已知范围的列表子集。


提取列表末尾的元素

提取列表末尾的元素有时会让人困惑。一个常见的错误是使用-2这样的负数作为起始索引,但这通常不会得到预期的结果。

正确的方法是使用负索引作为起始点。例如,colors[-3:]会返回最后三个元素:'yellow''green''blue'

代码示例:

last_three = colors[-3:]
print(last_three)  # 输出: ['yellow', 'green', 'blue']

提取特定范围的元素

切片操作还可以用于提取列表中的特定范围。例如,要提取'yellow''green',可以使用索引13(注意结束索引不包含在内)。

代码示例:

range_subset = colors[1:3]
print(range_subset)  # 输出: ['yellow', 'green']

使用pop方法移除并返回元素

pop方法用于移除列表中指定索引的元素,并返回该元素的值。需要注意的是,指定的索引必须存在于列表中。

代码示例:

popped_item = colors.pop(1)
print(popped_item)  # 输出: 'yellow'
print(colors)       # 输出: ['red', 'green', 'blue']

如果尝试弹出不存在的索引(例如100),Python会引发IndexError异常。


使用remove方法移除元素

如果只是想移除列表中的某个元素,而不关心其返回值,可以使用remove方法。该方法会查找并移除第一个匹配的元素。

代码示例:

colors.remove('blue')
print(colors)  # 输出: ['red', 'green']

remove方法会执行两个步骤:首先查找元素的位置,然后将其从列表中移除。


总结

本节课中,我们一起学习了从Python列表提取数据的几种方法:

  1. 使用索引:直接通过位置获取单个元素。
  2. 使用切片操作:提取列表的子集,包括前N个元素、后N个元素或特定范围的元素。
  3. 使用pop方法:移除并返回指定索引的元素。
  4. 使用remove方法:移除列表中第一个匹配的元素。

掌握这些方法将帮助您更灵活地处理和操作列表数据。

18:从字典中提取数据 📖

在本节课中,我们将学习从 Python 字典中检索和提取数据的几种方法。我们将探讨如何安全地访问可能不存在的键,以及如何从字典中移除元素。


字典数据提取基础

上一节我们介绍了字典的基本概念和创建方法。本节中,我们来看看如何从字典中获取数据。

我们已经见过几种从字典提取数据的方式。首先,实例化一个名为 contact_information 的空字典。

contact_information = {}

尝试检索一个不存在的键会发生什么?例如,检索 height 键。

contact_information['height']

执行上述代码会引发 KeyError。当你尝试检索字典中不存在的键时,就会发生这种错误。字典可能是空的,也可能已包含大量键值对,但只要没有 height 这个键,就会引发键错误。


处理键错误的两种方法

以下是两种处理键错误的方法。

方法一:使用 try-except 块

第一种方法是使用 try-except 块。我们尚未详细讲解异常处理,这将在后续课程中介绍,但这是处理键错误的一种方式。

try:
    height = contact_information['height']
except KeyError:
    height = '6 feet'
print(height)

运行这段代码,会输出 6 feet。因为键错误被捕获,我们转而执行了其他操作。如果你知道在 height 键不存在时想做什么,这是一种处理方式。

方法二:使用 .get() 方法

我更喜欢另一种方法,即使用 .get() 方法。.get() 方法允许你传入想要提取的键,并返回一个特定值。在 Python 中,如果未找到键,默认会返回一个特殊类型 None

height = contact_information.get('height')
print(f"The height of contact is {height}.")

运行这段代码,输出为 The height of contact is None.。这样就不会再引发键错误,而是产生一个 None 值。

如果你不想要 None,而希望返回其他值,.get() 方法也允许你指定一个备用值。

height = contact_information.get('height', '5 feet 9 inches')
print(f"The height of contact is {height}.")

这里,.get() 方法的第二个参数是备用值。运行代码,输出为 The height of contact is 5 feet 9 inches.。这就是 .get() 方法的使用方式。


使用 .pop() 方法操作字典

与列表和集合类似,字典也可以使用 .pop() 方法。.pop() 方法会移除指定键及其对应的值,并返回该值。

contact_information['H'] = 31
h_value = contact_information.pop('H')
print(f"H is {h_value}.")
print(f"Contact information: {contact_information}")

在这段代码中,我们先将键 'H' 和值 31 添加到字典中。然后使用 .pop('H') 移除该键值对,并将值 31 赋给变量 h_value。最后打印结果。

运行代码,第一行输出 H is 31.,第二行输出 Contact information: {}。因为 .pop() 操作移除了该条目,所以字典变空了。


总结

本节课中,我们一起学习了从 Python 字典中提取和操作数据的几种方法:

  1. 直接通过键访问可能引发 KeyError
  2. 使用 try-except 块可以优雅地处理键不存在的情况。
  3. 使用 .get(key, default) 方法可以安全地获取值,并指定备用值。
  4. 使用 .pop(key) 方法可以移除键值对并获取其值。

掌握这些方法能让你更安全、灵活地使用字典数据结构。

19:19_01_06_课程回顾-数据添加与提取 📚

在本节课中,我们将回顾如何向Python的两种核心数据结构——列表(List)和字典(Dictionary)——添加数据,以及从它们中提取数据。掌握这些操作是高效处理数据的基础。

向数据结构添加数据 ➕

上一节我们介绍了列表和字典的基本概念,本节中我们来看看如何向它们添加新的元素。

向列表添加数据

列表是一种有序的集合。以下是向列表添加数据的几种主要方法:

  • 使用 append() 方法:在列表末尾添加一个元素。
    my_list = [1, 2, 3]
    my_list.append(4)
    # 结果:my_list 变为 [1, 2, 3, 4]
    
  • 使用 insert() 方法:在列表的指定索引位置插入一个元素。
    my_list = [1, 2, 3]
    my_list.insert(1, 'a')
    # 结果:my_list 变为 [1, 'a', 2, 3]
    
  • 使用 extend() 方法或 + 运算符:将另一个列表的所有元素添加到当前列表末尾。
    my_list = [1, 2, 3]
    my_list.extend([4, 5])
    # 或 my_list = my_list + [4, 5]
    # 结果:my_list 变为 [1, 2, 3, 4, 5]
    

向字典添加数据

字典是一种键值对(Key-Value Pair)的集合。以下是向字典添加数据的方法:

  • 直接赋值:通过新的键(Key)直接赋值,即可添加新的键值对。
    my_dict = {'name': 'Alice', 'age': 25}
    my_dict['city'] = 'New York'
    # 结果:my_dict 变为 {'name': 'Alice', 'age': 25, 'city': 'New York'}
    
  • 使用 update() 方法:用另一个字典或键值对序列更新当前字典,添加或覆盖条目。
    my_dict = {'name': 'Alice', 'age': 25}
    my_dict.update({'city': 'New York', 'age': 26})
    # 结果:my_dict 变为 {'name': 'Alice', 'age': 26, 'city': 'New York'}
    

从数据结构提取数据 📤

了解了如何添加数据后,接下来我们学习如何从这些结构中提取所需的数据。

从列表提取数据

列表通过索引(位置)来访问元素。以下是提取数据的方法:

  • 通过索引访问:使用方括号 [] 和索引(从0开始)获取单个元素。
    my_list = ['a', 'b', 'c', 'd']
    element = my_list[2]
    # 结果:element 的值为 'c'
    
  • 切片(Slicing):使用 [start:end:step] 语法获取一个子列表。
    my_list = ['a', 'b', 'c', 'd', 'e']
    sub_list = my_list[1:4]
    # 结果:sub_list 为 ['b', 'c', 'd']
    

从字典提取数据

字典通过键(Key)来访问对应的值(Value)。以下是提取数据的方法:

  • 通过键访问值:使用方括号 [] 和键名来获取对应的值。
    my_dict = {'name': 'Alice', 'age': 25, 'city': 'New York'}
    value = my_dict['name']
    # 结果:value 的值为 'Alice'
    
  • 使用 get() 方法:通过键获取值,如果键不存在可以返回默认值,避免程序报错。
    my_dict = {'name': 'Alice', 'age': 25}
    value = my_dict.get('city', 'Unknown')
    # 结果:因为键‘city’不存在,value 的值为 'Unknown'
    
  • 获取所有键、值或键值对
    my_dict = {'name': 'Alice', 'age': 25}
    keys = my_dict.keys()   # 获取所有键
    values = my_dict.values() # 获取所有值
    items = my_dict.items() # 获取所有键值对(元组形式)
    

总结 ✨

本节课中我们一起学习了Python列表和字典的核心操作。

你现在已经掌握了如何向列表和字典添加数据,也了解了从这些数据结构中提取数据的各种方法。你能够辨别在何种情况下应使用哪种数据结构,并知晓在处理数据时,与这些数据结构进行交互和提取数据的一些高效方式。

20:函数的使用 🧮

在本节课中,我们将学习函数的使用。我们将了解如何编写函数,如何要求函数必须接收某些参数,以及如何让函数变得更加灵活,以适应不同数量的参数需求。

概述

上一节我们介绍了Python的基础概念。本节中,我们将深入探讨函数。函数是组织代码、实现代码复用的核心工具。理解如何定义和使用函数,是编写高效、清晰程序的关键。

函数的定义与基本使用

函数通过 def 关键字定义。一个基本的函数包含函数名、参数列表和函数体。

以下是定义一个简单函数的语法:

def function_name(parameters):
    # 函数体
    # 执行的操作
    return value  # 可选

例如,定义一个打印问候语的函数:

def greet(name):
    print(f"Hello, {name}!")

# 调用函数
greet("Alice")

必需参数与默认参数

函数可以定义必需参数,调用时必须提供。也可以为参数指定默认值,使其成为可选参数。

以下是定义默认参数的示例:

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

# 调用时只提供必需参数
greet("Bob")  # 输出: Hello, Bob!

# 调用时提供所有参数
greet("Charlie", "Hi")  # 输出: Hi, Charlie!

可变长度参数

有时,我们无法预知函数需要接收多少个参数。Python提供了两种处理可变长度参数的方法:*args 用于接收任意数量的非关键字参数,**kwargs 用于接收任意数量的关键字参数。

以下是使用 *args 的示例:

def sum_all(*numbers):
    total = 0
    for num in numbers:
        total += num
    return total

result = sum_all(1, 2, 3, 4, 5)
print(result)  # 输出: 15

以下是使用 **kwargs 的示例:

def print_info(**info):
    for key, value in info.items():
        print(f"{key}: {value}")

print_info(name="Diana", age=30, city="New York")

何时使用不同类型的参数

理解不同参数类型的适用场景至关重要。

以下是选择参数类型的一些指导原则:

  • 必需参数:当函数的逻辑必须依赖某个输入值时使用。
  • 默认参数:当某个参数有常用的默认值,但用户可能需要覆盖它时使用。
  • *args:当函数需要处理数量不确定的位置参数时使用。
  • **kwargs:当函数需要处理数量不确定的关键字参数,或者需要将参数传递给另一个函数时使用。

总结

本节课中,我们一起学习了Python函数的核心用法。我们掌握了如何定义函数,如何使用必需参数和默认参数来平衡灵活性与约束,以及如何利用 *args**kwargs 来处理可变数量的参数。熟练运用这些技巧,将使你能够编写出更通用、更强大的代码模块。

21:函数结构与返回值 📘

在本节课中,我们将要学习Python中函数的基本结构,以及函数如何通过返回值与程序的其他部分进行交互。理解函数的定义、执行以及返回值的概念,是编写模块化和可重用代码的第一步。


函数的基本结构

上一节我们介绍了代码块和缩进的概念,本节中我们来看看如何定义一个函数。函数是组织可重用代码的基本单元。

定义一个函数,你需要使用 def 关键字。这标志着函数定义的开始。紧接着是函数名(不能有空格),后面跟着一对圆括号,最后以冒号结束。从下一行开始,所有属于该函数的代码行都必须缩进(通常是四个空格),这向Python表明这些代码是函数体的一部分。

def simple():
    print("this is a function")

运行上述代码会创建一个名为 simple 的函数。当我们调用它时,它会执行函数体内的代码,打印出“this is a function”。


函数的“执行”与“返回值”

在开始使用Python函数时,有一个概念可能会让人困惑:有些函数执行操作,而有些函数则返回值。并非所有执行操作的函数都会返回一个值。

我们上面定义的 simple 函数就是一个例子:它执行了打印操作,但没有返回值。在Python中,如果一个函数没有明确使用 return 语句返回值,它会隐式地返回一个特殊值 None

为了理解这一点,我们可以尝试将调用 simple 函数的结果赋值给一个变量,然后打印这个变量。

result = simple()
print(result)

运行这段代码,你会先看到打印出的“this is a function”,然后看到 result 的值是 None。这是因为 simple 函数执行了打印操作,但它的返回值是隐式的 None


如何让函数返回值

如果你希望函数计算一个结果并将其传递出去,就需要使用 return 语句。以下是一个返回值的函数示例:

def produce():
    return "this is a return value"

produce 函数使用 return 关键字明确地返回了一个字符串。现在,当我们调用这个函数并将其结果赋值给变量时,变量将保存返回的值,而不是 None

result = produce()
print(result)

执行这段代码,result 的值将是字符串“this is a return value”。


核心要点总结

以下是关于函数结构与返回值的关键点:

  • 定义函数:使用 def 关键字,后接函数名、括号和冒号。函数体必须缩进。
  • 函数行为:函数可以执行操作(如打印),也可以返回值,或者两者兼有。
  • 隐式返回值:如果函数没有 return 语句,它会自动返回 None
  • 显式返回值:使用 return 语句可以指定函数要返回的值。

本节课中我们一起学习了Python函数的基本定义方法,区分了仅执行操作的函数与返回值的函数,并理解了 None 作为隐式返回值的作用。掌握这些基础是后续学习函数参数、作用域以及构建复杂程序的关键。

22:Python 函数参数详解 📘

在本节课中,我们将学习 Python 函数参数的不同类型及其使用方法。理解函数参数是编写灵活且强大函数的关键。


🎯 函数参数概述

函数可以接收参数,这些参数允许我们向函数传递数据。本节将介绍参数的工作原理。


🔧 必需参数

函数定义中的必需参数位于括号内。以下是一个示例函数 squared,它返回传入数字的平方值。

def squared(number):
    return number ** 2

如果传入参数 4,函数将返回 16,因为 4 的平方是 16。该函数执行数学运算并需要一个输入值。

如果未传递任何参数,Python 将引发 TypeError,提示缺少必需的位置参数 number。传入的参数将使用函数定义中指定的变量名。


🔄 可选参数

某些函数具有可选参数。例如,Python 内置的 int 函数。

result = int()  # 返回 0
result = int("10")  # 返回整数 10

int 函数无需导入即可使用。如果不传递任何参数,它默认返回 0。它还可以将字符串转换为整数值,这使其非常灵活。有时我们可能需要这种灵活性,有时则不需要。


🗝️ 关键字参数

关键字参数允许我们为参数设置默认值,使其成为可选参数。以下是一个示例函数 greetings

def greetings(full_name="John Doe"):
    print(f"Greetings, {full_name}!")

调用 greetings() 将输出 Greetings, John Doe!,因为未传递参数,使用了默认值。调用 greetings("Superman") 将输出 Greetings, Superman!

Python 在幕后检查是否提供了 full_name 参数。如果未提供,则使用默认值 "John Doe"。这种键值对映射的参数称为关键字参数。


⚠️ 参数顺序注意事项

混合使用必需参数和关键字参数时,必须遵循特定的顺序:必需参数在前,关键字参数在后。否则,Python 将引发语法错误。

以下示例函数 formal_greet 演示了正确的参数顺序。

def formal_greet(name, title="Doctor"):
    print(f"Greetings, {title} {name}!")

调用 formal_greet("Jenkins") 将输出 Greetings, Doctor Jenkins!,因为未提供 title 参数,使用了默认值。

如果想传递不同的 title,有两种方式:

  1. 按位置传递:formal_greet("Jenkins", "Senior")
  2. 使用关键字指定:formal_greet("Jenkins", title="Senior")

两种方式效果相同。


📝 总结

本节课我们一起学习了 Python 函数参数的不同类型:必需参数、可选参数和关键字参数。我们还了解了混合使用这些参数时必须遵循的顺序规则。掌握这些概念将帮助你编写更灵活、更强大的函数。

23:可变参数与关键字参数 📘

在本节课中,我们将要学习Python函数中的两个高级概念:可变参数关键字参数。这些功能允许函数接受任意数量的参数,使代码更加灵活和通用。


🎯 概述:什么是可变参数与关键字参数?

在编写函数时,有时我们无法预先知道需要接收多少个参数。Python提供了两种机制来处理这种情况:可变参数(使用*args)和可变关键字参数(使用**kwargs)。它们允许函数接受零个到任意多个普通参数或键值对参数。


🔧 可变参数(*args)

上一节我们介绍了函数的基本参数传递,本节中我们来看看如何使用可变参数。可变参数允许函数接收任意数量的位置参数,这些参数在函数内部被组织成一个元组(tuple)。

以下是定义和使用可变参数的步骤:

  1. 定义函数时使用*args:在参数前添加一个星号(*),例如def function_name(*args):
  2. 传递任意数量的参数:调用函数时,可以传入任意数量的参数,包括零个。
  3. 在函数内部访问参数args变量在函数内部是一个元组,包含了所有传入的位置参数。
def family_members(*args):
    for name in args:
        print(name)

# 调用函数,传入三个参数
family_members('Lucy', 'Matt', 'Bob')

运行上述代码,函数会依次打印出:LucyMattBob

如果调用时不传入任何参数,函数也不会出错,只是不会执行循环体内的打印操作。这体现了其灵活性。


🔑 参数名称的灵活性

需要记住的是,*args中的args只是一个约定俗成的变量名,并非关键字。你可以使用任何有效的变量名。

以下是更改参数名称的示例:

def family_members(*names):
    for name in names:
        print(name)

family_members('John', 'Sam')

这段代码的功能与之前完全相同,只是将内部的循环变量从args改为了names


🗂️ 可变关键字参数(**kwargs)

除了可变的位置参数,Python还支持可变的关键字参数。这允许函数接收任意数量的键值对参数,这些参数在函数内部被组织成一个字典(dictionary)。

以下是定义和使用可变关键字参数的步骤:

  1. 定义函数时使用**kwargs:在参数前添加两个星号(**),例如def function_name(**kwargs):
  2. 传递任意数量的键值对:调用函数时,可以传入任意数量的key=value形式的参数。
  3. 在函数内部访问参数kwargs变量在函数内部是一个字典,你可以像操作字典一样访问这些键值对。
def stats(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

# 调用函数,传入多个关键字参数
stats(speed='slow', active=False, weight=210)

运行代码,输出结果为:

speed: slow
active: False
weight: 210

你可以传入任意不同的键值对组合,函数都能灵活处理。同样,调用时不传入任何参数也是允许的。


💎 总结

本节课中我们一起学习了Python函数中两个强大的特性:可变参数(*args)可变关键字参数(kwargs)**。

  • 可变参数将传入的所有位置参数打包成一个元组,适用于参数数量不确定但无需命名的场景。
  • 可变关键字参数将传入的所有键值对参数打包成一个字典,适用于需要接收任意数量命名参数的场景。

这两种机制极大地增强了函数的灵活性,在你无法预知函数将接收多少参数时(这在数据处理和机器学习任务中很常见)尤其有用。希望你能在实践中熟练运用它们。

24:函数使用回顾 🧮

在本节课中,我们将回顾Python函数的核心概念与使用方法。我们将学习如何定义函数、使用不同类型的参数,并理解如何使函数更加灵活和健壮。


函数定义与基础

上一节我们介绍了Python编程的基础知识,本节中我们来看看如何创建和使用函数。函数是组织好的、可重复使用的代码块,用于执行特定任务。

定义一个函数的基本语法如下:

def function_name(parameters):
    """可选的文档字符串"""
    # 函数体
    return value

函数参数类型

理解不同类型的参数是编写灵活函数的关键。以下是Python函数中常见的几种参数:

  1. 必需参数:调用函数时必须按正确顺序提供的参数。
  2. 默认参数:在定义函数时赋予默认值的参数,调用时可省略。
  3. 可变位置参数 (*args):允许函数接收任意数量的位置参数,在函数内部作为一个元组处理。
  4. 可变关键字参数 (**kwargs):允许函数接收任意数量的关键字参数,在函数内部作为一个字典处理。

参数使用示例

以下是每种参数类型的具体使用示例:

# 1. 必需参数
def greet(name):
    return f"Hello, {name}!"

# 2. 默认参数(提供回退值)
def greet_with_title(name, title="Mr./Ms."):
    return f"Hello, {title} {name}!"

# 3. 可变位置参数 (*args)
def sum_all(*numbers):
    return sum(numbers)

# 4. 可变关键字参数 (**kwargs)
def print_info(**info):
    for key, value in info.items():
        print(f"{key}: {value}")

灵活运用函数

现在,你应该对编写函数充满信心。你不仅能够编写函数,还能非常灵活地处理参数:有时需要必需参数,有时使用可变参数和关键字参数,并且可以为参数设置默认值作为回退。你已经掌握了所有这些知识,应该能够毫无困难地编写函数。


本节课中我们一起学习了Python函数的核心用法,包括如何定义函数、使用必需参数、默认参数、可变位置参数 (*args) 和可变关键字参数 (**kwargs)。掌握这些概念将使你能够编写出结构清晰、灵活且可重用的代码,这是进行机器学习运维(MLOps)和任何Python项目开发的重要基础。

25:构建类与方法 🏗️

在本节课中,我们将更深入地探讨Python中的类。我们将学习如何创建类,理解构造函数和__init__方法,了解类属性,并解释在类中无处不在的self参数。最后,我们还会简要介绍听起来复杂但实际并不难的类继承。通过这几节课,你将学会如何高效地使用类。

类的创建与构造函数

上一节我们介绍了类的基本概念,本节中我们来看看如何具体构建一个类。类的创建始于class关键字。

以下是创建一个简单类的语法:

class ClassName:
    # 类的主体
    pass

理解 __init__ 方法与 self

在定义类之后,我们通常需要初始化对象。这是通过一个特殊的方法——构造函数——来完成的。

在Python中,构造函数就是__init__方法。当创建一个类的新实例时,这个方法会自动被调用。self参数是一个指向实例本身的引用,它必须是类中每个方法的第一个参数。

以下是__init__方法的一个示例:

class Dog:
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age    # 实例属性

类属性与实例属性

理解了构造函数后,我们需要区分两种属性:类属性和实例属性。

  • 类属性:属于类本身,所有实例共享同一个值。
  • 实例属性:属于具体的对象实例,每个实例可以有不同的值。

以下是定义类属性和实例属性的示例:

class Dog:
    species = "Canis familiaris"  # 类属性

    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age    # 实例属性

类继承简介

最后,我们来探讨一下类继承。继承允许我们定义一个类(子类)来继承另一个类(父类)的属性和方法。这有助于代码重用和建立清晰的层次关系。

以下是类继承的基本语法:

class ParentClass:
    # 父类的属性和方法
    pass

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/duke-mlops/img/c6bb15c68489d270ee92f2b2373b995a_4.png)

class ChildClass(ParentClass):
    # 子类可以拥有自己独有的属性和方法,也可以重写父类的方法
    pass

本节课中我们一起学习了Python中类的核心构建块:如何创建类,如何使用__init__构造函数和self参数初始化对象,如何区分类属性与实例属性,并初步了解了类继承的概念。掌握这些知识是进行面向对象编程和构建复杂MLOps项目的基础。

26:Python类简介 🐍

在本节课中,我们将要学习Python中一个非常重要的概念:。类是面向对象编程的核心,它允许我们创建自定义的数据类型,将数据(属性)和操作数据的方法(函数)捆绑在一起。理解类是编写复杂、模块化Python程序的关键一步。

除了函数,类是你在Python中会经常遇到的东西之一。我倾向于偏爱函数,但类绝对有其存在的价值和位置,它们被用来实现强大的程序功能。我将通过这个笔记本来展示一些基础知识。

最基本的类

你可以创建的最基本的类会使用Python中的 class 关键字。然后你会给它一个名字,类似于在函数中使用 def 关键字后跟名字和冒号。这就是你创建或定义一个新类的方式。

class BasicClass:
    pass

我在这里使用 pass 是因为这个类是空的,意味着它没有实际功能。第4行代码不执行任何操作。然后在第6行,我进行实例化。一旦我调用这个类,它就变成了一个实例,它被创建、被构造。可以把第3到4行看作是描述这个东西将是什么的蓝图。而第6行则是它启动、运行、被创建、存在的时候。

这就是你能拥有的最基本的类。现在让我们看看,当我们尝试探索这个实例拥有的属性时,我们能发现什么。

my_instance = BasicClass()
print(dir(my_instance))

这里肯定有很多不同的东西,它们都以双下划线开头。我们至少会看到其中一个,即 __init__,在Python中我们也常称之为 Dunder init(双下划线init)。所有这些都属于类的特殊方法。在日常使用中,你可能不会用到它们中的大多数,但知道它们存在是有用的。一旦你创建了一个类的实例,即使它像上面那个两行代码的类一样基础且什么都不做,你也会有很多东西可以使用。

旧式类语法

现在让我们看另一种你可能会看到的类构造方式,它可能看起来有点奇怪。

class OldStyleClass(object):
    pass

在第3行,我们再次有这个基本的类,但我们使用了 object。这是什么 object?在非常旧的Python版本中,在定义类时习惯将 object 作为参数传递给类。在现代Python中,情况已不再如此,所以你通常不会看到它。有些人仍然倾向于使用它,但在现代Python中这不是必需的。无论你是否看到它,类的工作方式都是一样的,有它或没有它真的无关紧要。只是了解一下在旧版本的Python中这是惯例是有用的。

类属性与方法

那么,你还能在这里做什么呢?我将创建一个名为 Dog 的类,并使用一个叫做类属性的东西。现在我将开始解释类的一些组件,然后我们会使用它们并尝试调用它们,看看它们各自的作用。

class Dog:
    # 类属性
    is_animal = True

    # 方法
    def bark():
        print("Woof!")

在这个例子中,第5行我使用了 is_animal = True,这被称为类属性。然后我添加了一个方法。但这个方法有一个错误。如果你不熟悉类,你可能看不出这个类有什么问题。但如果你非常熟悉类,你会发现第7行有一个相当严重的问题。

好的,我来运行这个。我在这里定义了 Dog 类,定义了一个 is_animal = True 的类属性,然后有一个 bark 方法。这是一个方法,所以它属于 Dog 类。我在第10行实例化了这个对象,创建了 Dog 的一个实例。现在让我们让它叫。

my_dog = Dog()
my_dog.bark()

当我调用它时,你会看到 my_dog.bark()bark 属于 Dog 类。我运行这些代码,会得到一个 TypeError。为什么我会得到类型错误?因为我漏掉了一个关键字。错误信息显示:bark() takes 0 positional arguments but 1 was given

隐式地,Python在幕后传递了一个参数。按照惯例,这个参数被称为 self。我们稍后会看到更多关于方法的内容。只需注意,每次创建方法时,都必须有一个参数,并且第一个参数应该是 self

以下是修正后的代码:

class Dog:
    # 类属性
    is_animal = True

    # 方法 - 修正后
    def bark(self):
        print("Woof!")

现在,如果我再次运行这个单元格,my_dog.bark() 将是一个可调用对象,它会打印 “Woof!”。然后 my_dog.is_animal 将是 True,这就是那里的属性。

你可以根据需要创建任意多个这些类的实例。这是类的一个强大之处,它允许你创建许多、许多实例,并在任何地方开始生成类和对象。

注意类属性

但是,请注意类属性。我向你展示了 Dog.is_animal 是一个类属性。这意味着它是一种属于类的属性。但如果你不通过实例访问它,而是直接通过类进行修改,就像我在下面第2行做的那样,那么我将会搞乱已创建的该类的每一个实例。

# 直接修改类属性
Dog.is_animal = False

rufus = Dog()
print(rufus.is_animal)  # 输出:False

sparky = Dog()
print(sparky.is_animal)  # 输出:False

运行这个,你现在可以看到 rufus.is_animal 将是 False。所以你只需要注意,这可能会给你带来问题。如果你必须定义一个类属性(如果你知道你在做什么,这完全没问题),那么你必须注意它可能会改变。这个改变也会影响任何未来的实例。你可以看到这里 sparky 对象被创建,并且每个实例的 is_animal 也将是 False,尽管在第5行我们定义了 is_animal = True

关于 self 的总结

我简要介绍了 self 是什么,但你必须创建它。我们已经遇到了一个类型错误。你必须总是传入这个 self 参数,否则你会遇到麻烦。目前你只需要知道这些,我们稍后会深入细节。但请注意,你必须传入它。虽然不强制要求参数名必须叫 self,但这是一个你几乎在任何地方都能看到的约定。


本节课中我们一起学习了Python类的基础知识。我们了解了如何定义最基本的类,什么是实例化,以及蓝图(类定义)和实际对象(实例)之间的区别。我们探讨了类属性和实例方法,并强调了在定义方法时 self 参数的重要性。我们还看到了直接修改类属性会如何影响所有实例。掌握这些概念是迈向面向对象编程和构建更复杂Python应用的重要一步。

27:Python 类与构造函数 🐍

在本节课中,我们将学习 Python 类中一个非常重要的概念:构造函数。我们将了解构造函数的作用、如何定义它,以及如何使用它来初始化对象的属性。


概述

上一节我们快速介绍了 Python 类的基本概念。本节中,我们将深入探讨构造函数的使用。构造函数允许我们在创建类的实例时设置一些初始状态或执行一些操作。


构造函数简介

构造函数是一个特殊的方法,它在创建类的新实例时自动调用。在 Python 中,构造函数的名称是 __init__

以下是如何定义一个带有构造函数的类:

class Dog:
    def __init__(self):
        self.is_animal = True

在这个例子中,__init__ 方法在类 Dog 被实例化时执行。它使用 self 来设置一个实例属性 is_animalTrue

注意self 是一个指向实例本身的引用,用于访问实例的属性和方法。


实例属性与类属性

你可能还记得之前提到的类属性。实例属性与类属性的区别在于,实例属性属于特定的实例,而类属性属于类本身,被所有实例共享。

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

class Dog:
    is_animal = True  # 类属性

    def __init__(self):
        self.is_animal = True  # 实例属性

如果我们创建两个 Dog 实例并修改其中一个的 is_animal 属性,另一个实例不会受到影响。这是因为实例属性是独立的。


向构造函数传递参数

构造函数可以接受参数,这样我们可以在创建实例时初始化不同的属性。

以下是一个更复杂的例子:

class Animal:
    def __init__(self, name, legs=4, barks=True):
        self.name = name
        self.legs = legs
        self.barks = barks

在这个例子中:

  • name 是一个必需参数。
  • legsbarks 是可选参数,带有默认值。

在方法中使用实例属性

在类的方法中,我们需要使用 self 来访问实例属性。以下是一个常见错误及其修正方法:

错误示例

class Animal:
    def __init__(self, name, legs=4, barks=True):
        self.name = name
        self.legs = legs
        self.barks = barks

    def info(self):
        print(f"This is an animal named {name}, has {legs} legs, and {'barks' if barks else 'does not bark'}.")

运行上述代码会导致 NameError,因为 namelegsbarks 不是局部变量,而是实例属性。

修正后的示例

class Animal:
    def __init__(self, name, legs=4, barks=True):
        self.name = name
        self.legs = legs
        self.barks = barks

    def info(self):
        print(f"This is an animal named {self.name}, has {self.legs} legs, and {'barks' if self.barks else 'does not bark'}.")

现在,info 方法可以正确访问实例属性。


从外部访问实例属性

从类的外部访问实例属性非常简单,只需使用实例的名称即可:

bunny = Animal("Buster", legs=4, barks=False)
print(bunny.name)   # 输出: Buster
print(bunny.legs)   # 输出: 4
print(bunny.barks)  # 输出: False

总结

本节课中,我们一起学习了 Python 类的构造函数。我们了解了:

  1. 构造函数的作用是在创建实例时初始化属性。
  2. 如何使用 __init__ 方法定义构造函数。
  3. 如何向构造函数传递参数并设置实例属性。
  4. 如何在类的方法中使用 self 访问实例属性。
  5. 如何从类的外部访问实例属性。

掌握构造函数是理解面向对象编程的关键一步,它为创建灵活且可重用的代码奠定了基础。

28:为类添加方法 🧩

在本节课中,我们将学习如何在Python类中定义和使用多个方法。我们将通过创建一个预算管理类来演示如何添加方法、在方法间调用以及如何为方法设置参数。


我们已经简要了解了类中的方法。但关于方法的添加和使用,还有一些细节需要探讨。方法类似于特殊的函数,但它们在类中定义。接下来,我们将通过创建一个包含两个方法的类来深入了解。

首先,我创建一个名为 Budget 的类。这个类将包含一个构造方法 __init__ 和一个名为 expense 的方法。

class Budget:
    def __init__(self, budget):
        self.budget = budget

    def expense(self, amount):
        self.budget -= amount
        print(f"预算剩余: {self.budget}")

在上面的代码中,__init__ 是一个构造方法,用于初始化类的实例。expense 方法用于从预算中扣除指定金额,并打印剩余预算。

现在,让我们创建一个 Budget 类的实例,并调用 expense 方法。

b = Budget(100)
b.expense(23)
b.expense(45)

运行上述代码,我们将看到输出结果:扣除23后剩余77,再扣除45后剩余32。这表明我们的方法工作正常。

接下来,我们考虑将不同功能分离到不同的方法中。例如,我们可以将打印预算剩余的功能提取到一个单独的 report 方法中。

以下是更新后的 Budget 类:

class Budget:
    def __init__(self, budget):
        self.budget = budget

    def expense(self, amount):
        self.budget -= amount
        self.report()

    def report(self):
        print(f"预算剩余: {self.budget}")

在这个版本中,expense 方法在扣除金额后调用 report 方法来打印剩余预算。这样,我们可以将不同功能分离到不同方法中,使代码更加清晰。

现在,让我们测试更新后的类:

b = Budget(100)
b.expense(23)
b.expense(45)

运行代码,我们将看到与之前相同的输出结果。这表明我们的修改没有影响类的功能。

此外,我们还可以从类的外部访问实例的内部状态。例如,我们可以直接访问 budget 属性:

print(b.budget)

这将输出当前的预算剩余值。在类内部,我们通过 self.budget 来访问这个属性。

接下来,我们看看如何为方法添加参数。例如,我们可以为 report 方法添加一个可选的 currency 参数,用于指定货币符号。

以下是更新后的 Budget 类:

class Budget:
    def __init__(self, budget):
        self.budget = budget

    def expense(self, amount):
        self.budget -= amount
        self.report()

    def report(self, currency="$"):
        print(f"预算剩余: {currency}{self.budget}")

在这个版本中,report 方法接受一个 currency 参数,默认值为 "$"。这样,我们可以根据需要指定不同的货币符号。

现在,让我们测试这个新功能:

b = Budget(100)
b.expense(23)
b.report("¥")

运行代码,我们将看到输出结果中使用了指定的货币符号。这表明我们可以灵活地为方法添加参数。

需要注意的是,方法的参数规则与函数相同:必须先有位置参数,然后是关键字参数。我们还可以使用可变参数和可变关键字参数,就像在函数中一样。


在本节课中,我们一起学习了如何在Python类中添加和使用多个方法。我们通过创建一个预算管理类,演示了如何定义方法、在方法间调用以及为方法添加参数。这些技巧将帮助您编写更加模块化和灵活的代码。

29:类继承 🧬

在本节课中,我们将要学习 Python 中一个核心但有时令人困惑的概念:类继承。我们将通过一个简单的宠物管理示例,来理解继承如何工作,以及它如何让代码更高效、更易于管理。


概述

类继承允许我们创建一个“父类”(或基类),其中定义通用的属性和方法。然后,我们可以创建“子类”,这些子类会自动获得父类的所有功能,同时还可以添加或修改自己特有的行为。这有助于减少代码重复,并建立清晰的类层次结构。

上一节我们介绍了类的基本概念,本节中我们来看看如何通过继承来扩展类的功能。


父类的定义

首先,我们定义一个基础的 Pet(宠物)类。这个类将作为一个通用模板,包含所有宠物都可能有的行为。

class Pet:
    def eat(self):
        self.food = self.food - self.appetite

请注意,这个 eat 方法依赖于 self.foodself.appetite 这两个属性。但在 Pet 类本身,我们并没有定义它们。如果直接实例化 Pet 类并调用 eat 方法,会导致错误,因为缺少必要的属性。

my_pet = Pet()
my_pet.eat()  # 这会引发 AttributeError: 'Pet' object has no attribute 'food'

这正是设计继承的初衷:父类定义通用的行为框架,而具体的属性则由继承它的子类来提供。


创建子类

现在,我们创建两个具体的宠物子类:Parakeet(鹦鹉)和 Dog(狗)。它们将继承自 Pet 类。

以下是子类的定义方式:

class Parakeet(Pet):
    def __init__(self):
        self.food = 100
        self.appetite = 1

class Dog(Pet):
    def __init__(self):
        self.food = 400
        self.appetite = 7

在类名后的括号 (Pet) 指明了继承关系。ParakeetDog 现在自动拥有了 Pet 类中定义的 eat 方法。


使用继承的类

让我们创建这些类的实例,并观察继承是如何起作用的。

perry = Parakeet()
rufus = Dog()

perry.eat()
print(f"Perry 吃完后还剩 {perry.food} 份食物。")
# 输出: Perry 吃完后还剩 99 份食物。

rufus.eat()
print(f"Rufus 吃完后还剩 {rufus.food} 份食物。")
# 输出: Rufus 吃完后还剩 393 份食物。

关键点在于:ParakeetDog 类本身并没有定义 eat 方法,但它们可以调用它,因为该方法是从父类 Pet 继承而来的。


检查对象的属性和方法

为了更清楚地看到子类从父类继承了什么,我们可以使用 Python 内置的 dir() 函数。

print(dir(perry))
# 输出会包含: ['__class__', '__delattr__', ..., 'appetite', 'eat', 'food', ...]

dir() 函数会列出对象的所有属性和方法。在输出中,你可以看到除了子类自己定义的 appetitefood 属性外,还有从 Pet 类继承来的 eat 方法。


继承的实际应用场景

继承在现实世界的编程中非常常见。一个典型的例子是使用 Python 标准库中的 unittest 模块来编写测试。

以下是其基本用法:

import unittest

class MyTestCase(unittest.TestCase):
    pass

my_test = MyTestCase()
# 使用 dir() 查看 my_test 对象的所有方法
print(dir(my_test))
# 你会看到大量从 unittest.TestCase 继承来的方法,如 assertEqual, assertTrue 等。

当你创建一个继承自 unittest.TestCase 的类时,你的测试类会自动获得所有用于断言(判断测试结果)的方法。这展示了继承的强大之处:它为你提供了大量现成的、经过测试的功能。


总结

本节课中我们一起学习了 Python 的类继承。

我们了解到,继承允许子类获取父类的属性和方法,从而促进代码重用和逻辑组织。我们通过定义通用的 Pet 父类和具体的 ParakeetDog 子类,演示了继承的基本机制。最后,我们还探讨了如何使用 dir() 函数来检查继承关系,并了解了继承在 unittest 等实际库中的应用。

理解继承是掌握面向对象编程的关键一步,它能帮助你构建更清晰、更模块化的代码结构。

30:构建类与方法回顾 🧩

在本节课中,我们将回顾Python中关于类与方法的构建。我们将总结之前视频中介绍的核心概念,包括类、方法、self关键字、构造函数以及类属性。这些概念是提升Python编程能力的关键。

课程回顾与总结 📝

上一节我们介绍了类的基本概念,本节中我们来回顾并整合关于构建类与方法的核心知识点。

理解类与对象

现在你已经了解了如何使用类。掌握类并感到得心应手花费了我很长时间。

核心概念解析

希望我在这些视频中的讲解,能为你提供一种更清晰、更直接的理解方式。我们涵盖了一些内容,例如方法、self、构造函数,甚至是类属性。

以下是这些核心概念的简要说明:

  • 方法:定义在类内部的函数,用于描述对象的行为。
  • self:在类的方法中,用于指代调用该方法的对象实例本身的关键字。
  • 构造函数 (__init__):一个特殊的方法,在创建类的新实例时自动调用,用于初始化对象的属性。
    class MyClass:
        def __init__(self, attribute):
            self.attribute = attribute  # 初始化实例属性
    
  • 类属性:属于类本身的变量,被该类的所有实例共享。
    class MyClass:
        class_attribute = "I am shared"  # 这是一个类属性
    

这些概念可能相当复杂,但它们能让你提升Python知识水平,并变得更加熟练。

总结 🎯

本节课中我们一起回顾了Python中构建类与方法的核心内容。我们总结了方法、self关键字、构造函数和类属性的定义与用途。理解这些概念是进行面向对象编程和构建更复杂、模块化代码的基础。

31:模块与高级用法 🧩

在本节课中,我们将要学习Python编程的进阶知识,重点是如何组织代码以及管理外部依赖。你已经掌握了Python的基础语法,能够编写一些代码。接下来,我们将提升一个层次,学习如何使用导入、理解模块的概念、如何用Python模块、目录和子目录来组织代码,最后还会学习如何安装外部依赖。Python自带的功能已经非常强大,但有时在项目中,我们仍然需要安装额外的依赖包。我们将学习如何以安全、可重复的方式进行安装。


理解模块与导入 📦

上一节我们介绍了Python的基础知识。本节中,我们来看看如何将代码组织成可重用的单元。

在Python中,一个模块就是一个包含Python定义和语句的.py文件。模块让你能够有逻辑地组织代码,并可以在其他程序中重复使用。

要使用一个模块中的功能,你需要使用import语句。以下是导入模块的基本方式:

import math

导入后,你可以使用点号.来访问模块中的函数或变量:

result = math.sqrt(16)

组织代码:模块、目录与包 🗂️

当你开始编写更复杂的项目时,将所有代码放在一个文件中会变得难以管理。这时,你需要将代码拆分到多个模块中,并用目录结构来组织它们。

一个包含__init__.py文件的目录被称为一个“包”(Package)。包可以包含多个模块和子包,形成一个层次结构。

以下是组织代码的常见方式:

  • 模块:单个.py文件。
  • :一个包含__init__.py文件的目录。
  • 子包:包内的另一个包。

你可以使用点号语法来导入包中的模块:

from my_package import my_module
from my_package.sub_package import another_module

管理外部依赖 🔧

Python标准库功能丰富,但并非万能。在实际项目中,你经常需要使用第三方库来实现特定功能,例如数据分析或机器学习。

这些第三方库就是“依赖”。为了以安全、可重复的方式管理它们,我们通常使用一个名为requirements.txt的文件来列出所有依赖及其版本。

以下是管理依赖的步骤:

  1. 创建虚拟环境:这是一个独立的Python环境,用于隔离项目依赖。
    python -m venv myenv
    
  2. 激活虚拟环境
    • Windows: myenv\Scripts\activate
    • macOS/Linux: source myenv/bin/activate
  3. 安装依赖:使用pip命令并根据requirements.txt文件安装。
    pip install -r requirements.txt
    
  4. 生成依赖文件:你可以将当前环境中的依赖列表导出到requirements.txt
    pip freeze > requirements.txt
    

这种方法确保了任何人在任何机器上都能用相同的依赖版本复现你的项目环境。


总结 📝

本节课中我们一起学习了Python的进阶组织与管理技巧。我们首先理解了模块的概念以及如何使用import语句。接着,我们探讨了如何通过模块、目录和包来构建清晰的代码结构。最后,我们学习了如何通过虚拟环境和requirements.txt文件来安全、可重复地管理项目的外部依赖。掌握这些技能,将使你能够构建和维护更复杂、更专业的Python项目。

32:Python模块简介 📦

在本节课中,我们将要学习Python模块的基本概念。模块是Python中组织代码的核心方式,理解它们对于编写结构清晰、易于维护的程序至关重要。


什么是Python模块? 🤔

上一节我们介绍了Python的基础知识,本节中我们来看看如何组织代码。Python模块本质上就是一个以.py为扩展名的Python文件。这就是模块的全部定义。

你可以将模块视为组织代码的一种方式。如果你看到一个非常长的Python文件,里面包含多个类、许多函数,并且代码行数超过200行,那么它的组织方式可能不够好。当你需要更好地组织代码时,就需要使用模块。

我通常举的例子是:想象把你所有的衣服都放在一个抽屉里,这很可能行不通。把所有东西放在一个地方不如将它们分开存放,这样能保持整洁和可维护性。

模块的组织结构示例 🗂️

以下是一个示例项目结构,可以让你对模块的用途有一个直观的认识:

example_project/
├── __init__.py
├── engine.py
├── exceptions.py
├── main.py
└── network.py

这个结构让你大致了解代码可以如何分开放置。例如,如果你想处理特定的异常或创建自定义异常,所有这些代码都可以放在exceptions.py这个模块文件中。这为你提供了一种整洁、有序的工作方式。

Python本身也拥有大量的内置模块。例如,os模块。虽然我们稍后才会详细讲解导入(imports),但如果我在代码中导入os,你会发现这一个模块里就包含了许多功能。

实际项目中的模块应用 💼

除了上面的例子,我想向你展示我的一个个人项目结构:

my_project/
├── decorators.py
├── engine.py
├── exceptions.py
├── utilities.py
└── order.py

让我们看看exceptions.py模块。在这里,我定义了基础异常类,并运用了一些继承来创建自定义异常。所有自定义异常都可以在这里找到。

utilities.py是一个放置你希望复用、并使其对其他模块可用的代码的绝佳位置。这样其他模块就可以导入并使用它。这是一个非常典型的用例。

我们将在后续课程中讨论目录(包)及其工作原理。

核心要点总结 ✨

本节课中我们一起学习了Python模块。你需要掌握的核心概念是:任何以.py结尾的文件都是一个模块。模块可以帮助你更好地组织代码,使其更清晰、更易于维护。

33:使用导入语句 🐍

在本节课中,我们将要学习如何在Python中使用导入语句。导入语句是Python中组织和使用代码的核心机制,它允许你将代码分割到不同的文件中,并在需要时引入它们。我们将通过具体的例子来理解如何导入模块、访问其中的函数,以及处理包含多个文件的目录结构。


模块导入基础

上一节我们介绍了导入语句的基本概念,本节中我们来看看如何导入和使用一个简单的Python模块。

我有一个名为 utils.py 的文件,它是一个Python模块。在这个模块中,我定义了两个辅助函数。

以下是 utils.py 文件的内容示例:

# utils.py 文件内容
def string_to_int(s):
    """将字符串转换为整数"""
    return int(s)

def string_to_float(s):
    """将字符串转换为浮点数"""
    return float(s)

在同一个目录下的Jupyter笔记本中,我可以导入这个模块。Python是一种脚本语言,通过点号 . 来访问模块内的内容。

# 导入整个utils模块
import utils

# 查看模块中可用的内容
print(dir(utils))

运行 dir(utils) 会显示模块中的所有属性和方法。你可以忽略那些以下划线 _ 开头的方法和名称,重点关注我们定义的 string_to_intstring_to_float 函数。

访问这些函数的方式是使用 模块名.函数名 的格式。

# 使用模块中的函数
result_int = utils.string_to_int("123")
result_float = utils.string_to_float("45.67")

print(result_int, result_float)

这种方式要求模块文件(如 utils.py)位于Python可以找到的路径上。在本例中,因为它在同一个目录下,所以可以直接导入。


从模块中导入特定内容

除了导入整个模块,你还可以选择只导入需要的特定函数或变量。

以下是使用 from ... import ... 语句的方法:

# 从utils模块中只导入特定的函数
from utils import string_to_int, string_to_float

# 现在可以直接使用函数名,无需前缀
result = string_to_int("999")
print(result)

这种方式与导入整个模块的主要区别在于,你不需要在函数名前加上 utils. 前缀。因为这些函数被显式地导入到了当前的作用域中。


处理包(目录结构)

在实际项目中,代码通常被组织在多个文件和目录中。在Python中,包含 __init__.py 文件的目录被称为“包”。

假设我有一个名为 program 的目录,其结构如下:

program/
├── __init__.py
└── items.py

items.py 文件内容如下:

# program/items.py
def some_function():
    return "这是一个模块中的函数"

__init__.py 文件的存在告诉Python,这个目录应该被视为一个包。

现在,我们可以在笔记本中导入这个包内的模块。

# 导入包内的模块
import program.items

# 使用模块中的函数
message = program.items.some_function()
print(message)

如果你想直接导入 program 包,然后通过它访问 items 模块,你需要确保 __init__.py 文件中有相应的导入声明。否则,直接 import program 后,program.items 可能不可用。

这是处理项目结构时需要记住的一点。


总结

本节课中我们一起学习了Python导入语句的核心用法。

我们首先介绍了如何导入一个单独的模块并使用其中的函数,语法是 import module_namemodule_name.function_name

接着,我们学习了如何从模块中精确导入所需的部分,使用 from module_name import function_name 的格式,这样可以避免冗长的前缀。

最后,我们探讨了如何组织代码到目录(包)中,并通过 __init__.py 文件来管理包的导入行为。记住,点号 . 是访问嵌套模块和包内元素的关键。

掌握这些导入技巧,对于构建清晰、可维护的Python项目至关重要。

34:使用Python脚本的实用技巧 🐍

在本节课中,我们将学习如何编写和运行一个基础的Python脚本,并理解如何通过命令行向其传递参数。这对于自动化任务和构建可复用的工具至关重要。


脚本结构与文档

首先,我们从一个简单的Python脚本开始。脚本的开头通常包含一个文档字符串(Docstring),用于解释脚本的功能。文档字符串使用三个引号(""")定义,它比普通的注释更规范,常用于生成美观的文档。

"""
这是一个示例Python脚本,用于演示如何接收和处理命令行参数。
"""

定义主函数

接下来,我们定义一个名为 main 的函数。这个函数将负责处理脚本的主要逻辑。目前,它只是打印一条消息,并遍历接收到的所有参数。

def main():
    print("这是主函数")

处理命令行参数

为了从命令行接收参数,我们需要导入 sys 模块并使用 sys.argvsys.argv 是一个列表,其中包含了命令行传递给脚本的所有参数。

import sys

def main():
    print("这是主函数")
    for arg in sys.argv:
        print(f"接收到参数: {arg}")

条件执行与导入保护

在脚本的底部,我们使用一个条件判断来确保 main 函数只在脚本被直接运行时执行,而不是在被导入为模块时执行。这是通过检查 __name__ 变量是否为 "__main__" 来实现的。

if __name__ == "__main__":
    main()

这种机制允许脚本既可以直接运行,也可以作为模块被其他代码导入,而不会在导入时产生意外的副作用。


运行脚本与传递参数

现在,让我们在命令行中运行这个脚本,并观察它是如何处理参数的。

基本运行

首先,我们不带任何额外参数运行脚本:

python script.py

输出结果将显示脚本的名称(script.py)作为第一个参数。

传递多个参数

我们可以向脚本传递多个参数,包括标志和键值对:

python script.py argument help -f key=value

脚本将依次打印出所有接收到的参数。

查看参数类型

为了更清楚地了解参数的类型,我们可以在脚本中添加一行代码来打印每个参数的类型:

for arg in sys.argv:
    print(f"参数: {arg}, 类型: {type(arg)}")

再次运行脚本,你会看到所有参数都是以字符串形式传递的。


总结

本节课中,我们一起学习了如何编写一个基础的Python脚本,并通过 sys.argv 接收和处理命令行参数。我们还了解了如何使用 if __name__ == "__main__": 来保护脚本在被导入时不执行主逻辑。这些技巧是构建更复杂命令行工具的基础。如果你需要处理更复杂的参数(如可选标志、默认值等),建议使用专门的库,如 argparse

35:虚拟环境与依赖管理 🐍📦

在本节课中,我们将要学习Python开发中一个至关重要的概念:虚拟环境。我们将了解为什么需要它,以及如何使用它来安全地管理项目依赖。


在深入其他主题之前,理解Python在包管理方面普遍存在的问题非常重要。Python的打包、安装依赖以及相关操作有时会带来麻烦。

因此,接下来我将向你展示如何在终端中操作虚拟环境。你必须使用某种虚拟环境来安装依赖。

创建虚拟环境

我们使用Python3来创建虚拟环境。首先,你需要确认系统中Python可执行文件的位置。

创建虚拟环境的命令如下:

python3 -m venv vm

其中,-m venv 表示调用Python3内置的venv模块。vm是你为虚拟环境目录指定的名称,你可以使用任何名称。为了清晰起见,这里没有使用隐藏目录(以点开头)。

运行命令后,表面上似乎没有变化,但实际上已经创建了一个新目录。使用ls命令可以查看其内容:

ls vm

你会看到目录里包含许多文件。你只需要知道,它让你能够以一种自包含的方式在该目录内安装包。即使安装了错误的内容,也不会影响系统。

如果出现问题,你唯一需要做的就是删除这个目录,然后创建一个新的并从头开始。

激活虚拟环境

现在,我已经创建了目录,但还没有激活环境。

要激活它,在Linux或Mac上,请运行:

source vm/bin/activate

在Windows系统上操作略有不同,你可以使用PowerShell脚本。

激活后,你会注意到命令行提示符发生了变化,现在显示为(vm),这表示虚拟环境的名称。

验证环境

如果我现在运行which python,系统会告诉我Python来自哪里。它来自这个vm目录中的Python。

同样,运行which pip也会显示Pip工具来自虚拟环境目录。这意味着,如果我使用Pip安装包,例如Flask:

pip install flask

Pip是你用来安装和卸载依赖的工具。这个命令会将Flask安装到虚拟环境的目录中。如果进入vm/lib目录下的site-packages文件夹,就能看到已安装的包,例如因为安装Flask而连带安装的Jinja2和Click。

使用依赖清单文件

如果你有许多依赖项,使用一个requirements.txt文件会非常有用。

以下是创建和使用该文件的方法:
首先,将依赖(如flask)写入文件:

echo "flask" > requirements.txt

然后,你可以使用单个命令安装文件中的所有依赖,而无需逐个安装:

pip install -r requirements.txt

停用与删除环境

要停用当前激活的虚拟环境,只需运行:

deactivate

你会看到命令行提示符恢复原样。再次运行which python,它将指向系统Python或Conda环境,而不再是虚拟环境中的Python。

你可以随时创建新的虚拟环境,例如:

python3 -m venv other_vm

然后使用source other_vm/bin/activate激活它。


本节课中,我们一起学习了虚拟环境的使用。请务必在安装、卸载、管理包和依赖时使用虚拟环境。它能确保一切安全且隔离。如果遇到问题,你只需要停用并删除环境,然后重新创建一个即可。

36:课程回顾-模块与高级用法 📚

在本节课中,我们将对Python模块、虚拟环境及依赖管理等核心概念进行回顾与总结,帮助你巩固项目设置的基础知识。


上一节我们介绍了Python编程的基础,本节中我们来看看如何组织代码和管理项目环境。

So that concludes the introduction to Python, you should now feel comfortable working with different imports。

至此,Python的介绍部分已经结束。你现在应该能熟练地处理不同的导入操作。

understanding what the virtual environment is, installing dependencies。

理解什么是虚拟环境,以及如何安装依赖项。

not only the ones that I showed, but whenever new dependencies for new projects get started。

不仅限于我演示过的那些,还包括每当启动新项目时需要的新依赖项。

以下是关于项目设置的核心要点:

  • 你现在已经掌握了如何设置项目的基础。
  • 你学会了如何让项目中的各项功能正常运行。
  • 这种设置方式对你个人是安全且有用的。
  • 这种设置方式对你的项目协作者同样安全且有用。

本节课中我们一起学习了Python模块化编程和项目环境管理。你掌握了使用import语句、理解虚拟环境的概念、以及使用pip安装和管理项目依赖。这些技能是构建可维护、可协作的Python项目,特别是机器学习运维项目的坚实基础。

37:编写与执行测试 🧪

在本节课中,我们将学习测试的重要性以及如何熟练地进行测试。我们将介绍一些Python的约定和工具,并重点讲解一个我非常喜欢的外部库——PyTest。这个库能帮助你编写非常直观的测试,从而验证你的代码是否健壮、运行正确且没有错误。


为什么测试很重要?🤔

上一节我们明确了本课的目标,现在让我们首先探讨测试为何如此关键。测试是软件开发中不可或缺的一环,它能确保你的代码按预期工作,并在修改后依然保持正确性。通过编写测试,你可以提前发现并修复错误,提高代码质量,并增强对代码变更的信心。

Python测试约定与工具 🛠️

了解了测试的重要性后,本节中我们来看看Python社区中常用的一些测试约定和内置工具。Python提供了unittest模块作为标准测试框架,它遵循xUnit风格。然而,许多开发者更倾向于使用更简洁、功能更强大的第三方库。

以下是Python中常见的测试方法:

  • 单元测试:针对代码中最小的可测试单元(通常是函数或方法)进行测试。
  • 集成测试:测试多个模块或组件协同工作的情况。
  • 功能测试:从用户角度验证整个软件功能是否符合需求。

使用PyTest编写测试 ✨

Python内置的unittest模块功能完备,但语法有时略显繁琐。因此,本节我们将重点介绍PyTest,这是一个广受欢迎的外部测试框架,它让编写测试变得异常简单和直观。

PyTest的核心优势在于其简洁的语法。你只需要编写普通的函数,并使用assert语句进行断言。例如,测试一个加法函数:

# 被测试的函数
def add(a, b):
    return a + b

# 使用PyTest编写的测试函数
def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

以下是使用PyTest的主要步骤:

  1. 安装PyTest:pip install pytest
  2. 创建测试文件,文件名需以test_开头(如test_calculator.py)。
  3. 在文件中编写以test_开头的测试函数。
  4. 在终端运行pytest命令,它会自动发现并运行所有测试。

PyTest还支持丰富的插件和高级功能,如夹具(fixtures)用于测试前的设置和清理,参数化测试用于用多组数据运行同一测试逻辑,这能极大地提升测试效率和覆盖率。


总结 📝

本节课中,我们一起学习了软件测试的基础知识。我们首先探讨了测试对于保证代码质量和可靠性的重要性。接着,我们回顾了Python标准的测试约定。最后,我们深入介绍了PyTest这个强大的测试框架,学习了如何使用它简洁的语法来编写和运行测试,从而确保我们的机器学习项目代码健壮无误。

通过掌握这些测试技能,你可以更自信地开发和维护代码,为构建可靠的机器学习运维管道打下坚实基础。

38:Python测试的动机 🧪

在本节课中,我们将要学习为什么在软件开发,特别是Python项目中,编写测试是至关重要的。我们将探讨关于测试的常见误解,并理解自动化测试如何帮助我们构建更健壮、更可维护的软件。

概述

在开始学习测试的基础知识以及如何在Python中应用之前,理解一些测试的核心概念非常重要。很多人初次接触测试时,可能并不理解它的重要性。然而,随着在Python和软件开发领域的深入,你会发现自动化测试对于验证工作、确保发布质量以及在生产环境中进行更改前进行双重检查,具有巨大的价值。

测试的常见误解与动机

在实践测试或建议工程团队应该进行更多测试时,通常会遇到三种常见的借口。下面我们来逐一分析这些观点。

1. 测试太耗时 ⏳

首先,让我们看看“测试太耗时”这个观点背后的实际原因。

在软件开发中,任何有价值的事情都需要前期投入,测试也不例外。你确实需要预先做一些工作。然而,从长远来看,测试验证了你的工作成果。虽然它需要时间,但测试带来的回报远超其投入成本。

你可以根据自己的节奏来设定预期,按需添加测试,不必过度测试,并观察它们的实际效用。随着项目规模的增长,如果你需要调试问题或查明某些功能为何失效,缺乏测试将耗费你更多的时间。

2. 测试太难了 🧗

另一个常见的观点是认为测试太难。和几乎所有技能一样,测试也需要通过练习来取得进步并达到熟练。

你需要花时间去理解什么是一个好的测试。例如,一个运行缓慢或不能真实反映业务场景的测试就不是好测试。一个好的测试,尤其是单元测试,应该具备以下特点:

  • 快速:执行速度快。
  • 明确:能清晰地告诉你失败的原因、预期的结果以及问题所在。
  • 有指导性:为你提供修复问题的良好基础。

3. 我的代码很完美,没有Bug 🦸

最后一个观点可能令人惊讶,但确实有工程师曾对我说:“我写的代码很完美,从来没有Bug。”然而,所有软件都有Bug。这是软件讨论中一个不争的事实。

无论你是谁,拥有多少年经验,都会遇到Bug。而如果你拥有测试,它就有能力在你发布软件之前,准确地告诉你Bug在哪里。这样你就能理解问题的根源,进行修复,并在修复完成后验证发布或补丁的有效性。这本身就是一个强大的验证循环:如果你认为修复了一个Bug,什么能证明你真的修复了它?你已经下意识地进行了测试,那么为什么不将这个测试自动化,用代码来实现呢?

测试的核心价值:自动化与责任

几年前我做了一个关于测试的演讲,其核心前提是:如果软件出了问题,我们该责怪谁?

当你发布软件却不进行测试时,实际上你只是在等待用户抱怨软件无法工作或以何种方式无法工作。测试自动化是任何项目自动化的基石之一。试想,如果你有30个步骤来验证一切是否正常,作为人类,我们很可能会忘记其中一步,或是在某处打错字,从而导致结果与预期不符。

软件通常会不断增长,复杂性也会持续增加。软件测试将帮助你构建一个非常健壮高度可维护的软件产品。

总结

本节课我们一起学习了在Python项目中实施测试的核心动机。我们分析了关于测试“太耗时”、“太难”和“代码完美无需测试”的三种常见误解,并阐述了测试在自动化验证提前发现Bug构建可维护软件方面的不可替代价值。希望这些关于测试,特别是Python测试的经验,能对你的项目有所帮助。

39:Python 测试规范与 PyTest 入门 🧪

在本节课中,我们将学习 Python 中的测试规范,了解标准库 unittest 的基本用法及其局限性,并重点介绍更受推荐的测试框架 PyTest 的优势和基本使用规范。


Python 标准库:unittest

Python 自带 unittest 测试库,无需额外安装。然而,在多数情况下,它可能并非首选。我们先来看看它的基本用法。

以下是一个简单的 unittest 示例代码:

import unittest

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

在这段代码中,我们需要导入 unittest 模块,创建一个继承自 unittest.TestCase 的类,并在其中定义以 test_ 开头的方法。断言需要使用 self.assert 系列辅助方法,例如 self.assertEqual

unittest 提供了多种断言方法,如 assertEqualassertTrueassertFalseassertIsassertIsNot 等。然而,其测试报告在失败时提供的信息不够丰富和直观。例如,使用 assertNotAlmostEqual 比较两个整数时,错误信息可能包含“within 7 places”这类对调试帮助不大的描述。

此外,使用 unittest 必须采用面向对象的方式,要求创建测试类并使用继承,这增加了入门复杂度。


更优选择:PyTest 框架

鉴于 unittest 的上述特点,我们引入 PyTest 框架。PyTest 是一个功能强大且易用的命令行工具和测试框架。

PyTest 的核心优势在于它增强了原生的 assert 语句。在 Python 中,通常不鼓励在生产代码中使用原生 assert,一方面是因为可以通过 -O 选项移除它们,另一方面是其失败信息不够描述性。但 PyTest 解决了后者的问题。

PyTest 中,你可以直接使用原生 assert 进行断言,框架会为其生成非常详细和易读的失败报告。这使得测试代码更加简洁直观。

以下是 PyTest 的一个简单测试函数示例:

def test_addition():
    assert 1 + 1 == 2

def test_string():
    assert 'hello'.upper() == 'HELLO'

测试可以是简单的函数,也可以是类。如果使用类,类名需要以 Test 开头,但无需继承任何基类,这提供了极大的灵活性。


测试文件与代码的组织规范

上一节我们介绍了 PyTest 的基本写法,本节中我们来看看如何组织测试文件和代码。PyTest(以及 unittest 等工具)依赖一些命名约定来自动发现和运行测试。

以下是需要遵循的关键约定:

  • 测试目录命名:通常使用复数形式的 tests 作为目录名。避免使用单数 test,以免与 Python 内置的 test 模块冲突。
  • 测试文件命名:包含测试代码的文件名需要以 test_ 开头。
  • 测试项目结构示例
    my_project/
    │
    ├── my_project/          # 项目主代码包
    │   └── __init__.py
    │
    └── tests/               # 测试目录
        ├── __init__.py
        ├── test_main.py     # 测试文件,以 test_ 开头
        └── test_verify_output.py
    
  • 测试代码单元命名
    • 测试函数需要以 test_ 开头。
    • 测试类需要以 Test 开头(首字母大写)。

遵循这些约定后,在项目根目录下简单地运行 pytest 命令,工具就能自动发现并运行所有测试。

关于测试代码的位置,既可以放在项目内部的 tests 目录中,也可以放在项目外部,这取决于个人或团队的偏好。


课程总结

本节课中我们一起学习了 Python 的测试规范。我们首先了解了标准库 unittest 的基本用法和它在报告友好性、代码简洁性方面的不足。接着,我们重点介绍了 PyTest 框架,它通过增强原生 assert 语句提供了出色的失败报告,并且允许以更简单的函数形式编写测试,降低了使用门槛。最后,我们明确了组织测试代码时需要遵循的目录和文件命名约定,这些约定是测试工具自动发现测试的基础。掌握这些内容,是构建可靠 Python 项目的重要一步。

40:使用pytest进行测试 🧪

在本节课中,我们将学习如何安装和运行 pytest 测试框架,并理解其自动发现测试文件的命名约定。


创建虚拟环境

首先,建议为项目创建一个独立的虚拟环境。你可以使用以下命令创建,其中 venv 是虚拟环境的名称,你可以自定义。

python -m venv venv

创建完成后,需要激活这个虚拟环境。


安装pytest

激活虚拟环境后,使用 pip 安装 pytest

pip install pytest

为了便于项目依赖管理,强烈建议pytest 添加到 requirements.txt 文件中。这样能确保项目在任何时候都能明确其运行依赖。


测试文件命名约定

上一节我们介绍了环境的搭建,本节中我们来看看 pytest 如何发现和运行测试文件。

pytest 遵循特定的命名约定来自动发现测试文件。它会自动收集所有以 test_ 开头的文件或目录,以及文件中以 test_ 开头的函数或方法。

以下是两个示例文件:

  • test_examples.py:符合命名约定,会被自动发现。
  • non_test_examples.py:不符合命名约定,默认情况下不会被自动发现。

运行测试

在包含测试文件的目录下,直接运行 pytest 命令即可。

pytest

运行后,pytest 会输出测试结果摘要,包括测试平台、Python版本、收集到的测试项数量以及每个测试的通过或失败状态。

如果只想运行某个特定的文件,可以明确指定文件名。

pytest non_test_examples.py

即使文件不符合 test_ 前缀的命名约定,通过显式指定,pytest 也会执行其中的测试。


理解测试收集

如果我们将 test_examples.py 重命名为不符合约定的名字(例如 rtest_examples.py),再次运行 pytest 命令(不指定文件)将收集不到任何测试。

pytest
# 输出可能包含:collected 0 items

这体现了命名约定的重要性。遵循约定可以让测试运行变得简单高效。当然,pytest 的配置非常灵活,你可以修改默认的发现规则,但遵循约定是最佳实践。

你可能会问,为什么要在测试文件中放置非测试内容?有时,测试需要一些辅助函数或工具(例如生成测试数据、模拟文件操作),这些辅助代码可以放在同一个文件中,只要它们不以 test_ 开头,就不会被当作测试执行。


在项目结构中运行

pytest 的自动发现功能在项目子目录中同样有效。即使在项目根目录下运行 pytest,它也会递归地搜索所有子目录,找到并运行所有符合命名约定的测试文件。


本节课中我们一起学习了 pytest 测试框架的基本使用方法:从创建虚拟环境、安装 pytest,到理解其基于文件命名的自动测试发现机制,最后掌握了运行测试的几种方式。遵循 test_ 前缀的命名约定是高效使用 pytest 的关键。

41:编写与执行测试 🧪

在本节课中,我们将回顾并学习编写与执行测试的核心概念。测试是软件开发中至关重要的一环,它能确保代码的可靠性、健壮性和可维护性。我们将了解测试的重要性,并学习如何使用 pytest 工具来编写高质量的测试。

测试的重要性与基础

上一节我们介绍了测试的基本概念,本节中我们来看看为什么测试如此关键。

测试非常重要,不仅限于Python领域。你现在已经具备了理解测试重要性的基础。

你理解了为什么应该进行测试,以及测试如何使你的代码变得更好、更健壮、更易于维护。这些都是良好的基础。

使用 pytest 工具

除了理论基础,你现在还了解了一个名为 pytest 的工具。这个工具允许你编写非常出色的测试,并提供极其详尽的测试报告。

以下是 pytest 的核心优势:

  • 能够编写高质量的测试用例。
  • 生成非常清晰、详细的测试报告。
  • 使你成为更优秀的数据科学家、数据工程师或软件工程师。

总结

本节课中我们一起学习了测试在代码开发中的核心价值。我们明确了测试的目的是为了提升代码质量、健壮性和可维护性。同时,我们介绍了 pytest 这一强大工具,它通过提供简洁的语法和丰富的报告功能,能有效帮助我们编写和执行测试,从而提升我们的开发能力。

42:编写实用测试 🧪

在本节课中,我们将学习如何编写实用的Python测试。我们将从基础的断言开始,探讨如何组织测试代码,并介绍一个强大的高级功能,以帮助你编写更高效、更简洁的测试。


课程概述 📋

测试是确保代码质量的关键环节。本节课将涵盖以下核心内容:使用简单的assert语句、编写测试类和测试函数,以及如何根据情况选择最合适的测试组织形式。最后,我们将深入了解一个名为“参数化”的高级功能,它能在输出不同但断言逻辑相同时,显著提升测试的编写效率。


使用简单的断言(assert)

上一节我们介绍了测试的重要性,本节中我们来看看最基础的测试工具——assert语句。断言是测试的基石,用于验证代码行为是否符合预期。

assert语句的基本语法如下:

assert 条件表达式, "可选的错误信息"

如果条件表达式为False,程序将抛出AssertionError并停止执行。

以下是使用assert进行测试的几个要点:

  • assert简单直接,无需引入额外测试框架即可进行快速验证。
  • 它适用于简单的脚本或快速原型开发中的即时检查。
  • 在正式的测试套件中,通常使用更专业的测试框架(如pytestunittest),它们能提供更丰富的断言方法和更好的测试报告。

组织测试代码:类与函数

在编写了基础断言后,我们需要考虑如何组织测试代码。主要有两种方式:将测试编写在类中,或者编写为独立的函数。

以下是选择测试组织形式的一些指导原则:

  • 使用测试类:当你有许多相关的测试方法,并且它们可以共享通用的设置(setUp)和清理(tearDown)代码时,将测试组织在类中是很好的选择。这符合面向对象的思想,便于管理。
  • 使用测试函数:对于简单、独立的测试场景,使用函数更加轻量和直观。它没有类的结构开销,写起来更快速。

两种方式没有绝对的优劣,关键在于根据测试的逻辑复杂度和关联性做出合适的选择。


高级功能:参数化测试(parametrize)

上一节我们讨论了测试的组织形式,本节中我们来看看一个能极大提升测试效率的高级功能——参数化。

参数化测试允许你使用不同的输入数据运行同一个测试逻辑。当测试模式相同,只是输入和预期输出发生变化时,这能避免编写大量重复的测试代码。

例如,使用pytest@pytest.mark.parametrize装饰器:

import pytest

@pytest.mark.parametrize("input, expected", [
    (1, 2),
    (5, 10),
    (-3, -6),
])
def test_double(input, expected):
    assert input * 2 == expected

上面的代码会运行三次test_double函数,分别使用三组不同的(input, expected)数据。这样,我们就用一个测试函数覆盖了多种情况。


课程总结 🎯

本节课中我们一起学习了编写实用Python测试的核心知识。我们从最简单的assert语句开始,了解了其在快速验证中的作用。接着,我们探讨了如何通过类或函数来组织测试代码,并分析了各自的适用场景。最后,我们介绍了参数化测试这一强大工具,它能够帮助我们用更简洁的代码测试多种输入输出组合,是编写高效测试套件的关键。

掌握这些测试编写方法,将为你的机器学习运维(MLOps)工作打下坚实的基础,确保模型代码的可靠性和可维护性。

43:在 pytest 中使用断言 🔍

在本节课中,我们将学习如何在 pytest 框架中使用普通的 assert 语句来测试和比较复杂的数据结构。我们将通过三个具体的例子——长字典、长列表和长字符串——来展示 pytest 如何清晰地定位并报告测试失败的原因,从而帮助你高效地调试代码。


之前我们提到,在 pytest 中直接使用普通的 assert 语句是非常好的做法,你无需为此担心。

现在,我打开了一个终端,激活了 pytest 虚拟环境,pytest 已经安装完毕,可以开始使用了。我有三个示例文件,将向你展示它是如何工作的。

测试长字典 📚

首先,我们来看一个测试长字典的例子。有时在测试中,你需要比较非常大、非常密集、完全填充的对象或数据结构,比如字典。使用 pytest 的普通 assert 会非常棒,因为它能清晰地指出差异所在,无论差异在哪里。

以下是一个测试函数,其中包含一个结果和一个期望值:

def test_long_dictionaries():
    result = {"first_name": "John", "last_name": "Doe", "age": 30}
    expectation = {"first_name": "John", "last_aim": "Doe", "age": 30}
    assert result == expectation

运行这个测试,我们预期它会失败,因为 result 中的键是 "last_name",而 expectation 中是 "last_aim"

让我们在终端中运行这个测试:

pytest test_long_dictionaries.py -v

pytest 会报告失败,并指出:“左边包含一个额外的项 ‘last_name’: ‘Doe’,右边包含一个额外的项 ‘last_aim’: ‘Doe’”。这有点难以分辨,因为只是键名不同。

如果我们想看到更详细的差异对比,可以加上 -vv 标志:

pytest test_long_dictionaries.py -vv

现在,pytest 会运行完整的差异引擎,将两个字典并排显示,并用 + 号标出 result 中有而 expectation 中没有的项(即 ‘last_name’: ‘Doe’)。这就是使用普通 assert 的强大之处。

测试长列表 📋

上一节我们看到了 pytest 如何处理字典的差异。本节中,我们来看看它如何处理长列表。列表是一种有序的数据结构,通过索引访问。

假设我们有以下测试:

def test_long_lists():
    result = [1, 2, 3, 4, 5]
    expectation = [1, 2, 99, 4, 5]
    assert result == expectation

运行这个测试:

pytest test_long_lists.py -v

pytest 会报告:“在索引 2 处不匹配”。列表索引从 0 开始,所以索引 2 指的是第三个元素。它准确地告诉你问题出在哪里。

同样,使用 -vv 标志可以获得更详细的输出,显示列表中之前被省略的其他相同元素。

测试长字符串 📝

最后,我们来看长字符串的比较。这对于 Python 中的所有数据结构都适用,尽管对于自定义数据结构,你可能需要做一些额外的工作,但 pytest 的基础功能已经非常有用。

考虑以下测试:

def test_long_strings():
    result = "This is a very long string with a typo."
    expectation = "This is a very long string with a typo."
    assert result == expectation

运行测试:

pytest test_long_strings.py -v

如果字符串很长且存在差异(例如大小写问题),pytest 会毫无困难地找出不同之处。它还会创建你所测试数据的子集,比较两者,并精确地显示你需要修复的内容。


本节课中,我们一起学习了如何在 pytest 中使用普通的 assert 语句来测试三种常见的数据结构:字典、列表和字符串。pytest 能够精确地指出测试失败的位置和原因,并提供了详细的差异对比,使你能够快速定位并修复问题。这是一个非常强大且实用的功能,适用于所有 Python 数据结构。

44:编写测试类 🧪

在本节课中,我们将学习如何在 Python 中使用 pytest 编写测试类。我们将了解测试类与测试函数的区别,并探索如何利用 setupteardown 方法来组织和管理测试代码。


概述

之前我们已经学习了测试函数,但尚未涉及测试类。测试类同样有用,它允许你将相关的测试分组在一起。我们将探讨决定使用测试函数还是测试类的因素,并通过一个实际示例来演示测试类的用法。

测试类简介

测试类提供了一种将多个测试组织在一起的方式。与测试函数相比,测试类允许你在每个测试运行前后执行一些预备和清理工作,这通过 setupteardown 方法实现。

示例:字符串转整数函数

我们有一个将字符串转换为整数的函数 string_to_int。例如,输入字符串 "1.1" 将返回整数 1。我们将为此函数编写测试。

以下是使用测试类的示例代码:

class TestStringToInt:
    @classmethod
    def setup_class(cls):
        print("运行类级别的 setup_class")

    @classmethod
    def teardown_class(cls):
        print("运行类级别的 teardown_class")

    def setup(self):
        print("运行测试前的 setup")

    def teardown(self):
        print("运行测试后的 teardown")

    def test_round_down(self):
        assert string_to_int("1.99") == 2

    def test_round_down_lesser_half(self):
        assert string_to_int("1.2") == 2

测试类中的方法解析

以下是测试类中各个方法的作用:

  • setup:在每个测试方法运行前执行。
  • teardown:在每个测试方法运行后执行,无论测试通过、失败还是抛出异常。
  • setup_class:在所有测试方法运行前执行一次。注意,它使用 cls 参数而非 self
  • teardown_class:在所有测试方法运行后执行一次。

运行测试并分析输出

在终端中运行测试命令:

pytest test_classes.py

当测试失败时,pytest 会显示详细的输出,包括 setupteardown 方法打印的信息。这有助于调试失败的原因。

测试执行顺序如下:

  1. 执行 setup_class(一次)。
  2. 对于每个测试方法:
    • 执行 setup
    • 执行测试方法。
    • 执行 teardown
  3. 执行 teardown_class(一次)。

使测试通过

为了演示测试通过的情况,我们将测试中的预期结果修改为正确的值:

def test_round_down(self):
    assert string_to_int("1.99") == 1

def test_round_down_lesser_half(self):
    assert string_to_int("1.2") == 1

再次运行测试,所有测试通过。此时,pytest 默认不会显示 setupteardown 的输出,以避免在一切正常时产生大量文本信息。

总结

本节课中,我们一起学习了如何使用 pytest 编写测试类。测试类允许你将相关测试分组,并通过 setupteardownsetup_classteardown_class 方法灵活地管理测试环境。当测试失败时,详细的输出有助于快速定位问题;而当所有测试通过时,简洁的输出则提供了清晰的反馈。

45:测试类与测试函数对比 🧪

在本节课中,我们将探讨在编写测试时,如何选择使用测试函数还是测试类。我们将分析两者的适用场景、优缺点,并通过具体示例帮助你理解如何根据实际需求做出选择。


概述

在之前的课程中,我们学习了使用 PyTest 编写测试的两种主要方式:测试函数测试类。你可能想知道,在实际项目中应该何时使用函数,何时使用类。本节将对比这两种方式,帮助你根据测试的组织结构、共享设置需求和个人偏好做出决策。

测试函数与测试类的对比

上一节我们介绍了测试函数和测试类的基本写法,本节中我们来看看如何在实际中选择使用它们。

测试函数的优势

测试函数通常更简单、更直接。对于大多数测试场景,尤其是独立的、不需要复杂设置或清理操作的测试,使用函数是推荐的做法。

以下是测试函数的一些特点:

  • 简单直接:函数定义清晰,易于编写和理解。
  • 无需样板代码:不需要像类那样定义 self 参数或继承特定类。
  • 适合独立测试:每个测试函数都是自包含的,适合测试单一功能。

例如,一个测试浮点数操作的函数可能如下所示:

def test_string_to_int_floats():
    result = int(float("1.99"))
    assert result == 1

测试类的优势

当需要将多个相关的测试分组,并且这些测试需要共享相同的设置或清理步骤时,使用测试类更为合适。

以下是测试类的一些适用场景:

  • 测试分组:可以将测试相同模块、功能或数据类型的测试组织在一起。
  • 共享设置:通过 setup_classteardown_classsetup_methodteardown_method 等方法,可以一次性为类中的所有测试执行准备和清理工作,避免代码重复。

例如,一个包含共享设置的测试类可能结构如下:

class TestFloatOperations:
    @classmethod
    def setup_class(cls):
        # 为整个测试类执行一次初始化,例如建立数据库连接
        cls.shared_resource = initialize_resource()

    def test_string_to_int_for_floats(self):
        result = int(float("1.99"))
        assert result == 1

    def test_another_float_operation(self):
        # 可以使用 cls.shared_resource
        assert self.shared_resource.is_valid()

如何选择:灵活性在于你

选择使用函数还是类,并没有强制规定,这完全取决于你如何组织代码以及项目的具体需求。

  • 如果你觉得函数更直观,并且测试用例之间相对独立,那么就坚持使用函数。
  • 如果你发现有一组测试都需要相同的数据库连接、临时文件或模拟对象,那么将它们放入一个测试类中,并利用类级别的设置方法,会是更高效、更清晰的做法。

此外,类的命名本身可以作为测试内容的一种提示。例如,一个名为 TestStringToIntFloats 的类,明确指出了它专注于测试“看起来像浮点数的字符串转换为整数”的行为。

总结

本节课中我们一起学习了测试函数与测试类的对比。核心要点如下:

  • 测试函数 简单直接,是大多数情况下的首选。
  • 测试类 适合需要分组共享设置/清理逻辑的测试场景。
  • 最终选择取决于你的组织需求个人偏好。PyTest 同时支持两者,提供了充分的灵活性。

对于初学者,可以从测试函数开始,随着测试套件变得复杂,再自然地引入测试类来更好地组织代码。关键在于编写出清晰、可维护的测试。

46:参数化测试 🧪

概述

在本节课中,我们将要学习 PyTest 框架中一个非常强大的功能——参数化测试。参数化测试听起来可能有些复杂,但它能极大地简化我们的测试代码,让测试用例的组织和执行更加清晰高效。我们将通过一个具体的例子,了解参数化测试的用途、优势以及如何实现它。


什么是参数化测试?

上一节我们介绍了单元测试的基本概念。本节中我们来看看一种更高效的测试编写方式。

参数化测试允许我们为同一个测试函数提供多组不同的输入数据和预期结果。这样,我们无需为每个相似的测试场景重复编写几乎相同的测试函数,只需编写一个测试函数,然后通过装饰器为其提供多组参数即可。

一个具体的例子

为了更好地理解,我们来看一个实际的工具函数。这个函数叫做 string_to_bool,它的功能是将特定的字符串转换为布尔值。

以下是该函数的核心逻辑,用代码描述:

def string_to_bool(value):
    if value in ('yes', 'y', ''):
        return True
    elif value in ('no', 'n'):
        return False

简单来说,当输入是 'yes''y' 或空字符串 '' 时,函数返回 True;当输入是 'no''n' 时,函数返回 False

传统测试方法的问题

现在,我们需要为返回 True 的几种情况编写测试。一种直观但不推荐的做法是使用 for 循环。

以下是传统测试方法的一个示例:

def test_string_to_bool():
    true_values = ['yes', 'y', '']
    for value in true_values:
        result = string_to_bool(value)
        assert result is True

这种方法存在一个主要问题:当测试失败时,信息不完整。如果循环中的第一个值 'yes' 就导致测试失败,PyTest 会立即停止执行这个测试函数。我们无法知道后面的 'y''' 是否也能通过测试。这不利于我们全面定位问题。

因此,在单元测试中应避免使用循环来测试多组数据。

参数化测试的解决方案

为了解决上述问题,我们可以使用 PyTest 的 @pytest.mark.parametrize 装饰器。它能让 PyTest 将一组参数视为多个独立的测试用例来执行。

以下是使用参数化测试的步骤:

首先,我们需要从 pytest 中导入必要的模块。

import pytest
from utils import string_to_bool

接着,我们使用 @pytest.mark.parametrize 装饰器来定义测试。装饰器接收两个主要参数:第一个是字符串,代表要传递给测试函数的参数名;第二个是一个列表,包含所有要测试的输入值。

@pytest.mark.parametrize("value", ['y', 'yes', ''])
def test_string_to_bool_is_true(value):
    result = string_to_bool(value)
    assert result is True

在这个例子中,"value" 是参数名,['y', 'yes', ''] 是三个要测试的输入值。PyTest 会为列表中的每个值单独运行一次 test_string_to_bool_is_true 函数。

参数化测试的优势

当我们运行测试时,优势就体现出来了。

在终端执行 pytest -v 命令,你会看到类似下面的输出:

collected 3 items

test_utils.py::test_string_to_bool_is_true[y] PASSED
test_utils.py::test_string_to_bool_is_true[yes] PASSED
test_utils.py::test_string_to_bool_is_true[] PASSED

关键点在于:PyTest 收集了3个测试项。尽管我们只写了一个测试函数,但参数化装饰器让它变成了三个独立的测试。如果其中某一个测试失败(例如输入 'n' 被意外加入了 True 值的列表),PyTest 会明确报告是哪个参数值导致了失败,而其他测试仍会继续执行并报告结果。

这样,我们就能获得完整、清晰的测试反馈。

编写完整的测试套件

利用参数化测试,我们可以快速为返回 False 的情况也编写测试。

以下是完整的测试用例示例:

# 测试返回 True 的情况
@pytest.mark.parametrize("value", ['y', 'yes', ''])
def test_string_to_bool_is_true(value):
    result = string_to_bool(value)
    assert result is True

# 测试返回 False 的情况
@pytest.mark.parametrize("value", ['n', 'no'])
def test_string_to_bool_is_false(value):
    result = string_to_bool(value)
    assert result is False

最终,我们只编写了两个测试函数,但 PyTest 会将其执行为五个独立的测试用例(3个 True + 2个 False),覆盖了所有输入情况。代码简洁,且测试报告详尽。


总结

本节课中我们一起学习了 PyTest 的参数化测试。

  • 核心概念:使用 @pytest.mark.parametrize 装饰器为单个测试函数提供多组输入参数,使每组参数都作为独立的测试用例运行。
  • 主要优势
    1. 避免代码重复:无需为相似逻辑编写多个几乎相同的测试函数。
    2. 获得完整反馈:某个参数测试失败不会影响其他参数的测试执行,测试报告会清晰指出每个参数的结果。
    3. 提升可读性:测试意图明确,输入和预期的对应关系一目了然。

参数化测试是编写高效、清晰单元测试的强大工具,强烈建议你在合适的场景中使用它。


47:编写实用测试 🧪

在本节课中,我们将回顾并总结关于编写实用测试的核心知识。我们将学习如何选择使用类还是函数来组织测试,以及如何利用 Pytest 的高级特性,如参数化测试。同时,我们也会巩固使用 assert 语句进行断言的方法,它能提供详尽的错误报告。


测试的组织:类 vs. 函数

上一节我们介绍了测试的基本概念,本节中我们来看看如何组织测试代码。在 Pytest 中,你可以选择使用函数或类来编写测试。

使用函数编写测试通常更简单直接,适合小型或独立的测试场景。而使用类(特别是遵循 unittest 模块风格的类)则有助于将相关的测试方法分组,便于管理。

以下是两种风格的简单示例:

函数形式测试:

def test_addition():
    assert 1 + 2 == 3

def test_string_concatenation():
    assert "Hello" + " " + "World" == "Hello World"

类形式测试:

class TestMathOperations:
    def test_addition(self):
        assert 1 + 2 == 3

    def test_multiplication(self):
        assert 2 * 3 == 6

选择哪种形式取决于你的项目结构和团队偏好。Pytest 对两者都支持良好。


利用 Pytest 高级特性:参数化测试

当你需要对同一段测试逻辑使用多组不同的输入数据进行验证时,手动编写多个测试函数会非常繁琐。这时,Pytest 的参数化测试功能就非常有用了。

参数化测试允许你使用一个装饰器来定义多组测试数据,Pytest 会自动为每组数据生成一个独立的测试用例。

以下是使用 @pytest.mark.parametrize 装饰器的示例:

import pytest

@pytest.mark.parametrize("input_a, input_b, expected", [
    (1, 2, 3),
    (5, -1, 4),
    (0, 0, 0)
])
def test_addition_with_params(input_a, input_b, expected):
    assert input_a + input_b == expected

在这个例子中,Pytest 会运行 test_addition_with_params 三次,每次使用一组不同的 (input_a, input_b, expected) 数据。如果任何一次断言失败,报告会明确指出是哪一组数据导致了问题。


使用 Assert 进行断言与错误报告

在测试中,断言是验证代码行为是否符合预期的核心手段。Pytest 支持使用 Python 原生的 assert 语句,这比某些测试框架专用的断言方法更灵活。

assert 语句后的条件为 False 时,测试失败,Pytest 会捕获这个异常并生成详细的报告。这份报告会显示断言失败的位置、使用的变量值以及表达式本身,极大地帮助开发者定位问题根源。

例如,对于以下测试:

def test_divide():
    result = 10 / 2
    assert result == 5

如果 result 意外地等于其他值(比如由于代码修改引入了 bug),Pytest 的报告会清晰地显示 assert result == 5 失败了,并且 result 的实际值是多少。


核心要点总结

本节课中我们一起学习了编写实用测试的几个关键方面:

  1. 测试组织:了解了根据情况选择使用函数或类来组织测试用例。
  2. 参数化测试:掌握了使用 @pytest.mark.parametrize 装饰器来高效测试多组数据,避免代码重复。
  3. 断言与调试:巩固了使用 assert 语句进行断言的方法,并认识到其生成的详细错误报告对于快速定位和修复问题至关重要。

这些关于 Pytest 和测试的通用知识非常关键。现在,你能够编写测试、分析测试失败报告,并精准定位问题所在。这确保了你所做的修复能真正解决问题,而不会引入新的错误。

48:测试与故障处理 🐛🔧

在本节课中,我们将学习如何更有效地编写测试和进行调试。深入了解所使用的工具,能让你在遇到故障时更加熟练地应对。我们将涵盖多种内容,包括使用Python调试器PDB,以及像fixtures这样的工具,它能帮助你编写更健壮的测试,并自动处理一些底层问题,例如自动清理每次测试后不需要的文件。掌握这些知识,将为你在编写和调试测试时打下坚实的基础。


深入掌握工具 🛠️

上一节我们介绍了测试的重要性,本节中我们来看看如何通过掌握核心工具来提升测试与调试的效率。

更深入地了解你正在使用的工具,将使你在发生故障时更加熟练。


使用Python调试器(PDB) 🐞

当测试失败或代码出现异常时,我们需要定位问题根源。Python内置的调试器PDB是一个强大的交互式工具。

以下是使用PDB进行调试的基本步骤:

  1. 导入与设置断点:在代码中你想开始调试的位置插入import pdb; pdb.set_trace()
  2. 运行程序:当程序执行到该行时,会自动暂停并进入PDB交互式命令行。
  3. 常用命令
    • n (next): 执行下一行代码。
    • s (step): 进入函数内部。
    • c (continue): 继续执行,直到下一个断点或程序结束。
    • l (list): 显示当前行附近的代码。
    • p <变量名> (print): 打印变量的当前值。
    • q (quit): 退出调试器。

通过PDB,你可以逐行检查代码执行状态,观察变量值的变化,从而精准定位逻辑错误。


利用Fixtures编写健壮测试 🧪

仅仅发现错误还不够,我们还需要能预防错误和简化测试环境的工具。Fixtures是pytest框架中的一个核心概念,它提供了一种优雅的方式来设置测试的初始状态和清理工作。

Fixture是一个函数,用@pytest.fixture装饰器标记。它可以在测试函数运行前提供所需的数据或状态,并在测试结束后执行清理。

以下是一个fixture的简单示例:

import pytest
import tempfile
import os

@pytest.fixture
def temporary_file():
    # 测试前:创建一个临时文件
    temp = tempfile.NamedTemporaryFile(delete=False, mode='w')
    temp.write('Initial data')
    temp.close()
    file_path = temp.name

    yield file_path  # 将文件路径提供给测试函数使用

    # 测试后:清理临时文件
    os.unlink(file_path)

def test_file_operations(temporary_file):
    # 测试函数接收fixture返回的值作为参数
    with open(temporary_file, 'r') as f:
        content = f.read()
    assert content == 'Initial data'
    # 测试结束后,fixture会自动删除临时文件

使用fixtures的主要优势包括:

  • 代码复用:多个测试可以共享同一个设置和清理逻辑。
  • 可靠性:确保每次测试都在一个干净、一致的环境中开始。
  • 自动化清理:自动处理诸如关闭数据库连接、删除临时文件等任务,避免资源泄漏或测试间相互干扰。

正如课程所述,fixtures能帮你处理所有因使用辅助工具可能带来的底层问题,例如自动移除每个单次测试中不需要的文件。


总结 📚

本节课中我们一起学习了提升测试与调试效率的两个关键技能。

我们首先介绍了如何使用Python调试器PDB来交互式地诊断代码中的问题。接着,我们探讨了如何利用pytest的fixtures来构建更清晰、更健壮且易于维护的测试,它能自动管理测试前后的状态,让开发者专注于测试逻辑本身。

掌握这些工具和方法,将使你能够更自信地应对开发过程中出现的故障,并编写出质量更高的代码。

49:测试失败输出分析 🔍

在本节课中,我们将学习如何详细分析 pytest 测试框架输出的失败信息。理解这些输出对于快速定位和修复代码中的问题至关重要。

概述

我们已经初步了解了 pytest 的输出。现在,我们将深入细节,通过一个具体的例子来学习如何解读测试失败时的输出信息,从而高效地调试代码。

详细分析测试失败输出

我们以一个名为 u_module.py 的 Python 文件为例,其中包含一些工具函数。我们将测试其中的 string_to_int 函数,并故意制造一些失败案例来观察输出。

以下是三个测试函数,其中一些会引发问题:

# test_failure_output.py
def test_string_to_int():
    # 一些会失败的测试用例
    pass

def test_another_case():
    # 另一个测试用例
    pass

def test_yet_another():
    # 又一个测试用例
    pass

在终端中运行以下命令来执行测试并查看失败输出:

pytest test_failure_output.py

运行后,我们会看到一个失败和两个通过的测试。让我们快速看一下这里发生了什么。

解读输出信息

输出首先会告诉你执行的路径、收集到的测试项数量,以及使用的 pytestPython 版本(例如 Python 3.9)。

接着,它会总结在这个特定的测试文件中,有一个失败(F)和两个通过(.),完成度为100%。

然后,它会进入失败详情。其中一个关键点是,它会展示被测试代码的表示。例如,对于 string_to_int 函数,你可以看到它需要一个字符串参数,但同时也会显示导致测试失败的实际值。这非常有用,因为开发者经常需要查看引发失败的具体值。

例如,输出可能显示值为 “14, 44”,这显然是有问题的。

定位错误源头

输出会精确指出错误发生的行号,并用特殊字符标记出来。错误信息可能是 ValueError: could not convert string to float,表明转换完全无效。

需要注意的是,错误并非发生在测试文件本身,而是发生在另一个名为 ut_sub.py 的文件中(例如第11行)。异常类型是 ValueError

Python 在处理上述异常时,可能会因为函数捕获并处理了该异常,然后抛出了另一个异常,最终导致整个测试在此失败。输出会明确指出,测试 test_string_to_inttest_failure_output.py 文件的第5行失败。

即使没有详细的值信息,我们也能根据行号(第5行)定位到问题。同时,它也会关联到 ut_sub.py 的第11行。如果我们查看 ut_sub.py 的第11行,可能会发现是 int(float(string.replace(...))) 这行代码出了问题。这些就是我们需要关注的问题区域。

错误处理与重新抛出

回到输出,我们可以看到类似的情况再次发生。最后,它抛出了一个 RuntimeError。这是因为代码捕获了 TypeErrorValueError,然后重新抛出了一个更有帮助的错误信息,例如“无法转换为整数”。

在输出的最后,会有一个简短的总结。这样设计的原因是,想象一下如果有1000个测试失败,你很可能需要查看这个简短总结来了解如何处理大量的失败测试输出。

增加输出详细程度

你可以通过增加 -v 参数来提高输出的详细程度(例如 -vv-vvv)。这样有时能获得更多上下文信息,并且通过的测试也会被包含在输出中,而不仅仅是失败的测试。

例如,使用更详细的输出,你可以清楚地看到所有测试都来自 test_failure_output.py 文件,每个测试的名称以及它是通过还是失败。

以下是增加详细程度后可能看到的信息示例:

pytest test_failure_output.py -vv

总结

本节课中,我们一起学习了如何分析 pytest 的测试失败输出。我们了解了输出结构如何帮助定位问题代码和具体失败值,如何通过行号追踪错误源头到原始文件,以及如何利用简短总结和详细模式来高效处理大量测试结果。掌握这些技巧将极大提升你调试和修复代码的效率。

50:使用pdb进行Python调试 🐛

在本节课中,我们将学习如何使用Python内置的调试器——pdb,来动态地检查和调试代码中的问题。我们将通过一个具体的测试失败案例,演示如何启动pdb、查看上下文、检查变量,并定位错误的根源。


上一节我们介绍了测试失败的基本情况,本节中我们来看看如何使用pdb进行交互式调试。

我们将再次使用 test_score_failure_output 文件,并运行测试来查看失败信息,目标是调试这里出现的问题。

你可以使用pdb,它是Python调试器的简称。Python调试器允许你动态地进入代码内部,查看代码执行时的具体情况。除了你正在使用的编辑器(本例中使用VS Code,它本身具备调试测试和代码的功能)外,还有几种方法可以做到这一点。如果你使用pytest,你也可以设置调试器。

调试器可以通过一个特殊的标志来启动,我将向你展示这个特殊标志是什么。

因此,不是直接运行 pytest(我们知道它会导致一些测试失败输出),而是可以明确地输入:

pytest --pdb

这个命令将在第一个测试失败时暂停,并将你带入一个带有Python调试器的交互式终端。

我将运行这个命令。

然后它开始运行,并在第一个失败处提示“entering PDB”,即进入了Python调试器。你可以看到这里抛出了一个运行时错误。

以下是你可以尝试的一些操作。我认为 h 是帮助命令。没错,这是文档命令。你手头有所有这些命令可供使用。

我通常喜欢用 l 来列出我当前所在的位置。你可以看到我正好在发生运行时错误的地方,因此我可以访问所有这些变量。让我们看看 integer 是否存在。如果我回头看看我们的代码,看看我能访问什么,我会查看这里的函数,我有 string 变量。如果我在这里输入 string,你可以看到 string 的值是 14,044

l 命令可以给我更好的上下文。如果我传入行号(这是pdb在任何地方都通用的工作方式,它只是给你提供了更好的调试能力),比如输入 l 10,它会给我第10行更好的上下文。

我可以看到这里它试图执行字符串替换。integer 失败了,它实际上没有正常工作。因为如果我查看 integer,它没有被定义,所以它甚至没有走到 integer 那一步就失败了。它试图再次设置,也失败了,并导致了运行时错误。

现在,我们想知道那个 float(string.replace(...)) 操作可能发生了什么。让我看看是否可以执行 float(string),其中 string 是输入。我们来调试一下。哦,明白了,ValueError: could not convert string to float

现在我对问题可能出在哪里有了一个想法,因为它试图将字符串转换为浮点数。那么我将需要找出一种健壮的方法来转换这些数据。我认为即使我在这里处理,也可能遇到同样的问题。

因此,也许一种方法是去掉那个逗号,因为它被四舍五入了,这无关紧要,然后取第一个部分。这可能是一个解决方案。

让我试试看。所以 solution = string.split(','),然后取第一个项。那么 solution 就会是 14,而 14 既可以用于 float 转换,也可以用于 int 转换。这样就可以了。

我对我想达到的目标有了一个想法。如果我想执行下一步,我可以输入 n,或者输入 c 来继续执行。

现在,如果我想精确地定位到这个失败发生的地方,我可以这样做:在代码中,我可以输入 import pdb,然后输入 pdb.set_trace(),这将在那里设置一个断点。

但这样做的一个特定问题是,它会为每一个测试都停在这里。我有三个测试,它会对每一个都这样做。现在我将移除我的 --pdb 标志,使用 -s 标志,因为我不想……我想告诉pytest,顺便说一句,我将以交互方式运行。它收集了三个测试项,这不是第一个失败的测试。

第一个测试失败了,第二个没有,第三个也没有。但你看到它把我带到了这三个测试中的每一个开始处。现在如果我输入 l,你可以看到它正好在第9行。现在它把我带到了测试代码的下一行。如果我输入 stringstring 的值是 14,044

以上就是两种可以使用pdb进行调试的方法,可以查看变量状态。你既可以在测试本身中设置断点,也可以在受测的代码中设置断点。


本节课中我们一起学习了如何使用pdb调试器。我们介绍了通过 pytest --pdb 在测试失败时进入调试模式,以及使用 pdb.set_trace() 在代码中设置断点。在调试会话中,我们使用了 l 查看代码上下文,检查了变量值,并定位了字符串转换错误的原因。这些技能对于诊断和修复代码中的问题至关重要。

51:其他pytest运行器选项 🚀

在本节课中,我们将学习几个实用的 pytest 命令行工具标志,以及如何通过插件来增强测试功能。这些技巧能帮助你更高效地管理和运行测试套件。

上一节我们介绍了 pytest 的基础用法,本节中我们来看看一些进阶的运行选项。


除了查阅官方文档,查看帮助菜单也是一个好习惯。pytest 提供了许多不同的命令行选项。

以下是查看帮助菜单的方法:

pytest --help

帮助菜单中列出了大量选项。虽然快速滚动难以捕捉所有细节,但强烈建议你浏览一遍,寻找感兴趣的功能。


我经常使用 --collect-only 选项来排查测试收集阶段的问题。该命令只会显示收集到的测试列表,而不会执行任何测试。

以下是使用 --collect-only 的示例:

pytest --collect-only

执行后,它会列出所有收集到的测试。例如,它可能显示收集到了一个位于 test_failure_output.py 文件中的名为 test_score_features 的测试(我们将在下一节介绍它),以及其他测试。

目前,我们的测试套件包含多个文件,共有六个测试。如果直接运行 pytest,会看到一个测试失败,五个测试通过。


当测试套件非常庞大时,你可能希望在第一个失败出现时就停止运行,以便快速定位问题。这时可以使用 -x 标志。

以下是使用 -x 标志的示例:

pytest -x

使用该标志后,pytest 会在遇到第一个失败后立即停止。输出结果将只聚焦于首次失败发生的地方,使调试更加集中。在本例中,失败恰好发生在第一个测试,因此后续测试都没有运行。


pytest 另一个强大的特性是支持插件扩展。有一个插件特别适合在拥有大量测试时使用,它允许以分布式方式并行运行测试,从而显著缩短总执行时间。

以下是安装该插件的方法:

pip install pytest-xdist

安装后,你可以使用 -n 标志来指定并行运行的进程数量。

以下是使用该插件并行运行测试的示例:

pytest -n 4

这个命令会创建4个测试运行器实例来同时执行测试。虽然当前示例因为存在失败而输出不够清晰,但你可以看到它创建了网关并开始并行执行测试。

为了更直观地展示效果,让我快速修复之前的测试失败,然后重新运行。

修复后,再次运行并行测试命令。虽然我们只有六个测试,但你可以想象,如果有成百上千个测试,并行执行将极大地提升速度。


本节课中我们一起学习了几个有用的 pytest 命令行标志:用于检查测试收集的 --collect-only,用于遇到失败即停止的 -x,以及用于并行加速测试执行的 pytest-xdist 插件。掌握这些工具能帮助你更高效地管理和运行测试。

52:pytest夹具 🧪

在本节课中,我们将学习pytest框架中的一个强大功能——夹具(fixtures)。我们将了解什么是夹具,为什么它们比传统的测试设置方法更优越,以及如何在测试中简单地使用它们。


什么是pytest夹具?

上一节我们介绍了基本的测试结构。本节中我们来看看如何让测试代码更健壮、更可移植。pytest夹具可以看作是pytest提供的插件或工具。使用它们的唯一要求,就是在测试函数或方法中将其声明为一个参数。

夹具的核心思想是,由pytest框架来管理测试所需资源的生命周期(如创建、清理),而不是由测试代码自己手动处理。

一个传统测试的例子

首先,我们来看一个没有使用夹具的测试例子,并指出其潜在问题。

我们有一个简单的函数 write_boolean,它将一个字符串(“true”或“false”)转换为布尔值,并写入指定的文件路径。

def write_boolean(a_string, a_path):
    value = strtobool(a_string)
    with open(a_path, 'w') as f:
        f.write(str(value))

以下是测试这个函数的类。它使用了 setUp 方法,该方法在每个测试方法运行前执行,用于清理可能存在的旧测试文件。

import os
from unittest import TestCase

class TestWriteBooleans(TestCase):
    def setUp(self):
        # 每次测试前,如果文件存在就删除它
        if os.path.exists("/tmp/test_value"):
            os.remove("/tmp/test_value")

    def test_write_yes(self):
        write_boolean("yes", "/tmp/test_value")
        with open("/tmp/test_value") as f:
            contents = f.read()
        self.assertEqual(contents, "True")

    def test_write_n(self):
        write_boolean("n", "/tmp/test_value")
        with open("/tmp/test_value") as f:
            contents = f.read()
        self.assertEqual(contents, "False")

这个测试结构本身没有问题,但底层代码不够健壮:

  1. 路径硬编码:它依赖 /tmp 目录,这个目录在Windows系统上不存在,可能导致测试失败。
  2. 并发问题:如果多个测试并行运行(例如使用 pytest -n auto),它们可能会同时读写同一个文件,导致不可预知的失败。
  3. 手动清理:我们需要在 setUp 中手动管理文件的清理工作。

使用pytest夹具改进测试

现在,我们使用pytest内置的 tmpdir 夹具来解决上述所有问题。使用夹具非常简单:只需在测试方法中将其作为参数名声明即可。

以下是使用 tmpdir 夹具重写的测试:

def test_write_yes_with_fixture(tmpdir):
    # tmpdir 是一个夹具,pytest会自动注入一个唯一的临时目录对象
    file_path = tmpdir.join("test_value")
    write_boolean("yes", str(file_path))
    with open(file_path) as f:
        contents = f.read()
    assert contents == "True"
    # 为了演示,打印出临时路径
    print(f"The path from tmpdir fixture is: {file_path}")
    # 此行会失败,仅用于在pytest输出中展示路径
    assert False

def test_write_n_with_fixture(tmpdir):
    file_path = tmpdir.join("test_value")
    write_boolean("n", str(file_path))
    with open(file_path) as f:
        contents = f.read()
    assert contents == "False"

运行这个测试时,pytest会:

  1. 自动创建一个唯一的、临时的目录供测试使用。路径类似于 /private/var/folders/.../pytest-of-username/pytest-13/test_write_yes_with_fixture0/test_value。这种唯一性完美避免了并发冲突。
  2. 在测试结束后,自动清理这个临时目录及其所有内容。我们无需编写任何清理代码。
  3. 在不同操作系统上提供一致的行为,因为路径是由pytest运行时环境管理的。

pytest夹具的强大之处

tmpdir 只是pytest众多内置夹具中的一个。pytest夹具生态系统非常丰富。

以下是你可以探索的一些有用夹具:

  • capsys:用于捕获 stdoutstderr 的输出。
  • monkeypatch:用于在测试中动态设置/修改属性、字典项、环境变量等。
  • caplog:用于捕获和检查日志记录。
  • request:提供对当前测试请求的访问,可用于更高级的夹具编写。

你可以通过查阅 pytest官方文档 来发现更多内置夹具并学习如何创建自定义夹具。


本节课中我们一起学习了pytest的夹具(fixtures)功能。我们看到了传统测试设置方法在路径硬编码和并发安全方面的局限性,并通过 tmpdir 夹具演示了如何更优雅、更健壮地管理测试资源。关键在于,夹具由pytest框架自动管理其生命周期,你只需在测试函数中声明所需的夹具参数即可使用。这极大地提高了测试代码的可移植性、安全性和简洁性。

53:课程回顾-测试故障处理 🐛

在本节课中,我们将回顾并学习如何调试和修复失败的测试。我们将探讨如何使用 pytest 的高级功能来定位问题,并理解测试失败的根本原因。


调试测试有时会变得棘手,但我们能够看到 pytest 如何帮助我们,以及如何在终端中使用 pytest 的一些更高级的标志。

所有这些都将帮助您更熟练地修复可能失败的测试,并探究为什么某些值可能无法让测试通过。

因此,您现在已准备好处理更复杂的测试,并且希望您能顺利编写更多可能有用的测试。


上一节我们介绍了测试可能失败的各种场景,本节中我们来看看具体的调试工具和策略。

以下是 pytest 中一些用于调试和故障处理的核心标志:

  • -v--verbose:输出更详细的测试执行信息。
  • --tb=style:控制回溯信息的显示样式。例如,--tb=short 显示简短回溯,--tb=no 不显示回溯。
  • -x:在第一个测试失败后立即停止执行。
  • --lf--last-failed:只重新运行上一次失败的测试。
  • -k EXPRESSION:只运行名称与给定表达式匹配的测试。

为了更直观地理解,我们可以通过一个简单的代码示例来看如何使用这些标志。假设我们有一个测试文件 test_example.py

def test_addition():
    assert 1 + 1 == 2  # 这个测试会通过

def test_subtraction():
    assert 5 - 3 == 3  # 这个测试会失败,因为 5-3=2

在终端中,我们可以使用以下命令进行调试:

# 运行所有测试并显示详细信息
pytest -v

# 只运行上一次失败的测试
pytest --lf

# 只运行名称包含 ‘subtraction’ 的测试,并在失败后停止
pytest -k subtraction -x


本节课中我们一起学习了如何利用 pytest 提供的工具来高效地调试失败的测试。通过使用不同的命令行标志,我们可以快速定位问题、过滤测试用例并控制输出信息,从而提升测试修复的效率。掌握这些技能是处理复杂测试套件和编写健壮代码的重要基础。

54:pandas基础使用 📊

在本节课中,我们将初步了解pandas库,学习如何加载数据、初步可视化数据,并探索多种用于导出和写入数据的方法。我们还将了解pandas为何如此灵活,以及这种灵活性为何有益。

1. 加载数据

上一节我们介绍了本课程的目标,本节中我们来看看如何使用pandas加载数据。pandas提供了多种函数来读取不同格式的数据文件。

以下是几种常见的加载数据的方法:

  • 读取CSV文件:使用 pd.read_csv('file.csv')
  • 读取Excel文件:使用 pd.read_excel('file.xlsx')
  • 读取JSON文件:使用 pd.read_json('file.json')

2. 数据初步探索与可视化

在成功加载数据之后,我们需要对数据有一个基本的了解。pandas提供了快速查看数据和生成简单图表的方法。

以下是几个用于初步探索数据的常用操作:

  • 查看数据前几行:使用 df.head()
  • 查看数据基本信息:使用 df.info()
  • 生成简单统计摘要:使用 df.describe()
  • 绘制简单图表:使用 df.plot()

3. 导出与写入数据

处理完数据后,我们经常需要将结果保存下来。pandas同样支持将数据写入多种格式的文件。

以下是几种常见的导出数据的方法:

  • 写入CSV文件:使用 df.to_csv('output.csv', index=False)
  • 写入Excel文件:使用 df.to_excel('output.xlsx', index=False)
  • 写入JSON文件:使用 df.to_json('output.json')

4. pandas的灵活性与优势

pandas的灵活性体现在它能够轻松处理各种数据源和格式,无论是读取、处理还是写入。其统一的API设计使得数据操作流程化,极大地提高了数据分析和机器学习项目中的数据预处理效率。

本节课中我们一起学习了pandas的基础使用,包括加载数据、初步探索与可视化数据、以及导出数据。理解了这些基本操作,是后续进行更复杂数据分析和机器学习运维工作的重要第一步。

55:Pandas 简介 🐼

在本节课中,我们将要学习一个名为 Pandas 的强大 Python 库。Pandas 是数据科学和机器学习领域的基础工具,它可以帮助我们轻松地加载、查看、清理和操作数据集。


什么是 Pandas?📊

Pandas 是一个用于数据分析和操作的 Python 库。它功能强大,应用广泛,提供了各种不同的辅助工具和实用程序来处理数据。

Pandas 不随 Python 自带,因此需要单独安装。


如何安装 Pandas?🔧

上一节我们介绍了 Pandas 是什么,本节中我们来看看如何安装它。

以下是安装步骤:

  1. 创建一个虚拟环境(推荐)。
  2. 激活该虚拟环境。
  3. 使用 pip 安装 pandas 包。

一个典型的安装命令序列如下(在命令行中执行):

# 创建并激活名为‘venv’的虚拟环境(名称可自定义)
python3 -m venv venv
source venv/bin/activate  # 在 Windows 上是 venv\Scripts\activate

# 在激活的虚拟环境中安装 pandas
pip install pandas

请确保为你的项目维护一个 requirements.txt 文件或类似的文件来声明依赖项,以便于后续管理和复现环境。

提示:如果在安装过程中遇到问题,或需要在不同系统上安装,请参考 Pandas 官方安装文档

在 Jupyter Notebook 中使用时,请确保已选择正确的内核(即已安装 pandas 的虚拟环境)。


开始使用 Pandas 🚀

你已经安装了 Pandas,并且 Jupyter Notebook 正在使用正确的内核和依赖项运行。现在,让我们开始使用它。

首先,我们需要导入 Pandas 库。一个非常常见的做法是使用别名 pd 来导入它。

import pandas as pd

这种 import ... as ... 的语法被称为“导入别名”。几乎所有教程,包括 Pandas 官方文档,都使用 pd 作为别名。


核心概念:DataFrame 📈

上一节我们导入了 Pandas,本节中我们来看看它的核心数据结构——DataFrame。

你可以将 DataFrame 理解为一个多维数组,或者更直观地,想象成一个电子表格。它包含行、列以及单元格中的数据。

让我们通过一个例子来创建第一个 DataFrame。

假设我们记录了从周一到周三拥有的苹果、香蕉和橙子的数量。

以下是创建此 DataFrame 的步骤:

  1. 准备数据(一个列表的列表)。
  2. 定义列名。
  3. 定义行索引(即行标签)。
# 准备数据:一个列表的列表,每个子列表代表一行
data = [
    [1, 2, 3],   # 星期一的数量
    [4, 5, 6],   # 星期二的数量
    [7, 8, 9]    # 星期三的数量
]

# 定义列名
columns = ['apples', 'bananas', 'oranges']

# 定义行索引(行标签)
index = ['Monday', 'Tuesday', 'Wednesday']

# 使用 pd.DataFrame() 创建 DataFrame
df = pd.DataFrame(data=data, columns=columns, index=index)

# 打印查看 DataFrame
print(df)

运行以上代码,你将看到一个整洁的表格输出:

          apples  bananas  oranges
Monday         1        2        3
Tuesday        4        5        6
Wednesday      7        8        9

Pandas 将我们原始的、不易阅读的列表数据,转换成了一个结构清晰、像电子表格一样的表格。这就是 Pandas 的主要作用:加载数据,并以一种有意义、易于查看和操作的方式呈现它

DataFrame 是 Pandas 中你将主要操作的对象。Pandas 还有其他数据结构(如 Series),但在本入门教程中,我们将主要关注 DataFrame。


总结 🎯

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

  1. Pandas 是什么:一个用于数据操作和分析的 Python 库。
  2. 如何安装 Pandas:通过 pip 在虚拟环境中安装。
  3. 如何导入 Pandas:使用 import pandas as pd 是标准做法。
  4. 核心数据结构 DataFrame:它类似于电子表格,是存储和操作数据的主要载体。我们通过一个水果库存的例子创建了第一个 DataFrame。

Pandas 的功能远不止于此,它还能帮助你清理数据、转换数据、进行计算分析甚至绘图。在接下来的课程中,我们将深入探索这个强大库的更多优秀特性。

56:将数据加载到 Pandas 📊

在本节课中,我们将学习如何将数据加载到 Pandas 库中。Pandas 提供了多种灵活的数据加载方式,能够处理来自不同来源的数据,包括远程 URL、本地文件以及多种数据格式。


概述

Pandas 是一个非常灵活的库,其设计者为其内置了多种数据加载功能。本节将介绍几种将数据加载到 Pandas 的主要方法,涵盖从远程 GitHub 仓库加载 CSV 文件、从本地文件系统加载 CSV 和 JSON 文件,以及处理其他格式如 Excel 等。


从远程 URL 加载数据

上一节我们介绍了 Pandas 的灵活性,本节中我们来看看如何从远程 URL(例如 GitHub 仓库)加载数据。这在数据存储于在线仓库时非常有用。

以下是具体步骤:

  1. 在 GitHub 上找到目标数据文件(例如一个 CSV 文件)。
  2. 点击“Raw”按钮以获取文件的原始内容 URL。
  3. 确保 URL 指向的是原始内容(通常域名包含 raw.githubusercontent.com),而不是 GitHub 的预览页面。
  4. 使用 pd.read_csv() 函数并传入该 URL。
import pandas as pd

# 使用从 GitHub 复制的原始文件 URL
url = "https://raw.githubusercontent.com/用户名/仓库名/分支名/wine_ratings.csv"
df = pd.read_csv(url, index_col=0) # index_col=0 指定第一列为索引
print(df.head(10))

执行上述代码后,数据将被成功加载为一个 Pandas DataFrame,并显示前 10 行。DataFrame 通常缩写为 df,但你可以使用更具描述性的变量名,例如 wine_ratings


从本地文件加载数据

除了远程源,Pandas 也能轻松加载本地存储的数据文件。这是最常见的数据处理场景之一。

以下是加载本地 CSV 文件的示例:

# 假设 CSV 文件与笔记本在同一目录下
df_local = pd.read_csv('world_championship_qualifier.csv')
print(df_local)
# 或者使用 .head() 方法查看前几行
print(df_local.head())

代码会读取名为 world_championship_qualifier.csv 的本地文件,并将其内容加载到 DataFrame 中。你可以看到列名(如 rank, name, nationality)和数据都被正确解析。


加载其他格式的数据

Pandas 的强大之处在于它能处理多种数据格式。读取不同格式的文件,通常只需更换对应的读取函数。

以下是加载本地 JSON 文件的示例:

# 读取本地 JSON 文件
df_json = pd.read_json('data.json')
print(df_json.head())

即使数据源是 JSON 格式,Pandas 在幕后也会将其转换为标准的行列结构进行展示,因此其显示形式与 CSV 数据类似。

此外,Pandas 还支持从多种其他格式加载数据,例如:

  • Excel 文件:使用 pd.read_excel('file.xlsx')
  • 剪贴板:使用 pd.read_clipboard() 可以直接从剪贴板读取表格数据。
  • XML/HTML:Pandas 也能解析网页表格。

这体现了 Pandas 库处理数据源的强大能力和灵活性。


总结

本节课中我们一起学习了将数据加载到 Pandas 的几种核心方法。我们掌握了如何从远程 GitHub URL 加载 CSV 数据,如何读取本地的 CSV 和 JSON 文件,并了解到 Pandas 支持包括 Excel、剪贴板在内的多种数据格式。关键在于根据数据源的位置和格式,选择合适的 pd.read_*() 函数。

57:从Pandas数据框写入数据 📤

在本节课中,我们将学习如何将Pandas数据框中的数据写入到不同的文件格式和目的地。我们将涵盖写入CSV、HTML、Markdown以及剪贴板等常用方法。


上一节我们介绍了如何将数据加载到Pandas数据框中。本节中,我们来看看如何将数据框中的数据写入到其他目的地。

我们从Pandas库本身加载数据。当我们从Pandas写入数据时,操作对象是数据框。

我们将从一个数据框开始操作。这里使用一个本地的CSV文件,内容是1996年世界田径锦标赛资格赛的结果。首先导入Pandas。

import pandas as pd

接着,创建数据框并查看其内容。

df = pd.read_csv('world_championship_qualifier.csv')
print(df)

数据已成功加载为数据框对象。现在可以对其进行操作。

你可能会想知道如何将数据写入其他文件或目的地。

与加载数据类似,你可以将数据写入不同的目的地,例如剪贴板、CSV文件、Python字典、Excel电子表格,甚至是Parquet格式(一种存储大型数据的强大方式)。所有这些选项都可用。

我想向你展示如何将数据框写入HTML格式并查看效果。

运行以下代码将数据框转换为HTML。

df.to_html('output.html')

HTML文件已生成。它创建了一个包含表头、表体和行列数据的HTML表格。在浏览器中本地打开此文件,可以完美加载。

如果你在处理HTML,并希望将数据从一处转换到另一处,to_html方法非常有效。

这只是众多数据导出方式中的一种。现在,我想展示如何将数据复制粘贴到其他格式,特别是Markdown。

你可能会遇到一个关于tabulate依赖项的报错。确保已安装此依赖。

从Pandas的io.clipboards模块导入to_clipboard函数。这意味着对象将被粘贴到系统剪贴板上。

from pandas.io.clipboards import to_clipboard

告诉数据框需要以Markdown格式导出,并调用to_clipboard方法。

df.to_markdown()
to_clipboard(df.to_markdown(), excel=False)

数据框现在已复制到剪贴板。创建一个新单元格并粘贴,即可看到Markdown格式的数据。

点击检查按钮,可以看到Markdown已成功创建。这是一种非常实用的方法,因为Pandas在数据导出方面非常灵活。


本节课中我们一起学习了如何将Pandas数据框中的数据写入多种格式,包括HTML和Markdown,以及如何利用剪贴板进行快速数据转移。Pandas提供了灵活多样的数据导出选项,方便我们在不同场景下使用。

58:使用 Pandas 进行探索性数据分析 📊

在本节课中,我们将学习如何使用 Pandas 库对数据进行探索性分析。我们将涵盖如何加载数据、查看数据概览、排序、筛选以及进行分组计算等基本操作。


数据加载与初步查看

首先,我们需要创建一个 Pandas DataFrame 并加载数据集。我们将从一个之前展示过的 GitHub 仓库中读取数据。

import pandas as pd

# 加载数据集,指定第一列为索引
df = pd.read_csv('wine_data.csv', index_col=0)

加载数据后,最常见的操作之一是使用 .head() 方法查看数据的前几行。你可以传入一个可选参数来指定要查看的行数。

# 查看前15行数据
df.head(15)

运行上述代码,你将看到数据的前15行,索引从0开始。如果不传入参数,默认会显示前5行。

数据集中包含以下列:name(酒名)、grape(葡萄品种)、region(产区)、variety(类型,如红葡萄酒或白葡萄酒)、rating(评分)和 notes(备注)。这是一个葡萄酒数据集。


数据描述与信息概览

接下来,我们可以使用 .describe() 方法来获取数据的统计描述。

# 获取数据的统计描述
df.describe()

执行后,你会看到一些有用的统计数字。例如,rating 列的最小值是85,最大值是99,平均值约为91,75%分位数是92。这有助于我们了解数据的分布情况。

此外,我们还可以使用 .info() 方法来获取数据集的更多信息。

# 获取数据集的详细信息
df.info()

.info() 方法会显示数据集的条目数、索引范围、列数、每列的数据类型以及内存使用情况。例如,它可能显示有3200个条目,索引从0到3199,共有6列,其中包含浮点数和对象类型,内存使用量约为1.8 MB。


数据排序与筛选

在探索性分析中,我们经常需要根据特定条件对数据进行排序。例如,我们可以按评分降序排列数据。

# 按评分降序排序
df.sort_values(by='rating', ascending=False)

执行上述代码后,数据将按评分从高到低排列。你可以看到评分最高的葡萄酒,例如来自法国和西班牙的葡萄酒,评分为99分。


处理缺失值与删除列

在数据集中,我们注意到 grape 列存在缺失值(用 NaN 表示)。我们可以删除这一列。

# 删除 ‘grape’ 列
df.drop('grape', axis=1, inplace=True)

axis=1 表示删除列,inplace=True 表示直接在原 DataFrame 上进行修改,而不创建新的副本。删除后,再次使用 .describe() 方法查看数据,你会发现 grape 列已不存在。


分组计算与聚合分析

最后,我们可以进行分组计算,例如计算每个产区的平均评分。

# 按产区分组并计算平均评分
df.groupby('region')['rating'].mean()

执行上述代码,你将看到每个产区的平均评分。例如,意大利产区的平均评分约为90分,而纳帕谷和帕罗斯产区的平均评分分别为93分和92分,这些都是著名的葡萄酒产区。


总结

在本节课中,我们一起学习了如何使用 Pandas 进行探索性数据分析。我们涵盖了数据加载、初步查看、统计描述、排序、处理缺失值以及分组计算等核心操作。这些技能是数据分析和机器学习工作流中的重要基础,能帮助你快速理解数据集的特征和分布。

59:Pandas基础使用 📊

概述

在本节课中,我们将回顾使用Python的Pandas库进行数据处理的基础知识。我们将重点学习如何从不同来源加载数据、探索数据的基本结构,以及如何将数据保存到文件系统中。


数据加载方法

上一节我们介绍了Pandas库的基本概念,本节中我们来看看如何从不同来源加载数据。

我们首先查看了远程GitHub仓库中一个CSV格式的数据集。我们能够从该URL远程获取数据,并将其加载并创建为一个DataFrame。

以下是加载远程CSV数据的基本代码示例:

import pandas as pd
url = 'https://raw.githubusercontent.com/your_repo/data.csv'
df = pd.read_csv(url)

支持的文件格式

除了CSV格式,Pandas还支持多种其他文件格式。

我们能够查看不同的文件格式,学习加载这些文件格式的不同方法,以及将它们保存到文件系统的不同方式。

以下是Pandas支持的部分文件格式及其加载方法:

  • CSV格式:使用 pd.read_csv() 加载,使用 df.to_csv() 保存。
  • Excel格式:使用 pd.read_excel() 加载,使用 df.to_excel() 保存。
  • JSON格式:使用 pd.read_json() 加载,使用 df.to_json() 保存。

数据探索与分析

在成功加载数据后,进行初步的探索性分析是理解数据的关键步骤。

我们进行了一些探索性工作,这些工作应该能让你进行各种更高级的探索性分析,并从不同位置加载数据或将数据保存到不同位置。

以下是几个基础的数据探索操作:

  • 查看数据头部:使用 df.head() 查看前几行数据。
  • 查看数据信息:使用 df.info() 获取数据集的简要摘要,包括列名、非空值数量和数据类型。
  • 查看统计摘要:使用 df.describe() 获取数值列的统计信息(如计数、均值、标准差等)。

总结

本节课中我们一起学习了Pandas库的基础使用。我们回顾了从远程URL加载CSV数据、认识了Pandas支持的其他常见文件格式及其读写方法,并实践了初步的数据探索操作。掌握这些基础技能是进行更复杂数据分析和机器学习运维工作的重要第一步。

60:使用Pandas处理数据框 🐼

在本节课中,我们将学习如何使用Pandas库对数据框进行一些更高级的操作。我们将重点介绍如何操作文本数据、创建新的数据列,以及如何可视化对数据框执行的操作。


上一节我们介绍了Pandas数据框的基础知识,本节中我们来看看如何对数据框中的文本数据进行处理。

Pandas提供了强大的字符串处理方法,可以方便地对数据框中的文本列进行各种操作。这些方法可以通过.str访问器来调用。

以下是处理文本数据的一些常用操作:

  • 转换大小写:使用.str.lower()将文本转换为小写,或使用.str.upper()转换为大写。
  • 去除空格:使用.str.strip()去除文本两端的空格。
  • 分割字符串:使用.str.split()根据指定的分隔符将字符串分割成列表。
  • 检查子串:使用.str.contains()检查字符串是否包含指定的子串。
  • 替换文本:使用.str.replace()将字符串中的特定部分替换为其他内容。

例如,如果我们有一个包含姓名的列df[‘name’],我们可以轻松地将其全部转换为大写:

df[‘name_upper’] = df[‘name’].str.upper()

了解了文本处理之后,我们来看看如何基于现有数据创建新的列。这在数据清洗和特征工程中非常常见。

创建新列的核心思想是,通过对一个或多个现有列应用函数或运算,将结果赋值给一个新的列名。

以下是创建新列的几种主要方式:

  • 简单运算:直接对数值列进行算术运算(加、减、乘、除)。
  • 应用函数:使用.apply()方法传入一个自定义函数,对行或列进行计算。
  • 条件赋值:使用np.where().loc[]根据条件逻辑来创建新列。

例如,假设我们有一个数据框df,包含‘height’(身高,米)和‘weight’(体重,千克)两列,我们可以计算身体质量指数(BMI)并创建新列:

df[‘bmi’] = df[‘weight’] / (df[‘height’] ** 2)

或者,根据BMI值分类:

df[‘bmi_category’] = np.where(df[‘bmi’] > 25, ‘Overweight’, ‘Normal’)

最后,我们将探讨如何可视化对数据框的操作。虽然Pandas本身不是专业的绘图库,但它集成了Matplotlib,可以快速生成图表来帮助我们理解数据分布和操作结果。

可视化是理解数据和验证操作有效性的重要手段。Pandas数据框的.plot()方法提供了快速绘图的接口。

以下是几种常见的绘图类型:

  • 折线图:展示数据随时间或其他连续变量的趋势,使用.plot.line()
  • 柱状图:比较不同类别的数据,使用.plot.bar()
  • 直方图:显示数值数据的分布情况,使用.plot.hist()
  • 散点图:观察两个数值变量之间的关系,使用.plot.scatter()

例如,要可视化我们刚刚创建的‘bmi’列的分布,可以简单地调用:

df[‘bmi’].plot.hist(title=‘BMI Distribution’, bins=20)
plt.show()


本节课中我们一起学习了Pandas数据框的进阶操作。我们掌握了如何使用.str访问器处理文本数据,如何通过运算、函数或条件逻辑来创建新的数据列,以及如何利用.plot()方法快速可视化数据以辅助分析。这些技能是进行数据清洗、特征工程和探索性数据分析的基础。

61:常见数据框操作 📊

在本节课中,我们将要学习使用 pandas 库进行数据框(DataFrame)的常见操作。我们将涵盖如何选择特定列、筛选特定行、使用条件查询以及进行字符串匹配等核心技能。


数据框列操作 🔧

上一节我们介绍了数据框的基本概念,本节中我们来看看如何操作数据框的列。

要处理数据框中的特定列,可以使用方括号 [] 并指定列名。例如,notes 列包含葡萄酒的描述文本。通过以下方式可以获取该列:

df['notes']

此操作将返回一个包含所有行 notes 值的序列(Series),并附带索引。

如果你想同时处理多个列,例如 namerating 列,可以这样做:

name_ratings = df[['name', 'rating']]

以下是操作数据框列的关键步骤:

  • 使用方括号 [] 和列名来获取单列。
  • 使用包含列名的列表 [['col1', 'col2']] 来获取多列。
  • 可以将结果赋值给一个新变量,从而创建一个只包含所需列的新数据框子集。

这个新的数据框子集可以用于后续分析,也可以像之前课程介绍的那样,导出为不同格式的文件。


数据框行筛选 🎯

在掌握了列操作之后,我们来看看如何根据条件筛选数据框的特定行。

假设我们想从之前创建的 name_ratings 数据框中,找出评分(rating)高于 96 的葡萄酒。可以使用条件语句进行筛选:

top_rated = name_ratings[name_ratings['rating'] > 96]

运行此代码后,top_rated 数据框将只包含评分高于 96 的行。

条件本身也可以独立使用。例如,df['rating'] > 96 会对数据框的每一行进行评估,返回一个布尔值序列(True/False),指示哪些行满足条件。


使用 query 方法进行查询 🔍

除了直接使用方括号进行条件筛选,pandas 还提供了一个更强大的 query 方法。

query 方法可以直接在数据框上调用。例如,要筛选评分高于 93 的所有行:

df.query('rating > 93').head()

此操作将返回整个数据框中评分高于 93 的前几行数据,而不仅仅是特定的列。

你还可以组合多个查询条件。例如,查找评分高于 94 并且 产区(region)为西班牙著名产区 “Ribera del Duero” 的葡萄酒:

df.query('rating > 94 and region == "Ribera del Duero"').head(10)

以下是使用 query 方法的核心要点:

  • 使用 df.query('条件') 的语法。
  • 在查询字符串中,列名可以直接使用。
  • 使用 andor 等关键字组合多个条件。
  • 可以使用 .head(n) 来限制显示的行数,以便预览查询结果。

字符串匹配查询 📝

最后,我们来看一个非常实用的技巧:如何在查询中进行灵活的字符串匹配。

假设我们想找出评分高于 95 的葡萄酒,并且其产区名称中包含 “Robles”。这时,我们可以结合 query 方法和字符串函数。

top_wines = df.query('rating > 95')
paso_robles = top_wines.query('region.str.contains("Robles", na=False)', engine='python')
paso_robles.head()

以下是此操作的关键解释:

  • region.str.contains("Robles"):检查 region 列中的字符串是否包含子串 “Robles”。
  • na=False:忽略值为 NaN(非数字/空值)的条目。
  • engine='python':指定使用 Python 引擎来解析和执行查询字符串,这对于字符串操作通常是必要的。

运行后,结果将显示所有评分高于 95 且产区名称包含 “Robles” 的葡萄酒。


总结 📚

本节课中我们一起学习了 pandas 数据框的几种核心操作:

  1. 列选择:使用 df['col']df[['col1', 'col2']] 来选取单列或多列。
  2. 行筛选:使用布尔条件 df[df['col'] > value] 来筛选满足条件的行。
  3. 高级查询:使用 df.query() 方法进行更清晰、更强大的条件查询,并支持多条件组合。
  4. 字符串匹配:在 query 方法中结合 .str.contains() 等字符串函数进行灵活的文本搜索。

这些操作是数据清洗、探索和分析的基础,熟练掌握它们将为后续的机器学习工作流运维打下坚实的基础。

62:在数据框中处理文本 📝

在本节课中,我们将要学习如何在Pandas数据框中处理和操作文本数据。文本数据是数据集中常见的一种类型,掌握其处理方法对于数据清洗和特征工程至关重要。

上一节我们介绍了数据框的基本操作,本节中我们来看看如何对数据框中的文本列进行特定的转换和操作。

加载数据集

首先,我们需要加载一个数据集作为示例。我们将使用之前课程中一直使用的葡萄酒评分数据集。

import pandas as pd

# 假设df是已加载的葡萄酒评分数据框
# df = pd.read_csv('wine_ratings.csv')

替换文本内容

有时,我们需要将列中的特定文本值替换为更简洁或标准化的形式。Pandas提供了便捷的方法来实现这一点。

以下是使用replace方法创建新列的步骤:

  1. 确定需要替换的原始文本值。
  2. 定义替换后的目标值。
  3. 使用replace方法并指定一个映射字典。
  4. 将结果赋值给一个新的列。
# 创建一个新列‘variety_short’,将‘red wine’替换为‘R’,‘white wine’替换为‘W’
df['variety_short'] = df['variety'].replace({'red wine': 'R', 'white wine': 'W'})

运行这段代码后,数据框中会新增一个名为variety_short的列。例如,原来variety列中为“red wine”的行,其variety_short列的值会变为“R”。这是一种安全的做法,因为它保留了原始数据,方便在需要时进行核对。

分割文本并提取部分内容

另一种常见的文本操作是根据特定分隔符分割字符串,并提取其中的某一部分。这在处理包含复合信息的字段时非常有用。

例如,在“region”列中,数据格式可能是“Mendocino, California”。如果我们只想保留州名“California”,可以按照以下步骤操作:

以下是使用str.split方法分割文本并提取最后一部分的步骤:

  1. 使用.str访问器调用字符串方法。
  2. 使用split(‘,’)方法以逗号为分隔符进行分割,这会生成一个列表。
  3. 使用.str.get(-1)获取列表中的最后一个元素(即州名)。
# 创建一个新列‘region_short’,提取‘region’列中逗号后的最后一部分内容
df['region_short'] = df['region'].str.split(',').str.get(-1)

执行此操作后,region_short列将只包含地区名称的最后一部分,如“California”。对于那些原本没有逗号的条目(例如“Spain”),该操作会直接返回原始字符串。

本节课中我们一起学习了在Pandas数据框中处理文本数据的两种基本方法:使用replace进行文本替换,以及使用str.split结合str.get来分割和提取字符串的特定部分。这些技巧是数据预处理中文本清洗和特征创建的基础,希望你能在实践中找到更多有用的应用方式。

63:使用pandas应用函数 🐼

在本节课中,我们将要学习如何在pandas中应用自定义函数来操作数据,这是pandas数据处理与纯Python循环处理数据的一个关键区别。

概述

使用pandas处理数据与使用纯Python处理数据的一个区别在于,在纯Python中,你通常会围绕数据进行循环,无论是列表还是字典,你都在创建新的数据结构或提取特定内容,但始终离不开循环。而pandas提供了一种方式,当你想要操作数据时,可以应用一个函数。这并不意味着一种方法比另一种更好,但如果你不习惯对数据应用函数,这种方式可能会让你感到有些意外。其核心在于,你可以使用自己的函数来操作和执行运算。

应用函数创建新列

这里我加载了与之前相同的数据集。我们的业务前提和需求是:我们希望根据对评分的某种主观看法,找出顶级葡萄酒。

正如之前提到的,葡萄酒评分基本上从80多分到100分不等,100分意味着葡萄酒的完美。因此,我们想说任何高于95分(即95分或更高)的葡萄酒都是好酒。我们想要操作数据并创建这样一个列。

以下是实现步骤:

  1. 定义条件与函数:我们有一个需要匹配的条件,我们想要一个输出,并希望创建一个新列。因此,我将在这里创建一个函数。你可以看到这只是一个常规函数,它在这里做的事情不多,整个函数只有四行,其中三行是实际执行工作的代码。该函数将接收一个由pandas传入的值,逻辑是:如果该值大于94,则返回True(表示是好酒),否则返回False。

    def good_wine(value):
        if value > 94:
            return True
        else:
            return False
    
  2. 将函数应用到数据框:接下来,我将创建一个名为“good”的新列。具体操作是:查看“rating”列,然后应用上面定义的good_wine函数。pandas会为每一个项目传递评分值,该函数的返回值将作为新列“good”的基础。

    df['good'] = df['rating'].apply(good_wine)
    
  3. 查询与验证:然后,我们可以查询评分高于94的记录,并查看结果。第一条记录显示“good”为True。

关于数据类型的说明

pandas中的布尔值True/False也可以表示为1或0。如果你希望进行其他数值分析或统计分析,使用1或0可能是比True/False更好的类型。你可以轻松地修改函数来实现这一点。

例如,将函数修改为返回1和0:

def good_wine(value):
    if value > 94:
        return 1
    else:
        return 0

运行修改后的代码,你可以看到现在“good”列的值是1或0。

总结

本节课中我们一起学习了如何在pandas中应用自定义函数。通过这种方式,我们创建了一个名为“good”的新列。你甚至可以就地修改数据,但这里我们直接应用函数来操作。这就是你通过应用函数、将特定逻辑应用于列和值来操作数据的方法。

64:使用pandas可视化数据 📊

在本节课中,我们将要学习如何使用Pandas库进行基本的数据可视化。Pandas不仅是一个强大的数据分析工具,还内置了基于Matplotlib的绘图功能,能够帮助我们快速、直观地探索和理解数据。


数据准备与清理 🧹

上一节我们介绍了Pandas的数据处理能力,本节中我们来看看如何为可视化准备数据。在开始绘图前,确保数据干净、格式正确至关重要,特别是处理缺失值(NaN)。

首先,我们加载数据框。如果数据集中存在非数值或NaN值,可能会在可视化时遇到问题。

# 加载数据框并清理缺失值
df = pd.read_csv('your_data.csv')
# 删除包含特定问题值(如‘grape’)的行
df.drop(df[df['column_name'] == 'grape'].index, inplace=True)
# 删除所有包含NaN值的行
df = df.dropna()
# 查看清理后的数据前几行
print(df.head())

通过以上步骤,我们移除了可能导致绘图错误的非数值和缺失数据。


安装Matplotlib 📦

为了使用Pandas的绘图功能,通常需要确保Matplotlib库已安装。虽然Pandas内置了绘图方法,但Matplotlib是其底层引擎。

以下是安装Matplotlib的方法:

pip install matplotlib

如果系统已安装,终端会显示“Requirement already satisfied”。如果遇到错误,此命令可帮助你完成安装。


基础绘图 📈

安装好环境后,我们可以开始基础绘图。Pandas的.plot()方法能直接从数据框生成图表。

绘制整个数据框

无需额外参数,我们可以快速绘制数据框中所有数值列的折线图,以初步观察数据趋势。

df.plot()

此命令会生成一个图表,例如,可以立即看到“rating”评分数据的大致分布,有些值高达98分。

绘制单列数据

我们也可以选择只绘制特定的数值列。以下是绘制“rating”列的示例:

df['rating'].plot()

默认情况下,Pandas会为数值列生成折线图。这种方法简单直接,适合快速查看单一变量的分布。


处理非数值数据与散点图 🔍

并非所有列都适合直接绘图。如果尝试绘制非数值列(如文本类型的“variety”),Pandas会提示错误,因为它需要数值数据来生成坐标。

以下是尝试绘制非数值列会遇到的问题:

# 此操作可能不会成功,因为‘variety’不是数值列
df['variety'].plot()

创建散点图

为了可视化两个变量之间的关系,我们可以使用散点图。例如,我们可以探索“rating”和“variety”之间的关系,但需要确保至少一个轴是数值型。

实际上,更常见的做法是绘制两个数值列。假设我们想查看价格与评分的关系:

df.plot.scatter(x='price', y='rating')

散点图能帮助我们直观地发现数据中的模式、集群或异常值。例如,你可能发现某些葡萄酒品种的评分集中在一个较高的区间。


总结 ✨

本节课中我们一起学习了使用Pandas进行数据可视化的基本步骤。

我们首先强调了数据清理的重要性,特别是处理NaN值。接着,我们介绍了如何安装必要的Matplotlib库。然后,我们实践了直接从数据框绘图以及绘制特定数值列的方法。最后,我们探讨了数据类型的限制,并介绍了散点图的使用场景,它可以帮助我们洞察两个数值变量之间的关系。

通过Pandas内置的可视化功能,我们可以快速、有效地对数据进行初步探索,这是机器学习工作流中理解数据的关键一步。

65:课程回顾-处理数据框 📊

在本节课中,我们将回顾使用Pandas数据框进行数据处理的核心操作。我们将学习如何创建新列、筛选行、进行文本操作与替换,并最终将处理结果可视化。这些技能是进行更高级数据分析的坚实基础。


上一节我们介绍了数据框的基本概念,本节中我们来看看一些更具体的操作。

创建新列

在数据分析中,经常需要基于现有数据计算并生成新的数据列。这可以通过直接赋值给一个新列名来实现。

以下是创建新列的常用方法:

  • 直接赋值df['new_column'] = df['existing_column'] * 2
  • 使用apply函数df['new_column'] = df['existing_column'].apply(lambda x: x**2)
  • 使用向量化操作df['new_column'] = df['col1'] + df['col2']

提取与筛选行

从数据框中提取特定行是数据清洗和探索性分析的关键步骤。我们可以根据条件来筛选出需要的数据子集。

以下是提取行的主要方式:

  • 布尔索引df[df['column'] > 50]
  • 使用locilocdf.loc[df['column'] == 'value']df.iloc[0:5]
  • 多重条件筛选df[(df['col1'] > 10) & (df['col2'] == 'A')]

文本操作与替换

处理文本数据时,Pandas提供了强大的字符串方法来执行清洗和转换。这些操作通常作用于Series对象的str属性上。

以下是常见的文本操作:

  • 字符串方法df['text_column'].str.lower()df['text_column'].str.contains('pattern')
  • 替换文本df['column'].replace('old_value', 'new_value', inplace=True)
  • 分割字符串df['column'].str.split(' ', expand=True)

数据可视化

将数据处理结果可视化,能帮助我们更直观地理解数据模式和洞察。Pandas集成了Matplotlib,可以方便地绘制基本图表。

以下是简单的可视化示例:

  • 绘制折线图df['column'].plot()
  • 绘制柱状图df['column'].value_counts().plot(kind='bar')
  • 绘制直方图df['numeric_column'].plot(kind='hist', bins=30)


本节课中我们一起学习了Pandas数据框的几个核心操作:创建新列、筛选行、进行文本处理与替换,以及基础的数据可视化。这些技能构成了数据处理工作流的重要部分,为你后续进行更复杂的机器学习数据准备打下了坚实的基础。

66:NumPy基础 🧮

在本节课中,我们将学习NumPy的基础知识。NumPy是Python中用于科学计算的核心库,它提供了高性能的多维数组对象以及处理这些数组的工具。我们将重点介绍如何创建和操作NumPy数组。


什么是NumPy? 🤔

上一节我们介绍了本课程的主题,本节中我们来看看NumPy的具体定义。

NumPy是Numerical Python的缩写。它是一个功能强大的Python库,专门用于处理数值数据。NumPy提供了多种能力,能以与Pandas类似的方式处理数据。我们将介绍一些基础知识,特别是关于数组的操作。


核心概念与操作 🛠️

NumPy的核心是其多维数组对象,即ndarray。以下是一些基本操作。

1. 导入NumPy

在开始使用前,需要先导入NumPy库。通常我们使用np作为其别名。

import numpy as np

2. 创建数组

以下是创建NumPy数组的几种基本方法。

  • 从Python列表创建:使用np.array()函数。
    arr_from_list = np.array([1, 2, 3, 4, 5])
    
  • 创建特定数值的数组:例如,全零、全一或指定范围的数组。
    zeros_arr = np.zeros(5)  # 创建包含5个0的数组
    ones_arr = np.ones((3, 3))  # 创建3x3的全1数组
    range_arr = np.arange(0, 10, 2)  # 创建从0到10(不含),步长为2的数组
    

3. 数组的基本属性

创建数组后,可以查看其属性以了解其结构。

  • 形状array.shape 返回一个表示数组维度的元组。
  • 数据类型array.dtype 返回数组中元素的数据类型。
  • 维度array.ndim 返回数组的维数。

4. 数组的索引与切片

访问和修改数组元素的方法与Python列表类似。

arr = np.array([10, 20, 30, 40, 50])
print(arr[0])   # 输出第一个元素: 10
print(arr[1:4]) # 输出索引1到3的元素: [20 30 40]
arr[2] = 99     # 将第三个元素修改为99

对于多维数组,索引和切片在每个维度上独立进行。

matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(matrix[1, 2])  # 输出第2行第3列的元素: 6
print(matrix[:, 1])  # 输出所有行的第2列: [2 5 8]

5. 数组的基本运算

NumPy支持对数组进行逐元素的数学运算,这是其强大之处。

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

print(a + b)  # 逐元素相加: [5 7 9]
print(a * b)  # 逐元素相乘: [4 10 18]
print(a ** 2) # 逐元素平方: [1 4 9]


总结 📝

本节课中我们一起学习了NumPy的基础知识。我们了解了NumPy是一个用于高效数值计算的Python库,其核心是ndarray多维数组对象。我们练习了如何创建数组、查看数组属性、进行索引切片以及执行基本的数组运算。掌握NumPy是后续学习Pandas和机器学习中数据处理的重要基石。

67:NumPy 数组简介 🧮

在本节课中,我们将学习 NumPy 的基础知识。NumPy 是 Python 中用于科学计算的核心库,它提供了高性能的多维数组对象以及处理这些数组的工具。我们将介绍如何安装 NumPy、导入库、创建数组以及使用一些基本的内置函数。


什么是 NumPy?🤔

NumPy 代表 Numerical Python。它是一个库,Pandas 等高级数据处理库在底层也使用 NumPy 来执行各种复杂操作。与常规的 Python 数据结构相比,NumPy 拥有多项优化,因此处理速度更快。

在开始使用 NumPy 之前,你需要安装它。安装方式是通过 pip 命令:pip install numpy。建议始终在激活的虚拟环境中安装,并确保依赖项已正确安装在该环境中。


导入 NumPy 📥

导入 NumPy 的常见方式与导入 Pandas 类似,通常使用别名以简化代码。

以下是导入 NumPy 的标准方式:

import numpy as np

此后,在整个代码中都可以使用 np 来引用 NumPy 库。这是一种广泛遵循的约定,有助于阅读文档和示例代码。


NumPy 数组的特点

NumPy 的核心是 数组(array)。这些数组是同质的,意味着数组中的所有元素必须是相同的数据类型。如果你尝试创建包含不同数据类型的数组,NumPy 会在后台强制转换数据类型,以使所有元素类型一致,这有时可能导致意外结果。


创建数组 🛠️

上一节我们介绍了 NumPy 的基本概念和导入方式,本节中我们来看看如何创建 NumPy 数组。有多种方法可以创建数组,从简单的列表转换到使用内置函数。

从列表创建数组

这是创建数组最直接的方法。你可以将一个 Python 列表传递给 NumPy 来创建数组。

import numpy as np

# 从一维列表创建数组
arr_1d = np.array([1, 2, 3, 4, 5])
print(arr_1d)
# 输出: [1 2 3 4 5]
print(type(arr_1d))
# 输出: <class 'numpy.ndarray'>

从二维列表(嵌套列表)可以创建二维数组。

# 从二维列表(嵌套列表)创建数组
arr_2d = np.array([[1, 2], [3, 4]])
print(arr_2d)
# 输出:
# [[1 2]
#  [3 4]]
print(arr_2d.shape)
# 输出: (2, 2)  # 表示一个2行2列的数组

使用内置函数创建数组

除了从列表创建,NumPy 还提供了许多方便的函数来快速生成特定模式的数组。

以下是几种常用的创建数组的函数:

  • np.random.rand: 创建一个指定形状的数组,并用 0 到 1 之间的随机浮点数填充。
    random_arr = np.random.rand(2, 2)  # 创建一个2x2的随机数组
    print(random_arr)
    
  • np.full: 创建一个指定形状的数组,并用给定的固定值填充所有元素。
    full_arr = np.full((3, 4), 8)  # 创建一个3行4列,所有元素都为8的数组
    print(full_arr)
    
  • np.ones: 创建一个指定形状的数组,并用 1 填充。
    ones_arr = np.ones((4, 4))  # 创建一个4x4的全1数组
    print(ones_arr)
    
  • np.zeros: 创建一个指定形状的数组,并用 0 填充。
    zeros_arr = np.zeros((4, 4))  # 创建一个4x4的全0数组
    print(zeros_arr)
    
  • np.arange: 类似于 Python 的 range 函数,但返回一个 NumPy 数组。它可以生成一个具有特定起始值、结束值和步长的序列。
    seq_arr = np.arange(0, 5)  # 生成从0到4(不包括5)的序列
    print(seq_arr)  # 输出: [0 1 2 3 4]
    
    step_arr = np.arange(0, 27, 3)  # 生成从0到27,步长为3的序列
    print(step_arr)  # 输出: [ 0  3  6  9 12 15 18 21 24]
    

总结 📝

本节课中我们一起学习了 NumPy 的基础知识。我们了解了 NumPy 是一个用于高效数值计算的 Python 库,学会了如何安装和导入它。我们重点探讨了 NumPy 数组的同质性特点,并掌握了多种创建数组的方法:包括从列表转换,以及使用 np.random.randnp.fullnp.onesnp.zerosnp.arange 等内置函数。这些是开始使用 NumPy 进行数据操作和科学计算的重要第一步。

68:NumPy常见数组操作教程 🧮

在本节课中,我们将要学习NumPy库中一些常见的数组操作。这些操作是数据处理和科学计算的基础,包括对数组进行数学运算、改变形状以及展平数组等。


上一节我们介绍了NumPy数组的创建,本节中我们来看看如何对数组进行一些基础但重要的操作。

首先,我们需要导入NumPy库。在大多数教程和文档中,你会看到这样的导入方式:

import numpy as np

接下来,我们将创建一个数组。这里用一个列表来初始化它,列表中的数字代表以磅为单位的体重。

weights_lbs = np.array([140, 174, 166, 192])

运行这段代码会创建一个数组。这个过程非常直接。

现在,我们来进行一个数学运算:将这个数组除以2.2。你可能会觉得这有点奇怪,因为数组中有多个元素。但在幕后,NumPy会将这个除法运算应用到数组中的每一个元素上。这里的2.2是磅与公斤的换算系数(1公斤约等于2.2磅)。

weights_kgs = weights_lbs / 2.2

运行后,你会得到一个新的数组,其中的数值代表以公斤为单位的体重。


除了数学运算,你还会经常遇到重塑数组形状的操作。

以下是重塑数组时需要注意的几个要点:

首先,我们创建一个3行4列、元素全为1的数组。

arr = np.ones((3, 4))

现在,我们可以使用reshape函数来改变它的形状。例如,我想把它变成4行3列。

reshaped_arr = arr.reshape(4, 3)

这里有一个关键点必须注意:重塑前后的数组,其元素总数必须保持一致。例如,一个形状为(3,4)的数组有12个元素。你可以把它重塑为(4,3)、(2,6)、(12,1)或(1,12),因为这些形状对应的元素总数也是12。

但是,你不能把它重塑为(4,4),因为那需要16个元素。如果尝试这样做,程序会报错。

# 这会引发错误,因为元素总数不匹配
# invalid_arr = arr.reshape(4, 4)

另一种常见的操作是展平数组,即将多维数组转换为一维数组。

我们继续使用上面那个3行4列的数组。你可以使用flatten函数轻松地将其展平。

flattened_arr = arr.flatten()

展平后的数组将包含所有原始元素,但排列在一个维度上。


本节课中我们一起学习了NumPy中几种常见的数组操作。我们看到了如何对数组进行基础的数学运算(如除法),也学习了如何重塑数组的形状,并理解了重塑时必须保持元素总数不变的原则。最后,我们还了解了如何将多维数组展平为一维数组。这些操作是使用NumPy进行高效数据处理的基石。

69:更多NumPy数组操作 🧮

在本节课中,我们将学习NumPy库中更多实用的数组操作。我们将探索如何扩展数组、应用条件筛选、进行切片、堆叠以及分割数组。这些操作是高效处理数值数据的基础。

导入NumPy库

首先,我们需要导入NumPy库。这是使用其所有功能的前提。

import numpy as np

扩展数组维度

上一节我们导入了NumPy,本节中我们来看看如何扩展数组的维度。这通常用于调整数据的形状以适应不同的计算需求。

以下是使用 np.newaxis 扩展数组的步骤:

  • 创建一个一维数组。
  • 使用 np.newaxis 为其添加一个新的行维度。
  • 检查扩展前后数组的形状变化。
# 创建一个包含4个元素的一维数组
arr = np.ones(4)
print("原始数组:", arr)
print("原始形状:", arr.shape)

# 使用np.newaxis扩展维度(添加一个行维度)
expanded_arr = arr[np.newaxis, :]
print("扩展后数组:", expanded_arr)
print("扩展后形状:", expanded_arr.shape)

条件筛选数组

接下来,我们将学习如何根据条件筛选数组中的元素。NumPy允许我们将条件直接应用于整个数组,并返回布尔值或匹配的值。

以下是进行条件筛选的两种方式:

  • 创建一个二维数组。
  • 应用条件(如小于20)并获取布尔掩码。
  • 使用布尔掩码获取所有匹配条件的实际值。
# 创建一个二维数组
arr_2d = np.array([[12, 1211], [45, 2211], [56, 15], [22, 33]])
print("原始数组:\n", arr_2d)

# 获取布尔掩码:哪些元素小于20?
mask = arr_2d < 20
print("条件掩码 (arr < 20):\n", mask)

# 获取所有小于20的元素值
matching_values = arr_2d[arr_2d < 20]
print("匹配值 (arr < 20):", matching_values)

数组切片操作

现在,我们来看看数组的切片操作。这与Python列表的切片非常相似,是获取数组子集的有效方法。

# 创建一个从0到5的一维数组
arr_slice = np.arange(6)
print("原始数组:", arr_slice)

# 进行切片操作
print("前三个元素:", arr_slice[:3])
print("第二到第四个元素:", arr_slice[1:4])
print("最后两个元素:", arr_slice[-2:])

数组堆叠操作

数组堆叠是将多个数组合并成一个新数组的常用操作。NumPy提供了水平和垂直两种堆叠方式。

以下是水平堆叠 (hstack) 的示例:

  • 创建两个具有相同行数的二维数组。
  • 使用 np.hstack 将它们水平连接。
# 创建第一个数组
arr1 = np.array([[1, 2], [3, 4]])
print("数组1:\n", arr1)

# 创建第二个数组
arr2 = np.array([[5, 6], [7, 8]])
print("数组2:\n", arr2)

# 水平堆叠
h_stacked = np.hstack((arr1, arr2))
print("水平堆叠结果:\n", h_stacked)
print("新形状:", h_stacked.shape)

以下是垂直堆叠 (vstack) 的示例:

  • 创建两个具有相同列数的二维数组。
  • 使用 np.vstack 将它们垂直连接。
# 创建两个数组
arr_a = np.ones((3, 3))
arr_b = np.full((3, 3), 2)
print("数组A:\n", arr_a)
print("数组B:\n", arr_b)

# 垂直堆叠
v_stacked = np.vstack((arr_a, arr_b))
print("垂直堆叠结果:\n", v_stacked)
print("新形状:", v_stacked.shape)

数组分割操作

最后,我们来学习如何将数组分割成多个子数组。np.split 函数要求分割后的各部分大小相等。

以下是分割数组的示例:

  • 创建一个一维数组。
  • 使用 np.split 将其分割成指定数量的等份。
  • 注意:数组长度必须能被分割数整除,否则会报错。
# 创建一个包含11个元素的一维数组
arr_to_split = np.arange(11)
print("待分割数组:", arr_to_split)

# 尝试分割成2等份(可行,因为11不能被2整除?这里原描述有误,实际应为不可行)
# 正确示例:创建一个可被整除的数组
arr_even = np.arange(10)
print("新数组:", arr_even)

# 分割成2等份
split_result = np.split(arr_even, 2)
print("分割结果 (2等份):")
for i, part in enumerate(split_result):
    print(f"  部分{i+1}: {part}")

# 使用解包赋值接收分割结果
first_part, second_part = np.split(arr_even, 2)
print("解包-第一部分:", first_part)
print("解包-第二部分:", second_part)

本节课中我们一起学习了NumPy库中几种重要的数组操作:扩展维度、条件筛选、切片、堆叠和分割。掌握这些操作能让你更灵活地处理和准备数据,为后续的机器学习工作流打下坚实基础。NumPy的功能远不止于此,但这些核心操作是高效使用该库的关键。

70:NumPy基础回顾 🧮

在本节课中,我们将回顾NumPy库的基础知识。NumPy是Python中进行科学计算的核心库,它提供了高性能的多维数组对象以及处理这些数组的工具。我们将从创建数组开始,逐步学习如何使用内置函数,并对数组进行重塑、分割和切片等操作。

创建数组

首先,我们来看看如何创建NumPy数组。NumPy数组是存储同类型数据的网格,可以通过多种方式创建。

以下是创建数组的几种常用方法:

  • 从Python列表创建:使用 np.array() 函数,将Python列表转换为NumPy数组。
    import numpy as np
    arr = np.array([1, 2, 3, 4, 5])
    
  • 创建特殊数组:NumPy提供了一些函数来快速创建具有特定结构的数组。
    • np.zeros(shape):创建指定形状的全零数组。
    • np.ones(shape):创建指定形状的全一数组。
    • np.arange(start, stop, step):创建等差序列数组。
    • np.linspace(start, stop, num):在指定区间内创建均匀间隔的数值数组。

使用内置函数

上一节我们介绍了如何创建数组,本节中我们来看看NumPy提供的一些强大内置函数。这些函数可以对整个数组执行数学运算,而无需编写循环,这大大提升了计算效率。

以下是几个常用的内置函数示例:

  • 数学运算np.sqrt(arr) 计算平方根,np.exp(arr) 计算指数,np.sin(arr) 计算正弦值。
  • 统计函数np.mean(arr) 计算平均值,np.std(arr) 计算标准差,np.sum(arr) 计算所有元素的和。
  • 随机数生成np.random.rand(shape) 生成指定形状的[0,1)区间均匀分布随机数。

数组操作:重塑、分割与切片

掌握了创建和基本运算后,我们进入更灵活的部分:操作数组的形状和内容。这些操作对于数据预处理和模型输入准备至关重要。

以下是核心的数组操作方法:

  • 重塑数组:使用 arr.reshape(new_shape) 方法可以改变数组的维度,而不改变其数据。例如,将一个一维数组重塑为二维矩阵。
  • 分割数组:使用 np.split(arr, indices) 可以将一个数组分割成多个子数组。
  • 数组切片:与Python列表类似,NumPy数组支持切片操作以获取部分数据,语法为 arr[start:stop:step]。对于多维数组,可以沿每个轴分别切片,例如 arr[行切片, 列切片]

本节课中我们一起学习了NumPy的基础知识。我们从创建数组开始,探索了其便捷的内置数学与统计函数,最后学习了如何通过重塑、分割和切片来灵活地操作数组结构。掌握这些基础是后续进行高效数据分析和机器学习建模的重要一步。

71:API与SDK介绍 🚀

在本节课中,我们将要学习API(应用程序编程接口)与SDK(软件开发工具包)的基本概念。它们是构建自动化流程、与云服务交互以及整合机器学习运维环境的核心工具。

概述

API和SDK是开发者在构建自动化系统、与云平台交互时最可能接触到的工具。它们能帮助你将所有组件整合在一起,构建出可重用、自动化的机器学习运维环境。使用这些工具不仅能增强你对系统稳定性的信心,还能通过编写脚本提升效率,让工作流程运行得更快。

上一节我们介绍了机器学习运维的基础环境,本节中我们来看看如何利用API和SDK来增强和自动化这个环境。

核心概念解析

什么是API?

API,即应用程序编程接口,是一组明确定义的规则和协议,允许不同的软件应用程序相互通信。你可以将其视为一个“服务菜单”或“契约”,你的代码通过它来请求另一个服务执行特定操作或获取数据。

一个简单的类比是餐厅:你(客户端)通过菜单(API文档)点餐,厨房(服务器)接收订单并准备食物,最后由服务员将食物(响应)送给你。你无需知道厨房如何运作,只需遵循点餐协议即可。

在代码中,调用一个API通常意味着向一个特定的URL(端点)发送HTTP请求。例如,使用Python的requests库调用一个天气API:

import requests

response = requests.get('https://api.weather.com/v1/forecast?city=Beijing')
weather_data = response.json()
print(weather_data)

什么是SDK?

SDK,即软件开发工具包,是一套工具、库、文档和代码示例的集合,旨在帮助开发者更轻松地为特定平台、框架或服务构建应用程序。SDK通常包含了对底层API的封装,提供了更友好、更符合特定编程语言习惯的接口。

如果说API是餐厅的菜单,那么SDK就像是为你提供了餐具、点餐平板以及标准操作流程,让你用餐(开发)体验更顺畅。

例如,使用云服务提供商(如AWS、Google Cloud)的Python SDK来操作存储服务,会比直接发送原始的HTTP请求简单得多:

# 使用AWS的Boto3 SDK上传文件到S3
import boto3

s3_client = boto3.client('s3')
s3_client.upload_file('local_file.txt', 'my-bucket', 'remote_file.txt')

API与SDK在MLOps中的作用

在机器学习运维中,API和SDK是实现自动化和集成的关键。以下是它们的一些常见用途:

以下是API与SDK在MLOps工作流中的几个关键应用场景:

  • 模型部署与服务化:将训练好的模型封装为REST API(例如使用FastAPI或Flask),供其他应用程序调用。
  • 流水线自动化:使用云服务商的SDK(如AWS Step Functions SDK、Kubeflow Pipelines SDK)来编排和自动化从数据准备到模型监控的整个ML流程。
  • 基础设施即代码:通过SDK(如Terraform的提供商SDK、AWS CDK)以编程方式定义和配置云资源(虚拟机、容器集群、存储桶等)。
  • 监控与日志:调用监控平台的API(如Prometheus API、Datadog API)来获取模型性能指标和系统日志,实现自动告警。
  • 数据获取与存储:从内部数据库或外部数据源通过API获取训练数据,并使用存储服务的SDK将处理后的数据和模型版本保存起来。

如何选择:API还是SDK?

选择直接使用API还是SDK,取决于你的具体需求:

以下是帮助你做出选择的一些考量因素:

  • 追求灵活性与控制力:如果你需要高度定制化的请求,或使用的服务仅提供基础的HTTP API,那么直接调用API是合适的选择。
  • 追求开发效率与易用性:如果你希望快速集成,避免处理底层的HTTP连接、认证和错误重试等细节,那么使用官方SDK通常是更高效的方式。SDK往往集成了最佳实践。
  • 考虑语言与生态:选择与你团队技术栈匹配的SDK。主流云服务商都为Python、Java、Go等语言提供了完善的SDK支持。
  • 评估功能完整性:有时SDK可能未及时包含API的所有最新功能。如果必须使用新功能,可能需要直接调用底层API或等待SDK更新。

总结

本节课中我们一起学习了API与SDK的核心概念及其在MLOps中的重要性。API定义了软件组件间的通信契约,而SDK则提供了实现这些交互的便捷工具包。它们是构建自动化、可重用且高效的机器学习运维体系的基石。理解并熟练运用这些工具,将极大地提升你整合系统、实现持续交付与运维的能力。

72:安装Azure命令行界面(CLI) 🛠️

在本节课中,我们将学习如何安装和配置Azure命令行界面(CLI)以及Python Azure SDK。这是构建机器学习运维(MLOps)自动化流程的基础步骤。


安装Azure命令行界面(CLI)概述

Azure命令行界面(CLI)是一个强大的工具,它允许我们通过命令行与Azure服务进行交互。安装它并学习如何使用和认证,对于构建自动化流程非常有价值。

安装过程具有很高的灵活性,支持多种操作系统。这使得无论是在本地开发环境,还是在如Github Actions、Jenkins或CodeBuild等CI/CD系统中,都能轻松部署。


安装Azure CLI

以下是安装Azure CLI的步骤。

  1. 访问安装页面:首先,访问Azure CLI的官方安装概述页面,根据你的操作系统(如Windows、macOS或Linux)选择对应的安装方法。
  2. 选择安装方式:例如,在Windows上,你可以下载MSI(Microsoft安装程序)文件,并通过命令行或图形界面进行安装。
  3. 验证安装:安装完成后,打开一个新的终端窗口,输入以下命令来验证安装是否成功:
    az --version
    
    这个命令会显示已安装的Azure CLI版本信息,并提示是否有可用更新。

安装Azure CLI扩展

上一节我们安装了Azure CLI的核心功能,本节中我们来看看如何为其添加扩展以增强功能。扩展类似于插件,可以为CLI添加特定服务(如Azure机器学习)的管理能力。

以下是安装机器学习(ML)扩展的步骤。

  1. 添加ML扩展:在终端中运行以下命令来添加Azure机器学习扩展:
    az extension add --name ml --yes
    
    参数 --yes 表示自动确认安装。
  2. 验证扩展安装:安装完成后,使用以下命令列出所有已安装的扩展,确认 ml 扩展已存在:
    az extension list
    
  3. 使用ML扩展:现在,你可以使用 az ml --help 命令来查看所有可用的机器学习相关命令。这些命令允许你管理工作区、在线端点、批量部署等几乎所有在Azure机器学习工作室中可用的功能。

认证与账户管理

安装好工具后,下一步是与你的Azure账户进行认证和连接。

以下是进行认证和查看账户信息的步骤。

  1. 交互式登录:运行以下命令进行交互式登录。这通常会打开你的默认浏览器,引导你完成认证流程:
    az login
    
    成功登录后,终端会以JSON格式输出你账户的详细信息,包括租户ID、订阅ID等敏感信息,请注意保护。
  2. 查看账户列表:登录后,你可以使用以下命令以表格形式清晰地查看所有订阅账户:
    az account list --output table
    
    输出会显示账户名称、云环境名称和订阅ID等信息,其中标有 True 的为默认账户。

安装Azure机器学习Python SDK

为了能在Python代码中操作Azure机器学习服务,我们需要安装对应的Python SDK。

以下是安装Python SDK的步骤。

  1. 创建虚拟环境:首先,创建一个独立的Python虚拟环境以管理依赖。在终端中运行:
    python -m venv .venv
    
  2. 激活虚拟环境
    • Windows:
      .venv\Scripts\activate
      
    • macOS/Linux:
      source .venv/bin/activate
      
  3. 安装SDK包:在激活的虚拟环境中,运行以下命令安装Azure机器学习核心库:
    pip install azureml-core
    
    这个命令会安装所有必要的依赖库。
  4. 验证安装:安装完成后,启动Python解释器并尝试导入库,以验证安装是否成功:
    import azureml
    
    你可以通过 print(azureml.__file__) 来确认模块是从刚创建的虚拟环境中导入的。

总结 🎯

本节课中我们一起学习了构建MLOps自动化基础的关键步骤。

我们首先安装了Azure命令行界面(CLI),并为其添加了机器学习扩展以增强功能。接着,我们学习了如何通过 az login 进行账户认证和管理订阅。最后,我们创建了一个Python虚拟环境,并安装了 azureml-core SDK,从而为后续使用Python代码自动化管理Azure机器学习资源做好了准备。

掌握这些工具的安装与基本操作,是迈向高效机器学习运维的重要第一步。

73:使用 Python 操作 Azure ML Studio 🚀

在本节课中,我们将学习如何使用 Azure ML 核心 Python SDK 与 Azure Machine Learning Studio 进行交互。我们将涵盖从身份验证、创建工作区、创建计算集群,到最后清理资源以避免额外费用的完整流程。


准备工作

上一节我们介绍了 Azure ML SDK 的安装,本节中我们来看看如何配置环境以开始使用。

首先,你需要准备好以下信息。以下是所需的关键信息列表:

  • 订阅 ID:你的 Azure 订阅标识。
  • 服务主体信息(可选但推荐):
    • 租户 ID
    • 应用(客户端)ID
    • 客户端密码

如果你创建了服务主体,这些信息将非常有用。否则,你也可以仅使用订阅 ID 进行交互式登录认证。

一种安全存储这些机密信息的方法是使用类似 GitHub Codespaces 的环境变量或机密管理功能。

身份验证与初始化

现在,我们将在 Jupyter Notebook 中开始编写代码。首先需要导入必要的模块并进行身份验证。

from azureml.core import Workspace
from azureml.core.authentication import ServicePrincipalAuthentication

# 使用服务主体进行身份验证
svc_pr = ServicePrincipalAuthentication(
    tenant_id="your-tenant-id",
    service_principal_id="your-app-id",
    service_principal_password="your-password"
)

运行以上代码后,身份验证即告完成。

创建工作区

身份验证成功后,下一步是创建一个 Azure Machine Learning 工作区。这个工作区将在 Azure 门户中显示,是所有机器学习活动的中心。

# 创建工作区
ws = Workspace.create(name='demo-try-azure-ml',
                      subscription_id='your-subscription-id',
                      resource_group='demo-try-azure-ml',
                      create_resource_group=True,
                      location='eastus2',
                      auth=svc_pr)

运行此代码将触发一系列 Azure 资源的部署,包括存储账户、密钥库和应用洞察等。这个过程可能需要一些时间。

创建完成后,你可以在 Azure 门户中刷新并看到新创建的工作区“demo-try-azure-ml”。

创建计算集群

有了工作区之后,我们通常需要计算资源来运行任务。接下来,我们将以编程方式创建一个计算集群。

以下是创建计算集群的步骤:

  1. 指定计算集群的名称和配置。
  2. 设置虚拟机类型和最大最小节点数。
  3. 定义空闲缩容时间以优化成本。
from azureml.core.compute import ComputeTarget, AmlCompute
from azureml.core.compute_target import ComputeTargetException

# 计算集群配置
compute_config = AmlCompute.provisioning_configuration(vm_size='STANDARD_DS3_V2',
                                                         min_nodes=0,
                                                         max_nodes=4,
                                                         idle_seconds_before_scaledown=1800)

# 创建集群
cpu_cluster = ComputeTarget.create(ws, 'cpu-cluster', compute_config)
cpu_cluster.wait_for_completion(show_output=True)

代码中的 wait_for_completion(show_output=True) 会阻塞执行,直到集群创建完毕。创建成功后,你可以在 Azure ML Studio 的“计算”->“计算集群”部分看到名为“cpu-cluster”的集群。

清理资源:删除工作区

完成所有实验和计算后,为了避免产生不必要的费用,彻底清理资源至关重要。我们将删除整个工作区及其关联的所有资源。

# 删除工作区及其所有资源
ws.delete(delete_dependent_resources=True)

注意:此操作可能需要几分钟到更长时间(在极少数情况下可能更长),并且不可逆。执行后,工作区、计算集群、存储账户等所有相关资源都将被永久删除。

操作完成后,刷新 Azure 门户,对应的资源组和工作区将消失。


总结

本节课中我们一起学习了如何使用 Azure ML Python SDK 进行端到端操作。我们完成了从设置身份验证、创建 Azure ML 工作区、配置计算集群,到最后安全删除所有资源以避免费用产生的全过程。通过编程方式管理这些资源,可以实现自动化、可重复的机器学习运维流程。

74:使用 Hugging Face Transformers 进行机器学习探索 🚀

在本节课中,我们将学习如何使用 Hugging Face 的 Transformers 库进行机器学习探索。Hugging Face 是一家提供众多人工智能工具和软件包的公司,其 Transformers 库允许我们处理文本、图像、音频等多种任务。我们将从安装依赖开始,逐步学习如何加载模型并使用其进行文本生成、情感分析、问答和翻译等操作。


概述

Hugging Face 是一家专注于构建机器学习工具和软件包的公司。本节课我们将重点介绍其 Transformers 库,这是一个功能强大的工具,支持文本、图像、音频等多种机器学习任务。我们将通过安装依赖、加载模型并进行实际交互,来探索其基本用法。


安装依赖

首先,我们需要安装必要的依赖包。以下是使用 pip 安装 Transformers 库的步骤,建议在虚拟环境中进行安装。

以下是 requirements.txt 文件的内容示例:

transformers==4.21.1
ipywidgets
ipykernel

其中,ipywidgetsipykernel 是用于 Jupyter Notebook 的可选依赖。如果你不使用 Jupyter Notebook,可以忽略这些依赖。


导入 Pipeline 助手

安装完成后,我们可以在 Jupyter Notebook 中开始使用 Transformers 库。首先,导入 pipeline 助手,它可以帮助我们与模型进行交互。

from transformers import pipeline

如果 Transformers 库安装成功,上述导入语句将不会报错。


使用文本到文本模型

接下来,我们将使用一个文本到文本模型进行自然语言处理任务。通过 pipeline 助手,我们可以指定模型类型并加载它。

以下是加载模型的代码:

generator = pipeline("text2text-generation", model="t5-small")

运行上述代码后,模型将被动态加载到内存中。这意味着我们无需提前下载或保存模型文件,非常适合快速探索和实验。


模型功能探索

加载模型后,我们可以使用它执行多种任务。以下是几个示例:

文本摘要

我们可以使用模型对一段文本进行摘要生成。

text = "Machine learning is a key to a successful production environment. It is a foundational process for many applications."
summary = generator("summarize: " + text)
print(summary)

模型将生成摘要,例如:“Machine learning is a key to a successful production environment.”

情感分析

模型还可以进行情感分析,判断文本的情感倾向。

sentiment = generator("sentiment: Automation takes hard work but allows you to have solid deployment.")
print(sentiment)

输出可能是“positive”,表示文本情感为积极。

问答任务

模型可以回答基于文本的问题。

answer = generator("question: Is deploying models into production hard? context: Deploying models requires careful planning and automation.")
print(answer)

模型将生成答案,例如:“It requires careful planning.”

翻译任务

模型还支持翻译功能,例如将英语翻译成法语。

translation = generator("translate English to French: Hello, how are you?")
print(translation)

输出可能是:“Bonjour, comment allez-vous?”


使用 GPT-2 进行文本生成

除了文本到文本模型,我们还可以使用其他模型,例如 GPT-2 进行文本生成。以下是加载 GPT-2 模型的示例:

gpt2_generator = pipeline("text-generation", model="gpt2")

加载模型后,我们可以基于初始短语生成文本。

initial_phrase = "The history of religion"
generated_text = gpt2_generator(initial_phrase, max_new_tokens=50)
print(generated_text)

模型将根据初始短语生成一段文本,例如:“The history of religion is thought to be deeply intertwined with the development of human societies...”


总结

本节课中,我们一起学习了如何使用 Hugging Face 的 Transformers 库进行机器学习探索。我们从安装依赖开始,逐步学习了如何加载模型、使用 pipeline 助手执行文本摘要、情感分析、问答和翻译等任务。此外,我们还探索了如何使用 GPT-2 模型进行文本生成。通过这些基础操作,你可以进一步探索更多模型和任务,为后续构建机器学习 API 打下坚实基础。


75:Hugging Face Datasets 库介绍 🧠

在本节课中,我们将要学习 Hugging Face 生态系统的另一个核心组件:datasets 库。这是一个用于动态加载数据集的库,其功能类似于其他动态加载库,如 ashopen data。我们将了解如何利用它来检索存储在 Hugging Face Hub 上的海量数据集。

概述

Hugging Face datasets 库允许你动态检索 Hugging Face 平台上的任何数据集。所有数据集都存储在 Hugging Face Hub 上。你可以将 Hugging Face Hub 理解为数据科学领域的 GitHub,它是分享数据集、机器学习模型以及进行模型微调等任务的中心平台。

环境准备与库导入

上一节我们介绍了 datasets 库的基本概念,本节中我们来看看如何在代码中使用它。首先,我们需要确保环境配置正确。

以下是在 Jupyter Notebook 中开始使用 datasets 库的初始步骤。核心是导入 load_datasetlist_datasets 这两个函数。

from datasets import load_dataset, list_datasets

探索可用数据集

在加载特定数据集之前,我们可以先查看 Hugging Face Hub 上有哪些数据集可用。list_datasets 函数可以帮助我们做到这一点。

以下是获取所有可用数据集列表的代码:

all_datasets = list_datasets()
print(f"可用数据集总数: {len(all_datasets)}")

执行上述代码后,你会发现有超过 9000 个数据集可用。这些数据集来源广泛,其中一部分由 Hugging Face 官方提供(名称中不包含斜杠 /),另一部分则由社区用户上传(名称中包含用户名,如 username/dataset_name)。

为了聚焦于官方数据集,我们可以进行筛选:

# 筛选出来自 Hugging Face 官方的数据集(名称中不包含‘/’)
hf_datasets = [ds for ds in all_datasets if '/' not in ds]
print(f"官方数据集示例(前10个): {hf_datasets[:10]}")

你会看到诸如 amazon_polarityamazon_reviews_multi 等大型知名数据集。

加载特定数据集

了解了数据集的概况后,现在我们来学习如何加载一个特定的数据集。这个过程非常直接,只需使用 load_dataset 函数并传入数据集的名称字符串即可。

例如,我们要加载名为 "movie_rationales" 的数据集:

dataset = load_dataset("movie_rationales")

首次运行此代码时,它会从网上下载数据集。下载完成后,数据集会被缓存到本地(通常位于 ~/.cache/huggingface/datasets 目录下)。这意味着再次加载同一数据集时速度会极快,无需重复下载。这对于处理大型数据集非常有用。

理解数据集结构

数据集加载后,我们需要理解其结构。datasets 库加载的对象并非直接的 Pandas DataFrame,而是一个具有特定结构的字典式对象。

运行以下代码查看数据集结构:

print(dataset)

你会发现输出中通常包含 trainvalidationtest 三个键。这表示数据集已经预先划分好了训练集、验证集和测试集。每个部分都显示了行数(num_rows)和包含的特征(features),例如 reviewlabelevidence 等。

转换为 Pandas DataFrame 进行分析

虽然 datasets 对象有其优势,但为了利用熟悉的工具进行数据分析,我们经常需要将其转换为 Pandas DataFrame。

以下是选择训练集并将其转换为 DataFrame 的步骤:

# 选择训练集
train_data = dataset['train']

# 转换为 Pandas DataFrame
df = train_data.to_pandas()

# 查看数据框的基本信息和前几行
print(f"数据形状: {df.shape}")
print(df.head())

现在,你可以使用所有熟悉的 Pandas 方法(如 df.describe()df.info())来探索和分析这个数据集了。

使用自定义或用户上传的数据集

datasets 库的强大之处不仅在于访问官方数据集,还在于能够加载用户上传的自定义数据集。

加载用户数据集的方法类似,只需在数据集名称前加上用户名即可:

# 假设用户 'username' 上传了一个名为 'my_dataset' 的数据集
user_dataset = load_dataset("username/my_dataset")

为何使用 Hugging Face Datasets?

你可能会问,为什么要使用这个库而不是直接管理数据文件?主要有以下几个原因:

以下是使用 datasets 库的主要优势:

  1. 动态加载与存储分离:你无需将大型数据集文件包含在项目代码仓库中,避免了仓库体积膨胀和 Git 托管限制。
  2. 便捷的数据集发现:轻松访问和尝试数千个预处理好的数据集,无需自己寻找和下载。
  3. 内置预处理与版本控制:许多数据集提供了清洗后的版本,且 Hub 支持数据集版本管理。
  4. 无缝的框架集成datasets 对象可以轻松与 PyTorch、TensorFlow 以及 Hugging Face transformers 库配合使用。

总结

本节课中我们一起学习了 Hugging Face datasets 库的核心用法。我们了解了如何列出和筛选 Hub 上的海量数据集,如何加载特定数据集并利用缓存机制提升效率,以及如何将加载的数据转换为 Pandas DataFrame 以便进行深入分析。此外,我们还探讨了使用该库管理数据在团队协作和项目维护上的优势。掌握这个工具将极大简化你在机器学习项目中的数据准备阶段。

76:Azure 开放数据集 🗃️

在本节课中,我们将要学习如何使用 Azure 开放数据集。这是一个类似于 Hugging Face 数据集的平台,它允许我们通过安装库来动态加载数据,从而无需在代码仓库中存储庞大的数据文件。

概述

上一节我们介绍了其他数据源,本节中我们来看看 Azure 开放数据集。它提供了经过整理和准备的数据集,我们可以像使用 Hugging Face 一样动态加载它们。此外,其文档中还包含了大量示例,方便我们进行交互和探索。

开始使用 Azure 开放数据集

要开始使用 Azure 开放数据集,首先需要安装相应的 Python 库。

以下是安装所需的包和版本信息:

  • 包名称azureml-opendatasets
  • 版本1.44.0

你可以通过以下命令安装:

pip install azureml-opendatasets==1.44.0

探索数据集:公共假期

现在,让我们通过一个 Jupyter Notebook 示例来探索数据。我们将首先加载“公共假期”数据集。

首先,我们需要导入必要的模块并获取数据。处理日期数据有时会比较棘手,因此我们会使用 Python 的 datetimedateutil 库来辅助。

# 导入必要的库
from azureml.opendatasets import PublicHolidays
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
import pandas as pd

# 定义日期范围:从去年今天到今日
today = datetime.today()
last_year = today - relativedelta(months=12)

# 加载公共假期数据集
holidays = PublicHolidays(start_date=last_year, end_date=today)
holidays_df = holidays.to_pandas_dataframe()

代码运行后,数据会被加载到一个 Pandas DataFrame 中。这样,我们就可以利用 Pandas 强大的功能进行数据探索。

以下是使用 Pandas 查看数据基本信息和前几行的方法:

# 查看数据集的统计摘要
print(holidays_df.describe())

# 查看前10行数据
print(holidays_df.head(10))

执行上述代码,我们可以看到数据包含国家/地区、假期名称等列,并且数据已经过一定程度的规范化处理。

探索数据集:糖尿病数据

除了公共假期,Azure 开放数据集还包含许多其他数据集,例如糖尿病数据集。其使用方法保持一致,非常方便。

以下是加载并查看糖尿病数据集前几行数据的代码:

# 假设已从 azureml.opendatasets 导入 Diabetes 数据集
# from azureml.opendatasets import Diabetes
diabetes_df = diabetes.to_pandas_dataframe()

# 查看前10行数据,而不是描述性统计
print(diabetes_df.head(10))

数据集中可能包含如 HbA1c(糖化血红蛋白)、BMI(身体质量指数)、BloodPressure(血压)等字段。

我们可以利用 Pandas 的查询功能来探索数据。例如,我们可以查询 BMI 小于 18(偏瘦)的样本:

# 查询 BMI 小于 18 的数据
healthy_bmi = diabetes_df[diabetes_df[‘BMI’] < 18]
print(healthy_bmi)

如果提高 BMI 的阈值,例如查询 BMI 小于 30 的数据,返回的条目数会显著增加。这让我们能够快速地对数据分布有一个直观了解。

总结

本节课中我们一起学习了 Azure 开放数据集的基本用法。我们了解到它是一个提供高质量、可动态加载数据集的平台。通过安装 azureml-opendatasets 库,我们可以轻松地将数据加载为 Pandas DataFrame,并利用熟悉的工具进行数据探索和分析。这种方法避免了本地存储大型数据文件的麻烦,为机器学习项目的数据准备阶段提供了便利。

77:API与SDK回顾 🧠

在本节课中,我们将回顾在机器学习运维日常工作中可能接触到的几种API和SDK。我们将学习如何动态加载数据、与Hugging Face API交互,以及使用Azure开放数据和Azure CLI。这些工具对于处理大规模数据和构建自动化流程至关重要。


动态加载数据 📂

上一节我们介绍了课程概述,本节中我们来看看动态加载数据的概念。动态加载数据至关重要,因为它允许你与海量数据进行交互,而这些数据通常无法直接包含在GitHub等代码仓库中。这为你提供了在流程中交互、处理和调整数据的能力,从而基于此构建自动化。

核心概念:动态加载通常意味着数据不存储在代码库中,而是在运行时从外部源(如数据库、API或云存储)获取。一个简单的伪代码示例如下:

# 示例:从远程URL动态加载数据
import pandas as pd
data_url = "https://example.com/large_dataset.csv"
df = pd.read_csv(data_url)

Hugging Face API 🤗

接下来,我们了解了一下Hugging Face API,并与它的库进行了一些交互。我们理解了如何安装它并使用它。

以下是使用Hugging Face transformers 库的基本步骤:

  1. 安装库:使用pip安装必要的包。
    pip install transformers
    
  2. 加载预训练模型:使用库提供的API加载模型和分词器。
    from transformers import pipeline
    classifier = pipeline('sentiment-analysis')
    
  3. 进行预测:使用加载的模型对新数据进行推理。
    result = classifier("I love this course!")
    print(result)
    

Azure开放数据与Azure CLI ☁️

与Hugging Face类似,我们还接触了Azure开放数据和Azure CLI。

以下是开始使用Azure CLI的简要步骤:

  1. 安装Azure CLI:根据你的操作系统,从微软官方文档下载并安装。
  2. 登录账户:在终端中使用命令登录你的Azure账户。
    az login
    
  3. 访问开放数据集:使用CLI命令或SDK(如Python的 azure-storage-blob 库)来列出或下载Azure上托管的开放数据集。
    # 示例:列出存储容器(需先配置好上下文)
    az storage container list --account-name <your_account_name>
    

总结 📝

本节课中我们一起学习了机器学习运维中关键的API与SDK工具。我们探讨了动态加载数据的重要性,它使我们能够处理超出本地存储限制的大型数据集。我们实践了如何与Hugging Face API交互,利用其丰富的预训练模型。最后,我们介绍了Azure开放数据平台以及通过Azure CLI管理云资源的基本方法。掌握这些工具将极大地提升你构建高效、可扩展的机器学习管道的能力。

78:使用Python构建命令行工具 🛠️

在本节课中,我们将学习如何使用Python构建强大的命令行工具。这将帮助你将自动化能力提升到一个新的水平。

我们将探讨几种不同的实现策略,从简单的单文件脚本到更高级的、包含依赖声明和打包分发的完整工具。最后,我们会将这些概念应用于解决一个实际的机器学习问题。


构建Python命令行工具的策略 📋

上一节我们介绍了本课程的目标,本节中我们来看看构建命令行工具的具体策略。

Python提供了多种方式来创建命令行工具,你可以根据项目的复杂度和分发需求来选择。

以下是几种主要的构建策略:

  • 单文件脚本:这是最简单的方式,适合快速、一次性的任务。你只需在一个Python文件中编写逻辑,并通过sys.argvargparse库来处理命令行参数。
  • 声明依赖的进阶工具:对于更复杂的工具,你可能需要依赖外部库。这时,你需要使用如requirements.txtpyproject.toml文件来管理依赖,确保工具可以在不同环境中运行。
  • 打包与分发:如果你希望将工具分享给他人或在其他系统上安装,你需要学习如何打包你的Python项目。这通常涉及创建setup.py或使用现代工具如setuptoolspip进行打包。

应用于机器学习问题 🤖

前面我们讨论了构建命令行工具的一般方法,现在让我们看看如何将这些概念应用于解决实际的机器学习问题。

一个典型的应用场景是创建一个命令行工具来自动化机器学习工作流中的某个环节,例如数据预处理、模型训练或结果评估。

以下是构建此类工具的核心步骤:

  1. 定义问题与接口:明确你的工具要解决什么机器学习任务,并设计清晰的命令行参数。例如,一个工具可能需要接收数据路径、模型类型和输出目录作为参数。
    • 代码示例:使用argparse定义参数
      import argparse
      parser = argparse.ArgumentParser(description='训练一个分类模型。')
      parser.add_argument('--data_path', type=str, required=True, help='训练数据文件路径')
      parser.add_argument('--model_type', type=str, default='logistic', help='要使用的模型类型')
      parser.add_argument('--output_dir', type=str, default='./results', help='结果输出目录')
      args = parser.parse_args()
      
  2. 实现核心逻辑:在工具中编写完成机器学习任务的代码,例如加载数据、训练模型和保存结果。
  3. 处理依赖与打包:确保所有必需的库(如scikit-learn, pandas, numpy)都在配置文件中声明,并将整个项目打包,使其易于安装和运行。
    • 公式/概念:依赖管理文件 requirements.txt
      scikit-learn>=1.0
      pandas>=1.4
      numpy>=1.21
      
  4. 测试与分发:在不同环境下测试你的命令行工具,确保其稳定运行,然后通过PyPI或私有仓库进行分发。

本节课中我们一起学习了使用Python构建命令行工具的多种策略,从简单的脚本到可打包分发的完整应用,并探讨了如何将这些技术应用于自动化机器学习任务。掌握这些技能能显著提升你的工作效率和代码复用能力。

79:创建单文件脚本 📄

在本节课中,我们将学习如何创建一个简单的单文件Python脚本,该脚本能够从命令行接收参数,并利用Python内置模块处理JSON文件的格式化。我们将重点介绍如何使用 sys 模块来解析命令行参数,以及如何通过条件判断 if __name__ == "__main__" 来确保脚本既能作为独立程序运行,也能作为模块被安全导入。


脚本基础结构

我们从一个非常小的脚本开始。它包含一个 main 函数和一个名为 formatter 的辅助函数。formatter 函数的功能是接受一个来自命令行的参数,这个参数是一个文件路径。脚本会打开该路径下的文件并读取内容,然后进行格式化。它期望处理一个类JSON文件。

以下是一个示例文件,其内容非常紧凑,我们将使用Python将其格式化得更加美观。

import json
import sys

def formatter(path):
    """读取并格式化JSON文件。"""
    with open(path, 'r') as file:
        data = json.load(file)
    formatted_json = json.dumps(data, indent=4, sort_keys=True)
    print(formatted_json)

def main(path):
    """主函数,调用格式化器。"""
    formatter(path)

if __name__ == "__main__":
    # 使用sys.argv获取命令行参数
    main(sys.argv[-1])

解析命令行参数

上一节我们介绍了脚本的基本结构,本节中我们来看看如何让脚本从命令行接收参数。我们将使用Python内置的 sys 模块来实现这一功能。

sys.argv 是一个列表,它包含了从命令行传递给Python脚本的所有参数。列表的第一个元素 sys.argv[0] 是脚本的名称,后续元素是用户提供的参数。

例如,如果我们这样执行脚本:

python script.py argument1 argument2

那么 sys.argv 将是:

['script.py', 'argument1', 'argument2']

创建一个简单的命令行工具的第一个技巧就是使用 sys.argv 模块。我们将用它来传递参数。

在本例中,我们只希望处理一个参数。我们将看到如何实现这一点。

实现命令行交互

我们将使用Python中一个非常实用的结构,它允许我们将脚本转换为能在终端处理输入的工具。这个结构就是 if __name__ == "__main__":

这个条件判断允许我们将参数传递给 main 函数。我们将使用 sys.argv,并选择该列表中的最后一个参数传递给 main 函数。

这意味着我们不关心命令行中传递了多少个参数,我们只选择最后一个。例如,如果传递了 argument1argument2,那么 argument2 将被选中。

这样,选中的参数(例如 argument2)将被传递到第11行的 main 函数中。main 函数随后会调用 formatter 来读取并格式化我们提供的示例JSON文件。

你可以看到,这里使用了 json.dumps,它允许你使用所有功能来漂亮地重新格式化JSON。

再次强调,你需要使用 if __name__ == "__main__": 这个条件。这样,只有当脚本在终端作为命令行工具运行时,才会触发相应的代码逻辑,从而避免副作用。如果你是在导入这段代码,你可以安全地导入 formatter 甚至 main 函数而不会产生任何副作用。但如果你在命令行运行此脚本,这个条件就会被触发。

运行示例

现在,我们将在终端中使用Python 3运行 jformat.py 脚本,并传入示例JSON文件。

python3 jformat.py examples/example.json

运行后,脚本会格式化我们的参数。具体过程是:命令行中传递的最后一个参数(在本例中是 examples/example.json)被作为路径传递给 main 函数。然后脚本读取该文件,并打印出经过JSON模块美化格式化的结果。

我们的原始示例JSON文件看起来非常紧凑,经过处理后变得结构清晰、易于阅读。这就是使用Python的 sys 模块在命令行中解析单个参数的方法。


本节课中我们一起学习了如何创建一个单文件Python命令行脚本。我们掌握了使用 sys.argv 来获取命令行输入,利用 if __name__ == "__main__": 来区分脚本的运行模式,以及使用 json 模块来格式化JSON文件。这些是构建更复杂命令行工具和自动化脚本的基础。

80:使用argparse框架 🛠️

在本节课中,我们将学习如何使用Python的argparse库来为命令行脚本添加参数解析功能。我们将以一个JSON格式化脚本为例,逐步演示如何初始化解析器、添加标志参数,并理解其工作机制。


导入与初始化ArgumentParser

上一节我们介绍了命令行工具的基本概念,本节中我们来看看如何开始使用argparse

首先,我们需要导入argparse模块。然后,我们将初始化一个ArgumentParser类的实例。这个实例将作为我们参数解析的核心对象。

import argparse

parser = argparse.ArgumentParser(description="J format tool")
args = parser.parse_args()

初始化ArgumentParser时,可以提供一个简短的描述。这个描述是可选的,但它能为我们自动生成帮助菜单。parse_args()方法会处理命令行传入的参数。


测试基础帮助菜单

初始化完成后,我们就可以在终端中测试脚本了。

以下是测试步骤:

  1. 打开终端。
  2. 运行命令 python3 j_format.py -h
  3. argparse会自动识别 -h 参数并显示帮助信息。

帮助菜单会显示我们之前提供的描述信息“J format tool”,以及默认的帮助选项说明。仅用这两行代码,我们就获得了一个功能完整的帮助菜单。


添加标志参数(Flag)

现在我们已经有了解析器实例,接下来可以为其添加具体的参数。首先,我们添加一个控制排序功能的标志参数。

在我们的原始脚本中,有一个控制是否对输出键进行排序的布尔变量。我们将利用argparse将其转换为一个命令行标志。

parser.add_argument('--sort', action='store_true', help='set the sorting')

add_argument方法用于添加参数。对于标志参数,我们使用action='store_true'。这意味着当在命令行中使用--sort时,该参数的值将被设为True;如果不使用,则其值为False


验证标志参数的功能

添加参数后,我们可以再次运行脚本来验证其效果。

以下是验证过程:

  1. 再次运行 python3 j_format.py -h,可以看到帮助菜单中已经列出了--sort标志及其说明。
  2. 为了查看参数的实际值,我们可以在脚本中打印args.sort
  3. 运行 python3 j_format.py(不带参数),args.sort的值是False
  4. 运行 python3 j_format.py --sort(带参数),args.sort的值变为True

通过这种方式,我们成功地将一个布尔开关转换为了命令行标志,无需在代码中手动设置TrueFalse


总结

本节课中我们一起学习了argparse框架的基础用法。我们首先导入了模块并初始化了参数解析器,然后测试了自动生成的帮助菜单。接着,我们学习了如何添加一个标志参数,并使用action='store_true'来将其定义为布尔开关。最后,我们验证了该参数能够根据命令行输入正确切换TrueFalse值。这为构建更复杂的命令行工具打下了基础。

81:声明依赖项 📦

在本节课中,我们将要学习如何在Python项目中清晰地声明和管理依赖项。这是确保你的代码能在任何环境中稳定运行的关键一步。


声明依赖项的常见方式

在命令行工具中,清晰地声明依赖项可以通过几种不同的方式实现。其中最常见的一种是使用 requirements.txt 文件。

以下是一个Python项目的示例,它包含了许多不同的文件,但最重要的是位于此处的 requirements.txt 文件。

ls

通过列出文件,你可以看到 requirements.txt 文件就在这里。你可能已经见过它了,让我们快速浏览一下它的内容。


理解 requirements.txt 文件

requirements.txt 文件的内容通常如下所示:

package_name==1.2.3
another_package>=4.5.6
some_package

它包含包名版本号。例如,使用两个等号 == 意味着该包被“锁定”到了一个特定的版本。文件中可以使用各种不同的修饰符来指定版本范围。如果你不指定版本,例如只写 some_package,那么它将安装该包的最新版本。


创建虚拟环境并安装依赖

与所有Python项目一样,你首先需要创建一个Python虚拟环境来隔离依赖。

python -m venv .venv

这条命令会在当前工作目录下创建一个名为 .venv 的隐藏子目录作为虚拟环境。接着,我们需要激活这个虚拟环境。

source .venv/bin/activate

激活后,命令行提示符通常会发生变化,表示你现在已处于虚拟环境中。


使用 pip 安装依赖

现在,我们可以使用 pip 工具来安装 requirements.txt 中列出的所有依赖。pip 是Python的包安装工具。

pip install -r requirements.txt

执行此命令后,pip 会逐行读取 requirements.txt 文件,开始收集每个指定的包及其依赖项,然后依次安装它们。这个过程可能需要一些时间。


验证安装结果

安装完成后,我们可以验证这些包是否已成功安装并可用。

启动Python解释器:

python

在Python交互式环境中,尝试导入一个我们知道已安装的包,例如 pytest

import pytest

如果导入成功且没有报错,则证明依赖项已正确安装。


锁定版本与非锁定版本的区别

让我们再次查看 requirements.txt 文件,以理解锁定版本和未锁定版本的区别。

如果你在文件中只写:

pytest

这表示未锁定版本,pip 会安装 pytest 的最新可用版本。

如果你想锁定版本,则需要明确指定:

pytest==7.1.2

这样就会强制安装该特定版本。在之前的示例中,未指定版本时安装的就是当时最新的 7.1.2 版本。


总结

本节课中,我们一起学习了在Python项目中声明依赖项的核心方法。我们介绍了如何使用 requirements.txt 文件来列出项目依赖,探讨了锁定特定版本与使用最新版本的区别,并完整演示了创建虚拟环境、使用 pip install -r requirements.txt 安装依赖以及验证安装结果的流程。虽然管理Python依赖还有其他方式,但 requirements.txt 是最常见和基础的一种。掌握它,是构建可复现的Python项目环境的第一步。

82:使用 Click 框架构建命令行工具 🛠️

在本节课中,我们将学习如何使用 Python 的 Click 框架,将一个简单的 JSON 格式化工具从使用 argparse 迁移到 Click。我们将重点学习如何定义命令、添加位置参数以及创建布尔标志。


概述

我们将改造一个名为 j_format 的 JSON 格式化命令行工具。该工具最初使用 Python 内置的 argparse 模块,现在我们将使用 Click 框架重构它,使其更简洁、功能更强大。


导入 Click 并设置基础命令

首先,我们需要导入 Click 库。请确保已通过虚拟环境安装了 Click(例如版本 8.x)。

import click

接下来,我们使用装饰器将主函数声明为一个 Click 命令。这是 Click 框架的核心模式。

@click.command()
def main():
    pass

添加位置参数

上一节我们介绍了如何创建一个基础的 Click 命令。本节中,我们来看看如何为其添加一个位置参数。

我们使用 @click.argument() 装饰器来添加一个名为 path 的位置参数。这个参数将被传递给主函数。

@click.command()
@click.argument('path')
def main(path):
    # 主函数逻辑将在这里处理 path 参数
    pass

此时,如果在终端运行命令时不提供 path 参数,Click 会自动显示帮助信息并提示缺少该参数。


增强参数:指定类型和验证

仅仅接受一个字符串参数还不够健壮。我们可以通过指定参数类型来增加验证功能。

以下是增强 path 参数的方法,我们指定它必须是一个存在的文件路径。

@click.command()
@click.argument('path', type=click.Path(exists=True))
def main(path):
    # 现在 path 参数会被验证是否存在
    pass

这样配置后,如果用户提供了一个不存在的文件路径,Click 会自动报错,提示路径不存在,这使我们的工具更加可靠。


添加布尔标志选项

现在我们的工具可以接受文件路径了。接下来,我们为其添加一个功能开关:一个用于控制是否对 JSON 键进行排序的布尔标志。

在 Click 中,我们使用 @click.option() 装饰器来添加标志或选项。这与位置参数 argument 不同。

以下是添加 --sort 标志的方法:

@click.command()
@click.argument('path', type=click.Path(exists=True))
@click.option('--sort', is_flag=True, help='Sort the JSON keys alphabetically.')
def main(path, sort):
    # sort 参数是一个布尔值,当使用 --sort 标志时为 True
    if sort:
        # 执行排序逻辑
        pass
    else:
        # 不执行排序
        pass

关键点在于 is_flag=True 参数,它告诉 Click 这是一个开关标志,不需要额外的值。当用户在命令行中输入 --sort 时,sort 变量将被设置为 True


测试工具功能

让我们在终端中测试工具的功能。

  1. 获取帮助:运行 python j_format.py --help,现在可以看到 --sort 选项出现在帮助信息中。
  2. 不使用排序:运行 python j_format.py examples/example.json,JSON 输出将保持原始键的顺序。
  3. 启用排序:运行 python j_format.py examples/example.json --sort,JSON 输出的键将按字母顺序排列。

通过这个简单的例子,可以看到 Click 极大地简化了命令行参数和标志的处理逻辑。


总结

本节课中我们一起学习了使用 Click 框架构建 Python 命令行工具的基础知识:

  1. 我们使用 @click.command() 装饰器将函数声明为命令。
  2. 我们使用 @click.argument() 添加了位置参数,并通过 type=click.Path(exists=True) 增强了其验证功能。
  3. 我们使用 @click.option() 并设置 is_flag=True 添加了一个布尔标志(--sort),用于控制工具的行为。

Click 通过装饰器提供了清晰、简洁的 API,使得创建功能丰富且用户友好的命令行工具变得非常简单。

83:打包项目 📦

在本节课中,我们将学习如何将一个Python项目打包成可分发和安装的命令行工具。我们将重点介绍如何定义项目依赖以及如何通过配置入口点来创建可执行脚本。


现在我们已经准备好开始打包我们的项目。我们有一个非常基础的 setup.py 文件,但它并不完整,因为它缺少一些关键信息。

我们之前安装了 clickcolorama 这两个库。如果我想将项目分享给其他人,就会遇到问题,因为这些依赖关系没有被声明。

我们需要在 setup.py 文件中定义这些依赖。定义依赖的方法是添加一个新参数。

我们将在 setup.py 中添加一个名为 install_requires 的新行。install_requires 接受一个列表。我们将在这个列表中填入 clickcolorama

你还可以在这里指定特定的版本号,例如 click==8.0.0。目前,我们暂时不固定版本号,但这是定义依赖的方式。你可能会问,这和 requirements.txt 文件有什么区别?如果你要分发项目,通常会使用 install_requires 来定义。有些人喜欢额外添加一行代码,从 requirements.txt 文件中读取依赖列表,然后传递给 install_requires,这也是完全可行的。但在本例中,我们没有使用 requirements.txt,所以我们直接在 setup.py 中定义。

接下来一个重要的问题是:我们有一个包含文件的目录,现在它算是一个命令行工具吗?我们通过定义入口点来实现这一点。

我们将定义入口点。入口点需要一个 console_scripts 部分,它负责从一个特定的路径、文件和函数创建可执行文件。

在本例中,我们希望 jformat 成为我们可执行文件的名称。我们将它设置为等于 jformat 模块中的 main.py 文件里的 main 函数。

保存文件后,我们打开终端。如果我们输入 which jformat,会发现它不可用,因为它还没有安装到我们的系统路径中。

为了开发目的,我们将在虚拟环境中运行 python setup.py develop 命令。这将在我们的虚拟环境中安装这个工具。

你可以看到整个安装路径,并且它发现我们声明了一些依赖,并将 jformat 脚本添加到了仓库中。这非常完美。

现在,如果我们输入 jformat,它已经存在了。输入 jformat --help,就会显示我们想要打包和分发的工具。这就是你如何用几个简单的选项(如入口点和在 setup 函数的 install_requires 参数中定义依赖)来打包一个工具。

再次说明,你完全可以使用 requirements.txt 文件,但这不是必须的,它是可选的。在本例中,setup.py 工作得很好。


本节课中我们一起学习了如何打包Python项目。我们介绍了如何在 setup.py 文件中声明项目依赖,以及如何通过配置 console_scripts 入口点来创建可执行命令行工具。这使得我们的项目可以被轻松地分发和安装。

84:使用CLI工具解决机器学习问题 📝

在本节课中,我们将应用之前学到的命令行工具和API知识,构建一个实用的机器学习工具。具体来说,我们将创建一个命令行工具,它能更好地与Hugging Face库和外部API协同工作,以实现文本摘要功能。


概述

我们将构建一个名为 summarize 的Python命令行工具。该工具能够从指定的URL或本地文件中读取文本,并利用预训练的机器学习模型自动生成摘要。通过这个过程,你将学习如何将机器学习模型集成到命令行应用中。


环境与依赖设置

首先,我们需要安装必要的Python库。我们将使用 transformers 库来调用Hugging Face的模型,使用 click 框架来构建易用的命令行界面,并使用 TensorFlow 来支持模型的推理过程。

以下是安装依赖的步骤:

  1. 创建一个新的Python虚拟环境。
  2. 使用 pip 安装以下包:
    pip install transformers click tensorflow requests beautifulsoup4
    

工具设计与核心代码

上一节我们介绍了工具的目标和所需环境,本节中我们来看看工具的核心代码是如何设计的。

主要的逻辑集中在 main.py 文件中。我们首先导入必要的库。

import click
from transformers import pipeline
import requests
from bs4 import BeautifulSoup
import warnings
warnings.filterwarnings(‘ignore’)

接下来,我们定义两个辅助函数。第一个函数 extract_from_url 负责从给定的URL中抓取并提取文本内容。

def extract_from_url(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, ‘html.parser’)
    paragraphs = soup.find_all(‘p’)
    text = ‘ ‘.join([p.get_text() for p in paragraphs])
    return text

第二个函数 process 是工具的核心,它负责加载摘要模型并对输入的文本进行处理。

def process(text):
    summarizer = pipeline(“summarization”, model=“t5-small”, max_length=180)
    result = summarizer(text)
    return result[0][‘summary_text’]

最后,我们使用 click 框架来定义命令行接口。它接受两个参数:--url--file

@click.command()
@click.option(‘--url’, default=None, help=‘URL of the webpage to summarize.’)
@click.option(‘--file’, default=None, help=‘Path to the text file to summarize.’)
def main(url, file):
    if url:
        input_text = extract_from_url(url)
    elif file:
        with open(file, ‘r’) as f:
            input_text = f.read()
    else:
        click.echo(‘Please provide either a --url or a --file.’)
        return

    summary = process(input_text)
    click.echo(summary)

安装与使用工具

代码编写完成后,我们需要将其安装为系统可用的命令行工具。

在项目根目录下,运行以下命令进行本地安装:

python setup.py develop

安装成功后,你就可以在终端中使用 summarize 命令了。首先,可以通过帮助菜单查看用法。

以下是该工具的基本命令选项:

  • --help:查看工具的使用说明和参数列表。
  • --url <网页地址>:指定一个需要摘要的网页URL。
  • --file <文件路径>:指定一个需要摘要的本地文本文件。

实践演示

现在,让我们用两个例子来演示工具的实际效果。

首先,我们尝试对一个关于MLOps的微软官方文档网页进行摘要。在终端中执行:

summarize --url https://docs.microsoft.com/en-us/azure/machine-learning/concept-mlops

工具会先抓取网页内容,提取段落文本,然后调用T5模型生成摘要。你会看到模型输出的摘要,概括了MLOps的核心原则和实践。

接下来,我们尝试对一个本地文件进行摘要。假设我们有一个名为 mlops_wikipedia.txt 的文件,其中包含了维基百科上关于MLOps的定义。

执行以下命令:

summarize --file ./examples/mlops_wikipedia.txt

工具会读取文件内容,并生成一个简洁的摘要,例如:“MLOps是一套旨在可靠且高效地将机器学习模型部署到生产环境的实践,是机器学习与DevOps持续开发实践的复合词。”


总结

本节课中我们一起学习了如何构建一个机器学习命令行工具。我们整合了Hugging Face的预训练模型、网页抓取技术和click命令行框架,创建了一个能够从URL或文件生成文本摘要的实用工具。

这个工具只是一个起点。你可以进一步优化它,例如:

  • 改进模型配置以减少警告信息。
  • 增强网页文本提取的准确性。
  • 添加更多功能选项,如指定摘要长度、选择不同模型等。

希望这个项目能为你打下坚实基础,启发你构建出更多属于自己的、功能强大的机器学习相关工具。

85:命令行工具自动化策略回顾 🛠️

在本节课中,我们将回顾构建命令行工具时可应用的不同策略。我们将从最简单的单文件脚本开始,逐步探讨更复杂的、需要外部依赖的工具构建方法。


上一节我们介绍了命令行工具的重要性,本节中我们来看看构建这些工具的具体策略。

构建命令行工具时,可以根据项目的复杂度和需求选择不同的策略。以下是几种常见的策略:

  1. 单文件脚本
    这是最简单直接的方式。所有代码逻辑都写在一个Python文件中,通过 if __name__ == "__main__": 块来执行。它适合快速原型或简单的自动化任务。

  2. 使用 argparse 模块
    对于需要处理命令行参数的工具,Python内置的 argparse 模块是标准选择。它可以轻松定义位置参数、可选参数,并自动生成帮助信息。

    import argparse
    parser = argparse.ArgumentParser(description='描述你的工具')
    parser.add_argument('filename', help='输入文件名')
    args = parser.parse_args()
    
  3. 利用 setuptools 打包
    当工具变得复杂,或需要分发给他人使用时,可以使用 setuptools 进行打包。通过在 setup.py 文件中定义入口点,可以将工具安装为系统命令。

    # setup.py 示例
    from setuptools import setup
    setup(
        name='my_tool',
        entry_points={
            'console_scripts': [
                'my-command=my_tool.cli:main',
            ],
        },
    )
    
  4. 管理外部依赖
    对于需要与外部API(例如Hugging Face的API或SDK)交互的复杂工具,必须声明和管理外部依赖。这通常在 requirements.txt 文件或 setup.pyinstall_requires 部分完成,以确保使用特定版本的库。


本节课中我们一起学习了构建Python命令行工具的多种策略。你现已掌握从选择单文件脚本到管理复杂外部依赖的方法,能够根据项目需求选择最佳策略,从而构建强大的命令行工具,并将自动化流程提升到新的水平。

86:构建机器学习API 🚀

在本节课中,我们将学习如何构建强大的HTTP API。我们将简要对比两个非常流行的Python Web框架:Flask和FastAPI。它们非常相似,但也存在一些细微差别。我们将看到使用这些框架构建强大API的便利性,并探讨如何将机器学习模型集成到其中,从而将它们作为HTTP API对外提供服务。

框架对比:Flask 与 FastAPI

上一节我们介绍了构建机器学习API的目标,本节中我们来看看两个核心工具:Flask和FastAPI。

这两个框架都是用于构建Web应用和API的流行选择。它们都基于Python,但设计哲学和特性集有所不同。

以下是它们的一些主要特点:

  • Flask:一个轻量级的“微框架”,以其简洁和灵活性著称。它提供了构建Web应用所需的核心功能,其他高级功能可以通过扩展添加。
  • FastAPI:一个现代、快速(高性能)的Web框架,专为构建API而设计。它内置了对OpenAPI和JSON Schema的支持,能自动生成交互式API文档。

构建与集成API

了解了基础框架后,我们现在来看看如何使用它们构建API,并集成机器学习模型。

我们将看到,利用这些框架可以非常便捷地搭建API服务。关键在于定义路由(URL端点)和处理函数,这些函数接收请求并返回响应。

以下是将机器学习模型暴露为HTTP API的基本步骤:

  1. 加载模型:在应用启动时,将训练好的机器学习模型(例如一个pickle文件或joblib文件)加载到内存中。
  2. 定义预测端点:创建一个API端点(例如/predict),它通过HTTP POST请求接收输入数据。
  3. 处理请求与推理:在端点的处理函数中,解析请求中的数据,将其转换为模型所需的格式,然后调用模型进行预测。
  4. 返回结果:将模型的预测结果封装成JSON格式,并通过HTTP响应返回给客户端。

例如,一个简单的FastAPI预测端点可能如下所示:

from fastapi import FastAPI
from pydantic import BaseModel
import joblib

# 加载模型
model = joblib.load("model.pkl")

app = FastAPI()

# 定义输入数据的结构
class PredictionInput(BaseModel):
    feature1: float
    feature2: float

# 定义预测端点
@app.post("/predict")
def predict(input_data: PredictionInput):
    # 将输入数据转换为模型所需的格式(如numpy数组)
    features = [[input_data.feature1, input_data.feature2]]
    # 进行预测
    prediction = model.predict(features)
    # 返回预测结果
    return {"prediction": prediction.tolist()}

本节课中我们一起学习了如何构建用于机器学习的HTTP API。我们比较了Flask和FastAPI这两个Python Web框架的特点,并概述了将机器学习模型集成到API中的基本流程。通过这种方式,我们可以让模型通过网络提供服务,便于其他系统调用和集成。

87:Flask 框架简介 🚀

在本节课中,我们将学习 Flask 框架的基础知识。Flask 是一个轻量级的 Python Web 框架,用于快速构建 Web 应用程序和 API。我们将通过一个简单的示例,了解如何初始化 Flask 应用、定义路由、处理请求以及运行服务器。


Flask 应用的基本结构

首先,我们来看一个非常简单的 Flask 应用程序。它没有复杂的打包,仅包含一个 Docker 文件和一个容器化流程。这个示例展示了使用 Flask 构建应用的基本形态。

要使用 Flask,首先需要安装它。安装完成后,你可以导入 Flask 并开始通过定义特定的路由来暴露函数。

初始化 Flask 应用

以下代码展示了如何初始化一个 Flask 应用:

from flask import Flask

app = Flask(__name__)

这里,我们导入了 Flask 模块,并创建了一个 Flask 应用实例。__name__ 是一个常用的参数,它告诉 Flask 当前模块的名称。你也可以将这个实例命名为其他名称,例如 my_flask_app,但使用 app 是社区常见的做法。

定义路由与处理函数

创建了 app 对象后,你就可以使用它提供的装饰器来定义路由。装饰器是一种特殊的语法,用于修改函数的行为。

以下是定义路由的示例:

@app.route('/')
def home():
    return '<h1>Hello, World!</h1>'

这个 @app.route('/') 装饰器意味着,当用户访问网站的根路径(例如 example.com/)时,Flask 将调用 home 函数来处理该请求。函数返回的字符串可以是纯文本,也可以是简单的 HTML,浏览器会将其渲染出来。

处理错误

除了正常的路由,Flask 也允许你定义错误处理。例如,你可以强制触发一个错误,并返回自定义的错误信息。

以下是如何定义一个返回错误的路由:

from flask import abort

@app.route('/error')
def trigger_error():
    abort(500, description="Some error text")

这里,我们使用了 Flask 的 abort 辅助函数。它可以接受任何 HTTP 状态码和可选的错误描述信息。当用户访问 /error 路径时,服务器将返回一个 500 内部服务器错误,并显示“Some error text”。


运行 Flask 应用

定义了应用后,下一步就是运行它。在开发环境中,我们可以直接使用 Python 运行 Flask 应用。

配置与运行服务器

在 Flask 应用的末尾,我们通常会有以下代码来启动服务器:

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=8000)

这段代码的含义是:

  • debug=True:启用调试模式。注意:切勿在生产环境中使用此设置,因为它不安全。 但在本地开发时,它非常有用,可以自动重载代码并显示详细的错误信息。
  • host='0.0.0.0':使服务器监听所有可用的网络接口。
  • port=8000:指定服务器运行的端口号为 8000。选择 80 端口(HTTP默认端口)在本地调试时可能会有权限问题,因此使用 8000 等端口更为方便。

实际运行步骤

上一节我们介绍了代码结构,本节中我们来看看如何实际运行它。运行一个 Flask 应用通常遵循以下步骤:

  1. 创建虚拟环境:这是一个隔离的 Python 环境,用于管理项目依赖。
    python -m venv .venv
    
  2. 激活虚拟环境
    • 在 Windows 上:.venv\Scripts\activate
    • 在 macOS/Linux 上:source .venv/bin/activate
  3. 安装依赖:项目通常会有一个 requirements.txt 文件列出所有需要的包。
    pip install -r requirements.txt
    
  4. 运行应用:执行主 Python 文件。
    python web_app.py
    

执行后,终端会显示警告信息,提醒你不要在生产环境使用调试模式,同时会输出服务器运行的地址(例如 http://127.0.0.1:8000)。

测试应用

服务器运行后,你可以在浏览器中打开相应的地址进行测试:

  • 访问根路径 /,你会看到 “Hello, World!” 的标题。
  • 访问一个未定义的路径(如 /fake),Flask 会自动返回一个 404 错误页面。
  • 访问 /error 路径,则会触发我们定义的 500 错误,并显示“Some error text”。

生产环境部署说明

我们目前使用 app.run() 的方式运行服务器,这只适用于开发和测试。对于更高级的 Flask 应用或生产环境,建议使用专业的 WSGI HTTP 服务器,例如:

  • Gunicorn
  • uWSGI
  • 或者通过 Apache、Nginx 等 Web 服务器进行反向代理

这些服务器能提供更好的性能、稳定性和安全性。具体选择哪种方式,取决于你的项目需求和部署环境。


总结 🎯

本节课中,我们一起学习了 Flask 框架的基础知识。我们了解了如何通过几行代码初始化一个 Flask 应用,如何使用 @app.route() 装饰器来定义路由和处理函数,以及如何利用 abort() 函数返回 HTTP 错误。最后,我们演示了如何在开发环境中配置并运行 Flask 服务器,并简要提及了生产环境的部署选项。这为你使用 Flask 构建 Web 应用打下了坚实的基础。

88:使用 Flask 构建 API 🚀

在本节课中,我们将学习如何使用 Flask 框架构建一个简单的机器学习 API。我们将创建一个能够接收文本输入并返回情感分析预测结果的端点。


概述

我们将使用 Flask 创建一个 Web API,该 API 会加载一个预训练的 RoBERTa 序列分类模型,并通过一个 /predict 路由来接收 POST 请求,对传入的文本进行情感分析。


初始化 Flask 应用

首先,我们需要初始化 Flask 应用。这是构建任何 Flask 应用的起点。

from flask import Flask, request, jsonify
app = Flask(__name__)

app = Flask(__name__) 这行代码创建了 Flask 应用的一个实例。__name__ 参数帮助 Flask 确定应用的位置。


加载 ONNX 运行时与模型

上一节我们介绍了如何初始化应用,本节中我们来看看如何加载模型。我们将使用 ONNX 运行时来处理模型推理。

import onnxruntime as ort
session = ort.InferenceSession(‘roberta-sequence-classification-9.onnx’)

这里,我们导入了 ONNX 运行时库,并创建了一个推理会话,用于加载名为 roberta-sequence-classification-9.onnx 的预训练模型。这个模型文件需要预先下载并放在项目目录中。


创建辅助函数

在加载模型之后,我们需要一个辅助函数来帮助处理输入数据。

以下是 to_numpy 辅助函数,它将输入数据转换为模型所需的 NumPy 数组格式。

import numpy as np
def to_numpy(tensor):
    return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()

这个函数检查张量是否需要梯度计算,并将其转换为 NumPy 数组,以便 ONNX 运行时能够处理。


定义主页路由

接下来,我们定义一个简单的路由作为应用的主页。

@app.route(‘/’)
def home():
    return ‘<h1>Roberta Sentiment Analysis</h1>’

当用户访问根路径 / 时,应用会返回一个简单的 HTML 标题,显示“Roberta Sentiment Analysis”。


构建预测路由

现在,我们来构建核心的预测路由。这是 API 接收数据并返回预测结果的地方。

以下是 /predict 路由的定义,它只接受 POST 方法。

@app.route(‘/predict’, methods=[‘POST’])
def predict():
    # 预测逻辑将在这里实现
    pass

我们使用 @app.route 装饰器将 /predict 路径映射到 predict 函数,并指定它只响应 POST 请求。


处理请求数据

在预测函数内部,我们需要从请求中提取并处理数据。

以下是处理传入 JSON 请求数据的代码。

    data = request.get_json()
    input_text = data[0]

这段代码从 POST 请求的 JSON 主体中获取数据。我们假设数据是一个列表,并取出其中的第一个元素作为要分析的文本。


执行模型推理

获取文本后,我们需要将其传递给模型进行推理。

以下是调用 ONNX 模型进行情感分析的代码。

    # 此处应有将文本转换为模型输入格式的代码(例如,分词)
    # input_ids = tokenizer(input_text, ...)
    # inputs = {‘input_ids’: to_numpy(input_ids)}
    # outputs = session.run(None, inputs)
    # logits = outputs[0]
    # prediction = np.argmax(logits, axis=-1)
    # is_positive = bool(prediction)

由于原始代码片段未展示完整的分词和模型输入准备过程,此处用注释概述了典型步骤:将文本分词、转换为模型输入格式、运行会话并获得输出 logits,最后通过 argmax 得到预测类别(例如,正面或负面情感)。


返回 JSON 响应

最后,我们需要将预测结果以 JSON 格式返回给客户端。

以下是使用 Flask 的 jsonify 助手函数返回响应的代码。

    return jsonify({‘positive’: is_positive})

jsonify 函数将 Python 字典转换为 JSON 格式的 HTTP 响应。


运行 Flask 应用

所有功能定义完成后,我们需要添加代码来运行应用服务器。

以下是启动 Flask 开发服务器的代码。

if __name__ == ‘__main__’:
    app.run(host=‘0.0.0.0’, port=45000, debug=True)

这行代码确保当脚本直接运行时(而不是被导入时),会启动 Flask 开发服务器。它监听所有网络接口(0.0.0.0)的 45000 端口,并开启调试模式。


通过 cURL 测试 API

应用运行后,我们可以使用命令行工具如 cURL 来测试 API。

以下是使用 cURL 发送 POST 请求到 /predict 端点的命令示例。

curl -X POST -H “Content-Type: application/json” --data ‘[“Flask is a very useful framework.”]’ http://localhost:45000/predict

这个命令向 http://localhost:45000/predict 发送一个 POST 请求,请求体是一个包含待分析文本的 JSON 数组。服务器应返回类似 {“positive”: true} 的 JSON 响应。


总结

本节课中我们一起学习了如何使用 Flask 构建一个完整的机器学习 API。我们从初始化应用、加载 ONNX 模型开始,逐步定义了主页路由和核心的预测路由,并实现了请求处理、模型推理和 JSON 响应返回的全过程。最后,我们还演示了如何使用 cURL 命令行工具来测试这个 API。通过这个流程,你可以将训练好的模型部署为 Web 服务,供其他应用调用。

89:FastAPI框架简介 🚀

在本节课中,我们将要学习FastAPI框架的基础知识。FastAPI是一个用于构建API的现代、快速(高性能)的Web框架,它基于Python类型提示,并自动生成交互式API文档。我们将了解其基本结构、如何定义路由和处理请求,并运行一个简单的示例应用。


FastAPI框架概述

FastAPI框架与Flask框架非常相似,但两者之间存在一些细微差别,这些差别决定了在不同场景下选择使用哪一个框架更为合适。我个人非常喜欢FastAPI,我们将在接下来的课程中快速了解它的样子。

让我们从一个已经准备好的代码仓库开始,它告诉我们运行此应用需要用到一些不同的工具。

运行应用所需组件

以下是运行此FastAPI应用所需的关键组件:

  • Uvicorn:一个ASGI服务器,用于运行Python Web应用。它是一个可执行文件,本质上是一个允许我们运行Python应用的Python Web框架。
  • 主机(Host):Uvicorn需要知道在哪个主机地址上运行服务。
  • Python模块:Uvicorn需要知道要运行的Python应用入口点。在本例中,入口点是main.py模块中的app对象。

项目结构解析

现在,让我们快速查看一下主要的应用文件。

我们打开main.py文件。这里有几个不同的文件,但我们现在不需要关注README,可以先关闭它。

main.py中,我们可以看到以下几点:

  1. 实例化FastAPI应用:代码通过app = FastAPI()实例化了一个FastAPI应用对象,这与Flask的做法非常相似。
  2. 定义路由:与Flask一样,我们通过装饰器来暴露路由(即API端点)。
    • @app.get("/"):这个装饰器将下面的root函数暴露为处理根路径(/)的GET请求的处理器。
    • @app.post("/generate"):这个装饰器将generate函数暴露为仅处理/generate路径的POST请求。
  3. 请求体模型:代码中有一个有趣的部分是第18行定义的Body类。它继承自BaseModel,用于定义对传入请求体的期望。这里,它期望一个JSON请求体中包含一个名为strf_time、类型为字符串(str)的字段。
  4. 函数参数:在generate函数中,body是一个必需的参数,其类型被指定为上面定义的Body对象。这使得FastAPI能够自动验证请求数据。

我们看到了一个非常基础的API,它允许你定义函数、指定如何暴露它们(路由和方法)。

一个关键区别:文件响应

这里的一个关键区别是,在根路由(/)的处理函数中,我们使用了FileResponse来返回一个HTML文件。如果我们查看static目录,会发现一个index.html文件。这表明FastAPI也可以用来提供静态内容。

如何运行应用

了解如何运行应用总是有用的。让我们查看一下Dockerfile来了解其运行方式。它使用Uvicorn,指定主机为0.0.0.0(绑定到所有网络接口),并运行main:app(即main.py模块中的app对象)。

接下来,我们快速创建一个终端,按照Dockerfile中的模式来运行这个应用,以此完成对FastAPI的介绍。

运行命令如下:

uvicorn main:app --host 0.0.0.0
  • main:app:表示运行main.py模块中的app对象(即第14行创建的FastAPI()实例)。
  • --host 0.0.0.0:指定服务器监听所有可用的网络接口。

运行该命令后,服务器启动。我们可以打开浏览器访问 http://localhost:8000

交互式API文档

访问根路径(/)会看到HTML页面。但FastAPI的一个优点是它提供了动态生成的交互式API文档,这个文档通常位于 /docs 路径。

虽然在我们查看的main.py代码中,并没有显式地暴露一个/docs路由,但FastAPI框架默认提供了这个功能。

访问 http://localhost:8000/docs,你会看到自动生成的OpenAPI文档。在这里,你可以看到已定义的/generate端点。点击它,然后点击“Try it out”按钮。

你会发现,文档界面已经根据我们函数定义中的类型提示(如Body模型),自动生成了参数输入框。例如,它知道需要一个strf_time字符串参数,并给出了示例%Y-%m-%d

我们来尝试一下,在输入框中填入%Y,然后点击“Execute”。

查看请求与响应

执行后,滚动到“Responses”部分查看结果。

  1. 响应体(Response body):你会看到响应体是“2022”。这是因为%Y在Python的strftime格式中代表完整的年份。
  2. cURL命令(curl command):文档还提供了完整的cURL命令,方便你在其他地方测试这个API。
  3. 请求URL(Request URL):显示了实际调用的URL。
  4. 响应头(Response headers):展示了服务器返回的响应头信息。

这些信息非常有用。FastAPI内置了OpenAPI规范支持,允许你动态地与正在运行的API进行交互。


总结

本节课中,我们一起学习了FastAPI框架的入门知识。我们了解了如何通过FastAPI()创建应用实例,如何使用@app.get()@app.post()装饰器定义路由,以及如何利用Pydantic模型来定义和验证请求体的数据结构。我们还成功运行了应用,并体验了FastAPI自动生成的、功能强大的交互式API文档(/docs)。这为后续集成机器学习模型并构建其API接口打下了坚实的基础。下一节,我们将探索如何将机器学习模型引入到这个框架中。

90:使用 FastAPI 构建机器学习 API 🚀

在本节课中,我们将学习如何使用 FastAPI 构建一个真实的机器学习 API。我们将结合 Hugging Face Transformers 库加载一个文本生成模型,并通过 API 端点提供服务。


概述

我们将使用 FastAPI、Hugging Face Transformers、TensorFlow 和 Uvicorn 来构建一个文本生成 API。与需要将模型预先存储在文件系统中的方式不同,本例将动态加载模型。


项目依赖与设置

以下是构建此 API 所需的核心库:

  • fastapi: 用于构建 Web API 框架。
  • uvicorn: 用于运行 FastAPI 应用的 ASGI 服务器。
  • transformers: Hugging Face 提供的库,用于加载和使用预训练模型。
  • tensorflow: 作为模型运行的后端之一。

主要 API 逻辑定义在 main.py 文件中。


核心代码解析

上一节我们介绍了项目所需的工具,本节中我们来看看 main.py 文件的具体实现。

首先,我们导入必要的模块并初始化文本生成管道。

from fastapi import FastAPI
from pydantic import BaseModel
from transformers import pipeline

# 使用 Hugging Face 的 pipeline 工具加载文本生成模型
generator = pipeline("text-generation", model="gpt2")

接下来,我们创建 FastAPI 应用实例并定义数据模型。

app = FastAPI()

# 定义请求体的数据模型
class TextRequest(BaseModel):
    text: str

然后,我们定义 API 端点。根路径 / 返回一个简单的 HTML 页面。

@app.get("/")
def read_root():
    return {"message": "Go to /docs for the API documentation"}

核心的文本生成端点定义在 /generate 路径下,它接收 TextRequest 作为请求体。

@app.post("/generate")
def predict(body: TextRequest):
    # 使用模型生成文本
    result = generator(body.text, max_length=50, num_return_sequences=1)
    # 返回生成结果
    return result

运行与测试应用

代码编写完成后,我们需要运行服务器并进行测试。

使用以下命令通过 Uvicorn 启动应用:

uvicorn main:app --host 0.0.0.0 --port 8000

启动时,Hugging Face 会自动下载指定的 gpt2 模型。启动成功后,FastAPI 会自动生成交互式 API 文档。

访问 http://localhost:8000/docs 即可看到文档页面。以下是测试步骤:

  1. 在文档页面找到 /generate 端点。
  2. 点击 “Try it out” 按钮。
  3. 在请求体的 text 字段中输入提示文本,例如:"Unlike other programming languages, Python is"
  4. 点击 “Execute” 按钮发送请求。

服务器将调用模型进行推理,并返回生成的文本。响应中会包含生成的完整文本、使用的 curl 命令示例以及 HTTP 状态码。


总结

本节课中我们一起学习了如何使用 FastAPI 构建一个完整的机器学习 API。我们实现了以下关键步骤:

  1. 初始化模型:利用 Hugging Face Transformers 的 pipeline 动态加载 GPT-2 模型。
  2. 定义 API 结构:使用 Pydantic 模型定义请求体,并使用 FastAPI 装饰器创建端点。
  3. 处理请求与响应:在 /generate 端点中接收用户输入,调用模型生成文本,并返回结果。
  4. 运行与交互:通过 Uvicorn 运行应用,并利用 FastAPI 自动生成的交互式文档进行测试。

整个过程展示了 FastAPI 在快速构建和部署机器学习服务方面的简洁性与强大功能。

91:API最佳实践 🚀

在本节课中,我们将学习构建API时应遵循的一些核心最佳实践。我们将重点探讨HTTP状态码的正确使用、不同HTTP请求方法(如GET、POST、PUT、HEAD)的适用场景,以及编写良好API文档的重要性。


HTTP状态码的最佳实践 🔢

上一节我们介绍了API的基本概念,本节中我们来看看如何通过HTTP状态码清晰地传达API的行为和错误信息。正确使用状态码对于定义与特定行为相关的错误至关重要。

以下是几种常见且重要的HTTP状态码及其使用场景:

  • 400 Bad Request:当请求的格式或内容不符合预期时使用,例如客户端发送了一个字符串,但API期望一个整数。
  • 401 Unauthorized:当访问某个资源需要授权,而客户端未提供有效凭证时使用。
  • 404 Not Found:表示请求的资源不存在。一个值得注意的实践是,有时为了安全,可以对未授权的资源访问也返回404,而不是401。这可以防止恶意用户探测系统中是否存在特定资源(例如,通过尝试不同用户名来确认其是否存在)。

HTTP请求方法:GET, POST, PUT, HEAD 📤

了解了状态码后,我们来看看客户端与服务器交互的不同方式,即HTTP请求方法。每种方法都有其特定的语义和用途。

以下是四种核心HTTP请求方法的说明:

  • GET:用于只读请求。客户端请求获取一个已存在的资源(如数据),服务器不会创建新资源。这通常映射到数据库的读取操作。
    • 代码示例GET /api/users/123
  • POST:用于创建新资源。例如,提交数据以创建一个新用户或新地址。
    • 代码示例POST /api/users (请求体中包含新用户数据)
  • PUT:用于更新已存在的资源。例如,一个用户更改了住址,就需要使用PUT来更新该用户的地址信息。
    • 代码示例PUT /api/users/123 (请求体中包含更新后的用户数据)
  • HEAD:其行为与GET类似,但服务器只返回响应头,不返回响应体。它常用于检查某个资源是否存在,或获取资源的元信息(如最后修改时间),而对客户端和服务器造成的开销最小。

为了更直观地理解HEAD请求,请看以下对比示例。假设我们有一个获取二进制数据的接口:

# HEAD 请求示例:只检查资源是否存在,返回空响应体
def head(self):
    # 向数据库查询资源
    if not resource_exists:
        return 404  # 返回状态码,无响应体
    return {}       # 返回空字典(通常转为空JSON对象),表示资源存在

# GET 请求示例:获取资源的完整信息
def get(self):
    # 向数据库查询资源
    data = fetch_complete_resource_data()
    return data     # 返回包含完整资源信息的响应体

文档化你的API 📖

最后,我们来探讨一个至关重要但常被忽视的实践:API文档化。清晰的文档能极大提升API的易用性。

我们之前看到的Hugging Face部署示例存在一个典型问题:缺乏文档。如果API端点没有文档,使用者就必须通过阅读源代码来猜测如何调用。例如,他们需要找到 /generate 端点,然后逐行查看代码才能知道请求体需要一个名为 text 的字符串,并且不清楚应该提供什么样格式的示例文本。

核心要点:如果你在开发生产级应用程序,务必确保为所有API端点编写文档,并提供清晰的调用示例。


本节课中我们一起学习了构建稳健API的关键最佳实践:正确使用HTTP状态码来传达请求结果、根据操作意图(读、写、更新、检查)选择合适的HTTP方法,以及通过完善的文档来提升API的可用性和可维护性。遵循这些实践将帮助你构建出更专业、更易于使用的服务接口。

92:构建机器学习API 🚀

在本节课中,我们将学习如何构建强大的HTTP API。我们将简要对比两个非常流行的Python Web框架:Flask和FastAPI。它们非常相似,但也存在一些细微差别。我们将看到使用这些框架构建强大API的便利性,并探讨如何将机器学习模型集成到其中,从而将它们作为HTTP API对外提供服务。


框架对比:Flask 与 FastAPI

上一节我们介绍了构建机器学习API的目标,本节中我们来看看两个核心工具:Flask和FastAPI。它们都是用于构建Web API的Python框架,但在设计哲学和功能上有所不同。

以下是它们的一些主要特点:

  • Flask:一个轻量级的“微框架”,以其简洁和灵活性著称。它提供了构建Web应用所需的核心功能,其他高级功能可以通过扩展添加。
  • FastAPI:一个现代、快速(高性能)的Web框架,专为构建API而设计。它内置了对OpenAPI(Swagger)文档和自动数据验证(基于Pydantic)的支持。


构建API的便利性

了解了基本框架后,我们来看看使用这些框架构建API的具体优势。它们通过抽象底层的网络通信细节,让开发者能更专注于业务逻辑。

使用这些框架,你可以通过简单的代码结构快速定义API端点(Endpoint)。例如,一个基本的“Hello World”端点:

Flask示例代码:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

FastAPI示例代码:

from fastapi import FastAPI
app = FastAPI()

@app.get('/')
def read_root():
    return {'Hello': 'World'}

集成机器学习模型

构建好API框架后,最关键的一步是将机器学习模型集成进去。这使我们能够通过HTTP请求来调用模型,实现预测功能。

这个过程通常涉及以下步骤:

  1. 加载已训练好的模型(例如,一个.pkl.h5文件)。
  2. 在API端点函数中,接收输入数据(通常以JSON格式)。
  3. 对输入数据进行必要的预处理。
  4. 调用模型进行预测。
  5. 将预测结果格式化为响应(如JSON)并返回。

以下是一个简化的集成示例流程:

# 伪代码示例
import pickle
from fastapi import FastAPI

app = FastAPI()
model = pickle.load(open('model.pkl', 'rb'))

@app.post('/predict')
def predict(input_data: InputSchema):
    processed_data = preprocess(input_data)
    prediction = model.predict(processed_data)
    return {'prediction': prediction}

总结

本节课中我们一起学习了构建机器学习API的核心知识。我们首先对比了Flask和FastAPI这两个流行的Python Web框架,了解了它们的特点。接着,我们探讨了使用这些框架构建API的便捷性,并通过代码示例进行了说明。最后,我们重点介绍了如何将训练好的机器学习模型集成到API中,使其能够通过HTTP协议接收请求并返回预测结果,从而完成机器学习模型的服务化部署。

83:MLOps简介 🚀

在本节课中,我们将要学习机器学习运维(MLOps)的基础概念。我们将探讨MLOps的定义、核心组成部分以及如何将其应用于解决实际问题。课程旨在为初学者提供一个清晰、实用的入门指南。


什么是MLOps? 🤔

欢迎来到MLOps基础课程。我是你们的讲师Noah Gi。我是杜克大学MIDS与AI项目的驻场高管。我将引导大家了解这门激动人心的课程中将要涵盖的一些关键概念。

首先,我们将讨论如何应用MLOps的基础知识来解决实际问题。对我而言,MLOps的本质在于将理论付诸实践,并将这种实践用于解决问题,而不仅仅是讨论潜在的解决方案。


MLOps的核心概念 ⚙️

上一节我们介绍了MLOps的目标,本节中我们来看看构成MLOps实践的关键组成部分。

以下是构建MLOps流程所需的核心环节:

  1. 数据管理与版本控制:确保数据可追溯、可复现。
  2. 模型训练与实验跟踪:系统化地管理模型训练过程和超参数。
  3. 模型评估与验证:使用标准化的指标评估模型性能。
  4. 模型部署与服务化:将训练好的模型转化为可用的API服务。
  5. 监控与持续改进:在生产环境中监控模型表现,并触发重新训练。

端到端MLOps入门 🛠️

理解了核心概念后,本节我们将探讨如何开始一个端到端的MLOps项目。这非常令人兴奋,因为我们可以利用Hugging Face等技术和预训练模型,整合所有环节来解决客户或特定领域的问题。

例如,假设你是一名科学家,希望使用机器学习来检测濒危动物。本课程将帮助你达到能够实现这一目标的水平。


后续课程概览 📚

接下来,在第二周,我们将讨论机器学习所需的必备数学与数据科学知识,涵盖以下几个主题:

以下是第二周的主要学习内容:

  • 第一天的数据科学:这对于刚加入组织的新人至关重要,我们将探讨如何快速上手并产生影响。
  • 优化、启发式与模拟:理解启发式方法与优化问题的区别非常重要。并非所有问题都适合用机器学习解决,许多问题使用启发式方法效果更好,例如经典的旅行商问题(TSP)
  • 实践中的机器学习与AI:探讨在现实世界中应用机器学习和人工智能的具体细节。

在第三周,我们将讨论运维流水线,包括DevOps、DataOps和MLOps。我们将特别介绍如何在Github生态系统中进行开发,这是一种新兴且令人兴奋的MLOps实践方式,因为它集成了Github Copilot、提示工程和Dev Container等工具。

在第四周,关于端到端MLOps和AIOps,我们将深入探讨机器学习的容器化。这是一个非常重要的概念,它关乎如何将应用打包,以便轻松部署到支持容器的云系统中。其理念类似于海运集装箱,标准化包装便于运输和规模化部署。

我们还将讨论如何利用AI结对编程(例如Copilot)来构建端到端解决方案,以及如何使用AI来构建AI应用。这是一个成为MLOps从业者的绝佳时代。


总结 🎯

本节课中我们一起学习了MLOps的基本定义、其核心组成部分以及一个完整的MLOps项目流程概览。我们了解到,MLOps是将机器学习模型从开发到生产部署并持续维护的一套工程实践,其核心目标是实现高效、可靠和可复现的机器学习生命周期管理。从下一节课开始,我们将深入每个环节的具体实践。

94:MLOps 背景与核心理念 🧠

在本节课中,我们将学习 MLOps 的背景、核心理念及其历史渊源。我们将了解 MLOps 如何从制造业的持续改进理念和软件行业的 DevOps 实践中演化而来,并掌握其核心构成。


核心理念:持续改进

MLOps 的核心是 改进 这一概念。在日本汽车工业中,这一理念被称为 “改善”。日本汽车工业有着悠久的持续改进历史,不断寻求质量控制措施,以优化汽车生产方式,更高效地生产无缺陷的车辆。

上一节我们介绍了 MLOps 源于持续改进的理念,本节中我们来看看软件行业如何实践这一理念。

从 DevOps 到 MLOps

DevOps 是软件行业中一种非常常见的方法论,它也利用了上述持续改进的特性。DevOps 包含一些核心组件:

以下是 DevOps 的一些核心组成部分:

  • 基础设施即代码:通过编程方式部署你的基础设施。
  • 持续集成:自动测试你的代码以改进它。
  • 持续部署:让你的代码不断在生产环境中更新新功能。
  • 增量变更能力:能够进行增量式的更改,而非“全有或全无”式的变更。

因此,MLOps 继承了这种能够快速进行变更的演进历史。

MLOps 的构成:25% 法则

我倾向于将 MLOps 视为一个 25% 法则

以下是 MLOps 的四个主要构成部分:

  1. 25% 是 DevOps:继承软件工程的最佳实践。
  2. 25% 是数据操作或自动化数据:涉及数据的处理与管理自动化。
  3. 25% 是建模:围绕模型改进与优化。
  4. 25% 是框架或审视业务需求:确保机器学习项目与业务目标对齐。

所以,当你审视 MLOps 时,它已成为一个稳固的体系,机器学习工程师应当了解 MLOps。但它很大程度上继承了 DevOps 的历史,甚至可以追溯到一个世纪前日本制造业(包括汽车工业)的自动化历史。


本节课中,我们一起学习了 MLOps 的背景。我们了解到 MLOps 植根于“改善”这一持续改进理念,并继承了 DevOps 的自动化与敏捷实践。其核心构成可以用 25% 法则 来概括,即 DevOps、数据操作、建模和业务框架各占四分之一。理解这些背景有助于我们更好地掌握和应用 MLOps。

95:03_01_05_MLOps趋势与技术 📈

概述

在本节课中,我们将探讨MLOps领域的最新趋势与关键技术。课程内容基于O‘Reilly出版的《Practical MLOps》一书,旨在帮助你了解当前MLOps的热点话题、发展方向以及需要关注的重点。

讲师背景介绍

我是Noah Gift,是O‘Reilly出版的《Practical MLOps》一书的作者之一。我的背景如下:

  • 早在21世纪初,我参与了迪士尼未来动画和索尼Imageworks首个3D动画管线的创建。这让我在数据工程成为热门领域之前,就积累了丰富的经验。
  • 我在电影制作领域进行了大量的Python编程。
  • 我撰写了9本以上的书籍,其中4本以上由O‘Reilly出版。目前正在撰写第五本,主题是MLOps。
  • 我曾从零开始,利用MLOps技术创建了一家拥有数百万用户、营收数百万美元的社交媒体公司。
  • 我为《财富》100强企业和初创公司提供MLOps咨询服务。

为什么需要MLOps?

接下来,让我们深入探讨为什么需要MLOps。

需要指出的几点是:

  • 数据科学家的工作成果往往难以交付到生产环境,但这不一定是他们的过错。
  • 团队结构可能存在问题,例如组建了多达10个不同的数据科学团队,但由于缺乏足够的规范和流程,我们并不清楚他们在构建什么。
  • 生命短暂,我们需要的是实际成果,而非仅仅是实验。
  • 我们需要高级别的工具来简化流程。

以上这些正是推动MLOps发展的核心动机。

行业趋势与机遇

如果我们观察行业的发展方向,会发现巨大的机遇。

  • 《华尔街日报》报道称,云计算相关职位从2019年的约40万个,增长到2020年的77.5万个。
  • 数据工程师和机器学习工程师的职位需求也在上升。
  • 云计算领域存在大量职位空缺。
  • 截至2022年,O‘Reilly的薪资调查显示,获得认证的云开发人员年薪可达20万美元以上。

MLOps的“25%法则”

在MLOps领域,不存在“银弹”(即一劳永逸的解决方案)。

MLOps的成功需要多个方面的结合,我将其总结为“25%法则”:

  • 25% DevOps:你需要实施DevOps实践。
  • 25% 数据工程最佳实践:你需要处理数据,遵循数据工程的最佳实践。
  • 25% 模型专业知识:你需要了解数据科学,具备模型相关的专业知识。
  • 25% 业务问题界定:你需要正确地界定问题。如果你无法阐明为何使用机器学习来解决某个问题,那么这可能不是一个合适的应用场景。同时,要确保你解决的是正确的问题。

MLOps战略考量

现在,让我们深入探讨一些重要的MLOps战略考量。

选择技术合作伙伴

首先要考虑的是选择一个技术合作伙伴,因为在平台和技术提供商方面没有“银弹”。需要注意的是,一个合适的平台能极大地帮助你开启MLOps之旅。

主次投资策略

第二个需要考虑的是“主次投资”的概念。

  • 主要平台:应具备低成本、流行度高、易于招聘人才、功能广泛等特点。你应该能够轻松地在其之上构建抽象层。
  • 次要平台/工具:这些工具可能专门解决某一类问题。例如,Databricks擅长解决Spark问题,Splunk擅长解决日志问题,Snowflake擅长解决数据仓库问题,Kubernetes擅长解决模型服务问题。在选择不同的技术投资时,这些都是重要的考虑因素。
  • 研发与创新:确保你在这些其他领域也进行投入和探索。有时你并不清楚它们是否会成功,但如果你的组织足够大,一些成功的探索可能会带来丰厚回报。

云计算平台选择

目前,亚马逊在云计算领域处于领先地位。

  • 在2021年第四季度,AWS约占33%的市场份额。
  • Azure约占20%。
  • 谷歌云位居第三,约占10%。

选择云计算平台时,主要考虑因素包括:能否获得企业级支持(例如,在需要时能否打电话获得帮助),以及招聘是否容易。如果团队中有人离职,你能否轻松找到熟悉该特定云平台的人来接手。同时,培训资源的可获得性也很重要。因此,选择一款在商业连续性方面相当流行的平台至关重要。

次要平台的考量

对于次要平台,需要考虑其成熟度、平台策略、功能特性、创新性,以及它是否能让特定工作变得更简单。你能否获得相关认证?它们是否能与主要平台良好地协同工作?它们是否基于Kubernetes等技术构建?

人才招聘与技能提升策略

招聘和技能提升策略是许多组织的“秘密武器”,让我们简要讨论一下。

以下是一些建议:

  • 获取学习订阅:我建议人们获取学习订阅,这在当今世界就像拥有一张图书馆借书卡。可以获取几个不同的订阅。
  • 鼓励认证:通过公司付费或向经理申请,鼓励员工获取专业认证。
  • 建立内部用户组:组织月度技术分享和演示。
  • 设定学习目标:设定年度和季度学习目标,甚至可以超越公司的要求,制定个人的学习计划。
  • 为学习付费:我认为为员工的学习付费是一个好主意,奖励技能的获取。
  • 鼓励演示:这涉及“元认知”的概念。研究表明,更多地谈论某件事实际上能增加你的知识,并提高你理解自己知道什么和不知道什么的能力。

关键的MLOps认证

让我们看看一些关键的MLOps认证。

  • AWS机器学习专项认证:这是一个很好的选择,因为AWS非常流行。
  • AWS数据分析认证AWS解决方案架构师认证:如果你想获得全部三项认证,这将是一个极佳的组合,因为你将同时掌握数据、架构和机器学习知识。
  • 其他热门认证:包括Snowflake、Databricks认证开发者、Kubernetes认证、谷歌云认证等。
  • MLflow:这是一个开源软件包,允许你构建“配方”(recipes)。你可以尝试构建一个配方,展示如何使用MLOps工具。

未来趋势展望

现在,让我们谈谈一些未来趋势,看看MLOps领域即将发生什么,以及我们可以预测什么。

文件系统的回归

我认为我们正在见证文件系统的回归。通过“NFS Ops”的概念,开发者可以在云端的终端中工作,这现在已成为主流。例如,我们有GitHub Codespaces、AWS Cloud9、Google Cloud Shell等众多环境。其中,GitHub Codespaces是我最喜欢的之一。

其核心思想是挂载一个网络文件系统(例如Amazon EFS)。这样,构建系统(如Jenkins)和部署流程都可以访问同一个挂载点。由于磁盘I/O分布在系统中,你本质上可以实时访问部署过程和机器学习系统使用的数据。因此,我认为这种NFS Ops风格的部署方式将会回归。

Kubernetes的持续发展

Kubernetes不会消失,它无疑是一个非常复杂的系统。但有许多工具(如Kubeflow工作流)可以很好地融入Kubernetes生态,并且所有云提供商都支持它。我认为,尤其是托管Kubernetes服务,我们将看到越来越多这样的趋势。

边缘机器学习

我们正在看到基于边缘的机器学习兴起。其思想是使用一个ML框架进行转换,然后可以部署到许多不同的位置,包括物理设备(如带有推理芯片的硬件USB设备或手机),允许在手机甚至操作系统上进行预测。因此,边缘机器学习是我们将越来越多看到的趋势。

可持续性与治理

另一个新兴的概念是可持续性。我们面临着气候变化问题,需要谨慎行事。我们不仅要关心环境,还要关心人。例如,我们提供的推荐引擎结果是否会激化社会矛盾或破坏民主?这些都是重要的考虑因素。

此外,治理也很关键:我们是否以既能盈利,又不会将负面外部性转嫁给无力承担的人的方式构建系统?例如,构建一个导致城市交通瘫痪、污染加剧并摧毁就业的系统,然后一走了之,这就是不良治理的例子。

预训练模型与AutoML

在MLOps中,你并不一定需要自己构建模型才能使用它。我们可以用一个类比来理解:

  • 面粉:就像训练一个模型。
  • 冷冻披萨:就像AutoML。
  • 披萨外卖:就像情感分析服务。

每一层都代表不同的抽象级别。当然,你可以做任何事,也可以从面粉自己做到冷冻披萨,但这取决于你想要实现的目标。

模型可移植性

模型可移植性也是一个巨大的趋势。你可以将用多种不同框架(如TensorFlow、PyTorch、Scikit-learn、XGBoost)构建的模型,转换为通用格式(如ONNX)。

“Kaizen ML”与自动化

我非常推崇“Kaizen ML”的概念。我们可能将看到数据科学家职位变得更加“双峰化”:一方面,会有首席数据科学家,他们薪酬极高,在组织中做出关键决策;另一方面,许多过去由初级数据科学家完成的、可重复性高的工作可能会被完全自动化,因为AutoML和数据自动化可以完成其中的许多任务。

因此,ML工程师和数据工程师可能会在成本驱动下成为中坚力量,而数据科学家则处于双峰分布的两端。

“Kaizen ML”本质上是一个自动化的过程改进。我们不仅仅改进机器学习模型,而是改进与之相关的每一件事。

这意味着你需要具备“生产优先”的思维模式。在这种模式下,你不能只关注占问题5%的模型,而要关注整个系统。这包括:

  • 数据科学家的行为与问题界定。
  • 自动化数据工程流水线(使用作业系统收集和清理数据)。
  • 使用特征存储等进行自动化特征工程。
  • 自动化测试。
  • 自动化商业智能。
  • 自动化模型精度评估。
  • 自动化扩展。

你可以看到,每一件事都需要自动化。如果你只挑选自己喜欢的一件事情来做,那就不是“生产优先”的思维模式。这就是我称之为“Kaizen ML”的原因:将机器学习推向生产所需的一切都必须自动化。我认为这将是未来一个重要的趋势。

总结

本节课中,我们一起学习了MLOps的核心动机、行业机遇、成功的“25%法则”以及重要的战略考量,如技术选型、人才策略和认证路径。最后,我们展望了MLOps的未来趋势,包括文件系统回归、Kubernetes的持续重要性、边缘计算、可持续性治理以及全面的自动化(Kaizen ML)。理解这些趋势将帮助你在快速发展的MLOps领域保持领先。

96:04_01_06_什么是DevOps 🛠️

在本节课中,我们将学习DevOps的核心概念。我们将探讨DevOps的三个主要组成部分,并理解它们如何共同构成一个高效的反馈循环,以实现软件开发和运维的自动化与持续改进。


概述

当有人提到DevOps时,其核心组成部分是什么?为了进行总结,我们可以将其归纳为三个主要方面:软件工程最佳实践、组织文化以及自动化。这三者共同构成了DevOps的核心反馈循环。


DevOps的核心组成部分

上一节我们概述了DevOps的整体框架,本节中我们将详细探讨其三个核心组成部分。

以下是DevOps的三个核心组成部分:

  1. 软件工程最佳实践
    这指的是在构建项目时需要遵循的一系列标准化操作清单。这些实践确保了开发过程的一致性和质量。

  2. 组织文化
    这指的是公司文化中必须包含持续改进的理念。这个概念源自日本汽车工业的术语“Kaizen”,意为“持续改进”。

  3. 自动化
    这是DevOps的精髓,主要指持续集成与持续交付的概念。其核心目标是实现代码的自动化交付。与此紧密相关的还有“基础设施即代码”的概念。


DevOps的反馈循环

上一节我们介绍了DevOps的三个支柱,本节中我们来看看它们如何协同工作。

DevOps可以被视为一个位于中心的组合体,它融合了软件工程最佳实践组织文化自动化。这三者形成了一个紧密的反馈循环。

这个反馈循环至关重要。如果缺少其中任何一个组成部分,就无法实现真正的DevOps。这是一个需要整体考虑的简单而有效的模型。


总结

本节课中,我们一起学习了DevOps的定义及其三个核心组成部分:软件工程最佳实践、以“Kaizen”为代表的持续改进文化,以及以CI/CD为核心的自动化。理解这三者如何构成一个完整的反馈循环,是掌握DevOps理念的关键。

97:05_01_07_什么是DataOps 🧩

在本节课中,我们将要学习DataOps(数据运维)的核心概念。我们将探讨它的起源、核心思想以及它如何应用于现代数据系统。


什么是DataOps

DataOps,即数据运维,与传统DevOps有着深厚的历史渊源。

DevOps的核心思想是自动化持续改进软件工程栈的生命周期,通过微小的、持续的变更实现渐进式优化,并促进团队成员间的协作,打破信息孤岛。

DataOps本质上遵循相同的理念,但将其专门应用于数据系统。随着各组织越来越多地利用数据(从历史数据到实时数据,再到预测性数据)来创造价值,数据系统正变得日益重要。

因此,DataOps继承了DevOps的历史与实践,并将其应用于数据系统,目标是实现持续改进。仔细想来,这是一个相当直观的策略:每一天、每一周,你的产品、你的数据系统都应该变得更好一点。

你应该能够利用这些数据产品完成更多任务,并且能够与组织中的每个人进行有效沟通。基于你所实现的自动化和改进,设定有助于提升组织价值的目标。

以上便是对DataOps的定义。


核心概念与策略

上一节我们介绍了DataOps的基本定义,本节中我们来看看其核心策略。

DataOps的核心策略可以概括为:通过自动化持续的小幅改进,使数据系统与产品像软件一样不断迭代和优化。

以下是实现这一策略的几个关键方面:

  • 持续改进:数据系统应日臻完善,能力应不断增强。
  • 高效协作:打破团队间的壁垒,促进围绕数据的有效沟通。
  • 价值驱动:设定的改进目标应最终服务于提升组织的整体价值。

本节课中我们一起学习了DataOps的概念。我们了解到它源于DevOps,核心是通过自动化持续的小幅改进来优化数据系统的生命周期,并强调团队协作价值导向。理解DataOps是构建高效、可靠数据管道和机器学习运维(MLOps)流程的重要基础。

98:MLOps 重型与轻型工作流对比 🏗️ vs. 🚀

在本节课中,我们将学习两种不同类型的 MLOps 工作流:轻量级工作流与重型工作流。我们将探讨它们各自的特点、适用场景以及核心组件,帮助你理解在构建机器学习系统时如何根据需求进行选择。


概述

MLOps 工作流可以根据其复杂性和侧重点分为不同类型。主要区别在于,是围绕数据还是围绕模型来构建系统,以及系统是轻量级的还是重型的。理解这些差异对于设计高效、可维护的机器学习运维流程至关重要。


轻量级 MLOps 工作流 🚀

上一节我们介绍了课程概述,本节中我们来看看第一种类型:轻量级工作流。

轻量级工作流通常更简单、直接,适用于模型较小或使用预训练模型的场景。在这种工作流中,模型可能直接存储在代码仓库(如 GitHub)中。

以下是该工作流的一个典型步骤:

  1. 模型来源:模型代码直接来自 GitHub。
  2. 构建系统:使用云原生构建系统(例如 Google Cloud Build)进行构建。
  3. 部署服务:通过平台即服务(PaaS)产品(例如 Google App Engine)进行部署。
  4. 提供服务:最终提供一个用于预测的 AI API。

这种流程非常轻量,适合爱好者项目或某些预训练模型的应用场景。


重型 MLOps 工作流 🏗️

了解了轻量级工作流后,我们接下来看看更复杂、功能更全面的重型 MLOps 工作流。

重型工作流通常体现为一种以数据为中心的方法,与之相对的是以模型为中心的方法。在以数据为中心的方法中,数据可能是最重要的方面,有时甚至完全不进行建模。工作流可能涉及使用自动化工具分析数据、创建模型,然后进行部署。

一个完整的重型 MLOps 平台(如 Google Vertex AI、Amazon SageMaker 或 Databricks)通常包含以下核心组件:

以下是重型 MLOps 平台常见的功能组件列表:

  • 数据标注:为训练数据添加标签。
  • 数据漂移检测:监控数据分布是否发生变化,并可触发重新训练。
    • 核心概念if data_distribution_changed: trigger_retraining()
  • 特征存储:集中管理特征,方便新模型构建时复用。
  • 模型注册:对训练好的模型进行版本管理和注册。
  • 模型监控:确保部署后的模型性能符合预期。
  • 实验跟踪:记录不同实验的准确率等指标,支持回溯和复现。
  • 可解释性 AI:使用工具(如 SHAP)解释模型决策,显示哪些特征影响了预测结果。
  • 预测服务:提供模型预测的端点服务。

此外,整个系统的构建和运行还依赖于流水线编排Notebooks容器等技术。在底层,现代系统通常还包含一个数据管理系统(例如 Google BigQuery),用于查询数据,甚至可以直接在其中执行机器学习任务。


如何选择:数据中心 vs. 模型中心

那么,在构建 MLOps 系统时,究竟应该选择哪种路径呢?这主要取决于你的项目重点。

  • 以数据为中心:如果你的项目核心在于数据质量、持续的数据流和数据本身的洞察,那么重型、以数据为中心的工作流更为合适。
  • 以模型为中心:如果你的项目更侧重于模型架构的创新、实验和调优,那么工作流可能会更偏向模型本身。

选择轻量级还是重型工作流,本质上是在灵活性、开发速度与系统的可扩展性、自动化及治理能力之间进行权衡。


总结

本节课中,我们一起学习了 MLOps 的两种主要工作流类型。

  • 轻量级工作流(🚀)简单直接,适合小模型或快速原型开发。
  • 重型工作流(🏗️)功能全面,集成了数据管理、模型运维、监控和可解释性等一系列组件,适合生产级、以数据为中心的复杂系统。

关键决策在于根据项目需求,在轻量级重型之间,以及在以数据为中心以模型为中心的方法之间做出合适的选择。理解这些工作流的构成有助于你设计出更高效、更可持续的机器学习系统。

99:MLOps需求层次 🏗️

在本节课中,我们将要学习MLOps的需求层次结构。这个结构以金字塔形式呈现,从底层的基础设施到顶层的业务价值,清晰地展示了成功实施机器学习运维所需的各个阶段和组件。

概述:MLOps需求层次金字塔

MLOps需求层次是一个金字塔结构,它说明了组织要成功实施机器学习运维,必须逐层建立和满足一系列基础需求。每一层都是上一层的支撑,缺失任何一层,上层的目标都难以实现。

DevOps:金字塔的基石 🧱

金字塔的最底层是DevOps。如果一个组织没有建立起有效的DevOps实践,那么实现MLOps将无从谈起。

以下是DevOps的一些关键组成部分:

  • 基础设施即代码:你是否能够自动配置环境,并将其集成到构建系统中?
  • 持续交付:你的软件栈(例如微服务)能否在预演环境中交付,并自动部署到生产环境?
  • 构建系统设计:你的整个流程是否围绕一个高效的构建系统进行设计?

上一节我们介绍了MLOps的基石——DevOps,本节中我们来看看支撑机器学习的数据层面。

DataOps:数据管理平台 📊

在具备了DevOps这一基础层之后,你需要关注数据管理平台。

核心在于,你是否在使用一个平台化的解决方案,例如Google BigQuery、Databricks、Snowflake或Amazon Athena。这些平台能够:

  • 让无服务器查询和可视化工作流变得非常直接。
  • 提供执行数据作业和数据任务的能力。

在稳固了数据和开发运维基础之后,我们可以进入专门的机器学习运维平台层。

MLOps平台:专用工具与服务 🤖

你不应该花费大量时间从头构建一切,这会偏离你的核心业务目标。因此,使用专门的MLOps平台至关重要。

以下是MLOps平台可能提供的功能:

  • 特征存储:用于存储和管理可供使用的精选特征。
  • 模型服务平台:专门用于部署和提供机器学习模型服务。
  • 实验跟踪工具:帮助你追踪不同实验的指标和解释性技术。
  • 数据监控:例如使用DataDrift等工具,监测生产环境中模型底层数据是否发生变化及其影响。

当你成功构建了上述三个层次(DevOps、DataOps、MLOps平台),你就达到了MLOps层。然而,要完全实现MLOps的价值,还需要从业务角度思考两个关键问题。

业务视角:价值与问题定义 💡

要充分发挥MLOps层的作用,必须考虑以下两点:

  1. 业务投资回报率:你创建的机器学习模型是否为组织增加了价值?这是衡量成功的根本标准。
  2. 问题正确定义:你是否正确地定义了要解决的问题?如果问题定义错误,你很可能在为组织解决一个错误的问题。

一旦正确地从业务角度定义了问题和目标,你就可以利用MLOps栈开展各种工作。

实现自动化工作流 ⚙️

在稳固的基础和清晰的业务目标下,MLOps能够实现自动化工作流。你可以进行:

  • 预测分析:例如,预测正确的库存量。
  • 无监督学习:用于发现数据中的新规律。

本质上,MLOps就是一个逐步自动化的工作流。它从软件栈自动化开始,延伸到数据栈、平台栈,最终实现MLOps栈的自动化。这一整套体系充当了催化剂,加速释放组织中已有的业务价值。

本节课中我们一起学习了MLOps的需求层次结构。我们了解到,它是一个从底层DevOps开始,经过DataOps和MLOps平台层,最终服务于业务价值实现的逐层构建过程。每一层都是上一层成功实施的前提,而顶层的业务价值是衡量一切工作的最终标准。这就是MLOps的需求层次。

posted @ 2026-03-26 12:26  布客飞龙III  阅读(4)  评论(0)    收藏  举报