python学习笔记3.4-函数装饰器

软件开发的过程中,最基本的技能就是:不要重复自己的工作。也就是说,在任何时候,当需要创建高度重复的代码时,通常都需要寻找一个更加快捷的解决方案。在python中,这类问题常常会归为“元编程”。 简而言之,元编程的主要目标是创建函数和类,并用他们来操纵代码(通常的行为有生成、修改、包装已有的代码)。Python中基于这个目的的方法有装饰器、类装饰器、元类以及有用的主题(常见的有对象签名、用exec()来执行代码以及检查函数和类的内部结构)。 元编程系列主要内容是探讨各种元编程技术,通过实例来讲解如何利用这些技术来自定义python的行为,使其能满足我们不同寻常的需求。

1 装饰器

1.1 用装饰器给函数添加一个包装

装饰器的本质就是一个函数,它可以接受一个函数作为输入并返回一个新的函数作为输出。我们可以利用给函数来建立一个装饰器(相当于给函数加上包装层)来添加额外的处理,例如记录日志、计时统计等。 最简单的一个例子是计时系统,我想记录函数的运行时间并打印到控制台,最通常的做法是:

def CutDown(n):
    import time
    start = time.time()

    while n > 0:
        n -= 1

    end = time.time()

    print(end-start)

CutDown(10000000)
屏幕输出:1.1250569820404053

下面引入装饰器方法:

def GetRunTime(func):
    import time
    from functools import wraps

    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result

    return wrapper


@GetRunTime
def cutdown(n):
    while n > 0:
        n -= 1

cutdown(10000000)
屏幕输出:cutdown 1.1274616718292236

用第二种方法的好处就是,加入我需要在很多的函数上记录其运行时间,则只需要在需要记录时间的函数上面添加@GetRunTime即可,调试非常方便,代码也简洁易懂。

装饰器内部的代码一般会涉及创建一个新的函数,利用*args和**kwargs来接受任意的参数,在示例中wrapper()函数就是这么操作的。在这个函数的内部,我们需要调用原来的输入参数(即被包装的函数的输入参数)并返回它的结果。

其实运行过程就是将被装饰函数打包到装饰器中运行并返回运行结果,作为代价肯定是需要牺牲一定的效率的。在示例中还有一个是需要注意的,那就是@wraps(func),它用来保存函数的元数据。其实也可以不要这个,但是就会丢失被装饰函数的一些元数据,例如函数名、文档字符串、函数注解以及调用签名。1.2就会讲解如何保存元数据。

1.2 运用装饰器时保存函数的元数据

在1.1中也提到过,即使没有@wraps(func)函数也能运行,代价是丢失一些函数的元数据:

def GetRunTime(func):
    import time
    #from functools import wraps

   # @wraps(func)
    def wrapper(*args, **kwargs):
        """
        :param args:
        :param kwargs:
        :return:
        """
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result

    return wrapper


@GetRunTime
def cutdown(n):
    """
    :param n:
    :return:
    """
    while n > 0:
        n -= 1

cutdown(10000000)
print(cutdown.__name__)
print(cutdown.__doc__)

控制台打印信息:

cutdown 1.4844520092010498
wrapper

        :param args:
        :param kwargs:
        :return:

我们发现打印信息不是cundown这个函数的,而是装饰器函数的。再使用@wraps(func)这个技术: (代码略) 控制台打印信息:

cutdown 1.193265676498413
cutdown

    :param n:
:return:

这个时候所有信息都属于cutdown函数的。 上面这个例子告诉我们,在我们自己编写装饰器的时候一定要记得使用@wraps(func),这样才不会丢失被装饰函数的元数据。同时,从这个例子中我们也可以看到被装饰函数的运行过程,装饰器会接管被装饰函数的所有,并返回结果。 @装饰器的重要特性就是它可以通过wrapped属性来访问被包装的函数,所以我们可以利用这个特性来解包。

1.3 对装饰器进行解包

在1.2的末尾也提到过,利用wrapped属性来进行解包。例如在1.2中可以直接使用:

@GetRunTime
def cutdown(n):
    """
    :param n:
    :return:
    """
    print('cundown is running')
    while n > 0:
        n -= 1

cutdown.__wrapped__(10000000)
打印输出:
cundown is running

当有多个装饰器的函数也可以通过这种方法来访问。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python中文社区

Python标准库(1) — itertools模块

專 欄 ❈Jerry,Python中文社区专栏作者。 blog:https://my.oschina.net/jhao104/blog github:ht...

1949
来自专栏黒之染开发日记

css3动画在手机端的流畅度比较

我发现即使都是用css3的transition做动画,有的属性在动画播放时却会不流畅,出现定格动画的效果,这里做个比较,方便我以后做动画。

1292
来自专栏武军超python专栏

2018年7月24日初次接触面向对象

昨天io模块知识的回顾补充: 用json模块可以把程序中的数据转换为字符串类型存储到文件中,但是字符串类型不安全,可以用记事本 直接打开查看里面的的所有内容

1043
来自专栏Python

python基础之数据类型与变量

一.变量 1 什么是变量之声明变量 #变量名=变量值 age=18 gender1='male' gender2='female' 2 为什么要有变量 变量作...

2646
来自专栏JetpropelledSnake

Python入门之迭代器/生成器/yield的表达方式/面向过程编程

 本章内容     迭代器     面向过程编程       一、什么是迭代       二、什么是迭代器       三、迭代器演示和举例       四、生...

2699
来自专栏web前端-

JavaScript基础概述

    1.常量: 常量就是在程序运行过程中,不会发生变化的量,常量通常用来表示固定不变的量,比如圆周率,万有引力常量

502
来自专栏java一日一条

函数式编程的优与劣

如今函数式编程越来越流行。越来越多的编程语言支持函数式编程风格,人们学习如何使用它们。函数式编程已不像以前那么小众——现在Ruby,Java和JavaScrip...

472
来自专栏java一日一条

函数式编程的优与劣

如今函数式编程越来越流行。越来越多的编程语言支持函数式编程风格,人们学习如何使用它们。函数式编程已不像以前那么小众——现在Ruby,Java和JavaScrip...

501
来自专栏技术点滴

虚函数与虚继承寻踪

虚函数与虚继承寻踪 封装、继承、多态是面向对象语言的三大特性,熟悉C++的人对此应该不会有太多异议。C语言提供的struct,顶多算得上对数据的简单封装,而C...

2209
来自专栏逆向技术

C语言第五讲,语句 顺序循环选择.

括号的内容我们给真假就行, 对应到高级语言中 则是 true (真) 和 false(假)

3345

扫码关注云+社区