Python装饰器

作者: 小白将 | 来源:发表于2016-06-15 21:18 被阅读141次

    装饰器简介

    在Python中,装饰器属于“元编程”的类别,“元编程”的主要目的就是创建函数或者类,并且用它们来操纵代码,比如说修改、生成或者包装已有的代码。而装饰器就是为修改或者包装函数提供了一种快捷的方式。
    装饰器本质上是一个函数,而且准确地说是一个高阶函数,这个函数以其他函数为参数,并且以一个修改过的函数作为返回来替换要装饰的函数。
    先看一个最简单的例子,这个装饰器什么也不做,直接返回原函数:

    def identity(f):
        ''' a simple decorator'''
        return f
    

    然后你可以这样使用装饰器:

    @identity
    def test(f):
        return "Hello, world"
    

    对于上面的装饰器来说,它的实现逻辑与下面的代码相似:

    def test(f):
        return "Hello, world"
    test = identity(test)
    

    可以看到装饰器的工作原理就是用新的函数替换被装饰的函数,当然上面是最简单的装饰器,它并不起什么作用。下面我们来建立一个可以对函数运行时间计时的装饰器,相对来说是比较实用的:

    import time
    def timethis(f):
        ''' Decorator that reports the running time.'''
        def wrapper(*args, **kwargs):
            start = time.time()
            result = f(*args, **kwargs)
            end = time.time()
            print(f.__name__, end - start)
            return result
        return wrapper
    

    相比上一个装饰器,这个装饰器相对复杂些,在这个装饰器内部新定义了一个函数wrapper,这个函数可以接受任意个参数,并且在内部调用被装饰的函数,在调用的过程中,计算函数的运行时间并输出。这是装饰器最一般的模式。装饰器没有修改原来函数的签名,因为wrapper接受任意参数,那么传递进来的参数将原样传递给f。而且装饰器没有修改函数的返回结果。
    那么,我们来看具体的装饰实例:

    @timethis
    def countdown(n):
        ''' 计数递减'''
        while n > 0:
            n -= 1
    
    In [7]: countdown(100000)
    countdown 0.011001110076904297
    In [8]: countdown(100000000)
    countdown 12.434711933135986
    
    @timethis
    def accumulation(n):
        ''' 累加'''
        sum = 0
        for i in range(n):
            sum += i
        return sum
    In [10]: accumulation(10000)
    accumulation 0.0009999275207519531
    Out[10]: 49995000
    
    In [11]: accumulation(10000000)
    accumulation 1.2270700931549072
    Out[11]: 49999995000000
    

    装饰器更常用在控制非法访问,比如某些操作只能有管理员级别的才能执行,对于其他用户将该操作屏蔽掉,看下面一个例子:

    def check_is_admin(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            fun_args = inspect.getcallargs(f, *args, **kwargs)
            if fun_args.get("username") != "admin":
                raise Exception("This user is not allowed to get food.")
            return f(*args, **kwargs)
        return wrapper
    

    可以看到这个装饰器是用来通过检查传入函数中的用户名来控制访问,如果使用户名不是 "admin",那么调用函数时会抛出异常。相比前面的例子,这个装饰器还有两处需要注意的地方。第一,在装饰器内层定义的函数中使用了系统中functools模块中的wraps装饰器。我们要知道装饰器的原理是用新函数替换被装饰的函数,尽管装饰器不改变函数原来的参数及返回值,但是函数还是会丧失一部分原有信息,如namedoc。这些信息将被装饰函数所覆盖掉,很多情况下,我们不想丢失函数的原有信息。那么系统自带的wraps装饰器很好地解决了这个问题,@wraps(f)会将f的原有信息复制给装饰函数wrapper.第二,我们使用了系统自带的inspect.getcallargs()方法,这个方法可以方便地将传递给函数的参数转化为一个字典,那么不管username参数是位置参数还是关键字参数,装饰函数都可以很好地获得它。下面看运行实例:

    @check_is_admin
    def foodbar(username):
        """Do something"""
        return "food"
    
    p, li { white-space: pre-wrap; }
    
    In [5]: foodbar("admin")
    Out[5]: 'food'
    
    In [6]: foodbar("user")
    ---------------------------------------------------------------------------
    Exception Traceback (most recent call last)
    Exception: This user is not allowed to get food.
    In [7]: foodbar(username="admin")
    Out[7]: 'food'
    
    In [8]: foodbar.__name__
    Out[8]: 'foodbar'
    
    In [9]: foodbar.__doc__
    Out[9]: 'Do something'
    

    可以接受参数的装饰器

    有时候,我们希望装饰器本身能够接受参数,这样创建的装饰器会更加灵活与实用。考虑这样的情况,你正在编写一个博客网站,用户具有下列权限:写文章,评论文章与修改文章。一个较好的办法,我们写一个通用型的装饰器,这个装饰器可以接受一个参数,这个参数是权限类别。那么我只需要定义一个装饰器,传递不同的参数给装饰器就能实现不同的装饰功能:

    #对于无权限用户抛出403状态码
    def permission_required(permission):
        def decorator(f):
            @wraps(f)
            def decorated_function(*args, **kwargs):
                if not current_user.can(permission):
                    abort(403)
                return f(*args, **kwargs)
            return decorated_function
    

    这个装饰器接受一个permission参数,并检查当前用户是否拥有该权限,如果没有将抛出403状态码。相比无参数的装饰器,这个装饰器多了一层函数,如果扒去最外层的函数,那么它将与无参数的装饰器一样。前面我们说过装饰器的原理,这里同样适用:

    #对于无权限用户抛出403状态码
    @permission_required("comment"):
    def comment(post_id):
        ''' make a comment'''
        pass
    #这与下面的定义类似
    def comment(post_id):
         ''' make a comment'''
         pass
    comment = permission_required("comment")(comment)
    

    下面看传递不同的参数,装饰器实现不同功能:

    #关注用户的处理视图
    @main.route('/follow/<username>')
    @login_required
    @permission_required(Permission.FOLLOW)
    def follow(username):
        user = User.query.filter_by(username=username).first()
        if user is None:
            flash("Invalid user.")
            return redirect(url_for('.index'))
        if current_user.is_following(user):
            flash('You have already following this user.')
            return redirect(url_for('.user', username=username))
        current_user.follow(user)
        flash("You are now following {0}.".format(username))
        return redirect(url_for('.user', username=username))
    
    
    #管理评论视图
    @main.route('/moderate')
    @login_required
    @permission_required(Permission.MODERATE_COMMENTS)
    def moderate():
        page = request.args.get('page', 1 ,type=int)
        pagination = Comment.query.order_by(Comment.timestamp.desc()).paginate(page,\
                    per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], error_out=False)
        comments = pagination.items
        return render_template('moderate.html', comments=comments, pagination=pagination, page=page)
    

    当然一个函数可以接受多个装饰器。
    $ f(x)=\left{
    \begin{aligned}
    x & = & \cos(t) \
    y & = & \sin(t) \
    z & = & \frac xy
    \end{aligned}
    \right.
    $
    未完待续
    学习参考

    相关文章

      网友评论

        本文标题:Python装饰器

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