美文网首页
python 装饰器以及开发中常用的例子

python 装饰器以及开发中常用的例子

作者: ketchup | 来源:发表于2017-06-06 20:34 被阅读0次

有时候我们想为多个函数,同意添加某一种功能,比如及时统计,记录日志,缓存运算结果等等,而又不想改变函数代码
那就定义装饰器函数,用它来生成一个在原函数基础添加了新功能的函数,代替原函数
参考金角大王的博客

装饰器从无到有的过程

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

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)

但是每次这样调用,如果很多模块要加这个功能,大家都要去调用一下,那太麻烦了</br>
python中一切皆对象,可以用

shopping = login(shoping)
shopping()

这样的方式执行shopping()就等于执行login(shopping)</br>
但是在前面赋值 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

关于尾递归参照Python开启尾递归优化

下面小练习:
如果有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 : 函数的名字</br>
f.doc : 函数的文档字符串,对这个函数的一些描述</br>
f.moudle : 函数所属的模块名</br>
f.dict : 属性字典</br>
f.defaults : 默认参数元祖</br>

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]: (<cell at 0x11013af68: int object at 0x7ff0b05056e0>,)

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

In [29]: c
Out[29]: <cell at 0x11013af68: int object at 0x7ff0b05056e0>

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</br>
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 '<class 'str'

我们来看看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': <Parameter "a">,
              'b': <Parameter "b">,
              'c': <Parameter "c=1">})

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 装饰器以及开发中常用的例子

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

  • 2019-05-26python装饰器到底是什么?

    装饰器例子 参考语法 装饰器是什么?个人理解,装饰器,是python中一种写法的定义。他仍然符合python的基本...

  • Python 高级编程 装饰器

    1.1装饰器 装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中...

  • Python 装饰器 续集

    前言: Python 装饰器 一节中,我们引入了装饰器的概念,以及常见的4中装饰器模型,本节我们会探索一下装饰器的...

  • Python 装饰器

    python 装饰器装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python...

  • 装饰器模式

    介绍 在python装饰器学习 这篇文章中,介绍了python 中的装饰器,python内置了对装饰器的支持。面向...

  • python 装饰器

    一、我们在python语言中常用@classmethod、@staticmethod这个装饰器,装饰器的作用简单来...

  • 理解和使用Python装饰器

    装饰器在 Python 中无处不在,功能强大。本篇介绍装饰器的原理和用法,力求通俗易懂。 我们从一个简单的例子开始...

  • python 装饰器

    装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题,但...

  • 装饰器

    装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题,但...

网友评论

      本文标题:python 装饰器以及开发中常用的例子

      本文链接:https://www.haomeiwen.com/subject/iitnfxtx.html