素材牛VIP会员
python装饰器为什么要双层嵌套函数
 訫***3  分类:Python  人气:925  回帖:6  发布于6年前 收藏

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

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

 标签:python

讨论这个帖子(6)垃圾回帖将一律封号处理……

Lv4 码徒
那***哦 移动开发工程师 6年前#1

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

@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 會返回一個全新的對象, 通常是一個 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

Lv3 码奴
馨***2 移动开发工程师 6年前#2
  1. 装饰器发生在定义而不是执行阶段.

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

楼下讲的详细, 很不错.

Lv6 码匠
ti***nx 学生 6年前#3
@foo
def bar()

等价于 bar = foo(bar),理解这个就好了

Lv1 新人
陈***1 学生 6年前#4

可能各位大神没明白我的意思,我的想法比较钻牛角尖,我的问题是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')
Lv5 码农
wa***ao 移动开发工程师 6年前#5

这个应该很多人都分享过了,我之前也写过一篇博客,题主有意思的话可以查看一下:

Python 装饰器详解

Lv6 码匠
真***名 其它 6年前#6

二楼说的对,在给bar赋值的时候就已经执行函数了。

 文明上网,理性发言!   😉 阿里云幸运券,戳我领取