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)