python裝飾器探究與參數的領取

来源:http://www.cnblogs.com/ansver/archive/2017/11/30/7900804.html
-Advertisement-
Play Games

首先上原文, 現在,假設我們要增強now()函數的功能,比如,在函數調用前後自動列印日誌,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。本質上,decorator就是一個返回函數的高階函數。 Decorator本質是高階函數? 不信 ...


首先上原文,

現在,假設我們要增強now()函數的功能,比如,在函數調用前後自動列印日誌,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
本質上,decorator就是一個返回函數的高階函數。

Decorator本質是高階函數?

不信邪的我試了下..

 1 def g():
 2     print("這裡是G")
 3     return "G"
 4 
 5 @g
 6 def f():
 7     print("這裡是F")
 8     return 1
 9 '''
10 --------------------------------------------
11 line 5, in <module>
12     @g
13 TypeError: g() takes 0 positional arguments but 1 was given
14 >>>
15 '''

運行結果在註釋里

尷尬了...g被強制塞了個參數,那個參數應該是g"修飾的對象"

修改下繼續.....

def g(f):
    print("這裡是G")
    return "G"

@g
def f():
    print("這裡是F")
    return 1
'''
--------------------------------------------
這裡是G
>>> f()
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    f()
TypeError: 'str' object is not callable
'''

str對象不能被調用,,,這裡的str只有一個,就是g的返回值

為了驗證下,我把"G"改成了2

結果是

TypeError: 'int' object is not callable

OK了,大概意思就是,裝飾器首先被"塞"一個參數,然後,返回值還要被調用一次,然而好像只有函數才能被調用,,所以,為了不報錯,裝飾器必須返回一個函數,裝飾器必須是高階函數......

我表示不服( ̄へ ̄),不就是函數嗎,g的參數就是函數,,,騷操作上腦ing

def g(f):
    print("這裡是G")
    return f

@g
def f():
    print("這裡是F")
    return 1
'''
--------------------------------------------
這裡是G
>>> f()
這裡是F
'1'
'''

運行成功.但是....說好的"列印日誌功能"呢,#沒錯"這裡是G"就是我想要的日誌

疑點: 列印的"這裡是G"是第一行,是在輸入"f()"之前發生的....

#單看g函數,它不算是"高階函數"

作為成功的例子,它太失敗了┐( ̄ヘ ̄)┌

 

##好吧,我投降了,負隅頑抗也不怎麼有意思.....

閉包告訴我們一個道理,,,為了保證返回值一定是函數,最好的措施就是"在函數內部,現場造一個函數然後扔出去"

def g(f):
    print("這裡是G")
    def h():
        print('這裡是H')
        return "H"
    return h

@g
def f():
    print("這裡是F")
    return 1
'''
--------------------------------------------
這裡是G
>>> f()
這裡是H
'H'
>>> f()
這裡是H
'H'
>>> 
'''

f函數不執行了,,是的沒錯,我還多試了一次的

另外,兩條日誌只有裡面的能用......(下文會解釋的)

看了一下書,h函數返回f()的話,f函數就能被執行了,,,,,個人感覺裝飾器應該叫"劫持器"

def g(f):
    print("這裡是G")
    def h():
        print('這裡是H')
        return f()
    return h

@g
def f():
    print("這裡是F")
    return 1
'''
--------------------------------------------
這裡是G
>>> f()
這裡是H
這裡是F
'1'
>>> f
<function g.<locals>.h at 0x0000020CBDBB6C80>
'''

 

按書上的思路解釋下

'''
@g
def f():
    pass
>>>f()
等價於
>>>g(f) ()
g函數執行,返回
>>>h ()
h函數執行(列印日誌)
>>>f()
f執行,返回1
>>>1
'''

加上參數,

def g(f):
    print("這裡是G")
    def h(*args,**kw):
        print('這裡是H')
        return f(*args,**kw)
    return h

@g
def f(*args,**kw):
    print("這裡是F")
    return 1
'''
>>>f(*args,**kw)
等價於
>>>g(f) (*args,**kw)
g函數執行,返回
>>>h (*args,**kw)
h函數執行(列印日誌)
>>>f(*args,**kw)
f執行,返回1
>>>1
'''

可以看出,(*args,**kw)本改被h函數拿走,所以,觀察h函數,h把它的參數原封不動的交給了f

機智的我動了歪主意

def g(f):
    print("這裡是G")
    def h():#h沒要求參數
        print('這裡是H')
        return f
    return h

@g
def f(*args,**kw):
    print("這裡是F")
    return 1
'''
>>>f()(*args,**kw)
等價於
>>>g(f) ()(*args,**kw)
g函數執行,返回
>>>h ()(*args,**kw)
h函數執行,h拿的空參數
(列印日誌)
>>>f(*args,**kw)
f執行,返回1
>>>1
'''

可是新的問題來了,後面f調用的時候得多加個空括弧,否則

'''
>>>f(*args,**kw)
等價於
>>>g(f) (*args,**kw)
g函數執行,返回
>>>h (*args,**kw)
h函數執行(列印日誌)返回f
>>>f
這是一個函數對象
'''

以上告訴我們一個道理"函數執行不執行取決於後面有沒有括弧"

舉個例子

def m(a):
    print(a)
    return m

print(m(1)(2)(3)(4)(5)(6)(7)(8)(9)(10))
'''
-----------------------------
1
2
3
4
5
6
7
8
9
10
<function m at 0x000002832BDB10D0>

 

燒腦時刻

 

 f = a.b.c()()[0]()[d()()[e]]
a模塊的b類的c方法是高階函數,最終返回一個列表,列表裡有個函數
函數又返回一個字典............

下麵開始,,裝飾器的三層嵌套

它要解決的問題是裝飾器需要額外的參數怎麼辦

就是g(m)變成了g(canshu)(m)多了一個括弧,那麼就得多加一層函數,,否則就會出錯

>>> 1()
    1()
TypeError: 'int' object is not callable

.修改後的

def i(canshu):
    def g(f):
        print("這裡是G")
        def h(*args,**kw):
            print('這裡是H')
            return f(*args,**kw)
        return h
    return g

@i('canshu')
def f(*args,**kw):
    print("這裡是F")
    return 1
'''
--------------------------------------------
這裡是G
>>> f()
這裡是H
這裡是F
'1'
>>> f
<function i.<locals>.g.<locals>.h at 0x00000283A6886D08>
'''

解釋一下

'''
>>>f(*args,**kw)
等價於
>>>i('canshu')(f)(*args,**kw)
i函數返回g
>>>g(f)(*args,**kw)
g函數執行,返回h
>>>h(*args,**kw)
h函數執行(列印日誌)返回f(*args,**kw)
>>>f(*args,**kw)
f執行返回1


>>>f
等價於
>>>i('canshu')(f)
i函數返回g
>>>g(f)
g函數執行,返回h
>>>h
這是個函數對象
'''

所以

>>> f.__name__
'h'

真是劫持器呀,__name__都變了...

至於為什麼每次最開始都有一個

應該可以這樣理解

執行@i('canshu')語句時候裝飾器被執行了一次

'''
>>>i('canshu')(f)
i函數返回g
>>>g(f)
g函數執行,列印了一句,返回h
>>>h
函數對象
'''

驗證

def i(canshu):
    def g(f):
        print(f.__name__)
        print('這裡是G')
        def h(*args,**kw):
            print('這裡是H')
            return f(*args,**kw)
        return h
    return g

@i('canshu')
def f(*args,**kw):
    print("這裡是F")
    return 1
'''
--------------------------------------------
f
這裡是G
>>> f()
這裡是H
這裡是F
'1'
'''

綜上,新啟示

  1. 裝飾器只有最內層的內容能起"日誌"的作用,其它層的內容會在定義時執行
  2. 只要設計得當,一個函數後面可以跟好多括弧,函數層數和括弧數得儘量一致

 另外,"劫持器"還可以用於調試,當兩個函數不知道哪個更好時

def i(f_1):
    def g(f):
        def h(*args,**kw):
            print('%s把%s替換了'%(f_1.__name__,f.__name__))
            return f_1(*args,**kw)
        return h
    return g

def f_1(*args,**kw):
    print("這裡是F_1")
    return 2

@i(f_1)
def f(*args,**kw):
    print("這裡是F")
    return 1
'''
--------------------------------------------
>>> f()
f_1把f替換了
這裡是F_1
2
'''

f=f_1語句的加日誌版φ(>ω<*) 

 應該完了,,,

有什麼遺漏以後再補

 突然覺得這篇隨筆處於"小白看不懂,大神不屑一顧"的尷尬境地.....

.


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

-Advertisement-
Play Games
更多相關文章
  • 第一階段:三年 我認為三年對於程式員來說是第一個門檻,這個階段將會淘汰掉一批不適合寫代碼的人。這一階段,我們走出校園,邁入社會,成為一名程式員,正式從書本 上的內容邁向真正的企業級開發。我們知道如何團隊協作、如何使用項目管理工具、項目版本如何控制、我們寫的代碼如何測試如何線上上運行等等,積累了一定的 ...
  • 1> 打開main.go文件,查看代碼 點擊快捷鍵F12,進入beego.go文件,查看代碼 2> 打開router.go文件,查看路由 3> 打開default.go文件,查看業務邏輯 4> 打開index.tpl文件,查看前臺頁面 5> 執行邏輯總結 第一步,進入main.go,編譯程式;第二步 ...
  • 本文介紹一下matplotlib的最基本用法。 這次我們要顯示一個線性方程的直線。 首先要引入matplotlib庫,一般是用plt這個簡寫的,我們就按照大多數人的慣例來進行命名: 下麵我們用numpy生成x軸上的數據:從 1到1,總共有50個點: 再定義一個線性方程: 接著把x和y在坐標軸上顯示出 ...
  • 英語里final這個單詞大家都知道是“最終的”意思,其實還有一個意思是“不可更改的”。在Java里,final關鍵字作“不可更改的”來解釋更合適,即由final修飾的東西是“不可更改的”。final可以修飾變數、成員方法和類,還可以修飾方法的參數,但參數歸根結底還是變數。下麵是詳細解釋。 作者: 蟬 ...
  • 一、回顧jQuery實現的ajax 首先說一下ajax的優缺點 jquery 實現的ajax 1 <!DOCTYPE html> 2 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>Title</title> 7 <scri ...
  • PyQt5 的 signal 與 slot 有所改變,例如,先定義一個 ZeroSignal 類: 使用時,一是綁定 slot 如下: 然後是找個機會發動之: 大約如此,完整代碼如下: OK! ...
  • java只所以被推廣,實際上很大原因是因為本身是跨平臺的,很大作用是因為虛擬機的關係。 一般情況下開發人員不需要關註虛擬機內部實現就可以日常開發了,但是有時候涉及到性能的時候就需要瞭解虛擬機的實現機制了。 那麼今天寫的內容更多的是關於編譯一套自己的虛擬機,為日後瞭解虛擬機底層原理鋪鋪路。 編譯虛擬機 ...
  • 歡迎訪問我的個人博客: "原文鏈接" 前言 人生苦短,我用python。學習python怎麼能不搞一下詞雲呢是不是(ง •̀_•́)ง 於是便有了這篇邊實踐邊記錄的筆記。 環境:VMware 12pro + CentOS7 + Python 2.7.5 安裝系統 之前一直用的是win10子系統,現在 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...