热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

详解Python装饰器的介绍

装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码。装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们以装饰函数为例子介绍其用法。要理解在Python中装饰器的原理,需要一步一步来。本文
装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码。装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们以装饰函数为例子介绍其用法。要理解在Python中装饰器的原理,需要一步一步来。本文尽量描述得浅显易懂,从最基础的内容讲起。
(注:以下使用Python3.5.1环境)
一、Python的函数相关基础
第一,必须强调的是python是从上往下顺序执行的,而且碰到函数的定义代码块是不会立即执行它的,只有等到该函数被调用时,才会执行其内部的代码块。

def foo():
print("foo函数被运行了!") 
如果就这么样,foo里的语句是不会被执行的。
程序只是简单的将定义代码块读入内存中。


再看看,顺序执行的例子:

def foo():
 print("我是上面的函数定义!")
def foo():
 print("我是下面的函数定义!")
foo()
运行结果:
我是下面的函数定义


可见,因为顺序执行的原因,下面的foo将上面的foo覆盖了。因此,在Python中代码的放置位置是有要求的,不能随意摆放,函数体要放在被调用的语句之前。
 其次,我们还要先搞清楚几样东西:函数名、函数体、返回值,函数的内存地址、函数名加括号、函数名被当作参数、函数名加括号被当作参数、返回函数名、返回函数名加括号。对于如下的函数:

def foo():
 print("让我们干点啥!")
 return "ok"
 foo()  


  函数名:        foo
  函数体:        第1-3行
  返回值:        字符串“ok”    如果不显式给出return的对象,那么默认返回None
  函数的内存地址:    当函数体被读进内存后的保存位置,它由标识符即函数名foo引用,
也就是说foo指向的是函数体在内存内的保存位置。
  函数名加括号:     例如foo(),函数的调用方法,只有见到这个括号,程序会根据
函数名从内存中找到函数体,然后执行它
再看下面这个例子:

def outer(func):
 def inner():
 print("我是内层函数!")
 return inner
def foo():
 print("我是原始函数!") 
outer(foo)
outer(foo())


在python中,一切都是对象,函数也不例外。因此可以将函数名,甚至函数名加括号进行调用的方式作为另一个函数的返回值。上面代码中,outer和foo是两个函数,outer(foo)表示将foo函数的函数名当做参数传递给outer函数并执行outer函数;outer(foo())表示将foo函数执行后的返回值当做参数传递给outer函数并执行outer函数,由于foo函数没有指定返回值,实际上它传递给了outer函数一个None。注意其中的差别,有没有括号是关键!
 同样,在outer函数内部,返回了一个inner,它是在outer函数内部定义的一个函数,注意,由于inner后面没有加括号,所以返回的是inner的函数体,实际上也就是inner这个名字,一个简单的引用而已。那么,如果outer函数返回的是inner()呢?现在你应该已经很清楚了,它会先执行inner函数的内容,然后返回个None给outer,outer再把这个None返回给调用它的对象。
 请记住,函数名、函数加括号可以被当做参数传递,也可以被当做返回值return,有没有括号是两个截然不同的意思!
二、装饰器的使用场景
装饰器通常用于在不改变原有函数代码和功能的情况下,为其添加额外的功能。比如在原函数执行前先执行点什么,在执行后执行点什么。
 让我们通过一个例子来看看,装饰器的使用场景和体现的设计模式。(抱歉的是我设计不出更好的场景,只能引用武大神的案例加以演绎)
 有一个大公司,下属的基础平台部负责内部应用程序及API的开发,有上百个业务部门负责不同的业务,他们各自调用基础平台部提供的不同函数处理自己的业务,情况如下: 

# 基础平台部门开发了上百个函数
def f1():
 print("业务部门1数据接口......")
def f2():
 print("业务部门2数据接口......")
def f3():
 print("业务部门3数据接口......")
def f100():
 print("业务部门100数据接口......") 
#各部门分别调用
f1()
f2()
f3()
f100()


由于公司在创业初期,基础平台部开发这些函数时,由于各种原因,比如时间,比如考虑不周等等,没有为函数调用进行安全认证。现在,平台部主管决定弥补这个缺陷,于是:
  第一回,主管叫来了一个运维工程师,工程师跑上跑下逐个部门进行通知,让他们在代码里加上认证功能,然而,当天他被开除了。
 第二回:主管又叫来了一个运维工程师,工程师用shell写了个复杂的脚本,勉强实现了功能。但他很快就回去接着做运维了,不会开发的运维不是好运维....
 第三回:主管叫来了一个python自动化开发工程师,哥们是这么干的:只对基础平台的代码进行重构,让N个业务部门无需做任何修改。这哥们很快也被开了,连运维也没得做。  

def f1():
 #加入认证程序代码
 print("业务部门1数据接口......")
def f2():
 # 加入认证程序代码
 print("业务部门2数据接口......")
def f3():
 # 加入认证程序代码
 print("业务部门3数据接口......")
def f100():
 #加入认证程序代码
 print("业务部门100数据接口......")
#各部门分别调用
f1()
f2()
f3()
f100()


 第四回:主管又换了个 工程师,他是这么干的:定义个认证函数,原来其他的函数调用它,代码如下框。但是,主管依然不满意,不过这一次他解释了为什么。主管说:写代码要遵循开放封闭原则,虽然在这个原则主要是针对面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码内部不允许被修改,但外部可以被扩展,即:封闭:已实现的功能代码块;开放:对扩展开放。如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3......f100的内部进行代码修改。遗憾的是,工程师没有漂亮的女朋友,所以很快也被开除了。

def login():
 print("认证成功!")
def f1():
 login()
 print("业务部门1数据接口......")
def f2():
 login()
 print("业务部门2数据接口......")
def f3():
 login()
 print("业务部门3数据接口......")
def f100():
 login()
 print("业务部门100数据接口......")
#各部门分别调用
f1()
f2()
f3()
f100()


第五回:已经没有时间让主管找别人来干这活了,他决定亲自上阵,并且打算在函数执行后再增加个日志功能。主管是这么想的:不会装饰器的主管不是好码农!要不为啥我能当主管,你只能被管呢?嘿嘿。他的代码如下:

#/usr/bin/env python
#coding:utf-8
def outer(func):
 def inner():
 print("认证成功!")
 result = func()
 print("日志添加成功")
 return result
 return inner
@outer
def f1():
 print("业务部门1数据接口......")
@outer
def f2():
 print("业务部门2数据接口......")
@outer
def f3():
 print("业务部门3数据接口......")
@outer
def f100():
 print("业务部门100数据接口......")
#各部门分别调用
f1()
f2()
f3()
f100()


对于上述代码,也是仅需对基础平台的代码进行拓展,就可以实现在其他部门调用函数 f1 f2 f3 f100 之前都进行认证操作,在操作结束后保存日志,并且其他业务部门无需他们自己的代码做任何修改,调用方式也不用变。“主管”写完代码后,觉得独乐了不如众乐乐,打算显摆一下,于是写了篇博客将过程进行了详细的说明。
三、装饰器的内部原理、
 下面我们以f1函数为例进行说明:

def outer(func):
 def inner():
 print("认证成功!")
 result = func()
 print("日志添加成功")
 return result
 return inner
@outer
def f1():
 print("业务部门1数据接口......")


运用我们在第一部分介绍的知识来分析一下上面这段代码:
程序开始运行,从上往下编译,读到def outer(func):的时候,发现这是个“一等公民”->函数,于是把函数体加载到内存里,然后过。
读到@outer的时候,程序被@这个语法糖吸引住了,知道这是个装饰器,按规矩要立即执行的,于是程序开始运行@后面那个名字outer所定义的函数。(相信没有人会愚蠢的将@outer写到别的位置,它只能放在被装饰的函数的上方最近处,不要空行。)
程序返回到outer函数,开始执行装饰器的语法规则,这部分规则是定死的,是python的“法律”,不要问为什么。规则是:被装饰的函数的名字会被当作参数传递给装饰函数。装饰函数执行它自己内部的代码后,会将它的返回值赋值给被装饰的函数。 
如下图所示:
 答:请注意,@outer这句代码在程序执行到这里的时候就会自动执行outer函数内部的代码,如果不封装一下,在业务部门还未进行调用的时候,就执行了些什么,这和初衷有点不符。当然,如果你对这个有需求也不是不行。请看下面的例子,它只有一层函数。

def outer(func):
 print("认证成功!")
 result = func()
 print("日志添加成功")
 return result
@outer
def f1():
 print("业务部门1数据接口......")
# 业务部门并没有开始执行f1函数
执行结果:
认证成功!
业务部门1数据接口......
日志添加成功


看到没?我只是定义好了函数,业务部门还没有调用f1函数呢,程序就把工作全做了。这就是封装一层函数的原因。
四、装饰器的参数传递
细心的朋友可能已经发现了,上面的例子中,f1函数没有参数,在实际情况中肯定会需要参数的,那参数怎么传递的呢?
 一个参数的情况:

def outer(func):
 def inner(username):
 print("认证成功!")
 result = func(username)
 print("日志添加成功")
 return result
 return inner
@outer
def f1(name):
print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack")

在inner函数的定义部分也加上一个参数,调用func函数的时候传递这个参数,很好理解吧?可问题又来了,那么另外一个部门调用的f2有2个参数呢?f3有3个参数呢?你怎么传递?
很简单,我们有*args和**kwargs嘛!号称“万能参数”!简单修改一下上面的代码:

def outer(func):
 def inner(*args,**kwargs):
 print("认证成功!")
 result = func(*args,**kwargs)
 print("日志添加成功")
 return result
 return inner
@outer
def f1(name,age):
 print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack",18)

五、更进一步的思考
 一个函数可以被多个函数装饰吗?可以的!看下面的例子!  

def outer1(func):
 def inner(*args,**kwargs):
 print("认证成功!")
 result = func(*args,**kwargs)
 print("日志添加成功")
 return result
 return inner
def outer2(func):
 def inner(*args,**kwargs):
 print("一条欢迎信息。。。")
 result = func(*args,**kwargs)
 print("一条欢送信息。。。")
 return result
 return inner
 @outer1
@outer2
def f1(name,age):
 print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack",18) 
执行结果:
认证成功!
一条欢迎信息。。。
jack 正在连接业务部门1数据接口......
一条欢送信息。。。
日志添加成功

更进一步的,装饰器自己可以有参数吗?可以的!看下面的例子:

# 认证函数
def auth(request,kargs):
 print("认证成功!")
# 日志函数
def log(request,kargs):
 print("日志添加成功")
# 装饰器函数。接收两个参数,这两个参数应该是某个函数的名字。
def Filter(auth_func,log_func):
 # 第一层封装,f1函数实际上被传递给了main_fuc这个参数
 def outer(main_func):
 # 第二层封装,auth和log函数的参数值被传递到了这里
 def wrapper(request,kargs):
 # 下面代码的判断逻辑不重要,重要的是参数的引用和返回值
 before_result = auth(request,kargs)
 if(before_result != None):
 return before_result;
 main_result = main_func(request,kargs)
 if(main_result != None):
 return main_result;
 after_result = log(request,kargs)
 if(after_result != None):
 return after_result;
 return wrapper
 return outer
# 注意了,这里的装饰器函数有参数哦,它的意思是先执行filter函数
# 然后将filter函数的返回值作为装饰器函数的名字返回到这里,所以,
# 其实这里,Filter(auth,log) = outer , @Filter(auth,log) = @outer
@Filter(auth,log)
def f1(name,age):
 print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack",18)
运行结果:
认证成功!
jack 正在连接业务部门1数据接口......
日志添加成功

又绕晕了?其实你可以这么理解,先执行Filter函数,获得它的返回值outer,再执行@outer装饰器语法。

以上就是详解Python装饰器的介绍的详细内容,更多请关注 第一PHP社区 其它相关文章!


推荐阅读
  • 本文探讨了如何使用Scrapy框架构建高效的数据采集系统,以及如何通过异步处理技术提升数据存储的效率。同时,文章还介绍了针对不同网站采用的不同采集策略。 ... [详细]
  • Python网络编程:深入探讨TCP粘包问题及解决方案
    本文详细探讨了TCP协议下的粘包现象及其产生的原因,并提供了通过自定义报头解决粘包问题的具体实现方案。同时,对比了TCP与UDP协议在数据传输上的不同特性。 ... [详细]
  • 本文介绍了使用Python和C语言编写程序来计算一个给定数值的平方根的方法。通过迭代算法,我们能够精确地得到所需的结果。 ... [详细]
  • 本文分享了作者在使用LaTeX过程中的几点心得,涵盖了从文档编辑、代码高亮、图形绘制到3D模型展示等多个方面的内容。适合希望深入了解LaTeX高级功能的用户。 ... [详细]
  • 本文探讨了使用Python实现监控信息收集的方法,涵盖从基础的日志记录到复杂的系统运维解决方案,旨在帮助开发者和运维人员提升工作效率。 ... [详细]
  • 解析Java虚拟机HotSpot中的GC算法实现
    本文探讨了Java虚拟机(JVM)中HotSpot实现的垃圾回收(GC)算法,重点介绍了根节点枚举、安全点及安全区域的概念和技术细节,以及这些机制如何影响GC的效率和准确性。 ... [详细]
  • 一家位于长沙的知名网络安全企业,现面向全国诚聘高级后端开发工程师,特别欢迎具有一线城市经验的技术精英回归故乡,共创辉煌。 ... [详细]
  • 随着Linux操作系统的广泛使用,确保用户账户及系统安全变得尤为重要。用户密码的复杂性直接关系到系统的整体安全性。本文将详细介绍如何在CentOS服务器上自定义密码规则,以增强系统的安全性。 ... [详细]
  • 本文详细介绍如何安装和配置DedeCMS的移动端站点,包括新版本安装、老版本升级、模板适配以及必要的代码修改,以确保移动站点的正常运行。 ... [详细]
  • JavaScript 跨域解决方案详解
    本文详细介绍了JavaScript在不同域之间进行数据传输或通信的技术,包括使用JSONP、修改document.domain、利用window.name以及HTML5的postMessage方法等跨域解决方案。 ... [详细]
  • 最适合初学者的编程语言
    本文探讨了适合编程新手的最佳语言选择,包括Python、JavaScript等易于上手且功能强大的语言,以及如何通过有效的学习方法提高编程技能。 ... [详细]
  • 在测试软件或进行系统维护时,有时会遇到电脑蓝屏的情况,即便使用了沙盒环境也无法完全避免。本文将详细介绍常见的蓝屏错误代码及其解决方案,帮助用户快速定位并解决问题。 ... [详细]
  • 搭建个人博客:WordPress安装详解
    计划建立个人博客来分享生活与工作的见解和经验,选择WordPress是因为它专为博客设计,功能强大且易于使用。 ... [详细]
  • 本文介绍了如何通过安装 sqlacodegen 和 pymysql 来根据现有的 MySQL 数据库自动生成 ORM 的模型文件(model.py)。此方法适用于需要快速搭建项目模型层的情况。 ... [详细]
  • 本文介绍了一个来自AIZU ONLINE JUDGE平台的问题,即清洁机器人2.0。该问题来源于某次编程竞赛,涉及复杂的算法逻辑与实现技巧。 ... [详细]
author-avatar
舅舅家123_204
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有