装饰器

此文可能是有史以来最全的关于Python装饰器的Blog了...

函数名的运用

关于函数名

函数名是⼀个变量,但它是⼀个特殊的变量。与括号配合可以执⾏函数的变量。

查看函数名的内存地址:

deffunc():

print('呵呵')

print(func) #

做变量

deffunc():

print('呵呵')

a = func # 把函数当成变量赋值给另外一个变量

a() # 通过变量a调用函数

做容器的元素

deffunc1():

print('func1')

deffunc2():

print('func2')

deffunc3():

print('func3')

deffunc4():

print('func4')

list1 =[func1, func2, func3, func4]

for i inlist1:

i()

做参数

deffunc1():

print('func1')

deffunc2(arg):

print('start')

arg() # 执行传递进来的arg

print('end')

func2(func1) # 把func1当成参数传递给func2

做返回值

deffunc1():

print('这里是func1')

deffunc2():

print('这里是func2')

return func2 # 把func2当成返回值返回

ret = func1() # 调用func1,把返回值赋值给ret

ret() # 调用ret

闭包

灵魂三问

首先我们来看一个例子:

deffunc1():

name = '张三'

deffunc2(arg):

print(arg)

func2(name)

func1()

理解了上面的例子,我们再看一个例子:

deffunc1():

name = '张三'

deffunc2():

print(name) # 能够访问到外层作用域的变量

func2()

func1()

最后再看一个例子:

deffunc1(name):

deffunc2():

print(name) # 能够访问到外层作用域的变量

func2()

func1('张三')

闭包的定义

一个内层函数中,引用了外层函数(非全局)的变量,这个内层函数就可以成为闭包。

在Python中,我们可以使用__closure__来检测函数是否是闭包。

deffunc1():

name = '张三'

deffunc2():

print(name) # 能够访问到外层作用域的变量

func2()

print(func2.__closure__) # (,)

func1()

print(func1.__closure__) # None

问题来了,我们如何在函数外边调用函数内部的函数呢?

当然是把内部函数当成返回值返回了。

deffunc1():

name = '张三'

deffunc2():

print(name)

return func2 # 把内部函数当成是返回值返回

ret = func1() # 把返回值赋值给变量ret

ret() # 调用内部函数

内部函数当然还可包含其他的函数,多层嵌套的道理都是一样的。

deffunc1():

deffunc2():

deffunc3():

print('func3')

returnfunc3

returnfunc2

ret1 = func1() # func2

ret2 = ret1() # func3

ret2()

接下来我们看下面这个例子,来更深刻的理解一下闭包的含义:

defprint_msg(msg):

# 这是外层函数

defprinter():

# 这是内层函数

print(msg)

return printer # 返回内层函数

func = print_msg("Hello")

func()

现在我们进行如下操作:

>>>del print_msg

>>>func()

Hello

>>> print_msg("Hello")

Traceback (most recent call last):

...

NameError: name 'print_msg' is not defined

我们知道如果⼀个函数执⾏完毕,则这个函数中的变量以及局部命名空间中的内容都将会被销毁。在闭包中内部函数会引用外层函数的变量,而且这个变量将不会随着外层函数的结束而销毁,它会在内存中保留。

也就是说,闭包函数可以保留其用到的变量的引用。

闭包面试题

# 编写代码实现func函数,使其实现以下效果:

foo = func(8)

print(foo(8)) # 输出64

print(foo(-1)) # 输出-8

装饰器

装饰器来历

在说装饰器之前,我们先说⼀个软件设计的原则: 开闭原则, ⼜被成为开放封闭原则。

开放封闭原则是指对扩展代码的功能是开放的,但是对修改源代码是封闭的。这样的软件设计思路可以保证我们更好的开发和维护我们的代码。

我们先来写一个例子,模拟一下女娲造人:

defcreate_people():

print('女娲真厉害,捏个泥吹口气就成了人!')

create_people()

好吧,现在问题来了。上古时期啊,天气很不稳定,这个时候突然大旱三年。女娲再去捏人啊,因为太干了就捏不到一块儿去了,需要在捏人之前洒点水才行。

defcreate_people():

print('洒点水')

print('女娲真厉害,捏个泥吹口气就成了人!')

create_people()

这不就搞定了么?但是呢,我们是不是违背了开放封闭原则呢?我们是添加了新的功能,但是我们是直接修改了源代码。在软件开发中我们应该对直接修改源代码是谨慎的。

比如,女娲为了防止浪费,想用剩下点泥巴捏个鸡、鸭、鹅什么的,也需要洒点水。那我们能在每个造鸡、造鸭、造鹅函数的源代码中都手动添加代码么?肯定是不现实的。

怎么办?再写一个函数不就OK了么?

defcreate_people():

print('女娲真厉害,捏个泥吹口气就成了人!')

defcreate_people_with_water():

print('洒点水')

create_people()

create_people_with_water()

不让我直接修改源代码,那我重新写一个函数不就可以了吗?

但是,你有没有想过一个问题,女娲造人也很累的,她后来开了很多分店,每家分店都是调用了之前的create_people函数造人,那么你修改了之后,是不是所有调用原来函数的人都需要修改调用函数的名称呢?很麻烦啊!!!

总结一句话就是如何在不改变函数的结构和调用方式的基础上,动态的给函数添加功能?

defcreate_people():

print('女娲真厉害,捏个泥吹口气就成了人!')

defa(func):

defb():

print('洒点水')

func()

returnb

ret =a(create_people)

ret()

利用闭包函数不就可以了么?

但是,你这最后调用的是ret啊,不还是改变了调用方式么?

再往下看:

defcreate_people():

print('女娲真厉害,捏个泥吹口气就成了人!')

defa(func):

defb():

print('洒点水')

func()

returnb

create_people =a(create_people)

create_people()

上面这段代码是不是完美解决了我们的问题呢?

看一下它的执行过程吧:

首先访问a(create_people)

把create_people函数赋值给了a函数的形参func,记住后续执行func的话实际上是执行了最开始传入的create_people函数。

a函数执行过程就是一句话,返回了b函数。这个时候把b函数赋值给了create_people这个变量

执行create_people的时候,相当于执行了b函数,先打印洒点水再执行func,也就是我们最开始传入的create_people函数

我们巧妙的使用闭包实现了,把一个函数包装了一下,然后再赋值给原来的函数名。

装饰器语法糖

上面的代码就是一个装饰器的雏形,Python中针对于上面的功能提供了一个快捷的写法,俗称装饰器语法糖。

使用装饰器语法糖的写法,实现同样功能的代码如下:

defa(func):

defb():

print('洒点水')

func()

returnb

@a # 装饰器语法糖

defcreate_people():

print('女娲真厉害,捏个泥吹口气就成了人!')

create_people()

装饰器进阶

装饰带返回值的函数

如果被装饰的函数有返回值,我们应该怎么处理呢?

请看下面的示例:

def foo(func): # 接收的参数是一个函数名

def bar(): # 定义一个内层函数

print("这里是新功能...") # 新功能

r = func() # 在内存函数中拿到被装饰函数的结果

return r # 返回被装饰函数的执行结果

returnbar

# 定义一个有返回值的函数

@foo

deff1():

return '嘿嘿嘿'

# 调用被装饰函数

ret = f1() # 调用被装饰函数并拿到结果

print(ret)

装饰带参数的函数

def foo(func): # 接收的参数是一个函数名

def bar(x, y): # 这里需要定义和被装饰函数相同的参数

print("这里是新功能...") # 新功能

func(x, y) # 被装饰函数名和参数都有了,就能执行被装饰函数了

returnbar

# 定义一个需要两个参数的函数

@foo

deff1(x, y):

print("{}+{}={}".format(x, y, x+y))

# 调用被装饰函数

f1(100, 200)

带参数的装饰器

被装饰的函数可以带参数,装饰器同样也可以带参数。

回头看我们上面写得那些装饰器,它们默认把被装饰的函数当成唯一的参数。但是呢,有时候我们需要为我们的装饰器传递参数,这种情况下应该怎么办呢?

接下来,我们就一步步实现带参数的装饰器:

首先我们来回顾下上面的代码:

def f1(func): # f1是我们定义的装饰器函数,func是被装饰的函数

def f2(*arg, **kwargs): # *args和**kwargs是被装饰函数的参数

func(*arg, **kwargs)

return f2

从上面的代码,我们发现了什么?

我的装饰器如果有参数的话,没地方写了…怎么办呢?

还是要使用闭包函数!

我们需要知道,函数除了可以嵌套两层,还能嵌套更多层:

# 三层嵌套的函数

deff1():

deff2():

name = "张三"

deff3():

print(name)

returnf3

return f2

嵌套三层之后的函数调用:

f = f1() # f --> f2

ff = f() # ff --> f3

ff() # ff() --> f3() --> print(name) --> 张三

注意:在内部函数f3中能够访问到它外层函数f2中定义的变量,当然也可以访问到它最外层函数f1中定义的变量。

# 三层嵌套的函数2

deff1():

name = '张三'

deff2():

deff3():

print(name)

returnf3

return f2

调用:

f = f1() # f --> f2

ff = f() # ff --> f3

ff() # ff() --> f3() --> print(name) --> 张三

好了,现在我们就可以实现我们的带参数的装饰器函数了:

# 带参数的装饰器需要定义一个三层的嵌套函数

def d(name): # d是新添加的最外层函数,为我们原来的装饰器传递参数,name就是我们要传递的函数

def f1(func): # f1是我们原来的装饰器函数,func是被装饰的函数

def f2(*arg, **kwargs): # f2是内部函数,*args和**kwargs是被装饰函数的参数

print(name) # 使用装饰器函数的参数

func(*arg, **kwargs) # 调用被装饰的函数

returnf2

return f1

上面就是一个带参装饰器的代码示例,现在我们来写一个完整的应用:

def d(a=None): # 定义一个外层函数,给装饰器传参数--role

def foo(func): # foo是我们原来的装饰器函数,func是被装饰的函数

def bar(*args, **kwargs): # args和kwargs是被装饰器函数的参数

# 根据装饰器的参数做一些逻辑判断

ifa:

print("欢迎来到{}页面。".format(a))

else:

print("欢迎来到首页。")

# 调用被装饰的函数,接收参数args和kwargs

func(*args, **kwargs)

returnbar

returnfoo

@d() # 不给装饰器传参数,使用默认的'None'参数

defindex(name):

print("Hello {}.".format(name))

@d("电影") # 给装饰器传一个'电影'参数

defmovie(name):

print("Hello {}.".format(name))

if __name__ == '__main__':

index('张三')

movie('张三')

装饰器修复技术

被装饰的函数最终都会失去本来的__doc__等信息, Python给我们提供了一个修复被装饰函数的工具。

defa(func):

@wraps(func)

defb():

print('洒点水')

func()

returnb

@a # 装饰器语法糖

defcreate_people():

"""这是一个女娲造人的功能函数"""

print('女娲真厉害,捏个泥吹口气就成了人!')

create_people()

print(create_people.__doc__)

print(create_people.__name__)

多个装饰器装饰同一函数

同一个函数可以被多个装饰器装饰,此时需要注意装饰器的执行顺序。

deffoo1(func):

print("d1")

definner1():

print("inner1")

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

returninner1

deffoo2(func):

print("d2")

definner2():

print("inner2")

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

returninner2

@foo1

@foo2

deff1():

return "Hello Andy"

# f1 = foo2(f1) ==> print("d2") ==> f1 = inner2

# f1 = foo1(f1) ==> print("d1") ==> f1 = foo1(inner2) ==> inner1

ret = f1() # 调用f1() ==> inner1() ==> inner2() ==> inner1() ==> Hello Andy

print(ret)

装饰器终极进阶

类装饰器

我们除了可以使用函数装饰函数外,还可以用类装饰函数。

classD(object):

def __init__(self, a=None):

self.a =a

self.mode = "装饰"

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

if self.mode == "装饰":

self.func = args[0] # 默认第一个参数是被装饰的函数

self.mode = "调用"

returnself

# 当self.mode == "调用"时,执行下面的代码(也就是调用使用类装饰的函数时执行)

ifself.a:

print("欢迎来到{}页面。".format(self.a))

else:

print("欢迎来到首页。")

self.func(*args, **kwargs)

@D()

defindex(name):

print("Hello {}.".format(name))

@D("电影")

defmovie(name):

print("Hello {}.".format(name))

if __name__ == '__main__':

index('张三')

movie('张三')

装饰类

我们上面所有的例子都是装饰一个函数,返回一个可执行函数。Python中的装饰器除了能装饰函数外,还能装饰类。

可以使用装饰器,来批量修改被装饰类的某些方法:

# 定义一个类装饰器

classD(object):

def __call__(self, cls):

classInner(cls):

# 重写被装饰类的f方法

deff(self):

print('Hello 张三.')

returnInner

@D()

class C(object): # 被装饰的类

# 有一个实例方法

deff(self):

print("Hello world.")

if __name__ == '__main__':

c =C()

c.f()

举个实际的应用示例:

我们把类中的一个只读属性定义为property属性方法,只有在访问它时才参与计算,一旦访问了该属性,我们就把这个值缓存起来,下次再访问的时候无需重新计算。

classlazyproperty:

def __init__(self, func):

self.func =func

def __get__(self, instance, owner):

if instance isNone:

returnself

else:

value =self.func(instance)

setattr(instance, self.func.__name__, value)

returnvalue

importmath

classCircle:

def __init__(self, radius):

self.radius =radius

@lazyproperty

defarea(self):

print('计算面积')

return math.pi * self.radius ** 2c1 = Circle(10)

print(c1.area)

print(c1.area)

python基础装饰器_Python基础之装饰器相关推荐

  1. python装饰器_Python基础-装饰器

    作者:Zarten知乎专栏:Python基础深入详解知乎ID: Zarten简介: 互联网一线工作者,尊重原创并欢迎评论留言指出不足之处,也希望多些关注和点赞是给作者最好的鼓励 ! 概述 装饰器其实就 ...

  2. python高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上

    Python高级编程--装饰器Decorator超详细讲解(上篇) 送你小心心记得关注我哦!! 进入正文 全文摘要 装饰器decorator,是python语言的重要特性,我们平时都会遇到,无论是面向 ...

  3. python什么是装饰器_python学习之装饰器是什么?

    学完python函数,接下来我们跟着猿人学python一起来看看什么是装饰器. 大家应该多多少少在其它地方有看过或是听过"装饰器"这个词.装饰器,顾名思义,就是用来装饰的,它装饰的 ...

  4. python的基础知识点总结_Python 基础知识点总结

    本篇基于Python 2.7.9 根据廖雪峰Python教程整理 URL:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb4931 ...

  5. python新手入门课_Python基础视频12集入门课程

    视频+笔记:├─1简介与实验环境配置9│ Python基础.pptx│ Python简介.pptx│ 实验环境配置2-1.pdf│ 2.1简介与实验环境配置.mp4│ 请简述 Python 语言与 C ...

  6. python的网页解析器_python 之网页解析器

    一.什么是网页解析器 1.网页解析器名词解释 首先让我们来了解下,什么是网页解析器,简单的说就是用来解析html网页的工具,准确的说:它是一个HTML网页信息提取工具,就是从html网页中解析提取出& ...

  7. python自带网页解析器_python 之网页解析器

    一.什么是网页解析器 1.网页解析器名词解释 首先让我们来了解下,什么是网页解析器,简单的说就是用来解析html网页的工具,准确的说:它是一个HTML网页信息提取工具,就是从html网页中解析提取出& ...

  8. python基础装饰器_Python基础 装饰器及练习

    Python视频教程讲解装饰器 推荐免费:Python视频教程 装饰器概念 装饰器,说白了,就是用来装饰函数的一个函数. 装饰器遵循 开放封闭原则.依赖倒置原则,这两个原则概念,自行百度下, 装饰器长 ...

  9. python重写和装饰器_python中的装饰器

    装饰器的本质: 当你在用某个@decorator来修饰某个函数func时,如下所示: @decorator def func(): pass 其解释器会解释成下面这样的语句: func=decorat ...

  10. python 生成器装饰器_python: 生成器,装饰器以及列表推导式写法

    1.连接字符串尽量用format,两个的时候可以用 + name = "lxw" age = 24 x = "{0}{1}".format(name, age) ...

最新文章

  1. 一种PacBio测序数据组装得到的基因组序列的纠错方法技术 (专利技术)
  2. STM32 基础系列教程 37 - Lwip_igmp
  3. 美国国防部CIO:企业IT可考虑用开源做数据分析
  4. 多项式的求逆、取模和多点求值学习小记
  5. ICCV 2017 CREST:《CREST: Convolutional Residual Learning for Visual Tracking》论文笔记
  6. 【干货】求之不得的 Java 文档教程大汇总!
  7. JAVA程序设计----函数基础1
  8. 安卓程序开发——创建安卓虚拟机
  9. 在安卓上安装java_如何在Android上安装JavaCV并使用FrameGrabber
  10. php文本文件操作,文本文件操作的php类
  11. Android笔记 显式意图demo
  12. ad用户和计算机显示2000个,AD 用户属性userAccountControl的详细解释
  13. 怎么样eclipse发达国家多重聚合关系maven项目和使用git管理
  14. html5显示特殊符号,HTML5特殊符号怎么显示-电脑自学网
  15. matlab电阻电路的计算,Matlab电路原理应用——计算未知电阻
  16. linux dev 下放的什么意思,详解Linux系统下的/dev目录
  17. 3种常用的缓存读写策略
  18. 办公软件小技巧:Excel表格自动排序
  19. ubc本科计算机雅思要求,加拿大各大学的雅思要求
  20. 数学建模中的插值问题

热门文章

  1. 如何保留和还原 Windows Phone 页面状态
  2. 10.这就是搜索引擎:核心技术详解 --- 网页去重
  3. 2.企业安全建设入门(基于开源软件打造企业网络安全) --- 业务网纵深防御体系建设
  4. 国产APP自动化测试工具AndroidRobot下载地址
  5. 学习MyBatis之简单入门HelloWorld
  6. 团队-科学计算器-代码设计规范
  7. TensorFlow 安装
  8. 下拉框绑定数据库后,如何在下拉框的最前面增加一项
  9. 用Java操作Office 2007
  10. JavaScript数组的某些操作(一)