目录 | 上一节 (7.3 返回函数) | 下一节 (7.5 装饰方法)

7.4 函数装饰器

本节介绍装饰器(decorator)。因为这是一个高级主题,所以我们只做简单介绍。

译注:根据译者个人的猜测,在《设计模式》(《 Design Patterns: Elements of Reusable Object-Oriented Software》)一书中写到 decorator also known as wrapper,所以下文提到包装器(wrapper),其实说的就是装饰器。这里为了保持和原文一致,所以翻译的时候没有将“包装器”替换为“装饰器”。

日志示例

考虑这样一个函数:

def add(x, y):
return x + y

考虑给 add(x, y) 函数添加日志功能:

def add(x, y):
print('Calling add')
return x + y

也带有日志功能的 sub(x, y)函数:

def sub(x, y):
print('Calling sub')
return x - y

观察

观察: 这是一种重复。

在有大量重复代码的地方编写程序通常很烦人。这些代码不仅写起来枯燥,维护起来也很麻烦。尤其是你决定更改其工作方式的时候(例如,可能是另一种类型的日志记录)。

记录日志的代码

也许你可以创建一个添加了日志功能的函数。例如包装器(wrapper):

def logged(func):
def wrapper(*args, **kwargs):
print('Calling', func.__name__)
return func(*args, **kwargs)
return wrapper

使用该函数:

def add(x, y):
return x + y logged_add = logged(add)

当调用 logged 返回的函数时会发生什么?

logged_add(3, 4)      # You see the logging message appear

此示例阐明了创建所谓的包装器函数(wrapper function) 的过程。

包装器是一个函数,它包装了另一个带有额外处理功能的函数,但在其它方面与原始函数的工作方式完全相同。

>>> logged_add(3, 4)
Calling add # Extra output. Added by the wrapper
7
>>>

注意事项:logged() 函数创建了一个包装器,并作为结果返回。

装饰器

在 Python 中,在函数中使用包装器非常常见。因为如此普遍,所以有一个特殊的语法。

def add(x, y):
return x + y
add = logged(add) # Special syntax
@logged
def add(x, y):
return x + y

该特殊语法执行与上面完全相同的确切步骤。装饰器只是一种新语法,用于装饰函数。

说明

对于装饰器而言,还有许多比这里展示的更微妙的细节,例如,在类里面使用装饰器,或者对同一个函数使用多个装饰器。不过,这里的例子已经很好地说明了如何使用它们。一般而言,它是对出现在各种函数定义中的重复代码的响应。装饰器可以将重复代码移至中心定义。

练习

练习 7.10:计时装饰器

如果你定义了一个函数,那么函数的名称和函数所属模块的名称会分别存储到 __name____module__属性中。示例:

>>> def add(x,y):
return x+y >>> add.__name__
'add'
>>> add.__module__
'__main__'
>>>

请创建 timethis.py 文件,并在文件中编写 timethis(func) 函数。timethis(func) 函数包装一个具有额外逻辑层的函数,逻辑层打印出函数执行所需要的事件。为此,你将在函数中添加如下计时调用。

start = time.time()
r = func(*args,**kwargs)
end = time.time()
print('%s.%s: %f' % (func.__module__, func.__name__, end-start))

timethis(func))装饰器工作方式示例:

>>> from timethis import timethis
>>> @timethis
def countdown(n):
while n > 0:
n -= 1 >>> countdown(10000000)
__main__.countdown : 0.076562
>>>

讨论:@timethis 装饰器可以放在任何函数的前面,即你应该把装饰器用作性能调优(performance tuning)的诊断工具。

目录 | 上一节 (7.3 返回函数) | 下一节 (7.5 装饰方法)

注:完整翻译见 https://github.com/codists/practical-python-zh

翻译:《实用的Python编程》07_04_Function_decorators的更多相关文章

  1. 翻译:《实用的Python编程》InstructorNotes

    实用的 Python 编程--讲师说明 作者:戴维·比兹利(David Beazley) 概述 对于如何使用我的课程"实用的 Python 编程"进行教学的问题,本文档提供一些通用 ...

  2. 翻译:《实用的Python编程》README

    欢迎光临 大约 25 年前,当我第一次学习 Python 时,发现 Python 竟然可以被高效地应用到各种混乱的工作项目上,我立即被震惊了.15 年前,我自己也将这种乐趣教授给别人.教学的结果就是本 ...

  3. 翻译:《实用的Python编程》05_02_Classes_encapsulation

    目录 | 上一节 (5.1 再谈字典) | 下一节 (6 生成器) 5.2 类和封装 创建类时,通常会尝试将类的内部细节进行封装.本节介绍 Python 编程中有关封装的习惯用法(包括私有变量和私有属 ...

  4. 翻译:《实用的Python编程》04_02_Inheritance

    目录 | 上一节 (4.1 类) | 下一节 (4.3 特殊方法) 4.2 继承 继承(inheritance)是编写可扩展程序程序的常用手段.本节对继承的思想(idea)进行探讨. 简介 继承用于特 ...

  5. 翻译:《实用的Python编程》01_02_Hello_world

    目录 | 上一节 (1.1 Python) | 下一节 (1.3 数字) 1.2 第一个程序 本节讨论有关如何创建一个程序.运行解释器和调试的基础知识. 运行 Python Python 程序始终在解 ...

  6. 翻译:《实用的Python编程》03_03_Error_checking

    目录 | 上一节 (3.2 深入函数) | 下一节 (3.4 模块) 3.3 错误检查 虽然前面已经介绍了异常,但本节补充一些有关错误检查和异常处理的其它细节. 程序是如何运行失败的 Python 不 ...

  7. 翻译:《实用的Python编程》03_04_Modules

    目录 | 上一节 (3.3 错误检查) | 下一节 (3.5 主模块) 3.4 模块 本节介绍模块的概念以及如何使用跨多个文件的函数. 模块和导入 任何一个 Python 源文件都是一个模块. # f ...

  8. 翻译:《实用的Python编程》03_05_Main_module

    目录 | 上一节 (3.4 模块) | 下一节 (3.6 设计讨论) 3.5 主模块 本节介绍主程序(主模块)的概念 主函数 在许多编程语言中,存在一个主函数或者主方法的概念. // c / c++ ...

  9. 翻译:《实用的Python编程》04_01_Class

    目录 | 上一节 (3.6 设计讨论) | 下一节 (4.2 继承) 4.1 类 本节介绍 class 语句以及创建新对象的方式. 面向对象编程(OOP) 面向对象编程是一种将代码组织成对象集合的编程 ...

随机推荐

  1. c++ 遍历当前程序的线程

    #include <iostream> #include <Windows.h> #include <Psapi.h> #include <TlHelp32. ...

  2. TYLER ADAMS BRADBERRY:人到中年,要学会戒掉这三点

    在一些国家的一些人当中,总会出现这样一个问题"中年危机".而到了中年,人与人间的差距似乎也变得越来越大.有人说,人到中年,是一个门槛,有的人迈过去了,有的人没迈过去.但是,其实实话 ...

  3. django学习-5.获取url参数和name的作用

    1.前言 假如我们要打开这两个博客园地址:[https://www.cnblogs.com/xiamen-momo/archive/2020/11.html].[https://www.cnblogs ...

  4. c# 打印面单

    private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e) { ...

  5. 还原Oracle数据库dmp文件(Win系统)

    准备工作: 1.核对数据字符集:   一般Oracle在安装的时候默认是选择ZHS16GBK,如有改动,使用 select userenv('language') from dual;语句查看使用的字 ...

  6. iframe 调用父页面元素

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="IFrame.aspx.cs ...

  7. Go的map

    目录 map 一.map的创建 1.map的类型 2.定义并初始化 二.给map添加元素 三.获取map的元素 四.删除map的元素 五.获取map的长度 六.map的类型 七.map的相等性 八.循 ...

  8. 【转】理解Serverless

    理解Serverless No silver bullet. - The Mythical Man-Month 许多年前,我们开发的软件还是C/S(客户端/服务器)和MVC(模型-试图-控制器)的形式 ...

  9. Docker安装Openvas

    目录 安装 在本机内运行 在局域网内运行 关闭 参考 安装 ➜ ~ docker search openvas NAME DESCRIPTION STARS OFFICIAL AUTOMATED mi ...

  10. apicloud编译所需的ios证书的获取方法

    在我们通过apicloud或hbuilderX这些工具打包ios应用的时候,需要一个ios证书. 那么我们如何生成这个ios证书呢?网上介绍的方法都是需要使用mac电脑,然后用mac电脑的钥匙串访问的 ...