python装饰器为什么要双层嵌套函数
PHPz
PHPz 2017-04-18 09:41:45
0
6
842

今天学习了一下python的装饰器,比较难理解的是大家举最简单的装饰例子时都是双层嵌套:

但是单层函数,不也能实现装饰功能吗?python把装饰器设置成双层的目的是什么呢?@到底代表什么运作机制。

PHPz
PHPz

学习是最好的投资!

全部回覆(6)
刘奇

如同上面好幾位大大所說, 裝飾器 (@語法) 的作用:

@foo
def bar():
    ...

等價於:

bar = foo(bar)

翻成中文就是:

利用被 @ 的 function 當作引數來呼叫 @ function, 並且賦值給 被 @ function 的函數名稱

因為這個動作很像是 裝飾(修改, 擴增, 調整, 限制...) 原本的 bar, 所以被叫做 裝飾器:

By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

雖然說是裝飾, 但執行完的bar 跟原本的 bar 早已不是同一個人了, foo 跟原本的

早已不是同一個人了, foo 會返回一個全新的對象, 通常是一個function, 但很有可能返回的東西根本連function 都不是:

def foo(func):
    return None

@foo
def bar():
    ...
foo 變成了一個很奇怪的裝飾器, 因為他返還的東西是 None在上面的例子中, , 不但沒有裝飾, 還毀滅了 function

回到你一開始舉的例子:

def foo(func):
    print('foo')
    func()

@foo
def func_a():
    print('func_a')

func_a()
foo 沒有 return 述句, 這代表 foo 會返還 None, 你寫的根本是個毀滅器(開玩笑), 你看到的效果只是曇花一現的假象, 那是因為在 @ 語法發揮作用的那一瞬間, print你的 語法被執行了

func_a, 你會發現錯誤被引發了, 因為 func_a但是你大可以試著調用

根本不是一個 function, 當然你想要達到的效果也無法復用

裝飾器的確不一定要使用local function 或是嵌套式的函數, 但是我們通常都會讓裝飾器返還一個function, 我覺得這是很重要的一點, 畢竟我們都會直覺認為被裝飾過的函數還是個函數

裝飾器的樣貌千千萬萬種, 有的確使用單層嵌套就可以了, 比如說註冊函數:

registry = []

def register(func):
    print('register {}'.format(func))
    registry.append(func)
    return func  # 還是應該要 return func
原因是這個動作只需要在裝飾的時候處理一次即可, 你不會想要每次調用函數都註冊一次, 我們需要的只是原來的 function.

但是像打印log 或是計算時間

等等的行為, 我們還是得用嵌套的手法, 因為我們每次調用function 都想要打印和計算, 我們需要的是一個新的function, 這個function 就靠local function 製造出來, 那必然會產生層疊和嵌套, 還有一些比較複雜的帶參數的裝飾器, 可能會使用到超過兩層嵌套

小結

要不要嵌套要看目的, 但務必記得裝飾後返回函數

我回答過的問題

: Python-QA🎜
迷茫

可能各位大神沒明白我的意思,我的想法比較鑽牛角尖,我的問題是python為什麼這麼設計,因為開始我覺得嵌套兩層函數沒有用處,一個簡單的裝飾器,一層函數足以能打印個log,time啥的呀,這不是python的哲學,想了一晚上,自己理解點,寫的比較亂,有需要的朋友可以看看吧,驚嘆於這種設計模式的精彩絕倫。使用的逆推法,也就是明確根本需求,然後倒著推理,每一步為什麼這麼寫。

def outer(func):          #3.此处关键了,因为我们是倒推,下面的结构已经固定了,
    def inner(x):         #outer(f1)返回给f1的值,必须是一个函数!outer自己也是函数
        print('loding')   #可以返回自己嘛!但是注意哦,它已经有且必须有一个func参数,来传递原生函数名
        func(x)        #也就是f1函数名变量的入口,那么他返回给新的f1函数,就会使原生函数多个参数,
        print('Done')     #改变我们最初的目的,新f1与老f1调用方法要无区别,那么咋整
    return inner          #嵌套一个函数inner,他接收f1参数,外层outer接收函数名f1
def f1(x):                #至此装饰器是两层函数嵌套,当f1没有参数时,依然需要双层
    print('f1 func',x)    #因为必须返回一个函数,返回outer本身,就需要加func参数
@outer                    #而我们又追求不改变原生调用f1(),他是没有参数的
def f2(x):                #所以一个装饰器必须至少双层函数嵌套,第一层传递原生函数名
    print('f2 func',x)    #第二层执行装饰功能,这设计真是牛逼,逆推一晚上才有点理解。
@outer                   
def f3(x):
    print('f3 func',x)
                          #2.那么F1要指向一个新函数
                          #并且这个函数能被F2 F3都指向
f1  = outer(f1)              #所以它是一个可以传递函数名变量的函数
#f1 = outer(f1('tings'))  #ps:带上参数一起传递,这也是一种可能,但是没有价值,
                          #装饰器使用时每个都要写一遍,其实就已经不是原生方法调用了,
                          #因为要给语法糖@输送'things'参数,不符合我们初衷。
                          
f1('tings1')              #1.首先明确我们的根本需求,外部调用方法要完全相同,这也是装饰器的意义。
f2('tings2')
f3('tings3')
Ty80
  1. 裝飾器發生在定义而不是执行階段.

  2. 裝飾器函數outer必须返回一个被装饰的函数, 注意它必须返回一个定义, 而不是调用

樓下講的詳細, 很不錯.

刘奇

這個應該很多人都分享過了,我之前也寫過一篇博客,題主有意思的話可以查看一下:

Python 裝飾器詳解

PHPzhong
@foo
def bar()

等價於 bar = foo(bar),理解這就好了

PHPzhong

二樓說的對,在給bar賦值的時候就已經執行函數了。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板