理解Python中装饰器最佳方法~

了解装饰器之前,
可以先了解一下什么是闭包的概念为好:


闭包,
是指在一个函数中定义了一个另外一个函数,内函数里运用了外函数的临时变量(实际参数也是临时变量),并且外函数的返回值是内函数的引用(一切皆引用,所有的函数名字都只是函数体在内存空间的一个引用。)

通俗的解释:在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

闭包的作用:
可以隐藏内部函数的工作细节,只给外部使用者提供一个可以执行的内部函数的引用。避免了使用全局变量,保证了程序的封装性保证了内函数的安全性,其他函数不能访问


装饰器

装饰器就是用于拓展已有函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,实际上就是利用闭包语法实现的。

装饰器的作用

在不用更改原函数的代码前提下给函数增加新的功能。


嗯嗯嗯嗯……然后,就阅读了这么一堆:

详解Python的装饰器

理解 Python 装饰器

python装饰器简介---这一篇也许就够了

python 装饰器 一篇就能讲清楚

https://lotabout.me/2017/Python-Decorator/

Python装饰器的前世今生

巴拉巴拉巴拉大概意思就是,这个很重要,需要多理解~

我阅读了这么多文章的唯一的好处(感慨)就是,就算没理解,概念和基本使用也强行记住了

但是理解没?
——不知道,

!!!!!最后最后,只想安利:

最好的理解办法就是,先写一个简单的例子,然后,跟着调试器断点一步一步走,
然后多写点其他的情况,断点走走就知道其中的运行机制了
最后跟着概念也就理解了

def decorator(fun):
    print('I am in decorator!')

    print('will do fun')
    fun()
    print('goodbye fun')

    def inner():
        print('I am come in inner')
        fun()
        print('inner will say goodbye')
    
    #测试是否运行test2
    def test2():
        print('I am in test2')
        fun()


    return inner

print('test')

@decorator
def outfun():
    print('I am in outfun')

print('will do outfun')
outfun()

print('\nIt will do decorator')
decorator(outfun)

输出:

test
I am in decorator!
will do fun
I am in outfun
goodbye fun
will do outfun
I am come in inner
I am in outfun
inner will say goodbye

It will do decorator
I am in decorator!
will do fun
I am come in inner
I am in outfun
inner will say goodbye
goodbye fun

当然,关于装饰器还有很多知识点,远不止上面的测试程序这一点,详细了解还是可以看看上面的链接文章的。

如,

对有参函数进行装饰

对带返回值的函数进行装饰

带参数的装饰器

类装饰器

通用装饰器

内置的装饰器设置
……


下面的文字转载:python装饰器简介

对有参函数进行装饰

在使用中,有的函数可能会带有参数,那么这种如何处理呢?
代码优先:

def w_say(fun):
    """
    如果原函数有参数,那闭包函数必须保持参数个数一致,并且将参数传递给原方法
    """

    def inner(name):
        """
        如果被装饰的函数有行参,那么闭包函数必须有参数
        :param name:
        :return:
        """
        print('say inner called')
        fun(name)

    return inner


@w_say
def hello(name):
    print('hello ' + name)


hello('wangcai')

输出为:

say inner called
hello wangcai

此时,也许你就会问了,那是一个参数的,如果多个或者不定长参数呢,该如何处理呢?看看下面的代码你就秒懂了。

def w_add(func):
    def inner(*args, **kwargs):
        print('add inner called')
        func(*args, **kwargs)

    return inner


@w_add
def add(a, b):
    print('%d + %d = %d' % (a, b, a + b))


@w_add
def add2(a, b, c):
    print('%d + %d + %d = %d' % (a, b, c, a + b + c))


add(2, 4)
add2(2, 4, 6)

输出结果为:

add inner called
2 + 4 = 6
add inner called
2 + 4 + 6 = 12

对带返回值的函数进行装饰

下面对有返回值的函数进行装饰,按照之前的写法,代码是这样的

def w_test(func):
    def inner():
        print('w_test inner called start')
        func()
        print('w_test inner called end')
    return inner


@w_test
def test():
    print('this is test fun')
    return 'hello'


ret = test()
print('ret value is %s' % ret)

输出结果为

w_test inner called start
this is test fun
w_test inner called end
ret value is None

可以发现,此时,并没有输出test函数的‘hello’,而是None,那是为什么呢,可以发现,在inner函数中对test进行了调用,但是没有接受不了返回值,也没有进行返回,那么默认就是None了,知道了原因,那么来修改一下代码:

def w_test(func):
    def inner():
        print('w_test inner called start')
        str = func()
        print('w_test inner called end')
        return str

    return inner


@w_test
def test():
    print('this is test fun')
    return 'hello'


ret = test()
print('ret value is %s' % ret)

结果输出为:

w_test inner called start
this is test fun
w_test inner called end
ret value is hello

带参数的装饰器

介绍了对带参数的函数和有返回值的函数进行装饰,那么有没有带参数的装饰器呢,如果有的话,又有什么用呢?
答案肯定是有的,接下来通过代码来看一下吧。

def func_args(pre='xiaoqiang'):
    def w_test_log(func):
        def inner():
            print('...记录日志...visitor is %s' % pre)
            func()

        return inner

    return w_test_log


# 带有参数的装饰器能够起到在运行时,有不同的功能

# 先执行func_args('wangcai'),返回w_test_log函数的引用
# @w_test_log
# 使用@w_test_log对test_log进行装饰
@func_args('wangcai')
def test_log():
    print('this is test log')


test_log()

输出结果为:

...记录日志...visitor is wangcai
this is test log

简单理解,带参数的装饰器就是在原闭包的基础上又加了一层闭包,通过外层函数func_args的返回值w_test_log就看出来了,具体执行流程在注释里已经说明了。
好处就是可以在运行时,针对不同的参数做不同的应用功能处理。

通用装饰器
介绍了这么多,在实际应用中,如果针对没个类别的函数都要写一个装饰器的话,估计就累死了,那么有没有通用万能装饰器呢,答案肯定是有的,废话不多说,直接上代码。

def w_test(func):
    def inner(*args, **kwargs):
        ret = func(*args, **kwargs)
        return ret

    return inner


@w_test
def test():
    print('test called')


@w_test
def test1():
    print('test1 called')
    return 'python'


@w_test
def test2(a):
    print('test2 called and value is %d ' % a)


test()
test1()
test2(9)

输出为:

test called
test1 called
test2 called and value is 9 

类装饰器

装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。

当创建一个对象后,直接去执行这个对象,那么是会抛出异常的,因为他不是callable,无法直接执行,但进行修改后,就可以直接执行调用了,如下

class Test(object):
    def __call__(self, *args, **kwargs):
        print('call called')


t = Test()
print(t())

输出为:

call called

下面,引入正题,看一下如何用类装饰函数。

class Test(object):
    def __init__(self, func):
        print('test init')
        print('func name is %s ' % func.__name__)
        self.__func = func

    def __call__(self, *args, **kwargs):
        print('装饰器中的功能')
        self.__func()


@Test
def test():
    print('this is test func')


test()

输出结果为:

test init
func name is test 
装饰器中的功能
this is test func

和之前的原理一样,当python解释器执行到到@Test时,会把当前test函数作为参数传入Test对象,调用init方法,同时将test函数指向创建的Test对象,那么在接下来执行test()的时候,其实就是直接对创建的对象进行调用,执行其call方法。


关于装饰器里的上古神器:

https://zhangchuzhao.site/2018/05/25/python-decorator/

  1. @property -> getter/setter方法
  2. @classmethod、@staticmethod
  3. @functools.wraps
  4. Easter egg

另外,
关于迭代器和生成器可参考下面的优质文章

完全理解Python迭代对象、迭代器、生成器
Python迭代器与生成器


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