当前位置: 首页 > 科技观察

一个大家都能看懂的Python装饰器入门教程!

时间:2023-03-13 05:00:22 科技观察

大家好,我早起。上一篇文章提到过,很多人认为了解了装饰器的概念和用法之后,会觉得自己的Python水平有了明显的提升。但是很多教程一开始都会给出装饰器的定义和基本用法。比如你肯定会在很多文章中看到代码运行时定时器等相关的常用装饰器。直接从应用中学习当然很有效,但是不是看了就忘了,就是半懂半懂,因为装饰器从来就不是一个单独的概念,就像数学分析中计算积分一样,可以很快计算出来通过公式积分是需要的,但是如果你明白积分是由极限来定义的,那么你就会从不同的角度来看待积分。在本文中,我将尝试阐明为什么需要装饰器,什么是装饰器,以及如何编写一个简单的装饰器,但要完全理解装饰器,我们必须从函数开始。下面介绍关于函数的四个重要概念,希望大家能够理解。01.关于函数的四个重要概念相信在大多数文章中,你至少可以知道“装饰器是一个装饰函数”、“不修改函数代码的情况下添加额外的函数”等核心概念,但首先要知道为什么函数可以装饰。比如《流畅的Python》这本书,在谈到函数的时候,一开始就提出了一个概念。函数是一流的对象。正如书中所说,Python中的函数可以作为参数或另一个函数传递。的返回值,也是函数可以被装饰的关键,在介绍装饰器之前,有必要通过简单的代码对这段话有一个更直观的认识。1.1在函数中传递函数在函数中传递函数是指函数可以作为变量使用。让我们看一个简单的例子。在下面的代码中,func1是一个普通函数,它接受两个参数a、b并返回它们的和。func2的区别在于它接收了一个额外的func参数。这个func变量需要是一个函数deffunc1(a,b):print(f"Thefunction{func1.__name__}isexecuting")return+bdeffunc2(func,c,d):print(f"Thefunction{func2.__name__}isexecuting")__name__}isbeingexecuted")returnfunc(c,d)现在我们执行func1>>>func1(1,2)函数func1正在执行3下面的func1作为参数执行func2>>>func2(func1,3,4)functionfunc2isexecutingfunctionfunc1isexecuting7.可以看到func2先执行,func2收到fun1后,再次执行func1返回。注意这里的func1没有括号,只是使用了和a、b一样的参数。明白了这一点,我们就进入下一个知识点。1.2在函数中定义函数定义完函数后,可以在函数内部继续定义新的函数。为了理解这一点,让我们看一下下面的简单示例。我们先定义了一个函数func1,在func1中定义了func2,调用了func2deffunc1():print(f"函数{func1.__name__}正在执行中")deffunc2():print(f"inside函数{func2.__name__}正在执行中")func2()现在执行func1和func2看看会发生什么>>>func1()函数func1正在执行内部函数func2正在执行>>>func2()-----------------------------------------------Traceback(最近调用最后)---->1func2()NameError:name'func2'isnotdefined可以看到执行func1的时候会自动执行func2,但是单独执行func2的话提示undefined,说明func2只能在func1中调用!1.3函数返回函数最后是一个简单的例子,一个函数可以返回另一个函数作为返回值。在下面的代码中,我们首先定义一个外部函数func1(接受一个参数a),然后定义一个内部函数func2(接受一个参数b)并返回a+b,最后返回func2作为func1的返回值deffunc1(a):print(f"function{func1.__name__}isexecuting")deffunc2(b):print(f"function{func2.__name__}isexecuting")returna+breturnfunc2需要注意的是这里返回的func2没有括号,表示返回的是func2的地址!>>>func3=func1(1)>>>func3functionfunc1isexecuting.func2(b)>>>>func3(2)functionfunc2isexecuting3从上面的运行结果可以看到原来执行func1(1)时返回func2的地址赋值给func3,然后执行func3(2)才真正执行了内部函数func2!现在我们完成了上一节题“单独使用内部函数func2”!1.4函数内省函数内省是一个比较容易理解的概念。在Python中,这意味着我们可以访问函数的某些属性,例如打印函数。可以使用dir函数查看其所有属性>>>dir(print)['__call__','__class__','__delattr__',······'__subclasshook__','__text_signature__']现在可以查看其对应属性>>>print.__name__'print'>>>print。__call__>>>print.__doc__"print(value,...,sep='',end='\\n',file=sys.stdout,flush=False)\n\nPrintsthevaluestoastream,ortosys.stdoutbydefault.\nOptionalkeywordarguments:\nfile:afile-likeobject(stream);defaultstothecurrentsys.stdout.\nsep:stringinsertedbetweenvalues,defaultaspace.\nend:stringappendedafterthelastvalue,defaultanewline.度够了,这里再提知识点2.3节!至此,我已经介绍了在接触装饰器之前必须彻底理解的知识点。如果你觉得我的解释不够清楚,你可以查看任何其他教程或书籍来理解,然后继续阅读。我们来谈谈装饰器,当然肯定不是直接告诉你一个写好的装饰器,而是一点一点写一个简单的装饰器。2.1第一个装饰器在下面的代码中,我们首先定义了一个函数first_decorator,它接受一个函数作为参数(不理解的请参考本文1.1节),然后在内部定义了一个名为name_wrapper(不理解请参考本文1.2节),最后返回name_wrapper作为返回值(不理解请参考本文1.3节)deffirst_decorator(func):defname_wrapper():print(f"装饰函数{func.__name__}即将执行")func()print(f"装饰函数{func.__name__}已执行")returnname_wrapper的作用是打印一段之前和执行完接收到的函数,所以我们需要再定义一个函数来测试效果defadd():print("Thefunctionaddisbeingexecuted")这个fun1没什么好说的,打印一段就可以了。以下内容需要仔细阅读。我们来执行这两段代码>>>add=first_decorator(add)>>>add()装饰函数add即将执行函数add正在执行装饰函数add如我们所料执行同样有提示执行add前后,但是如果每次使用first_decorator函数都需要通过add,然后调用,来回写好几次,太麻烦了!因此,还有一种更Pythonic的写法,就是我们常见的装饰器形式,使用语法糖@,比如上面的例子,就相当于下面的写法@first_decoratordefadd():print("函数add是execution")和@+装饰器函数名可以放在要装饰的函数上面,现在就可以直接调用add实现装饰器的功能了!>>>add()被装饰的函数add即将执行函数add正在执行被装饰的函数add完成相信看到这里你应该明白装饰器@是如何工作的了,至少你需要想到类似的东西以后看到@的时候就相当于add=first_decorator(add)!2.2装饰器传参上面只是一个最简单的装饰器例子。实际使用中,自然的想法是加参数,不难改@first_decoratordefadd(x,y):print("Thefunctionaddisexecuting")print(f"{x}+{y}resultsin{x+y}")让我们测试一下>>>add(1,2)----------------------------------------------Traceback(最近调用最后)in---->1add(1,2)TypeError:name_wrapper()takes0positionalargumentsbut2weregiven无一例外的报错,虽然我们给装饰函数添加了参数,但是装饰器的内部函数name_wrapper()在执行的时候,并没有没有参数!所以我们之前的代码可以这样改,使用*args,**kwargs也是很常见的用法deffirst_decorator(func):defname_wrapper(*args,**kwargs):print(f"装饰函数{func.__name__}即将执行")func(*args,**kwargs)print(f"装饰函数{func.__name__}isexecuted")returnname_wrapper现在我们可以再次使用这个装饰器来返回我们预测的结果!@first_decoratordefadd(x,y):print("函数add正在执行")print(f"{x}+{y}结果为{x+y}")>>>add(1,2)被装饰函数add即将执行函数add正在执行1+2结果为3装饰函数add正在执行省相关知识?我们可以打印其他属性,例如函数指向的内存地址或名称。还是上面用到的add函数,我们都知道虽然是装饰了,但是功能上并没有什么变化。它仍然计算两个数的和,但真的没有变化吗?我们看一下defadd(x,y):print("函数add正在执行")print(f"{x}+{y}结果为{x+y}")>>>print(add)>>>print(add.__name__)add@first_decoratordefadd(x,y):print("函数add正在执行")print(f"{x}+{y}结果为{x+y}")>>>print(add).name_wrapperat0x7fddb9dd4e50>>>>print(add.__name__)name_wrapper可以看到装饰后,虽然函数没有变化,但是指向了定义的装饰器内部函数!这不是我们想看到的,比如不同的函数被两个装饰器装饰,就会出现相同的函数名!幸运的是,Python中的functools库可以轻松解决这个问题,只需要添加一行简单的代码就可以搞定!importfunctoolsdeffirst_decorator(func):@functools.wraps(func)defname_wrapper(*args,**kwargs):print(f"装饰函数{func.__name__}即将执行")func(*args,**kwargs)print(f"装饰函数{func.__name__}执行完毕")returnname_wrapper@first_decoratordefadd(x,y):print("函数add正在执行中")print(f"{x}+{y}结果为{x+y}")>>>print(add)<函数addat0x7fddb9dd4e50>>>>print(add.__name__)add可以看到此时保留了函数名和其他函数属性。其实@functools.wraps(func)通过functools.update_wrapper()反省了原函数的一部分属性是固定的,只传递了一些关键参数就实现了这个功能。有兴趣的读者可以自行深入研究这一点。我想你应该明白为什么需要装饰器,什么是装饰器,以及如何编写一个简单的装饰器。当你再看装饰器的时候,想到的概念应该不仅仅是@。关于装饰器更高级的用法,以及一些常用的、好用的装饰器,我会在装饰器的第二篇中介绍!