一文读懂Python装饰器由来

Python装饰器是非常不错的特性,熟练掌握装饰器会让你的编程思路更加宽广,程序也更加pythonic。下面就让我们一起来探讨一下python的装饰器吧。

装饰器的存在是为了适用两个场景,一个是增强被装饰函数的行为,另一个是代码重用。

先看一个例子,直观的感受一下:

import time

def out_wrapper(func):
 def inner_wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
 print('Used time {}'.format(stop_time-start_time))
 return inner_wrapper


@out_wrapper
def test1():
    time.sleep(1)
 print('I am test1!')

输出:

I am test1!
Used time 1.0000572204589844

这个装饰器是用来计算函数执行时间的。原本test1函数只是休眠1秒,然后输出字符串,但是在使用装饰器(out_wrapper)后,它的功能多了一项:输出执行时间。 这是一个最简单的装饰器,实现了 “增强被装饰函数的行为”。而我们需要思考的是为什么装饰器是这个样子的? 那是因为行为良好的装饰器必须要遵守两个原则:

1、不能修改被装饰函数的代码;

2、不能修改被装饰函数的调用方式;

这并不难以理解,因为在生产环境中如果我们要给某个函数添加功能,最好不要修改该函数的源码,因为可能造成意想不到的影响,或者这个代码是一个大神写的,你根本不知从何改起;同时你也不能修改其调用方式,因为你不知道程序中有多少地方调用了此函数。

那么我们从函数和函数名说起吧。

def func(name):
 print('I am {}!'.format(name))

func('li')
y = func
y('liu')

输出:

I am li!
I am liu!

定义函数func,调用函数func,将函数名func赋值给y,调用y。y=func 表明:函数名可以赋值给变量,并且并不影响调用。

这其实和整数、数字是一样的:

a = 1
b = a
print(a, b)

明白了这一点,下面再说说高阶函数: 高阶函数满足如下两个条件中的任意一个: a. 可以接收函数名作为实参; b. b.返回值中可以包含函数名;

其实python标准库中的map和filter等函数就是高阶函数。

l = [1, 2, 4]
r = map(lambda x: x*3, l)
for i in r:
 print(i)

自定义一个能返回函数的函数,也是高阶函数

def f(l):
 return map(lambda x: x*5, l)
a = f(l)
for i in a:
 print(i)

有了这些基础,我们就可以尝试实现一下类似装饰器的功能了。

def out(func):
 print('Add a function.')
 return func

def test1():
    time.sleep(1)
 print('I am test1!')

temp = out(test1)
temp()

输出:

Add a function.
I am test1!

还是第一个例子中的test1函数,我们定义了一个函数out,out接收一个函数名然后直接返回该函数名。这样,我们实现了不修改原函数test1,并且添加了一个新功能的需求,但是缺陷就是调用方式改变了。如何解决这个问题呢?其实很简单,相信 a = a * 3 这样的表达式我们都见过,那么上述代码中的temp = out(test1) 同样可以修改为 test1 = out(test1),这样我们就完美的解决了问题:既添加了新功能又没有修改原函数和其调用方式。修改后的代码如下:

def out(func):
 print('Add a function.')
 return func

def test1():
    time.sleep(1)
 print('I am test1!')

test1 = out(test1)
test1()

只是美中不足的事每次需要使用装饰器的时候,都要在写一句类似test1 = out(test1) 的代码。python为了简化这种情况,提供了一个语法糖@,在每个被装饰的函数上方使用这个语法糖就可以省掉这一句代码test1 = out(test1)。如下:

def out(func):
 print('Add a function.')
 return func
@out
def test1():
    time.sleep(1)
 print('I am test1!')

# test1 = out(test1)
test1()

至此,我们搞清楚了装饰器的工作原理,但是对比开篇的例子,还是有些不一样。这又是为什么呢? 开篇例子实现的是输出被装饰函数的执行时间,那么必须在函数执行之前记录一下时间,函数执行之后记录一下时间,这样才能计算出函数的执行时间,但是我们现在是直接返回了函数名,这样函数调用后我们就没办法做任何事情了,所以此时我们需要在嵌套一层函数,将实现额外功能的部分写在内层函数中,然后将这个内层函数返回即可。这也是为什么装饰器都是嵌套函数的原因。 另外,开篇的例子并没有返回值,也没有参数,要对既有参数又有返回值的函数进行装饰的话,还需要进一步完善。 能够处理返回值的装饰器:

import time

def out_wrapper(func):
 def inner_wrapper():
        start_time = time.time()
        result = func()
        stop_time = time.time()
 print('Used time {}'.format(stop_time - start_time))
 return result
 return inner_wrapper


@out_wrapper
def test1():
    time.sleep(1)
 print('I am {test1}!')
 return 'test1 return'

x = test1()
print(x)

输出:

I am {test1}!
Used time 1.0000572204589844
test1 return

能够处理参数的装饰器:

def out_wrapper(func):
 def inner_wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        stop_time = time.time()
 print('Used time {}'.format(stop_time - start_time))
 return result
 return inner_wrapper


@out_wrapper
def test1(args):
    time.sleep(1)
 print('I am {}!'.format(args))
 return 'test1 return'

x = test1('li')
y = test1('liu')
print(x, y)

输出:

I am li!
Used time 1.0000569820404053
I am liu!
Used time 1.0000572204589844
test1 return test1 return

总结:装饰器的本质是函数,其参数是另一个函数(被装饰的函数)。 装饰器通常会额外处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。行为良好的装饰器可以重用,以减少代码量。

原文发布于微信公众号 - IT派(transfer_3255716726)

原文发表时间:2018-05-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大数据学习笔记

Java程序设计(Java9版):第2章 数据类型与运算符(Data types and Operators)

第2章 数据类型与运算符(Data types and Operators) I think everybody in this country should ...

2625
来自专栏计算机视觉与深度学习基础

Leetcode 238. Product of Array Except Self

Given an array of n integers where n > 1, nums, return an array output such th...

1849
来自专栏chenjx85的技术专栏

leetcode-179-Largest Number(理解规则,自定义cmp函数进行排序)

1、这道题给定一个vector,里面存放着int类型的非负整数,要求把这些非负整数拼起来,尽可能拼成一个最大的整数。

1913
来自专栏静晴轩

类数组借用数组方法

于JavaScript如何将对象转化为数组对象,其用法写法已经很常见且完善,比如JQuery中的makeArray函数对此的实现,也是跟大家想的差不多,只是考虑...

3509
来自专栏xx_Cc的学习总结专栏

iOS-正则表达式的简单使用

4437
来自专栏JetpropelledSnake

Python入门之装饰器九步学习入门

第一步:最简单的函数,准备附加额外功能 '''示例1: 最简单的函数,表示调用了两次''' def myfunc(): print("myfunc(...

2628
来自专栏互联网开发者交流社区

Java逻辑

1514
来自专栏静晴轩

JavaScript对象length

前几日有在Javascript数组操作一文中稍提及了数组的length属性;深入一点探究,就发现JS这length确有许多难为所知的特性。这就边学边探究下这朵奇...

3818
来自专栏Python中文社区

Python装饰器探秘

add的功能是计算x和y的值,我们称作功能函数。 logger的作业是在执行add函数的同时再打印了其他的信息,这部分的作为add的功能增强,我们称为装饰。 在...

1111
来自专栏软件开发

JavaSE学习总结(八)—— 异常处理(Exception)

一、理解异常及异常处理的概念 异常就是在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序。 异常不是错误 程序中关键的位置有异常处理,提高程序的稳定...

2339

扫码关注云+社区

领取腾讯云代金券