Python----装饰器/生成器/迭代器

来源:互联网 发布:淘宝优惠券群怎么弄 编辑:程序博客网 时间:2024/06/13 23:47

装饰器

装饰器decorator:本质是函数,功能是装饰其他函数,就是为其他函数添加附加功能。
原则:
1.不能修改被装饰函数的源代码。
2.不能修改被装饰函数的调用方式。
3.装饰器对被装饰函数来说是透明的,被装饰函数不会感知到装饰器的存在。

实现装饰器知识储备:
1.函数即”变量”。
可以理解为函数名就是变量名,函数体就是变量的内容。函数名指向内存中函数体的位置。

def bar():    print("in the bar")print(bar)fun=barfun()
<function bar at 0x7fe98a3b22f0>in the bar

如上所示:
bar是函数名,保存了函数bar()在内存中的地址0x7fe98a3b22f0。
fun也保存了bar()的内存地址,所以fun()可以执行,相当于执行bar().

2.高阶函数。
a.把一个函数名当做实参传给另一个函数(在不修改被装饰函数源代码的情况下为其添加功能)。

import timedef bar():    time.sleep(1)    print("in the bar")def test(func):    start_time = time.time()    func()    end_time = time.time()    print("the run func time is %s " % (end_time-start_time))test(bar)
in the bar    #没有改变源码the run func time is 1.0003530979156494  #添加了新的功能

b.返回值中包含函数名(不修改函数的调用方式)。

import timedef bar():    time.sleep(1)    print("in the bar")def test(func):    start_time = time.time()    func()    end_time = time.time()    print("the run func time is %s " % (end_time-start_time))    return func   #装饰器返回函数名bar = test(bar)  #重点,返回的源函数名重新赋给源函数名bar()          #源函数调用方式不变
in the barthe run func time is 1.0005016326904297 in the bar

3.嵌套函数。
高阶函数+嵌套函数=》》》》装饰器。
函数嵌套注意与函数调用区别,嵌套是指在一个定义函数体内,再用def声明定义另一个函数,才称为函数嵌套,如果只是在单纯调用,则不叫函数嵌套。

函数嵌套

def foo():    print("in the foo")    def bar():        print("in the bar")    bar()

函数调用

def bar():    print("in the bar")def foo():    print("in the foo")    bar()

装饰器实例:

import time#装饰器timer()def timer(func):    def deco(*agrs,**kwargs): #传递多参数符        start_time = time.time()        func(*agrs,**kwargs)  #传递多参数符        end_time = time.time()        print("the run time = %s" % (end_time - start_time))    return deco@timer #test1=timer(test1)def test1():    time.sleep(1)    print("int the test1")@timer #test2=timer(test2)def test2(name,age):    time.sleep(1)    print("int the test2:%s %d\n" % (name,age))test1()test2("Jack Ma",50)
int the test1the run time = 1.0011448860168457int the test2:Jack Ma 50the run time = 1.0011117458343506

例子中特别注意:
@timer 等价于test1=timer(test1),在需要被装饰的函数前加上@装饰器。
从中可以看出,要创建修饰器,步骤包括:
1.要构建一个新功能函数deco(),这个新功能函数deco包含两个部分:一是调用原函数而是添加新功能。
2.构建装饰函数timer(test1),传参是原函数名。装饰函数是一个由新功能函数构成的嵌套函数,并在装饰函数返回新功能函数内存地址(即新功能函数名)。
3.在原函数定义处前加@timer,把新功能函数内存地址重新赋给原函数,原函数地址指向新功能函数地址。
4.执行test1(), —-这里实质已经变成了执行新功能函数。
5.原函数test1不带参数,原函数test2带参数,如果同一个装饰器要同时装饰带参数和不带参数的原函数,则需要在新功能函数deco()传多参数适配符(*args,**kwargs)。

装饰器最终版

根据不同用户选择不同登录方式进入网站,要求装饰器根据输入参数不同,装饰不同网页。

import timeuser,passwd = 'mayun','123456'def auth(auth_type):#执行step=4    #在最外层接收区分用户的参数    print("auth_type:",auth_type)#执行step=5    def outer_wrapper(func):#执行step=6        #包装新功能函数wrapper()的外层函数,作用是传入原函数home()的内存地址home。        def wrapper(*args, **kwargs):#执行step=8        #真正的新功能函数wrapper()=原函数home()+新功能            if auth_type == "local" :#执行step=10                print("wrapper func",*args, **kwargs)                Username = input("Username:").strip()                PassWord = input("PassWord:").strip()                if user == Username and passwd == PassWord:#执行step=11                    print("\033[32:1m Username has passed authentication\033[0m")                    res = func(*args, **kwargs)  # from home#执行step=12                    print("-----afer authentication------")                    return res#执行step=15                    #返回home()执行结果,保证装饰器输出结果与原函数一致,不改变原函数返回值。                else:                    print("\032[32:1m Invalid username of passwd!\033[0m")            elif auth_type == "ldap" :#执行step=10                print("go to ldap")#执行step=11        return wrapper#执行step=9    return outer_wrapper#执行step=7def index():    print("welcome to index page")@auth(auth_type="local") #home=wrapper() #执行step=3def home():#执行step=13    print("welcome to home page")    return "from home"#执行step=14@auth(auth_type="ldap")def bbs():    print("welcome to bbs page")index()  #执行step=1print(home())#执行step=2print(bbs())
welcome to index pagewrapper funcUsername:mayunPassWord:123456[32:1m Username has passed authenticationwelcome to home page-----afer authentication------from homego to ldapNone

列表生成式

list_original = [0,2,4,6,8]#需要在原始列表list_original中的每个值加1,方法有以下几种:#普通青年列表生成式list_normer = []for i in list_original:list_normer.append(i+1)print(list_original)print(list_normer)#文艺青年列表生成式list_artist = []list_artist=map(lambda i:i+1,list_original)print(list_original)print(list_artist)for i in list_artist:    print(i)#装逼青年列表生成式list_bier = [2*i+1 for i in range(5) ]print(list_original)print(list_bier)
[0, 2, 4, 6, 8][1, 3, 5, 7, 9][0, 2, 4, 6, 8]<map object at 0x7f2d27ce3a58>13579[0, 2, 4, 6, 8][1, 3, 5, 7, 9]

生成器

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

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

普通列表生成方法是把一次性生成列表所有值,并存储在内存当中,可以随时调用任意值。
生成器只有在调用时才会生成相应的数据,内存只记录生成方法,和当前生成器指针位置。
生成方法只有一个next()—–Python3.X __next()__ —-Python2.X

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)]>>> L[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]>>> g = (x * x for x in range(10))>>> g<generator object <genexpr> at 0x1022ef630>>>> next(g)0>>> next(g)1

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, …
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

#Fibonacci#1, 1, 2, 3, 5, 8, 13, 21, 34, ...def fib(max):    n, a, b = 0, 0, 1    while n < max:        print(b)        a, b = b, a + b   #理解赋值内涵,中间生成临时元组进行赋值。        n = n + 1    return 'done'fib(5)

注意,赋值语句:

a, b = b, a + b

相当于:

t = (b, a + b) # t是一个tuplea = t[0]b = t[1]

函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

生成器实例

#Fibonacci generator#1, 1, 2, 3, 5, 8, 13, 21, 34, ...def fib(max):    n, a, b = 0, 0, 1    while n < max:        #print(b)        yield b  #生成器标识符yield(产生,出产)        a, b = b, a + b        n = n + 1    return 'done'f=fib(5)print(f)print(f.__next__())print(f.__next__())print(f.__next__())print(f.__next__())print(f.__next__())print(f.__next__())print("===start loop===")for i in f:    print(i)
<generator object fib at 0x7f34e84b95c8>11235===start loop===813213455

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误,如上max=5时,调用 了6次数f.next(),调用越界报StopIteration异常

    print(f.__next__())StopIteration: done

当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:
所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。正确使用生成器的姿势是使用for循环调用

简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
我们可以得出以下结论:

一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。

如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:
清单 7. 使用 isgeneratorfunction 判断

from inspect import isgeneratorfunctionprint(isgeneratorfunction(fib))
True

要注意区分 fib 和 fib(5),fib 是一个 generator function,而 fib(5) 是调用 fib 返回的一个 generator,好比类的定义和类的实例的区别:

import typesprint(isinstance(fib, types.GeneratorType))print(isinstance(fib(5), types.GeneratorType))
FalseTrue

return 的作用

在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

串行并发

生成器简单的串行并发实例:
一个生产包子,一个吃包子,同时进行。

import timedef consumer(name):    print("%s 准备吃包子" % name)    while True:        baozi = yield        print("包子[%s]来了,被[%s]吃"%(baozi,name))def producer(name):    c = consumer('A')    c2 = consumer('B')    c.__next__()  #注意执行一遍next()的意义是为了是consumer函数执行并到达yield出中断。    c2.__next__() #如果没有执行一遍next(),则执行send()时consumer不是在yield中断处获取到参数。    print("厨师准备开始做包子")    for i in range(2):        time.sleep(1)        print("做了1个包子!分两个人吃!")        c.send(i)  #send()方法传递一个值给生成器yield,并触发中断的yield继续执行,知道再次遇到yield。        c2.send(i)producer("Mayun")
A 准备吃包子B 准备吃包子厨师准备开始做包子做了1个包子!包子[0]来了,被[A]吃包子[0]来了,被[B]吃做了1个包子!包子[1]来了,被[A]吃包子[1]来了,被[B]

注意生成器传参函数send()的使用。

迭代器

可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:

from collections import Iterableprint(isinstance([],Iterable))print(isinstance('abc', Iterable))print(isinstance(100,Iterable))
TrueTrueFalse

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

from collections import Iteratorprint(isinstance((x for x in range(10)), Iterator))print(isinstance([], Iterator))print(isinstance({}, Iterator))print(isinstance('abc', Iterator))
TrueFalseFalseFalse

注意:
可迭代(Iterable)和迭代器(Iterator)是有差别的,
列表等集合数据类型都是可迭代的,所以是可迭代对象,但不是迭代器,因为没有next()方法。
生成器肯定是迭代器,因为生成器可迭代,且有next()方法。

>>> dir([])['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

通过dir([])查看列表内置方法可以看到并没有next()方法,所以只能说列表是可迭代对象,但别不是迭代器。

重点:可迭代对象可以通过iter()方法转成迭代器。

from collections import Iteratorlist1 = [1,2,3]list2=iter(list1)print(list2.__next__())print(list2.__next__())print(isinstance(list1,Iterator))print(isinstance(list2,Iterator))
12FalseTrue

你可能会问,为什么list、dict、str等数据类型不是Iterator?

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

小结:

1.凡是可作用于for循环的对象都是Iterable类型;
2.凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
3.集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的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

事实上,在Python3.X上,ranger()就是一个迭代器。在Python2.X上range()不是迭代器,xrange()才是迭代器,所以有如下区别:
Python2.X

>>> range(5)[0, 1, 2, 3, 4]>>> xrange(5)xrange(5)

读取文件for line in f:利用的就是迭代器的原理取数据,使用next()取数据。readlines()则是一次性读取所有内容到内存。

原创粉丝点击