疫情环境下的网络学习笔记 python 3.23

3.23

上节回顾

*args,**kwargs

  • *args:*用于把实参中多出来的位置实参赋值给args

  • **kwargs,**把多出来的关键字实参变成字典赋值给kwargs

  • 实参中 * 用于打散

  •   def index(*args,**kwargs):
          print(x,y)
      
      def wrapper(*args,**kwargs):  # (1,2,34),{'x':1,'y':2}
      	index(*args,**kwargs)  # index(1,2,34,x=1,y=2)
      	
      wrapper(1,2,34,x=1,y=2)
    

    用这种格式,将wrapper调用时的实参格式原封不动传给index

名称空间与作用域

  • 名称空间的嵌套关系时在函数定义阶段,即检测语法的时候确定的
  • 函数对象
    • 可以把函数当作另外一个函数的参数传入
    • 函数可以当作返回值

函数的嵌套定义

  • 在函数内包一个函数

    def outter():
    	def inner():
    		pass
    	return inner  # 不加括号,返回 inner的内存地址
    

闭包函数

  1. 内嵌函数,且引用了其外层函数中的名字

    def f1():
    	x = 1
    	y = 2
    	z = 3
    
    f1()
    

    f1的名称空间正常在调用完之后会被清理,但是如果其中的名字被其他地方引用到了,那他的引用计数还存在,他的名称空间不会被回收

传参的方式

  1. 通过参数的形式为函数传值

  2. 通过闭包的方式为函数体传值

    def outter(x):
    	def wrapper():
    		print(x)
    	return wrapper  # 处于局部
    	
    wrapper = outter(1)  # 处于全局
    
  3. 名称空间:不同的名称空间可以使用同样的名字,所以把局部的函数赋值给全局的同名函数是可以的

正课

装饰器

什么是

  • 器指的是工具,可以定义成函数

  • 装饰指的是为其他工具添加功能

  • 装饰器:

    定义一个工具(类,函数,目前只学了函数),用来为其他函数增加功能

为何要用

在真实开发环境中对原功能进行拓展,应遵循开放封闭原则,线上运行的软件不能轻易修改或关闭

开放封闭:想为一个函数增加新功能,但是不想改变这个函数的源代码

  • 开放:对拓展功能开放
  • 封闭:对修改源代码是封闭的

为原函数增加新功能的时候,可能涉及到很多个地方,如果错了一个,则整体程序报错,牵一发动全身。

装饰器就是在不修改被装饰对象源代码及调用方式为前提下,为原函数增加功能

如何用

示例

def index(x,y):
	print('index %s %s'%(x,y))
	
index(111,222)

这里有一个普通的函数,现在想为这个函数加上一个新功能,统计函数的运行时间

方法一:修改了源代码

  • 用time 模块 time.time 返回当前时间戳:从1970年到当前时间的秒数

  • 运行函数前time.time,运行结束后再time.time,函数结束时间 减 函数起始时间就是函数执行时间

    import time
    def index(x,y):
        start = time.time()
    	time.sleep(1)
    	print('index %s %s'%(x,y))
        end = time.time()
    	print(end - start)
    
    index(111,222)
    

    确实实现了功能,没有修改调用函的方式,但是修改了源代码,不符合开放封闭原则

方法二:调用前后加语句

  • 在调用前后用time.time

    import time
    def index(x,y):
    	time.sleep(1)
    	print('index %s %s'%(x,y))
        
    start = time.time()
    index(111, 222)
    end = time.time()
    print(end - start)
    

    在每一个需要统计时间的时候,调用前后都加上这几行代码,实现了不改变源代码,又不改变调用方式,但是在很多个地方都需要这个新功能,都需要重复写这几句语句,代码十分冗余,不妥

解决办法:装饰器

  • 使用闭包函数,解决了代码冗余的问题,但是调用方式变成 wrapper(),且index被写死111,222

    import time
    def index(x,y):
    	time.sleep(1)
    	print('index %s %s'%(x,y))
    	
    def wrapper(x,y):
    	start = time.time()
        index(111, 222)
        end = time.time()
        print(end - start)
       
    wrapper()
    
  • 改进1,不把wrapper写死:

    	index(x,y)
    	...
    wrapper(111,222)
    
  • 改进2,使用 **,可以传入多个参数,把参数写活了

    def index(x,y,z):
    	...
    def wrapper(*args,**kwargs):
    	...
    	index(*args,**kwargs)
    	
    wrapper(111,222,33,444)
    
  • 改进3:不想只装饰index一个函数,想让wrapper可以装饰所有函数:需要一个函数作为参数传进wrapper,但是不能通过形参把这个函数传进来:使用闭包函数将函数当作参数传递,把函数写活了

    def outter(func):  # 把要装饰的函数当作参数传给装饰器的包
    	def wrapper(*args,**kwargs):
    		func(*args,**kwargs)
    	return wrapper
    
    def index(x,y):
    	print(x,y)
    
    f = outter(index)  # 实际上是运行了wrapper,wrapper中的func实际上是调用时当作参数传入的函数
    f(1,2)
    

    解决了不改变源代码,写死的问题,但是也改变了调用方式

  • 最终:不改变调用方式

    用原函数的名字命名改进的函数,偷梁换柱

    index = outter(index)
    index(1,2)
    

总结方法

  1. 将装饰的功能做成闭包函数

  2. 在包中将要装饰的函数当作参数传入嵌套函数

  3. 闭包函数用包中的函数为对象做装饰,接收原函数的参数

  4. 包函数返回其内嵌函数的函数体

  5. 改名,偷梁换柱

    index = outter(index)
    index()
    

最终要达到的效果:以假乱真

这种方法在原函数有返回值时有问题:原函数已经被替换成函数wrapper,调用index实际上是在调用wrapper,而wrapper没有返回值,所以需要让wrapper返回原函数的返回值

最终改进

在装饰器内,返回原函数本来应该返回的值

def outter(func):  # 把要装饰的函数当作参数传给装饰器的包
	def wrapper(*args,**kwargs):
		func(*args,**kwargs)
        res = func(*args,**kwargs)
        return res  # wrapper函数原函数的运行结果
	return wrapper  # 包函数在偷梁换柱的时候返回内嵌函数的函数体

def index(x,y):
	print(x,y)

index = outter(index)
index(1,2)

最终达到了,调用方式不变,原代码不变,返回值不变,实现了以假乱真

语法糖 @装饰器名字

对每次装饰函数都要偷梁换柱原函数的名字

  •   index = outter(index)
      index(1,2)
    

对这种有语句简洁的语法:在被装饰对象正上方的单独一行写 @装饰器名字,python自动给你偷梁换柱

  •   def timmer():  # 装饰器必须在被装饰函数的上面
      	...
      
      @timmer  # 相当于帮你做了 index = timmer(index)
      def index():
      	...
    
  • 装饰器需要放到被装饰器的上面,否则运行到@这一行代码,装饰器还没有被定义,则报错

  • 一个函数可以叠加多个装饰器

    加载顺序:

    @deco1  # deco1.wrapper的内存地址 = deco1(index)
    @deco2  # deco2.wrapper的内存地址 = deco2(index)
    @deco3  # deco3.wrapper的内存地址 = deco3(index)
    def index():
    	...
    

总结

基本模板

def outter(func):
	def wrapper(*args,**kwargs):
		res = func()
		# 新功能
		...
		return res
	return wrapper

@outter
def index(x,y):
	# 原功能
    ...
    
index(x,y)

这就得到一个模板,原功能写到index,新功能写到wrapper,就得到了一个完整的无参装饰器

猜你喜欢

转载自www.cnblogs.com/telecasterfanclub/p/12551447.html