python魔法函数和装饰器_Python—装饰器

一、装饰器概述

在Python中,装饰器是一种设计模式,装饰器可以在不改变被装饰对象(函数、类)的同时为对象增添新的功能。这也被成为元编程(metaprogramming),因为一部分代码尝试在编译时修改另一部分代码。

二、装饰器理解必备知识

1. 必备一:Python中一切皆对象

在Python中,一切皆对象,包括函数、类等,而变量名只是用于指向对象的标识符,多个不同的变量可以指向同一个对象。如下面代码表明两个变量名指向了同一个函数对象,则以两个变量名加上()可以调用同一个函数。

def plus_one(number):

return number + 1

add_one = plus_one

print("add_one(5) = %d" % add_one(5))

代码运行结果为:

add_one(5) = 6

2. 必备二:函数可作为参数传递

在Python中,指向函数的变量可以作为参数传递给另外一个函数,如下述代码所述:

def plus_one(number):

return number + 1

def function_call(function):

number_to_add = 5

return function(number_to_add)

print("function_call(plus_one) = %d" % function_call(plus_one))

代码运行结果为:

function_call(plus_one) = 6

3. 必备三:Python中的闭包特性指向函数对象的变量(即函数名)可被当作返回值返回;

内层嵌套函数可以访问并记录外层函数中定义的变量。

4. 必备四:Python可调用对象本质

在Python中,函数和方法都被称作可调用对象。实际上,Python中任何实现了魔法方法__call__的对象都是可调用对象。因此,Python中,装饰器就是一个可调用对象,该可调用对象能够返回一个可调用对象。

实际上,可以通过函数名.__dir__()查看函数对象的确有魔法方法__call__。

三、装饰器探究

1. 函数装饰器

1.1 装饰普通函数

基于上述必备知识,先看下列代码示例:

def make_pretty(func):

def inner():

print("I got decorated")

func()

return inner

def ordinary():

print("I am ordinary")

def main():

ordinary()

pretty = make_pretty(ordinary)

pretty()

if __name__ == '__main__':

main()

上述代码的运行结果为:

I am ordinary

I got decorated

I am ordinary

上述示例代码中,make_pretty()就是一个装饰器,在下述步骤中:

pretty = make_pretty(ordinary)

函数ordinary()被装饰,且装饰器的返回值被赋给了变量pretty。因此,装饰器函数在原函数的基础上增添了新的功能。

事实上,基于必备一,通常在装饰一个函数后,用以接收装饰器返回值的变量名和被装饰函数名保持一致,用上述例子,即:

ordinary = make_pretty(ordinary)

因此,Python中对此有如下简化语法(在Python,这就叫所谓的语法糖):

@make_pretty

def ordinary():

print("I am ordinary")

即上述语法等价于:

def ordinary():

print("I am ordinary")

ordinary = make_pretty(ordinary)

1.2 装饰带参数函数

上述装饰器非常简单且且只能装饰不带任何参数的函数。如果希望装饰如下所示函数:

def divide(a, b):

return a/b

由于上述函数接受两个参数,且当传递b等于零时会发生异常,下面通过非捕获异常的方式完善上述代码:

def smart_divide(func):

print("Preparing to decorate the divide func")

def inner(a, b):

print("I am going to divide", a, "and", b)

if b == 0:

print("Whoops! cannot divide because divisor is zero...")

return

func(a, b)

return inner

@smart_divide

def divide(a, b):

print("a / b = ", a / b)

def main():

print("-" * 25)

divide(2, 5)

divide(2, 0)

if __name__ == '__main__':

main()

上述代码的运行结果为:

Preparing to decorate the divide func

-------------------------

I am going to divide 2 and 5

a / b = 0.4

I am going to divide 2 and 0

Whoops! cannot divide because divisor is zero…

即上述代码完成了对于接收两个参数的函数进行装饰。事实上,由于:

@smart_divide

def divide(a, b):

print("a / b = ", a / b)

等价于:

def divide(a, b):

print("a / b = ", a / b)

divide = smart_divide(divide)

即此时变量divide和smart_divide的返回值inner同时指向嵌套函数处,则在第22、23行时,参数a、b相当于分别被传递至嵌套函数inner处。进而,在第10行调用func指向的原函数时,参数a、b分别被进一步传递。

1.3 装饰有返回值函数

def smart_divide(func):

print("Preparing to decorate the divide func")

def inner(a, b):

print("I am going to divide", a, "and", b)

if b == 0:

print("Whoops! cannot divide because divisor is zero...")

return

return func(a, b)

return inner

@smart_divide

def divide(a, b):

return a / b

def main():

print("-" * 25)

ret1 = divide(2, 5)

print(ret1)

ret2 = divide(2, 0)

print(ret2)

if __name__ == '__main__':

main()

实际上,由于程序第23、26行调用被装饰后的divide()函数相当于调用inner函数,在调用inner()时,由于需要使用func调用指向被装饰前的divide()函数并确保后者仍旧正确返回,则需要在inner()中返回func()的返回值。

1.4 通用装饰器

实际上,为了使得装饰器可以通用,需要考虑Python中函数接收不定长参数的特性,为了确保装饰器能够较为通用,即对接收不定长(元组、字典)参数的函数进行装饰,则通用装饰器有如下格式:

def universal_decorator(func):

def inner(*args, **kwargs):

print("Decorative operations for func")

return func(*args, **kwargs)

return inner

需要注意的是,嵌套函数参数位置的args、kwargs分别表示元组和字典,而在嵌套函数中调用被装饰前函数时,使用的*args、**kwargs分别表示对元组和字典先进行拆包,然后再传递。

1.5 多个装饰器装饰一个函数

在Python,多个装饰器可对同一个函数进行装饰,如下列代码所示:

def star(func):

def inner(*args, **kwargs):

print("*" * 30)

func(*args, **kwargs)

print("*" * 30)

return inner

def percent(func):

def inner(*args, **kwargs):

print("%" * 30)

func(*args, **kwargs)

print("%" * 30)

return inner

@star

@percent

def printer(msg):

print(msg)

def main():

printer("Hello")

if __name__ == '__main__':

main()

上述代码的运行结果为:

******************************

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Hello

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

******************************

实际上,产生上述运行结果的原因在于,上述代码中:

@star

@percent

def printer(msg):

print(msg)

等价于:

def printer(msg):

print(msg)

printer = star(percent(printer))

即:先由装饰器percent对printer()函数进行装饰,然后再由装饰器star对被装饰后的printer()函数进行装饰。

1.6 带参数装饰器

1.7 装饰类

2. 类装饰器

下列代码演示了如何通过一个名为Test的类来装饰函数get_str。

class Test(object):

def __init__(self, func):

self.func = func

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

print("这里是装饰器添加的功能...")

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

@Test # 相当于get_str = Test(get_str)

def get_str(*args, **kwargs):

return "Strings to be decorated..."

print(get_str())

实际上,第10行代码@Test相当于:

get_str = Test(get_str)

即Test(get_str)相当于创建实例对象,且对象名为get_str,且此时参数get_str被传入类的初始化方法。

又由必备四,在使用()调用对象时会默认调用对象的__call__()方法,则该方法相当于函数装饰器中的嵌套函数。

四、装饰器总结

在Python中,装饰器可以在不改变函数源代码、不继承父类并重写父类方法的同时,动态更改函数、方法、类的功能。使用装饰器可以确保你的代码DRY(Don’t Repeat Yourself)。装饰器有如下几种应用场景:在Flask、Django等框架中实现验证;

打日志;

测量程序执行时间;

同步。

五、参考资料

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值