1. 程式人生 > >Python基礎(十四)

Python基礎(十四)

今日主要內容

  • 裝飾器擴充套件
    • 有參裝飾器
    • 多個裝飾器裝飾一個函式
  • 遞迴

一、裝飾器擴充套件

(一)含有引數的裝飾器

  • 先來回顧一下裝飾器的標準模式

    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)