• 生成器、迭代器、装饰器


    生成器(generator)

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

    所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    要创建一个generator,有很多种方法。

    第一种generator,把一个列表生成式的[]改成(),就创建了一个generator:

    >>> L = [x*x for x in range(5)]
    >>> L
    [0, 1, 4, 9, 16]
    >>> g = (x*x for x in range(5))
    >>> g
    <generator object <genexpr> at 0x01BB55A0>

    创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator,我们可以直接打印出list的每一个元素,打印generator只能获得一个内存地址,那我们要怎么获得值呢?
    • 调用next()或者__next__(),next是内置函数,__next__()是generator方法,generator保存的是算法,每次调用next(g)或g.__next__(),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
    >>> next(g)    #等同于g.__next__()
    0
    >>> next(g)
    1
    >>> next(g)
    4
    >>> next(g)
    9
    >>> next(g)
    16
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration  
    • 上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象。
    >>> g = (x*x for x in range(5))
    >>> for i in g:
    ...     print(i)
    ...
    0
    1
    4
    9
    16  

    所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

    • 用捕捉异常的方式try ..except..else..也可以实现
    g = (x*x for x in range(5))
    while True:
        try:
            num = next(g)
        except StopIteration:
            break
        else:
            print(num)
    结果:
    0
    1
    4
    9
    16

    第二种generator,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现:

    比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

    1, 1, 2, 3, 5, 8, 13, 21, 34, ...

    • 函数实现斐波拉契数列
    def fib(n):
        i,a,b=0,1,1
    
        while i<n:
            print(a)
            a,b = b,a+b #等同于t= (b,a+b),a = t[0] b = t[1]
            i+=1
    fib(6)
    
    结果:
    1
    1
    2
    3
    5
    8
    • 上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:
    def fib(n):
        i,a,b=0,1,1
    
        while i<n:
            yield a
            a,b = b,a+b
            i+=1
        return '超出'

      这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

    f = fib(6)
    print(f)
    结果:
    <generator object fib at 0x01265540>
    

      generator和函数的执行流程不一样,函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。那怎么取得值呢?同样有三种方法,这里就不写next的实现方式了。

    for i in fib(6):   #for实现方式
        print(i)
    结果:
    1
    1
    2
    3
    5
    8
    -------------------------------------------------------
    f = fib(6)        #捕捉异常实现方式
    while True:
        try:
            x = f.__next__()
        except StopIteration as e:
            print("Generator return value %s"%e.value)
            break
        else:
            print(x)
    结果:
    1
    1
    2
    3
    5
    8
    Generator return value 超出

    yield还可实现在单线程的情况下实现并发运算,详见代码

    def customer():
    
        while True:
            s = yield                               #等待请求
            
            if isinstance(s,str):#接收到请求后,开始处理请求
                print("字符串")
            elif isinstance(s,list):
                print("列表:%s"%s)
    
    def productor():
        a = customer()
        a.__next__()             #不掉用next,只是把函数变成一个生成器
    
        a.send("你知道我是什么类型嘛?")  #发送信息
        a.send([1,2,3,4,5])
    
    productor()

     

    迭代器(iterator)

    可以直接作用于for循环的数据类型有以下几种:

    一类是集合数据类型,如listtupledictsetstr等;

    一类是generator,包括生成器和带yield的generator function。

    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

    可以使用isinstance()判断一个对象是否是Iterable对象

    >>> from collections import Iterable
    >>> isinstance([],Iterable)
    True
    >>> isinstance({},Iterable)
    True
    >>> isinstance(3,Iterable)
    False
    >>> isinstance('123',Iterable)
    True 
    可以被next()函数调用并不断返回下一个对象称为迭代器:iterator
    可以使用isinstance()判断一个对象是否是Iterator对象
    >>> from collections import Iterator
    >>> isinstance([],Iterator)
    False
    >>> isinstance((x for x in range(5)),Iterator)
    True 
    生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator
    listdictstrIterable变成Iterator可以使用iter()函数
    >>> from collections import Iterator
    >>> isinstance([],Iterator)
    False
    >>> isinstance(iter([]),Iterator)
    True

     

    装饰器(decorator)

    先了解清除几个概念:什么是高阶函数?什么是嵌套函数?

    • 高阶函数

      变量可以指向函数,下面以内置函数求绝对值函数abs为例

    >>> abs(-10)
    10
    >>> abs
    <built-in function abs>
    >>> f= abs             #变量f指向函数abs
    >>> f
    <built-in function abs>
    >>> f(-10)            #调用函数
    10
    

      说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。

    函数名也是变量

    那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!

    如果把abs指向其他对象,会有什么情况发生?

    >>> abs = 10
    >>> abs(-10)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not callable
    >>> abs
    10

    abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10

    当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。

    注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10

    传入函数

    既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

    def add(x,y,f):
        return f(x)+f(y)
    
    print(add(-1,3,abs))
    
    结果:
    4
    

      编写高阶函数,就是让函数的参数能够接收别的函数。

      返回函数

    高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

    我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:

    def calc_sum(*args):
        ax = 0
        for i in args:
            ax+=i
        return ax
    

      但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:

    def lazy_sum(*args):
        def add():
            ax = 0
            for i in args:
                ax += i
            return ax
        return add
    

      分别调用calc_sum(1,2,3,4,5)和lazy_sum(1,2,3,4,5)是什么结果呢?

    print(calc_sum(1,2,3,4,5))
    print(lazy_sum(1,2,3,4,5))
    结果:
    15
    <function lazy_sum.<locals>.add at 0x01062198>
    

      calc_sum直接返回计算结果

      lazy_sum返回嵌套函数add的内存地址,lazy_sum即嵌套函数,那要怎么得到结果呢?很简单,和调用函数的方式一样

    print(lazy_sum(1,2,3,4,5)())
    #15
    

      请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

    f1 = lazy_sum(1,2,3,4,5)
    f2 = lazy_sum(1,2,3,4,5)
    print(f1)
    print(f2)
    结果:
    <function lazy_sum.<locals>.add at 0x011D2198> 
    <function lazy_sum.<locals>.add at 0x011D2108>   

    那装饰器是什么呢?  

    定义:本质是函数,(装饰其他函数)就是为其他函数添加附加功能
    原则:1.不能修改被装饰的函数的源代码
            2.不能修改被装饰的函数的调用方式
     
    高阶函数+嵌套函数 = 装饰器
     
    装饰器普通版:不加参数,为text函数增加了个计算text函数运行的时间
    import time
    
    def timer(func):               #传入函数
        print("我进入timer函数了")
    
        def demo():                  #嵌套函数
            start_time = time.time()
            func()
            end_time = time.time()
            print("func time is %s"%(end_time-start_time))
        return demo               #返回函数
    
    @timer      #装饰器@timer相当于text = timer(text)
    def text():               #不加参数
        time.sleep(1)
        print("in the text")
    
    text()
    结果:
    我进入timer函数了
    in the text
    func time is 1.0075056552886963
    当我们调用text()函数时,实际上运行了两步,第一、text = timer(text),此时text指向了demo,第二、调用text(),那实际上就是调用demo()

    装饰器升级版:每个函数可能都有不同的参数,那我们要怎么实现呢?
    import time
    
    def timer(func):
        def demo(*args,**kwargs):                  #传入非固定参数
            start_time = time.time()
            func(*args,**kwargs)
            end_time = time.time()
            print("func time is %s"%(end_time-start_time))
        return demo               
    
    @timer        
    def text():               #不加参数
        time.sleep(1)
        print("in the text")
    
    @timer       
    def text2(name):          #加参数
        time.sleep(1)
        print("in the text2!parameter is %s"%name)
    text()
    text2("lxj")
    结果:
    in the text
    func time is 1.000171422958374
    in the text2!parameter is lxj
    func time is 1.000110387802124
    
    
    
    装饰器终极版,装饰器传入参数。认证用户登录登陆user和bbs需进行密码验证
    user1,word1 = 'lxj','123'
    
    def my_key(key):
        def wrapper(func):
            def loggin(*args,**kwargs):
                if key == 'local':
                    username = input("username:").strip()
                    password = input("password:").strip()
                    if username == user1 and password ==word1:
                        func(*args,**kwargs)
                        print("33[32;1m验证成功33[0m")
                    else:
                        print("33[32;1m验证失败33[0m")
                elif key =='lbdr':
                    print("%s验证尚未激活该功能"%key)
            return loggin
        return wrapper
    def home():
        print("welcome to homepage!")
    
    @my_key(key = 'local') 
    def user():
        print("welcome to personal page!")
    @my_key(key = 'lbdr')        #装饰器带参数
    def bbs():
        print("welcome to bbs !")
    home()
    user()
    bbs()
    结果:
    welcome to homepage!
    username:lxj
    password:123
    welcome to personal page!
    验证成功
    lbdr验证尚未激活该功能
    

      我们对调用uesr()进行剖析:当我们调用user时,实际上user = my_key('local')(user),首先是执行my_key('local'),返回函数wrapper,相当于指向wrapper,后面加(user),相当于调用wrapper(user),此时user指向了loggin,最后我们调用user()其实就是调用loggin()

    
    
  • 相关阅读:
    一定要在3 20前完成所有的程序开发工作
    浅谈图像处理方向的就业前景[转)
    期待牛人指教的问题?
    vc6 7工程转vc8时的问题
    今天的工作计划
    定点数与浮点数区别
    difference between texRECT and tex2D
    Render to Texture
    不明白gluperpective的fovy参数
    批处理程序教程(转)
  • 原文地址:https://www.cnblogs.com/zj-luxj/p/6839853.html
Copyright © 2020-2023  润新知