python之装饰器详解

这几天翻看python语法,看到装饰器这里着实卡了一阵,最初认为也就是个函数指针的用法,但仔细研究后发现,不止这么简单。

首先很多资料将装饰器定义为AOP的范畴,也就是Aspect Oriented Programming面向切面编程的概念,不懂AOP不要紧,只要有函数指针的概念,又有嵌套函数的基础知识,看懂此文一点压力都没有。

先说说为什么要有装饰器这么个东西存在吧,这是一种设计模式,较为经典的有插入日志、性能测试、事务处理等等。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

关于嵌套函数,C/C++程序员可能会觉得一头雾水,因为谭老爷子的书明确指出函数不可以嵌套定义,在C++ primer也仅仅有嵌套类的讲解,并不存在嵌套函数一说,但是在一些高级语言中,比如Object C,swift都存在嵌套函数主题,感兴趣的同学可以去翻一下,尤其是当下热门swift,嵌套函数和闭包密不可分,可以方便的实现很多逻辑,比如返回一个函数给调用者。

书归正传,所谓装饰器,顾名思义,就是用来装饰的神器,你有一个函数A(),觉得它不够漂亮,但是又不想重写它,咋办呢?装饰器(Decorator)这时候就派上用场了。

为了方便大家的理解,我先写一个简单的嵌套函数:

def funcA():print("i am funcA()!")def funcB():print("i am funcB()")return funcB
callSth=funcA()//调用funcA得到返回的funcB
callSth()//相当于调用funcB
 

值得说明一点,在python里只要你不怕蛋疼,你可以任意嵌套,哪怕你嵌套个百十层直到栈溢出都没问题。

如下:

def ppp():print("return vlaue")
def first():a=10// a被嵌套内的函数捕获!print("the first layer")def second():print("the second layer %d" % a)def third():print("the third layer %d " % a)def forth():print("the forth layer")return ppp()return forthreturn thirdreturn second aaa=first()()()//得到forth()
aaa()

 关于嵌套函数是这样执行的,先执行最外层的first(),返回second然后发现后面跟着(),那继续返回third,以此类推,聪明如你,肯定明白了。嵌套函数就这样,简单到爆。如果到这里诸君理解了,装饰器基本上也就理解了。

下面正式开始装饰器的概念(开启摘抄模式):

装饰器的定义很是抽象,我们来看一个小例子。

def foo():print 'in foo()'
foo()

 

这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:

import time
def foo():start = time.clock()print 'in foo()'end = time.clock()print 'used:', end - startfoo()

 

很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。

怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?

 

以不变应万变:还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!

import timedef foo():print 'in foo()'def timeit(func):start = time.clock()func()end =time.clock()print 'used:', end - starttimeit(foo)

看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。

最大限度地少改动!

既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!

一个真正意义上的装饰器诞生了:

#-*- coding: UTF-8 -*-
import timedef foo():print 'in foo()'# 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
def timeit(func):# 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装def wrapper():start = time.clock()func()end =time.clock()print 'used:', end - start# 将包装后的函数返回return wrapperfoo = timeit(foo)
foo()

 

样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。

 

语法糖

上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。

import timedef timeit(func):def wrapper():start = time.clock()func()end =time.clock()print 'used:', end - startreturn wrapper@timeit// syntax sugar 是也,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。
def foo():print 'in foo()'foo()

 给出原文链接:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

相关资料:http://blog.csdn.net/thy38/article/details/4471421

http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html

 

转载于:https://www.cnblogs.com/wangjunyan/p/5046223.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/419198.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“睡服”面试官系列第十六篇之Symbol(建议收藏学习)

目录 1. 概述 2. 作为属性名的 Symbol 3. 实例:消除魔术字符串 4. 属性名的遍历 5. Symbol.for(),Symbol.keyFor() 6. 实例:模块的 Singleton 模式 7. 内置的 Symbol 值 7.1Symbol.hasInstance 7.2Symbol.isConcatSpreadable 7.3Sy…

POST请求传入中文参数,接收端乱码

问题描述&#xff1a;通过post请求调试短信接口发送出去后&#xff0c;客户端无法收到短信&#xff0c;中文内容乱码 追踪过程&#xff1a; 接口采用post请求进行&#xff0c;无法收取短信的接口代码如下&#xff1a; public static String sendPost(String url, Map<Stri…

LeetCode - Container With Most Water

题目&#xff1a; Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms…

TCP/IP协议模型

1. 数据链路层 作用(1) 实现网卡接口的网络驱动&#xff0c;以处理数据在以太网线等物理媒介上的传输   (2) 网络驱动程序隐藏了不同物理网络的不同电气特性&#xff0c;为上层协议提供一个统一的接口 应用ARP和RARP(Reverse Address Resolve Protocol)即逆地址解析协议&am…

【转】 Pro Android学习笔记(二九):用户界面和控制(17):include和merge

目录(?)[-] xml控件代码重用includexml控件代码重用merge横屏和竖屏landsacpe portraitxml控件代码重用&#xff1a;include 如果我们定义一个控件&#xff0c;需要在不同的layout中重复使用&#xff0c;或者在同一个layout中重复使用&#xff0c;可以采用include的方式。例如…

管理者一定会遇到的那些事

——极客时间——沈剑老师分享有感

Git使用攻略

Git使用攻略 merge&#xff0c;将develop合并到master# 切换到Master分支git checkout master# 将Develop分支合并到master git merge --no-ff develop 切换分支git checkout master 检出代码git checkout master 创建分支git branch newBranch查看本地分支git branch查看远程分…

Java停止线程的方式

1、使用中断标志位 public class StopThreadTest extends Thread {private boolean exit false;Overridepublic void run() {while (!exit) {try {System.out.println("i am running,please wait a moment");Thread.sleep(500);} catch (InterruptedException e) {…

Vim文本编辑器 指令簿(二)

常常处理文本以及常常须要写代码的人&#xff0c;都会有自己比較常常使用的编辑器&#xff0c;本人喜欢用Vim。理由就是Vim编辑器灵活&#xff0c;而且能够达到纯键盘操作&#xff0c;使用纯熟情况下&#xff0c;根本不须要鼠标操作。听起来是不是非常酷的&#xff1f;只是别高…

读《数学之美》

数学之美 数学的发展实际上是不断的抽象和概括的过程 目录 数学之美 第一章 第二章&#xff08;从规则到统计&#xff09; 第三章 统计语言模型 第四章 分词 第五章 隐马尔科夫模型 第六章 信息的度量和作用 第七章 贾里尼克和和现代语言处理 第八章 简单之美——布…

每天一个JavaScript实例-动态省份选择城市

<!DOCTYPE html> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8" /> <title>每天一个JavaScript实例-动态省份选择城市</title> <script> var citystore new Array(); citys…

阅读源码那些事

方法 看spring的源码的时候如果我们一直追究所有的细节那会让我们会越陷越深&#xff0c;掉入细节的无底洞&#xff0c;稍不留神脑回路跟不上就会蒙圈。 因此&#xff0c;我们要学会找源码中的关键部分看&#xff0c;弄懂主要流程和本次看源码的目的的那部分就行。 等我们对…

Advanced C++ --- const function

上一篇介绍了const修饰的变量或者指针的含义&#xff0c;这篇我们介绍const修饰的函数以及函数参数含义。 首先我们看一个例子 class Dog{int age;string name; public:Dog(){age 3;name "dummy";}void setAge(const int &a){age a;a;} };int main(){Dog d;in…

slf4j、log4j日志报错排查

1、WARN Please initialize the log4j system properly 解法&#xff1a;只要在 src文件目录下建立配置文件log4j.properties即可 2、SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" 解法&#xff1a;这是因为sl4j和log4j的不兼容导致的&#…