说说我对 Python 装饰器的理解 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
lzjun
V2EX    Python

说说我对 Python 装饰器的理解

  •  
  •   lzjun
    lzjun567 2017-06-19 16:46:11 +08:00 6362 次点击
    这是一个创建于 3045 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先说明一下,如有不正确的地方还请指正,大神请轻喷。

    Python 初学者对装饰器的理解存在困扰,我认为本质上是对 Python 函数理解不到位,Python 函数不同于其他编程语言,它可以作为第一类对象使用,这是关键。因为装饰器本质上还是函数,所以我们从函数开始说起

    函数定义

    先从一个最简单函数定义开始:

    def foo(num): return num + 1 

    上面定义了一个函数,名字叫foo,也可以把 foo 可理解为变量名,该变量指向一个函数对象

    调用函数只需要给函数名加上括号并传递必要的参数(如果函数定义的时候有参数的话)

    value = foo(3) print(value) # 4 

    变量名 foo 现在指向 <function foo at 0x1030060c8> 函数对象,但它也可以指向另外一个函数。

    def bar(): print("bar") foo = bar foo() # bar 

    函数作为返回值

    在 Python 中,一切皆为对象,函数也不例外,它可以像整数一样作为其它函数的返回值,例如:

    def foo(): return 1 def bar(): return foo print(bar()) # <function foo at 0x10a2f4140> print(bar()()) # 1 # 等价于 print(foo()) # 1 

    调用函数 bar() 的返回值是一个函数对象 <function foo at 0x10a2f4140>,因为返回值是函数,所以我们可以继续对返回值进行调用(记住:调用函数就是在函数名后面加())调用bar()()相当于调用 foo(),因为 变量 foo 指向的对象与 bar() 的返回值是同一个对象。

    函数作为参数

    函数还可以像整数一样作为函数的参数,例如:

    def foo(num): return num + 1 def bar(fun): return fun(3) value = bar(foo) print(value) # 4 

    函数 bar 接收一个参数,这个参数是一个可被调用的函数对象,把函数 foo 传递到 bar 中去时,foo 和 fun 两个变量名指向的都是同一个函数对象,所以调用 fun(3) 相当于调用 foo(3)。

    函数嵌套

    函数不仅可以作为参数和返回值,函数还可以定义在另一个函数中,作为嵌套函数存在,例如:

    def outer(): x = 1 def inner(): print(x) inner() outer() # 1 

    inner做为嵌套函数,它可以访问外部函数的变量,调用 outer 函数时,发生了 3 件事:

    1. 给 变量 x 赋值为 1
    2. 定义嵌套函数 inner,此时并不会执行 inner 中的代码,因为该函数还没被调用,直到第 3 步
    3. 调用 inner 函数,执行 inner 中的代码逻辑。

    闭包

    再来看一个例子:

    def outer(x): def inner(): print(x) return inner closure = outer(1) closure() # 1 

    同样是嵌套函数,只是稍改动一下,把局部变量 x 作为参数了传递进来,嵌套函数不再直接在函数里被调用,而是作为返回值返回,这里的 closure 就是一个闭包,本质上它还是函数,闭包是引用了自由变量(x)的函数(inner)。

    装饰器

    继续往下看:

    def foo(): print("foo") 

    上面这个函数这可能是史上最简单的业务代码了,虽然没什么用,但是能说明问题就行。现在,有一个新的需求,需要在执行该函数时加上日志:

    def foo(): print("记录日志开始") print("foo") print("记录日志结束") 

    功能实现,唯一的问题就是它需要侵入到原来的代码里面,把日志逻辑加上去,如果还有好几十个这样的函数要加日志,也必须这样做,显然,这样的代码一点都不 Pythonic。那么有没有可能在不修改业务代码的提前下,实现日志功能呢?答案就是装饰器。

    def outer(func): def inner(): print("记录日志开始") func() # 业务函数 print("记录日志结束") return inner def foo(): print("foo") foo = outer(foo) foo() 

    我没有修改 foo 函数里面的任何逻辑,只是给 foo 变量重新赋值了,指向了一个新的函数对象。最后调用 foo(),不仅能打印日志,业务逻辑也执行完了。现在来分析一下它的执行流程。

    这里的 outer 函数其实就是一个装饰器,装饰器是一个带有函数作为参数并返回一个新函数的闭包,本质上装饰器也是函数。outer 函数的返回值是 inner 函数,在 inner 函数中,除了执行日志操作,还有业务代码,该函数重新赋值给 foo 变量后,调用 foo() 就相当于调用 inner()

    foo 重新赋值前:

    重新赋值后,foo = outer(foo)

    另外,Python 为装饰器提供了语法糖 **@**,它用在函数的定义处:

    @outer def foo(): print("foo") foo() 

    这样就省去了手动给foo重新赋值的步骤。

    到这里不知你对装饰器理解了没有?当然,装饰器还可以更加复杂,比如可以接受参数的装饰器,基于类的装饰器等等,对后续感兴趣的可以关注一下公众号 Python 之禅,总之装饰器可以做的事情还是很多的。

    26 条回复    2017-06-21 10:48:17 +08:00
    prasanta
        1
    prasanta  
       2017-06-19 18:50:09 +08:00 via Android
    图用什么画的
    XYxe
        2
    XYxe  
       2017-06-19 18:56:15 +08:00
    dylanninin
        3
    dylanninin  
       2017-06-19 19:35:00 +08:00
    Kilerd
        4
    Kilerd  
       2017-06-19 21:01:06 +08:00
    @XYxe
    Server error! Your code might be too long for this tool. Shorten your code and re-try.

    略尴尬,才 400 行代码就不行了
    XYxe
        5
    XYxe  
       2017-06-19 21:17:03 +08:00
    @Kilerd #4 因为 URL 太长会导致 414 Request-URI Too Large
    代码在这: https://github.com/pgbovine/OnlinePythonTutor/blob/master/v5-unity/js/opt-frontend-common.ts#L145-L169
    lxml
        6
    lxml  
       2017-06-19 21:27:23 +08:00
    帮楼主广播一下博客,Python 之禅,在我入门和进阶的过程中都起了蛮大作用的,很多内容很契合 Fluent Python 的深度,由浅入深。
    douglas1997
        7
    douglas1997  
       2017-06-19 21:30:40 +08:00
    好像有一个错误

    ```
    def foo(num):
    return num + 1

    def bar(fun):
    return fun(3)

    value = bar(foo)
    print(value) # 4
    ```

    这里应该是 foo(3)?
    douglas1997
        8
    douglas1997  
       2017-06-19 21:31:52 +08:00
    @douglas1997 我的锅= =没看清楚。Sorry
    zzcchh
        9
    zzcchh  
       2017-06-19 22:42:34 +08:00 via Android
    python 开发组成员说过,类是字典,函数是字典,一切皆为字典。
    lzjun
        10
    lzjun  
    OP
       2017-06-20 00:59:51 +08:00
    @lxml 感谢仁兄支持
    lzjun
        11
    lzjun  
    OP
       2017-06-20 01:01:19 +08:00
    @zzcchh 的确,字典在 Python 中是基石,所以在 Python3.6 中可以看到核心开发者不留余力地对字典进行深度优化
    FrankFang128
        12
    FrankFang128  
       2017-06-20 01:02:49 +08:00
    跟 JS 很像。JS 也快要有 annotation 了
    lzjun
        13
    lzjun  
    OP
       2017-06-20 01:03:04 +08:00
    @Kilerd pythontutor 只是一个用于帮助初学者理解程序内部执行流程的工具图,对于大段代码显然很吃力
    kuntang
        14
    kuntang  
       2017-06-20 01:04:26 +08:00
    @FrankFang128 这有点像 java 中的 aop,而不是 annotation
    FrankFang128
        15
    FrankFang128  
       2017-06-20 01:13:30 +08:00
    @kuntang 形式是 annotation,实质是 AOP。
    AOP 的实质就是前面插个函数、屁股后面插个函数,我的理解
    linkiosk
        16
    linkiosk  
       2017-06-20 07:22:45 +08:00 via iPhone
    完全 copy 来的,标点都不带变的
    araraloren
        17
    araraloren  
       2017-06-20 09:04:16 +08:00
    ~~ 看英文名字 一看就明白了 wrapper,包裹在原来的函数外面
    gnaggnoyil
        18
    gnaggnoyil  
       2017-06-20 09:14:34 +08:00
    >Python 函数不同于其他编程语言,它可以作为第一类对象使用

    Pascal 和 Ada 看来似乎的确已经是一具尸体了.
    pyufftj
        19
    pyufftj  
       2017-06-20 09:35:26 +08:00
    哇,很有用。谢谢分享!
    AllenHai
        20
    AllenHai  
       2017-06-20 10:07:04 +08:00
    我好像在哪儿看过这篇文章
    wentian
        21
    wentian  
       2017-06-20 11:06:14 +08:00
    can you solve this problem?
    t/369746#reply0
    lzjun
        22
    lzjun  
    OP
       2017-06-20 13:11:43 +08:00
    @AllenHai 微信公众号上?
    qingshi
        23
    qingshi  
       2017-06-20 14:11:20 +08:00
    @lzjun python 之禅 的公众号拜读过这篇文章,看名字应该是本人。多谢作者的分享
    araraloren
        24
    araraloren  
       2017-06-20 14:16:34 +08:00
    @gnaggnoyil 眼里只有 python 了。。哈哈
    kisnows
        25
    kisnows  
       2017-06-20 16:39:49 +08:00
    看起来跟 js 很像
    joeHuang
        26
    joeHuang  
       2017-06-21 10:48:17 +08:00
    谢谢分享
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2953 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 00:21 PVG 08:21 LAX 17:21 JFK 20:21
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86