P03-Python裝飾器

来源:https://www.cnblogs.com/suhaha/archive/2018/04/02/8689634.html
-Advertisement-
Play Games

本文總結自oldboy python教學視頻。 一、前言 1.裝飾器本質上就是函數,功能是裝飾其他函數,為其他函數添加附加功能。 裝飾器在裝飾函數時必須遵循3個重要的原則: (1)不能修改被裝飾的函數的源代碼 (2)不能修改被裝飾的函數的調用方式 (3)不能修改表裝飾函數的返回結果 2.實現裝飾器的 ...


本文總結自oldboy python教學視頻。

一、前言

1.裝飾器本質上就是函數,功能是裝飾其他函數,為其他函數添加附加功能。

裝飾器在裝飾函數時必須遵循3個重要的原則:

(1)不能修改被裝飾的函數的源代碼

(2)不能修改被裝飾的函數的調用方式

(3)不能修改表裝飾函數的返回結果

 

2.實現裝飾器的知識儲備:

(1)函數即“變數”

關於函數即變數,我的理解如下所示,即當定義一個函數時,相當於在記憶體中劃一塊記憶體空間,裡面存放函數的函數體,而函數名指向這塊空間——這跟普通的函數定義如 a = 9527 是一樣的。如此一來,函數名便可以跟普通變數一樣,作為參數傳給其他函數,也能在函數中作為結果被返回。

 

(2)高階函數

a.把一個函數名當做實參傳給另一個函數(在不修改被裝飾函數源代碼的情況下為其添加功能)

或:

b.返回值中包含函數名(不修改函數的調用方式)

(3)嵌套函數

所謂嵌套函數,即在一個函數內定義另外一個函數(註意是定義,而不是調用)。

 

3.高階函數 + 嵌套函數 => 裝飾器

 

 

二、裝飾器應用

1.被裝飾函數沒有參數

假設被裝飾的函數為test1(),此時的test1()沒有形參,預設返回NULL;裝飾器timmer完成的附加功能是計時。

# -*- coding:utf-8 -*-
#Author:Suxy

import time

def timmer(func):
    def wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func run time is %s" %(stop_time - start_time))
    return  wrapper

@timmer  #test1 = timmer(test1),即test1 = wrapper
def test1():
    time.sleep(1)
    print("in the test1")

test1()

輸出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
in the test1
the func run time is 1.0003414154052734

Process finished with exit code 0

 註:在函數test1()的上面加上@timmer,作用相當於test1 = timmer(test1),又因為timmer函數中返回wrapper,所以有test1 = timmer(test1) = wrapper,因此最後一行代碼在使用test()方式調用函數的時候,實際調用的是wrapper(),而wrapper中的變數func接收了timemr(test1)傳進來的test1,所以實際調用時,先執行wrapper中附加的功能(計時),之後執行test1()本身的功能。

 

 2.被裝飾函數帶有參數

裝飾器不僅要能裝飾無參函數,還得能裝飾帶參函數。這裡以只帶一個參數的函數test2(name)為例。此時若是不修改上面裝飾器的任何代碼便用來裝飾test2(name),則會報錯,具體如下:

# -*- coding:utf-8 -*-
#Author:Suxy

import time

def timmer(func):
    def wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func run time is %s" %(stop_time - start_time))
    return  wrapper

@timmer  #test1 = timmer(test1),即test1 = wrapper
def test1():
    time.sleep(1)
    print("in the test1")

@timmer
def test2(name):
    time.sleep(1)
    print("in the test2, ", name)

test1()
test2("suxy")

 輸出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
Traceback (most recent call last):
  File "E:/python_workspace/pure_python/day4/decorator.py", line 25, in <module>
    test2("suxy")
TypeError: wrapper() takes 0 positional arguments but 1 was given
in the test1
the func run time is 1.000788688659668

Process finished with exit code 1

 可見,此時裝飾器timmer用於裝飾test1()時沒有問題,但是裝飾test2()時報錯了:wrapper()函數接受0個位置參數,但是在調用時卻傳了1個。

由第1步可知,在使用裝飾器timmer裝飾函數test2之後,調用test2(“suxy”)就相當於調用wrapper(“suxy”)函數,而當前的wrapper()函數是不接收參數的。

若需要timme裝飾器能夠同時裝飾test1和test2函數,則需要對timmer裝飾器進行修改——不過也不能簡單地將timmer()中wrapper()函數的定義修改為wrapper(name),因為這樣一來timmer裝飾test1就會出錯,此外,timmer裝飾器有可能還要裝飾其他的比如帶有預設參數、關鍵字參數等的函數,因此,wrapper函數的定義應修改為wrapper(*args, **kwargs),並且wrapper中func的調用方式也得進行相應的修改func(*args, **kwargs),這樣一來,無論被裝飾的函數是無參函數、預設參數、關鍵字參數還是如*args列表參數、**kwargs字典參數,裝飾器都能進行裝飾。如下:

# -*- coding:utf-8 -*-
#Author:Suxy

import time

def timmer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        stop_time = time.time()
        print("the func run time is %s" %(stop_time - start_time))
    return  wrapper

@timmer  #test1 = timmer(test1),即test1 = wrapper
def test1():
    time.sleep(1)
    print("in the test1")

@timmer
def test2(name):
    time.sleep(1)
    print("\nin the test2, ", name)

test1()
test2("suxy")

 輸出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
in the test1
the func run time is 1.000464916229248

in the test2,  suxy
the func run time is 1.0004689693450928

Process finished with exit code 0

 

 3.上面第2步所說的裝飾器已經能夠裝飾一般的帶參函數了,不過,若是被裝飾函數具有返回值,則上面的裝飾器就有問題了。如下(為縮減篇幅,暫將函數test1省略掉):

# -*- coding:utf-8 -*-
#Author:Suxy

import time

def timmer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        stop_time = time.time()
        print("the func run time is %s" %(stop_time - start_time))
    return  wrapper

@timmer
def test2(name):
    time.sleep(1)
    print("\nin the test2, ", name)
    return 9527

print(test2("suxy"))

 輸出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py

in the test2,  suxy
the func run time is 1.0003151893615723
None

Process finished with exit code 0

 可見,函數test2()中明明返回了9527,但是最後輸出確是None,違反了裝飾器不能修改被裝飾函數的返回結果的原則。這是因為裝飾器的wrapper中調用func(*args, **kwargs)函數(註: func(*args, **kwargs) = test2(*args, **kwargs))時,只是進行直接的調用,而沒有將調用的結果返回。可以將裝飾器代碼修改為 return func(*args, **kwargs),或者先用res = func(*args, **kwargs)將函數執行結果保存到res中,最後再返回res。如下:

# -*- coding:utf-8 -*-
#Author:Suxy

import time

def timmer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print("the func run time is %s" %(stop_time - start_time))
        return res
    return  wrapper


@timmer
def test2(name):
    time.sleep(1)
    print("\nin the test2, ", name)
    return 9527

print(test2("suxy"))

 輸出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py

in the test2,  suxy
the func run time is 1.0006110668182373
9527

Process finished with exit code 0

 

4.到上一步,裝飾器已經可以裝飾絕大部分的函數了。但是目前的裝飾器只能簡單的接受一個參數,即被裝飾函數的函數名,無法接受其他的參數。若是有其他複雜的需求,比如接受func_type欄位,根據func_type的不同而做不同的輸出,則目前的裝飾器無法完成這個功能。

# -*- coding:utf-8 -*-
#Author:Suxy

import time

def before_timmer(func_type):
    if func_type == "type1":
        print("It is type 1")
    elif func_type == "type2":
        print("It is type 2")
    else:
        print("Another type")

    def timmer(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            res = func(*args, **kwargs)
            stop_time = time.time()
            print("the func run time is %s" % (stop_time - start_time))
            return res
        return wrapper
    return timmer

@before_timmer(func_type = "type2")
def test2(name):
    time.sleep(1)
    print("\nin the test2, ", name)
    return 9527

test2("suxy")

 輸出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
It is type 2

in the test2,  suxy
the func run time is 1.0008769035339355

Process finished with exit code 0

 仔細對比這裡和3中裝飾器代碼的區別可以發現,這一步相當於又新建了一個裝飾器before_timmer將原先的裝飾器timmer包了起來,並將timmer函數作為結果返回,使得可以在before_timmer裝飾器中先完成判斷功能,再調用timmer計時功能。

此時對於test1()函數定義上的@before_timmer(func_type = "type1"),我的理解是:test2 = before_timmer(func_type = “type2”),又因為before_timmer中有return timmer,所以test2 = before_timmer(func_type = “type2”) = timmer(test2),同時由於timmer中有return wrapper,所以 test2 = before_timmer(func_type = “type2”) = timmer(test2) = wrapper,因此,當調用test2(“suxy”)時,還是最後還是相當於調用了wrapper(“suxy”)。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • HTML5和CSS3技術是目前整個網頁的基礎。《HTML5與CSS3實例教程(第2版)》共分3部分,集中討論了HTML5和CSS3規範及其技術的使用方法。這一版全面講解了最新的HTML5和CSS3技術,所有實例均使用最新特性實現,針對的是最新版本的瀏覽器。 《HTML5與CSS3實例教程(第2版)》 ...
  • 一、jQuery($)命名空間 為了避免聲明瞭一些全局變數造成變數污染,使用立即執行函數形成jQuery($)獨立的命名空間; 二、jQuery的本質是什麼? 由jQuery的源碼可知,jQuery的本質是一個函數(對象),是函數就應該有原型(prototype對象),但是,jQuery重置了該原型 ...
  • 創建一個函數,接受兩個或多個數組,返回所給數組的 對等差分(symmetric difference) (△ or ⊕)數組. 創建一個函數,接受兩個或多個數組,返回所給數組的 對等差分(symmetric difference) (△ or ⊕)數組. 給出兩個集合 (如集合 A = {1, 2, ...
  • jQury Mobile是以全球最流行的jQuery為核心的跨平臺移動Web應用開發框架,自從誕生以來,就贏得了業內專家和技術社區的強烈關註。《jQuery Mobile入門經典》採用直觀、循序漸進的方法講解瞭如何藉助jQuery Mobile,通過一個單一的代碼庫來創建適合各種移動設備的移動應用。 ...
  • 設計模式簡介: 設計模式是一套被反覆使用的、多數人知曉的、經過分類編目的、代碼設計經驗的總結。(個人理解:設計模式是不關乎業務,邏輯實現,針對普遍問題的一種解決方案)。 設計模式的類型: 傳統23種設計模式可分為3大類:創建型模式(Creational Patterns)、結構型模式(Structu ...
  • 【Java】0x01 配置開發環境,JDK、CLASSPATH等 一. 下載JDK安裝文件 首先,進入Oracle官網Java頁面。 註意,要下載的是JDK而不是JRE,這點很重要,因為JRE並不包含我們要用的源碼編譯之類的工具。 當前JDK最新版本為Java10,但如今主流的運行環境還是1.7、1 ...
  • Description 小Q的媽媽是一個出納,經常需要做一些統計報表的工作。今天是媽媽的生日,小Q希望可以幫媽媽分擔一些工 作,作為她的生日禮物之一。經過仔細觀察,小Q發現統計一張報表實際上是維護一個可能為負數的整數數列,並 且進行一些查詢操作。在最開始的時候,有一個長度為N的整數序列,並且有以下三 ...
  • 導讀 : 1.if語句 2.while語句 一、if語句 if語句是最常用的條件控制語句,Python中的一般形式為: if 條件一: statements elif 條件二: pass # 空語句/占位語句 else: statements Python中用 elif 代替了 else if ,所 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...