Python基礎(十三)


今日主要內容

  • 閉包
  • 裝飾器初識
  • 標准裝飾器

一、閉包

(一)什么是閉包

  1. 閉包:內層函數調用外層函數的變量就是閉包(不能是全局變量)

    def func1():
    	a = 10
    	def func2():
    		print(a)  # 內層函數調用外層函數的變量,這就是一個閉包
    
    func1()
    
  2. 檢測閉包的方法

    • 函數名.__closure__
    • 若返回對象地址就是一個閉包,返回None就不是一個閉包
    • 注意:.__closure__檢測的是函數名,判斷這個函數是否是閉包
    def func1():
    	a = 10
    	def func2():
    		print(a)  # 調用外層變量a,是閉包
    	print(func2.__closure__)  # 判斷func2是否是閉包
    
    func1()
    
    運行結果:
    (<cell at 0x00000230F21874F8: int object at 0x0000000063258190>,)
    
    def func1():
        a = 10
        def func2():
            a = 10
            print(a)  # 使用自身函數變量a,不是閉包
        print(func2.__closure__)
    
    func1()
    
    運行結果:
    None
    
  3. 如何在全局空間中調用內部函數

    • 外層函數返回內層函數的函數名(內存地址),就可在全局空間中調用內部函數
    def func1():
    	a = 10
    	def func2():
    		print(a)
    	return func2  # 返回內層函數的函數名
    	
    f = func1()  # 此時f就是func2
    f()  # 調用內層函數func2
    print(f.__closure__)
    
    運行結果:
    10
    (<cell at 0x00000280A87574F8: int object at 0x0000000063258190>,)
    

(二)閉包的作用

  1. 保護數據安全
  • 說白話一點,如果數據放在全局變量中(頂格寫代碼),數據的安全性很低,因為所有人都可以無意間修改它,想解決這個問題我們可以把數據放到函數中,只要不調用這個函數,我的數據就是安全的。但是有個問題,每次執行函數完,解釋器就會自動清空此函數開辟的局部空間中所有內容(包括數據開辟的空間),所以每次調用完函數,我的數據就沒了,此時就需要利用到了閉包。閉包可以在內層函數調用外層函數中的變量,而且內層函數如果調用了外層函數中的變量,那這個變量將不會消亡,將會常駐內存。所以,我們只需要在全局空間中調用這個閉包的內層函數,就可以使用我的數據了,而且數據的安全性也提高了。
  1. 將變量常駐內存,供后續代碼使用

    def outer():
    	lst = [1,2,3,4,5]
    	def inner():
    		return lst
    	return inner
    	
    f = outer()
    f_lst = f()
    print(f_lst)
    
    運行結果:
    [1,2,3,4,5]
    

(三)閉包的應用

  • 防止數據被誤修改
  • 裝飾器(與閉包格式相同)

二、裝飾器初識

(一)軟件開發的六大原則(了解)

  1. 開閉原則(Open Close Principle)——裝飾器依據
  2. 里氏代換原則(Liskov Substitution Principle)
  3. 依賴倒轉原則(Dependence Inversion Principle)
  4. 接口隔離原則(Interface Segregation Principle)
  5. 迪米特法則(最少知道原則)(Demeter Principle)
  6. 合成復用原則(Composite Reuse Principle)

(二)裝飾器依據——開閉原則

  • 開放封閉原則:
    • 對功能擴展開放
    • 對源碼修改封閉

(三)裝飾器引入

  • 相信大多數人都玩LOL,我們模擬一次游戲過程:

    • 向平常一樣的流程,十連跪...
    def play_lol():
    	print("登陸游戲")
    	print("開始排位...")
    	print("游戲中...")
    	print("失敗...")
    	print("結束游戲")
    
    play_lol()
    
    運行結果:
    登陸游戲
    開始排位...
    游戲中...
    Virtory
    結束游戲
    
    • 受不了了,開一個外掛吧,此時我的函數需要擴展,在前后添加開啟關閉外掛的功能
    def play_lol():
    	print("開啟外掛!")  # 添加開啟外掛功能
    	print("登陸游戲")
    	print("開始排位...")
    	print("游戲中...")
    	print("勝利!!!")
    	print("結束游戲")
    	print("關閉外掛!")  # 添加關閉外掛功能
    
    play_lol()
    
    運行結果:
    開啟外掛!
    登陸游戲
    開始排位...
    游戲中...
    勝利!!!
    結束游戲
    關閉外掛!
    
    • 但此時,違背了開閉原則,對源代碼進行了修改。想一個方法,對源代碼進行前后包裝,不改變源碼
    def play_lol():
    	print("登陸游戲")
    	print("開始排位...")
    	print("游戲中...")
    	print("勝利!!!")
    	print("結束游戲")
    	
    def new_play():  # 將上面代碼前后包裝成了一個新的函數,沒有改變源碼
    	print("開啟外掛!")
    	play_lol()
    	print("關閉外掛!")
    	
    new_play()
    
    運行結果:
    開啟外掛!
    登陸游戲
    開始排位...
    游戲中...
    勝利!!!
    結束游戲
    關閉外掛!
    
    • 功能實現了,而且還沒有改變源碼,但是有一個問題,我們之前訪問調用的是play_lol這個函數,但此時我們訪問調用的是new_play()這個函數,相當於改變了調用,還是違背了開閉原則,沒有達到擴展的效果,此時我們就需要對這段代碼稍作變化
    def play_lol():
    	print("登陸游戲")
    	print("開始排位...")
    	print("游戲中...")
    	print("勝利!!!")
    	print("結束游戲")
    	
    def wrapper(fn):    # 裝飾器雛形
    	def inner():
    		print("開啟外掛!")
    		fn()
    		print("關閉外掛!")
    	return inner
    
    func = wrapper(play_lol)  # 調用裝飾器函數將我基礎函數傳入進去包裝
    play_lol = func  # 返回的是包裝函數,將包裝函數重命名成我原來的函數
    play_lol()  # 調用此函數
    
    運行結果:
    開啟外掛!
    登陸游戲
    開始排位...
    游戲中...
    勝利!!!
    結束游戲
    關閉外掛!
    
    • 上述代碼就引出了裝飾器的雛形,刨析一下:
      • 裝飾器雛形:與閉包的格式相同,兩層函數構成:
        • 內層函數就是我的包裝函數,將擴展的功能和原函數包在一起組成一個函數
        • 外層函數的作用就是給內層函數傳參用的,傳入的是我原函數的函數名,在內層調用
      • 裝飾器的返回值 return inner:裝飾器的返回值是內層函數的函數名,真正進行包裝擴展的是內層函數
      • 將返回值重命名成原函數名:將返回值重命名成原來的函數名,其實就是把真正作用的包裝函數重命名成原來的函數名,所以就解決了調用新函數的問題,真正遵循了開閉原則,再次調用原來的函數其實真正運行的是裝飾器內部的inner函數
    def wrapper(fn):    # 裝飾器雛形
    	def inner():
    		print("開啟外掛!")
    		fn()
    		print("關閉外掛!")
    	return inner
    
    func = wrapper(play_lol)  # 調用裝飾器函數將我基礎函數傳入進去包裝
    play_lol = func  # 返回的是包裝函數,將包賺函數重命名成我原來的函數
    play_lol()  # 調用此函數
    
    • 利用語法糖裝飾

      • 使用裝飾器的兩行代碼可以轉換成語法糖
      func = wrapper(play_lol)  # 調用裝飾器函數將我基礎函數傳入進去包裝
      play_lol = func  # 返回的是包裝函數,將包賺函數重命名成我原來的函數
      
      • 語法糖
      @wrapper  # 語法糖
      def play_lol():
      	print("登陸游戲")
      	print("開始排位...")
      	print("游戲中...")
      	print("勝利!!!")
      	print("結束游戲")
      
    • 此時,裝飾器的雛形就出來了

  • 裝飾器雛形

    def wrapper(fn):
    	def inner():
    		"""擴展功能"""
    		fn()
    		"""擴展功能"""
    	return inner
    	
    @wrapper
    def func():
    	pass
    	
    func()
    
  • 游戲模擬繼續進行

    • 就算開掛,我們也得選完英雄,才能進入游戲,所以我們給基礎函數傳個參數,但是我們真正執行的是裝飾器內部的inner包裝函數,所以也要給inner傳入參數
    def wrapper(fn):    # 裝飾器雛形
    	def inner(hero):  # 套到裝飾器中內層包裝函數參數
    		print("開啟外掛!")
    		fn(hero)  # 基礎函數參數
    		print("關閉外掛!")
    	return inner
    
    @wrapper
    def play_lol(hero):  # 基礎函數參數
    	print("登陸游戲")
    	print("開始排位...")
    	print(f"選擇英雄:{hero}")
    	print("游戲中...")
    	print("勝利!!!")
    	print("結束游戲")
    
    play_lol("蓋倫")
    
    運行結果:
    開啟外掛!
    登陸游戲
    開始排位...
    選擇英雄:蓋倫  # 傳入的參數
    游戲中...
    勝利!!!
    結束游戲
    關閉外掛!
    
    • 雖然開掛了,但是還是會遇到巨坑無敵坑的隊友,我們得把他記下來舉報他,所以要給基礎函數填寫返回值,同時給真正執行的inner函數填寫返回值
    def wrapper(fn):    # 裝飾器雛形
    	def inner(hero):  
    		print("開啟外掛!")
    		ret = fn(hero)  # 接收基礎函數的返回值
    		print("關閉外掛!")
    		return ret  # 返回包裝后的函數的返回值
    	return inner
    
    @wrapper
    def play_lol(hero):  # 基礎函數參數
    	print("登陸游戲")
    	print("開始排位...")
    	print(f"選擇英雄:{hero}")
    	print("游戲中...")
    	print("勝利!!!")
    	print("結束游戲")
    	return "坑比隊友:xxx"  # 基礎函數返回值
    
    print(play_lol("蓋倫"))
    
    運行結果:
    開啟外掛!
    登陸游戲
    開始排位...
    選擇英雄:蓋倫
    游戲中...
    勝利!!!
    結束游戲
    關閉外掛!
    坑比隊友:xxx  # 返回值
    
    • 到此,我們裝飾器標准模式也就出來了
  • 裝飾器標准模式(非常重要)

    def wrapper(fn):
    	def inner(*args, **kwargs):
    		"""擴展功能"""
    		ret = fn(*args, **kwargs)
    		"""擴展功能"""
    		return ret
    	return inner
    	
    @wrapper
    def func():
    	pass
    
    func()
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM