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

 

posted @ 2018-12-05 16:44  毛斯钢  阅读(184)  评论(0编辑  收藏  举报