Python 是一种很棒的编程语言!它是开发人工智能和机器学习应用程序最流行的语言之一。Python 语法易于学习,并具有一些使其与其他语言区别开来的特殊功能。在本教程中,我们将讨论 Python 编程语言的一些独特属性。
完成本教程后,您将了解:
- 列表和字典推导式结构
- 如何使用 zip 和 enumerate 函数
- 什么是函数上下文和装饰器
- Python 中生成器的用途是什么
使用我的新书《Python for Machine Learning》来启动您的项目,其中包括分步教程和所有示例的 Python 源代码文件。
让我们开始吧。教程概述
本教程分为四个部分;它们是
- 列表和字典推导式
- Zip 和 enumerate 函数
- 函数上下文和装饰器
- Python 中的生成器以及 Keras 生成器的示例
导入部分
本教程中使用的库在下面的代码中导入。
1 2 3 4 5 |
from tensorflow import keras from tensorflow.keras.preprocessing.image import ImageDataGenerator import numpy as np import matplotlib.pyplot as plt import math |
列表推导式
列表推导式提供了一种从现有列表创建新列表的简短、简单语法。例如,假设我们需要一个新列表,其中每个新项都是旧项乘以 3。一种方法是使用如下所示的 for
循环
1 2 3 4 5 6 |
original_list = [1, 2, 3, 4] times3_list = [] for i in original_list: times3_list.append(i*3) print(times3_list) |
1 |
[3, 6, 9, 12] |
使用列表推导式的较短方法只需要一行代码
1 2 |
time3_list_awesome_method = [i*3 for i in original_list] print(time3_list_awesome_method) |
1 |
[3, 6, 9, 12] |
您甚至可以根据特殊条件创建新列表。例如,如果我们只想将偶数添加到新列表
1 2 |
even_list_awesome_method = [i for i in original_list if i%2==0] print(even_list_awesome_method) |
1 |
[2, 4] |
还可以将 else
与上述相关联。例如,我们可以保留所有偶数不变,并将奇数替换为零
1 2 |
new_list_awesome_method = [i if i%2==0 else 0 for i in original_list] print(new_list_awesome_method) |
1 |
[0, 2, 0, 4] |
列表推导式也可以用于替换嵌套循环。例如
1 2 3 4 5 6 7 |
colors = ["red", "green", "blue"] animals = ["cat", "dog", "bird"] newlist = [] for c in colors: for a in animals: newlist.append(c + " " + a) print(newlist) |
1 |
['red cat', 'red dog', 'red bird', 'green cat', 'green dog', 'green bird', 'blue cat', 'blue dog', 'blue bird'] |
可以使用以下方式完成,在列表推导式中有两个“for”
1 2 3 4 5 |
colors = ["red", "green", "blue"] animals = ["cat", "dog", "bird"] newlist = [c+" "+a for c in colors for a in animals] print(newlist) |
语法
列表推导式的语法如下所示
newlist = [expression for item in iterable if condition == True]
或者
newList = [expression if condition == True else expression for item in iterable]
想开始学习用于机器学习的 Python 吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
字典推导式
字典推导式与列表推导式类似,只是现在我们有(键、值)对。这是一个示例;我们将通过将字符串“number ”连接到每个值来修改字典的每个值
1 2 3 |
original_dict = {1: 'one', 2: 'two', 3: 'three', 4: 'four'} new_dict = {key:'number ' + value for (key, value) in original_dict.items()} print(new_dict) |
1 |
{1: 'number one', 2: 'number two', 3: 'number three', 4: 'number four'} |
同样,条件表达式也是可能的。我们可以根据新字典中的条件选择添加(键,值)对。
1 2 3 4 5 6 7 |
#只添加大于 2 的键 new_dict_high_keys = {key:'number ' + value for (key, value) in original_dict.items() if key>2} print(new_dict_high_keys) # 只更改键大于 2 的值 new_dict_2 = {key:('number ' + value if key>2 else value) for (key, value) in original_dict.items() } print(new_dict_2) |
1 2 |
{3: 'number three', 4: 'number four'} {1: 'one', 2: 'two', 3: 'number three', 4: 'number four'} |
Python 中的枚举器和 Zip
在 Python 中,可迭代对象定义为任何可以一次返回其所有项的数据结构。这样,您就可以使用 for
循环逐一进一步处理所有项。Python 有两个额外的构造,使 for
循环更容易使用,即 enumerate()
和 zip()
。
枚举
在传统编程语言中,您需要一个循环变量来遍历容器的不同值。在 Python 中,这通过允许您访问循环变量以及可迭代对象的一个值来简化。enumerate(x)
函数返回两个可迭代对象。一个可迭代对象从 0 到 len(x)-1 变化。另一个是其值等于 x 项的可迭代对象。示例如下所示
1 2 3 4 5 |
name = ['Triangle', 'Square', 'Hexagon', 'Pentagon'] # enumerate 返回两个可迭代对象 for i, n in enumerate(name): print(i, 'name: ', n) |
1 2 3 4 |
0 name: Triangle 1 name: Square 2 name: Hexagon 3 name: Pentagon |
默认情况下,enumerate 从 0 开始,但如果指定,我们可以从其他数字开始。这在某些情况下很有用,例如
1 2 3 |
data = [1,4,1,5,9,2,6,5,3,5,8,9,7,9,3] for n, digit in enumerate(data[5:], 6): print("The %d-th digit is %d" % (n, digit)) |
1 2 3 4 5 6 7 8 9 10 |
第 6 位数字是 2 第 7 位数字是 6 第 8 位数字是 5 第 9 位数字是 3 第 10 位数字是 5 第 11 位数字是 8 第 12 位数字是 9 第 13 位数字是 7 第 14 位数字是 9 第 15 位数字是 3 |
Zip
Zip 允许您创建元组的可迭代对象。Zip 将多个容器 $(m_1, m_2, \ldots, m_n)$ 作为参数,并通过配对每个容器中的一个项来创建第 i 个元组。第 i 个元组是 $(m_{1i}, m_{2i}, \ldots, m_{ni})$。如果传递的对象长度不同,则形成的元组总数长度等于传递对象的最小长度。
下面是使用 zip()
和 enumerate().
的示例
1 2 3 4 5 6 7 8 9 10 |
sides = [3, 4, 6, 5] colors = ['red', 'green', 'yellow', 'blue'] shapes = zip(name, sides, colors) # 元组由每个列表中的一个项创建 print(set(shapes)) # 轻松地将 enumerate 和 zip 结合使用,一次遍历多个列表 for i, (n, s, c) in enumerate(zip(name, sides, colors)): print(i, 'Shape- ', n, '; Sides ', s) |
1 2 3 4 5 |
{('Triangle', 3, 'red'), ('Square', 4, 'green'), ('Hexagon', 6, 'yellow'), ('Pentagon', 5, 'blue')} 0 Shape- Triangle ; Sides 3 1 Shape- Square ; Sides 4 2 Shape- Hexagon ; Sides 6 3 Shape- Pentagon ; Sides 5 |
函数上下文
Python 允许嵌套函数,您可以在外部函数中定义内部函数。Python 中的嵌套函数有一些很棒的特性。
- 外部函数可以返回内部函数的句柄。
- 内部函数保留其所有环境和局部变量,以及其包含函数中的变量,即使外部函数执行结束。
下面给出一个示例,并在注释中进行解释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def circle(r): area = 0 def area_obj(): nonlocal area area = math.pi * r * r print("area_obj") return area_obj def circle(r): area_val = math.pi * r * r def area(): print(area_val) return area # 返回 area_obj()。传递的 r 值保留 circle_1 = circle(1) circle_2 = circle(2) # 调用 area_obj(),半径 = 1 circle_1() # 调用 area_obj(),半径 = 2 circle_2() |
1 2 |
3.141592653589793 12.566370614359172 |
Python 中的装饰器
装饰器是 Python 的一个强大功能。您可以使用装饰器自定义类或函数的工作方式。将它们视为应用于另一个函数的函数。使用带有 @
符号的函数名来在被装饰函数上定义装饰器函数。装饰器将函数作为参数,提供了很大的灵活性。
考虑以下函数 square_decorator()
,它接受一个函数作为参数,也返回一个函数。
- 内部嵌套函数
square_it()
接受一个参数arg
。 square_it()
函数将该函数应用于arg
并将结果平方。- 我们可以将诸如
sin
的函数传递给square_decorator()
,它反过来将返回 $\sin^2(x)$。 - 您还可以编写自己的自定义函数,并使用特殊的 @ 符号在其上使用
square_decorator()
函数,如下所示。函数plus_one(x)
返回x+1
。此函数由square_decorator()
装饰,因此我们得到 $(x+1)^2$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def square_decorator(function): def square_it(arg): x = function(arg) return x*x return square_it size_sq = square_decorator(len) print(size_sq([1,2,3])) sin_sq = square_decorator(math.sin) print(sin_sq(math.pi/4)) @square_decorator def plus_one(a): return a+1 a = plus_one(3) print(a) |
1 2 3 |
9 0.4999999999999999 16 |
Python 中的生成器
Python 中的生成器允许您生成序列。生成器不是返回一个 return
语句,而是通过多个 yield
语句返回多个值。第一次调用函数时,返回第一个 yield
值。第二次调用时,返回第二个 yield
值,依此类推。
生成器函数可以通过 next()
调用。每次调用 next()
时,都会返回下一个 yield
值。下面显示了一个生成斐波那契序列直到给定数字 x
的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def get_fibonacci(x): x0 = 0 x1 = 1 for i in range(x): yield x0 temp = x0 + x1 x0 = x1 x1 = temp f = get_fibonacci(6) for i in range(6): print(next(f)) |
1 2 3 4 5 6 |
0 1 1 2 3 5 |
Keras 数据生成器示例
生成器的一个用途是 Keras 中的数据生成器。它很有用,因为我们不想将所有数据都保留在内存中,而是想在训练循环需要时即时创建它。请记住,在 Keras 中,神经网络模型是分批训练的,因此生成器用于发出数据批次。下面的函数来自我们之前的文章,“使用 CNN 进行金融时间序列预测”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
def datagen(data, seq_len, batch_size, targetcol, kind): "作为生成器为 Keras 模型生成样本" batch = [] while True: # 从池中选择一个数据框 key = random.choice(list(data.keys())) df = data[key] input_cols = [c for c in df.columns if c != targetcol] index = df.index[df.index < TRAIN_TEST_CUTOFF] split = int(len(index) * TRAIN_VALID_RATIO) if kind == 'train': index = index[:split] # 训练集的范围 elif kind == 'valid': index = index[split:] # 验证集的范围 # 选择一个位置,然后剪切序列长度 while True: t = random.choice(index) # 选择一个时间步 n = (df.index == t).argmax() # 查找其在数据框中的位置 if n-seq_len+1 < 0: continue # 无法获得足够的数据用于一个序列长度 frame = df.iloc[n-seq_len+1:n+1] batch.append([frame[input_cols].values, df.loc[t, targetcol]]) break # 如果我们收集到足够的一批数据,则分派 if len(batch) == batch_size: X, y = zip(*batch) X, y = np.expand_dims(np.array(X), 3), np.array(y) yield X, y batch = [] |
上述函数的作用是选取 pandas 数据框的随机行作为起点,然后剪切接下来的几行作为一次性时间间隔样本。这个过程重复多次,将许多时间间隔收集到一批中。当我们收集到足够的时间间隔样本时,在上述函数的倒数第二行,使用 yield
命令分派批次。您可能已经注意到,生成器函数没有 return 语句。在这个示例中,函数将永远运行。这很有用且必要,因为它允许我们的 Keras 训练过程运行任意数量的 epoch。
如果我们不使用生成器,我们将需要将数据框转换为所有可能的时间间隔,并将它们保存在内存中以供训练循环使用。这将是大量重复数据(因为时间间隔是重叠的),并且会占用大量内存。
由于它很有用,Keras 在库中预定义了一些生成器函数。下面是 ImageDataGenerator()
的一个示例。我们已经在 x_train
中加载了 32x32 图像的 cifar10
数据集。数据通过 flow()
方法连接到生成器。next()
函数返回下一批数据。在下面的示例中,对 next()
进行了 4 次调用。在每种情况下,都会返回 8 张图像,因为批次大小为 8。
下面是完整的代码,它还在每次调用 next()
后显示所有图像。
1 2 3 4 5 6 7 8 9 10 11 |
(x_train, y_train), _ = keras.datasets.cifar10.load_data() datagen = ImageDataGenerator() data_iterator = datagen.flow(x_train, y_train, batch_size=8) fig,ax = plt.subplots(nrows=4, ncols=8,figsize=(18,6),subplot_kw=dict(xticks=[], yticks=[])) for i in range(4): # next() 函数将从 CIFAR 加载 8 张图像 X, Y = data_iterator.next() for j, img in enumerate(X): ax[i, j].imshow(img.astype('int')) |
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
Python 文档
书籍
- 《像计算机科学家一样思考 Python》 作者:Allen B. Downey
- 《Python 3 编程:Python 语言的完整介绍》 作者:Mark Summerfield
- 《Python 编程:计算机科学入门》 作者:John Zelle
API 参考
总结
在本教程中,您了解了 Python 的一些特殊功能。
具体来说,你学到了:
- 列表和字典推导式的目的
- 如何使用 zip 和 enumerate
- 嵌套函数、函数上下文和装饰器
- Python 中的生成器和 Python 中的 ImageDataGenerator
您对本文讨论的 Python 功能有任何疑问吗?请在下面的评论中提出您的问题,我将尽力回答。
亲爱的 Jason Brownlee(博士)和 Mehreen Saeed,
感谢你们毫无保留地在全球范围内分享有用的知识。我热切地关注你们的许多教程。
我观察到在使用 set() 函数和 zip() 函数时有以下行为。
>>> shapes=zip(name,sides,colors)
>>> shapes
>>> b=set(shapes)
>>> b
{('Pentagon', 5, 'blue'), ('Triangle', 3, 'red'), ('Hexagon', 6, 'yellow'), ('Square', 4, 'green')}
>>> print(b)
{('Pentagon', 5, 'blue'), ('Triangle', 3, 'red'), ('Hexagon', 6, 'yellow'), ('Square', 4, 'green')}
>>> shapes
>>> b=set(shapes)
>>> shapes
>>> print(b)
set()
>>> b
set()
有没有人能告诉我为什么我第二次执行赋值 b=set(shapes) 时 b 会变成一个空集合?
请注意,对象 shapes 在内部仍定义为
在 Python 2 中,您的代码应该会产生您想要的结果。但在 Python 3 中,zip 的输出是一个生成器,只能读取一次。因此,如果您第二次运行
b=set(shapes)
,它将为空。亲爱的 Jason Brownlee 和 Mehreen,
我注意到变量“shapes”的内部表示在早些时候发送的注释中被“过滤掉了”。
我还想告诉您,我的代码是在安装在 Windows 10 上的 Python 3.7.7 shell 上运行的。
Gerard Elifuraha Mtalo
亲爱的授权答复者,
我尝试了“函数上下文”部分
在代码中,您有两个 circle 函数。
输出
请问:
我屏蔽了第一个 circle 函数。第一个 circle 函数的目的是什么?它从未被调用过?
这两个 circle 函数的目的是否是为了函数重载?
谢谢你,
悉尼的Anthony
澄清一下,即使是函数“def circle(r)”的第一个版本,第一个函数也永远不会被调用。
重载不像其他语言那样工作
输出
结论
* 显然 python 不允许重载,如运行时错误所示
* 但它没有回答为什么可以有两个函数。请注意,第一个函数根本没有被调用。
输出显示只调用了第二个函数。第一个函数从未被调用/执行。
谢谢你
悉尼的Anthony
感谢您的反馈和关注,Anthony!
有两个函数,而且只有第二个函数被调用,这是怎么回事?
通过注释掉第一个函数来屏蔽它并没有什么害处。
我不理解本教程这一部分的要点。
谢谢你,
悉尼的Anthony
要理解这种行为,您需要记住 Python 中一切皆对象。这是与许多其他语言的主要区别。当您定义函数
circle()
并再次定义它时,后面的定义将优先,因为名称circle
被重新分配给新的函数对象。同样,如果您可以从函数返回一个整数,您也可以返回一个函数,因为从 Python 的角度来看,整数和函数都是对象。它们没有太大区别。因此,在您的代码中,调用的不是第二个函数。而是最新定义的
circle
被调用了。