python 基础知识梳理——装饰器

python 基础知识梳理——装饰器

装饰器一直都是Python中非常有用的一个特征,在后端开发的Django框架中,比如日志、缓存等等的任务中都会用到。

函数 -> 装饰器

函数核心

def get_message(message):
    return 'Got a message:'+message

def root_call(func,message):
    return (func(message))

print(root_call(get_message,'hello world'))

在这个例子中,我们把get_message当做变量传给root_call函数。

当然,我们也可以使用函数嵌套,如下:

def func(message):
    def get_message(message):
        print('Got a message : {}'.format(message))
    return get_message(message)
func('hello world')
# 输出
Got a message : hello world

除了函数嵌套,闭包也可以:

def func():
    def get_message(message):
        return ('Got a message : {}'.format(message))
    return get_message

send_message = func()
send_message('hello world!')

这里,函数func()的返回值是内函数的对象get_message()本身,之后我们将get_message()实例化,再调用send_message(‘hello world !’)输出的得到Got a message : hello world!

装饰器

闭包有一个最大的用处就是用来实现闭包,我们先看一个简单的例子:

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

def greet():
    print('hello world')

greet = my_decorator(greet)
greet()
# 输出
wrapper of decorator
hello world

在上述代码中,greet指向了内部函数wrapper(),而内部函数又会调用print和原函数greet(),然后先打印wrapper of decorator,再打印hello world。

当然,我们还可以用Python中的语法糖方法,实现更简单、更优雅,更更更Pythonic的用法:

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper
@my_decorator
def greet():
    print('hello world')

greet()
# 输出
wrapper of decorator
hello world

这里的@用法,我们称为语法糖,@my_decorator相当于greet = my_decorator(greet),大大提高了函数的重复性和程序的可读性。

带有参数的装饰器

当带有参数时,假如参数固定,那我们可以为对应的装饰器函数添加一个缺省参数。

def my_decorator(func):
    def wrapper(message):
        print('wrapper of decorator')
        func(message)
    return wrapper
@my_decorator
def greet(message):
    print(message)

greet('hello world')
# 输出
wrapper of decorator
hello world

但是,假如我的另一个函数有两个参数,那么我该怎么办呢?

扫描二维码关注公众号,回复: 12277856 查看本文章

通常情况下,我们都会使用*args**kwargs,作为装饰器内部函数wrapper()的参数。*args**kwargs表示可变参数和可变关键字参数,当我们传入列表和元组时使用*args,当我们使用字典传入参数时使用**kwargs

因此,当不确定参数的数量和类型的时候,装饰器可以写成下面的形式:

def my_decorator(func):
    def wrapper(*args,**kwargs):
        print('wrapper of decorator')
        func(*args,**kwargs)
    return wrapper

带有自定义参数的装饰器

装饰器除了能够接受原函数任意类型的参数外,还可以接收自己定义的参数。

假如我想要定义一个参数用来表示装饰器内部函数被执行的次数,那么就可以写成下面这种形式:

def repeat(num):
    def my_decorator(func):
        def wrapper(*args,**kwargs):
            for i in range(num):
                print('wrapper of decorator!')
                func(*args,**kwargs)
        return wrapper
    return my_decorator
@repeat(4)
def greet(message):
    print(message)

greet('hello world')
# 输出
wrapper of decorator!
hello world
wrapper of decorator!
hello world
wrapper of decorator!
hello world
wrapper of decorator!
hello world

你会发现,greet()函数被装饰之后,它的元信息被改变了,访问后返回的其实是wrapper()函数。

print(greet.__name__)
# 输出
wrapper

help(greet)
# 输出
Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

想要解决这个问题,可以使用包functools中的内置装饰器@functools.wraps,它可以帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)

import functools
def repeat(num):
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args,**kwargs):
            for i in range(num):
                print('wrapper of decorator!')
                func(*args,**kwargs)
        return wrapper
    return my_decorator
@repeat(4)
def greet(message):
    print(message)

print(greet.__name__)
# 输出
greet
help(greet)
# 输出
Help on function greet in module __main__:

greet(message)

类装饰器

类装饰器主要依赖函数的__call__(),每当你调用一个类的示例时,函数__call__()就会被执行一次。

class Count:
    def __init__(self,func):
        self.func = func
        self.num_calls = 0
    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of call is : {}'.format(self.num_calls))
        return self.func(*args,**kwargs)
@Count
def example():
    print('hello world')
# 相当于example = count(example)
example()
example()
# 输出
num of call is : 1
hello world
num of call is : 2
hello world

这里,我们定义了类Count,初始化时给func传入exaple()函数, __call__()函数表示让变量num_calls自增1,然后打印调用次数和原函数。

静态装饰器

@staticmethod静态方法,不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样

class static_method_class(object):
    @staticmethod
    def static_method_use():
        print('@staticmethod is run!')

static_method_class.static_method_use()
# 输出
@staticmethod is run!

函数的嵌套

一句话总结:装饰器函数在函数调用时从上到下执行嵌套时从里向外执行

@decorator1
@decorator2
@decorator3
def func():
  ····
  
# 执行顺序为:
dectorator1(dectorator2(dectorator3(func)))

装饰器的用法示例

身份认证

当你访问一下网站,你不登录就可以访问,但是当你需要发表评论、点赞等操作,服务端就会查询你是否登录,必须登录才有权限执行这些操作。

import functools

def authenticate(func):
    @functools.wraps(func)# 保留原函数的元信息
    def wrapper(*args,**kwargs):
        request = args[0]
        if check_user_logged_in(request):# 如果用户处于登录状态
            return func(*args,**kwargs)
        else:
            raise Exception('Authenticate failed')
    return wrapper
@authenticate
def post_comment(request,···):
    ···

在这个例子中,我们定义了一个装饰器@authenticate,函数post_comment()用来发表评论,每次调用这个函数前,都会检查这个用户是否登录,如果是登录状态,则允许评论;如果没有登录,则不允许评论。

日志记录

日志记录也是一种装饰器的常用案例,如果要测试某些函数的执行时间,那么装饰器就是一种很常用的手段。

import time
import functools

def log_excute_time(func):

    def wrapper(*args,**kwargs):
        start = time.perf_counter()
        res = func(*args,**kwargs)
        end = time.perf_counter()
        print('{} took {} s'.format(func.__name__,(end-start)*1000))
        return res
    return wrapper
@log_excute_time
def calculate_similarity(items):
    print('run the '+items)

calculate_similarity('code !')
# 输出
run the code !
calculate_similarity took 0.0539150000000016 s

通过装饰器@log_excute_time可以计算某个函数的运行时间,可以通过time.time()time.perf_counter()计算函数运行时间,但time.perf_counter()精度比time.time()更高,time.time()更适合计算日期时间。

输入合理性检查

在机器学习乃至深度学习中,对于数据集进行训练前,往往会用装饰器对输入的json进行合理性检查,这可以大大避免不正确的数据导致的梯度无法收敛等问题。

import functools

def validation_check(input):
    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        # 检查是否合法
        ···
@validation_check
def training(param1,param2,param3,···):
    ···

缓存

LRU全称为:Least recently use 最近少使用,在有限空间存储对象时,当对key访问时,将该key放到队列的最前端,这就实现了对key按照最后一次访问的时间降序排列,当向空间中增加新对象时,如果空间满了,删除队尾的对象。

缓存装饰器的用法,其实十分常见,LRU cache在Python中的实现形式是@lru_cache@lru_cache会缓存进程中的参数和结果,当缓存满了之后,会删除least recently use 数据。

大公司的服务端代码中,会检查你使用的设备是iPhone还是Android,版本号是什么,将数据加入LRU cache,调用函数前都会用装饰起来包裹这些检查函数,避免反复调用,提高进程的运行效率。

from functools import lru_cache
@lru_cache
def check(param1,param2,···):# 检查设备型号
    ···

总结

装饰器是通过闭包来实现的,装饰器函数的特点是:可以在不更改原函数的基础上,为原函数添加一些功能






博文的后续更新,请关注我的个人博客:星尘博客

猜你喜欢

转载自blog.csdn.net/u011130655/article/details/113018869