文章目录
综述:在学习了Python的map,reduce以及迭代器和生成器等工具的使用方法之后,根据自己的理解,将其整理如下。
1. 迭代器(iterator)
为什么先讲迭代器呢,因为可以说map和reduce的实现都借助了迭代器,如果能够把迭代器弄清楚,那么这两个自然也不难理解。
首先介绍一个概念,iterable,意为可迭代的。哪些对象是可迭代的呢?在Python中,可以直接作用于for循环的对象,都是可迭代的,即iterable。Python中的可迭代对象有列表(list)
、元组(tuple)
、集合(set)
、字典(dict)
、字符串(string)
,以及下边会讲的生成器
。
直观理解,迭代就是逐个遍历或访问。而迭代器,则可以帮助我们进行迭代。
我们通常使用for循环进行迭代,如:
a = ['h', 'a', 'r', 'r', 'y', ' ', 'p', 'o', 't', 't', 'e', 'r']
for i in a:
print(i,end='')
# 执行结果如下,上述end控制输出的结尾,默认的话是换行,这里为了显示效果设置为空
harry potter
这里的列表就是可迭代对象,变量i
就相当于一个迭代器,最初指向列表a的第一个元素,循环每进行一次,它便指向列表的下一个元素,借助它,我们可以逐个访问到列表中的元素。
当然,我们也可以直接使用iter()
构造一个迭代器,并使用next()
逐个访问(顺序为从第一个到最后一个)迭代器中的元素,如:
a = ['h', 'a', 'r', 'r', 'y', ' ', 'p', 'o', 't', 't', 'e', 'r']
# 构造一个迭代器,用于对列表a进行迭代
i = iter(a)
print(next(i))
print(next(i))
print(next(i))
# 输出结果
h
a
r
借助for循环,我们可以遍历到可迭代对象中的每个元素,但是,在for循环中,往往对每个元素进行的操作都是一样的。如果是使用迭代器,则可以对顺次对对象中每个元素进行更灵活的处理,每处理完一个,使用next()
即可访问到下一个元素,进行与其相应的处理。
2. map
2.1 map函数介绍
查看手册,map函数的声明如下:
map(function, iterable, ...)
map函数的第一个参数是一个函数,在该函数中,会使用迭代器,将函数应用于可迭代对象中的每一项(或者说是每一个元素),并生成结果。
不难看出,map函数的参数列表是可变的,在函数后可以添加多个可迭代对象。当然,前提是第一个参数传入的函数必须能够接收这么多的参数,即有对应处理这些可迭代对象的方法。如果传入了多个可迭代对象,那么,当最短的那个可迭代对象耗尽时(已经访问完其中所有元素),迭代器就会停止。值得注意的是,map返回的是一个对象。
2.2 map实例
叙述冗杂,但举个栗子,也许一下就清晰了。
传入一个可迭代对象的栗子如下:函数的功能是将字符串变自身重复一次,而map函数则将此函数应用到自身每一个元素。
a = ['h', 'a', 'r', 'r', 'y', ' ', 'p', 'o', 't', 't', 'e', 'r']
def duplicate(x):
return x + x # 简单的字符串拼接
y = map(duplicate, a);
print(list(y))
# 输出
['hh', 'aa', 'rr', 'rr', 'yy', ' ', 'pp', 'oo', 'tt', 'tt', 'ee', 'rr']
再来一个多个迭代对象的栗子吧。
a = ['h', 'a', 'r', 'r', 'y']
b = ['p', 'o', 't', 't', 'e', 'r']
def connect(x, y):
return x + '-' + y # 这里也是简单的字符串拼接
y = map(connect, a, b);
for i in y:
print(i)
# 输出结果
h-p
a-o
r-t
r-t
y-e
这里函数可以接收两个参数,因此可以处理map中的两个可迭代对象。从输出结果来看,两个可迭代对象中元素的访问顺序是一致的,而且,由于两个对象长度不同,一个为5,一个为6,当第一个对象元素耗尽时,迭代就停止了。
3. reduce
3.1 reduce函数介绍
reduce函数的原型如下:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value
它的第一个参数也是一个函数,第二个参数是一个可迭代对象,而这里的第三个参数有些特殊。如果此参数没有省略,则reduce中使用的函数的第一个参数,将会使用initializer进行初始化,第二个参数从可迭代对象中选取;如果为空,则默认两个参数逐个从可迭代对象中选取。
reduce中传入的函数,每执行一次,会根据传入其中的两个迭代元素生成一个结果,用于下一次迭代,这样意味着,每执行一次,可迭代项就会少1。
map函数中第一个参数是一个相对自由的函数,参数个数与后边可迭代对象保持一直即可,而reduce里的第一个参数,则限定了只能接收两个参数,而且这两个参数都来自与后边的可迭代对象与一个初始值。
3.2 reduce实例
找个好吃的栗子。(大半夜突然好想吃栗子)
Harry Potter有五个朋友,每个人有不一样数额的零花钱,他们想买栗子吃,且要找五个人中钱最多的当领队,谁钱最多呢?
# 使用reduce需要从functools模块导入
from functools import reduce
# 下边是每个人的钱数
a = [1, 33, 8, 17, 4]
# 函数用于比较大小
def max(x, y):
z = x if x > y else y
print(x, end='-') # 这里的打印便于观察比较过程
print(y)
return z
y = reduce(max, a)
print(y)
# 执行结果为33
1-33
33-8
33-17
33-4
33
这里使用reduce达到的目的是,从5人中从前到后,依次两两比较,每次比较得出一个结果,并从后边再取出一个元素与其进行比较,以此类推,结果为找到其中的最大值。
哈利波特也想吃栗子,且有10块钱,他也参与其中,于是他把自己作为initializer也加入到了比较队列。
from functools import reduce
# 下边是每个人的钱数
a = [1, 33, 8, 17, 4]
# 函数用于比较大小
def max(x, y):
z = x if x > y else y
print(x, end='-') # 这里的打印便于观察比较过程
print(y)
return z
y = reduce(max, a, 10)
print(y)
# 结果如下
10-1
10-33
33-8
33-17
33-4
33
不难看出,在给reduce函数添加了initializer之后,他调用的函数的第一个参数就使用initializer进行初始化,第二个参数为可迭代对象的第一个元素。而在前边的栗子中,没有传入initializer参数,则直接顺次使用可迭代对象的前两个元素。
这样reduce的用法应该就很清晰了。
4. 生成器(generator)
4.1 生成器使用方法简介
我们可以用生成器来按照我们的规则,生成一个可迭代对象。
如下,使用生成器生成值为0-10这些自然数每个数的平方的n倍的一个序列:
def gen(n):
for i in range(10):
yield n * i * i
a = gen(6)
for i in a:
print(i, end=' ')
# 0 6 24 54 96 150 216 294 384 486
注意在函数gen中,我们没有使用return返回,而是使用了yield,它会向外返回生成的结果,但是不会直接退出函数。这样for循环能够继续执行。
生成器还有一种简化的方法,如下:
gen = (i * i for i in range(10))
for i in gen:
print(i, end='')
但是这样做,似乎有个局限,就是生成器是写死的,不能像函数那样接收一个参数。
4.2 使用生成器的优势
生成器这种在循环中不断计算出后续元素值的方法,可以为我们节省大量的空间。
性能测试,如下
import sys
import time
t1 = time.time()
List_1 = [i for i in range(9999999)] # 列表初始化使用[]包裹起来
t2 = time.time()
t3 = time.time()
List_2 = (i for i in range(9999999)) # 注意生成器是使用()
t4 = time.time()
print("List_1使用时间为:", t2 - t1)
print("List_2使用时间为:", t4 - t3)
print("List_1使用空间为:", sys.getsizeof(List_1))
print("List_2使用空间为:", sys.getsizeof(List_2))
# 输出如下
List_1使用时间为: 0.4146158695220947
List_2使用时间为: 5.602836608886719e-05
List_1使用空间为: 81528048
List_2使用空间为: 112
使用生成器使用时间基本为0,使用空间也要少很多。显然,无论是在时间还是空间上,生成器都有着极大的优势。
5. 总结
Python为我们提供的map、reduce得带器和生成器这些工具,能够提高编程时的便捷性和灵活性,借助生成器的优势,可以提高我们程序的性能。
See you again.