Python 笔记 | 从零开始,理解python装饰器

还是决定写一篇关于python装饰器的文章. 装饰器实在是太常用,也太好用的东西了. 这篇文章会从函数开始, 一步步解释装饰器. 因为装饰器实际是也只是高阶函数而已.

1. 函数

函数就比较基础了, 就像一个黑箱, 传入参数, 出来返回值. 比如这样

>>> def incr(num):
...     return num + 1
... 
>>> incr(21)
22

2. 函数内部定义函数

python支持在函数内部定义函数, 比如:

def parent():
    print("Printing from the parent() function")
    def first_child():
        print("Printing from the first_child() function")
    def second_child():
        print("Printing from the second_child() function")
    second_child()
    first_child()

这个也很好理解, 输出结果是这样的:

>>> parent()
Printing from the parent() function
Printing from the second_child() function
Printing from the first_child() function

在函数内部定义的函数, 作用域只在函数内部. 当然, 我们可以再来个复杂点的例子.

>>> def hello(name):
...     def say_hello():
...         print("Hello", name)
...     say_hello()
... 
>>> hello('world')
Hello world

注意, 这里在hello函数里面定义了一个函数 say_hello , say_hello这里面用到一个变量name, 是函数hello传入的参数, 这个在装饰器中会用到. 大家自己理解下.

3. 一切皆对象

python中一切皆是对象, 所有的类, 生成器, 变量, 甚至函数也是一样. 比如我们上面定义的incr函数:

>>> a = incr
>>> a
<function incr at 0x7f5485f77050>
>>> a(7)
8
>>> 

那既然是对象, 就可以作为参数传进函数中, 比如说:

>>> def print_incr(fun, num):
...     num = fun(num)
...     print(num+10)
>>> print_incr(incr, 10)
21

同样的道理, 函数既然可以作为参数, 也可以作为返回值, 比如:


>>> def left_or_right(direction):
... 
...     def left():
...         print('<-')
... 
...     def right():
...         print('->')
... 
...     if direction == 'left':
...         return left
...
...     elif direction == 'right':
...         return right
... 
>>> left_or_right('left')
<function left at 0x7f5485f73cb0>

>>> fun = left_or_right('left')

>>> fun()
<-

4. 简单的装饰器

装饰器听得很多了, 那到底装饰器是个什么东西? 听起来这么高端? 其实很简单, 我们玩个补全句子的文字游戏, 括号括起来的是定语

  1. 装饰器是函数.
  2. 装饰器是 (参数是函数, 返回值也是函数的) 函数.

写装饰器的时候大家请一定牢记这两句
那么我们开始写第一个装饰器了.

def decorator(func):
   # 我们在函数内部定义一个函数
   def wrapper():
        print('start')
        # 这里调用一下我们传进来的函数
        func()
        print('end')
   #  返回一个函数
   return wrapper

好了, 这就是一个简单的装饰器了. 我们可以这样去使用这个装饰器.

>>> def hello():
...     print('hello')
... 
>>> say_hello = decorator(hello)
>>> say_hello
<function decorator.<locals>.wrapper at 0x7f5485f4df80>
>>> say_hello()
start
hello
end
>>> 

这里的decorator就是装饰器了, 传入一个hello函数, 返回一个新的函数, 所以归根到底就是个函数. 一切皆对象, 所以返回函数和返回数字, 返回类的实例, 都没什么太大区别.
如果你认真阅读前面三个部分, 这里的装饰器应该没有任何问题. 如果还是感觉到困惑, 再去理解一遍前面三点.

5. 语法糖

如果我们做个这样一个装饰器, 每次调用都要调用一下, 这可不太优雅, 于是python提供一种便捷的语法糖方式. 还是上面的这个装饰器举例子

def decorator(func):
   # 我们在函数内部定义一个函数
   def wrapper():
        print('start')
        # 这里调用一下我们传进来的函数
        func()
        print('end')
   #  返回一个函数
   return wrapper

@decorator
def hello():
    print('hello')

这时候我们再调用hello

>>> hello()
start
hello
end

所以, @decorator 的意思就是说, hello = decorator(hello) , 是不是装饰器也没有那么复杂?

6. 带参数的函数的装饰器

动手试一下, 如何写这样一个函数的装饰器?

def add(a, b):
    return a+b

如果你对前面的部分理解的比较清楚透彻, 你大概已经实现出来了.

我们理清一下思路, 装饰器就是一个函数, 传入函数, 返回函数, 那就是这样子了

def decorator(fun):
    def wrapper(a, b):
        print('start')
        t = fun(a,b)
        print(t)
        print('end')
    return wrapper

>>> a = decorator(add)
>>> a
<function decorator.<locals>.wrapper at 0x7f5485f4d680>
>>> a(7, 8)的
start
15
end

7. 可带参数的装饰器

我们这里实现一个装饰器功能:
给定两个参数, msg(string) 和 times(int), 对于被装饰的函数, 先输出msg信息, 然后将fun运行times次,

同理, 如果你对于前面理解的透彻, 这部分应该写出来了.

给点提示, 装饰器是什么?
装饰器是函数, 参数是函数, 返回值也是函数.

那么我们的装饰器能不能用别的函数返回回来呢? 当然可以.


#  外面这个函数其实是用来接受参数的外壳
def deco(msg, times):
    # 里面的这个函数其实才是我们最终的装饰器
    def wrapper(fun):
        print(msg)
        for i in range(times):   
            fun()
    # 这里返回我们的装饰器
    return wrapper

>>> @deco(time=5) 
... def hello(): 
...     print('hello world') 
...                
                                                                                                                                                                                             
hello world
hello world
hello world
hello world
hello world

8. 举个例子

计数装饰器: 计算函数运行次数

import functools

def count_calls(func):
    @functools.wraps(func)
    def wrapper_count_calls(*args, **kwargs):
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)
    wrapper_count_calls.num_calls = 0
    return wrapper_count_calls

@count_calls
def say_whee():
    print("Whee!")

9. 写在后面

装饰器的用途实在很多很广, 如果有兴趣进一步了解, 可以去python的装饰器库去看一下:
https://wiki.python.org/moin/PythonDecoratorLibrary

我一开始用装饰器的时候也是一脸懵逼, 但是也坚持去用, 只要能用到的地方, 都尽可能的去使用, 不会的地方就去查别人的代码, 然后模仿着去写, 慢慢会用了之后再回头咀嚼原理直到吃透. 所以, 最重要的是多动手, 写就完了.

写了蛮久的, 如果对你有帮助, 顺手点个赞(≧▽≦)/. 大佬们赞赏就更欢迎啦. 还在学习, 如果有哪里不对的请多指教.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • 部分细节自己改了点,也加了点自己例子,基本上属于转载。转载出处:https://my.oschina.net/le...
    洛克黄瓜阅读 1,976评论 0 3
  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,504评论 0 6
  • Python的装饰器(decorator)是一个很棒的机制,也是熟练运用Python的必杀技之一。装饰器,顾名思义...
    温柔的倾诉阅读 396评论 0 0
  • 一、装饰器的基本使用 在不改变函数源代码的前提下,给函数添加新的功能,这时就需要用到“装饰器”。 0.开放封闭原则...
    NJingZYuan阅读 529评论 0 0
  • 在灰蓝的宿舍里,住着四位性格迥异的小仙女,他们有着开朗乐观的性子,也有着温柔体贴的时候。就这样彼此和谐相处,虽然有...
    木莓橙阅读 789评论 0 1