0、引子

如果你正在做一个网站,且需要为每次请求做登录状态的检测。在收到用户每一个访问请求的第一步,你都要检测一下用户的登录状态。然而为每个访问请求做额外状态检测,真的是一件不那么优雅,繁琐而又不那么令人开心的事情。

有经验的程序员马上想了“修饰器模式”,即为每个访问增加额外的功能,且可以不改变原有功能的代码。事件上,很多用Python实现的网站的确也是这么做的。

我不打算在这里讨论什么是“修饰器模式”,但Python的“修饰器”就是基于“修饰器模式”的思路而实现的。所以如果你不了解什么是“修饰器模式”,你可以先看一下我推荐的两篇文章。这两篇文章的内容基本一样,一篇是中文版本的,一篇是英文版本的。https://www.tutorialspoint.com/design_pattern/decorator_pattern.htm​www.tutorialspoint.com装饰器模式 | 菜鸟教程​www.runoob.com

换句话说,Decorator(修饰器)就是Python内部提供的一个语法便利,用于更加快捷、方法、直观地实现修饰器模式。修饰器模式会使用得代码结构比较复杂,而decorator则不存在这个问题。为了表述方便和统一,下文中将直接使用Decorator,而不再使用中文“修饰器”。

当然了,Decorator虽然好实现,却并不是特别好理解。特别是对于初学者来说,学习和掌握Decorator是一个比较烧脑的过程。本文的目的就是为大家从本质到现象(对,就是本质到现象)剖析Decorator,让大家一次就深刻理解并掌握Decorator的用法。

本文的主要目的是带你理解Decorator,所以并没有对Decorator的所有细节进行讲解。在阅读本文之后,你可以通过阅读Python的官方文档来深入学习Decorator。

1、函数是对象(A function is an object)

在Python中,万物皆对象。(Everything in Python is an object.)

如果你学习了Python,你应该起码要理解这句话的含义。在搜索引擎中搜索上面这句话,会得到成千上万的讨论,所以本文不深入讨论这个问题,默认你理解这句话的含义。

当你定义一个函数的时候,你就得到了一个函数对象,这个对象的引用就是函数名。通过函数名,你可以做很多事情:

调用函数

将函数赋值给其它变量

把它当成参数传给其它函数

可以在函数内定义一个函数,并做为返回值返回

这里的每一条,都不难理解,却都是Decorator实现的关键。我们通过例子来深入地理解上面四句话的内涵。

# 定义一个加法函数

def add(a, b):

return a + b

# 调用函数

sum1 = add(1, 2)

# 把函数赋值给其它变量

sum_of_two_num = add

# 通过其它变量调用add

sum2 = sum_of_two_num(2, 3)

print(sum1, sum2) # 打印 3 5

#####和谐的分割线#####

def double(x):

return 2 * x

# 一个数字列表

nums = [1, 2, 3, 4, 5]

# 将每个数自乘以2

# map(func, iter)就是对iter中的每个元素e调用 func(e)

# 下面的代码相当于 double_nums = list(map(lambda x: 2 * x, nums))

dubled_nums = list(map(double, nums))

print(dubled_nums) # 打印 [2, 4, 6, 8, 10]

#####和谐的分割线#####

# 根据lan返回不同版本的greet函数

def getGreet(lan):

if lan == "En":

def greet(name):

return "Hello " + name

elif lan == "Es":

return lambda name: "Hola " + name

else:

return lambda name: "Ciao " + name

greet = getGreet("Es") # 获取西语版本的greet函数

# Say "Hello World" in Spanish

print(greet("Mondo")) # 打印 Hola Mondo

如果上面的每一行代码你都能理解,那么你就已经准备好了。

2、为函数增加额外的行为

实现整数的四则运行函数,并且保证每个函数的参数都为整数,如果有至少一个参数不为整数,则直接返回0。四则运行4个函数在参数合法性检测方面的行为是一样的,所以我们可以通过定义一个公共的参数检测函数来实现。

def arg_check(x):

return type(x) == int

def add(a, b):

if not arg_check(a) or not arg_check(b):

return 0

return a + b

def sub(a, b):

if not arg_check(a) or not arg_check(b):

return 0

return a - b

def mul(a, b):

if not arg_check(a) or not arg_check(b):

return 0

return a * b

def div(a, b):

if not arg_check(a) or not arg_check(b):

return 0

if b == 0: return 0

return a / b

上面的实现方式虽然没有问题,但代码还是显得重复而又繁琐。

一个优化的思路,就是利用“修饰器模式”。修饰器模式就是在不改变现有功能的代码的基础上,为功能增加额外的行为。

def add(a, b):

return a + b

def sub(a, b):

return a - b

def mul(a, b):

return a * b

def div(a, b):

if b == 0: return 0

return a / b

def arg_check(x):

return type(x) == int

def checker(func):

def new_func(a, b):

# 额外的功能

if not arg_check(a) or not arg_check(b):

return 0

# 原来的功能

return func(a, b)

# 返回一个新的函数,这个函数增加了额外的功能

# 并保留了原函数的功能

return new_func

# 通过调用checker来为每个函数增加额外的行为

# 即对函数进行修饰

add = checker(add)

sub = checker(sub)

mul = checker(mul)

div = checker(div)

通过这种方法,无论我们定义了多少二目(提供两个参数)整数运算函数,我会都只需要调用checker,用返回值替代原来的函数行为就可以了。

Decorator就是依据这个原理来实现的。Python为我们提供了语法便利。

3、Decorator

事实上,到这里,你已经知道Decorator怎么实现了。我们只需要把第2节的第2个例子稍微修改一下,就是Decorator了。

def arg_check(x):

return type(x) == int

def checker(func):

def new_func(a, b):

# 额外的功能

if not arg_check(a) or not arg_check(b):

return 0

# 原来的功能

return func(a, b)

# 返回一个新的函数,这个函数增加了额外的功能

# 并保留了原函数的功能

return new_func

@checker

def add(a, b):

return a + b

@checker

def sub(a, b):

return a - b

@checker

def mul(a, b):

return a * b

@checker

def div(a, b):

if b == 0: return 0

return a / b

也就是说,下两组代码是等价的:

def xx(a, b):

# blabla

xx = checker(xx)

# 相当于

@checker

def xx(a, b):

# blabla

到目前为止,你应该已经完全理解什么是Decorator,并懂得如何实现。

在我们的例子中,我们在原有函数的前面增加了参数检测。你可也可以原有函数的后面增加其它行为;甚至,你也可以不使用原有函数的行为(即不调用原函数)。

4、带额外参数的"Decorator"

本小节可能直接看例子的代码,会更加容易理解。所以你可以先看一下代码,再回头阅读本小节的文章讲解。

因为修饰器是一个函数(其实是“可调用对象”,下一小节会深入讲解),它返回一个新的函数,这个返回的函数用于替代原来的函数的行为。

修改器函数只提供一个参数,即被修饰的函数本身,然后返回一个新的版本,用于替换被修饰的函数。注意到本小节的标题中,我为Decorator加了引号。这是因为我们没有办法为Decorator提供额外的参数,但我们却可以通过巧妙的方式来实现这个效果。

假如我们需要为每一次四则运算的调用打一条日志,而且希望能为每个函数指定不同日志文件。通过对第3小节代码的简单修改,就可以达到这个效果。

def arg_check(x):

return type(x) == int

def checker(log="arithmatic.log"):

def real_dec(func):

def new_func(a, b):

# 参数检测

if not arg_check(a) or not arg_check(b):

return 0

# 打印日志

fout = open(log, mode="w")

fout.write("called: %s(%d, %d)\n".format(func.__name__, a, b))

fout.close()

# 原来的功能

return func(a, b)

return new_func

return real_dec

@checker("add.log")

def add(a, b):

return a + b

@checker("sub.log")

def sub(a, b):

return a - b

@checker()

def mul(a, b):

return a * b

@checker()

def div(a, b):

if b == 0: return 0

return a / b

通过这个方式,add和sub每次被调用,都会分别向add.log和sub.log写入一条日志;而调用mul和div则会将日志写入arithmatic.log。但无论我们是否提供参数,checker的括号都不可省略。这个例子中的checker跟第3小节中的checker已经不是同一个层次的东西,而real_dec才是与第3小节的checker同一个层次的东西。

为了方便对这段代码的理解,我们可以把函数定义的代码进行如下的拆解:

add_dec = checher("add.log") # 得到一个修饰器

@add_dec

def add(a, b):

return a + b

所以,checker并不是一个Decorator,而其返回值才是。

5、可调用对象作为Decorator

本节的内容,是第4小节的一个延伸。在第4小节中,我们通过调用checker函数得到一个Decorator。同样的道理,我们可以通过实例化一个可调用对象(调用类的构造函数)来作为Decorator。

在理解本小节之前,我们假设你知道什么是callable object(可调用对象)。实现在一个类的时候,我们只需要重载__call__函数即可。用对象来作为Decorator,可以实现更新丰富的内容。不过我们还是延续之前四则的功能,进行适当的改造来达到讲解的效果。我相信各位读者一定能学以致用,实现更加复杂的延伸。

class Checker:

def __init__(self, log="arithmatic.log"):

self.log = log

@staticmethod

def arg_check(x):

return type(x) == int

def __call__(self, func):

def new_func(a, b):

# 参数检测

if not Checker.arg_check(a) or not Checker.arg_check(b):

return 0

# 打日志

fout = open(self.log, mode="w")

fout.write("called: %s(%d, %d)\n".format(func.__name__, a, b))

fout.close()

# 原来的功能

return func(a, b)

return new_func

@Checker("add.log")

def add(a, b):

return a + b

@Checker("sub.log")

def sub(a, b):

return a - b

@Checker()

def mul(a, b):

return a * b

@Checker()

def div(a, b):

if b == 0: return 0

return a / b

为了方便理解,我们同样可以对函数定义的代码进行如下的拆解:

checher = Checher("add.log") # 得到一个修饰器

@checher

def add(a, b):

return a + b

我们可以看到,通过类实现的Decorator,与第4小节的一脉相承。

python3 修饰器_Python3基础之: Decorator (修饰器)相关推荐

  1. java中布局管理器flowlayout_JAVA基础:FlowLayout布局管理器

    在前面的例子中,使用到了FlowLayout布局管理器.FlowLayout型布局管理器对容器中组件进行布局的方式是将组件逐个地安放在容器中的一行上.一行放满后就另起一个新行. FlowLayout有 ...

  2. python3 字典打印_Python3基础 dict items 以元组的形式打印出字典的每一个项

    Python : 3.7.0 OS : Ubuntu 18.04.1 LTS IDE : PyCharm 2018.2.4 Conda : 4.5.11 typesetting : Markdown ...

  3. python3基本知识_Python3 - 基础知识、基本了解

    我初学Python时,听到的关于Python的第一句话就是,Python是一门解释性语言,我就这样一直相信下去,直到发现了*.pyc文件的存在.如果是解释型语言,那么生成的*.pyc文件是什么呢?c应 ...

  4. python3字典平均值_python3基础之字典

    Python3 字典 字典是另一种可变容器模型,且可存储任意类型对象. 字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中 ,格 ...

  5. python3 面向对象编程_Python3基础-面向对象编程

    类的定义与实例化 语法格式如下: classClassName:. . . 类:用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法.对象是类的实例. 对象: 通过类定 ...

  6. python3字符串拼接_Python3基础 str + 字符串变量拼接

    ? ????   Python : 3.7.0 ??????   OS : Ubuntu 18.04.1 LTS ??????  IDE : PyCharm 2018.2.4 ????? Conda ...

  7. python中修饰器的优点和作用_Python入门基础教程之装饰器

    Python装饰器的定义:在代码运行期间在不改变原函数定义的基础上,动态给该函数增加功能的方式称之为装饰器(Decorator) 装饰器的优点和用途: 1. 抽离出大量函数中与函数功能本身无关的的雷同 ...

  8. OC基础之访问修饰符

    做C#.Java的人都知道private.protect.public这些访问权限,在OC中看项目基本都没用到这些,昨天就好奇的仔细了解了下,其实OC中也是有这些访问权限的修饰.说实话有好多做过编程的 ...

  9. 一文读懂 @Decorator 装饰器——理解 VS Code 源码的基础

    作者:easonruan,腾讯 CSIG 前端开发工程师 1. 装饰器的样子 我们先来看看 Decorator 装饰器长什么样子,大家可能没在项目中用过 Decorator 装饰器,但多多少少会看过下 ...

最新文章

  1. Convert Plant to Retail Site Master
  2. retinajs 使用方法
  3. lnmp、lamp、lnmpa一键安装包
  4. c# hash 泛型_C# 泛型Dictionary (Hashtable)
  5. [原]RHCS集群的服务切换测试札记
  6. openmv串口数据 串口助手_STM32 串口接收不定长数据 STM32 USART空闲检测中断
  7. 62、剑指offer--二叉搜索树的第k个结点
  8. android otg读取索尼相机usb_索尼新概念!即将上市全画幅无反相机α7C先睹为快
  9. HTML中的几种空格
  10. SAP 下载EXCEL模板
  11. 使用T-SQL语句创建数据库
  12. 神秘邻居把我的信息卖给了诈骗团伙
  13. ccy测试影响因子版270ms
  14. 广州药业vs加多宝 王老吉
  15. 2019年Unity学习资源指南[精心整理]
  16. 哈迪-温伯格平衡(Hardy-Weinberg equilibrium)法则
  17. 简单工厂和工厂方法模式的区别和个人看法
  18. 【PythonPlanet】二手房产成交数据分析
  19. 十余省份高速人工通道实现支付宝收费 与ETC互为补充
  20. 自己动手编写仿QQ的app -1注册界面by sdust iot zhl

热门文章

  1. 第二本书:疯狂人类进化史20190624
  2. mysql数据转mongodb_mysql数据转存到mongodb
  3. 上市第二天股价大涨6% 但小米蛰伏隐患已经显露!
  4. 表格全选和取消全选功能实现
  5. YOLOv5/v7 应用轻量级通用上采样算子CARAFE
  6. 华为机架服务器的型号和cpu,华为更新3款模块化机架服务器的处理器
  7. 平面设计师要掌握的版式基础大全
  8. 星三角电路启动原理图解
  9. 阿里云rds mysql坑_配置ECS上自建MySQL作为RDS从库过程中踩到的坑
  10. 一文读懂自动驾驶运行设计域ODD