更多课程 选择中心


Python培训

400-996-5531

掌握python装饰器知识将让你的编程事半功倍

  • 发布:无名小妖
  • 来源:Python中文社区
  • 时间:2018-05-16 14:55

掌握python装饰器知识将让你的编程事半功倍

今天我们python培训班主要收集了python装饰器方面的知识,为大家做一详细讲述,如果还有人不知道python装饰器是什么、有什么用等问题,请看本文。

Python 是一种强类型语言,但是变量的类型却是动态变化的。虽然这会带来很多好处,但是同时这也意味着更容易引入 bug。对于静态语言,例如 Java, 这些 bug 在编译阶段就可以被发现。因而,你可能希望在对传入或返回的数据进行一些自定义的的检查。装饰器就可以让你非常容易地实现这个需求,并一次性将其应用到多个函数上。一旦你掌握了如何写装饰器,你就能够从其使用的简单的语法中获益颇丰,你可以为语言添加新的语义使其使用更加简单。接下来最棒的就是你可以自己扩展 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

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

以上主要以一步一步演进的方式介绍了装饰器的工作原理以及使用,就已经能够满足日常对装饰器的使用了。但是,若想真正理解装饰器,并进行更高阶的使用还要了解其他一些知识:

python中,函数是一等对象;

区分导入时执行和运行时执行;

闭包和 nonlocal 声明等。

下面我们逐个介绍:

第一点,在 Python 中,函数是一等对象,这在上一篇其实已经提到了。“一等对象”满足下述条件:

a.在运行时创建;

b.能赋值给变量或数据结构中的元素;

c.能作为参数传给函数;

d.能作为函数的返回结果;

Python 中的整数、字符串和字典等都是一等对象,大家对比着理解一下,在此不再过多介绍。 

第二点,函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。看下面的例子:

al = []

def

deco(func):

print

(

'running deco and parm is{}'

.format(func))

al.append(func)

return

func

@deco

def

f1():

print

(

'running f1()'

)

@deco

def

f2():

print

(

'running f2()'

)

def

f3():

print

(

'running f3()'

)

def

main():

print

(

'running main()'

)

print

(

'al ->'

, al)

f1()

f2()

f3()

if

__name__==

'__main__'

:

main()

输出:

running deco

and

parm

is

<

function

f1 at

0x00000000006C2AE8

>

running deco

and

parm

is

<

function

f2 at

0x00000000011E6510

>

running main()

al -> [<

function

f1 at

0x00000000006C2AE8

>, <

function

f2 at

0x00000000011E6510

>]

running f1()

running f2()

running f3()

我们简单定义了一个装饰器,把传进来的参数(函数名)添加到列表,然后再返回该函数名。观察输出结果,在运行main函数之前,deco就已经运行了(输出了2次,因为f1和f2都用deco进行了装饰),之后对列表的输出也印证了这一点,而不管是被装饰的f1、f2还是未被装饰的f3都是在明确的调用之后才执行的。这就是Python 程序员所说的导入时和运行时之间的区别。 

第三点,闭包可以说是行为良好的装饰器赖以生存的关键。闭包其实并不难以理解,因为它只存在于嵌套函数中。还是看例子:

def

get_averager():

nums = []

def

averager(new_value):

nums.append(new_value)

total = sum(nums)

return

total/len(nums)

return

averager

avg = get_averager()

print

(avg)

print

(avg(

10

))

print

(avg(

11

))

print

(avg(

12

))

输出:

<function get_averager.

<locals>

.averager at 0x0000000000672AE8>

10.0

10.5

11.0

定义一个嵌套函数,作用是计算累计传入参数的平均值。通过输出结果我们可以看到avg是getaverager()返回的averager,通过不断的调用avg(),返回当前的平均值。这里面有个问题是我们之前没有探讨的:nums是外层函数中的变量,那么在getaverager()返回完毕之后,它的本地作用域应该一并消失,那为什么avg中还可以使用呢?这就是闭包的作用了。其实,闭包就是指函数作用域延伸了(从外层函数延伸到内层函数)。延伸的值保存在内层函数的code属性中:

>>>

def

get_averager():

nums = []

def

averager(new_value):

nums.append(new_value)

total = sum(nums)

return

total/len(nums)

return

averager

>>> avg = get_averager()

>>> avg.__code__.co_freevars

(

'nums'

,)

我们注意到上面这个例子把所有值存储在历史列表中,然后在每次调用 averager 时使用 sum 求和。更好的实现方式是,只存储目前的总值和元素个数,然后使用这两个数计算均值。依照这个思路我们可以对代码进行优化,但是在此之前我们需要看一个简单的例子:

>>> b =

99

>>>

def

f(t):

print

(t)

print

(b)

b =

2

>>> f(

10

)

各位可以想象一下,这个输出会是什么?

10

99

是不是这个?其实不然,真实的结果是这样:

>>> f(

10

)

10

Traceback

(most recent call

last

):

File

"<pyshell#42>"

, line

1

,

in

<module>

f(

10

)

File

"<pyshell#41>"

, line

3

,

in

f

print

(b)

UnboundLocalError

:

local

variable

'b'

referenced before assignment

>>>

这个结果可能让你惊讶,但事实就是如此。因为Python 编译函数的定义体时,由于b在函数中给它赋值了,因此它判断 b 是局部变量。后面调用 f(10) 时, f 的定义体会获取并打印局部变量 b的值,但是尝试获取局部变量 b的值时,发现 b 没有绑定值。这不是缺陷,而是设计选择:Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。了解了这一点,我们来优化一下之前计算平均值的例子:

def

get_averager():

count =

0

total =

0

def

averager(new_value):

count +=

1

total += new_value

return

total / count

return

averager

逻辑上看没啥问题,但是有了之前的铺垫,你可能会发现一些问题:内层函数对外层函数中的变量进行了重新赋值。我们来运行一下代码,就会发现报错:

UnboundLocalError

:

local

variable

'count'

referenced before assignment

而优化前的例子没遇到这个问题,因为nums是列表,我们只是调用 ums.append,也就是说,我们利用了列表是可变的对象这一事实。但是对数字、字符串、元组等不可变类型来说,只能读取,不能更新。如果尝试重新绑定,例如 count = count + 1,其实会隐式创建局部变量 count。 为了解决这个问题,Python 3 引入了 nonlocal 声明,如果为 onlocal 声明的变量赋予新值,闭包中保存的绑定会更新。

>>>

def

get_averager():

count =

0

total =

0

def

averager(new_value):

nonlocal

count, total

count +=

1

total += new_value

return

total / count

return

averager

>>> avg = get_averager()

>>> avg(

10

)

10.0

>>> avg(

11

)

10.5

>>> avg(

12

)

11.0

以上这三点就是对前文装饰器基础知识的补充,希望对大家有所帮助。

恭喜你阅读完了本文,相信通过本文的阅读,你已经知道了python装饰器是什么、有什么用、怎么用等问题,掌握python装饰器相关的问题,可以让你的编程工作事半功倍,不信的话就快点试试吧。如果你还有python相关的问题,欢迎随时来达内python培训机构进行咨询。

免责声明:内容和图片源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

预约申请免费试听课

填写下面表单即可预约申请免费试听! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!

上一篇:python培训分享之python全貌讲解
下一篇:详述python中的闭包,旨在让你理解!

如何运用Python编程处理大数据?用Python编程处理大数据的技巧是什么?

Python面向对象编程的知识点都在这了!

Python的高级特征及用法(部分)

听说这些Python知识,很少有人知道!

Copyright © 2023 Tedu.cn All Rights Reserved 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有

选择城市和中心
黑龙江省

吉林省

河北省

湖南省

贵州省

云南省

广西省

海南省