Fork me on GitHub

py10 装饰器

装饰器

装饰器就是闭包函数的一种应用场景

为何要用装饰器

开放封闭原则:对修改封闭,对扩展开放

什么是装饰器

装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。

强调装饰器的原则:1 不修改被装饰对象的源代码 2 不修改被装饰对象的调用方式

装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能

装饰器的使用

函数不固定参数,装饰器的使用

import time
def timmer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper

@timmer # 相当于 foo = timmer(foo)
def foo():
time.sleep(2)
print('from foo')

@timmer # 相当于 foo = timmer(foo)
def foo1(name):
time.sleep(2)
print('from foo', name)


foo()
foo1('weilianxin')

上述的foo经过装饰器装饰后,foo已经相当于wrapper,foo1亦是如此,所以运行foo和foo1相当于运行wrapper,传参也是向wrapper传参。

 foo1给timmer(func):,name给了wrapper(*args,**kwargs):,然后传给res=func(*args,**kwargs),原foo1若有返回值,则传给res

有参装饰器的使用

当装饰器带有多个参数的时候, 装饰器函数就需要多加一层嵌套,如果不调用被装饰函数,可以不多加一层(只写两层):

def auth(auth_type):
    print("auth func:", auth_type)

    def outer_wrapper(func):
        print('123456')
        def wrapper(*args, **kwargs):
            print("wrapper func args:", *args, **kwargs)
            if auth_type == "local":
                username = input("Username:").strip()
                password = input("Password:").strip()
                if user == username and passwd == password:
                    print("\033[32;1mUser has passed authentication\033[0m")
                    res = func(*args, **kwargs)  # from home
                    print("---after authenticaion ")
                    return res
                else:
                    exit("\033[31;1mInvalid username or password\033[0m")
            elif auth_type == "ldap":
                print("搞毛线ldap,不会。。。。")
        return wrapper
    return outer_wrapper



@auth(auth_type="local")   # home = wrapper()
def home():
    print("welcome to home  page")
    return "from home"


@auth(auth_type="ldap")
def bbs():
    print("welcome to bbs  page")


print('===============',home())  # wrapper()
# bbs()

相当于xx = auth("local")  home = xx(home)--简单说就是先执行最外层函数,然后剩下和之前一样

正常一层为@timer------(@函数名),两层的时候auth(auth_type="local")返回内层wrapper函数名,也就是相当于@wrapper

auth_type="local"会传参给auth(auth_type),home传给outer_wrapper(func),home若有参数则类似上一种,继续往里传。

又如flask源码中的:

def route(self, rule, **options):
    """Like :meth:`Flask.route` but for a blueprint.  The endpoint for the
    :func:`url_for` function is prefixed with the name of the blueprint.
    """
    def decorator(f):
        endpoint = options.pop("endpoint", f.__name__)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator

  flask的蓝图route源码中的装饰器, 最内层直接返回return f 并没有多加一层处理的函数, 在无需对被装饰函数进行过多处理的时候这是较为方便的做法. route源码中只是对装饰器参数进行了处理.

注意:

装饰器中函数上面的@fun在解释器走到函数定义时会运行代码,运行外层函数:

import time


def timer(func):
    print('adfadsfasf')
    def deco(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("time is", end-start)
    return deco


@timer  # test1 = timer(test1)
def test1():
    time.sleep(3)
    print("it is test1")


@timer
def test2():
    time.sleep(3)
    print("it is test2")


@timer
def test3(name):
    time.sleep(1)
    print("it is test3", name)
# test1 = timer(test1)
# test1()
# test2()
# test3("weilianxin")
pass

在调用语句被注释后,依然执行了@timmer,执行了外层函数,输出了三行
adfadsfasf
adfadsfasf
adfadsfasf

多装饰器

多个装饰器的原则:(极其重要

装饰器的加载顺序是:由下而上,装饰器的执行顺序是:由上而下。

执行从上至下,按顺序执行时,遇到fun(被装饰函数的调用)才会跳转到另一个装饰器继续执行(因为可以别的装饰器函数里也有fun),但是遇到第一个return就直接返回了,不管别的装饰器有没有执行成。

要点说明:

def bold(fun):
    print('----a----')

    def inner1():
        print('----1----')
        fun()
        print('----1111----')
        return "11111111111111"

    return inner1


def italic(fun):
    print('----b----')

    def inner2():
        print('----2----')
        fun()
        print('----2222----')
        return "2222222222222"

    return inner2


@bold
@italic
def test():
    print("123456")
    return 'hello python decorator'


ret = test()
print(ret)

  装饰器的加载顺序是:由下而上,装饰器的执行顺序是:由上而下,遇到第一个fun()跳转到下一个装饰器,遇到第一个return返回,结果如下:

----b----
----a----
----1----
----2----   # 前四行是顺序
123456      # fun()执行
----2222----
----1111----
11111111111111  # 第一个return

  上面的例子是简单的说明要点的重要性

要点说明2:

def bold(fun):
    print('----a----')

    def inner1():
        print('----1----')
        # fun()
        # print('----1111----')
        return fun()
        # return fun()相当于fun() 然后return None,因为不写return和return None效果一样,所以这句话就相当于执行fun,return可以忽略

    return inner1


def italic(fun):
    print('----b----')

    def inner2():
        print('----2----')
        # fun()
        # print('----2222----')
        return "222222222222"

    return inner2


def line(fun):
    print('----c----')

    def inner3():
        print('----3----')
        # fun()
        # print('----3333----')
        return fun()

    return inner3


@bold
@italic
@line
def test():
    print("123456")
    return 'hello python decorator'


ret = test()
print(ret)

  在第一个装饰器中遇到fun()跳转到第二个装饰器执行,遇到return返回,下面的都不执行,结果如下:

----c----
----b----
----a----
----1----
----2----
222222222222

  注意,return fun()相当于fun(),可以理解成先执行fun() 然后return None,因为不写return和return None效果一样,所以这句话就相当于执行fun,return可以忽略

要点说明三:

def bold(fun):
    print('----a----')
    
    def inner1():
        print('----1----')
        return '1111111111111'
    return inner1


def italic(fun):
    print('----b----')

    def inner2():
        print('----2----')
        return fun()
        # return fun()相当于fun() 然后return None,因为不写return和return None效果一样,所以这句话就相当于执行fun,return可以忽略
    return inner2


def line(fun):
    print('----c----')

    def inner3():
        print('----3----')
    return inner3


@bold
@italic
@line
def test():
    print("123456")
    return 'hello python decorator'


ret = test()
print(ret)

  这里遇到第一个return就已经返回,下面的fun都不执行,结果如下:

----c----
----b----
----a----
----1----
1111111111111

  如果你理解了,可以看看flask的登陆验证装饰器放置的位置在上好还是在下好

flask登陆验证装饰器和路由装饰器:

from flask import Flask, render_template, request, redirect, session, url_for

app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = '123456'
app.config.from_object("settings.DevelopmentConfig")

USERS = {
    1: {'name': '张桂坤', 'age': 18, 'gender': '男',
        'text': "当眼泪掉下来的时候,是真的累了, 其实人生就是这样: 越不过的无奈,听不完的谎言,看不透的人心放不下的牵挂,经历不完的酸甜苦辣,这就是人生,这就是生活。"},
    2: {'name': '主城', 'age': 28, 'gender': '男',
        'text': "高中的时候有一个同学家里穷,每顿饭都是膜膜加点水,有时候吃点咸菜,我们六科老师每天下课都叫他去办公室回答问题背诵课文,然后说太晚啦一起吃个饭,后来他考上了人大,拿到通知书的时候给每个老师磕了一个头"},
    3: {'name': '服城', 'age': 18, 'gender': '女',
        'text': "高中的时候有一个同学家里穷,每顿饭都是膜膜加点水,有时候吃点咸菜,我们六科老师每天下课都叫他去办公室回答问题背诵课文,然后说太晚啦一起吃个饭,后来他考上了人大,拿到通知书的时候给每个老师磕了一个头"},
}


def wapper(func):
    def inner(*args, **kwargs):
        user = session.get('user_info')
        if not user:
            return redirect("/login")
        return func(*args, **kwargs)

    return inner


@app.route('/detail/<int:nid>', methods=['GET'], endpoint='l0')  # 配置动态url
@wapper
def detail(nid):
    user = session.get('user_info')
    if not user:
        return redirect('/login')

    info = USERS.get(nid)  # 获取动态url
    return render_template('detail.html', info=info)


@app.route('/index', methods=['GET'])
def index():
    user = session.get('user_info')
    if not user:
        # return redirect('/login')
        url = url_for('l1')  # url_for可以进行反向解析
        return redirect(url)
    return render_template('index.html', user_dict=USERS)


@app.route('/login', methods=['GET', 'POST'], endpoint='l1')  # endpoint设置url别名
def login():
    if request.method == "GET":
        return render_template('login.html')
    else:
        # request.query_string
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user == 'alex' and pwd == '123':
            session['user_info'] = user  # 设置session
            # return redirect('http://www.luffycity.com')
            return redirect('/index')
        return render_template('login.html', error='用户名或密码错误')


if __name__ == '__main__':
    app.run()

"""
对象后面加括号调用对象的call方法,
run():
from werkzeug.serving import run_simple
run_simple(host, port, self, **options)
run_simple()的第三个参数是self,是上面实例化的app,所以对象()调用的是对象的call方法

def __call__(self, environ, start_response):
    # environ,是请求相关,start_response是响应相关
    return self.wsgi_app(environ, start_response)
"""

'''路由:
def route(self, rule, **options):
    """Like :meth:`Flask.route` but for a blueprint.  The endpoint for the
    :func:`url_for` function is prefixed with the name of the blueprint.
    """
    def decorator(f):
        endpoint = options.pop("endpoint", f.__name__)
        self.add_url_rule(rule, endpoint, f, **options)  # 路由最关键的就是执行这句话
        return f
    return decorator
'''

  这里的wrapper装饰器只能放在route装饰器下面,因为wrapper装饰器在if not user时,会直接return,这样,后面的路由就无法添加了,suoyi只能在下面,在下面也有问题,因为wrapper装饰后,函数名变成了inner,这样很多函数名都变成inner,endpoint会重复,所以要指定一下,或者使用装饰器修复。

类的装饰器

介绍如何使用Python的装饰器装饰一个类的方法,同时在装饰器函数中调用类里面的其他方法。以捕获一个方法的异常为例来进行说明。

def catch_exception(origin_func):
    def wrapper(self, *args, **kwargs):
        try:
            u = origin_func(self, *args, **kwargs)
            return u
        except Exception:
            self.revive() #不用顾虑,直接调用原来的类的方法
            return 'an Exception raised.'
    return wrapper
 
 
class Test(object):
    def __init__(self):
        pass
 
    def revive(self):
        print('revive from exception.')
        # do something to restore
 
    @catch_exception
    def read_value(self):
        print('here I will do something.')
        # do something.

test = Test()
test.read_value()

  注意装饰器是写在类的定义外面的

装饰器补充:functools.wraps

functools.wraps的作用:

我们在使用 Decorator 的过程中,难免会损失一些原本的功能信息(.__name__等)。直接拿 stackoverflow 里面的栗子

而functools.wraps 则可以将原函数对象的指定属性复制给包装函数对象, 默认有 __module____name____doc__,或者通过参数选择。代码如下:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging
 
@logged
def f(x):
   """does some math"""
   return x + x * x
 
print f.__name__  # prints 'f'
print f.__doc__   # prints 'does some math'

functools.wraps原理解析

预备知识

在了解wraps修饰器之前,我们首先要了解partialupdate_wrapper这两个函数,因为在wraps的代码中,用到了这两个函数。

partial

首先说partial函数,在官方文档的描述中,这个函数的声明如下:functools.partial(func, *args, **keywords)。它的作用就是返回一个partial对象,当这个partial对象被调用的时候,就像通过func(*args, **kwargs)的形式来调用func函数一样。如果有额外的 位置参数(args) 或者 关键字参数(*kwargs) 被传给了这个partial对象,那它们也都会被传递给func函数,如果一个参数被多次传入,那么后面的值会覆盖前面的值。

个人感觉这个函数很像C++中的bind函数,都是把某个函数的某个参数固定,从而构造出一个新的函数来。比如下面这个例子:

from functools import partial

def add(x:int, y:int):
    return x+y

# 这里创造了一个新的函数add2,只接受一个整型参数,然后将这个参数统一加上2
add2 = partial(add, y=2)

add2(3)  # 这里将会输出5

  这个函数是使用C而不是Python实现的,但是官方文档中给出了Python实现的代码,如下所示,大家可以进行参考:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

update_wrapper

接下来,我们再来聊一聊update_wrapper这个函数,顾名思义,这个函数就是用来更新修饰器函数的,具体更新些什么呢,我们可以直接把它的源码搬过来看一下:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    for attr in assigned:
        try:
            value = getattr(wrapped, attr)
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value)
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    wrapper.__wrapped__ = wrapped
    return wrapper

  大家可以发现,这个函数的作用就是从 被修饰的函数(wrapped) 中取出一些属性值来,赋值给 修饰器函数(wrapper) 。为什么要这么做呢,我们看下面这个例子。

自定义修饰器v1

首先我们写个自定义的修饰器,没有任何的功能,仅有文档字符串,如下所示:

def wrapper(f):
    def wrapper_function(*args, **kwargs):
        """这个是修饰函数"""
        return f(*args, **kwargs)
    return wrapper_function
    
@wrapper
def wrapped():
    """这个是被修饰的函数"""
    print('wrapped')

print(wrapped.__doc__)  # 输出`这个是修饰函数`
print(wrapped.__name__)  # 输出`wrapper_function`

  从上面的例子我们可以看到,我想要获取wrapped这个被修饰函数的文档字符串,但是却获取成了wrapper_function的文档字符串,wrapped函数的名字也变成了wrapper_function函数的名字。这是因为给wrapped添加上@wrapper修饰器相当于执行了一句wrapped = wrapper(wrapped),执行完这条语句之后,wrapped函数就变成了wrapper_function函数。遇到这种情况该怎么办呢,首先我们可以手动地在wrapper函数中更改wrapper_function__doc____name__属性,但聪明的你肯定也想到了,我们可以直接用update_wrapper函数来实现这个功能。

自定义修饰器v2

我们对上面定义的修饰器稍作修改,添加了一句update_wrapper(wrapper_function, f)

from functools import update_wrapper

def wrapper(f):
    def wrapper_function(*args, **kwargs):
        """这个是修饰函数"""
        return f(*args, **kwargs)
    update_wrapper(wrapper_function, f)  # <<  添加了这条语句
    return wrapper_function
    
@wrapper
def wrapped():
    """这个是被修饰的函数"""
    print('wrapped')


print(wrapped.__doc__)  # 输出`这个是被修饰的函数`
print(wrapped.__name__)  # 输出`wrapped`

  此时我们可以发现,__doc____name__属性已经能够按我们预想的那样显示了,除此之外,update_wrapper函数也对__module____dict__等属性进行了更改和更新。

wraps修饰器

OK,至此,我们已经了解了partialupdate_wrapper这两个函数的功能,接下来我们翻出wraps修饰器的源码:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

  没错,就是这么的简单,只有这么一句,我们可以看出,wraps函数其实就是一个修饰器版的update_wrapper函数,它的功能和update_wrapper是一模一样的。我们可以修改我们上面的自定义修饰器的例子,做出一个更方便阅读的版本。

自定义修饰器v3

from functools import wraps

def wrapper(f):
    @wraps(f)
    def wrapper_function(*args, **kwargs):
        """这个是修饰函数"""
        return f(*args, **kwargs)
    return wrapper_function
    
@wrapper
def wrapped():
    """这个是被修饰的函数
    """
    print('wrapped')

print(wrapped.__doc__)  # 输出`这个是被修饰的函数`
print(wrapped.__name__)  # 输出`wrapped`

  至此,我想大家应该明白wraps这个修饰器的作用了吧,就是将 被修饰的函数(wrapped) 的一些属性值赋值给 修饰器函数(wrapper) ,最终让属性的显示更符合我们的直觉。

 

posted @ 2018-08-09 09:30  醉生卐梦死  阅读(298)  评论(0编辑  收藏  举报