7.1 又见函数
1.Python中的函数式
前面介绍了面向进程和面向对象的编程,这一章讲解面向函数的编程即函数式编程。函数式编程的本质在于封装,它以函数为中心进行代码封装,函数在面向进程中涉及过,它有参数和返回值,起到输入和输出数据的功能。
函数式编程强调了函数的纯粹性,一个纯函数是没有副作用的,即一个函数不会影响其他函数。我们在之前的可变对象中演示过,在函数内部改变列表里面的对象,会影响函数外部的列表元素,其他调用该列表的函数也会产生相应的影响,这就产生了副作用。所以,为了是函数纯粹,我们在函数内部使用的都是不可变更的变量。
由于Python中存在可变对象,因此只能尽量避免副作用。
函数式编程由于函数相互独立,因此不用担心函数调用对其他函数的影响,使用起来更加简单。
而且,纯函数也方便进行并行化运算。
在进行并行化编程时,我们经常担心不同进程之间相互干扰的问题。当多个进程或者线程同时修改一个变量时,进程或线程的先后顺序会影响最终结果,比如
from threading import Thread
x = 5
def double():
global x # 声明全局变量x
x = x * 2
def plus_ten():
global x # 声明全局变量x
x = x + 10
th1 = Thread(target = double) # 线程1
th2 = Thread(target = plus_ten) # 线程2
th1.start() # 线程1开始执行
th2.start() # 线程2开始执行
th1.join() # 线程1结束
th2.join() # 线程2结束
print(x)
结果:
20
将上面的线程调换一下
th2.start() # 线程2开始执行
th1.start() # 线程1开始执行
th2.join() # 线程2结束
th1.join() # 线程1结束
print(x)
结果:
30
global
是声明全局变量的关键字,函数对全局变量的修改能被其他函数看见,因此造成了副作用。如果并行地执行上面两个函数,执行顺序是不确定的,结果也不一样。比如先执行double,再执行plus_ten,得到的结果是20;若先执行plus_ten,再执行double,得到的结果是30。这被称为竟跑条件,是并行编程中需要尽力避免的。
而函数式编程消灭了副作用,也在无形之中消除了竟跑条件的可能。因此,函数式编程天然地适用于并行化运算。
Python中加入了lambda函数、map、filter、reduce等高阶函数,引入了函数式编程的特征。
函数式编程的思路是自上而下的,先提出一个大问题,在最高层定义一个函数来解决这个大问题,在函数的内部,再用其他函数来解决小问题。比如,我们要解决"如何把大象放进冰箱"的问题,首先我们定义一个函数"把大象放进冰箱"来解决大问题,然后在"把大象放进冰箱"里面,有三步函数"打开冰箱门"、"放入大象"、"关上冰箱",如果需要继续细化,则在这三步函数内部继续调用其他函数。
2.并行运算
并行运算是指多条指令同时执行。对应的串行运算指的是同一时间只能执行一条指令。
大规模的并行运算一般是在多个主机组成的集群上进行的,主机之间通过高速的网络设备通信。但由于集群的成本过高,我们可以在单机上通过多线程或多进程来模拟集群的并行处理。
在一台单机上,往往运行着多个程序,即进程。如用浏览器上网时,用网易云听音乐,计算机就同时运行两个进程。这就是单机"分时复用"的方式,把运算能力分给多个进程。
也就是说,集群和单机都实现了多个进程的并行运算。集群上的多进程分布在不同的主机,而单机则使用"分时复用"的方式来实现。下面看一个多进程的例子
import multiprocessing as mulpr
def proc1():
return 333*333
def proc2():
return 444*444
p1 = mulpr.Process(target = proc1())
p2 = mulpr.Process(target = proc2())
p1.start() # 启动进程
p2.start()
p1.join() # 结束进程
p2.join()
多进程和多线程的区分在哪呢?
一个程序运行后,就是一个进程。进程有自己的内存空间,存储自己的运行状态、数据和代码。进程与进程之间一般不会相互读取数据。
但在一个进程中,可以有多个"线程"任务,处理器在多个线程之间进行切换,从而形成并行的多线程处理。线程之间可以共享同一个进程的内存。
7.2 被解放的函数
1.函数作为参数
在函数式编程中,函数是第一级对象,也即是函数能像普通对象一样使用。因此,函数也可以作为参数,成为其他函数的参数。如
def square_sum(a,b): # 准备作为参数的函数,求出平方和
return a**2 + b**2
def cubic_sum(a,b): # 准备作为参数的函数,求出立方和
return a**3 + b**3
def argF_demo(f,a,b): # f是函数,a是数字,b是数字
return f(a,b)
print(argF_demo(square_sum,3,5))
print(argF_demo(cubic_sum,3,5))
结果:
34
152
argF_demo()的第一个参数f是一个函数对象。square_sum和cubic_sum作为f传递给argF_demo()。
再如,图形用户界面中,作为参数的函数经常起到回调(callback)的作用。当某个事件发生时(如界面中的某个按钮被点击),回调函数就会被调用。下面是GUI回调的例子
import tkinter as tk
def callback():
listbox.insert(tk.END,"我出现了,我是回调函数")
if __name__ == "__main__":
master = tk.Tk()
button = tk.Button(master,text = "OK",command = callback)
# 创建一个按钮,一旦点击就会使用callback函数
button.pack()
listbox = tk.Listbox(master)
listbox.pack()
tk.mainloop()
Python中内置了tkinter的图形化功能。listbox是列表栏,就是上图的显示框,一旦点击按钮OK,就会在列表栏中插入"我出现了,我是回调函数 "。
2.函数作为返回值
函数是一个对象,除了可以作为函数的参数,还可以作为函数的返回值。
def line_res():
def line(x):
return 2*x + 1
return line # 返回一个函数
line1 = line_res()
print(line1(3))
结果:
7
line_res将函数line作为对象返回给line1,line1通过输入参数就可以得到结果11。
从上例可以看出,函数里面再定义了函数,函数内部的函数对象也有作用域,Python中用缩进块来表示。如
def line_res():
def line(x):
return 2*x + 1
print(line(3)) # 作用域内
line_res() # 打印出7
print(line(3)) # 作用域之外,将会报错
结果:
7
NameError: name 'line' is not defined
line()函数在line_res()的作用域之内,只能在line_res()之内调用,当在line_res()的作用域之外调用line()函数时,将会报错。
3.闭包
闭包:一个函数与它的环境变量合在一起,即为闭包。
用一个例子来理解闭包和环境变量
def line_res():
b = 15 # 这个就是line()的环境变量
def line(x):
return 2*x + b
b = 5 # line()的环境变量
return line # 返回函数对象
line1 = line_res() # 将函数对象赋给line1
print(line1(3))
结果:
11
line()函数的引用了它作用域外的变量b,b是line()外部的变量,这个b就是line()函数的环境变量。line()函数和环境变量b合在一块,这就是闭包。
上面代码中,b分别在line()定义前后有两次不同的值,最终结果是11,即line()中的b=5。因此,闭包中的环境变量是内部函数作为对象返回时,最近赋值的那个值。
在Python中,闭包(closure)是一个包含环境变量取值的函数对象。环境变量取值被复制到函数对象的__closure__
属性中。
def line_res():
b = 15 # 这个就是line()的环境变量
def line(x):
return 2*x + b + a
b = 5 # line()的环境变量
a = 2 # line()的环境变量
return line # 返回函数对象
line1 = line_res()
print(line1.__closure__) # 闭包
print(line1.__closure__[0].cell_contents) # 打印2 也即是a=2
print(line1.__closure__[1].cell_contents) # 打印5 也即是b=5
结果:
(<cell at 0x000001CC6DA83288: int object at 0x00007FFBB9B19360>,
<cell at 0x000001CC6DDFFFD8: int object at 0x00007FFBB9B193C0>)
2
5
可以看出,闭包属性中包含了一个元组,元组中是cell型的对象。
第一个是2,即环境变量a=2,第二个是5,即b=5。
闭包可以提高代码的复用性。
def line1():
return x + 1
def line2():
return 2*x + 1
def line3():
return 4*x + 2
上面一共有三条直线,可以看出都有一个自变量x,还有另外两个数,可以定义为a和b。则上面的直线可以统一写为:a*x+b。用闭包来改写代码
def line_res(a,b):
def line(x):
return a*x + b
return line
line1 = line_res(1,1)
line2 = line_res(2,1)
line3 = line_res(4,2)
print(line1(2),line2(2),line3(2))
结果:
3 5 10
闭包还可以起到减少函数参数的作用。
def curve(a,b,c,x): # 定义一个二次函数,需要4个参数a,b,c,x
return a*(x**2) + b*x + c
print(curve(2,4,3,1))
print(curve(2,4,3,2))
结果:
9
19
可见每次计算一个二次函数值需要输入很多参数,尽管我们只是自变量x改变而其他参数不变。我们可以通过闭包来减少参数
def curve_closure(a,b,c):
def curve(x):
return a*(x**2) + b*x + c
return curve
curve1 = curve_closure(2,4,3)
print(curve1(1))
print(curve1(2))
结果:
9
19
这段代码可以与上面的代码比较,需要输入的参数少了3个。
7.3 小女子的梳妆匣子
1.装饰器
装饰器是一种高级Python语法。它可以对一个函数、方法或类进行加工。
首先我们定义两个函数,一个计算平方和,一个计算平方差。
def square_sum(a,b):
return a**2 + b**2
def square_diff(a,b):
return a**2 - b**2
print(square_sum(4,3))
print(square_diff(4,2))
结果:
25
12
如果我们想要显示我们的输入,可以改进
def square_sum(a,b):
print("输入的数据",a,b)
return a**2 + b**2
def square_diff(a,b):
print("输入的数据",a,b)
return a**2 - b**2
print(square_sum(4,3))
print(square_diff(4,2))
结果:
输入的数据 4 3
25
输入的数据 4 2
12
可以看出来,打印输入数据的操作语句一样,我们可以将这个打印输入数据的操作写成装饰器,然后用于函数上。
def decorator_demo(old_fun): # 定义一个装饰器
def new_fun(a,b):
print("输入的数据",a,b)
return old_fun(a,b)
return new_fun
@decorator_demo
def square_sum(a,b):
return a**2 + b**2
@decorator_demo
def square_diff(a,b):
return a**2 - b**2
print(square_sum(4,3))
print(square_diff(4,2))
结果:
输入的数据 4 3
25
输入的数据 4 2
12
装饰器用def定义,它接收了一个可调用对象(如函数对象)作为输入参数,并且返回一个新的可调用对象,如上面的new_fun()。在new_fun()中,我们增加了打印的功能,同时也保留了原来old_fun的功能。
定义好装饰器之后,我们通过"@"语法使用它。
在函数square_sum(),square_diff()之前调用装饰器@decorator_demo,实际上就是将原来的函数square_sum(),square_diff()
传递给decorator_demo()
,然后将decorator_demo()
返回的新函数对象赋给原来的函数名square_sum(),square_diff()
。即如在调用square_sum(3,4)
时,实际发生
我们可以再来举一个例子,假设我们想知道函数的运行时间。这个对于每个函数都是适用的,因此我们可以把这个功能做成装饰器。
import time as t
def decorator(old_fun):
def new_fun(*arg,**dict_arg):
start = t.perf_counter()
res = old_fun(*arg,**dict_arg)
end = t.perf_counter()
print("函数运行时间为:",end - start)
return res
return new_fun
@decorator
def curve(a,b,c,x):
return a*x**2 + b*x + c
print(curve(2,3,4,1))
结果:
函数运行时间为: 4.700001227320172e-06
9
2.带参装饰器
装饰器允许我们使用它时,传入其他参数。
def pre_str(pre): # 带参的装饰器
def decorator(old_fun):
def new_fun(a,b):
print(pre,"输入的数据是",a,b)
return old_fun(a,b)
return new_fun
return decorator
#装饰square_sum()
@pre_str(">_<")
def square_sum(a,b):
return a**2 + b**2
#装饰square_dif()
@pre_str("^_^")
def square_dif(a,b):
return a**2 - b**2
print(square_sum(3,4))
print(square_dif(2,1))
结果:
>_< 输入的数据是 3 4
25
^_^ 输入的数据是 2 1
3
pre_str是一个带参装饰器,对原来装饰器进行封装,返回的是一个装饰器。当我们使用@pre_str("_")时,Python能够发现这一层的封装,并将参数传入到装饰器中,相当于
根据参数的不同,带参装饰器会对函数进行不同的加工,进一步提高装饰器的适用范围。
3.装饰类
装饰器还可以装饰类,一个装饰器装饰一个旧类,并返回一个新类,起到了加工类的效果。
def decorator_Class(OldClass):
class NewClass(object):
def __init__(self,age):
self.total_display = 0
self.wrapped = OldClass(age) # 将旧类的对象进行打包
def display(self):
self.total_display += 1
print("展示次数",self.total_display)
self.wrapped.display() # 调用旧类对象的功能或数学
return NewClass # 最终返回一个新类
@decorator_Class
class Bird(object):
def __init__(self,age):
self.age = age
def display(self):
print("我的年龄是:",self.age,"岁")
eagle = Bird(5)
for i in range(3):
eagle.display()
在装饰器decorator_class中,我们定义了新类,在新类中的初始化方法中,用self.wrapped将旧类的对象进行打包,并附加了新属性total_display,用于记录展示次数。而且我们还同时更改了display方法。通过装饰,旧类就可以显示调用display()的次数。7.4 高阶函数
1.lambda与map函数
能接收其他函数作为参数的函数,称为"高阶函数"。像上面介绍的装饰器,也属于高阶函数。最具有代表性的高阶函数有:map()、filter()、reduce()。
在讲解高阶函数之前,先讲解匿名函数lambda。lambda语法也可以用来定义函数,只不过比较实用简短语句的函数。如
lambda_sum = lambda x,y:x+y
print(lambda_sum(3,4))
通过lambda,我们创建了一个匿名函数对象,它的参数是x,y,返回值是x+y,然后将它赋值给lambda_sum()。lambda_sum()的使用与正常函数一样。lambda定义的函数适用于简短函数。
高阶函数从map()开始介绍,map()的第一个参数是函数对象,它把这一个函数对象作用于后面多个元素。第二个元素之后都是循环对象。
data1 = [1,3,5,7]
res = map(lambda x:x+3,data1) # x : 4 7 8 10
map()的第一个参数是函数对象,第二个参数是可循环对象。对于data1中的每个元素,都会成为lambda函数的参数,lambda函数都会调用一次。也就是说,map()接收到的函数对象参数依次作用于每一个参数。map()会返回一个迭代器。上面的代码就相当于如下
def generator(fun,iterator):
for item in iterator:
yield fun(item)
data1 = [1,3,5,7]
res = generator(lambda x:x+3,data1)
lambda函数也可以是多个参数的,如
def square_sum(x,y):
return x**2 + y**2
data1 = [1,3,5,7]
data2 = [2,4,6,8]
res = map(square_sum,data1,data2)
map()函数中第一个参数是函数对象,第二和第三个是可循环对象。第二个循环对象对应于square_sum中的x,第三个循环对象对应于square_sum中的y。2.filter函数
filter函数与map函数相似,但filter()只有两个参数,第一个参数也是函数对象,第二个参数都是循环对象。如果函数对象返回的是True,则该元素被放到迭代器中,即filter()函数通过调用函数来筛选数据。如
def larage100(x):
if x > 100:
return True
else:
return False
for item in filter(larage100,[1,101,202,5]):
print(item)
结果:
101
202
import math
def is_Sqr(x):
return math.sqrt(x) % 1 == 0 # 判断平方根是否为整数
newlist = filter(is_Sqr, range(1, 101))
for item in newlist:
print(item,end = " ")
结果:
1 4 9 16 25 36 49 64 81 100
filter()函数顾名思义,就是一个过滤器,更多的用于筛选出符合我们定义的函数对象里面的条件的数据。
3.reduce函数
reduce函数一共有三个参数,第一个是函数对象,且这个函数对象能够接收两个参数;第二个参数是序列;第三个参数是初始值,无初始值就按照序列的第一个为初始值。
from functools import reduce
res1 = reduce(lambda x,y:x+y,[1,3,5,7,9])
print(res1)
res2 = reduce(lambda x,y:x+y,["x","y","z"],"a")
print(res2)
结果:
24
axyz
从第一个res1看出,它接收两个参数x和y,将列表中的元素进行累进运算,即(((1+3)+5)+7)=16;相似的res2也是如此。
reduce通过二元运算,将多个元素聚集成一个结果。map和reduce都是单线程的,运行效果和循环类似,但map和reduce可以方便的移植入并行化的运行环境下。
在并行运算中,reduce紧接着map运算,map将运算结果分布在多个主机上,reduce运算把结果收集起来。谷歌用于并行运算的软件架构叫MapReduce
4.并行处理
import time
from multiprocessing import Pool
import requests
# 定义一个运行时间的修饰器
def decorator_timer(oldFun):
def newFun(*arg,**dictarg):
start = time.perf_counter()
res = oldFun(*arg,**dictarg)
end = time.perf_counter()
print("运行时间为:",end - start)
return res
return newFun
# 访问网页任务
def download_once(i,addres="http://cnblogs.com"):
print("第",i+1,"次访问完成")
r = requests.get(addres)
return r.status_code
# 单线程,一次处理一个
@decorator_timer
def single_thread(f,times):
print("我是单线程,每次只执行一次")
res = map(f,range(times))
return list(res)
# 多线程,并行处理
@decorator_timer
def multiple_thread(f,times,process_num = 5):
print("我是多线程,任务同时完成")
p = Pool(process_num)
res = p.map(f,range(times))
return list(res)
if __name__ == "__main__":
TOTAL = 10
print(single_thread(download_once,TOTAL))
print()
print(multiple_thread(download_once,TOTAL))
结果:
我是单线程,每次只执行一次
第 1 次访问完成
第 2 次访问完成
第 3 次访问完成
第 4 次访问完成
第 5 次访问完成
第 6 次访问完成
第 7 次访问完成
第 8 次访问完成
第 9 次访问完成
第 10 次访问完成
运行时间为: 12.258385399999952
[200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
我是多线程,任务同时完成
运行时间为: 2.3248039000000063
[200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
上面的download_once是单次的访问网页任务,TOTAL是访问次数需求,一共是10次。
单线程一次一次的进行,最终获取了10个状态码;
而多线程开辟了5个进程,10次任务同时进行,把10个任务分配给了5个工人。从运行时间可以明显看出,多线程的运算更加快速。
7.5 自上而下
1.便捷式表达
Python中有几种体现自上而下思维的语法,如生成器表达式、列表解析与词典解析。
-
生成器表达式
生成器表达式是构建生成器的便捷表达式。假设我们要构建一个生成器
def gen():
for i in range(4):
yield i
上面代码用生成器表达式可以改写为
gen = (x for x in range(4))
-
列表解析(列表生成式)
若我们想要一个0-9每个数的平方的列表,可以这样生成
li = []
for x in range(10):
li.append(x**2)
print(li)
结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
更加便捷地,我们可以使用列表解析,或者说列表生成式
li = [x**2 for x in range(10)]
print(li)
结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
列表生成式的语法很直观,首先说明我们需要的是什么样的元素,再通过for加入限定条件,即哪些元素。再如生成3个随机数列表,每个列表包含10个1到100内的随机整数
import random as r
ran_li1 = [r.randint(1,100) for i in range(10)]
ran_li2 = [r.randint(1,100) for i in range(10)]
ran_li3 = [r.randint(1,100) for i in range(10)]
print(ran_li1)
print(ran_li2)
print(ran_li3)
结果:
[47, 10, 21, 26, 37, 62, 28, 57, 40, 77]
[3, 7, 56, 70, 9, 17, 1, 60, 46, 96]
[59, 98, 87, 79, 55, 52, 37, 31, 84, 39]
我们还可以在列表解析里面加入if语法
x1 = [1,3,5,7]
y1 = [2,4,6,8]
li = [x**2 for x,y in zip(x1,y1) if y > 5]
# 当y>5时,生成对应的x的平方,再保存入列表
print(li)
结果:
[25, 49]
zip()是一个将序列打包的方法,而for i in zip()则可以看成得到序列中的每个元素,即解压。
a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
for i in zip(a,b,c):
print(i)
结果:
(1, 4, 4)
(2, 5, 5)
(3, 6, 6)
--------------------------------------------------
x1 = [1,3,5,7]
y1 = [2,4,6,8,10]
z1 = ["a","b","c","d","e"]
for x,y,z in zip(x1,y1,z1):
print(x,y,z)
结果:
1 2 a
3 4 b
5 6 c
7 8 d
-
词典解析(词典生成式)
类似的,词典也可以快捷生成。与列表解析的语法类似。
d1 = {key:val for key,val in enumerate("HelloWorld")}
print(d1)
d2 = {k:v for k,v in enumerate("Python") if v in "Py"}
print(d2)
"""
enumerate是枚举,包括索引和元素
"""
结果:
{0: 'H', 1: 'e', 2: 'l', 3: 'l', 4: 'o', 5: 'W', 6: 'o', 7: 'r', 8: 'l', 9: 'd'}
{0: 'P', 1: 'y'}
2.懒惰求值
迭代器有时候看起来像列表,但实际上迭代器的元素是实时运算出来的,在使用元素之前,是不会占据空间;而列表在建立时,就先产生了元素,并保存在空间内。
迭代器的工作方式就是懒惰求值,即需要的时候再使用元素,才会计算具体的值。
import time
start1 = time.perf_counter()
for i in (x**2 for x in range(100)):
print(i)
end1 = time.perf_counter()
print("迭代器运行时间:",end1-start1)
结果:
0
1
4
9
……
迭代器运行时间: 0.004747199999656004
import time
start2 = time.perf_counter()
for i in [x**2 for x in range(100)]:
print(i)
end2 = time.perf_counter()
print("列表运行时间:",end2 - start2)
结果:
0
1
4
9
……
列表运行时间: 0.008195000000341679
通过上面的对比可以看出,虽然生成的结果相同,但是迭代器的运行时间小于使用列表运行的时间。这是因为建立列表需要先计算产生元素,再保存生成入列表,再使用列表;而迭代器却是"懒惰求值"。
map和range函数返回的都是迭代器,它们所做的也是懒惰求值,需要的时候再使用;但是若将它们转化为列表,时间将会大大增加、如
#未转化为列表之前
import time as t
start = t.perf_counter()
ite = range(100)
res = map(lambda x:x**2,ite)
print(res) # 返回的是一个迭代器
end = t.perf_counter()
print("运行时间为:",end-start)
结果:
<map object at 0x0000018149C5B8D0>
运行时间为: 0.00012900000001536682
转化为列表
import time as t
start = t.perf_counter()
ite = range(1000)
res = map(lambda x:x**2,ite)
print(list(res))
end = t.perf_counter()
print("运行时间为:",end-start)
结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801]
运行时间为: 0.00015100000018719584
可见100次时,生成为迭代器的时间只需要0.000129,而转化为列表需要0.000151,虽然相差不大,但是已经生成为迭代器的时间已经小于生成为列表的时间。如果将100设置为1000,相差的时间更大。如
1000次时
生成为迭代器的时间
运行时间为: 0.00013020000005781185
生成为列表的时间
运行时间为: 0.0005742000003010617
可以看出,将map生成的结果转化为列表,时间将大大增加。
再者,如果我们不需要穷尽所有数据元素,那么懒惰求值将节省很多时间,而列表生成式的方式,提前准备的数据就会造成极大浪费。如
import time as t
start1 = t.perf_counter()
for i in (x**2 for x in range(100000)):
if i > 500:
break
end1 = t.perf_counter()
print("迭代器运行时间:",end1-start1)
start2 = t.perf_counter()
for i in [x**2 for x in range(100000)]:
if i > 500:
break
end2 = t.perf_counter()
print("列表运行时间",end2-start2)
结果:
迭代器运行时间: 1.700000029813964e-05
列表运行时间 0.04208400000061374
迭代器的运行时间为1.7*10-5,而列表运行时间是0.0420,可见 迭代器更快。也可以看出,除了运行更快外,懒惰求值还可以节省内存空间。
除了map、filter函数,Python中itertools包提供丰富的操作迭代器的工具。
3.itertools包
首先导入itertools包
from itertools import *
- count()和cycle()可以生成无限循环的迭代器。
count(5,2):从5开始每次增加2,无限循环
# count(5,2):从5开始每次增加2,无限循环
for i in count(5,2):
print(i)
if i > 15: # 到15结束,不然无限循环了
break
结果:
5
7
9
11
13
15
17
cycle(list|str|tuple|dict):不断重复序列中的元素
# cycle(list|str|tuple|dict):不断重复序列中的元素
cut1 = 0
for i in cycle([1,3,"ab"]):
cut1 += 1
print(i)
if cut1 == 7: # 不断循环,直到第7次时跳出
break
结果:
1
3
ab
1
3
ab
1
- repeat():返回一个不断重复元素的迭代器,也可以有次数限制的重复。
# repeat():不断重复元素,也可以有次数限制
cut2 = 0
for i in repeat(5):
cut2 += 1
print(i)
if cut2 == 7:# 不断循环,直到第7次时跳出
break
for i in repeat([1,"a",1.2],4): # 重复4次该列表
print(i)
结果:
5
5
5
5
5
5
5
[1, 'a', 1.2]
[1, 'a', 1.2]
[1, 'a', 1.2]
[1, 'a', 1.2]
- 组合旧迭代器,生成新迭代器
chain():连接两个及以上的迭代器
# chain():连接两个及以上的迭代器
for i in chain([1,3,5,7],[2,4,6,8]):
print(i)
结果:
1
3
5
7
2
4
6
8
product():返回多个迭代器的笛卡尔积,即得到元素的所有可能的组合方式,相当于嵌套循环。
# product():返回多个迭代器的笛卡尔积,
# 即得到元素的所有可能的组合方式,相当于嵌套循环
for i in product("abc",[1,3]): # 以元组来接收
print(i)
for x,y in product("abc",[1,3]):# 以每个元素来接收
print(x,y)
结果:
('a', 1)
('a', 3)
('b', 1)
('b', 3)
('c', 1)
('c', 3)
a 1
a 3
b 1
b 3
c 1
c 3
permutations("abc",2):从"abc"中挑2个元素,将所有结果排序,返回新的迭代器,且组合区分顺序。
'''
permutations("abc",2)
从"abc"中挑2个元素,将所有结果排序,
返回新的迭代器,且组合区分顺序
'''
for i in permutations("abc",2):
print(i)
结果:
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')
combinations("abcd",3):从"abcd"中挑3个元素进行组合,将所有结果排序,返回新迭代器且组合不区分顺序,即ab和ba都是ab
'''
combinations("abcd",3)
从"abcd"中挑3个元素进行组合,
将所有结果排序,返回新迭代器
且组合不区分顺序,即ab和ba都是ab
'''
for i in combinations("abcd",3):
print(i)
结果:
('a', 'b', 'c')
('a', 'b', 'd')
('a', 'c', 'd')
('b', 'c', 'd')
combinations_with_replacement("abcd",3):与上面类似,但是允许出现重复的元素,如a,a,a。
'''
combinations_with_replacement("abcd",3)
与上面类似,但是允许出现重复的元素,如a,a,a
'''
for i in combinations_with_replacement("abcd",3):
print(i)
结果:
('a', 'a', 'a')
('a', 'a', 'b')
('a', 'a', 'c')
('a', 'a', 'd')
('a', 'b', 'b')
('a', 'b', 'c')
('a', 'b', 'd')
('a', 'c', 'c')
('a', 'c', 'd')
('a', 'd', 'd')
('b', 'b', 'b')
('b', 'b', 'c')
('b', 'b', 'd')
('b', 'c', 'c')
('b', 'c', 'd')
('b', 'd', 'd')
('c', 'c', 'c')
('c', 'c', 'd')
('c', 'd', 'd')
('d', 'd', 'd')
- starmap(pow,[[1,1],(2,2),(3,3)]):将pow作用于列表中的每个序列
'''
starmap(pow,[[1,1],(2,2),(3,3)])
将pow作用于列表中的每个序列
'''
for i in starmap(pow,[[1,1],(2,2),(3,3)]):
print(i)
结果:
1
4
27
- takewhile(lambda x:x>2,[1,2,5,6]):只有两个参数。当函数返回True时,收集元素到迭代器。一旦返回False,就停止。
'''
takewhile(lambda x:x>2,[1,2,5,6]),只有两个参数
当函数返回True时,收集元素到迭代器。一旦返回False,就停止。
'''
for i in takewhile(lambda x:x<6,[1,2,5,6]):
print(i)
结果:
1
2
5
dropwhile(lambda x:x<3,[1,2,3,2,1,8]):当函数返回False时,跳过并记录元素。一旦返回True,则开始收集剩下的所有元素到迭代器。
'''
dropwhile(lambda x:x<3,[1,2,3,2,1,8])
当函数返回False时,跳过并记录元素
一旦返回True,则开始收集剩下的所有元素到迭代器。
'''
for i in dropwhile(lambda x:x<3,[1,2,3,2,1,8]):
print(i)
结果:
3
2
1
8
groupby():能将一个key函数作用于原迭代器的各个元素,从而获得各个函数的键值。根据key()函数的结果,来对原来迭代器的元素进行分组。
'''
groupby():
能将一个key函数作用于原迭代器的各个元素,从而获得各个函数的键值。
根据key()函数的结果,来对原来迭代器的元素进行分组。
'''
def height_keyFun(height):
if height > 180:
return "Tall"
elif height < 160:
return "Short"
else:
return "Middle"
People = [191,160,158,172,185,157,179]
People = sorted(People,key = height_keyFun) # 按照height_keyFun进行排序,
# 让同组元素先在位置上靠拢。
print(People)
for x,y in groupby(People,key = height_keyFun):
print(x) # x是键
print(list(y)) # y是一个迭代器,相对应于键的值,转化为列表才可以显示
结果:
[160, 172, 179, 158, 157, 191, 185]
Middle
[160, 172, 179]
Short
[158, 157]
Tall
[191, 185]
- 方便迭代器构建的工具
compress(["a",1,9,7],[1,0,0,1]):根据真假情况,选择保留序列中的元素。True保存,False不保存
'''
compress(["a",1,9,7],[1,0,0,1])
根据真假情况,选择保留序列中的元素。True保存,False不保存
'''
for i in compress(["a",1,9,7],[1,0,0,1]):
print(i)
结果:
a
7
islice()与slice相似,只不过返回的是一个迭代器
'''
slice(start,stop,step) 切片器
start -- 起始位置
stop -- 结束位置
step -- 步长
islice()与slice相似,只不过返回的是一个迭代器
'''
li = [1,2,3,"a","z"]
print("切片slice1",li[slice(1,4,1)])
print("切片slice2",li[slice(3)])
print("我是slice,返回的是函数对象",slice(3))
print("我是islice,返回的是迭代器",islice(li,4))
for i in islice(li,5):
print(i,end = " ")
结果:
切片slice1 [2, 3, 'a']
切片slice2 [1, 2, 3]
我是slice,返回的是函数对象 slice(None, 3, None)
我是islice,返回的是迭代器 <itertools.islice object at 0x000001814A824958>
1 2 3 a z
zip_longest()与zip()相似,但是返回的是一个迭代器,且按照的是最长的序列来组合的
'''
zip()在上述中讲过,是按照最短序列来组合的
zip_longest()与其相似,但是返回的是一个迭代器,且按照的是最长的序列来组合的
'''
a = [1,"abc",8,"x","yz"]
b = [9,"ok","Python",777]
print("我是zip(),返回的是一个函数对象,是按照最短序列来组合的",zip(a,b))
for i in zip(a,b):
print(i)
print()
print("我是zip_longest(),返回的是一个迭代器,\
且按照的是最长的序列来组合的,没有就给None",zip_longest(a,b))
for i in zip_longest(a,b):
print(i)
结果:
我是zip(),返回的是一个函数对象,是按照最短序列来组合的
<zip object at 0x0000018149C7D9C8>
(1, 9)
('abc', 'ok')
(8, 'Python')
('x', 777)
我是zip_longest(),返回的是一个迭代器,且按照的是最长的序列来组合的,没有就给None
<itertools.zip_longest object at 0x000001814A824958>
(1, 9)
('abc', 'ok')
(8, 'Python')
('x', 777)
('yz', None)
以上是itertools包的各种功能。