前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python 装饰器

Python 装饰器

作者头像
Steve Wang
发布2022-05-10 09:16:56
4160
发布2022-05-10 09:16:56
举报
文章被收录于专栏:从流域到海域从流域到海域

装饰器在Java中是一种设计模式。 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

装饰器在Python中提供类似于注解使用方式,直接使用@装饰器名就是调用已经写好的装饰器对现有方法进行装饰,同时python语言已经内置了大量已经实现好的装饰器。这种便利性是基于Python支持面向函数编程这一特性从而可以简单实现闭包而产生的。

闭包

闭包就是能够读取其他函数内部变量的函数,它实现上是一种特殊的代码块,一个函数包裹着一个内嵌函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁,它可以通过参数传递改变变量的作用域。

Python中的一个闭包示例:

代码语言:javascript
复制
def outer(a):
    c = 5
    def inner():
        return a + c + 10
    return inner

fun1 = outer(10)
fun2 = outer(20)
fun3 = outer(30)
print(fun1())
print(fun2())
print(fun3())
# 25
# 35
# 45

改变了变量作用域体现在,函数`inner()`可以访问到变量`a`。

在Python中创建一个闭包可以归结为以下三点:

  1. 闭包函数必须有内嵌函数
  2. 内嵌函数需要引用该嵌套函数上一级namespace中的变量
  3. 闭包函数必须返回内嵌函数

闭包可以被理解为一个只读的对象,你可以给他传递一个属性,但它只能提供给你一个执行的接口,这就牵扯到的另一个特性:惰性求值。 还有另一种用处:需要对某个函数的参数提前赋值的情况,当然在Python中已经有了很好的解决方案functools.parial, 但是用闭包也能实现。

惰性求值这点,我不是很赞同,下面是我关于闭包的看法。

  • 首先,闭包具有一定程度的封装性,内嵌函数只能通过外层函数的接口传递参数并访问。
  • 其次,内层函数可以使用传递给外层函数的参数以及外层函数里定义在内嵌函数之前的变量。闭包改变了变量的作用域,使得我们可以使用局部变量来完成全局变量的功能,减少了全局变量的使用。
  • 再者,外层函数本身也可以自己执行一些功能,相当于增加了内嵌函数的功能(装饰器就是通过闭包实现的)。
  • 最后,可以创建多个变量用外层函数赋值,每一个变量所代表的函数都具有独立的参数范围和作用范围。

装饰器

装饰器用于在一个函数上添加一些额外的操作,比如日志、计时等固定操作,一定程度上可以实现切面编程。Python可以非常简单地使用@装饰器名这种注解方式使用已经写好的装饰器。

Python的装饰器基于闭包实现,以下是一个标准范本:

代码语言:javascript
复制
from functools import wraps

def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "func will not run"
        return f(*args, **kwargs)
    return decorated

@decorator_name
def func():
    return ("func is running")

can_run = True
print(func())

can_run = False
print(func())

装饰器依赖具备以下特点的闭包实现:

  • 外层函数(装饰器函数)被传递的参数是一个函数对象
  • 内层函数(装饰函数)建议以return形式调用被传入到外层函数的函数对象
  • 外层函数返回装饰函数(闭包本身的特性)

以下是一个来自Python cookbook的简单示例:

代码语言:javascript
复制
import time
from functools import wraps

def timethis(func):
    """
    Decorator that reports the executions time.
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, "exeuction time:", end-start, "s")
        return res
    return wrapper

@timethis
def countdown(n):
    """
    counts down
    """
    while n > 0:
        n -= 1
countdown(100000)

# countdown exeuction time: 0.007203102111816406 s

该例说明了装饰器的便利性,只需要在每一个函数前加上@timethis,就可以实现对函数执行时间进行打印的功能,在没有改变原函数代码的条件下,为原函数增加了计时功能,而且能够对任何函数使用。

wraps是用来做什么的?

看到这里,聪明的你或许已经发现了@wraps本身就是一个装饰器,那么它的功能是什么呢?

实际上,不加@wraps,也可以实现装饰器的逻辑。加上@wrap是为了保留被装饰函数的元信息(函数名、文档、注释等)。

代码语言:javascript
复制
# 不使用wraps
>>> countdown.__name__
'wrapper'
>>> countdown.__doc__
>>> countdown.__annotations__
{}
# 使用wraps
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown.__name__
'countdown'
>>> countdown.__doc__
'\n\tCounts down\n\t'
>>> countdown.__annotations__
{'n': <class 'int'>}
>>>

装饰器类

Python不仅支持利用闭包实现装饰器,也支持在类中实现装饰器,实现了装饰器的类可以被称作装饰器类。

在一个类中实现内置的__call__()__get__()两个内置方法,就实现了一个装饰器类,并且能想装饰器那样使用@加装饰器名的方式使用,下面是一个来自python cookbook的示例:

代码语言:javascript
复制
import types
from functools import wraps

class Profiled:
    def __init__(self, func):
        wraps(func)(self)
        self.ncalls = 0
    
    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        print("call times: ", self.ncalls)
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

这里的wraps一样是用来保留函数元信息的。

装饰器类一样是通过将函数作为参数传递,并作为返回值返回来实现的。

在类里面或者外面都可以像使用普通装饰器那样使用它:

代码语言:javascript
复制
@Profiled
def add(x, y):
    return x+y

print(add(3, 4))
print(add(4, 5))

# call times:  1
# 7
# call times:  2
# 9

class Spam:
    @Profiled
    def bar(self, x):
        print(self, x)

你可以通过首字母是否大写来判断一个装饰器是依靠闭包还是类实现的。

代码语言:javascript
复制

更多高级用法可以参见参考文献第3篇。

参考文献

  1. 装饰器模式(Decorator Pattern)
  2. 装饰器-廖雪峰的Python教程
  3. Python cookbook: 第九章元编程
  4. 面试Python高频问题
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-03-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 闭包
  • 装饰器
    • wraps是用来做什么的?
    • 装饰器类
    • 参考文献
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档