本文总结自oldboy python教学视频。

一、前言

1.装饰器本质上就是函数,功能是装饰其他函数,为其他函数添加附加功能。

装饰器在装饰函数时必须遵循3个重要的原则:

(1)不能修改被装饰的函数的源代码

(2)不能修改被装饰的函数的调用方式

(3)不能修改表装饰函数的返回结果

2.实现装饰器的知识储备:

(1)函数即“变量”

关于函数即变量,我的理解如下所示,即当定义一个函数时,相当于在内存中划一块内存空间,里面存放函数的函数体,而函数名指向这块空间——这跟普通的函数定义如 a = 9527 是一样的。如此一来,函数名便可以跟普通变量一样,作为参数传给其他函数,也能在函数中作为结果被返回。

(2)高阶函数

a.把一个函数名当做实参传给另一个函数(在不修改被装饰函数源代码的情况下为其添加功能)

或:

b.返回值中包含函数名(不修改函数的调用方式)

(3)嵌套函数

所谓嵌套函数,即在一个函数内定义另外一个函数(注意是定义,而不是调用)。

3.高阶函数 + 嵌套函数 => 装饰器

 

二、装饰器应用

1.被装饰函数没有参数

假设被装饰的函数为test1(),此时的test1()没有形参,默认返回NULL;装饰器timmer完成的附加功能是计时。

# -*- coding:utf-8 -*-
#Author:Suxy import time def timmer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print("the func run time is %s" %(stop_time - start_time))
return wrapper @timmer #test1 = timmer(test1),即test1 = wrapper
def test1():
time.sleep(1)
print("in the test1") test1()

输出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
in the test1
the func run time is 1.0003414154052734

Process finished with exit code 0

注:在函数test1()的上面加上@timmer,作用相当于test1 = timmer(test1),又因为timmer函数中返回wrapper,所以有test1 = timmer(test1) = wrapper,因此最后一行代码在使用test()方式调用函数的时候,实际调用的是wrapper(),而wrapper中的变量func接收了timemr(test1)传进来的test1,所以实际调用时,先执行wrapper中附加的功能(计时),之后执行test1()本身的功能。

2.被装饰函数带有参数

装饰器不仅要能装饰无参函数,还得能装饰带参函数。这里以只带一个参数的函数test2(name)为例。此时若是不修改上面装饰器的任何代码便用来装饰test2(name),则会报错,具体如下:

# -*- coding:utf-8 -*-
#Author:Suxy import time def timmer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print("the func run time is %s" %(stop_time - start_time))
return wrapper @timmer #test1 = timmer(test1),即test1 = wrapper
def test1():
time.sleep(1)
print("in the test1") @timmer
def test2(name):
time.sleep(1)
print("in the test2, ", name) test1()
test2("suxy")

输出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
Traceback (most recent call last):
File "E:/python_workspace/pure_python/day4/decorator.py", line 25, in <module>
test2("suxy")
TypeError: wrapper() takes 0 positional arguments but 1 was given
in the test1
the func run time is 1.000788688659668 Process finished with exit code 1

可见,此时装饰器timmer用于装饰test1()时没有问题,但是装饰test2()时报错了:wrapper()函数接受0个位置参数,但是在调用时却传了1个。

由第1步可知,在使用装饰器timmer装饰函数test2之后,调用test2(“suxy”)就相当于调用wrapper(“suxy”)函数,而当前的wrapper()函数是不接收参数的。

若需要timme装饰器能够同时装饰test1和test2函数,则需要对timmer装饰器进行修改——不过也不能简单地将timmer()中wrapper()函数的定义修改为wrapper(name),因为这样一来timmer装饰test1就会出错,此外,timmer装饰器有可能还要装饰其他的比如带有默认参数、关键字参数等的函数,因此,wrapper函数的定义应修改为wrapper(*args, **kwargs),并且wrapper中func的调用方式也得进行相应的修改func(*args, **kwargs),这样一来,无论被装饰的函数是无参函数、默认参数、关键字参数还是如*args列表参数、**kwargs字典参数,装饰器都能进行装饰。如下:

# -*- coding:utf-8 -*-
#Author:Suxy import time def timmer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
stop_time = time.time()
print("the func run time is %s" %(stop_time - start_time))
return wrapper @timmer #test1 = timmer(test1),即test1 = wrapper
def test1():
time.sleep(1)
print("in the test1") @timmer
def test2(name):
time.sleep(1)
print("\nin the test2, ", name) test1()
test2("suxy")

输出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
in the test1
the func run time is 1.000464916229248 in the test2, suxy
the func run time is 1.0004689693450928 Process finished with exit code 0

3.上面第2步所说的装饰器已经能够装饰一般的带参函数了,不过,若是被装饰函数具有返回值,则上面的装饰器就有问题了。如下(为缩减篇幅,暂将函数test1省略掉):

# -*- coding:utf-8 -*-
#Author:Suxy import time def timmer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
stop_time = time.time()
print("the func run time is %s" %(stop_time - start_time))
return wrapper @timmer
def test2(name):
time.sleep(1)
print("\nin the test2, ", name)
return 9527 print(test2("suxy"))

输出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py

in the test2,  suxy
the func run time is 1.0003151893615723
None Process finished with exit code 0

可见,函数test2()中明明返回了9527,但是最后输出确是None,违反了装饰器不能修改被装饰函数的返回结果的原则。这是因为装饰器的wrapper中调用func(*args, **kwargs)函数(注: func(*args, **kwargs) = test2(*args, **kwargs))时,只是进行直接的调用,而没有将调用的结果返回。可以将装饰器代码修改为 return func(*args, **kwargs),或者先用res = func(*args, **kwargs)将函数执行结果保存到res中,最后再返回res。如下:

# -*- coding:utf-8 -*-
#Author:Suxy import time def timmer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print("the func run time is %s" %(stop_time - start_time))
return res
return wrapper @timmer
def test2(name):
time.sleep(1)
print("\nin the test2, ", name)
return 9527 print(test2("suxy"))

输出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py

in the test2,  suxy
the func run time is 1.0006110668182373 Process finished with exit code 0

4.到上一步,装饰器已经可以装饰绝大部分的函数了。但是目前的装饰器只能简单的接受一个参数,即被装饰函数的函数名,无法接受其他的参数。若是有其他复杂的需求,比如接受func_type字段,根据func_type的不同而做不同的输出,则目前的装饰器无法完成这个功能。

# -*- coding:utf-8 -*-
#Author:Suxy import time def before_timmer(func_type):
if func_type == "type1":
print("It is type 1")
elif func_type == "type2":
print("It is type 2")
else:
print("Another type") def timmer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print("the func run time is %s" % (stop_time - start_time))
return res
return wrapper
return timmer @before_timmer(func_type = "type2")
def test2(name):
time.sleep(1)
print("\nin the test2, ", name)
return 9527 test2("suxy")

输出:

C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
It is type 2 in the test2, suxy
the func run time is 1.0008769035339355 Process finished with exit code 0

仔细对比这里和3中装饰器代码的区别可以发现,这一步相当于又新建了一个装饰器before_timmer将原先的装饰器timmer包了起来,并将timmer函数作为结果返回,使得可以在before_timmer装饰器中先完成判断功能,再调用timmer计时功能。

此时对于test1()函数定义上的@before_timmer(func_type = "type1"),我的理解是:test2 = before_timmer(func_type = “type2”),又因为before_timmer中有return timmer,所以test2 = before_timmer(func_type = “type2”) = timmer(test2),同时由于timmer中有return wrapper,所以 test2 = before_timmer(func_type = “type2”) = timmer(test2) = wrapper,因此,当调用test2(“suxy”)时,还是最后还是相当于调用了wrapper(“suxy”)。

P03-Python装饰器的更多相关文章

  1. 关于python装饰器

    关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...

  2. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

  3. Python 装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...

  4. python 装饰器修改调整函数参数

    简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...

  5. python 装饰器学习(decorator)

    最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...

  6. Python装饰器详解

    python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...

  7. 关于python装饰器(Decorators)最底层理解的一句话

    一个decorator只是一个带有一个函数作为参数并返回一个替换函数的闭包. http://www.xxx.com/html/2016/pythonhexinbiancheng_0718/1044.h ...

  8. Python装饰器由浅入深

    装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...

  9. Python装饰器与面向切面编程

    今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数 ...

  10. python装饰器方法

    前几天向几位新同事介绍项目,被问起了@login_required的实现,我说这是django框架提供的装饰器方法,验证用户是否登录,只要这样用就行了,因为自己不熟,并没有做过多解释. 今天查看dja ...

随机推荐

  1. Java Thread系列(十)生产者消费者模式

    Java Thread系列(十)生产者消费者模式 生产者消费者问题(producer-consumer problem),是一个多线程同步问题的经典案例.该问题描述了两个共亨固定大小缓冲区的线程-即所 ...

  2. 模板模式c#(非常简单,但又非常简洁好玩)

    using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace 模板模式{   ...

  3. Docker添加官方加速源(必须)

    在国内使用Docker必须用加速镜像不然的话无论是pull 官方的还是私有的镜像都会WAIT TIME EXCEED 下面给出macos的添加方式,非常简单 macOS 对于使用 macOS 的用户, ...

  4. jmeter 计数器 (可自动生成新数字、注册专用)

    1.打开jmeter,创建好线程组后,添加计数器 2.设置计数器 3.添加HTTP请求,验证所设置的计数器 4.填写对应参数 5.添加查看结果树,查看结果 6.修改一下线程属性 7.跑一下,看下结果就 ...

  5. [javascript]两段 javaScript 代码的逻辑比较

    两段 javaScript 代码的逻辑比较: #1 if(tagName.length < 3){    $(this).parent().addClass('active');    tagN ...

  6. 基于SSH的网上图书商城-JavaWeb项目-有源码

    开发工具:Myeclipse/Eclipse + MySQL + Tomcat 项目简介: 技术:Java:JSP:JDBC,struts2,spring,hibernate数据库: mysqlweb ...

  7. PG数据库错误: 检测到OA幸运飞艇源码ShareLock死锁处理

    PostgreSQL 是一个免费数据库,OA幸运飞艇源码下载,详情咨询[企娥166848365]对于处理分析型+交易型混合型系统来说确实很不错,特别是版本的升级到11.2后性能提升很多,很多运行机制跟 ...

  8. MSP430 G2553 Launchpad实现电容测量

    一.基本原理 对于Source-Free RC电路,其电容放电的特性可以描述为: 其中V0是电容的初始电压,t是放电时间,R是串接的电阻阻值,C是电容值,v(t)是t时刻电容上的电压.因此,若已知V0 ...

  9. java的一些命名规范吧

    注意事项: 1.由于Java是面向对象编程的,所以在命名的时候尽量选择名词. 2.(Camel-Case)驼峰命名法:当变量名或函式名是由一个或多个单字连结在一起,而构成的唯一识别字时,首字母以小写开 ...

  10. 开源应用框架BitAdminCore:更新日志20180605

    索引 NET Core应用框架之BitAdminCore框架应用篇系列 框架演示:http://bit.bitdao.cn 框架源码:https://github.com/chenyinxin/coo ...