详解python的装饰器decorator
装饰器本质上是一个python函数,它可以让其它函数在不需要任何代码改动的情况下增加额外的功能。
装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志,性能测试,事务处理,缓存,权限校验等场景。
装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能无关的雷同代码并继续重用。
原文:http://www.cnblogs.com/cicaday/p/python-decorator.html
概括的讲:装饰器的作用就是为已经存在的函数或对象添加额外的功能。
怎么写一个装饰器?
在python2.4以前,为一个函数添加额外功能的写法是:
- def debug(func):
- def wrapper():
- print('[DEBUG]: enter {}()'.format(func.__name__))
- return func()
- return wrapper()
- def say_hello():
- print('hello')
- say_hello = debug(say_hello)
- '''
- 输出:
- [DEBUG]: enter say_hello()
- hello
- '''
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
- def debug(func):
- def wrapper():
- print('[DEBUG]: enter {}()'.format(func.__name__))
- return func()
- return wrapper()
- @debug
- def say_hello():
- print('hello')
这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper
接受和原函数一样的参数,比如:
- def debug(func):
- def wrapper(something): # 指定一模一样的参数
- print "[DEBUG]: enter {}()".format(func.__name__)
- return func(something)
- return wrapper # 返回包装过函数
- @debug
- def say(something):
- print "hello {}!".format(something)
这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args
和关键字参数**kwargs
,有了这两个参数,装饰器就可以用于任意目标函数了。
- def debug(func):
- def wrapper(*args, **kwargs): # 指定宇宙无敌参数
- print "[DEBUG]: enter {}()".format(func.__name__)
- print 'Prepare and say...',
- return func(*args, **kwargs)
- return wrapper # 返回
- @debug
- def say(something):
- print "hello {}!".format(something)
至此,你已完全掌握初级的装饰器写法。
高级一点的装饰器
带参数的装饰器和类装饰器属于进阶的内容。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
- def logging(level):
- def wrapper(func):
- def inner_wrapper(*args, **kwargs):
- print('[{level}]:enter function {func}()'.format(level=level,func=func.__name__))
- return func(*args, **kwargs)
- return inner_wrapper
- return wrapper
- #@logging(level='INFO')
- def say(something):
- print('say {}'.format(something))
- # 如果没有使用@语法,等同于
- # say = logging(level='INFO')(say)
- @logging(level='DEBUG')
- def do(something):
- print('do {}...'.format(something))
- if __name__ == '__main__':
- say('hello')
- do("my work")
基于类实现的装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()
方法,那么这个对象就是callable的。
- class Test():
- def __call__(self):
- print 'call me!'
- t = Test()
- t() # call me
像__call__
这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()
接受一个函数,然后重载__call__()
并返回一个函数,也可以达到装饰器函数的效果。
- class logging(object):
- def __init__(self,func):
- self.func = func
- def __call__(self, *args, **kwargs):
- print("[DEBUG]: enter function {func}()".format(func=self.func.__name__))
- return self.func(*args, **kwargs)
- @logging
- def say(something):
- print('say {}'.format(something))
- say('hihao')
带参数的类装饰器
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__
方法是就需要接受一个函数并返回一个函数。
- class logging(object):
- def __init__(self,level='INFO'):
- self.level = level
- def __call__(self, func):
- def wrapper(*args, **kwargs):
- print("[{level}]: enter function {func}()".format(
- level=self.level,
- func=func.__name__))
- func(*args, **kwargs)
- return wrapper
- @logging(level='INFO')
- def say(something):
- print("say {}!".format(something))
- say('nihao')
内置的装饰器
内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些。
@property
在了解这个装饰器前,你需要知道在不使用装饰器时怎么写一个属性。
- def getx(self):
- return self._x
- def setx(self, value):
- self._x = value
- def delx(self):
- del self._x
- # create a property
- x = property(getx, setx, delx, "I am doc for x property")
以上就是一个Python属性的标准写法,其实和Java挺像的,但是太罗嗦。有了@语法糖,能达到一样的效果但看起来更简单。
- @property
- def x(self): ...
- # 等同于
- def x(self): ...
- x = property(x)
属性有三个装饰器:setter
, getter
, deleter
,都是在property()
的基础上做了一些封装,因为setter
和deleter
是property()
的第二和第三个参数,不能直接套用@语法。getter
装饰器和不带getter
的属性装饰器效果是一样的,估计只是为了凑数,本身没有任何存在的意义。经过@property
装饰过的函数返回的不再是一个函数,而是一个property
对象。
@staticmethod, @classmethod
有了@property
装饰器的了解,这两个装饰器的原理是差不多的。@staticmethod
返回的是一个staticmethod
类对象,而@classmethod
返回的是一个classmethod
类对象。他们都是调用的是各自的__init__()
构造函数。
- class classmethod(object):
- """
- classmethod(function) -> method
- """
- def __init__(self, function): # for @classmethod decorator
- pass
- # ...
- class staticmethod(object):
- """
- staticmethod(function) -> method
- """
- def __init__(self, function): # for @staticmethod decorator
- pass
- # ...
- class Foo(object):
- @staticmethod
- def bar():
- pass
- # 等同于 bar = staticmethod(bar)
至此,我们上文提到的装饰器接口定义可以更加明确一些,装饰器必须接受一个callable对象,其实它并不关心你返回什么,可以是另外一个callable对象(大部分情况),也可以是其他类对象,比如property。
详解python的装饰器decorator的更多相关文章
- 【转】详解Python的装饰器
原文链接:http://python.jobbole.com/86717/ Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里. 为什么需要装饰器 我们假设你的程序实现 ...
- 详解Python的装饰器
Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里. 为什么需要装饰器 我们假设你的程序实现了say_hello()和say_goodbye()两个函数. def sa ...
- python函数编程-装饰器decorator
函数是个对象,并且可以赋值给一个变量,通过变量也能调用该函数: >>> def now(): ... print('2017-12-28') ... >>> l = ...
- 第7.27节 Python案例详解: @property装饰器定义属性访问方法getter、setter、deleter
上节详细介绍了利用@property装饰器定义属性的语法,本节通过具体案例来进一步说明. 一. 案例说明 本节的案例是定义Rectangle(长方形)类,为了说明问题,除构造函数外,其他方法都只 ...
- python 深入浅出装饰器(decorator)--举的例子关于星级争霸2(starcraft2)
其实早就想写一篇深入浅出装饰器的文章,苦于一直没有找到很好的例子描述,自己除了在写api参数检测和日志打印的时候用到以外,其他地方也没有什么重度使用所以一直没有写. 我不会讲解装饰器的理论,还有各种基 ...
- 关于Python的装饰器 decorator
装饰器的原理:其实就是高阶函数,接收原函数以在之前之后进行操作. 语法格式是固定的:先定义一个函数,再使用@语法调用该函数. 例子一: import functools # 定义装饰器,固定格式 de ...
- Python学习——装饰器/decorator/语法糖
装饰器 定义:本质是函数,为其他函数添加附加的功能. 原则:1.不能修改原函数的源代码 2.不能修改被原函数的调用方式 重点理解: 1.函数即“变量” 2.高阶函数:返回值中包含函数名 3.嵌套函数 ...
- 详解python包管理器pip安装
pip对于使用python的朋友并不陌生,当你想安装python模块的时候一定会首先想到它.pip 是一个安装和管理 Python 包的工具 , 是 easy_install 的一个替换品. 今天来说 ...
- Python多重装饰器
多重装饰器,即多个装饰器修饰同一个对象[实际上并非完全如此,且看下文详解] 1.装饰器无参数: >>> def first(func): print '%s() was post t ...
随机推荐
- KMP 和 扩展KMP
KMP:在主串S中找子串T的位置KMP算法的时间复杂度O(|S|+|T|). #define maxn 1000 char s[maxn],t[maxn];//s为主串,t为子串 int net[ma ...
- 流程控制-物流费用计算(嵌套if)
题目描述 快递公司规定,如果物品体积超过2.5立方米,不允许快递.如果重量超过40kg,不允许快递.快递收费价格为: 小于等于1kg,一口价10块钱: 大于1kg,小于等于5kg,10块钱的基础上,每 ...
- Java类成员之方法
方法含义: 1. 方法是类或对象行为特征的抽象,用来完成某个功能操作. 2.在某些语言中也称为函数或过程. 3.将功能封装为方法的目的是简化代码,可以实现代码重用. 4.在Java里的方法不能独立存在 ...
- dubbo配置文件解读(1)
详细的Dubbo配置也可以参考:https://blog.csdn.net/abcde474524573/article/details/53026110 (1)<dubbo:service/& ...
- crawler碎碎念6 豆瓣爬取操作之获取数据
import requests from lxml import etree s = requests.Session() for id in range(0,251,25): url ='https ...
- sqlalchemy 单表增删改查
1.连接数据库,并创建session from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine engi ...
- Java入门 - 语言基础 - 11.switch_case
原文地址:http://www.work100.net/training/java-switch-case.html 更多教程:光束云 - 免费课程 switch_case 序号 文内章节 视频 1 ...
- js多图预览及上传功能
<%-- Created by IntelliJ IDEA. User: Old Zhang Date: 2018/12/27 Time: 11:17 To change this templa ...
- [bzoj4447] [loj#2010] [Scoi2015] 小凸解密码
Description 小凸得到了一个密码盘,密码盘被等分成 \(N\) 个扇形,每个扇形上有一个数字(0-9),和一个符号("+"或"*") 密码盘解密的方法 ...
- Go的切片:长度和容量
虽然说 Go 的语法在很大程度上和 PHP 很像,但 PHP 中却是没有"切片"这个概念的,在学习的过程中也遇到了一些困惑,遂做此笔记. 困惑1:使用 append 函数为切片追加 ...