zoukankan      html  css  js  c++  java
  • 疫情环境下的网络学习笔记 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,就得到了一个完整的无参装饰器

  • 相关阅读:
    每周总结
    5月2日学习日志
    5月1日学习日志
    4月30日学习日志
    4月29日学习日志
    4月28日学习日志
    4月27日学习日志
    每周总结
    vue滚动插件BetterScroll
    vue 获取页面高度
  • 原文地址:https://www.cnblogs.com/telecasterfanclub/p/12551447.html
Copyright © 2011-2022 走看看