Python中的函数式编程 3个问题了解装饰器

前言:

装饰器是python的一个重要特性, 通过对以下3个方面来进行介绍,

1. 装饰器是什么

2. 为什么需要装饰器

3. 如何使用装饰器

希望可以通过本文理解装饰器, 从而写出更加简洁,优雅, 更加pythonic的代码.

什么是闭包?

有关装饰器的文章中, 经常看到一句话, 装饰器的本质是个闭包. 那么什么是闭包呢, 维基百科中的定义: 闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表), 环境里是若干对符号和值的对应关系

 先通过一个例子理解闭包的概念. 

In [1]: X = 10

In [2]: def to_int(s: str):
   ...:     return int(s)

In [3]: def adder(y: str):
   ...:     ret = X + to_int(y)
   ...:     return ret

In [4]: adder('8')
Out[4]: 18

 从以上代码示例中可以看到,  参数y是函数adder的局部变量, 在函数adder中尝试获取变量X时, 发现函数内没有定义, 所以尝试去上一级作用域获取变量内容, 获取到了变量X和函数to_int.  从而得到执行结果 10 + to_int('8') = 18.  函数adder依赖于一个外部环境, 变量X和函数to_int才可以成功运行. 若将它们大包到一起, 定义一个新的函数, 就是一个闭包.

In [1]: def closure(y: str):
   ...:     X = 10
   ...:
   ...:     def to_int(s: str):
   ...:         return int(s)
   ...:
   ...:     def adder(y: str):
   ...:         return X + to_int(y)
   ...:
   ...:     return adder(y)

In [2]: closure('8')
Out[2]: 18

1. 什么是装饰器

装饰器的目的是扩展函数的功能, 接受一个函数作为参数, 返回一个新的装饰过的函数. 在不修改原函数的前提下, 为其添加新的功能.  

🎈可以把装饰器理解成一个手机壳, 在不修改手机的情况下, 通过套一个手机壳, 增加了一些新的功能(更耐摔, 更多的配色), 返回一个新的手机. 

手机壳接受一个手机, 返回一个包装过的"新手机", 增加一些额外的功能. 

2. 为什么需要装饰器

有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。

3. 装饰器的使用

下面从一个实例开始

# 定义一个函数
In [1]: def decorator(func):  # 接受一个函数 作为参数
   ...:     def wrapper():    # 定义一个新的函数, 在func函数增加额外的功能
   ...:         print("Do something before the function is called.")
   ...:         func()
   ...:         print("Do something after the function is called.")
   ...:     return wrapper    # 返回新的函数

In [2]: def woohoo():
   ...:     print("woo hoo hoo!")

# 将woohoo装饰后的函数赋值给new_woohoo
In [3]: new_woohoo = decorator(woohoo)

# 运行new_woohoo()
In [4]: new_woohoo()
Do something before the function is called.
woo hoo hoo!
Do something after the function is called.

# 运行woohoo()
In [5]: woohoo()
woo hoo hoo!

所谓的装饰发生在以下行:

new_woohoo = decorator(woohoo)

docorator 对函数woohoo进行装饰, 返回一个新函数赋值给new_woohoo

实际上 new_woohoo函数指向的是在decorator中定义的wrapper

In [6]: new_woohoo
Out[6]: <function __main__.decorator.<locals>.wrapper()>

3.1 语法糖 

 上面的写法有点繁琐, 在python中可以使用另一种方式来使用装饰器, 使用 @ 符号

# 定义一个函数
In [1]: def decorator(func):  # 接受一个函数 作为参数
   ...:     def wrapper():    # 定义一个新的函数, 在func函数增加额外的功能
   ...:         print("Do something before the function is called.")
   ...:         func()
   ...:         print("Do something after the function is called.")
   ...:     return wrapper    # 返回新的函数
    
In [2]: @decorator
   ...: def woohoo():
   ...:     print("woo hoo hoo!")

其中 @decorator 就是  woohoo = decorator(woohoo) 一种简洁的写法。这就是将装饰器应用于函数的方式。

3.2 装饰带参数的函数

之前的例子中, 被装饰的函数woohoo没有参数, 如果需要支持对带参数的函数进行装饰, 需要在装饰器中定义的函数也支持参数. (带上手机壳的手机要尽可能跟原来的手机保持一致, 耳机口和充电口也要支持)

如下例, 因为装饰器中最终返回的新函数是warpper, 所以wrapper需要和被装饰的函数wooha支持同样的参数.

In [1]: def decorator(func):
   ...:     def wrapper(name):
   ...:         print("Do something before the function is called.")
   ...:         func(name)
   ...:         print("Do something after the function is called.")
   ...:     return wrapper

In [2]: @decorator
   ...: def wooha(name):
   ...:     print("wooha!", name)

In [3]: wooha('dongmei')
Do something before the function is called.
wooha! dongmei
Do something after the function is called.

为了使装饰器更加通用,  通常的做法是在内部包装函数中使用*args和**kwargs。然后它将接受任意数量的位置和关键字参数。

In [1]: def decorator(func):
   ...:     def wrapper(*args, **kwargs): # 接受任意长度的参数
   ...:         print("Do something before the function is called.")
   ...:         func(*args, **kwargs)
   ...:         print("Do something after the function is called.")
   ...:     return wrapper

In [2]: @decorator
   ...: def yahaha(ya, haha='haha'):
   ...:     print("got you,", ya, haha)

In [3]: yahaha('bruce', 'lee')
Do something before the function is called.
got you, bruce lee
Do something after the function is called.

 3.3 装饰带返回数据的函数

通过上面的介绍, 装饰器中函数的输入要和被装饰的函数一致, 同理, 输出也要保持. 即wrapper的入参和返回要和被装饰的函数func保持一致. 需要返回数据的函数, 装饰器示例如下

In [1]: def decorator(func):
   ...:     def wrapper(*args, **kwargs): 
   ...:         print("Do something before the function is called.")
   ...:         ret = func(*args, **kwargs)
   ...:         print("Do something after the function is called.")
   ...:         return ret  # 返回func函数的返回
   ...:     return wrapper

In [2]: @decorator
   ...: def yahaha(ya, haha='haha'):
   ...:     return f"got you, {ya} {haha}"
   

In [3]: yahaha('bruce', 'lee')
Do something before the function is called.
Do something after the function is called.
Out[3]: 'got you, bruce lee'

4.装饰器的高阶用法

4.1 保留被装饰的函数信息

我们查看一下刚刚定义的yahaha函数的信息

In [4]: yahaha
Out[4]: <function __main__.decorator.<locals>.wrapper(*args, **kwargs)>

In [5]: yahaha.__name__
Out[5]: 'wrapper'

如上面代码可以看到, yahaha被decorator装饰之后, 查看yahaha的信息之后,发现返回的是decorator中定义的wrapper函数的信息, 尽管技术上是正确的, yahaha目前确实是wrapper函数. 但是为了保持可读性, 我需要保持yahaha函数的信息. 

为了解决这个问题,装饰器应该使用functools模块下的wraps装饰器,它会保留有关原始函数的信息。

In [1]: from functools import wraps

In [2]: def decorator(func):
   ...:     @wraps(func) # 给wrapper添加上func函数的信息
   ...:     def wrapper(*args, **kwargs): 
   ...:         print("Do something before the function is called.")
   ...:         ret = func(*args, **kwargs)
   ...:         print("Do something after the function is called.")
   ...:         return ret
   ...:     return wrapper

In [2]: @decorator
   ...: def yahaha(ya, haha='haha'):
   ...:     return f"got you, {ya} {haha}"
   

In [3]: yahaha.__name__
Out[3]: 'yahaha'
In [4]: yahaha
Out[4]: <function __main__.yahaha(ya, haha='haha')>

4.2 带参数的装饰器

有些时候需要一些参数来定制装饰器的属性. @decorator(args)这样使用, 思路就很简单, 只需要decorator(args)函数的返回是一个装饰器就可以了.  写代码的时候就是再装饰器外面再套一层函数即可, 比如一个将函数执行特定次数的装饰器repeat, 代码示例:

In [1]: def repeat(n):
   ...:     def decorator(func): 
   ...:         @wraps(func)
   ...:         def wrapper(*args, **kwargs):
   ...:             for _ in range(n):
   ...:                 ret = func(*args, **kwargs)
   ...:             return ret
   ...:         return wrapper
   ...:     return decorator

In [2]: @repeat(2)
   ...: def yahaha(ya):
   ...:     print('ola', ya)

In [3]: yahaha('maggie')
ola maggie
ola maggie

如以上代码所示, 函数repeat(2) 返回的是一个decorator装饰器,  @repeat(2) 返回的是一个wrapper函数实例, 将yahaha函数执行2次.

4.3 用类来实现装饰器

回想一下,装饰器语法@decorator只是一种更简单的说法func = decorator(func)。这个语句和我们熟悉的创建一个类的实例语法相似, 只需要类返回的实例是可被调用的即可. 那么只需要实现请特殊的.__call__()方法来复写实例的 ()  调用即可. 

下面来看一个示例:

In [66]: class Deco:
    ...:     def __init__(self, func):
    ...:         self.func =func
    ...:     def __call__(self, *args, **kwargs):
    ...:         print('aloha')
    ...:         return self.func(*args, **kwargs)

In [67]: @Deco
    ...: def yahaha(ya):
    ...:     print('ola', ya)

In [68]: yahaha('moogy')
aloha
ola moogy

5. 总结

  • 装饰器的作用, 在不修改原函数的前提下, 为其添加新的功能.
  • 可以重复使用
  • 可以用参数和返回值装饰函数
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值