大师兄的Python学习笔记(十二): 常用高级函数
大师兄的Python学习笔记(十四): 迭代器、生成器和协程
一、关于装饰器
- 是Python中最难理解的概念之一,也是最"Pythonic"的用法之一。
- 可以理解是在为函数附加功能,让代码更简洁。
- 需要先理解闭包等概念。
- 本文仅能帮助理解装饰器的概念,更深入的用法还需不断学习。
二、理解装饰器的逻辑
1. 在函数中创建函数
- 先举个简单的例子,如果在函数中创建函数并调用:
>>>def outer():
>>> print("joining inner ... ...") # 第二步
>>> def inner():
>>> print("here in inner!") # 第四步
>>> inner() # 第三步
>>> print("left inner ... ...") # 第五步
>>>if __name__ == "__main__":
>>> outer() # 第一步
joining inner ... ...
here in inner!
left inner ... ...
2. 将函数作为参数并调用
- Python中一切皆对象,所以函数当然也可以当成对象使用。
- 用函数作为参数的方式实现案例1。
>>>def outer(func): # 第二步
>>> print("joining inner ... ...") # 第三步
>>> func() # 第四步
>>> print("left inner ... ...") # 第六步
>>>def inner():
print("here in inner!") # 第五步
>>>if __name__ == "__main__":
>>> outer(inner) # 第一步
joining inner ... ...
here in inner!
left inner ... ...
3. 用老版装饰器的方式实现
- 这是老版本装饰器的实现方式,将函数本身作为参数传递给装饰器,并用装饰器为自己赋值。
>>>def outer(func):
>>> def wrapper():
>>> print("joining inner ... ...") #第三步
>>> func() #第四步
>>> print("left inner ... ...") #第六步
>>> return wrapper() #第二步
>>>if __name__ == "__main__":
>>> def inner():
>>> print("here in inner!") #第五步
>>> inner = outer(inner) #第一步
joining inner ... ...
here in inner!
left inner ... ...
4. 用添加了语法糖@
的装饰器实现
- 在新版本中,为了简化代码,用语法糖
@
替代了
的部分。= ( )
>>>def outer(func):
>>> def wrapper():
>>> print("joining inner ... ...") # 第三步
>>> func() # 第四步
>>> print("left inner ... ...") # 第六步
>>> return wrapper() # 第二步
>>>if __name__ == "__main__":
>>> @outer
>>> def inner(): # 第一步
>>> print("here in inner!") # 第五步
joining inner ... ...
here in inner!
left inner ... ...
5. 解读案例3
- 装饰器用
@
的语法糖表示,放在被装饰函数的上一行。 - 在上面的案例中,
@outer
是函数inner()
的装饰器 - 根据程序运行的顺序可以看出,在使用装饰器的方式时,被装饰的函数作为参数传入装饰器中。
- 需要理解装饰器和案例1、2的关系。
6. 再换个角度理解装饰器
- 假如我们已经创建了“飞翔”的装饰器函数,并将其看成是一种功能。
- 可以创建任何函数(“人”或“鱼”),通过装饰器为他们赋予“飞翔”的功能,见下方代码:
>>>def fly(func):
>>> def wrapper():
>>> func()
>>> print("I can fly now!\n")
>>> return wrapper()
>>>if __name__ == "__main__":
>>> @fly
>>> def human():
>>> print("I am human!")
>>> @fly
>>> def fish():
>>> print("I am fish!")
I am human!
I can fly now!
I am fish!
I can fly now!
三、装饰器的传参
1. 闭包的简单概念
- 在函数A中嵌套另一个函数B,如果函数B引用了函数A的变量,则产生闭包,这些参数可以保存在函数B的作用域中。
>>>def A(param):
>>> def B():
>>> res = param + 1
>>> return res
>>> return B
>>>res = A(1)
>>>print(res())
2
- 另一个例子更好理解闭包。
>>>def A(month):
>>> def B(date):
>>> calendar= month + "/" + date
>>> return calendar
>>> return B
>>>match_month = A("10月")
>>>print(match_month("5日"))
10月/5日
>>>print(match_month("20日"))
10月/20日
2. 先理解不用语法糖的装饰器传参
>>>def outer(func,para):
>>> def decorator(func):
>>> def wrapper(func):
>>> print("joining {} ... ...".format(para)) #第四步
>>> func(para) #第五步
>>> print("left {} ... ...".format(para))#第七步
>>> return wrapper(func)#第三步
>>> return decorator(func) #第二步
>>>if __name__ == "__main__":
>>> def inner(para):
>>> print("here in {}!".format(para)) #第六步
>>> inner = outer(inner,"inner") #第一步
joining inner ... ...
here in inner!
left inner ... ...
- 思路是将函数
和参数 分别存在闭包中,所以需要多一层函数。 - 在装饰器中将函数和参数一起调用。
3. 用语法糖的装饰器传参
>>>def outer(para):
>>> def decorator(func):
>>> def wrapper(func):
>>> print("joining {} ... ...".format(para)) #第四步
>>> func(para) #第五步
>>> print("left {} ... ...".format(para)) #第七步
>>> return wrapper(func)#第三步
>>> return decorator #第二步
>>>if __name__ == "__main__":
>>> @outer("inner") #第一步
>>> def inner(para):
>>> print("here in {}!".format(para)) #第六步
joining inner ... ...
here in inner!
left inner ... ...
- 装饰器语法糖会将被装饰函数作为一个参数传回。
- 其余部分与2.5相同。
四、装饰器的更多用法
1. 多个装饰器修饰同一个函数
- 可以用多个装饰器修饰一个函数,方法是在函数上方添加装饰器,每行一个。
- 本质上就是多个函数嵌套。
- 顺序是先完成离函数最近的装饰器。
>>>import time
>>>def count_time(func):
>>> def wrapper(*args,**kargs):
>>> t0 = time.perf_counter() # 第二步
>>> func(*args,**kargs)
>>> t1 = time.perf_counter() # 第七步
>>> print("耗时:{}秒".format(int(t0 - t1))) # 第八步
>>> return wrapper # 第二步
>>>def notice(func):
>>> def wrapper(*args,**kargs):
>>> print("开始等待...") # 第四步
>>> func(*args,**kargs)
>>> print("结束等待...") # 第六步
>>> return wrapper
>>>if __name__ == '__main__':
>>> @count_time
>>> @notice
>>> def wait():
>>> time.sleep(5) # 第三步(引用函数名) /# 第五步 (执行)
>>> wait() # 第一步
开始等待...
结束等待...
耗时:-5秒
2. 无参数的类装饰器
- 需要使用类的构造函数
__init__
(初始化时触发)和魔法函数__call__
(类被当做函数调用时触发)。
>>>class outer():
>>> def __init__(self,func):
>>> self.func = func
>>> def __call__(self,*args,**kargs):
>>> print("joining inner...")
>>> self.func()
>>> print("left inner...")
>>>if __name__ == '__main__':
>>> @outer
>>> def inner():
>>> print('here in inner!')
>>> inner()
joining inner...
here in inner!
left inner...
3. 带参数的类装饰器
- 与3.3相同,需要在
__call__
中增加一层函数,用于获取参数。 - 需要仔细看代码理解思路。
>>>class outer():
>>> def __init__(self,para):
>>> self.para = para
>>> def __call__(self,func):
>>> def wrapper(*args,**kargs):
>>> print('here in {}...'.format(self.para))
>>> func(*args,**kargs)
>>> print('here in {}...'.format(self.para))
>>> return wrapper
>>>if __name__ == '__main__':
>>> @outer(para = "outer")
>>> def inner(para):
>>> print('here in {}!'.format(para))
>>> inner("inner")
here in outer...
here in inner!
here in outer...
4. 类的装饰器
- 只要类可以被调用,就可以被装饰器装饰。
>>>def outer(cls):
>>> def wrapper():
>>> print('here in outer...')
>>> cls()
>>> print('here in outer...')
>>> return wrapper
>>>if __name__ == '__main__':
>>> @outer
>>> class inner():
>>> def __init__(self):
>>> print('here in inner')
>>> inner()
here in outer...
here in inner
here in outer...
参考资料
- https://blog.csdn.net/u010138758/article/details/80152151 J-Ombudsman
- https://www.cnblogs.com/zhuluqing/p/8832205.html moisiet
- https://www.runoob.com 菜鸟教程
- http://www.tulingxueyuan.com/ 北京图灵学院
- http://www.imooc.com/article/19184?block_id=tuijian_wz#child_5_1 两点水
- https://blog.csdn.net/weixin_44213550/article/details/91346411 python老菜鸟
- https://realpython.com/python-string-formatting/ Dan Bader
- https://www.liaoxuefeng.com/ 廖雪峰
- https://blog.csdn.net/Gnewocean/article/details/85319590 新海说
- https://www.cnblogs.com/Nicholas0707/p/9021672.html Nicholas
- 《Python学习手册》Mark Lutz
- 《Python编程 从入门到实践》Eric Matthes
本文作者:大师兄(superkmi)