Python----装饰器/生成器/迭代器
2017-11-11 19:42
651 查看
装饰器
装饰器decorator:本质是函数,功能是装饰其他函数,就是为其他函数添加附加功能。原则:
1.不能修改被装饰函数的源代码。
2.不能修改被装饰函数的调用方式。
3.装饰器对被装饰函数来说是透明的,被装饰函数不会感知到装饰器的存在。
实现装饰器知识储备:
1.函数即”变量”。
可以理解为函数名就是变量名,函数体就是变量的内容。函数名指向内存中函数体的位置。
def bar(): print("in the bar") print(bar) fun=bar fun()
<function bar at 0x7fe98a3b22f0> in the bar
如上所示:
bar是函数名,保存了函数bar()在内存中的地址0x7fe98a3b22f0。
fun也保存了bar()的内存地址,所以fun()可以执行,相当于执行bar().
2.高阶函数。
a.把一个函数名当做实参传给另一个函数(在不修改被装饰函数源代码的情况下为其添加功能)。
import time def 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 time def 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 bar the 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 test1 the run time = 1.0011448860168457 int the test2:Jack Ma 50 the 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 time user,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=7 def index(): print("welcome to index page") @auth(auth_type="local") #home=wrapper() #执行step=3 def 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=1 print(home())#执行step=2 print(bbs())
welcome to index page wrapper func Username:mayun PassWord:123456 [32:1m Username has passed authentication[0m welcome to home page -----afer authentication------ from home go to ldap None
列表生成式
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> 1 3 5 7 9 [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是一个tuple a = 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> 1 1 2 3 d725 5 ===start loop=== 8 13 21 34 55
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 isgeneratorfunction print(isgeneratorfunction(fib))
True
要注意区分 fib 和 fib(5),fib 是一个 generator function,而 fib(5) 是调用 fib 返回的一个 generator,好比类的定义和类的实例的区别:
import types print(isinstance(fib, types.GeneratorType)) print(isinstance(fib(5), types.GeneratorType))
False True
return 的作用
在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。串行并发
生成器简单的串行并发实例:一个生产包子,一个吃包子,同时进行。
import time def 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]来了,被吃 做了1个包子! 包子[1]来了,被[A]吃 包子[1]来了,被[B]吃
[b]注意生成器传参函数send()的使用。
迭代器
可以直接作用于for循环的数据类型有以下几种:一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
from collections import Iterable print(isinstance([],Iterable)) print(isinstance('abc', Iterable)) print(isinstance(100,Iterable))
True True False
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
from collections import Iterator print(isinstance((x for x in range(10)), Iterator)) print(isinstance([], Iterator)) print(isinstance({}, Iterator)) print(isinstance('abc', Iterator))
True False False False
注意:
可迭代(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 Iterator list1 = [1,2,3] list2=iter(list1) print(list2.__next__()) print(list2.__next__()) print(isinstance(list1,Iterator)) print(isinstance(list2,Iterator))
1
2
False True
你可能会问,为什么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()则是一次性读取所有内容到内存。
相关文章推荐
- Python之函数(自定义函数,内置函数,装饰器,迭代器,生成器)
- Python(四)装饰器、迭代器&生成器、re正则表达式、字符串格式化
- Python_Day_5装饰器、字符串格式化、序列化、内置模块、生成器、迭代器之篇
- 2.7 学python 装饰器2 生成器 迭代器 模块time random
- 第4章 python闭包函数 装饰器 迭代器 生成器
- Python学习(三):迭代器、生成器、装饰器、递归、算法、正则
- python is、==区别;with;gil;python中tuple和list的区别;Python 中的迭代器、生成器、装饰器
- python学习笔记(5)--迭代器,生成器,装饰器,常用模块,序列化
- 5.python(迭代器,装饰器,生成器,基本算法,正则)
- 老生常谈Python之装饰器、迭代器和生成器
- Python--day4--迭代器-生成器-装饰器-目录
- Python之装饰器、迭代器和生成器
- python核心高级学习总结8------动态性、__slots__、生成器、迭代器、装饰、闭包
- Python学习之三大名器-装饰器、迭代器、生成器 推荐
- Python 三目运算,列表解析,装饰器,迭代器和生成器
- python迭代器 生成器 装饰器
- Python之装饰器、迭代器和生成器
- python 装饰器 上下文管理器 迭代器 生成器 描述符
- python开发学习-day04(迭代器、生成器、装饰器、二分查找、正则)
- python学习 生成器 列表生成式 迭代器