文章目录

  • 1. 迭代器(iterator)
  • 2. map
  • 2.1 map函数介绍
  • 2.2 map实例
  • 3. reduce
  • 3.1 reduce函数介绍
  • 3.2 reduce实例
  • 4. 生成器(generator)
  • 4.1 生成器使用方法简介
  • 4.2 使用生成器的优势
  • 5. 总结



综述:在学习了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.