引言

装饰器简单来说是我们向一个现有的已经存在的函数或对象添加新的功能,同时呢我们又不用改变他现有的结构。

为什么我们需要装饰器

我们假设你的程序实现了say_hello()和say_goodbye()两个函数。

def say_hello():

print("hello!")

def say_goodbye():

print("hello!") # bug here

if __name__ == '__main__':

say_hello()

say_goodbye()

但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:

[DEBUG]: Enter say_hello()

Hello!

[DEBUG]: Enter say_goodbye()

Goodbye!

好,小A是个毕业生,他是这样实现的。

def say_hello():

print("[DEBUG]: enter say_hello()")

print("hello!")

def say_goodbye():

print("[DEBUG]: enter say_goodbye()")

print("hello!")

if __name__ == '__main__':

say_hello()

say_goodbye()

很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。

def debug():

import inspect

caller_name = inspect.stack()[1][3]

print("[DEBUG:enter{}()".format(caller_name))

def say_hello():

debug()

print("hello!")

def say_goodbye():

debug()

print("goodbye!")

if __name__ == '__main__':

say_hello()

say_goodbye()

是不是好一点?那当然,但是每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?

那么装饰器这时候应该登场了。

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。

如何写一个装饰器

在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。

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函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的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_hello(something):

print("hello: {}".format(something))

if __name__ == '__main__':

say_hello("jiumo")

这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。

def debug(func):

def wrapper(*args, **kwargs): # 指定一个相同的参数

print("[DEBUG]: enter {}[]".format(func.__name__))

return func(*args, **kwargs)

return wrapper

@debug

def say_hello(something):

print("hello: {}".format(something))

if __name__ == '__main__':

say_hello("jiumo")

带参数的装饰器

假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出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("asy {}!".format(something))

@logging(level='DEBUG')

def do(something):

print("do {}...".format(something))

if __name__ == '__main__':

say('hello')

do("my work")

是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG'),它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。细细再体会一下。

基于类实现的装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个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(somthing):

print("say{}".format(somthing))

if __name__ == '__main__':

say("jiu_mo")

带参数的类装饰器

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来,然后在重载__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))

if __name__ == '__main__':

say("jiumo")

内置的装饰器

内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些

@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对象。

>>> 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自带装饰器详解_Python装饰器详解相关推荐

  1. python3装饰器详解_Python装饰器详解

    按照 Python 的编程原则,当一个函数被定义后,如要修改或扩展其功能应尽量避免直接修改函数定义的代码段,否则该函数在其他地方被调用时将无法正常运行.因此,当需要修改或扩展已被定义的函数的功能而不希 ...

  2. python中的装饰器怎么运行_Python 装饰器入门(上)

    翻译前想说的话: 这是一篇介绍python装饰器的文章,对比之前看到的类似介绍装饰器的文章,个人认为无人可出其右,文章由浅到深,由函数介绍到装饰器的高级应用,每个介绍必有例子说明.文章太长,看完原文后 ...

  3. python装饰器与闭包_Python 装饰器和闭包

    Python 装饰器和闭包 装饰器是 Python 中常见的语法糖,这篇文章讲了闭包和装饰器的原理,并且分析了函数中变量的作用域,以及尝试总结了常见的坑. 装饰器基础 首先来看看装饰器的定义:装饰器本 ...

  4. python if后面要不要加括号_Python装饰器兼容加括号与不加括号的写法

    使用Django的时候,我发现一个很神奇的装饰器:@login_required, 这是控制一个view的权限的,比如一个视图必须登录才可以访问,可以这样用: 1 2 3 4 @login_requi ...

  5. python装饰器传递参数_Python装饰器高级版—Python类内定义装饰器并传递self参数...

    本文重点:解决了类里面定义的装饰器,在同一个类里面使用的问题,并实现了装饰器的类属性参数传递 目录: 一.基本装饰器 二.在类里定义装饰器,装饰本类内函数 三.类装饰器 正文: 一.基本装饰器 装饰不 ...

  6. python魔法方法详解_Python魔术方法详解

    写这个的初衷主要是因为网上充斥的大量的假冒伪劣解释说明 好歹自己试一试再写文章啊! 真的是误人子弟 例如: __ getattr__:获取一个不存在的属性时调用的方法 事实上获取任何属性的时候都会调用 ...

  7. python修饰器执行步骤_Python修饰器学习总结

    1.大总结,修饰器就是把被修饰的函数作为一个参数传入修饰器函数.格式如下 @修饰器函数 def 函数 修饰器函数是要至少套一个函数的,即: def xxx def yyy ... return yyy ...

  8. python描述器 触发事件_Python描述器引导(转)

    原文:http://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html 作者: Raymond Hettinger 联系: 翻译: h ...

  9. python解析器的作用_Python 解析器

    2008-01-07 17:55 星期一 嵌入Python解析器执行一些简单的Python 脚本脚本很容易,但是当python解析器用到扩展模块时和多个线程都需要Python解析器执行脚本时,遇到了一 ...

最新文章

  1. 【ruoyi若依】Caused by: java.lang.NoClassDefFoundError: com/sun/jna/platform/win32/VersionHelpers
  2. 【Libevent】Libevent学习笔记(一):简介和安装
  3. cesium 获取圆形边界位置_Cesium中级教程4 – 空间数据可视化(二)
  4. python 装饰器有哪些_Python装饰器有哪些常见用途?
  5. matlab读txt文件不完整,求助Matlab批量读取TXT文件出错
  6. 六、Webpack详解学习笔记——webpack的安装、起步、配置、loader的使用、webpack中配置Vue、plugin的使用、搭建本地服务器、webpack配置的分离
  7. android rn 和webview,RN Webview与Web的通信与调试
  8. iscroll5实现一个下拉刷新上拉加载的效果
  9. CentOS下编译安装Gcc-4.9
  10. python 模拟登录验证码_Python模拟登陆 —— 征服验证码 3 CSDN
  11. (日常搬砖)ubuntu18.04风扇断断续续响,提示 ‘GPU fan error‘
  12. 回顾 | Apache Flink Meetup ·上海站(附PPT下载链接)
  13. 《数据结构C语言版》
  14. Oralce数据库计算工作日(处理假期及加班)
  15. 股市预测python代码<循环神经网络>
  16. 基于无人机的目标检测平台——数据中转(安卓App)
  17. IP定位如何揪出SEM、百度竞价的“头号天敌——恶意点击”
  18. excel自动汇总多表格数据
  19. dreamweaver作业静态HTML网页设计模板——迪士尼影视电影(6页)带音乐
  20. cmake(13):构建时设置预处理宏定义以及add_compile_definitions命令详解

热门文章

  1. 数据安全建设需要遵守的安全规定
  2. linux shell脚本执行sql语句建表建库
  3. 稳定性专题 | StackOverFlowError 常见原因及解决方法
  4. QCustomPlot之瀑布图(十五)
  5. 小布老师loadrunner视频教程地址
  6. Academic Phrasebank 2021《学术短语库 2021在线版 英译汉》
  7. [Delphi]CopyFile函數詳解
  8. esxi虚拟山闪存修改
  9. 中国AI投资报告:真格、创新工厂、红杉排名前三
  10. 当PyInstaller打包的文件被判定为病毒