1.装饰器

  所谓装饰器一般是对已经使用(上线)的函数增加功能.

  但是因为一般的大公司的严格按照开放封闭原则(对扩展是开放的,对修改是封闭的),不会让你修改原本的函数.

  装饰器就是在不改变原本的函数且不改变原本函数的调用方式上,为原本的函数增加功能的东西.

  所以需求就是:

  1.不改变原函数

  2.不改变原函数的调用方式

  3.增加函数的功能

1.1最基本的装饰器

  假设存在这样的原函数

 import time
def func():
time.sleep(0.01)
print('func is running!')

  很简单的一个函数,我们现在要为这个原函数增加一个计算函数运行时间的功能.

  我们知道计算函数运行时间的函数可以这么写:

 def time_func():
start = time.time() #记录运行前时间
func() #调用原函数
end = time.time() #记录运行结束时间
print(end - start) time_func()

  运行结果:

 func is running!
0.01015472412109375

  这样虽然可以算出函数的运行时间,但是对原函数的调用就完全变了,为了让调用不变,我们可以将装饰器函数写成这样:

 def time_func(f):
def inner():
start = time.time() #记录运行前时间
f() #调用原函数
end = time.time() #记录运行结束时间
print(end - start)
return inner #返回inner的地址 print(func) #func函数的地址
func = time_func(func) #将func函数的内存地址发给time_func函数
print(func) #inner函数的地址
func()

  运行结果: 

 <function func at 0x0000023371712E18>
<function time_func.<locals>.inner at 0x00000233735867B8>
func is running!
0.010173320770263672

  可以看到这个装饰器函数实现了通过原函数的调用方式---func()---实现了计算函数的运行时间功能的增加.实现的最基本的装饰器功能.

  分析一下实现过程:

  1.将func函数的内存地址作为参数传给time_func函数,使inner中的f()就相当于func()调用原函数

  2.time_func将inner函数的地址赋值给变量func,让第12行的func()等价于inner()

  使得表面上是:

      func()调用原函数

  实际上是:

      func()等价于inner()

      inner调用f()

      func作为参数传给time_func函数,即f()等价于func()

1.2  有返回值的装饰器

  最开始发原函数太简单了,我们给他增加返回值:

 import time
def func():
time.sleep(0.01)
print('func is running!')
return 'Hello World!'

  通过分析,我们知道前面的func()其实是inner(),而inner函数本身没有返回值,我们需要在inner()中增加接受原函数的返回值并返回的功能,可以写作:

 def time_func(f):
def inner():
start = time.time() #记录运行前时间
ret = f() #ret接受原函数的返回值原函数 ----增加的行
end = time.time() #记录运行结束时间
print(end - start)
return ret #返回原函数的返回值 ----增加的行
return inner #返回inner的地址
 func = time_func(func)
print(func())

  运行结果:

 func is running!
0.01049661636352539
Hello World!

1.3 有参数的装饰器

  有的返回值,还缺什么才比较像基本的函数?    参数

  先来简单一点的,有限个参数的,原函数写作:

 import time
def func(a,b):
time.sleep(0.01)
print('func is running!',a,b)
return 'Hello World!'

  有参数和返回值不是一样么,在inner函数后面加上参数不就得了,装饰器可以写做:

 def time_func(f):
def inner(a,b): #增加参数                 --增加的行
start = time.time() #记录运行前时间
ret = f(a,b) #将inner函数接到的参数传给原函数 --增加的行
end = time.time() #记录运行结束时间
print(end - start)
return ret #返回原函数的返回值
return inner #返回inner的地址
 func = time_func(func)
print(func('haha','heihei')) #传入haha和heihei

  运行结果:

 func is running! haha heihei
0.010972023010253906
Hello World!

  成功了!

  实际中,原函数的参数可能有很多种,数量也不可能固定不变,不可能原函数的参数一变,你就去改装饰器函数,这样太麻烦

  为了解决参数的问题,我们提出王炸策略

  *args   ------动态参数,可以接受任意数量的位置参数

  **kwargs ----动态参数,可以接受任意数量的关键字参数

  这种策略在python内置函数中多有用到,比如len函数:

 def len(*args, **kwargs): # real signature unknown
""" Return the number of items in a container. """
pass

  它能接受一切数量的参数,无论是位置参数还是关键字参数,仿照这个,我们可以写出接受任意参数的装饰器函数

  原函数:

 import time
def func(*args, **kwargs):
time.sleep(0.01)
print('func is running!',*args, **kwargs)
return 'Hello World!'

  装饰器函数:

 def time_func(f):
def inner(*args, **kwargs): #增加参数    ----修改部分
start = time.time() #记录运行前时间
ret = f(*args, **kwargs) #将inner函数接到的参数传给原函数-----修改部分
end = time.time() #记录运行结束时间
print(end - start)
return ret #返回原函数的返回值
return inner #返回inner的地址
 # print(func)         #func函数的地址
func = time_func(func) #将func函数的内存地址发给time_func函数
# print(func) #inner函数的地址
print(func('haha','heihei',1,2,3,4,5,6,3,2,1))

  运行结果:

 func is running! haha heihei 1 2 3 4 5 6 3 2 1
0.010983467102050781
Hello World!

1.4  装饰器的固定模式

  前面以求运行时间为例,将其进行推广就可以得到装饰器的固定模式:

 def wrapper(f):    #装饰器函数,f是被装饰的函数
def inner(*args,**kwargs):
'''在被装饰函数之前要做的事'''
ret = f(*args,**kwargs) #被装饰的函数
'''在被装饰函数之后要做的事'''
return ret
return inner func = wrapper(func) #感觉有点多余

  很明显发现第九行看起来有点多余,于是有了语法糖的概念

  语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。----by百度百科

  装饰器的语法糖就是在被装饰函数,也就是原函数的前面加@装饰器函数

  像这样:

 @wrapper
def func(*args, **kwargs):
time.sleep(0.01)
print('func is running!',*args, **kwargs)
return 'Hello World!'

  由于@wrapper 等价于func = wrapper(func)

  而@wrapper要写在原函数上面,按照python解释器从上到下的工作规律,如果装饰器函数在原函数下面,@wrapper相当于没有意义,会报错,所以装饰器函数必须在原函数之前,整体写做这样:

 import time

 def wrapper(f):    #装饰器函数,f是被装饰的函数
def inner(*args,**kwargs):
'''在被装饰函数之前要做的事'''
ret = f(*args,**kwargs) #被装饰的函数
'''在被装饰函数之后要做的事'''
return ret
return inner @wrapper
def func(*args, **kwargs):
time.sleep(0.01)
print('func is running!',*args, **kwargs)
return 'Hello World!'
print(func('haha','heihei',1,2,3,4,5,6,3,2,1))

  运行结果:

 func is running! haha heihei 1 2 3 4 5 6 3 2 1
Hello World!

  

<Python基础>装饰器的基本原理的更多相关文章

  1. python基础——装饰器

    python基础——装饰器 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数. >>> def now(): ... print('2015-3-25 ...

  2. python基础—装饰器

    python基础-装饰器 定义:一个函数,可以接受一个函数作为参数,对该函数进行一些包装,不改变函数的本身. def foo(): return 123 a=foo(); b=foo; print(a ...

  3. python 基础——装饰器

    python 的装饰器,其实用到了以下几个语言特点: 1. 一切皆对象 2. 函数可以嵌套定义 3. 闭包,可以延长变量作用域 4. *args 和 **kwargs 可变参数 第1点,一切皆对象,包 ...

  4. day5学python 基础+装饰器内容

    基础+装饰器内容 递归特性# 1.必须有一个明确的结束条件# 2.每次进入更深一层递归时,问题规模相比上次递归应有所减少# 3.递归效率不高 def run(n): print(n) if int(n ...

  5. Python自动化 【第四篇】:Python基础-装饰器 生成器 迭代器 Json & pickle

    目录: 装饰器 生成器 迭代器 Json & pickle 数据序列化 软件目录结构规范 1. Python装饰器 装饰器:本质是函数,(功能是装饰其它函数)就是为其他函数添加附加功能 原则: ...

  6. python基础 (装饰器,内置函数)

    https://docs.python.org/zh-cn/3.7/library/functions.html 1.闭包回顾 在学习装饰器之前,可以先复习一下什么是闭包? 在嵌套函数内部的函数可以使 ...

  7. python基础-装饰器,生成器和迭代器

    学习内容 1.装饰器 2.生成器 3.迭代器 4.软件目录结构规范 一:装饰器(decorator) 1.装饰器定义:本质就是函数,用来装饰其他函数,即为其他函数添加附加功能. 2.装饰器原则:1)不 ...

  8. python基础===装饰器@property 的扩展

    以下来自Python 3.6.0 Document: class property(fget=None, fset=None, fdel=None, doc=None) Return a proper ...

  9. python基础-装饰器

    一.什么是装饰器 装饰器本质就是函数,功能是为其他函数附加功能 二.装饰器遵循的原则 1.不修改被修饰函数的源代码 2.不修改被修饰函数的调用方式 三.实现装饰器的知识储备 装饰器=高阶函数+函数嵌套 ...

随机推荐

  1. Java——Eclipse使用

    从这开始使用IDE啦~ ①File → New → Java Project →填写工程名字,选择jdk版本,其他默认,单击finish. ②在Src(源文件)上鼠标右键 → new → packag ...

  2. 转-C/C++ new与malloc区别

    1.属性 new/delete是C++关键字,需要编译器支持.malloc/free是库函数,需要头文件支持. 2.参数 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计 ...

  3. scp 传输下载

    利用scp传输文件 1.从服务器下载文件 scp username@servername:/path/filename /tmp/local_destination 例如scp codinglog@1 ...

  4. 【洛谷】P1962

    题目链接:https://www.luogu.org/problemnew/show/P1962 题意:求fib数列的第n项,很大.mod 1e9+7. 题解:BM直接推. 代码: #include ...

  5. linux安装openoffice,并解决中文乱码

    1.安装openoffice 官网http://www.openoffice.org/zh-cn/download/下载 2.解压并进入文件夹: cd /zh-cn/RPMS yum localins ...

  6. MVC 传递数据 从前台到后台,包括单个对象,多个对象,集合

    MVC 传递数据 从前台到后台,包括单个对象,多个对象,集合 1.基本数据类型 我们常见有传递 int, string, bool, double, decimal 等类型. 需要注意的是前台传递的参 ...

  7. js正则表达式常见面试题

    1 . 给一个连字符串例如:get-element-by-id转化成驼峰形式. var str = "get-element-by-id"; var reg = /-\w/g; / ...

  8. 启动 AXD 配置开发板

    1. 启动 AXD 先启动 Dragon­ICE Server 程序. 按如下步聚启动 AXD: 开始­>所有程序­>ARM Developer Suite v1.2­>AXD De ...

  9. 在 Mac 上使用 `sed` 命令的几个坑

    不可忽略的备份格式 sed -i 's/hello/world/g' hello.text 上面这行代码,可以在 linux 上运行,作用是将找到的 hello 替换为 world,并且直接保存修改到 ...

  10. Linux queue.h之TAILQ队列分析

    转自 这两天想看看memcached的实现,所以先学习了libevent,使用起来还是比较简单的,其实是对select/poll/kqueue等的封装,学习libevent过程中又遇到了linux下队 ...