有时候我们想为多个函数,同意添加某一种功能,比如及时统计,记录日志,缓存运算结果等等,而又不想改变函数代码

那就定义装饰器函数,用它来生成一个在原函数基础添加了新功能的函数,代替原函数

参考金角大王的博客

装饰器从无到有的过程

比如现在有三个已经实现功能的函数

def shoping():

print 'shoping'

def info():

print 'information'

def talking():

print 'talking'

然后然后想给每个模块加一个登陆验证功能

user_status = False # 先定义变量

def login():

_username = "ketchup" #假装这是DB里存的用户信息

_password = "123456" #假装这是DB里存的用户信息

global user_status

if user_status == False:

username = input("user:")

password = input("pasword:")

if username == _username and password == _password:

print("welcome login....")

user_status = True

else:

print("wrong username or password!")

else:

print("用户已登录,验证通过...")

一开始的想法是这样,在每个函数中添加函数

def shoping():

login()

print 'shoping'

def info():

login()

print 'info'

def talking():

login()

print 'talking'

但是,软件开发要遵循"开放封闭的原则’,它规定已经实现的功能代码不允许被修改,但可以被扩展,

封闭:就是已经实现功能的代码块,尽量不在内部做修改

开放:对扩展开发

然后修改为:

user_status = False # 先定义变量

def login(func):

_username = "ketchup" #假装这是DB里存的用户信息

_password = "123456" #假装这是DB里存的用户信息

global user_status

if user_status == False:

username = input("user:")

password = input("pasword:")

if username == _username and password == _password:

print("welcome login....")

user_status = True

else:

print("wrong username or password!")

if user_status == True:

func() #如果登陆成功,就调用传入的函数

def shoping():

print 'shoping'

def info():

print 'info'

def talking():

print 'talking'

login(shoping) #这样,先执行login函数,在login函数内部就会调用执行shoping函数

login(info)

login(talking)

但是每次这样调用,如果很多模块要加这个功能,大家都要去调用一下,那太麻烦了

python中一切皆对象,可以用

shopping = login(shoping)

shopping()

这样的方式执行shopping()就等于执行login(shopping)

但是在前面赋值 shopping = login(shoping)的时候,就已经调用login()函数了,执行了里面的func()函数

要解决这个问题,就要在shopping = login(shoping)这次调用的时候,不执行func函数,只是把一个函数名给了他,然后下面shoppin()函数执行的时候才会执行,

所以,就要在login函数里加一层闭包函数

def login(func):

def wrapper():

_username = "ketchup" #假装这是DB里存的用户信息

_password = "123456" #假装这是DB里存的用户信息

global user_status

if user_status == False:

username = input("user:")

password = input("pasword:")

if username == _username and password == _password:

print("welcome login....")

user_status = True

else:

print("wrong username or password!")

if user_status == True:

func() #如果登陆成功,就调用传入的函数

return wrapper

这样的话,第一次shopping = login(shopping) 的时候,shopping 的值为wrapper

后面

def shoping():

print 'shoping'

的时候,才执行wrapper() ,才调用里面的func()

然后python对这种写法有一个语法糖 这种写法就等于在shopping函数前面加上@login

如果要在shopping里面传参数怎么办呢?

那就要在login里面把参数拿过来,然后传给func

def login(func):

def wrapper(*args,**kwargs):

_username = "ketchup" #假装这是DB里存的用户信息

_password = "123456" #假装这是DB里存的用户信息

global user_status

if user_status == False:

username = input("user:")

password = input("pasword:")

if username == _username and password == _password:

print("welcome login....")

user_status = True

else:

print("wrong username or password!")

if user_status == True:

func(*args,**kwargs) #如果登陆成功,就调用传入的函数

return wrapper

@login

def shoping(num):

print 'shoping %d 个'%num

@login

def info():

print 'info'

@login

def talking():

print 'talking'

如果这时候要对login传参数呢,那就在多加一层对login参数的判断,比如,要判断是从qq还是weixin登陆的

def login(auth_type):

def auth(func):

def wrapper(*args,**kwargs):

if auth_type == 'qq':

_username = "ketchup" #假装这是DB里存的用户信息

_password = "123456" #假装这是DB里存的用户信息

global user_status

if user_status == False:

username = input("user:")

password = input("pasword:")

if username == _username and password == _password:

print("welcome login....")

user_status = True

else:

print("wrong username or password!")

if user_status == True:

func(*args,**kwargs) #如果登陆成功,就调用传入的函数

else:

print('only support qq or weixin ')

return wrapper

return auth

下面以几个实际问题的例子深入理解装饰器

1-实现斐波那契的几个方法

为什么要用装饰器实现斐波那契,因为实现过程中有很多重复的步骤,所以这样很浪费

image.png

def memo(func):

cache = {}

def wrap(*args):

if args not in cache:

cache[args] = func(*args)

return cache[args]

return wrap

@memo

def fib1(n):

if n<=1:

return 1

return fib1(n-1) + fib1(n-2)

def fib2(n,cache = None):

if cache is None:

cache = {}

if n in cache:

return cache[n]

if n<= 1:

return 1

cache[n] = fib2(n-1,cache) + fib2(n-2,cache)

return cache[n]

def fib3(n):

a,b = 1,1

while n >= 2:

a,b = b, a+b

n -= 1

return b

def fib4(n):

li = [1,1]

while n >=2:

li.append(li[-2]+li[-1])

n -= 1

return li[-1]

测试:

/ print(fib1(500))

print(fib2(500))

print(fib3(500))

print(fib4(500))

22559151616193633087251269503607207204601132491375819058863886641847462773868688340

5015987052796968498626

22559151616193633087251269503607207204601132491375819058863886641847462773868688340

5015987052796968498626

22559151616193633087251269503607207204601132491375819058863886641847462773868688340

5015987052796968498626

当到500 的时候,fib1已经报错了,RecursionError: maximum recursion depth exceeded in comparison

报错是因为每一级递归都需要调用函数, 会创建新的栈,

随着递归深度的增加, 创建的栈越来越多, 造成爆栈

当1000的时候,fib2 也报这个错误了

因为python 不支持尾递归,所以超过1000也会报错

ERROR

下面小练习:

如果有n级台阶,每一次可以跨1-3级台阶,那么可以有多少种走法

@memo

def climb(n, steps):

count = 0

if n == 0:

count =1

elif n>0:

for step in steps:

count += climb(n - step, steps)

return count

#测试

print climb(200,(1,2,3))

52622583840983769603765180599790256716084480555530641

有时候我们想为多个函数,同意添加某一种功能,比如及时统计,记录日志,缓存运算结果等等

而又不想改变函数代码

定义装饰器函数,用它来生成一个在原函数基础添加了新功能的函数,代替原函数

2-为被装饰的函数保留原来的元数据

解决方法:

使用标准库functools中的装饰器wraps装饰内部包裹函数,可以定制原函>数的某些属性,更新到包裹函数上面

先看一下函数的元数据一般有哪些:

f.name : 函数的名字

f.doc : 函数的文档字符串,对这个函数的一些描述

f.moudle : 函数所属的模块名

f.dict : 属性字典

f.defaults : 默认参数元祖

In [1]: def f():

...: '''f doc'''

...: print('ffff')

...:

In [11]: f.__doc__

Out[11]: 'f doc'

In [12]: f.__module__

Out[12]: '__main__'

In [13]: f.__defaults__

In [14]: def f(a, b=1, c=[]):

...: print a,b,c

...:

In [15]: f.__defaults__

Out[15]: (1, [])

In [17]: f.__defaults__[1].append('abc')

In [19]: f(5)

5 1 ['abc']

所以在默认参数里尽量不要使用可变对象

In [20]: f.__closure__

In [21]: def f():

...: a = 2

...: return lambda k:a**k

...:

In [22]: g = f()

In [27]: g.__closure__

Out[27]: (,)

In [28]: c = g.__closure__[0]

In [29]: c

Out[29]:

In [30]: c.cell_contents

Out[30]: 2

问题:

我们在使用装饰器装饰函数了之后,查看函数的元数据会显示是装饰器函数的元数据,而不是原函数的

def mydecorator(func):

def wrapper(*args,**kwargs):

'''wrapper function'''

print 'in wrapper'

return wrapper

@mydecorator

def example():

'''example function'''

print 'in example'

print example.__name__

print example.__doc__

运行结果:

wrapper

wrapper function

解决1:

def mydecorator(func):

def wrapper(*args,**kwargs):

'''wrapper function'''

wrapper.__name__ = func.__name__

print 'in wrapper'

return wrapper

但是代码很不优雅

解决2:

from functools import update_wrapper

def mydecorator(func):

def wrapper(*args,**kwargs):

'''wrapper function'''

update_wrapper(wrapper, func, ('__name__', '__doc__'), ('__dic__'))

print 'in wrapper'

return wrapper

functools 里有两个默认参数,WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES 其实就对应着("module’, 'name', 'doc'), ('dic'),)

所以可以直接不用写,这两个是默认带的

解决3:

from functools import update_wrapper

def mydecorator(func):

def wrapper(*args,**kwargs):

'''wrapper function'''

update_wrapper(wrapper, func)

print 'in wrapper'

return wrapper

最后来说这个wraps,这个wraps 就是一个边界函数,他也是一个装饰器,是一个带参数的装饰器,可以直接用

使用标准库functools中的装饰器wraps装饰内部包裹函数,可以定制原函数的某些属性,更新到包裹函数上面

解决end:

from functools import update_wrapper,wraps

def mydecorator(func):

@wraps(func)

def wrapper(*args,**kwargs):

'''wrapper function'''

#update_wrapper(wrapper, func)

print 'in wrapper'

return wrapper

3-定义带参数的装饰器

实现一个装饰器,他用来检查呗装饰函数的参数类型,装饰器可以通过参数致命函数参数的类型,调用时如果检测出类型不匹配则抛出异常

@typeassert(str,int,int)

def f(a,b,c):

....

@typeassert(y= list)

def g(x,y):

...

解决方案, 带参数的装饰器也就是根据参数定制出一个装饰器可以看成生产装饰器的工厂,每次调用typeassert 返回一个特定的装饰器,然后用它去装饰其他函数

from inspect import signature

def typeassert(*ty_args, **ty_kargs):

def decorator(func):

# func -> a,b

# d = {'a': int, 'b': str}

sig = signature(func)

btypes = sig.bind_partial(*ty_args, **ty_kargs).arguments

def wrapper(*args, **kargs):

# arg in d, instance(arg, d[arg])

for name, obj in sig.bind(*args, **kargs).arguments.items():

if name in btypes:

if not isinstance(obj, btypes[name]):

raise TypeError('"%s" must be "%s"' % (name, btypes[name]))

return func(*args, **kargs)

return wrapper

return decorator

@typeassert(int, str, list)

def f(a, b, c):

print(a, b, c)

测试

f(1,'666',[1,2,3])

f(1,2,[])

TypeError: 'b' must be '

我们来看看signature 是怎么用的

In [1]: from inspect import signature

In [2]: def f(a, b, c=1):pass

In [16]: c = sig.parameters

In [17]: c

Out[17]:

mappingproxy({'a': ,

'b': ,

'c': })

In [18]: c = sig.parameters['c']

In [20]: c.default

Out[20]: 1

如果想对a b c 简历一个类型的字典{"a’:'int','b':'str','c':'list'}

In [23]: bargs = sig.bind(str,int,int)

In [24]: bargs.args

Out[24]: (str, int, int)

In [26]: bargs.arguments

Out[26]: OrderedDict([('a', str), ('b', int), ('c', int)])

In [29]: bargs.arguments['a']

Out[29]: str

但是如果sig.bind(str)

只传了一个参数,就会报错,如果想只穿一个参数的话,就用

sig.bind_partial(str)

例:写一个出错重试次数的装饰器,可以用来处理HTTP超时等

import requests

def retry(attempt):

def decorator(fuc):

def wrapper(*args, **kw):

att = 0

while att < 3:

try:

return func(*args, **kw)

except Exception as e:

att += 1

return wrapper

return decorator

#重试次数

@retry(attempt=3)

def get_response(url):

r = resquests.get('www.baidu.com')

return r.content

python中的装饰器有哪些-python 装饰器以及开发中常用的例子相关推荐

  1. [项目过程中所遇到的各种问题记录]工具篇——.NET开发时常用的工具类库

    在日常开发的过程当中我们总是会根据项目的开发需求将一些公用的类或者方法进行抽象封装,这些类或方法的抽象封装可能是基于某个项目或者多个项目,最常见的应该就是SQLHelper了,这些类库在实际使用的过程 ...

  2. 在python中如何判断数组中的数据为空值_缓存穿透问题,开发中真实解决方案

    前几天我们讲到了缓存的读写策略(缓存读写策略,我们开发人员都是这么用的)以及如何搭建高可用缓存系统(分布式缓存的高可用方案,我们都是这么做的),都是为了能在基础架构上让我们的缓存命中率能更高,防止大量 ...

  3. protobuf在java中使用_记录:Protocol Buffers(protobuf)在Java开发中使用

    1.编写一个.proto文件命名为:addressbook.proto,该文件内容来自protocal-buffers官网 2.使用protoc-2.6.0-win32.zip解压后的protoc.e ...

  4. python是干什么用的-使用Python究竟可以做什么?下面是Python的3个主要应用

    原文传送门: https://medium.com/free-code-camp/what-can-you-do-with-python-the-3-main-applications-518db9a ...

  5. python手机版_手机编程软件python

    要在e63手机上编写python程序必须先安装py平台,然后下载安装比如ped或者ipro7等编程软件就可以进行编程了.哈哈,有趣的很.解释器也可以安上,不过用处不大哈 . 用python写安卓app ...

  6. python爬虫实验报告怎么写_[Python]新手写爬虫全过程(转)

    今天早上起来,第一件事情就是理一理今天该做的事情,瞬间get到任务,写一个只用python字符串内建函数的爬虫,定义为v1.0,开发中的版本号定义为v0.x.数据存放?这个是一个练手的玩具,就写在tx ...

  7. Java ME游戏开发中,碰撞检测算法在Java?ME中的实现(

    2019独角兽企业重金招聘Python工程师标准>>> 在Java ME游戏开发中,碰撞检测算法在Java?ME中的实现(百搜技术) 在Java ME游戏开发中,经常需要进行碰撞检测 ...

  8. php验证器的调用,ThinkPHP5 验证器的具体使用

    这篇文章主要介绍了关于ThinkPHP5 验证器的具体使用,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下 前言: 我们在做API开发的时候,我们会接受客户端传来的参数,大家都知道这个参 ...

  9. 前端开发中的MCRV模式

    针对前端开发中基于ajax的复杂页面开发所面临的代码规模大,难以组织和维护,代码复用性.扩展性和适应性差等问题,本文尝试以MVC思想为基础,结合Web前端开发中内容-结构-表现-行为相分离的开发标准, ...

  10. 如何解决嵌入式培训开发中的PCB设计问题?

    不管是嵌入式培训开发还是学习嵌入式的过程中肯定都是或多或少都能遇到一些小问题的,但是不管这个问题有多小如果说你不解决好的话那么你就很难能够进行到下一步的.今天粤嵌科技就也来给大家说下如何解决嵌入式培训 ...

最新文章

  1. 机器学习一 -- 什么是监督学习和无监督学习?
  2. 阿里云面试官:如果是MySQL引起的CPU消耗过大,你会如何优化?
  3. Java实现队列(循环队列,链队列)
  4. 超详细 图解 : IntelliJ IDEA 逆向生成 JAVA 实体类
  5. 顺丰不顺、京东动荡,都是物流基因惹的祸?
  6. sklearn 决策树例子_使用 sklearn 构建决策树并使用 Graphviz 绘制树结构
  7. linux硬盘类型怎么选,如何选择linux系统安装类型
  8. 简单记录jasypt使用
  9. 如何计算十五个字节(多字节)的CRC16校验
  10. gantt图 classDiagram图应用举例 南北朝更迭图 南北朝帝王关系类图
  11. Failure recovering jobs: Lock wait timeout exceeded; try restarting transaction
  12. 【LeetCode】64. 最小路径和 结题报告 (C++)
  13. 思维模型 DISC色彩性格
  14. 港科夜闻|香港科技大学史维校长及汪扬副校长出席“一流大学建设系列研讨会--2021”暨中国大学校长联谊会线上会议...
  15. win10 安装下载jupyter lab
  16. python爬虫58同城(多个信息一次爬取)
  17. erp 维护费 要交吗_Erp系统一般多少钱一年,一般怎么收费呢
  18. java毕业设计校园自行车租赁系统(附源码、数据库)
  19. 两相四线步进电机时序
  20. 基于阿里云的MQTT协议入门一:注册及开通阿里云IoT物联网平台

热门文章

  1. Sql面试题之三(难度:简单| 含答案)
  2. ST表 (模板) 洛谷3865
  3. 计蒜客 三值排序 (模拟)
  4. Windows 系统安装Docker Compose 步骤
  5. jQuery Ajax详解
  6. JavaScript window
  7. maven指定构建的编码格式
  8. NSTimer与Run loop Modes
  9. .NET Excel 2003 批量插入数据很慢的解决办法
  10. 洛谷 P3182 [HAOI2016]放棋子(错排问题)