zoukankan      html  css  js  c++  java
  • Python3 从零单排6_装饰器&生成器&迭代器

    1.装饰器

      明月装饰了你的窗子,你装饰了我的梦。所谓装饰就是不改变原来实物本身,只是在事物之外给它加以装饰。

      在编程里一样,因为项目会一直有优化、更新,所以可能会对以前的功能进行优化,那么开发的原则是“开放-封闭”。开放:允许在原有的功能上扩展功能;封闭:不允许修改原代码。所以有了装饰器,在不改变源代码的情况下,增加功能。

      装饰器原理:

    # 现在已经写好了一个函数,现在需要得到它的运行时间
    import time,random
    
    
    def sort_lis(n):
        lis = [i for i in range(n)]
        random.shuffle(lis) #打乱列表顺序
        lis.sort()  # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多
        print("done!")
        
    start_time = time.time()
    sort_lis(1000000)
    end_time = time.time()
    rum_time = end_time - start_time
    print(rum_time)
    
    #完美,我们拿到这个时间,但是如果有很多个这样的函数需要获得运行时间,每次调用都这样去加代码显然很low,而且违反了开发项目的封闭原则。

      这个时候会思考,那么我把这个获取时间的代码,写成一个函数,调sort_lis的时候,直接调这个函数就好了,但是要怎么写呢,因为sort_lis要在你的函数中运行才能拿到时间啊,于是写出了这样的代码

    import time,random
    
    def get_run_time(func):
        start_time = time.time()
        res = func()
        end_time = time.time()
        rum_time = end_time - start_time
        print(rum_time)
        return res
    
    
    def sort_lis():
        lis = [i for i in range(100000)]
        random.shuffle(lis) #打乱列表顺序
        lis.sort()  # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多
        print("done!")
    
    get_run_time(sort_lis)

      哈哈哈,太机智了,直接就拿到了这个运行时间,但是老铁,1你改变了函数的调用方式,2.有没发现万一sort_lis这个函数有传参怎么办,再这样写是不是凉凉了?于是想到了更进一步的办法

    import time,random
    
    def get_run_time(func):
        def wrapper():
            start_time = time.time()
            res = func()
            end_time = time.time()
            rum_time = end_time - start_time
            print(rum_time)
            return res
        return wrapper
    
    def sort_lis():
        lis = [i for i in range(10000)]
        random.shuffle(lis) #打乱列表顺序
        lis.sort()  # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多
        print("done!")
    
    new_func = get_run_time(sort_lis)
    new_func()

      这回你想没啥问题了吧,但是还是上面两点没达到,别人之前是sort_lis()调用,现在变成了new_func()调用,函数名都被你改了,以前的那么多代码都一个个去改么,显然又懵了,于是你想到了我直接用原函数变量名来命名不就好了么?

    import time,random
    
    def get_run_time(func):
        def wrapper():
            start_time = time.time()
            res = func()
            end_time = time.time()
            rum_time = end_time - start_time
            print(rum_time)
            return res
        return wrapper
    
    def sort_lis():
        lis = [i for i in range(10000)]
        random.shuffle(lis) #打乱列表顺序
        lis.sort()  # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多
        print("done!")
    
    sort_lis = get_run_time(sort_lis)
    sort_lis()

      看这样我就实现了吧,既不改变原函数代码,也没改变原来的调用方式,只是加了一句 sort_lis = get_run_time(sort_lis) 就搞定了,没错,这就是装饰器的原理了,不过一般不这样写,用@,俗称语法糖,如下:

    import time,random
    
    def get_run_time(func):
        def wrapper():
            start_time = time.time()
            res = func()
            end_time = time.time()
            rum_time = end_time - start_time
            print(rum_time)
            return res
        return wrapper
    
    @get_run_time
    def sort_lis():
        lis = [i for i in range(10000)]
        random.shuffle(lis) #打乱列表顺序
        lis.sort()  # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多
        print("done!")
    
    sort_lis()

      那么问题来了,要是被装饰函数有传参,上面的这个不是搞不定了吗?

      被装饰函数有传参:

    import time,random
    
    def get_run_time(func):
        def wrapper(*args,**kwargs):
            start_time = time.time()
            res = func(*args,**kwargs)
            end_time = time.time()
            rum_time = end_time - start_time
            print(rum_time)
            return res
        return wrapper
    
    @get_run_time
    def sort_lis(n):
        lis = [i for i in range(n)]
        random.shuffle(lis) #打乱列表顺序
        lis.sort()  # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多
        print("done!")
    
    sort_lis(10000)

      装饰器本身带有传参:等于是把上面的装饰器再包了一层。
      比如这里你想让测试人员吓一跳,你可以这样弄,一个功能相应时间多加sec秒,然后下次提测把不传这个参数,相应时间瞬间就上去了,让测试觉得你很厉害,嘿嘿。

    import time,random
    
    def timer(sec=''):
        def get_run_time(func):
            def wrapper(*args, **kwargs):
                start_time = time.time()
                if sec:
                    time.sleep(sec)
                res = func(*args, **kwargs)
                end_time = time.time()
                rum_time = end_time - start_time
                print(rum_time)
                return res
            return wrapper
        return get_run_time
    
    @timer(2)
    def sort_lis(n):
        lis = [i for i in range(n)]
        random.shuffle(lis) #打乱列表顺序
        lis.sort()  # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多
        print("done!")
    
    sort_lis(10000)

    2.生成器

      先来看下列表生成式:就是根据一个规则,生成一个列表。

    lis = [i for i in range(10)]
    new_lis = [x**x for x in lis]
    print(lis,new_lis)

      生成式很好用,但是当列表元素太多的时候,内存会吃不消,有没有一种方法,我只做一个生产元素的模型,需要元素了我再去模型里拿,每取一次给我生成一个元素,这样就不占内存,而且还达到了目的。是的,这个模型就是生成器。

      生成器的创建和调用:

    # 生成器的第一种创建方式:就是把上面的列表生成式的[]符号,改成(),就是一个生成器了:
    new_lis = (x**x for x in range(10))
    print(new_lis)
    # <generator object <genexpr> at 0x0000000004034938> 这就是我们的生成器了
    
    # 生成器的第二种创建方式:函数里加上 yield,函数就变成了一个生成器
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            # print(b)
            yield  b
            a, b = b, a + b
            n = n + 1
        return 'done'
    new_fib = fib(2)
    print(new_fib)
    # <generator object fib at 0x00000000040149E8>  这就是我们函数生成的生成器了
    
    # 生成器的三种调用方式:
    print(next(new_fib))  # 直接next可以一次次的获取生成器生成的元素,当取完最后一个元素时,会报错:StopIteration: done
    print(new_fib.send("hello")) # 同next,只是可以传递一个信号到生成器内部。
    for i in new_fib:  # 这个调用和循环去可迭代对象一样,一般是用这种方法调用生成器,调完结束不会报错。
        print(i)

      上面有说道信号,这个信号有啥用呢?

    def fib2(max):
        n, a, b = 0, 0, 1
        while n < max:
            # print(b)
            single = yield  b  #拿到send过来的信号
            if single == "stop": #如果信号说stop,就跳出循环,不再生成元素
                break
            a, b = b, a + b
            n = n + 1
        return 'done'
    new_fib = fib2(7)
    print(next(new_fib))
    print(new_fib.send("hello"))
    print(new_fib.send("stop"))  #生成器在接到这个信号的时候,直接不再返回元素,抛错:StopIteration: done

    3.迭代器

      我们已经知道,可以直接作用于for循环的数据类型有以下几种:一类是集合数据类型,如list、tuple、dict、set、str等;一类是generator,包括生成器和带yield的generator function。

      这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象:

    from collections import Iterable
    
    
    isinstance([], Iterable)
    # True
    isinstance({}, Iterable)
    # True
    isinstance('abc', Iterable)
    # True
    isinstance((x for x in range(10)), Iterable)
    # True
    isinstance(100, Iterable)
    # False

      而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
      *可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。可以使用isinstance()判断一个对象是否是Iterator对象:

    from collections import Iterator
    
    
    isinstance((x for x in range(10)), Iterator)
    # True
    isinstance([], Iterator)
    # False
    isinstance({}, Iterator)
    # False
    isinstance('abc', Iterator)
    # False

      生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
      把list、dict、str等Iterable变成Iterator可以使用iter()函数:

    isinstance(iter([]), Iterator)
    # True
    isinstance(iter('abc'), Iterator)
    # True

      可能会有疑问,为什么list、dict、str等数据类型不是Iterator?
      这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
      Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

      小结:
      凡是可作用于for循环的对象都是Iterable类型;
      凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
      集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
      Python3的for循环本质上就是通过不断调用next()函数实现的,例如:

    for x in [1, 2, 3, 4, 5]:
        pass
    # 实际上完全等价于:
    
    # 首先获得Iterator对象:
    it = iter([1, 2, 3, 4, 5])
    # 循环:
    while True:
        try:
            # 获得下一个值:
            x = next(it)
        except StopIteration:
            # 遇到StopIteration就退出循环
            break
  • 相关阅读:
    JS从后台获取数据,前台动态添加tr标签中的td标签
    Java方式导出EXCEL表格
    框架搭建相关博文
    Spring框架相关博文集
    Eclipse相关工具使用
    关于Spring Boot的博客集合
    Springboot spring data jpa 多数据源的配置01
    springboot1.X 到2.X 的改变
    Spring-Boot devtools项目自动重启
    JSR 303
  • 原文地址:https://www.cnblogs.com/znyyy/p/10021171.html
Copyright © 2011-2022 走看看