python笔记4(装饰器、生成器、迭代器)

一、熟练掌握装饰器的原理

(在装饰器学习的过程中,查了看了很多资料,个人感觉走了很多的弯路,这个笔记,分享我的理解,希望能帮助到一些人。本文对装饰器的描述,侧重点是条理与逻辑思路,想通过从无到有的方式,描述一个理解的过程,重点在step by step 更快的理解这个东西)

需要理解装饰器的两大组成,使用装饰器时能解决不确定长度的形参传递、函数返回值及条件判断的场景。

1、使用装饰器的目的及原则

目的:为原来的函数增加一些新的功能,如日志输出,运行时间计算,登录退出等。

实现的原则(两点):

(1)不修改原函数代码----->此处说明装饰器不能给原函数修改功能减少功能。

(2)不改变原函数再整个主函数中的调用方式

2、装饰器需要实现的效果以及需要面临并需要去解决的问题

我们通过定义三个函数来直观体现我们需要使用装饰器所实现的效果以及所面临的问题

def func1():
    time.sleep(3)
    print('so good')
def func2():
    t1=time.time()
    time.sleep(3)
    print('so good')
    t2=time.time()
    print('耗时%ss'%(t2-t1))
def func3():
    t1=time.time()
    func1()
    t2=time.time()
    print('耗时%ss'%(t2-t1))
#func1=func3 
func1()

此代码说明:上述代码有三个函数func1、func2、func3。其中func1模拟的是需要被修饰的函数。其功能为输出'so good',其中func2是我们希望通过装饰器所实现的效果,就是给函数添加一个计算运行时间,并输出耗时的功能,但是明显在原函数代码量非常大的情况下,不是很合适这种方式,相当于重写。func3是我们理想的效果,我们没有去动func1的代码,而是在func3中直接引用了func1并计算时间,以此来实现输出耗时的效果。此时,我们要面临的问题是,func3确实是实现了效果,但是此时我们需要引用func3()来实现最终完整的效果(即计算了耗时的同时又输出了so good),这一点不符合装饰器的第二点原则,我们需要解决它。

那么此时有人提出假如我们把func3赋值给func1呢,那不是解决了调用的问题,事实上仔细分析发现并不是这样的,func1=func3的赋值实际构建了一个递归函数,而且是个死循环。(此处如果不理解func1=func3不要紧,下文会仔细分析函数即变量这个内容。)

 

3、理解装饰器的基本功之一----函数与变量及内存的关系

3.1 理解python中变量内存中的存在形式与c的区别,理解函数的本质就是变量。

python中变量在内存中的保存方式与c不同,python采用一部分空间记录变量名及变量内容保存的位置,一部分空间保存变量的值。如下图所示:

 

 

此时,我们需要深入思考x=1 y=x x=[1,2,3,4] x={1:'a',2:'b'} def x():print('ok') ;calc = lambda x:x*3 匿名函数在内存中的保存方式

3.2  理解python中变量及赋值与内存的关系

 从内存的角度看上文的func1、func2、func3,以及我们想实现的效果

函数,实际上就是一个函数名,它指向内存内的一段空间,该空间保存着函数体,通过变量加()的方式来执行函数体。 函数可以赋值给变量,变量内如果保存着函数的函数体,那么可以执行。

 

通过上面的分析,我们大致认识到我们需要做什么,但是我们除了新写一个函数的方式以外似乎没有一个很好的方式来实现这个效果。真的除了新写函数以外就无能为力了吗,不是的。

(这里想补充说一句,我们在定义变量,列表,字典函数等的时候,python解释器会将你所定义的东西加入globals或locals中,通过print glocals或local的方式你可以查看到你的代码能使用到的各种变量名、函数名,程序运行时候,我们通过各种变量名称去取值,去执行,都是在全局变量(global)或者局部变量(local)中去寻找,如果你要使用的东西不在global或local中则会报错,这个点如果不理解的可以自己print globals或local查看一下就能发现了)

4、理解装饰器的基本功之二----用高阶函数构造一个装饰器半成品

通过上文的分析,到此处要明白几个点:1、我要给原函数添加功能,但是我不想重新写 2、函数就是个变量。我要尝试使用赋值等方式来完成我想要的效果。此部分我们通过高阶函数来完成一个装饰器的半成品,为什么叫半成品呢,此处先不多说,直接开始正式内容。

4.1 什么是高阶函数

高阶函数有两种形式,符合以下任意一种形式的函数都叫做高阶函数:

a.将函数名作为实参传入的函数,可以叫做高阶函数

def fun1(x):
    print(locals)
    x()
def fun2():
    print('ok')

fun1(fun2)
-------------------------

<built-in function locals>
ok

 

该程序说明:函数fun1的形参为x,在函数体内执行了x()对x进行了调用执行,所以此时这个x变量是一个可以被调用执行的变量,为一个函数名,此时fun1为高阶函数。fun2为一个普通函数,函数的效果为输出ok。fun1(fun2),先输出了locals,再输出了ok。相对于fun2而言,fun1(fun2)完成了增加print locals的功能,同时完成了原有功能print ok。是装饰器需要完成的效果之一(

不改变原函数的代码,给原函数添加新功能),但是这个fun1(fun2)的调用方式和原始的fun2()调用方式明显是更改的调用,所以不能称之为装饰器。

b.将函数名作为返回值的函数,可以叫做高阶函数

def a1():
    print('it is a1')
def b2():
    print('it is b2')
def gaojie():
    print('正在运行高阶函数gaojie()')
    return b2
a1()
a1=gaojie()
a1()
----------------------------
it is a1
正在运行高阶函数gaojie()
it is b2

 4.2 高阶函数与装饰器半成品

上文说了,高阶函数和函数即变量的观点我们来写一个装饰器的半成品

def a1():
    print('我是a1,我的功能就是输出这句话')
a1()
print('\n\n****************下面开始运行装饰后的a1**********************')
def a2(x):
   global tmp #装饰器具有复用性,所有我们此处用a2来接收需要修饰的函数x,把这个x赋值给tmp
   tmp=x      #由于tmp如果直接使用则为局部变量运行a2结束后会被释放,所以此处tmp要为global
   return a3
def a3():
    #print('我是a2,我的功能要修饰一个函数,添加一个欢迎栏')
    print('你好,欢迎使用本程序'.center(50,'-'))
    tmp()
a1=a2(a1)
print('*********************a1装饰结束***************************\n\n')
a1()
------------------------------------------

我是a1,我的功能就是输出这句话



****************下面开始运行装饰后的a1**********************
*********************a1装饰结束***************************



--------------------你好,欢迎使用本程序--------------------
我是a1,我的功能就是输出这句话

 

本程序说明:我们用了a3和a2两个高阶函数一起完成了一个很简陋的装饰器半成品。没有修改原来的函数的代码,也没有修改调用方式,也能够完成复用装饰多个函数。为什么此处说它是个半成品呢,因为我们要对这个装饰器做进一步的修改,我们希望能把a2和a3组合成一个函数,而且不希望用global的方式,函数内一般是不会轻易去使用global定义全局变量的。

如何修改,如何合并,如何解决这个函数内定义的global变量,这个是我们现在要面临的问题,此时需要用到嵌套函数

5、理解装饰器的基本功之三---嵌套函数与闭包

5.1 嵌套函数

def L1():
    print('i am L1')
    def L2():
        print('welcome to L2')
        def L3():
            print('L3 ok')
        #L3()
    #L2()
    #L3()
L1()
--------------------
i am L1

此函数说明:1、最下方的L3()如果执行会报错,因为L1中的global和local都没有这个L3变量   2、L2定义后如果L2不调用 就不会执行 3、L2中可以使用L3

5.2 闭包的理解(变量作用域、变量内存空间的释放及闭包)

5.2.1

明确一个点(此处不多做叙述)本地变量的优先级高于全局变量。

5.2.2

变量内存空间的释放--如果保存变量内容(值、函数体)的空间没有再被引用,那么这部分内容会被程序释放掉。内存回收机制的一种。此处不做深究。

根据此观点,如果a->print(‘hello world’),a->('hi man'), 那么此时hello world的方法会被释放

5.3 嵌套函数的局部变量与闭包。

我们从代码入手来说明闭包

a1=111
a2=222
def L1():
    #print(a1) 
    a1=111111
    print(a1)
    def L2():
        print(a1)
        a2=222222
        print(a2)
        print('globals is %s'%globals())
        print('locals is %s'%locals())
    return L2
new=L1()
new()
del L1
print('--------------现在L1函数已经被删除了-----------------')
new()
------------------------------------------------------------
111111
111111
222222
globals is { 'a1': 111, 'a2': 222, 'L1': <function L1 at 0x0000013CEF132E18>, 'new': <function L1.<locals>.L2 at 0x0000013CF0C93620>}
locals is {'a2': 222222, 'a1': 111111}
--------------现在L1函数已经被删除了-----------------
111111
222222
globals is {'a1': 111, 'a2': 222, 'new': <function L1.<locals>.L2 at 0x0000013CF0C93620>}  #这里的L1已经没有了
locals is {'a2': 222222, 'a1': 111111}

此代码说明:先定义了两个全局变量a1/a2,L1中又定义了一个局部变量a1,需要注意的是注释掉的那个print(a1),那个a1会报错因为a1是局部变量,但是a1局部变量定义的位置在print下方。L1中又定义了一个L2 。在运行结束后,我们把L1删掉,发现函数运行的结果没有变化,局部变量a1的值变成了L2的locals局部变量被保留在new函数中了。

def level1():
    L1=1
    def level2():
        L2=2
        def level3():
            L3=3
            #print(globals())
            print(locals())
            print(L1)
        level3()
        r2=level3
        return r2
    r1=level2
    return(r1)
xxx=level1()
print('--------------')
xxx()

--------------
{'L3': 3, 'L1': 1} #这里没有L2
1

这种现象就是闭包

闭包的含义:这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。

5.4 高阶函数+嵌套函数=装饰器

我们需要知道函数的实参被输入到函数后,会变成这个函数的局部变量,但嵌套函数被作为返回值直接赋值给本地的全局变量时,嵌套函数内所需要的局部变量会被闭包的概念保存下来,归属于这个被返回的函数。

故我们通过使用高阶函数和嵌套函数的方式来组成装饰器,解决上文的遗留问题global tmp。使得需要被修饰的函数闭包的方式保留在嵌套函数内。

def NB_test():
    print('我需要被装饰')
    time.sleep(2)
def NB_zsq(x): #定义一个装饰器,按照自己的思路来
    global tmp_fun
    tmp_fun=x
    def NB_zsq2():
        t1 = time.time()
        tmp_fun()
        t2 = time.time()
        print('耗时%ss' % (t2 - t1))
    return NB_zsq2

NB_test=NB_zsq(NB_test)
NB_test()
---------------------------------------------------------------------------------

def NB_test():
    print('我需要被装饰')
    time.sleep(2)
def NB_zsq(x): #装饰器的标准思路
    def NB_zsq2():
        t1 = time.time()
        x()#闭包保存
        t2 = time.time()
        print('耗时%ss' % (t2 - t1))
        print(locals())
    return NB_zsq2
print(NB_test)
NB_test=NB_zsq(NB_test)
NB_test()
del NB_zsq
NB_test()
print(NB_test)
---------------------------------------------------------------------------------
<function NB_test at 0x0000000001E28A60>
我需要被装饰
耗时2.0s
{'t2': 1516545552.4453638, 't1': 1516545550.4453638, 'x': <function NB_test at 0x0000000001E28A60>}
我需要被装饰
耗时2.0s
{'t2': 1516545554.4453638, 't1': 1516545552.4453638, 'x': <function NB_test at 0x0000000001E28A60>}
<function NB_zsq.<locals>.NB_zsq2 at 0x0000000001E28B70>

5.5 装饰器的简写---@

import time
def NB_zsq(x): #装饰器的标准思路
    def NB_zsq2():
        t1 = time.time()
        x()#闭包保存
        t2 = time.time()
        print('耗时%ss' % (t2 - t1))
    return NB_zsq2
@NB_zsq
def NB_test():
    print('我需要被装饰')
    time.sleep(2)
NB_test()

6、高级装饰器

6.1 不考虑装饰器,使用两个高阶函数如何处理返回值和参数传入

通常我们需要修饰的函数是需要有实参传入的,那么如何去处理有参数传入的问题,且考虑到装饰器的复用性,当多个需要被修饰的函数的参数不一样时,能否用一个统一的装饰器来处理呢,还有,原函数如果有返回值,如何去处理?

下面我们先通过用高阶函数拆分的方式来先分析一下我们面对的问题。

import time
def NB_test(x,y,z):
    print('我需要被装饰')
    print('三个数的积为%s'%(x*y*z))
    time.sleep(2)
def NB_zsq(x): #定义一个装饰器,按照自己的思路来
    global tmp_fun
    tmp_fun=x
    return NB_zsq2
def NB_zsq2(x,y,z):
    t1=time.time()
    tmp_return=tmp_fun(x,y,z)
    t2=time.time()
    print('耗时%ss' % (t2 - t1))
    return tmp_return 
NB_test=NB_zsq(NB_test)
NB_test(2,3,4)
----------------------------------
我需要被装饰
三个数的积为24
耗时2.0012001991271973s

该程序说明:我们在定义的高阶函数nb_zsq2中接收参数x,y.z。并将x,y,z传入到tmp_fun中,使用tmp_return 记录tmp_fun的返回值,并让nb_zsq2返回tmp_return。这样,对于NB_test这个被修饰的函数来说,我们解决了他参数传递和返回值的情况。此处我们先不着急思考如何将这两个高阶函数结合嵌套函数组成一个正式的装饰器,我们先来考虑一下复用性的问题。当多个函数所需的参数不定时,如何去处理这个不定的参数。

6.2 不定参数的处理

6.2.1 制作使用*的方式调用列表,元组及字典

a=(1,2,3,4,5)
print(*a)
b={'cus_key1':'123','name':'xxx'}
print(*b)
def c(x1,x2,x3,x4,x5):
    print(x1,x2,x3,x4,x5)
c(5,6,7,3,1)
c(*(5,6,7,1,1))
c(*[1,2,3,4,5])
print('------------')
print(*())#此处输出空
print('------------')
print(*{})
print('------------')
*****************************************************************
1 2 3 4 5
cus_key1 name
5 6 7 3 1
5 6 7 1 1
1 2 3 4 5
------------

------------

------------

该程序说明:通过程序可以看出*x,这个x不管是列表还是字典,使用*的方式都能将其中元素逐个取出,并供给需要的函数使用。当内容为空时,取出结果也为空。这个特性为我们解决非固定参数埋下伏笔

6.2.2 使用 *args *.kwargs 来接收函数的非固定参数

def x(*args,**kwargs):
    print(args)
    print(kwargs)
x(1,2,3,4,5)
x(11,22,33,44,[1,2,3,4,5],{1:1234})
x(666,x=1,y=2)
----------------------------

(1, 2, 3, 4, 5)
{}
(11, 22, 33, 44, [1, 2, 3, 4, 5], {1: 1234})
{}
(666,)
{'x': 1, 'y': 2}

此程序说明:此程序意在演示,当函数传入的参数为不确定的时,args及kwargs分别会获取到哪些内容。通过程序的运行结果,我们发现我们可以通过*argv **kwargs来获取不定参数,同时结合上面的列子*x来取值供给相应的函数使用参数。

通过这两个内容的应用,我们再结合上嵌套函数来构建一个装饰器用来处理不定参数的情况。

6.2.3 完成能处理不定参数的装饰器

import time
def NB_zsq(x): #定义一个装饰器,按照自己的思路来
    def NB_zsq2(*args,**kwargs):
        t1 = time.time()
        tmp_return=x(*args,*kwargs)
        t2 = time.time()
        print('耗时%ss' % (t2 - t1))
return tmp_return
return NB_zsq2 @NB_zsq def NB_test(x,y,z): print('我需要被装饰') print('三个数的积为%s'%(x*y*z)) time.sleep(2) @NB_zsq def NB_test2(): print('我没有参数') #NB_test=NB_zsq(NB_test) NB_test(2,3,4) NB_test2() ---------------------------- 我需要被装饰 三个数的积为24 耗时2.0002002716064453s 我没有参数 耗时0.0s

此程序说明:使用*args,**kwargs来完成一个不定参数的装饰器,分别装饰NB_test /NB_test2 两个函数。一个函数需要参数,一个函数无参数。均能解决。

6.3 复用性再提升,最终版装饰器

之前的装饰器都是@xxx的方式来装饰,做个假设我是一个要输出时间的装饰器,我希望装饰不通的函数的时候输出不通的时间格式,再或者我是个登录模块的装饰器,我希望通过装饰器给原始函数添加登录的功能。我希望不通的函数有不通的登录验证方式,比如本地认证、ldap认证、不认证等。那如何在一个装饰器中完成这种区别装饰,这是这里我们需要解决的。

name='yomi'
passwd='123456'
def zsq_type(type):
    def zsq_func(x):
        def new_func(*args,**kwargs):
            if type=='local':
                inputname=input('please input your name:').strip()
                inputwd=input('please input your password:').strip()
                if inputname==name and inputwd==passwd :
                    print('登录成功')
                else:
                    print('登录失败,退出')
                    return '登录失败'
            elif type=='ldap':
                print('连接ldap服务器,认证通过')
            else:
                print('认证方式未定义')
                return '认证方式未定义,请联系管理员处理'
            result=x(*args,*kwargs) #接受原有的返回值
            return result
        return new_func
    return zsq_func
@zsq_type(type='null')
def indexpage():
    print('welcome to index page')
    return 'from page'
@zsq_type(type='local')
def homepage():
    print('welcome to home page')
    return 'from home'
@zsq_type(type='ldap')
def bbspage():
    print('welcome to bbs')
    return 'from bbs'


x1=indexpage()
print(x1)
x2=homepage()
print(x2)
x3=bbspage()
print(x3)
----------------------------
认证方式未定义
认证方式未定义,请联系管理员处理
please input your name:yomi
please input your password:123456
登录成功
welcome to home page
from home
连接ldap服务器,认证通过
welcome to bbs
from bbs

此程序说明:@xxx(xxx)的格式的装饰器,相比之前的装饰器,最终版的装饰器有这三层结构,第一层结构用来传递type,return到第二层。第二层用来传递需要被修饰的函数,如return到第三层,第三层内为新函数,判断type的种类并执行相关的功能,最终ruturen原函数的返回值。此时第二层的return值,即第三层函数变为新函数传递并替换了原函数,并将type和原函数 以闭包的形式保存下来了。最终实现了终极版的装饰器。

二、生成器

1、列表生成式的写法

import time
t1=time.time()
a=[i*i for i in range(50000000)]
t2=time.time()
print(t2-t1)
----------------------------
3.7499794960021973

此程序说明:此程序说明的是一个列表生成式,也就是说,我们通过规定列表中的每个元素的关系,通过函数来讲每个元素生成出来

2、生成器的写法

t3=time.time()
b=(i*i for i in range(50000000))
t4=time.time()
print(t4-t3)
--------------------------
0.0

 此程序说明:将列表生成式的[]改成()则做成了一个生成器,很明显可以看出,生成器的耗时比列表生成器要节省得多

3、列表生成式与生成器在内存中的对比

import time
t1=time.time()
a=[i*i for i in range(50000000)]
t2=time.time()
#print(t2-t1)
t3=time.time()
b=(i*i for i in range(50000000))
t4=time.time()
#print(t4-t3)
print(a,b)
#print(a[3],b[3])列表可以切片,但是生成器不行
-----------------------------------
 [0....2499999800000004, 2499999900000001] <generator object <genexpr> at 0x0000029121222B48>

此程序说明:列表生成器之所以耗时,是因为它将整个列表的每一位元素都已经计算好了,并保存在了内存中,这样耗时,且占用内存,当带来的便利是我可以对列表进行切片,可以任意的获取列表内每一个元素的值,生成器则不同,运行生成器后,内存内只是保存当前的值以及下一个元素的生成方式。即生成器只能往下运行,获取下一个元素,不能倒退,也不能跳跃,必须逐一去获取下一个元素,但是生成器在生成的时候却相当节省时间节省内存。

4、生成器的取值方式 __next__方法

a=[i*2 for i in range(5)]
print('it is a {_a}'.format(_a=a))
b=[]
for i in range(5):
    b.append(i*2)
print('it is b %s'%b)
c=(i*2 for i in range(5))
print('it is c %s'%c)
print(c.__next__())
print('for循环开始')
for i in c:
    print(i)
print('*******************************')
#print(c.__next__()) 此处再运行next会报错
-------------------------------------------------

it is a [0, 2, 4, 6, 8]
it is b [0, 2, 4, 6, 8]
it is c <generator object <genexpr> at 0x000001A992242A98>
0
for循环开始
2
4
6
8
*******************************

 

此程序说明:意在表现与列表对比,列表能批量输出,生成器只能向下走next输出,不能往回走,也不能跳跃,走完就没有了。

5、yield形式的生成器

生成器还有一种yield发形式,yield可以用于输出,如print,也能用来接收值,类型x=yield的赋值

5.1 使用生成器来生成斐波那契数列---yield作为输出的使用

def fbnq(x):#斐波那契函数
   n,a,b=0,0,1
   while n<x:
     print(b)
     a,b=b,a+b
     n=n+1
   return 'done'
fbnq(6) #看函数的特点 不能回退 只能往下推一步
def scq(y):
    n,a,b=0,0,1
    while n<y:
      yield(b) #构建生成器,yield为print的使用
      a,b=b,a+b
      n+=1
    return 'done'
test1=scq(6)#此处无输出
print(test1)
print(test1.__next__())
print(test1.__next__())
print(test1.__next__())
print(test1.__next__())
----------------------------------------

1
1
2
3
5
8
<generator object scq at 0x000001C3E2782A98>
1
1
2
3

 

此程序的说明:函数fbnq为标准的一个函数,输出fbnq数列,其中需要注意的是a,b=b,a+b这种写法。函数scq为构建了一个生成器。相比于fbnq函数,实际就是把print(b)改成了yield b。在使用这个scq函数的时候,我们需要去使用这个生成器函数去构建一个生成器实体,在构建实体的过程中,不会有输出值。使用构建的生成器的next来分别输出值,这里注意要用print(xxx.__netx__())yield并不能直接输出。

5.2 yield作为赋值的使用

使用yield给变量赋值要搭配send来使用

 

def comsumer(name):
    print('我是{_name},我要吃包子了'.format(_name=name))
    while True:
         baozi=yield
         print('{_name}在吃包子,包子是第{_baozi}笼的'.format(_name=name,_baozi=baozi))

def producer(name):
     c1=comsumer('tom')#实体化一个生成器
     c2=comsumer('xiaoming') #此时并不会输入 我要吃包子这一句话
     c1.__next__() #如果没有这一句 程序会出错
     c2.__next__() #如果没有这一句 程序会出错
     print('欢迎光临小店')
     for i in range(10):
          print('%s正在生产第%s笼包子'%(name,i))
          c1.send(i)
          c2.send(i)

producer('leijun')
--------------------------------------
我是tom,我要吃包子了
我是xiaoming,我要吃包子了
欢迎光临小店
leijun正在生产第0笼包子
tom在吃包子,包子是第0笼的
xiaoming在吃包子,包子是第0笼的
leijun正在生产第1笼包子
tom在吃包子,包子是第1笼的
xiaoming在吃包子,包子是第1笼的
leijun正在生产第2笼包子
tom在吃包子,包子是第2笼的
xiaoming在吃包子,包子是第2笼的
leijun正在生产第3笼包子
tom在吃包子,包子是第3笼的
xiaoming在吃包子,包子是第3笼的
leijun正在生产第4笼包子
tom在吃包子,包子是第4笼的
xiaoming在吃包子,包子是第4笼的
leijun正在生产第5笼包子
tom在吃包子,包子是第5笼的
xiaoming在吃包子,包子是第5笼的
leijun正在生产第6笼包子
tom在吃包子,包子是第6笼的
xiaoming在吃包子,包子是第6笼的
leijun正在生产第7笼包子
tom在吃包子,包子是第7笼的
xiaoming在吃包子,包子是第7笼的
leijun正在生产第8笼包子
tom在吃包子,包子是第8笼的
xiaoming在吃包子,包子是第8笼的
leijun正在生产第9笼包子
tom在吃包子,包子是第9笼的
xiaoming在吃包子,包子是第9笼的

 

该程序说明:生产者赋值制造包子由消费者吃,消费者可以有很多,其中核心就是yield与send的配合。

def c(x):
    print('i am %s'%x)
    while True:
        baozi=yield
        print('%s is eating %s'%(x,baozi))
c1=c('xiaoming')
c1.__next__()
for i in range(10):
    c1.send(i)
-------------------------------------
i am xiaoming
xiaoming is eating 0
xiaoming is eating 1
xiaoming is eating 2
xiaoming is eating 3
xiaoming is eating 4
xiaoming is eating 5
xiaoming is eating 6
xiaoming is eating 7
xiaoming is eating 8
xiaoming is eating 9

该程序说明:单线程下的并行效果(协程)

三、迭代器

作为一种概念去理解

1.可迭代对象---可以通过for来输出所有的元素

常见的可迭代对象 iterable有两类:

一类是集合数据类型 包括了str list tuple set dict

一类是generator 生成器 包括 yeild的 generator func

for i in 'abc':#str
    print(i)
for i in [1,2,3]:#list
    print(i)
for i in {'111':'a','222':'b',333:'c'}:#dict
    print(i)
for i in (1*2 for x in range(5)):#生成器
    print(i)
for i in (1,2,3,4):#tuple 元组
    print(i)
----------------------------------------
a
b
c
1
2
3
111
222
333
2
2
2
2
2
1
2
3
4

此程序说明:此程序列出了str list tuple set dict tuple这些常见的数据类型为可迭代对象。同时生成器(两种形式)都为可迭代对象。需要注意的是dict用for i 输出的效果。

2、可迭代对象的判断

我们可以使用 from collections import Iterable来判断这个元素是不是可迭代对象

from collections import Iterable
print(isinstance([],Iterable))
print(isinstance('abc',Iterable))
print(isinstance({},Iterable))
print(isinstance((),Iterable))
print(isinstance(1000,Iterable)) #数字不可迭代
-------------------------------------
True
True
True
True
False

3、迭代器---next

可以被next函数不断调用并不断返回下一个值的对象叫做迭代器 iterator

from collections import Iterator
print(isinstance((x*2 for x in range(20)),Iterator))#判断是否为迭代器
print(dir((x*2 for x in range(20))))#dir 查看方法
-------------------------------------
True
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', 

'__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',

'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']

该程序的说明:几个要点一个是dir的使用、一个是collections import Iterator,一个print(isinstance((x*2 for x in range(20)),Iterator))

生成器一定是迭代器 迭代器不一定是生成器

3、可迭代对象与迭代器的对比及转换

使用iter可以将可迭代对象转换为迭代器

a='hello world'
xx=iter(a)
print(xx.__next__())
print(xx.__next__())
print(xx.__next__())
print(xx.__next__())

----------------------------------------
h
e
l
l

4、综述迭代器思想

迭代器是一种惰性计算 ,平时我们使用的range(10)是迭代器,文件操作 f 也是迭代器readline()

迭代器iterator在python中是一个数据流,我们可以吧数据流看做一个有序的序列,但是却不能提前知道序列的长度。itarator的计算是惰性的,它可以表示无穷大的序列。

四、补充一个

进度条的输出:核心思想就是\r 、flush、sys.stdout的组合使用,注意不能使用print

#进度条的输出
import time
import sys
for i in range(51):
     tmp_done='>'*i
     tmp_none=' '*(50-i)
     sys.stdout.write('\r[\033[31;1m%s%s\033[0m]%s%%'%(tmp_done,tmp_none,i*2))
     sys.stdout.flush()
     time.sleep(0.2)
#必须用sys.stdout.write来输出
---------------------------------
[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]100%
原文地址:https://www.cnblogs.com/yomi/p/8419475.html