学习Python,不懂装饰器怎么能行?!

一、函数的核心点

在聊装饰器之前,我们先“约法三章”,如果这“三章”都不承认,那我们的装饰器也就没得聊了,哪三章呢?

  1. 在Python中,函数是一等公民,函数也是对象,我们可以把函数赋予变量

比如下面的代码:


# -*- coding=utf-8 -*-

def func(message):

print('Got a message: {}'.format(message))

if __name__ == "__main__":

send_message = func

send_message('Hello World')

  1. 我们可以把函数当作参数,传入另一个函数中

# -*- coding=utf-8 -*-

def func(message):

print('Got a message: {}'.format(message))

def root_call(func, message):

func(message)

if __name__ == "__main__":

root_call(func, "Hellow World")

  1. 我们可以在函数里定义函数,也就是函数的嵌套。

# -*- coding=utf-8 -*-

def func(message):

def get_message(message):

print('Got a message: {}'.format(message))

return get_message(message)

if __name__ == "__main__":

func('hello world')

  1. 要知道,函数的返回值也可以是函数对象(闭包).

比如下面这个例子:


def func_closure():

def get_message(message):

print('Got a message: {}'.format(message))

return get_message

send_message = func_closure()

send_message('hello world')


二、一个简单的装饰器

我们先从一个简单的例子开始,这个例子是建立在充分理解“我们的约法三章”基础之上的。


# -*- coding=utf-8 -*-

import time

def time_decorator(func):

def wrapper():

start_time = time.time()

func()

print(time.time()-start_time)

return wrapper

def print_hello():

print("Hello")

time.sleep(2)

if __name__ == "__main__":

greet = time_decorator(print_hello)

greet()

在这段代码中,变量greet指向了内部函数wrapper,而内部函数又会调用原函数print_hello。最后执行greet时,会计算执行greet的时间。

这里的函数time_decorator便是一个简单的装饰器,它可用于计算某个函数的执行时间

事实上,上述代码有一个更优雅的方式:


# -*- coding=utf-8 -*-

import time

def time_decorator(func):

def wrapper():

start_time = time.time()

func()

print(time.time()-start_time)

return wrapper

@time_decorator

def print_hello():

print("Hello")

time.sleep(2)

if __name__ == "__main__":

print_hello()

这里的@,我们被称之为语法糖,@time_decorator就相当于前面的greet = time_decorator(print_hello)语句,只不过是更加简洁。因此如果我们的程序中有其它函数需要做类似的装饰,只需要在它们的上方加上@decorator就可以了,这样就大大提高了函数的重复利用和程序的可读性。


三、带有参数的装饰器

在现实的很多情况中,函数都是带有参数的。例如:


def print_message(message):

print(message)

如果我们想给上述类型的函数添加装饰器应该如何做呢?

简单的方式便是在对应的装饰器函数wrapper上,加上相应的参数:


def time_decorator(func):

def wrapper(message):

start_time = time.time()

func(message)

print(time.time()-start_time)

return wrapper

完整示例如下:


# -*- coding=utf-8 -*-

import time

def time_decorator(func):

def wrapper(message):

start_time = time.time()

func(message)

print(time.time()-start_time)

return wrapper

@time_decorator

def print_hello():

print("Hello")

time.sleep(2)

@time_decorator

def print_message(message):

print(message)

if __name__ == "__main__":

print_message("Hello World")

不过,新的问题来了。如果存在另外一个函数,也需要使用time_decorator()装饰器,但是这个新的函数有两个参数,又该怎么办呢?

事实上,通常情况下,我们会把*args**kwargs作为装饰器内部函数wrapper()的参数。*args**kwargs,表示接受任意数量和类型的参数,因此装饰器就可以写成下面的形式:


def time_decorator(func):

def wrapper(*args, **kwargs):

start_time = time.time()

func(*args, **kwargs)

print(time.time()-start_time)

return wrapper


四、带有自定义参数的装饰器

其实,装饰器还有更大程度的灵活性。装饰器可以接受原函数任意类型和数量的参数,除此之外,它还可以接受自己定义的参数。

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


def repeat(num):

def my_decorator(func):

def wrapper(*args, **kwargs):

for i in range(num):

func(*args, **kwargs)

return wrapper

return my_decorator

@repeat(4)

def print_name_message(name, message):

print(name)

print(message)

if __name__ == "__main__":

print_name_message('leo', 'message')


五、原函数还是原函数吗?

我们试着打印出greet()函数的一些元信息:


print(print_name_message.__name__)

help(print_name_message)

# 输出

wrapper

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

我们发现,函数被装饰以后,它的元信息变了。元信息告诉我们『它不再是以前的那个函数,而是被wrapper函数取代了』。

为了解决这个问题,我们通常使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)


import functools

def repeat(num):

def my_decorator(func):

@functools.wraps(func)

def wrapper(*args, **kwargs):

for i in range(num):

func(*args, **kwargs)

return wrapper

return my_decorator

@repeat(4)

def print_name_message(name, message):

print(name)

print(message)

if __name__ == "__main__":

print_name_message('leo', 'message')


六、类装饰器

上述介绍了函数作为装饰器的方法,实际上,类也可以作为装饰器。类装饰器主要依赖于函数call(),每当调用一个类的示例时,函数call()就会被执行一次。


class Count(object):

def __init__(self, func):

self.func = func

self.num_calls = 0

def __call__(self, *args, **kwargs):

self.num_calls += 1

print('num of calls is: {}'.format(self.num_calls))

return self.func(*args, **kwargs)

@Count

def example():

print("hello wolrd")

if __name__ == "__main__":

example()

example()

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


七、装饰器的嵌套

Python支持多个装饰器,比如下面的形式:


@decorator1

@decorator2

@decorator3

def func():

...

它的执行顺序从里到外,所以上面的语句也等效于下面这行代码:


decorator1(decorator2(decorator3(func)))


八、一个典型的装饰器实例用法

在实际过程中,如果我们怀疑某些函数的耗时时间过长,导致整个系统的latency(延迟)增加,所以想在线上测试某些函数的执行时间,那么装饰器就是一种很常用的手段。


# -*- coding=utf-8 -*-

import time

import functools

def log_execution_time(func):

@functools.wraps(func)

def wrapper(*args, **kwargs):

start = time.time()

res = func(*args, **kwargs)

end = time.time()

print('{} took {} ms'.format(func.__name__, (end - start) * 1000))

return res

return wrapper

@log_execution_time

def sleep():

time.sleep(5)

if __name__ == "__main__":

sleep()

你可能感兴趣的:(学习Python,不懂装饰器怎么能行?!)