Python基础(十四)

  • 2019 年 10 月 3 日
  • 筆記

今日主要内容

  • 装饰器扩展
    • 有参装饰器
    • 多个装饰器装饰一个函数
  • 递归

一、装饰器扩展

(一)含有参数的装饰器

  • 先来回顾一下装饰器的标准模式

    def wrapper(fn):    def inner(*args, **kwargs):        """扩展内容"""        ret = fn(*args, **kwargs)        """扩展内容"""    return inner    @wrapper  def func():    pass    func()
  • 回顾一下之前的游戏模拟,通过装饰器给我的游戏过程扩展了开挂的功能,装饰之后每次想玩游戏的时候调用函数都会给你先把挂打开,此时你的游戏函数已经被装饰了,但是现在有一个问题,我今天想自己玩一把,不想开挂了,怎么办?我们可以给装饰器传一个参数,来控制我的装饰器的开启和关闭就可以了

    def wrapper_outer(argv):  # 给装饰器加一个参数,控制装饰器开启关闭      def wrapper(fn):          def inner(hero):              if argv:  # 如果是True执行添加装饰                  print("开启外挂!")                  ret = fn(hero)                  print("关闭外挂!")                  return ret              else:  # 如果是False,执行原函数                  ret = fn(hero)                  return ret          return inner      return wrapper    @wrapper_outer(True)  def play_lol(hero):  # 基础函数参数    print("登陆游戏")    print("开始排位...")    print(f"选择英雄:{hero}")    print("游戏中...")    print("胜利!!!")    print("结束游戏")    return "坑比队友:xxx"  # 基础函数返回值    print(play_lol("盖伦"))    运行结果:  开启外挂!  登陆游戏  开始排位...  选择英雄:盖伦  游戏中...  胜利!!!  结束游戏  关闭外挂!  坑比队友:xxx
    • 刨析一下:

      • 先来看装饰器和语法糖
        • @wrapper_outer(True)先执行函数调用,函数调用返回的是我内层装饰器的函数名,相当于@wrapper
        • 装饰器最外层的参数控制内层包装函数inner里面的函数部分是否执行,如果argv为真,相当于执行了包装,如果argv为假,执行原函数
      • 通过给装饰器传参起到了控制装饰器是否生效的功能
      def wrapper_outer(argv):      def wrapper(fn):          def inner(hero):              if argv:  # 为真执行这里                  print("开启外挂!")                  ret = fn(hero)                  print("关闭外挂!")                  return ret              else:  # 为假执行这里                  ret = fn(hero)                  return ret          return inner      return wrapper    @wrapper_outer(True)  # 先执行函数调用
      • 注意:一旦给函数装饰过,装饰器的参数是不能变化的,因为闭包的原因参数已经被闭进去了,只能调用内层函数,无法再修改最外层的装饰器参数
      flag = True  def wrapper_outer(argv):      def wrapper(fn):          def inner(*args, **kwargs):              if argv:                  """扩展功能"""                  ret = fn(*args, **kwargs)                  """扩展功能"""                  return ret              else:                  ret = fn(*args, **kwargs)                  return ret          return inner      return wrapper    @wrapper_outer(flag)  def func():      pass    flag = False  func()  # 此时flag依然是True,装饰过就不能修改参数的值
  • 有参装饰器的标准模式

    def wrapper_outer(argv):      def wrapper(fn):          def inner(*args, **kwargs):              if argv:                  """扩展功能"""                  ret = fn(*args, **kwargs)                """扩展功能"""                  return ret              else:                  ret = fn(*args, **kwargs)                  return ret          return inner      return wrapper    @wrapper_outer(True)  def func():      pass    func()

(二)多个装饰器装饰一个函数

  • 执行原理:从里到外进行包装

    def wrapper1(fn):    def inner(*args, **kwargs):        print("扩展功能1")        ret = fn(*args, **kwargs)        print("扩展功能4")        return ret    return inner    def wrapper2(fn):    def inner(*args, **kwargs):        print("扩展功能2")        ret = fn(*args, **kwargs)        print("扩展功能3")        return ret    return inner    @wrapper1  @wrapper2  def func():    print("目标函数")    func()    运行结果:  扩展功能1  扩展功能2  目标函数  扩展功能3  扩展功能4
    • 刨析一下:

      • 从里往外看,先用第一层装饰器@wrapper2装饰目标函数func(),装饰完将其看作成一个整体,在被上层装饰器@wrapper1装饰
      • 返回值:执行完目标函数,将目标函数的返回值先反给最近的装饰器@wrapper2内部的inner包装函数中,之后再将@wrapper2内部的inner包装函数的返回值返回给上一层装饰器@wrapper1内部的inner中,最终得到的返回值是我调用函数的返回值
      • 最终调用目标函数其实真正执行的是最外层装饰器中的包装函数inner,而最外层装饰器中的包装函数inner包装着内层装饰器的包装函数inner,而内层装饰器的包装函数inner包装着真正的目表函数func
      # 伪代码:    def 装饰器1(传入目标函数):      def 内层包装函数1,也是真正执行的函数(目标函数的参数):          """前扩展功能"""          目标函数(目标函数的参数)          """后扩展功能"""      return 包装函数的函数名    def 装饰器2(传入目标函数):      def 内层包装函数2,也是真正执行的函数(目标函数的参数):          """前扩展功能"""          目标函数(目标函数的参数)          """后扩展功能"""      return 包装函数的函数名    @装饰器1  @装饰器2  def 目标函数(形参):      函数体    目标函数(实参)    # 真正执行过程:  先执行:装饰器1的内层包装函数1,而传入的目标函数是:装饰器2的内层包装函数2  再执行:装饰器2的内层包装函数2,而传入的目标函数是:目标函数

二、递归

(一)什么是递归

  • 首先递归是一个函数,只要满足两个要求的函数就是递归函数:
    • 不断调用自己本身
    • 有明确的结束条件

(二)递归深度

  • 如果只是在不断的调用自己本身,没有一个明确的结束条件,那么就是一个死递归(无限循环)。

  • Python官方规定,为了避免无限制的调用自己本身,递归的最大深度为1000(最多只能调用自己本身1000次),实际递归深度为998

    def func():      print(1)      func()    func()    运行结果:  [Previous line repeated 994 more times]  1  1  1  ...(共打印998个1)
  • 可以通过导入sys模块,修改最大递归深度

    import sys  sys.setrecursionlimit(100)  # 修改递归深度    def func():      print(1)      func()    func()    运行结果:  [Previous line repeated 94 more times]  1  1  1  ...(实际打印98个1)

(三)递归的应用

  1. 求n的阶乘

    def factorial(n):      if n == 1:          return 1      return factorial(n - 1) * n    print(factorial(5))    运行结果:  120
  2. 计算斐波那契序列

    def fib(n):   if n <= 2:       return 1   return fib(n-1) + fib(n-2)    print(list(map(fib,range(1, 6))))    运行结果:  [1, 1, 2, 3, 5]
  3. 打印列表嵌套的每一个元素

    l1 = [1, 2, [3, 4, [5, [6, 7, [8, 9], 10], 11, 12], 13], 14, 15]    def func(lst):      for el in lst:          if type(el) == list:              func(el)          else:              print(el, end=" ")    func(l1)    运行结果:  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 
  4. 给列表去重,不能使用集合

    l1 = [1, 1, 2, 3, 4, 5, 6, 3, 3, 5, 6, 3, 4, 5]    def del_repetition(lst):      for el in lst:          if lst.count(el) > 1:              lst.remove(el)              del_repetition(lst)    del_repetition(l1)  print(l1)    运行结果:  [1, 2, 6, 3, 4, 5]
  5. 遍历文件夹中所有的文件

    import os    def read(filepath, n):      files = os.listdir(filepath)  # 获取到当前文件夹中的所有文件      for fi in files:  # 遍历文件夹中的文件, 这里获取的只是本层文件名          fi_d = os.path.join(filepath, fi)  # 加入文件夹 获取到文件夹文件          if os.path.isdir(fi_d):  # 如果该路径下的文件是文件夹              print("t" * n, fi)              read(fi_d, n + 1)  # 继续进行相同的操作          else:              print("t" * n, fi)  # 递归出口. 最终在这里隐含着return    # 递归遍历目录下所有文件  read('../day16/', 0)
  6. 二分查找

    # 普通递归版本⼆二分法  lst = [22, 33, 44, 55, 66, 77, 88, 99, 101, 238, 345, 456, 567, 678, 789]  n = 567  left = 0  right = len(lst) - 1    def binary_search(n, left, right):      if left <= right:          middle = (left + right) // 2          if n < lst[middle]:              right = middle - 1          elif n > lst[middle]:              left = middle + 1          else:              return middle          return binary_search(n, left, right)          # 这个return必须要加. 否则接收到的永远是None.      else:          return -1    print(binary_search(567, 0, len(lst) - 1))
  7. 三级菜单进入返回

    menu = {      '北京': {          '海淀': {              '五道口': {                  'soho': {},                  '网易': {},                  'google': {}              },              '中关村': {                  '爱奇艺': {},                  '汽车之家': {},                  'youku': {},              },              '上地': {                  '百度': {},              },          },          '昌平': {              '沙河': {                  '北邮': {},                  '北航': {},              },              '天通苑': {},              '回龙观': {},          },          '朝阳': {},          '东城': {},      },      '上海': {          '闵行': {              "人民广场": {                  '炸鸡店': {}              }          },          '闸北': {              '火车战': {                  '携程': {}              }          },          '浦东': {},      },      '天津': {          "和平": {              "小白楼": {},              "五大道小洋楼": {},              "滨江道": {},          },          "南开": {              "天大": {},              "南开": {},              "理工": {},          },          "河北": {              "天津之眼": {},              "海河": {},              "意式风情区": {},              "世纪钟": {},              "大悲院": {},          },      },  }    def menu_func(menu):      while True:          for k in menu:              print(k)          key = input('input>>').strip()          if key == 'b' or key == 'q':              return key          elif menu.get(key):              ret = menu_func(menu[key])              if ret == 'q':                  return 'q'    menu_func(menu)