Python裝飾器的使用詳解

目錄

1、函數裝飾器

1.1、閉包函數

1.2、裝飾器語法

1.3、裝飾帶參數的函數

1.4、被裝飾函數的身份問題

1.4.1、解決被裝飾函數的身份問題

1.5、裝飾器本身攜帶/傳參數

1.6、嵌套多個裝飾器

2、類裝飾器

裝飾器顧名思義作為一個裝飾的作用,本身不改變被裝飾對象的原本功能,只是對被裝飾對象擴展了額外的功能,裝飾器分為兩類:函數裝飾器類裝飾器

1、函數裝飾器

說明:函數裝飾器是一個函數接受另一個函數作為參數的函數,它包裝了函數的行為,并返回包裝后的函數。所以裝飾器的目的是為了擴展函數的功能,而不是修改函數本身,裝飾器器的本質就是閉包函數。下面說明一下什么是閉包函數:

1.1、閉包函數

閉包的定義:在函數嵌套的前提下,內部函數使用外部函數的變量,并且外部函數返回內部函數
我們把這個使用外部函數變量的內部函數稱為閉包。

閉包函數的作用:變量在函數運行之后銷毀,但有的時候需要這個函數的變量,這時候使用閉包函數解決。

閉包函數的三要素:

  • 1.實現函數嵌套
  • 2.內部函數使用外部函數的變量
  • 3.外部函數返回內部函數

我們來看一個示例:

def wrapper_hello(func: callable):def wrapper():print("this is start")func()print("this is end")return wrapperdef hello():print("hello world")# 閉包函數的使用
hello_new = wrapper_hello(hello)
# 函數的調用
hello_new()

運行結果:

解釋:
上面的代碼中定義了兩個函數,其中hello()函數是一個普通函數,它的功能是打印hello world。
我們來看看wrapper_hello()函數具體實現了什么功能:

  • 1.首先它的參數func是一個可調用對象。
  • 2.然后它的內部定義了一個函數wrapper(),并把wrapper對象作為返回值。
  • 3.wrapper()函數內部執行過程:
    • 先打印輸出了“this is start”
    • 然后執行func()
    • 最后打印輸出"this is end"

所以我們可以說wrapper_hello()函數擴展了hello()函數的功能,hello()原本實現的功能并沒有改變。

1.2、裝飾器語法

說明:Python提供了一種語法來定義裝飾器。稱為糖語法(通過@修飾目標函數), 它可以將修飾后函數賦值給修飾函數本身,所以調用函數時,還是直接調用,裝飾器只是給函數增加額外的功能,本身并不改變函數功能和調用執行方式。
示例:

def wrapper_hello(func: callable):def wrapper():print("this is start")func()print("this is end")return wrapper# 裝飾器糖語法寫法
@wrapper_hello
def hello():print("hello world")# 還是正常的調用函數
hello()# 調用等價于
# hello = wrapper_hello(hello)
# hello()

1.3、裝飾帶參數的函數

說明:因為Python不允許裝飾器接受被裝飾對象的參數,所以要想實現裝飾帶參數的函數,在裝飾器內部函數中使用 *args 和 **kwargs 來實現。

示例:

def wrapper_hello(func: callable):def wrapper(*args, **kwargs):print("this is start")# 注意如果func函數有返回值,需要使用一個對象來接受返回值,不然func執行完成后就銷毀了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun執行結果:", res)print("this is end")return resreturn wrapper# 裝飾器糖語法寫法
@wrapper_hello
def hello(name):return "hello world %s"%name# 還是正常的調用函數
hello("Tom")

注意:要想獲取目標函數的返回值結果,必須要在裝飾器內部返回執行結果,否則無法獲取執行的結果,原因是func函數執行完之后就會被銷毀,所以需要在裝飾器內部保存目標函數的執行結果。

1.4、被裝飾函數的身份問題

說明:如果查看修飾后函數的名字,或者使用內置的help函數查看,發現被修飾函數的名字是wrapper。因為Python認為現在的函數是裝飾器函數的內部函數。

示例:

def wrapper_hello(func: callable):def wrapper(*args, **kwargs):print("this is start")# 注意如果func函數有返回值,需要使用一個對象來接受返回值,不然func執行完成后就銷毀了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun執行結果:", res)print("this is end")return resreturn wrapper# 裝飾器糖語法寫法
@wrapper_hello
def hello(name):return "hello world %s"%name# 查看hello函數的名字
print(hello.__name__)
help(hello)

運行結果:

1.4.1、解決被裝飾函數的身份問題

說明:可以使用functools.wraps裝飾器解決這個問題,它的作用是保留原函數的信息。 這可以幫助我們在運行時獲取原本對象的信息,比如函數的名字,參數等。

注:也可以顯示使用wrapper.__name__ = func.__name__的方法實現

示例:

def wrapper_hello(func: callable):@functools.wraps(func)def wrapper(*args, **kwargs):print("this is start")# 注意如果func函數有返回值,需要使用一個對象來接受返回值,不然func執行完成后就銷毀了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun執行結果:", res)print("this is end")return res# 等價于@functools.wraps(func)的作用# wrapper.__name__ =  func.__name__return wrapper# 裝飾器糖語法寫法
@wrapper_hello
def hello(name):return "hello world %s"%name# 查看hello函數的名字
print(hello.__name__)
help(hello)

運行結果:

1.5、裝飾器本身攜帶/傳參數

說明:為了更好地理解裝飾器參數的必要性, 我們實現一個repeat裝飾器,它接受一個數字作為輸入。這個裝飾器的功能是:重復執行目標函數給定的次數。

示例1:

import functools
def repeat(num_times):def inner_repeat(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn inner_repeat@repeat(num_times=3)
def hello(name):print('hello {}'.format(name))hello('Tom')

示例2:

import functools
def repeat1(num_times):def repeat():def inner_repeat(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn inner_repeat# 注意這里返回的內部repeat函數的調用return repeat()@repeat1(num_times=3)
def hello(name):print('hello {}'.format(name))hello('Tom')

執行結果都為以下結果:

解釋:
裝飾器執行原理:不管裝飾器嵌套了多少層函數,執行順序是從最外層的函數開始執行也就是repeat1函數,原因是理解為隊列,遵循先進先出的原理,所以從最外層的函數先執行。
所以若要裝飾器可以傳參數最多只需要嵌套3層即可,再嵌套就顯得多余和沒有必要。
注意:

  • 1、裝飾器函數多層嵌套,需要每層都要有返回值,即每層返回對應的函數名。
  • 2、函數括號的參數屬于整個函數內部的“全局變量”,也就是不管函數內部嵌套了多少層函數,都可以使用這些變量

1.6、嵌套多個裝飾器

說明:通過堆疊的方式將多個裝飾器應用到一個函數上。 這些裝飾器按照順序從上到下開始執行

示例:

import functools
# 裝飾器嵌套
def start_end(func):@functools.wraps(func)def wrapper(*args, **kwargs):print('this is start')result = func(*args, **kwargs)print('this is end')return resultreturn wrapperdef debug(func):@functools.wraps(func)def wrapper(*args, **kwargs):args_repr = [repr(a) for a in args]kwargs_repr = [f"{k} = {v!r}" for k, v in kwargs.items()]signature = ", ".join(args_repr + kwargs_repr)# print(signature)print(f"calling {func.__name__} ({signature})")result = func(*args, **kwargs)print(f" {func.__name__!r} returned {result!r}")return resultreturn wrapperdef hello(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("這里是hello函數開始")result = func(*args, **kwargs)print("這里是hello函數結束")return wrapper@start_end
@debug
@hello
def say_hello(name):res = f'hello {name}'print(res)return ressay_hello("張三")

運行結果:

解釋:

從運行結果可以看出,多重裝飾器的嵌套是從上至下開始執行的,但是并不是等一個裝飾器執行完了再執行下一個,而是從第一個裝飾器開始執行到目標函數停止繼續尋找下一個裝飾器執行,然后執行第二個裝飾器同樣執行到目標函數停止繼續尋找下一個裝飾器執行,按照該方式繼續執行,直到執行到最后一個裝飾器,才開始執行目標函數,然后層層返回到最外層。

執行原理:就是遵循的棧的先進后出原理。

注意:目標函數不管嵌套了多少層裝飾器,目標函數有且僅執行一次。

2、類裝飾器

說明:我們也可以使用類作為裝飾器。 但是必須實現__call__()方法,目的是使我們的對象可調用。 類裝飾器通常用于維護狀態。

示例:我們記錄函數被調用的次數。 __call__本質上和wrapper()方法是一樣的。 它添加了一些功能,執行函數,并返回其結果。

注意:這里我們使用functools.update_wrapper()而不是functools.wraps()來保留我們的函數的信息。

示例:

import functoolsclass CountCallNums:def __init__(self, func):functools.update_wrapper(self,func)self.func = funcself.count = 0def __call__(self, *args, **kwargs):self.count += 1print(f"函數{self.func.__name__} 被執行了 {self.count}次")return self.func(*args, **kwargs)@CountCallNums
def hello():print("hello")hello()
hello()# 上面裝飾器等價于
# hello = CountCallNums(hello)
# hello()
# hello()print(hello.__name__)
help(hello)

運行結果:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/712615.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/712615.shtml
英文地址,請注明出處:http://en.pswp.cn/news/712615.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Acwing 周賽135 解題報告 | 珂學家 | 反悔堆貪心

前言 整體評價 VP了這場比賽, T3挺有意思的,反悔貪心其實蠻套路的。 A. 買蘋果 思路: 簽到 n, x list(map(int, input().split())) print (n // x)B. 牛群 思路: 分類討論 from collections import Counters input() cnt Counter(s)lists sorte…

WPF 【十月的寒流】學習筆記(2):MVVM中是怎么實現通知的

文章目錄 前言相關鏈接代碼倉庫項目配置代碼初始代碼ViewPersonViewModel 嘗試老辦法通知解決方案ObservableCollectionBindingListICollectionView 總結 前言 我們這次詳細了解一下列表通知的底層是怎么實現的 相關鏈接 十月的寒流 MVVM實戰技巧之:可被觀測的集合…

2024年【A特種設備相關管理(電梯)】考試總結及A特種設備相關管理(電梯)證考試

題庫來源:安全生產模擬考試一點通公眾號小程序 2024年A特種設備相關管理(電梯)考試總結為正在備考A特種設備相關管理(電梯)操作證的學員準備的理論考試專題,每個月更新的A特種設備相關管理(電梯…

KVM部署Windriver Linux操作系統

安裝前準備 創建密碼配置文件&#xff0c;否則虛機啟動后無法登錄 cd /var/lib/libvirt/images/disks/windriver/ docker run -ti --rm quay.io/coreos/mkpasswd --methodyescrypt 123456 >password_hash.txt cat <<-ENDOF> sample.bu variant: fcos version: 1.4…

面試 Java 基礎八股文十問十答第十二期

面試 Java 基礎八股文十問十答第十二期 作者&#xff1a;程序員小白條&#xff0c;個人博客 相信看了本文后&#xff0c;對你的面試是有一定幫助的&#xff01;關注專欄后就能收到持續更新&#xff01; ?點贊?收藏?不迷路&#xff01;? 1&#xff09;創建一個對象用什么關…

代碼隨想錄day27:貪心part1,基礎篇

文章目錄 day27&#xff1a;貪心part1&#xff0c;基礎篇455.分發餅干376.擺動序列53.最大子數組和 day27&#xff1a;貪心part1&#xff0c;基礎篇 455.分發餅干 循環結束條件注意餅干比孩子多的情況 class Solution {public int findContentChildren(int[] g, int[] s) {A…

C++:非靜態成員默認初始化

C11之前只有常靜態成員變量才能進行默認初始化&#xff0c;其它變量初始化時總要進行繁瑣的過程 class A{int a; public:A():a(10){} };C11開始支持非靜態成員的默認初始化&#xff0c;默認初始化和初始化參數列表同時初始化一個變量時會使用初始化參數列表&#xff0c;不進行…

JavaScript new、apply call 方法

new、apply、call、bind JavaScript 中的 apply、call和 bind 方法是前端代碼開發中相當重要的概念&#xff0c;并且與 this 的指向密切相關 new new 關鍵詞的主要作用 就是執行一個構造函數、返回一個實例對象 根據構造函數的情況&#xff0c;來確定是否可以接受參數的傳遞…

Huggingface初上手即ERNIE-gram句子相似性實戰

大模型如火如荼的今天&#xff0c;不學點語言模型&#xff08;LM&#xff09;相關的技術實在是說不過去了。只不過由于過往項目用到LM較少&#xff0c;所以學習也主要停留在直面——動眼不動手的水平。Huggingface&#xff08;HF&#xff09;也是現在搞LM離不開的工具了。 出于…

最新 DataGrip 2023.3.4 下載安裝激活 + 永久免費

文章目錄 DataGrip簡介同類產品對比使用技巧不足實戰 下載安裝激活Stage 1 : 官網下載Stage 2 : 下載工具Stage 3-1 : windows為例Stage 3-2 : mac為例常見問題部分小伙伴 Mac 系統執行腳本遇到如下錯誤&#xff1a;解決方法&#xff1a; 執行腳本做了啥&#xff1f;和收費版區…

基于springboot+vue的可盈保險合同管理系統

博主主頁&#xff1a;貓頭鷹源碼 博主簡介&#xff1a;Java領域優質創作者、CSDN博客專家、阿里云專家博主、公司架構師、全網粉絲5萬、專注Java技術領域和畢業設計項目實戰&#xff0c;歡迎高校老師\講師\同行交流合作 ?主要內容&#xff1a;畢業設計(Javaweb項目|小程序|Pyt…

XUbuntu22.04之如何找到.so庫所在的軟件包?(二百一十六)

簡介&#xff1a; CSDN博客專家&#xff0c;專注Android/Linux系統&#xff0c;分享多mic語音方案、音視頻、編解碼等技術&#xff0c;與大家一起成長&#xff01; 優質專欄&#xff1a;Audio工程師進階系列【原創干貨持續更新中……】&#x1f680; 優質專欄&#xff1a;多媒…

Vue中的事件總線(EventBus)是什么?它有什么優點和缺點?

作為一名使用Vue的前端開發者&#xff0c;有時候會聽到事件總線(EventBus)這個名詞。但可能是我入行比較晚&#xff0c;我在Vue網站中并沒有看到過事件總線的介紹&#xff0c;在項目中也沒有使用過。那究竟什么是事件總線&#xff1f;事件總線可以解決什么問題&#xff1f; 事…

element-plus表格合并

要實現這樣的表格&#xff0c; 怎么做呢&#xff1f; 甚至是這種三級的呢&#xff1f; 官網的案例也是通過這個方法進行配置的&#xff0c;也就是說表格長什么樣&#xff0c;關鍵在怎么處理的方法上。 這是官網的方法&#xff0c;可參考拓展&#xff1a; const arraySpanMeth…

一款云滲透工具 - Sea Moon

SeaMoon - 月海 什么是月海 &#x1f315; 月出于云卻隱于海 月海(Sea Moon) 是一款 FaaS/BaaS 實現的 Serverless 網絡工具集&#xff0c;期望利用云原生的優勢&#xff0c;實現更簡單、更便宜的網絡功能。 月海之名取自于蘇軾的《西江月頃在黃州》&#xff0c;寓意月海取自…

【JVM】JVM相關機制

1. JVM內存區域劃分 1.1 內存區域劃分簡介 內存區域劃分&#xff1a;實際上JVM也是一個進程&#xff0c;進程運行時需要向操作系統申請一些系統資源&#xff08;內存就是典型的資源&#xff09;&#xff0c;這些內存空間就支撐著后續Java程序的運行&#xff0c;而這些內存又會…

Python環境搭建:一站式指南

在當前AIGC技術蓬勃發展的背景下&#xff0c;Python作為人工智能領域最受青睞的編程語言之一&#xff0c;成為我們必須掌握的技能。因此&#xff0c;搭建一個適合自己的Python環境成為了每個Python開發者的首要任務。本文將為您提供一站式的Python環境搭建指南&#xff0c;幫助…

PythonOpenCV隨機粘貼圖像

import cv2 import numpy as np import random # 讀取兩個圖像 image1 cv2.imread(image1.jpg) image2 cv2.imread(image2.jpg) # 將image1轉換為灰度圖像 gray_image1 cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY) # 創建掩碼&#xff0c;黑色部分為0&#xff0c;非黑色部…

Python自動發郵件

我經常需要用手機看服務器的運行情況&#xff0c;所以就寫一個腳本&#xff0c;通過郵件把服務器運行情況發送給我&#xff0c;直接手機可以查看煉丹狀態。事實證明還是很有用的&#xff0c;所以撰寫一篇博文將腳本分享給大家。這里用到smtplib和email兩個python包。 import s…

力扣精選算法100道——顏色分類(雙指針和三指針倆種方法解決此題)

目錄 &#x1f6a9;了解題意 &#x1f6a9;算法分析 第一種方法&#xff1a;雙指針 &#x1f6a9;代碼實現一 第二種方法&#xff1a;三指針 &#x1f6a9;代碼實現二 &#x1f6a9;了解題意 本題將整數0&#xff0c;1&#xff0c;2代表紅白籃&#xff0c;nums中的整數并…