1.开放封闭原则

简单来说,就是对扩展开放,对修改封闭

在面向对象的编程方式中,经常会定义各种函数。

一个函数的使用分为定义阶段和使用阶段,一个函数定义完成以后,可能会在很多位置被调用

这意味着如果函数的定义阶段代码被修改,受到影响的地方就会有很多,此时很容易因为一个小地方的修改而影响整套系统的崩溃,

所以对于现代程序开发行业来说,一套系统一旦上线,系统的源代码就一定不能够再改动了。

然而一套系统上线以后,随着用户数量的不断增加,一定会为一套系统扩展添加新的功能。

此时,又不能修改原有系统的源代码,又要为原有系统开发增加新功能,这就是程序开发行业的开放封闭原则,这时就要用到装饰器了。

2.什么是装饰器??

装饰器,顾名思义,就是装饰,修饰别的对象的一种工具。

所以装饰器可以是任意可调用的对象,被装饰的对象也可以是任意可调用对象

3.装饰器的作用

在不修改被装饰对象的源代码以及调用方式的前提下为被装饰对象添加新功能

原则:

1.不修改被装饰对象的源代码
2.不修改被装饰对象的调用方式

目标:

为被装饰对象添加新功能

4.装饰器的定义和使用

来看下面的代码:

    import time
import random def index():
time.sleep(random.randrange(1,5))
print("welcome to index page") index()

index函数的作用是程序在随机睡眠1到5秒之后,打印一句话

现在想为index函数添加一个新功能:统计index函数的运行时间,该怎么做呢??

修改index函数如下:

    import time
import random def index():
start_time=time.time()
time.sleep(random.randrange(1,5))
print("welcome to index page")
end_time=time.time()
print("cost time: %s" %(end_time - start_time)) index()

运行程序,执行结果如下:

welcome to index page
cost time: 2.000999927520752

可以看到,为index函数添加新功能确实实现了,但是却违反了开放封闭原则。

在符合开放封闭原则的前提下,如果想为index函数添加新功能,此时就要使用装饰器了

修改代码

    import time
import random def index():
time.sleep(random.randrange(1,5))
print("welcome to index page") def timmer():
def inner():
start_time=time.time()
index()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner f=timmer()
f()

运行程序,查看执行结果

welcome to index page
run time: 1.0

从程序执行结果可以看出,index函数的运行时间已经被统计出来了

但是查看源码可以知道,index函数的源码确实没有被修改,但是index的调用方式被修改了

而且还有一个问题就是,timmer这个装饰器只能被用来装饰index这个函数,如果以后想统计别的函数的运行时间,又要重新定义别的装饰器,这样也太不灵活了。

修改上面的代码

    import time
import random def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner def index():
time.sleep(random.randrange(1,5))
print("welcome to index page") index=timmer(index)
index()

运行程序,查看程序执行结果

welcome to index page
run time: 4.0

可以看到,index函数的源代码没有被修改,index函数的调用方式也没有改变,但是依然为index函数添加了统计时间的功能,这里使用的就是装饰器了。

来分析下上面代码的执行流程:

    1.导入time和random模块,定义index函数和timmer函数
2.把原始的index函数的内存地址作为参数传给timmer函数。
3.timmer函数内部嵌套定义一个函数inner,然后返回inner函数的内存地址
4.timmer函数执行完成,返回timmer函数的内部函数inner的内存地址,然后把inner的内存地址赋值给index变量
5.index是inner函数的内存地址,index变量加括号运行,实际上就是在运行inner函数
6.运行inner函数,定义程序开始时间。
7.执行timmer函数的变量func,在第2步知道,func这个变量就是index的内存地址,所以这里实际上是执行被装饰过后的index函数
8.index函数执行完成,定义程序的终止时间
9.统计并打印整个程序的执行过程中所花费的时间

这就是装饰器装饰index函数的执行流程

5.装饰器的简化使用

现在我又有另外一个函数home,现在我也想统计home函数的运行时间,可以把代码修改如下

    import time
import random def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner def index():
time.sleep(random.randrange(1,5))
print("welcome to index page") def home():
time.sleep(random.randrange(1,5))
print("welcome to home page") index=timmer(index)
index() home=timmer(home)
home()

运行程序,执行结果如下

welcome to index page
run time: 3.0
welcome to home page
run time: 4.0

可以看到,每次调用统计程序运行时间的装饰器timmer,都要先把被调用的函数的函数名作为参数传给timmer装饰器

然后再把timmer装饰器的执行结果赋值给被调用的函数名本身,最后才能调用被装饰的函数,太麻烦了有没有??

其实python中的装饰器可以简化成下面的格式

    import time
import random def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner @timmer
def index():
time.sleep(random.randrange(1,5))
print("welcome to index page") @timmer
def home():
time.sleep(random.randrange(1,5))
print("welcome to home page") index()
home()

程序执行结果

welcome to index page
run time: 2.0
welcome to home page
run time: 4.0

可以看出,使用@加装饰器名添加到被装饰对象的上方的方式也可以为一个函数添加装饰器中定义的功能

6.多个装饰器的定义与调用

在上面的例子里,定义并调用了一个统计程序运行时间的装饰器timmer,

如果现在想为index函数添加一个用户认证的功能,可以定义一个名为auth的装饰器

    import time
import random def auth(func):
def wrapper():
while True:
user=input("Input your username>>>:").strip()
pwd=input("Input your password>>>:").strip()
if user== "abcd" and pwd == "abcd1234":
print("login successful")
func()
break
else:
print("login error")
return wrapper @auth
def index():
time.sleep(random.randrange(1,5))
print("welcome to index page") index()

运行程序

    Input your username>>>:abcd             # 先输入错误的用户名和密码
Input your password>>>:1234
login error # 提示用户输入错误,登录失败
Input your username>>>:abcd # 让用户再次输入用户名和密码
Input your password>>>:abcd1234
login successful # 登录成功
welcome to index page # 执行index函数

从程序执行结果可以看出,用户登录密码验证的装饰器auth已经定义并被成功调用了

如果想为index函数添加用户认证的功能,又想统计index函数执行时间的功能,在使用装饰器的情况下该怎么调用呢

    import time
import random def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner def auth(func):
def wrapper():
while True:
user=input("Input your username>>>:").strip()
pwd=input("Input your password>>>:").strip()
if user== "abcd" and pwd == "abcd1234":
print("login successful")
func()
break
else:
print("login error")
return wrapper @timmer
@auth
def index():
time.sleep(2)
print("welcome to index page") index()

在上面的代码里,为index函数添加了两个装饰器,现在有一个问题,就是这两个装饰器究竟哪个先被调用,哪个后被调用呢??

来分析一下,

如果timmer装饰器先被调用,那么程序就会先执行timmer装饰器,然后再执行auth装饰器,提示输入用户名和密码,
这样一来timmer装饰器统计的时间就会包括输入用户名和密码的时间,这个时间会远远大于index函数睡眠的2秒种;
如果auth装饰器先被调用,timmer装饰器后被调用,那么timmer装饰器统计的运行时间就应该只包括index函数的执行时间值应该在2秒多一点点的时间范围内

运行程序,先输入错误的用户名和密码以使用程序的执行时间加长

Input your username>>>:abcd
Input your password>>>:abcd
login error
Input your username>>>:abcd
Input your password>>>:abcd1234
login successful
welcome to index page
run time: 12.759000062942505

从程序的执行结果可以知道,程序是先运行timmer装饰器,然后才运行auth装饰器,所以timmer统计的时间就包括了用户认证的时间,所以timmer统计到的程序运行时间远远大于index睡眠的2秒钟

所以这里得出一个结论:

当一个函数同时被两个装饰器装饰时,加上函数最上面的装饰器先执行,加在下面的装饰器先装饰

把上面例子里的timmer装饰器和auth装饰器位置互换一下

    import time
import random def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner def auth(func):
def wrapper():
while True:
user=input("Input your username>>>:").strip()
pwd=input("Input your password>>>:").strip()
if user== "abcd" and pwd == "abcd1234":
print("login successful")
func()
break
else:
print("login error")
return wrapper @auth
@timmer
def index():
time.sleep(2)
print("welcome to index page") index()

运行index函数,依然先输入错误的用户名和密码,增加用户认证的时间

Input your username>>>:abcd
Input your password>>>:abcd
login error
Input your username>>>:abcd
Input your password>>>:abcd1234
login successful
welcome to index page
run time: 2.0

可以看到,这次timmer统计到的时间只包含index函数的运行时间,不包含用户进行认证的时间

来分析一下上面例子中,index函数被timmer装饰器和auth装饰器装饰的代码装饰流程

    @auth           # index=auth(timmer(index))
@timmer # index=timmer(index)
def index():
time.sleep(2)
print("welcome to index page")

在上面得出结论,一个函数同时被两个装饰器时,加在下面的装饰器先装饰

1.timmer装饰器装饰原始的index,可以写成:index=timmer(index)
2.在timmer装饰器中,timmer装饰器实际上是返回inner的内存地址,所以在这里,index=inner
3.timmer装饰器装饰完成后,由auth装饰器来装饰,此时可以写成index=auth(index),
4.这里auth括号里的index已经不再是原始index函数,而是已经被timmer装饰过后的index了,所以index=auth(timmer(index))
5.又因为timmer装饰的结果等于inner函数的内存地址,所以:index=auth(inner)

至此,两个装饰器的装饰过程已经知道了,来看程序的执行过程

6.程序先执行auth装饰器,进入用户认证,请用户输入用户名和密码
7.用户输入正确的用户名和密码后,开始执行func函数,也已经上面分析的inner函数
8.timmer装饰器先定义程序的开始运行时间,然后运行func函数,也就是原生的index函数
9.index函数先睡眠2秒,然后执行print语句,再定义程序的结束时间
10.最后统计并打印程序的运行时间,至此程序运行完毕。

所以这里用户输入用户名和密码的时间不会被timmer装饰器统计在内

7.被装饰函数参数的设置与定义

先来看一段代码

    import time

    def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner @timmer
def index():
time.sleep(2)
print("welcome to index page") @timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name)

如上所示,home函数添加了一个参数,而index函数并没有参数

按照正常的函数的定义与调用方式,调用index函数和home函数的方式应该是下面这种形式

index()
home("python")

然后我们运行程序就会发现,程序抛出了异常

  File "E:\python_learn\py_code\test.py", line 28, in <module>
home("python")
TypeError: inner() takes 0 positional arguments but 1 was given

说个异常说明inner函数不需要位置参数,但是我们给了一个位置参数

回到timmer装饰器定义的部分,可以看到,timmer装饰器的内部函数确实没有定义参数

这样一来,timmer装饰器只能用于装饰没有参数的函数了,

我们可以在timmer装饰器定义的时候为inner函数添加一个参数

    import time

    def timmer(func):
def inner(name):
start_time=time.time()
func(name)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner @timmer
def index():
time.sleep(2)
print("welcome to index page") @timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name) index()
home("python")

但是这样一来,timmer装饰器装饰index函数的时候又会抛出异常,因为index函数没有参数

File "E:\python_learn\py_code\test.py", line 27, in <module>
index()
TypeError: inner() missing 1 required positional argument: 'name'

在不知道被装饰函数的参数个数的情况下,即被装饰函数的参数可变长,且形式不固定的时候,

可以使用*args**kwargs,把上面的代码修改

    import time

    def timmer(func):
def inner(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner @timmer
def index():
time.sleep(2)
print("welcome to index page") @timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name) index()
home("python")

再次运行程序,查看运行结果

welcome to index page
run time: 2.0
welcome to python home page
run time: 3.0

由上可知,在不知道被装饰函数的参数个数时,可以使用*args**kwargs来表示任意长度任意形式的参数

8.被装饰函数的返回值

修改上面的代码,为home函数定义一个返回值,分别打印index函数和home函数的返回值

    import time

    def timmer(func):
def inner(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner @timmer
def index():
time.sleep(2)
print("welcome to index page") @timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name)
return("home func") index_res=index()
print(index_res) home_res=home("python")
print(home_res)

运行程序,可以看到

welcome to index page
run time: 2.0
None
welcome to python home page
run time: 3.0
None

可以看到,home函数中定义的返回值并没有被打印出来,显示的值为None

因为这里执行的home函数不是原始定义的home函数,而是wrapper函数的执行结果

因为wrapper函数并没有定义返回值,所以执行被装饰后的home函数并没有打印出返回值

修改代码,在timmer装饰器中定义并返回被装饰函数执行的返回值

    import time

    def timmer(func):
def inner(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return res
return inner @timmer
def index():
time.sleep(2)
print("welcome to index page") @timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name)
return("home func") index_res=index()
print(index_res) home_res=home("python")
print(home_res)

再次执行函数,查看执行结果

    welcome to index page
run time: 2.0
None welcome to python home page
run time: 3.0
home func

可以看来,原始home函数中定义的返回值被打印出来了

结论:

如果被装饰函数没有定义返回值,timmer装饰器装饰后的返回值为None
而如果被装饰函数定义了返回值,则timmer装饰器装饰后则返回被装饰函数的返回值

9.wraps内置方法的作用

查看一个函数的帮助文档有两种方法

func_name.__doc__
help(func_name)

先来看一个例子,定义timmer装饰器和index函数,并且都添加了帮助文档

import time

def timmer(func):
def inner(*args,**kwargs):
'wrapper inner function'
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return res
return inner def index():
'index function'
time.sleep(2)
print("welcome to index page")

在index没有被timmer装饰前,来查看index的帮助文档

print(index.__doc__)

程序运行结果

index function

然后为index添加timmer装饰器,再次查看index函数的帮助文档

import time

def timmer(func):
def inner(*args,**kwargs):
'wrapper inner function'
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return res
return inner @timmer
def index():
'index function'
time.sleep(2)
print("welcome to index page") print(index.__doc__)

程序运行结果

wrapper inner function

可以看到,在为index函数添加装饰器后,index函数的帮助文档变成装饰器timmer内部函数的帮助文档了

换句话说,就是原始index函数内部的数据被装饰器timmer修改了

怎么样才能在保留原始被装饰函数的数据的前提下,为函数添加新功能呢??就是python内置的wraps装饰器

导入wraps装饰器,修改上面的代码,为timmer的内部函数添加wraps装饰器,然后再次查看被装饰函数的帮助文档

import time
from functools import wraps def timmer(func):
@wraps(func)
def inner(*args,**kwargs):
'wrapper inner function'
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return res
return inner @timmer
def index():
'index function'
time.sleep(2)
print("welcome to index page") print(index.__doc__)

运行程序,执行结果如下

index function

可以看到,index函数即使添加了装饰器,其内部的原始数据仍然没有被装饰器修改

从上面的示例可以看出,wraps装饰器的作用就是保留被装饰对象的原始数据信息

python函数式编程之装饰器(一)的更多相关文章

  1. python函数式编程之装饰器(二)

    以前用装饰器,都是定义好了装饰器后,使用@装饰器名的方法写入被装饰函数的正上方 在这里,定义的装饰器都是没有参数的 在定义装饰器的函数的时候,没有在括号里定义参数,这就叫做无参装饰器 既然有无参装饰器 ...

  2. Python函数式编程之装饰器

    原则:对修改是封闭的,对扩展是开放的,方法:一般不修改函数或者类,而是扩展函数或者类 一:装饰器 允许我们将一个提供核心功能的对象和其他可以改变这个功能的对象’包裹‘在一起, 使用装饰对象的任何对象与 ...

  3. Python模块化编程与装饰器

    Python的模块化编程 我们首先以一个例子来介绍模块化编程的应用场景,有这样一个名为requirements.py的python3文件,其中两个函数的作用是分别以不同的顺序来打印一个字符串: # r ...

  4. python高级编程之装饰器04

    from __future__ import with_statement # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrat ...

  5. python函数式编程之返回函数、匿名函数、装饰器、偏函数学习

    python函数式编程之返回函数 高阶函数处理可以接受函数作为参数外,还可以把函数作为结果值返回. 函数作为返回值 def laxy_sum(*args): def sum(): ax = 0; fo ...

  6. Python编程举例-装饰器

    装饰器的通常用途是扩展已定义好的函数的功能 一个浅显的装饰器编程例子 #装饰器函数 def outer(fun): def wrapper(): #添加新的功能 print('验证') fun() r ...

  7. Python函数式编程(进阶2)

    转载请标明出处: http://www.cnblogs.com/why168888/p/6411915.html 本文出自:[Edwin博客园] Python函数式编程(进阶2) 1. python把 ...

  8. Python中利用函数装饰器实现备忘功能

    Python中利用函数装饰器实现备忘功能 这篇文章主要介绍了Python中利用函数装饰器实现备忘功能,同时还降到了利用装饰器来检查函数的递归.确保参数传递的正确,需要的朋友可以参考下   " ...

  9. C#中的 Attribute 与 Python/TypeScript 中的装饰器是同个东西吗

    前言 最近成功把「前端带师」带入C#的坑(实际是前端带师开始从cocos转unity游戏开发了) 某天,「前端带师」看到这段代码后问了个问题:[这个是装饰器]? [HttpGet] public Re ...

随机推荐

  1. HDU 2689 Sort it【树状数组】

    Sort it Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Sub ...

  2. BZOJ3916: [Baltic2014]friends

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3916 复习一下hash(然后被傻叉错误卡了半天TAT... 取出一个字串:h[r]-h[l-1 ...

  3. MongoDB基本命令操作

    在上一篇随笔中记录了如何在Centos7上安装MongoDB数据库,这一篇我们就一起来学学基本的操作命令. 安装完成后,shell交互式下输入mongo就可以直接无密码登录到数据库. show dbs ...

  4. UEP-自定义持久化类

    HY-UEP提供了两种持久化方式:基于JPA的持久化机制和自定义持久化机制,JPA的持久化机制 比较符合现在的软件开发模式,支持各种主流数据库;自定义持久化时我们平台产品的特有持久化 机制的升级,在综 ...

  5. 程序员之殇 —— (The Beginning of the End)噩梦、崩坏

    Look at all those faces out there (当我环视周遭的一张张脸孔) We are so different(我们是如此的不同) But we have one thing ...

  6. Spark算子--coalesce和repartition

    coalesce和repartition--Transformation类算子 代码示例

  7. MYSQL 数据库导入导出命令

    在不同操作系统或MySQL版本情况下,直接拷贝文件的方法可能会有不兼容的情况发生.所以一般推荐用SQL脚本形式导入.下面分别介绍两种方法. MySQL命令行导出数据库 1,进入MySQL目录下的bin ...

  8. [拾 得] 一枚迷人的贝壳 SHELL / Linux | shell 脚本初步入门

    坚持知识分享,该文章由Alopex编著, 转载请注明源地址: http://www.cnblogs.com/alopex/   索引: 什么是shell shell的分类 shell脚本的执行方式   ...

  9. ios开发 第一天

    alloc 分配内存(类方法) init 调用构造函数 id可以替代任何数据类型(不加*号) 错误现象: 2013-06-27 21:44:21.769 FieldButtonFun[3465:113 ...

  10. PowerDesigner设置null约束

    在PDM的表属性的字段列表中每行最后的P,F,M中的M(Mandatory)打勾就可以了,这样在生成的SQL中会变成not NULL Mandatory:强制的,不知道是不是可以理解为必须赋值的