1. 生成器
- 生成器含义
- 创建生成器方法及其执行
- 生成器特点
- 注意一点
什么是生成器?
一边循环一边计算的机制,称为生成器:generator。从计算机科学角度上看,生成器是一种类协程或半协程,提供了一种可以通过特定语句或方法来使生成器的执行对象暂停,而这语句一般都是yield
创建生成器方法及其执行
第一种方法:借用列表推导式形式,区别就是将[ ]改成( )
第二种方法:生成器函数方法。使用 yield 关键字实现。典型的应用是生成器函数实现的菲波那切数列,通过yield语句将每一次的公式计算结果切出执行对象,并带到主线程上来。反过来,主线程也可以通过生成器对象的方法将一个值带回生成器的执行对象中去。例如:const inputValue = yield outputValue
生成器切出执行对象并带出outputValue
,主线程经过同步或异步处理,通过.next(val)
方法将inputValue
带回生成器的执行对象中。
执行生成器使用__next__()
(python3)。不同于列表推导式的for循环,一次性加载完列表元素。生成器对象具有生成值延迟的特性,__next__()
执行一次,输出一个元素,直到出现StopIteration
信息时,说明生成器元素迭代完成。
在函数的执行过程中,yield语句会把你需要的值返回给调用生成器函数的地方,然后进入挂起状态,下一次调用生成器函数的时候又从上次中断的地方开始执行,而生成器内的所有变量参数都会被保存下来供下一次使用。
l = [i**2 for i in range(10)]#列表推导式
g = (i for i in range(10))#列表推导式形式的生成器
>>>l
[0,1,4,9,16,25,36,49,64,81]
>>>g
<generator object <genexpr> at 0x0000000005693E08>
>>>g.__next__()
0
>>>g.__next__()
1
>>>g.__next__()
2
:
:
>>>g.__next__()
StopIteration Traceback (most recent call last)
<ipython-input-14-42e506b10868> in <module>()
----> 1 g.__next__()
StopIteration:
def fib_ret(n):
'''递归实现:return关键字'''
if n==0 or n==1:
return 1
else:
return fib_ret(n-2)+fib_ret(n-1)
#生成器函数
def fib(times):
'''生成器函数:yield关键字'''
n = 0
a,b = 0,1
while n < times:
yield b
a,b = b,a+b
n += 1
return 'done'
生成器特点
1.记录并挂起执行状态。生成器执行一次,就记住上一次返回时在函数体中的位置。对于第二次或者第n次调用跳转至挂起的地方,而上次调用的所有局部变量都保持不变。
2.节约内存。生成器具有暂停运行时的能力。延迟生成值,当调用
__next__()
时,返回一个值,而不是一次性加载所有值到内存中,再从内存中取值。3.迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的。
4.
send()
函数传变量。生成器函数最大的特点是可以接受外部传入的变量,并根据变量内容计算结果返回。一般协程全靠它。
def gen():
value = 0
while True:
receive = yield value
if receive=='e':
break
value = 'got:%s'% receive
g = gen()
print(g.send(None))
print(g.send('aaa'))
print(g.send(3))
print(g.send('e'))
执行流程:
1.通过
g.send(None)
或者g.__next__()
可以启动生成器函数,__next()__()
等价于send(None)
。并执行到第一个yield
语句结束的位置。此时,执行完了yield
语句,但是没有赋值给receive
。yield value
会输出初始值0.注意:在启动生成器函数时只能send(None)
,如果试图输入其它的值都会得到错误提示信息。
2.通过g.send('aaa')
,会传入aaa
,并赋值给receive
,然后计算出value
的值,并回到while
头部,执行yield value
语句有停止。此时,yield value
会输出got:aaa
,然后挂起。
3.通过g.send(3)
,会重复第2步,最后输出结果为got:3
4.当g.send('e')
时,程序会执行break
然后退出循环,最后整个函数执行完毕,所以会得到StopIteration
最后的执行结果如下:
0
got: aaa
got: 3
Traceback (most recent call last):
File "h.py", line 14, in <module>
print(g.send('e'))
StopIteration
5.延迟计算,并提高代码可读性。生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。运行第一个电脑卡机,第二个则没什么内存占用。其次,生成器能够有效提高代码可读性。
sum([i for i in range(10000000000)])
sum(i for i in range(1000000000))
#求一段文字中,每个单词出现的位置
#不使用生成器的情况
def index_words(text):
result = []
if text:
result.append(0)
for index,word in enumerate(text,1):
if word==' ':
result.append(index)
return result
#使用生成器
def index_words(text):
if text:
yield 0
for index,word in enumerate(text,1):
if word==' ':
yield index
1.使用生成器后,代码行数更少。
2.不使用生成器的情况下,对于每次结果,首先看到的是result.append(index)
,其次,才是index
。也就是说,每次是append
操作,且只append
是需要的结果。使用生成器的情况下,直接yield index
,减少了列表append
操作,清晰看到程序要返回index
。
注意一点:生成器只能遍历一次。生成器与python聚合函数一起使用的时候,会出现没有输出结果的情况。
#需求:假设`data.txt`存储每个省份的人口总数。需要求出每个省份的人口占全国总人口的比例。显然,需要先根据各个省份人口总数计算出全国总人口数,然后遍历每个省份的总人口数,用每一个省份的总人口数除以总人口数,得到了每个省份人口占比。
def get_province_population(filename):
with open(filename,'r') as f:
for line in f:
yield int(line)
gen = get_province_population('data.txt')
sum_population = sum(gen)
for population in gen:
print(population/sum_population)
执行以上程序,将不会有任何输出,因为,生成器只能遍历一次。在执行
sum()
语句的时候,就遍历了生成器,当再次遍历生成器的时候,将不会有任何记录。所以,不会有任何输出结果。
2.装饰器
- 含义
- 功能
- 使用
含义
本质上是一个Python函数,它可以让其他函数在不需要做任何代码改动的前提下增加额外的功能。装饰器的返回值也是一个函数对象,经常用于有切面需求的场面,比如:插入日志、性能测试、事务处理缓存、权限校验等场景,使用装饰器后,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。总之,装饰器就是为已经存在的函数或对象添加额外的功能。
功能
. 引入日志
. 函数执行时间统计
. 执行函数前预备处理
. 执行函数后清理功能
. 权限校验等场景
. 缓存
生产上,什么时候用装饰器?
当我们想要给一个函数func()增加某些功能,但又不希望修改func()函数的源代码的时候就需要用装饰器了。(在代码运行期间动态增加功能)
假如,你有一个网站,之前是免费开放的,谁都可以访问。但是有一天你不想免费开放了,你想让大家必须登陆后才能访问,但是呢,网站已经上线了,一直是跑着的,不能修改源码。这个时候就要用这个装饰器了。
import functools # 先得导入这个工具
def login(func):
@functools.wraps(func)
def wrapper(*args, **kw):
user = "zingp" # 假设这是数据库中的用户名和密码
passwd = "123"
username = input("输入用户名:")
password = input("输入密码:")
if username == user and password == passwd:
return func(*args, **kw)
else:
print("用户名或密码错误。")
return wrapper
@login
def home():
print("欢迎来到XX首页!")
home()
print(home.__name__)
3.迭代器
- 可迭代对象
- 迭代器
可迭代对象
可以直接作用于for循环的对象统称为可迭代对象,即Iterable。
. 一是集合数据类型,如list、tuple、dict、set、str等;
. 二是generator,包括生成器和带yield的generator function。
迭代器
可以被next()函数调用并不断返回下一个值(直到没有数据时抛出StopIteration错误)的对象称为迭代器,即Iterator。
import collections
print(isinstance([], collections.Iterable)) # True
print(isinstance(iter([]), collections.Iterator)) # True
print(isinstance(iter([]), collections.Iterable))# True
print(isinstance([], collections.Iterator)) # False
print(isinstance((x * x for x in range(10)), collections.Iterable))
isinstance() 是python内建函数,返回对象是否是类或其子类的实例。若是,返回True,反之返回False。
Iterable 英文是‘可迭代的’,形容词;Iterator英文是‘迭代器’,名词。
那么当 isinstance()的第二个参数是collections.Iterable时,是判断第一个参数是不是Iterable对象(可迭代对象)
当 isinstance()的第二个参数是collections.Iterator时,是判断第一个参数是不是Iterator对象(迭代器对象)
你可能会问,为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据
所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
terable 可以通过iter()函数转换得到 Iterator,但Iterable不一定是Iterator;而Iterator可以直接作用于for循环,所以Iterator是Iterable。