Python装饰器详解(一)


前言

  装饰器作为Python的基础,无论在面试还是在日常工作中用到的频率算是非常频繁的,而其本身的特点也非常适合用来考核水平,可以问的很浅,也能问的很深。在之前的学习中,我经常费劲心思去记录各种装饰器,比如普通装饰器、带参装饰器、类装饰器、带参类装饰器,觉得花里胡哨的,而平常用的不就是普通装饰器就够用了么,并且用的频率相对其他来说也不算很频繁,往往定义一个就够用上许久。
  在这篇文章里,我会通俗易懂地讲述这些装饰器的存在意义,以及如何快速地记录和使用他们,话不多说,开始吧。


定义

  老生常谈的定义还是绕不开的,学过设计模式的都知道23中基本设计模式里有一种就叫做装饰器模式,而Python就天然支持这样的语法来很好地装饰函数、类,所以,装饰器的作用就是在不改动原有的函数、类上,增加额外的操作处理(比如日志、计时等)
  在日常开发中用的是非常多,比如:

  • 数据库事务处理:一个函数里无论调用了多少的SQL语句,在上面添加一个装饰器即可让整个函数的SQL语句都能在事务里生效;
  • 登录权限验证:后台接口经常需要增加权限验证,而繁杂的权限验证又不涉及接口逻辑,这块额外的功能就非常适合装饰器
  • 添加缓存:同数据库事务处理,当我们需要添加缓存这种不涉及接口业务逻辑的功能,用装饰器就ok

  装饰器就是用的很广泛,而它的使用也非常简单,只要定义个装饰器函数(英文decorator),给目标函数上方添加@decorator即可,一个普通的给函数计时装饰器如下:

import time


def timer(func):
    def wrapper():
        start = time.time()
        result = func()
        end = time.time()
        print(func.__name__, "函数花费了(s):", end - start)
        return result

    return wrapper


@timer
def count():
	"""this is a count"""
    total = 0
    for i in range(10000):
        total += i
    return total


count()

通过给count函数增加@timer装饰器,提供了计时这个函数整体运行的时间。除此之外,我们可以给其他任意的函数也增加该装饰器。输出如下:

count 函数花费了(s): 0.0005476474761962891

进阶

  装饰器的@看着挺帅,实际上就是一种语法糖,属于Python特有,这就相当于如果你发明了另外一种语言Python2,你可以把@改为&、¥都行。所以我们要深究@究竟做了啥。
  简单来说,就是将函数作为参数传给装饰器函数,上面的timer也可以这样调用,输出的结果是一样的。

timer(count)()

  在上面count就是函数,将其作为参数传入timer即可,当我们把参数count传入后实际上就是调用里面的子函数:wrapper(count),所以我们可以简化timer到普通函数,但也实现装饰的效果,如下:

def timer_origin(func):
    start = time.time()
    result = func()
    end = time.time()
    print(func.__name__, "函数花费了(s):", end - start)
    return result
    
timer_origin(count)

  所以装饰器的本质就是一个传函数参的函数罢了,从这一层理解,就会发现装饰器很简单,而其他难的装饰器都是针对各式各样的需求而逐步衍生出来的。比如第一个装饰器,就是为了满足@的语法而产生的。
  那基于简单的装饰器第一个问题是,如何让被调用的函数也能使用上其自身的参数呢?我们知道本质上是调用wrapper(),那给这里加上可变参数即可:wrapper(*args, **kwargs),改写如下:

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, "函数花费了(s):", end - start)
        return result
    return wrapper

  这样无论被调用的函数是带着什么参数,都能被可变参数覆盖到,自然就满足了。那由此引发了第二个问题,我还想让装饰器也带上参数要怎么处理?不要觉得这个场景很少,恰恰相反,使用场景很频繁,比如权限验证(要给接口指定某某某的权限),比如添加缓存(要提供timeout参数来标识缓存时间等)。
  那为了解决给装饰器带上参数这个问题,我们可以引入最外层函数来包裹装饰器函数,实现如下:

def timer_rule(name):
    def timer(func):
        def wrapper(*args, **kwargs):
            if name != 'developer':
                return func(*args, **kwargs)
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print(func.__name__, "函数花费了(s):", end - start)
            return result
        return wrapper
    return timer


@timer_rule('admin')
def count():
	"""this is a count"""
    total = 0
    for i in range(10000):
        total += i
    print("total is ", total)
    return total

count()

  通过上面的三层函数实现了一个简单的带参装饰器,timer_rule里的参数也可以是keyword,通过这个函数,我们就可以在装饰的时候控制我们需要的装饰效果,比如只有管理员可以访问该接口,那就指定参数为’admin’即可。
  接着是第三个问题,通过上面我们知道调用count函数,本质上是调用wrapper(count),那这会导致count的元数据丢失,如何处理?Python里天然有库可以解决这个问题,首先说下什么是元数据,如果这时我们打印count的下列数据:

print(count.name, count.doc)
结果是:wrapper None {}

  会发现原本属于count的名字和解释都消失,变成了wrapper,这个在我们debug时会带来干扰,同时函数相关有用的信息都丢失了,那此时只要加个第三方库functools里的wraps装饰器即可,改造后如下:

from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, "函数花费了(s):", end - start)
        return result
    return wrapper

此时再打印,结果如下:

print(count.name, count.doc)
结果是:count this is a count

总结

  此次就说到这,除了函数装饰器外,当我们想用到类装饰器来更好的管理装饰器功能时,就需要用到类装饰器和带参类装饰器了,而这些留着下节再讲。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值