生成器、迭代器、装饰器

生成器(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()


原文地址:https://www.cnblogs.com/zj-luxj/p/6839853.html