资讯专栏INFORMATION COLUMN

python学习笔记 函数装饰器

jsliang / 507人阅读

摘要:实现一个简单的装饰器输出被装饰函数的运行时间简单运用运行结果运行过程中,首先输出装饰器函数中的内容被装饰函数运行时间长度函数名称和实际参数计算结果然后得到最终的计算结果。

函数装饰器

函数装饰器用于在源码中“标记”函数, 以某种方式增强函数的行为,这是一个强大的功能。

函数装饰器是一个可调用对象,其参数是另外一个函数,即被装饰函数。装饰器可能处理被装饰函数,然后将其返回,或者将其替换成另一个函数或可调用对象。

函数装饰器的一个重要特性就是,它们在被装饰的函数定义之后立即运行。

registry = []

def register(func):
    print("running register(%s)" % func)
    registry.append(func)
    return func

@register
def f1():
    print("running f1()")

@register
def f2():
    print("running f2()")

def f3():
    print("running f3()")

def main():
    print("running main()")
    print("registry ->", registry)
    f1()
    f2()
    f3()

if __name__ == "__main__":
    main()

运行结果:

running register()
running register()
running main()
registry -> [, ]
running f1()
running f2()
running f3()

可以看到,被装饰的 f1() 和 f2() 首先被运行,随后才运行main()中的语句。

被装饰函数运行时,其本身的内容(示例中print语句)并没有被执行,而是运行了装饰器函数中的print语句;这就是装饰器的作用,替代被装饰函数,同时装饰器也可以调用外界自由变量(registry),从而引出一个重要概念:

闭包

实例中registry变量和register函数组合的共同体,被成称为闭包。

该例中有两个不太寻常的地方:

装饰器函数和被装饰函数定义在一个模块之中;一般来说,两者应该定义在不同的模块;

register装饰器返回的函数与通过参数传入的相同;实际上,大部分装饰器会在内部定义一个函数,然后将其返回。

实现一个简单的装饰器
# clockdeco.py 输出被装饰函数的运行时间
import time


def clock(func):
    def clocked(*args):
        t0 = time.perf_counter()
        result = func(*args)
        elapsed = time.perf_counter() - t0
        name = func.__name__
        arg_str = ",".join(repr(arg) for arg in args)
        print("[%0.8fs] %s(%s) -> %r" % (elapsed, name, arg_str, result))
        return result
    return clocked

简单运用:

# clockdeco_demo.py 
import time
from clockdeco import clock

@clock
def snooze(seconds):
    time.sleep(seconds)

@clock
def factorial(n):
    return 1 if n < 2 else n*factorial(n-1)

def main():
    print("*" * 40, "Calling snooze(.123)")
    snooze(.123)
    print("*" * 40, "Calling factorial(6)")
    print("6! =", factorial(6))

if __name__ == "__main__":
    main()

运行结果:

**************************************** Calling snooze(.123)
[0.12240868s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000068s] factorial(1) -> 1
[0.00020317s] factorial(2) -> 2
[0.00039755s] factorial(3) -> 6
[0.00053638s] factorial(4) -> 24
[0.00062375s] factorial(5) -> 120
[0.00067319s] factorial(6) -> 720
6! = 720

运行过程中,首先输出装饰器函数中的内容:

被装饰函数运行时间长度;

函数名称和实际参数

计算结果

然后得到最终的计算结果。可见,装饰器函数的优先级较高

当然,该实例中的装饰器具有几个缺点:

不支持关键字参数

遮盖了被装饰函数的__name__和__doc__属性

下面的例子对其做出改进:

# clockdeco2.py
import time
import functools


def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(",".join(repr(arg) for arg in args))
        if kwargs:
            pairs = ["%s=%r" % (k, w) for k, w in sorted(kwargs.items())]
            arg_lst.append(", ".join(pairs))
        arg_str = ", ".join(arg_lst)
        print("[%0.8fs] %s(%s) -> %r " % (elapsed, name, arg_str, result))
        return result
    return clocked

运用:

# clockdeco_demo.py
import time
from clockdeco2 import clock

@clock
def snooze(seconds):
    time.sleep(seconds)

@clock
def factorial(n):
    return 1 if n < 2 else n*factorial(n-1)

def main():
    print("*" * 40, "Calling snooze(.123)")
    snooze(.123)
    print("*" * 40, "Calling factorial(6)")
    print("6! =", factorial(6))

if __name__ == "__main__":
    main()

运行结果:

**************************************** Calling snooze(.123)
[0.12328553s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000000s] factorial(1) -> 1
[0.00000000s] factorial(2) -> 2
[0.00000000s] factorial(3) -> 6
[0.00099683s] factorial(4) -> 24
[0.00099683s] factorial(5) -> 120
[0.00099683s] factorial(6) -> 720
6! = 720

改进后的clockdeco2.py中,使用functools.wraps装饰器把相关属性从func复制到clocked中,此外,这个新版本还能正确处理关键字参数。functools.wraps是标准库中的装饰器,它可以用于装饰一个函数,但是对于被装饰函数本身的功能没有任何影响,它的功能只是传递函数内置参数。

参数化clock装饰器
# clockdeco_param.py
import time


DEFAULT_FMT = "[{elapsed:0.8f}s] {name}({args}) -> {result}"

def clock(fmt=DEFAULT_FMT):
    def decorate(func):
        def clocked(*_args):
            t0 = time.time()
            _result = func(*_args)
            elapsed = time.time() - t0
            name = func.__name__
            args = ",".join(repr(arg) for arg in _args)
            result = repr(_result)
            print(fmt.format(**locals()))
            return _result
        return clocked
    return decorate

def clock 是参数化装饰器工厂函数

decorate(func) 是真正的装饰器

clocked 包装被装饰的函数

示例1:

# clockdeco_param_demo.py
import time
from clockdeco_param import clock

@clock()
def snooze(seconds):
    time.sleep(seconds)

for i in range(3):
    snooze(.123)

运行结果:

[0.12367034s] snooze(0.123) -> None
[0.12367010s] snooze(0.123) -> None
[0.12366986s] snooze(0.123) -> None

示例2:

# clockdeco_param_demo.py
import time
from clockdeco_param import clock

@clock("{name}:{elapsed}s")
def snooze(seconds):
    time.sleep(seconds)

for i in range(3):
    snooze(.123)

运行结果:

snooze:0.12366843223571777s
snooze:0.12369871139526367s
snooze:0.12366509437561035s

示例3:

# clockdeco_param_demo.py
import time
from clockdeco_param import clock

@clock("{name}({args}) dt={elapsed:0.3f}s")
def snooze(seconds):
    time.sleep(seconds)

for i in range(3):
    snooze(.123)

运行结果:

snooze(0.123) dt=0.124s
snooze(0.123) dt=0.124s
snooze(0.123) dt=0.124s

分析三个示例可以看出,当装饰器clock的参数不同时,被装饰函数运行所得结果也会不同。

python中参数化装饰器的用意在于将更多的参数传送给装饰器,因为装饰器的第一个参数一定是被装饰函数。

备忘装饰器 functools.lru_cache

functools.lru_cache和functools.wraps一样,也是一个python内置装饰器,它的功能是将耗时的函数结果保存起来,避免传图相同的参数造成重复计算,从而节省代码运行时间。

下面以斐波那契数列写一个案例:

使用functools.lru_cache

import functools
from clockdeco import clock

@functools.lru_cache()
@clock
def fibonacci(n):

if n < 2:
    return  n
return fibonacci(n-2) + fibonacci(n-1)

if __name__=="__main__":

print(fibonacci(30))

运行结果:

[0.00000000s] fibonacci(0) -> 0
[0.00000068s] fibonacci(1) -> 1
......
[0.00000271s] fibonacci(29) -> 514229
[0.00542815s] fibonacci(30) -> 832040
832040

多次运行,计算fibonacci(30)大概耗时0.005秒左右

作为对比:

import functools
from clockdeco import clock


@clock
def fibonacci(n):
    if n < 2:
        return  n
    return fibonacci(n-2) + fibonacci(n-1)

if __name__=="__main__":
    print(fibonacci(30))

运行结果:

.......
[156.42139917s] fibonacci(28) -> 317811
[230.80184171s] fibonacci(29) -> 514229
[368.52227404s] fibonacci(30) -> 832040
832040

嗯……陷入沉思,虽然笔记本渣渣配置,但是运行了6分钟,差距太大

总结

函数装饰器就是用来装饰函数的函数,并使用内置函数替代被装饰函数

被装饰函数在定义时就运行,无需显示调用

内置装饰器functools.wraps可以为被装饰函数赋值内置属性

参数化装饰器可以接受不同参数并适用于被装饰函数,显示不同的作用,相当于装饰器中的定制化

参数化的装饰器嵌套层次较深,逻辑相对复杂

内置装饰器functools.lru_cache采用缓存机制,存储需要多次调用的函数值,从而降低代码运行时间

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42259.html

相关文章

  • Python装饰学习笔记

    摘要:前言最近跟着流畅的和学习,看到装饰器部分,有些头大倒不是因为概念难以理解,而是书和网上文章中有些地方有些矛盾之处在简单学习和实践之后,整理出我对装饰器的理解如下装饰器的定义在不同语境下,装饰器有不一样的含义,我大致认为有种定义一种把另一个对 前言 最近跟着《流畅的Python》和《Python Cookbook》学习,看到装饰器部分,有些头大倒不是因为概念难以理解,而是书和网上文章中有...

    Cristalven 评论0 收藏0
  • python学习笔记装饰

    摘要:装饰器介绍中的装饰器的目的是为一个目标函数添加额外的功能却不修改函数本身。装饰器的本身其实是一个特殊的函数。那么有啥更好的解决方式呢装饰器代码像上面这么写,可以较好地解决了上面提到的第一个问题。装饰器语法糖放在函数前面,相当于执行了等。 怎么理解python中的装饰器 一个比喻 知乎上有一个比较形象的比喻 https://www.zhihu.com/questio...:人类穿着内裤很...

    张金宝 评论0 收藏0
  • Python 装饰的理解

    摘要:的装饰器可以实现在代码运行期间修改函数的上下文,即可以定义函数在执行之前进行何种操作和函数执行后进行何种操作,而函数本身并没有任何的改变。中的参数,实际上则是传递给实际上是的参数因为装饰器也是个函数,那么装饰器自己的能不能有参数传递呢。 Python的装饰器可以实现在代码运行期间修改函数的上下文, 即可以定义函数在执行之前进行何种操作和函数执行后进行何种操作, 而函数本身并没有任何的改...

    animabear 评论0 收藏0
  • flask文档学习笔记1-快速入门

    摘要:示例如下静态路由使用动态变量的路由未指定变量类型使用动态变量的路由指定变量类型指定的路由变量,可以作为被装饰的函数参数传入进来。 开始决定认真的在网上写一些东西,主要原因还是在于希望能提升学习效果。虽说python写了有几年,但是web后端框架的确没怎么接触过,买了本狗书寥寥草草的过了一遍,发现很多东西还是理解不深,真的是好记性不如烂笔头,知识也要从基础开始,退回来好好看看官方文档,再...

    lingdududu 评论0 收藏0
  • Python

    摘要:最近看前端都展开了几场而我大知乎最热语言还没有相关。有关书籍的介绍,大部分截取自是官方介绍。但从开始,标准库为我们提供了模块,它提供了和两个类,实现了对和的进一步抽象,对编写线程池进程池提供了直接的支持。 《流畅的python》阅读笔记 《流畅的python》是一本适合python进阶的书, 里面介绍的基本都是高级的python用法. 对于初学python的人来说, 基础大概也就够用了...

    dailybird 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<