Python自动化开发学习4-2

列表生成式python

先看2段代码算法

a = [ i*2 for i in range(10) ]
print(a)
#
b = []
for i in range(10):
   b.append(i*2)
print(b)
app

a和b的效果同样,可是a使用的代码更加简洁ide

列表生成式也可使用函数,生成更加复杂的列表函数

a = [ max(i,6) for i in range(10) ]
print(a)

上面的是铺垫,主要讲下面的生成器测试

生成器
spa

用列表生成式,咱们能够直接建立一个列表。等列表建立完以后,咱们能够访问列表中的元素。可是这都得等列表生成完以后。若是列表很大很复杂,就须要耗费很长的时间和内存空间,而后咱们才能访问列表中的元素。线程

若是列表元素能够按照某种算法推算出来,咱们没必要建立完列表在进行后续的操做,而是一边循环一边引用每个新建立的元素。在Python中,这种一边循环一边计算的机制,称为生成器:generator。code

a = [ i*2 for i in range(10) ]
for i in a:
    print(i)
b = ( i*2 for i in range(10) )  # 这个是生成器
for i in b:
    print(i)
print(type(a),type(b))

上面的代码中,a和b的效果是同样的。可是a的机制是先生成完整列表,再执行for循环。而b中只是记录了一个算法,并无生成任何数据。等到for循环调用b的时候,才一边循环一边计算每个元素。协程

这里咱们感受不出两种机制的差异,可是当列表很大或者计算很复杂的状况下,就能发现二者的差异。咱们强行来增长生成列表的时间。

import time
def f(n):
    time.sleep(1)
    return n*2
a = [ f(i) for i in range(10) ]
for i in a:
    print(i)
b = ( f(i) for i in range(10) )  # 这个是生成器
for i in b:
    print(i)

这下就发现区别了,a是等待了很长时间来生成列表,而后快速的把结果输出。而b是计算一个元素,输出一个元素,开始没有很长的等待时间,可是每次输出之间是要等待1秒来生成新的元素。

顺便我看了一下二者的效率,理论上是差很少的,可是测试下来a要耗时10.02秒,b要耗时10.005秒。每次结果会不一样,可是都在这个数字级别。

import time
def f(n):
    time.sleep(1)
    return n*2
t = time.time()
a = [ f(i) for i in range(10) ]
for i in a:
    print(i)
print(time.time()-t)
t = time.time()
b = ( f(i) for i in range(10) )  # 这个是生成器
for i in b:
    print(i)
print(time.time()-t)

看来使用生成器,也是一个更加效率的方法。

生成器是一边循环一边计算的,全部的元素须要一个一个计算出来。只能一个一个的计算出来,而且只记住了当前的位置,在当前位置你只能取到下一个值,不能退回去,也不能跳过。

因此,生成器只有一个方法.__next__

b = ( i*2 for i in range(10) )
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
# 超出范围会报错

上面好LOW,通常都用循环,__next__用的不是不少。

在继续以前,先用函数写一个斐波那契数列。

斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21,这个数列从第3项开始,每一项都等于前两项之和。

def fib(n):
    i,a,b = 0,0,1
    while i < n:
        print(b)
        a,b = b,a+b
        i += 1
    return "结束"
fib(10)

把上面的函数的print(b)替换成yield b,就实现了用函数作了一个生成器。

def fib(n):
    i,a,b = 0,0,1
    while i < n:
        #print(b)
        yield b  # 替换成这句
        a,b = b,a+b
        i += 1
    return "结束"
f = fib(10)
print(type(f))  # f的数据类型是generator

这是定义生成器的另外一种方法。若是一个函数定义中包含yield关键字,那么这个函数就再也不是一个普通函数,而是一个生成器。

生成器在语句执行遇到yield的时候会返回,可是会记住当前的位置,若是你使用.__next__()则会在以前的位置继续执行。也就是生成器在执行而且返回以后,并无彻底结束。跳出生成器后能够正常执行别的语句,在须要的时候再从以前生成器返回的位置继续执行下一次循环。这样的话生成器最后的return就没有意义了。接着看,咱们先试着打印出生成器中的全部元素。

def fib(n):
    i,a,b = 0,0,1
    while i < n:
        #print(b)
        yield b
        a,b = b,a+b
        i += 1
    return "结束"
f = fib(10)
print(type(f))
print(f.__next__())
print("你能够随时插入你的语句")
print(f.__next__())
print("生成器会记住以前的位置继续循环")
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print("打了那么多就是要超出限制")
print(f.__next__())
print(f.__next__())

若是超出了继续取,就会报错。仔细看一下报错的内容,最后的StopIteration:后的内容就是你return的内容。咱们能够用try来捕获这个错误从而获取return的值。try还没讲到,后面应该会细讲。

def fib(n):
    i,a,b = 0,0,1
    while i < n:
        #print(b)
        yield b
        a,b = b,a+b
        i += 1
    return "结束"
f = fib(10)
while 1:
    try:
        x = next(f)  # x = f.__next__()
        print(x)
    except StopIteration as e:
        print("返回值是:",e.value)
        break

这里x=next(f)和x=f,__next__()效果同样。这里上课没讲,暂时没发现有什么区别。

经过yield还能够实现单线程下的并行效果,这个不叫并行,叫协程。这里好烦,直接抄老师的例子了。

import time
def consumer(name):
    print("%s 准备吃包子啦!" %name)
    while True:
       baozi = yield  # 这里中断,经过send传值进来继续执行
       print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
def producer(name):
    c = consumer('A')  # 定义了一个consumer('A'),可是并无运行
    c2 = consumer('B')  # 定义了一个consumer('B')
    c.__next__()  # consumer('A')启动运行,运行到yield以前,打印"准备吃包子啦"
    c2.__next__()  # consumer('B')启动运行
    print("老子开始准备作包子啦!")
    for i in range(10):
        time.sleep(1)
        print("作了2个包子!")
        c.send(i)  # send是给yield传值
        c2.send(i)
producer("C")

上面的例子里.send(i)以前没提过,和.__next__()同样,可是sent能够传一个值回去。

上面的例子就是producer启动之后,又启动了2个consumer。consumer运行到yield中断,等待。producer将值经过send传给consumer后,consumer就执行一次循环,而后再中断,等待新的值传入。

迭代器

可直接做用于for循环的对象,统称为可迭代对象:Iterable

使用下面这个方法能够判断一个对象是否迭代

from collections import Iterable
print(isinstance('',Iterable))  # 字符串可迭代
print(isinstance(123,Iterable))  # 数字不可迭代
print(isinstance((),Iterable))
print(isinstance([],Iterable))
print(isinstance({},Iterable))
print(isinstance((x for x in range(10)),Iterable))  # 这个是生成器,可迭代

生成器不但能够做用于for循环,还能够next,不断调用返回下一个值。

能够被__next__()调用并不断返回下一个值的对象称为迭代器:Iterator

使用下面的方法再判断一下以前的对象是不是迭代器

from collections import Iterator
print(isinstance('',Iterator))
print(isinstance(123,Iterator))
print(isinstance((),Iterator))
print(isinstance([],Iterator))
print(isinstance({},Iterator))
print(isinstance((x for x in range(10)),Iterator))

只有最后一个能够__next__(),只有最后一个是True,是迭代器。

上面的这些可迭代对象,目前都不是迭代器。经过iter()就能够把这些可迭代对象变成迭代器。

from collections import Iterator
a = [1,2,3,4,5,6]
print(isinstance(a,Iterator))
b = iter(a)
print(isinstance(b,Iterator))
print(b.__next__())
print(b.__next__())
print(b.__next__())

Python的Iterator对象表示的是一个数据流,Iterator对象能够被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。能够把这个数据流看作是一个有序序列,但咱们却不能提早知道序列的长度,只能不断经过next()函数实现按需计算下一个数据,因此Iterator的计算是惰性的,只有在须要返回下一个数据时它才会计算。这样,迭代器甚至能够表示一个无限大的数据流。

咱们的for循环,本质上就是经过不断调用next来实现的。

相关文章
相关标签/搜索