- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章详解Python装饰器由浅入深由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码。装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们以装饰函数为例子介绍其用法。要理解在Python中装饰器的原理,需要一步一步来。本文尽量描述得浅显易懂,从最基础的内容讲起.
(注:以下使用Python3.5.1环境) 。
1、Python的函数相关基础 。
第一,必须强调的是python是从上往下顺序执行的,而且碰到函数的定义代码块是不会立即执行它的,只有等到该函数被调用时,才会执行其内部的代码块.
1
2
3
4
|
def
foo():
print
(
"foo函数被运行了!"
)
如果就这么样,foo里的语句是不会被执行的。
程序只是简单的将定义代码块读入内存中。
|
再看看,顺序执行的例子:
1
2
3
4
5
6
7
|
def
foo():
print
(
"我是上面的函数定义!"
)
def
foo():
print
(
"我是下面的函数定义!"
)
foo()
运行结果:
我是下面的函数定义
|
可见,因为顺序执行的原因,下面的foo将上面的foo覆盖了。因此,在Python中代码的放置位置是有要求的,不能随意摆放,函数体要放在被调用的语句之前.
其次,我们还要先搞清楚几样东西:函数名、函数体、返回值,函数的内存地址、函数名加括号、函数名被当作参数、函数名加括号被当作参数、返回函数名、返回函数名加括号。对于如下的函数:
1
2
3
4
|
def
foo():
print
(
"让我们干点啥!"
)
return
"ok"
foo()
|
函数名: foo 。
函数体: 第1-3行 。
返回值: 字符串“ok” 如果不显式给出return的对象,那么默认返回None 。
函数的内存地址: 当函数体被读进内存后的保存位置,它由标识符即函数名foo引用, 也就是说foo指向的是函数体在内存内的保存位置.
函数名加括号: 例如foo(),函数的调用方法,只有见到这个括号,程序会根据 函数名从内存中找到函数体,然后执行它 。
再看下面这个例子:
1
2
3
4
5
6
7
8
|
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,有没有括号是两个截然不同的意思! 。
2、装饰器的使用场景 。
装饰器通常用于在不改变原有函数代码和功能的情况下,为其添加额外的功能。比如在原函数执行前先执行点什么,在执行后执行点什么.
让我们通过一个例子来看看,装饰器的使用场景和体现的设计模式。(抱歉的是我设计不出更好的场景,只能引用武大神的案例加以演绎) 。
有一个大公司,下属的基础平台部负责内部应用程序及API的开发,有上百个业务部门负责不同的业务,他们各自调用基础平台部提供的不同函数处理自己的业务,情况如下: 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 基础平台部门开发了上百个函数
def
f1():
print
(
"业务部门1数据接口......"
)
def
f2():
print
(
"业务部门2数据接口......"
)
def
f3():
print
(
"业务部门3数据接口......"
)
def
f100():
print
(
"业务部门100数据接口......"
)
#各部门分别调用
f1()
f2()
f3()
f100()
|
由于公司在创业初期,基础平台部开发这些函数时,由于各种原因,比如时间,比如考虑不周等等,没有为函数调用进行安全认证。现在,平台部主管决定弥补这个缺陷,于是:
第一回,主管叫来了一个运维工程师,工程师跑上跑下逐个部门进行通知,让他们在代码里加上认证功能,然而,当天他被开除了.
第二回:主管又叫来了一个运维工程师,工程师用shell写了个复杂的脚本,勉强实现了功能。但他很快就回去接着做运维了,不会开发的运维不是好运维.... 。
第三回:主管叫来了一个python自动化开发工程师,哥们是这么干的:只对基础平台的代码进行重构,让N个业务部门无需做任何修改。这哥们很快也被开了,连运维也没得做。 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def
f1():
#加入认证程序代码
print
(
"业务部门1数据接口......"
)
def
f2():
# 加入认证程序代码
print
(
"业务部门2数据接口......"
)
def
f3():
# 加入认证程序代码
print
(
"业务部门3数据接口......"
)
def
f100():
#加入认证程序代码
print
(
"业务部门100数据接口......"
)
#各部门分别调用
f1()
f2()
f3()
f100()
|
第四回:主管又换了个 工程师,他是这么干的:定义个认证函数,原来其他的函数调用它,代码如下框。但是,主管依然不满意,不过这一次他解释了为什么。主管说:写代码要遵循开放封闭原则,虽然在这个原则主要是针对面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码内部不允许被修改,但外部可以被扩展,即:封闭:已实现的功能代码块;开放:对扩展开放。如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3......f100的内部进行代码修改。遗憾的是,工程师没有漂亮的女朋友,所以很快也被开除了.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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()
|
第五回:已经没有时间让主管找别人来干这活了,他决定亲自上阵,并且打算在函数执行后再增加个日志功能。主管是这么想的:不会装饰器的主管不是好码农!要不为啥我能当主管,你只能被管呢?嘿嘿。他的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#/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 之前都进行认证操作,在操作结束后保存日志,并且其他业务部门无需他们自己的代码做任何修改,调用方式也不用变。“主管”写完代码后,觉得独乐了不如众乐乐,打算显摆一下,于是写了篇博客将过程进行了详细的说明.
3、装饰器的内部原理、 。
下面我们以f1函数为例进行说明:
1
2
3
4
5
6
7
8
9
10
|
def
outer(func):
def
inner():
print
(
"认证成功!"
)
result
=
func()
print
(
"日志添加成功"
)
return
result
return
inner
@outer
def
f1():
print
(
"业务部门1数据接口......"
)
|
运用我们在第一部分介绍的知识来分析一下上面这段代码:
如下图所示:
这里面需要注意的是:
如果你对第一部分函数的基础知识有清晰的了解,那么上面的内容你应该很容易理解.
4. 程序开始执行outer函数内部的内容,一开始它又碰到了一个函数,很绕是吧?当然,你可以在 inner函数前后安排点别的代码,但它们不是重点,而且有点小麻烦,下面会解释。inner函数定义块被程序观察到后不会立刻执行,而是读入内存中(这是潜规则).
5. 再往下,碰到return inner,返回值是个函数名,并且这个函数名会被赋值给f1这个被装饰的函数,也就是f1 = inner。根据前面的知识,我们知道,此时f1函数被新的函数inner覆盖了(实际上是f1这个函数名更改成指向inner这个函数名指向的函数体内存地址,f1不再指向它原来的函数体的内存地址),再往后调用f1的时候将执行inner函数内的代码,而不是先前的函数体。那么先前的函数体去哪了?还记得我们将f1当做参数传递给func这个形参么?func这个变量保存了老的函数在内存中的地址,通过它就可以执行 老的函数体,你能在inner函数里看到result = func()这句代码,它就是这么干的! 。
6.接下来,还没有结束。当业务部门,依然通过f1()的方式调用f1函数时,执行的就不再是老的f1函数的代码,而是inner函数的代码。在本例中,它首先会打印个“认证成功”的提示,很显然你可以换成任意的代码,这只是个示例;然后,它会执行func函数并将返回值赋值个变量result,这个func函数就是老的f1函数;接着,它又打印了“日志保存”的提示,这也只是个示例,可以换成任何你想要的;最后返回result这个变量。我们在业务部门的代码上可以用 r = f1()的方式接受result的值.
7.以上流程走完后,你应该看出来了,在没有对业务部门的代码和接口调用方式做任何修改的同时,也没有对基础平台部原有的代码做内部修改,仅仅是添加了一个装饰函数,就实现了我们的需求,在函数调用前先认证,调用后写入日志。这就是装饰器的最大作用.
问题:那么为什么我们要搞一个outer函数一个inner函数这么复杂呢?一层函数不行吗?
答:请注意,@outer这句代码在程序执行到这里的时候就会自动执行outer函数内部的代码,如果不封装一下,在业务部门还未进行调用的时候,就执行了些什么,这和初衷有点不符。当然,如果你对这个有需求也不是不行。请看下面的例子,它只有一层函数.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def
outer(func):
print
(
"认证成功!"
)
result
=
func()
print
(
"日志添加成功"
)
return
result
@outer
def
f1():
print
(
"业务部门1数据接口......"
)
# 业务部门并没有开始执行f1函数
执行结果:
认证成功!
业务部门
1
数据接口......
日志添加成功
|
看到没?我只是定义好了函数,业务部门还没有调用f1函数呢,程序就把工作全做了。这就是封装一层函数的原因.
4、装饰器的参数传递 。
细心的朋友可能已经发现了,上面的例子中,f1函数没有参数,在实际情况中肯定会需要参数的,那参数怎么传递的呢?
一个参数的情况:
1
2
3
4
5
6
7
8
9
10
11
12
|
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嘛!号称“万能参数”!简单修改一下上面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
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
)
|
5、更进一步的思考 。
一个函数可以被多个函数装饰吗?可以的!看下面的例子! 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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数据接口......
一条欢送信息。。。
日志添加成功
|
更进一步的,装饰器自己可以有参数吗?可以的!看下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
# 认证函数
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装饰器语法.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我! 。
原文链接:http://www.cnblogs.com/feixuelove1009/p/5541632.html 。
最后此篇关于详解Python装饰器由浅入深的文章就讲到这里了,如果你想了解更多关于详解Python装饰器由浅入深的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有这些 ViewModel:RecordViewModel、ComponentViewModel,其中 RecordViewModel 本质上是几个 ComponentViewModel 的容器。
我正在尝试为我的 Controller 设置一个装饰器。我的目的是在我的应用程序中的所有 Controller 中引入一些常见的行为。 我已将其配置为在 Angular 1.2.x 中工作,但从 1.
我想用角上的时间戳装饰我生成的所有 JFreeCharts。 JFreeChart 框架中是否有一种方法可以在生成图表后在图像上绘制? 编辑:请注意,这些图表是在后台线程中生成并通过 servlet
在grails应用程序中,我想用自定义数据装饰每个日志。当前的需要只是在日志消息前添加当前用户名 我对如何解决此问题有一些想法: -Adding custom field to log4j patte
我有一个form-el,它只是一个容器,必须将所有子元素包装在具有特定类的div 中。我希望允许 from-el 来包装它们,而不是在每个表单元素中重复这个 div 。我可以循环所有元素并将它们包装在
我遵循以下约定来装饰 Python 类中的某些方法。我想知道是否有一些更好的方法可以做到同样的事情。我的方法看起来当然不太好;对原始成员函数的调用看起来一点也不直观。 from threading i
我的 DTO 看起来像这样 public class SomeDTO { public string last_name{ get; set; } public string ac
这可能是一个远景,但 .NET 的灵 active 一直让我惊叹不已,所以开始吧。 我正在开发一个 MVC 应用程序,它需要在一组程序集中搜索派生自公共(public)基类的类类型。 即我有几个程序集
在我正在进行的项目中,我正在按照项目负责人的要求实现开发人员通知系统。它的工作方式是,如果发生前端错误,开发团队会收到一封错误电子邮件。 但是,在我当前的实现中,我似乎有以下循环依赖: $rootSc
我需要一些关于如何取消装饰和装饰 JInternalFrame 的帮助。我的框架类是这样的: package com; import java.awt.BorderLayout; import jav
假设我们有可能需要长时间运行的任务: public class LongRunningTask { public ReturnType doSomething() { ...
我正在尝试创建一个装饰器方法,它将一些默认的生命周期方法添加到 react 组件中。我的目标是向组件中添加一些默认功能,例如,所有组件都应该能够在 componentWillMount 上执行特定操作
我正在尝试将 DBUS 的异步方法调用与 Twisted 的 Deferred 相结合,但我在调整常用的 DBUS 服务方法装饰器来执行此操作时遇到了麻烦。 要使用 DBUS 异步回调方法,您需要:
是否可以设置表格中一行的背景颜色?当条件适用时,我需要突出显示一行。效果为 ...我可以在其中指定“字体”属性。 (我需要突出显示整行)。 最佳答案 您必须子类化 qooxdoo 默认行渲染器才能做到
我正在开发一个具有不同视角的基于 Java Swing 的应用程序。对于“主菜单”视角,我不希望装饰窗口 (JFrame),而在其他视角中,我确实希望装饰窗口。换句话说,我需要动态更 retrofit
我想做一些类似下面代码所示的事情: class foo { private: std::fstream* m_stream; public: foo(std::fstream* str
我的数据源提供了一个 ObservableList ,但是对于我的 ListView,我需要一个 ObservableList . A Warning基本上只是字符串的装饰器,添加一个 boolean
我一直在纠结于装饰+接口(interface)。假设我有以下“行为”接口(interface): interface IFlyable { void Fly();} interface ISwimma
有没有人为 iOS 6 UICollectionView 实现过装饰 View ?不可能 查找有关在网络上实现装饰 View 的任何教程。基本上在我的应用程序中,我有多个部分,我只想在每个部分后面显示
我有一个简单的 Controller ,例如: function MyController($scope, $http) { ... $http.post(url).success(f
我是一名优秀的程序员,十分优秀!