一、装饰器详解
举个栗子:内裤可以用来遮羞,但是到了冬天它没法为我们御寒,聪明的人们发明了长裤,有了长裤宝宝再也不冷了,装饰器就像长裤,在不影响内裤作用的前提下给我们的身子提供了保暖的功效。
再举个栗子:装饰器本质是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。概括的将,装饰器的作用就是为已经存在的对象添加额外的功能。
应用场景:插入日志、性能测试、事物处理、缓存、权限校验等。
1、简易无参装饰器:
1 def user_logging(func): 2 def wrapper(*args,**kwargs): 3 print("这里写装饰器的逻辑代码") 4 return func(*args,**kwargs) 5 return wrapper 6 7 def bar(): 8 print('i am bar') 9 bar = user_logging(bar) 10 bar() 11 # 函数user_logging就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像bar被user_logging装饰了。 12 13 # 修饰后的版本(@符号是装饰器的语法糖,在定义函数的时候,避免再一次赋值操作) 14 def user_logging(func): 15 def wrapper(*args,**kwargs): 16 print("这里写装饰器的逻辑代码") 17 return func(*args) 18 return wrapper 19 20 @user_logging 21 def foo(): 22 print('i am foo') 23 24 @user_logging 25 def bar(): 26 print('i am bar') 27 bar() 28 # 如上所示这样就可以省去bar=user_logging(bar)这一句,直接调用bar()即可得到想要的结果。如果还有其它类似的函数,直接调用,无序重复修改函数。 29 # 这样不仅提高了程序的可重复利用性,并且增加了程序的可读性。
2、有参装饰器:
在上面的装饰器调用中,比如@user_logging,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许在调用时提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。
1 def user_logging(level): 2 def decorator(func): 3 def wrapper(*args,**kwargs): 4 print('这里写装饰器的逻辑代码') 5 return func(*args) 6 return wrapper 7 return decorator 8 9 @user_logging(level='warn') 10 def foo(name='foo'): 11 print('i am %s'% name) 12 foo() 13 # 上面的user_logging是允许带参数的装饰器。它实际上是对缘由装饰器的一个函数封装,并返回一个装饰器。 14 # 可以将它理解为一个含有参数的闭包,当使用@user_logging(level='warn')调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。
3、类装饰器
相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用@形式将装饰器附加到函数上时,就会调用此方法。
1 class Foo(object): 2 def __init__(self,func): 3 self.__func = func 4 5 def __call__(self): 6 print('class decorator runing') 7 self.__func 8 print('class decorator ending') 9 10 @Foo 11 def bar(): 12 print('bar') 13 14 bar()
二、迭代器详解
1、迭代器协议:
由于生成器自动实现了迭代器协议,而迭代器对很多人来说,也是一个较为抽象的概念。未来路更好的理解生成器,先简单回顾一下迭代器协议的概念。
a.迭代器协议是指:对象需要提供next方法,它要么返回迭代中的下一项,要么就引起一个Stoplteration异常,以上终止迭代;
b.可迭代对象:实现了迭代器协议的对象;
协议是一种约定,可迭代对象实现迭代协议,Python的内置工具(for,sum,min,max)等使用迭代器协议访问对象。
三、生成器
1、Python有两种不同的方式提供生成器:
a.生成器函数:常规函数定义,但是使用yield语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行;
1 # 使用生成器函数返回自然数的平方(注意返回的是多个值): 2 # 使用生成器函数: 3 def gensquares(N): 4 for i in range(N): 5 yield i**2 6 7 for item in gensquares(5): 8 print(item) 9 10 # 使用普通函数: 11 def gensquares(N): 12 res = [] 13 for i in range(N): 14 res.append(i*i) 15 return res 16 17 for item in gensquares(5): 18 print(item)
b.生成器表达式:类似于列表推导式,但是生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。
1 # 使用列表推到,将会一次产生所有结果: 2 squares = [x**2 for x in range(5)] 3 print(squares) 4 # [0, 1, 4, 9, 16] 5 6 # 将列表推导式的中括号替换成圆括号,就是一个生成器表达式: 7 squares = (x**2 for x in range(5)) 8 print(next(squares)) # 调用一次计算一次。 9 print(next(squares)) 10 print(next(squares)) 11 print(next(squares)) 12 print(next(squares))
2、深入了解生成器:
1、语法上和函数类似:生成器函数和常规函数几乎是一样的。都使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值;
2、自动是先迭代器协议:对于生成器,Python会自动实现迭代器协议,以便应用到迭代北京中(如for训话,sum函数)。由于生成器自动实现了迭代器协议,所以,调用next方法,并且。并且在没有值可以返回的时候,生成器自动产生 Stoplteration异常。
3、状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器的状态,保留足够的信息,一遍之后从它离开的地方继续执行。
4、生成器只能被遍历一次;
5、使用生成器与不使用生成器的区别:
1 # 假设有个需求是求一段文字中,每个单词出现的位置。 2 # 不适用生成器 3 def index_words(text): 4 result = [] 5 if text: 6 result.append(0) 7 for index,letter in enumerate(text,1): 8 if letter == '': 9 result.append(index) 10 return result 11 12 # 使用生成器的情况: 13 def index_words(text): 14 if text: 15 yield 0 16 for index,letter in enumerate(text,1): 17 if letter == '': 18 yield index
这里至少有两个充分的理由说明,使用生成器比不适用生成器代码更加清晰:
1、使用生成器以后,代码行数更少;
2、不适用生成器的时候,对于每次结果,首先看到的是result.append(index),其次才是index。每次看到的是一个列表的append操作,只是append的是我们想要的结果。使用生成器的时候,直接yield index,少了列表append操作的干扰,我们一眼就能够看出,代码是要返回index。
这个例子充分说明, 合理使用生成器,能够有效提高代码可读性。只要完全接受了生成器的概念,理解了yield语句和return语句一样,也是返回一个值。MAME,就能够理解为什么使用生成器比不适用生成器要好,能够理解使用生成器真的可以让代码变得清晰易懂。
个人备忘,如有错误还望指正。谢谢!