最近在学习python,下面是在Python学习小组上介绍的内容,现学现卖、多练习是好的学习方式,希望大家能够喜欢

Python有大量强大又贴心的特性,如果要列个最受欢迎排行榜,那么装饰器绝对会在其中。

初识装饰器,会感觉到优雅且神奇,想亲手实现时却总有距离感,就像深闺的冰美人一般。这往往是因为理解装饰器时把其他的一些概念混杂在一起了。待我抚去层层面纱,你会看到纯粹的装饰器其实蛮简单直率的。

装饰器的原理

在解释器下跑个装饰器的例子,直观地感受一下。

# make_bold就是装饰器,实现方式这里略去

>>> @make_bold

... def get_content():

... return 'hello world'

...

>>> get_content()

'hello world'

被 make_bold 装饰的 get_content ,调用后返回结果会自动被 b 标签包住。怎么做到的呢,简单4步就能明白了。

1. 函数是对象

我们定义个 get_content 函数。这时 get_content 也是个对象,它能做所有对象的操作。

def get_content():

return 'hello world'

它有 id ,有 type ,有值。

>>> id(get_content)

140090200473112

>>> type(get_content)

>>> get_content

跟其他对象一样可以被赋值给其它变量。

>>> func_name = get_content

>>> func_name()

'hello world'

它可以当参数传递,也可以当返回值

>>> def foo(bar):

... print(bar())

... return bar

...

>>> func = foo(get_content)

hello world

>>> func()

'hello world'

我们可以用 class 来构造函数对象。有成员函数 call 的就是函数对象了,函数对象被调用时正是调用的 call 。

class FuncObj(object):

def init(self, name):

print('Initialize')

self.name= name

def call(self):

print('Hi', self.name)

我们来调用看看。可以看到, 函数对象的使用分两步:构造和调用 (同学们注意了,这是考点)。

>>> fo = FuncObj('python')

Initialize

>>> fo()

Hi python

3. @ 是个语法糖

装饰器的 @ 没有做什么特别的事,不用它也可以实现一样的功能,只不过需要更多的代码。

@make_bold

def get_content():

return 'hello world'

# 上面的代码等价于下面的

def get_content():

return 'hello world'

get_content = make_bold(get_content)

make_bold 是个函数,要求入参是函数对象,返回值是函数对象。 @ 的语法糖其实是省去了上面最后一行代码,使可读性更好。用了装饰器后,每次调用 get_content ,真正调用的是 make_bold 返回的函数对象。

4. 用类实现装饰器

入参是函数对象,返回是函数对象,如果第2步里的类的构造函数改成入参是个函数对象,不就正好符合要求吗?我们来试试实现 make_bold 。

class make_bold(object):

def init(self, func):

print('Initialize')

self.func = func

def call(self):

print('Call')

return '{}'.format(self.func())

大功告成,看看能不能用。

>>> @make_bold

... def get_content():

... return 'hello world'

...

Initialize

>>> get_content()

Call

'hello world'

成功实现装饰器!是不是很简单?

这里分析一下之前强调的 构造 和 调用 两个过程。我们去掉 @ 语法糖好理解一些。

# 构造,使用装饰器时构造函数对象,调用了init

>>> get_content = make_bold(get_content)

Initialize

# 调用,实际上直接调用的是make_bold构造出来的函数对象

>>> get_content()

Call

'hello world'

到这里就彻底清楚了,完结撒花,可以关掉网页了~~~(如果只是想知道装饰器原理的话)

函数版装饰器

阅读源码时,经常见到用嵌套函数实现的装饰器,怎么理解?同样仅需4步。

1. def 的函数对象初始化

用 class 实现的函数对象很容易看到什么时候 构造 的,那 def 定义的函数对象什么时候 构造 的呢?

# 这里的全局变量删去了无关的内容

>>> globals()

{}

>>> def func():

... pass

...

>>> globals()

{'func': }

不像一些编译型语言,程序在启动时函数已经构造那好了。上面的例子可以看到,执行到 def 会才构造出一个函数对象,并赋值给变量 make_bold 。

这段代码和下面的代码效果是很像的。

class NoName(object):

def call(self):

pass

func = NoName()

2. 嵌套函数

Python的函数可以嵌套定义。

def outer():

print('Before def:', locals())

def inner():

pass

print('After def:', locals())

return inner

inner 是在 outer 内定义的,所以算 outer 的局部变量。执行到 def inner 时函数对象才创建,因此每次调用 outer 都会创建一个新的 inner 。下面可以看出,每次返回的 inner 是不同的。

>>> outer()

Before def: {}

After def: {'inner': .inner at 0x7f0b18fa0048>}

.inner at 0x7f0b18fa0048>

>>> outer()

Before def: {}

After def: {'inner': .inner at 0x7f0b18fa00d0>}

.inner at 0x7f0b18fa00d0>

3. 闭包

嵌套函数有什么特别之处?因为有闭包。

def outer():

msg = 'hello world'

def inner():

print(msg)

return inner

下面的试验表明, inner 可以访问到 outer 的局部变量 msg 。

>>> func = outer()

>>> func()

hello world

闭包有2个特点

1. inner 能访问 outer 及其祖先函数的命名空间内的变量(局部变量,函数参数)。

2. 调用 outer 已经返回了,但是它的命名空间被返回的 inner 对象引用,所以还不会被回收。

这部分想深入可以去了解Python的LEGB规则。

4. 用函数实现装饰器

装饰器要求入参是函数对象,返回值是函数对象,嵌套函数完全能胜任。

def make_bold(func):

print('Initialize')

def wrapper():

print('Call')

return '{}'.format(func())

return wrapper

用法跟类实现的装饰器一样。可以去掉 @ 语法糖分析下 构造 和 调用 的时机。

>>> @make_bold

... def get_content():

... return 'hello world'

...

Initialize

>>> get_content()

Call

'hello world'

因为返回的 wrapper 还在引用着,所以存在于 make_bold 命名空间的 func 不会消失。 make_bold 可以装饰多个函数, wrapper 不会调用混淆,因为每次调用 make_bold ,都会有创建新的命名空间和新的 wrapper 。

到此函数实现装饰器也理清楚了,完结撒花,可以关掉网页了~~~(后面是使用装饰的常见问题)

常见问题

1. 怎么实现带参数的装饰器?

带参数的装饰器,有时会异常的好用。我们看个例子。

>>> @make_header(2)

... def get_content():

... return 'hello world'

...

>>> get_content()

'

hello world

'

怎么做到的呢?其实这跟装饰器语法没什么关系。去掉 @ 语法糖会变得很容易理解。

@make_header(2)

def get_content():

return 'hello world'

# 等价于

def get_content():

return 'hello world'

unnamed_decorator = make_header(2)

get_content = unnamed_decorator(get_content)

上面代码中的 unnamed_decorator 才是真正的装饰器, make_header 是个普通的函数,它的返回值是装饰器。

来看一下实现的代码。

def make_header(level):

print('Create decorator')

# 这部分跟通常的装饰器一样,只是wrapper通过闭包访问了变量level

def decorator(func):

print('Initialize')

def wrapper():

print('Call')

return '{1}'.format(level, func())

return wrapper

# make_header返回装饰器

return decorator

看了实现代码,装饰器的 构造 和 调用 的时序已经很清楚了。

>>> @make_header(2)

... def get_content():

... return 'hello world'

...

Create decorator

Initialize

>>> get_content()

Call

'

hello world

'

2. 如何装饰有参数的函数?

为了有条理地理解装饰器,之前例子里的被装饰函数有意设计成无参的。我们来看个例子。

@make_bold

def get_login_tip(name):

return 'Welcome back, {}'.format(name)

最直接的想法是把 get_login_tip 的参数透传下去。

class make_bold(object):

def init(self, func):

self.func = func

def call(self, name):

return '{}'.format(self.func(name))

如果被装饰的函数参数是明确固定的,这么写是没有问题的。但是 make_bold 明显不是这种场景。它既需要装饰没有参数的 get_content ,又需要装饰有参数的 get_login_tip 。这时候就需要可变参数了。

class make_bold(object):

def init(self, func):

self.func = func

def call(self, *args, **kwargs):

return '{}'.format(self.func(*args, **kwargs))

当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数非常合适。可变参数不属于装饰器的语法内容,这里就不深入探讨了。

3. 一个函数能否被多个装饰器装饰?

下面这么写合法吗?

@make_italic

@make_bold

def get_content():

return 'hello world'

合法。上面的的代码和下面等价,留意一下装饰的顺序。

def get_content():

return 'hello world'

get_content = make_bold(get_content) # 先装饰离函数定义近的

get_content = make_italic(get_content)

4. functools.wraps 有什么用?

Python的装饰器倍感贴心的地方是对调用方透明。调用方完全不知道也不需要知道调用的函数被装饰了。这样我们就能在调用方的代码完全不改动的前提下,给函数patch功能。

为了对调用方透明,装饰器返回的对象要伪装成被装饰的函数。伪装得越像,对调用方来说差异越小。有时光伪装函数名和参数是不够的,因为Python的函数对象有一些元信息调用方可能读取了。为了连这些元信息也伪装上, functools.wraps 出场了。它能用于把被调用函数的 module , name , qualname , doc , annotations 赋值给装饰器返回的函数对象。

import functools

def make_bold(func):

@functools.wraps(func)

def wrapper(*args, **kwargs):

return '{}'.format(func(*args, **kwargs))

return wrapper

对比一下效果。

>>> @make_bold

... def get_content():

... '''Return page content'''

... return 'hello world'

# 不用functools.wraps的结果

>>> get_content.name

'wrapper'

>>> get_content.doc

>>>

# 用functools.wraps的结果

>>> get_content.name

'get_content'

>>> get_content.doc

'Return page content'

实现装饰器时往往不知道调用方会怎么用,所以养成好习惯加上 functools.wraps 吧。

这次是真·完结了,撒花吧~~~

python中装饰器的用法_总结Python中装饰器的使用介绍相关推荐

  1. python中shutil.copyfile的用法_用Python复制文件的9个方法

    Python 中有许多"开盖即食"的模块(比如 os,subprocess 和 shutil)以支持文件 I/O 操作.在这篇文章中,你将会看到一些用 Python 实现文件复制的 ...

  2. python中seek函数的用法_在Python中操作文件之seek()方法的使用教程

    seek()方法在偏移设定该文件的当前位置.参数是可选的,默认为0,这意味着绝对的文件定位,它的值如果是1,这意味着寻求相对于当前位置,2表示相对于文件的末尾. 没有返回值.需要注意的是,如果该文件被 ...

  3. python中if else语句用法_讲解Python中if语句的嵌套用法

    可能有这样一种情况,当你想检查其他条件后一个条件解析为真.在这种情况下,可以使用嵌套的if结构. 在嵌套的 if 语句结构,可以在一个 if... elif... else 结构里面可有另外一个 if ...

  4. python中while true的用法_解析Python中while true的使用

    无限循环 如果条件判断语句永远为 true,循环将会无限的执行下去,如下实例: #!/usr/bin/python # -*- coding: UTF-8 -*- var = 1 while var ...

  5. python return用法_初学Python要了解什么 装饰器知识汇总有哪些

    初学Python要了解什么?装饰器知识汇总有哪些?在Python学习过程中,有多种方法对函数和类进行加工,相对于其它方式,装饰器语法简单,代码可读性高.因此,装饰器在Python项目中有广泛的应用,比 ...

  6. python中result的用法_关于Python中的列表理解及用法

    在Python中,列表理解通常用于编写单行语句,这些语句通过可迭代对象进行迭代以创建新的列表或字典.本文首先介绍for循环如何在Python中工作,然后解释如何在Python中使用列表理解. Pyth ...

  7. python中len用法_【python】python中len()怎么用-百度经验

    本文,介绍一下python中len()函数的用法. 工具/原料 电脑 python3.6(Anaconda) 方法/步骤 1 len函数的作用,是Return the number of items ...

  8. python中主函数用法_【Python 1-13】Python手把手教程之——详解函数和函数的使用...

    - 作者 | 弗拉德 来源 | 弗拉德(公众号:fulade_me) 定义函数 下面是一个打印问候语的简单函数,名为greet_user(): def greet_user(): "&quo ...

  9. python装饰器实现单例模式_请教Python 使用装饰器实现单例模式的原理

    1 简单来讲,可以不严谨地把Python的装饰器看做一个包装函数的函数. 比如,有一个函数: def func(): print 'func() run.' if '__main__' == __na ...

  10. python中bool函数用法_在python中bool函数的取值方法

    bool是Boolean的缩写,只有真(True)和假(False)两种取值 bool函数只有一个参数,并根据这个参数的值返回真或者假. 1.当对数字使用bool函数时,0返回假(False),任何其 ...

最新文章

  1. python import_Python Import 详解
  2. oracle 整个表空间迁移,ORACLE表批量迁移表空间
  3. 【CV】YOLOv4最全复现代码合集(含PyTorch/TF/Keras和Caffe等)
  4. 04-异常处理-动手动脑
  5. 网页背景平铺_在大约十秒钟内为网页创建无缝平铺背景
  6. Largest Number 179
  7. mysql攻城掠地_【图片】攻城掠地单机版本服务端+攻城掠地GM工具【单兆权吧】_百度贴吧...
  8. linux下安装erlang,以及cowboy的初步接触的一些环境安装
  9. Step5:Clone EBS Using Rman
  10. Outlier Detection with Isolation Forest(孤立森林异常检测)
  11. Java常用类和方法重点总结
  12. LQR 控制学习-LQR控制 MATLAB官方教程-LQR 控制器_状态空间系统Matlab/Simulink建模分析
  13. Qt获取本机硬盘序列号,不受IDE硬盘与SCSI硬盘类型影响
  14. c# winform 浏览器调用chrome内核
  15. 【小技巧】2345——劫持IE浏览器主页
  16. 基于java实现学科竞赛管理系统【Springboot+mybatis+layui】
  17. 业余无线电通信_如何办理业余无线电台执照
  18. html背景图片怎么设置圆角,div+css实现圆角背景文字导航条
  19. centOS下,怎么能快速锁屏
  20. 12.11晚自习习题

热门文章

  1. springMVC Model ModelMap 和 ModelAndView的区别
  2. Spring JPA 使用@CreatedDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy 自动生成时间和修改者...
  3. 使用putty上传文件到linux
  4. 菜鸟的Python学习之路(流水账)
  5. (有图)仿QQ侧滑菜单:RecyclerView侧滑菜单,长按拖拽,滑动删除
  6. 《编写可维护的JavaScript》——JavaScript编码规范(七)
  7. SASS+COMPASS 自适应 学习笔记
  8. 博客堂服务器转移成功!
  9. MDN - 字符串API笔记
  10. PHP curl get post 请求的封装