作者简介

曾凡伟,携程信息安全部高级安全工程师,2015年加入携程,主要负责安全自动化产品的设计和研发,包括各类扫描器、漏洞管理平台、安全 SaaS 平台等。

Python 是一门追求优雅编程的语言,它很容易上手,也很容易写出意大利式的代码。本文将介绍如何使用 Python 进阶编程之装饰器,来帮助您写出更加精炼可读的代码。

全文主要分为四个部分:第一部分:尝鲜,通过讲解一个简单的装饰器例子,让您对装饰器的用法和作用有一个初步的感性认识;

第二部分:揭开面纱,将介绍装饰器抛开语法糖的使用方法,帮助您理解装饰器的本质原理;

第三部分:趁热打铁,将介绍装饰器在工作当中的实践用法,对应介绍的 retry 装饰器您可直接应用到项目代码中;

第四部分:更进一步,将介绍装饰器更多的高级用法,帮助您全方位掌握装饰器。

尝鲜

我们先来看一个简单的装饰器例子。首先定义一个装饰器 log:

def log(f):

def wrapper():

print "before"

f()

print "after"

return wrapper

使用装饰器 log 来装饰 greeting 函数,并调用之:

@log

def greeting():

print "Hello, World!"

greeting()

输出结果:

before

Hello, World!

after

可以看到,使用装饰器我们实现了在函数 greeting 前后打印调试日志。

揭开面纱

装饰器是什么?从字面意思我们大致可以推测出来,它的作用是用来装饰的。日常生活中,大家都见过很多装饰器,比如装饰在圣诞树上的彩纸,或者套在 iPhone 外面的保护壳。保护壳的存在,并不会改变 iPhone 内部的功能,它存在的意义,在于增强了 iPhone 的抗摔性能。Python 中的装饰器也是一样的道理,它并不会改变被装饰对象的内部逻辑,而是通过一种无侵入的方式,让它获得一些额外的能力,比如日志记录、权限认证、失败重试等等。

Python 装饰器看起来高深莫测,实际上它的实现原理非常简单。我们知道,在 Python 中一切皆对象,函数作为一个特殊的对象,可以作为参数传递给另外一个函数,装饰器的工作原理就是基于这一特性。装饰器的默认语法是使用@来调用,这实际上仅仅是一种语法糖。下面我们看看,不利用语法糖来怎么调用装饰器:

def greeting():

print "Hello, World!"

greeting = log(greeting)

把函数 greeting 作为参数传递给装饰器函数 log 就行了!对装饰器 log 来说,它接收一个函数作为入参,然后返回一个新的函数,最后再赋值给 greeting 标识符。这样便得到了一个增强功能的函数,而它的名字又和之前的保持一样。

趁热打铁

装饰器是一个编程利器,只需一处修改,任何被装饰的对象就可以获得额外的功能。撸起袖子,让我们来看看装饰器在编程实践中的具体应用。

我们知道,程序跑起来后,有一些因素往往是不可控的,比如网络的连通性。为了容错,我们可能会加入 try-except 语句来捕获异常;考虑到请求失败是有一定概率的,我们或许可以通过多次重试的策略,以达到提高成功率的目的。我们先来模拟一个 non_steady 函数:

import random

def non_steady():

if random.random() <= 0.5:

# 失败的概率是 0.5

raise Exception("died")

else:

# 成功的概率是 0.5

return "survived"

这个函数成功返回的概率是0.5。显然,单次调用的成功率太低,如果重试10次呢?计算一下:1-(0.5)^10,即成功的概率将提升到0.9990,相比单次调用的0.5,重试的成功率大 大地提升了。

按照上面的描述,我们先通过 for 循环来提升调用 non_steady 的成功率:

def non_steady_with_retry(times=10):

for i in xrange(times):

try:

return non_steady()

except Exception as e:

if (i + 1) < times:

# 尚未达到最大重试次数,默默吞掉异常

pass

else:

# 连续重试,达到最大次数时还是发生异常,则抛出异常

raise e

提升成功率的效果达到了,但是这种实现存在几个问题:

1、不支持无缝升级。假如函数 non_steady 在代码中被调用了 n 次,那么这意味着你需要同时修改 n 个地方(将调用 non_steady 修改为调用 non_steady_with_retry);

2、不支持代码复用。如果你有第二个函数 non_steady1,也需要升级一下重试机制,那么这意味着同样的重试代码,你需要再重写一遍。

再试试用装饰器来提升调用 non_steady 的成功率。定义一个 retry 装饰器:

def retry(times=10):

def outer(f):

def inner(*args, **kwargs):

for i in xrange(times):

try:

return f(*args, **kwargs)

except Exception as e:

if (i + 1) < times:

pass

else:

raise e

return inner

return outer

试用一下:

import random

@retry(10)

def non_steady():

if random.random() <= 0.5:

# 失败的概率是 0.5

raise Exception("died")

else:

# 成功的概率是 0.5

return "survived"

可以看到,只要函数前面加一行代码 @retry(10),即可为其升级重试机制。一处更改即可,无需处处担忧。同时,对于其他想要升级的函数,也只需要更改一个地方,同样的代码就无需重写多遍了。

更进一步

一个函数可以同时应用多个装饰器,比如下面使用两个装饰器来装饰 greeting 函数:

@log

@retry(10)

def greeting():

print "Hello, World!"

这段代码等价于:

def greeting():

print "Hello, World!"

temp = retry(10)(greeting)

greeting = log(temp)

可以看到,叠加的装饰器生效的顺序是从内往外的。这一点在使用的时候需要特别注意。

Java 中的注解,语法和 Python 中的装饰器很相似,它注解的顺序,没有 Python 中装饰器这么严格。使用时注意区分下。

除了函数,也可以用类来定义一个装饰器:

class Log(object):

def__init__(self, f):

self.f = f

def__call__(self, *args, **kwargs):

print "before"

self.f()

print "after"

类装饰器主要是通过它的__call__方法来实现的。相比函数装饰器,类装饰器具有面向对象编程所支援的一系列特点,比如高内聚、封装性和灵活度大等优点。使用类装饰器来装饰函数:

@Log

def greeting():

print "Hello, World!"

greeting()

输出结果和使用函数装饰器一样:

before

Hello, World!

after

实际上,Python 中任何 callable 的对象都可以用来定义装饰器。

结语

使用 Python 装饰器,可以让你的代码更易维护,可读性也有一定提升。相信大家在日常工作中也有碰到过很多使用装饰器的场景,欢迎留言分享!人生苦短,我用 Python。

什么是python装饰器_深入理解 Python 装饰器相关推荐

  1. python with关键字_完全理解Python关键字with与上下文管理器

    如果你有阅读源码的习惯,可能会看到一些优秀的代码经常出现带有 "with" 关键字的语句,它通常用在什么场景呢?今天就来说说 with 和 上下文管理器. 对于系统资源如文件.数据 ...

  2. python语句解释_深入理解python with 语句

    深入理解python with 语句 python中with 语句作为try/finally 编码范式的一种替代, 适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的" ...

  3. python iterable对象_如何理解Python中的iterable对象

    转载请注明出处:https://www.jianshu.com/u/5e6f798c903a [^*] 表示注脚,在文末可以查看对应连接,但简书不支持该语法. 首先,容器和 iterable 间没有必 ...

  4. python参数传递方法_深入理解python中函数传递参数是值传递还是引用传递

    python 的 深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是&q ...

  5. 完全理解python迭代对象_完全理解Python迭代对象、迭代器、生成器

    1.assert:python assert断言是声明其布尔值必须为真的判定,如果发生异常就说明表达示为假.可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触 ...

  6. python赋值语句对错_深入理解Python中变量赋值的问题

    前言 在Python中变量名规则与其他大多数高级语言一样,都是受C语言影响的,另外变量名是大小写敏感的. Python是动态类型语言,也就是说不需要预先声明变量类型,变量的类型和值在赋值那一刻被初始化 ...

  7. 深入理解python异步编程_深入理解Python异步编程

    1 什么是异步编程 1.1 阻塞程序未得到所需计算资源时被挂起的状态. 程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的. 常见的阻塞形式有:网络I/O阻塞.磁盘I/O ...

  8. python select模块_深入理解python中的select模块

    简介 Python中的select模块专注于I/O多路复用,提供了select  poll  epoll三个方法(其中后两个在Linux中可用,windows仅支持select),另外也提供了kque ...

  9. python sorted原理_深入理解Python的sorted

    1. 简介 sorted的派排序功能真强大,从前一直使用C++.Java以为其功能很强大了,但与Python一比,真的要差一些. 2. 函数定义 sorted(iterable, cmp=None, ...

最新文章

  1. Linux远程远程控制程序TeamViewer
  2. c++一个问题:while(!cin) 的解释
  3. Java注解解析-搭建自己的注解处理器(CLASS注解使用篇)
  4. linux脚本转换exe,Ps1 To Exe(powershell脚本转换EXE工具) V3.0.6 官方版
  5. POJ2243 Knight Moves —— A*算法
  6. leetcode54. 螺旋矩阵(详解)
  7. mysql binlog 订阅_数据库binlog订阅和消费组件canal快速入门
  8. JQuery模拟boostrap模态框效果
  9. go基础_defer
  10. paip.InternetExplorer.Application打开非IE的解决方法
  11. 使用hashcat获取哈希值
  12. [luoguT30208]太极剑
  13. 「硬核JS」一次搞懂JS运行机制
  14. swiper滑动切换变换样式,实时显示当前索引
  15. HRSaaS系统和ERP系统有什么区别?
  16. 阿里 卫哲谈阿里人力招聘价值观
  17. 【2022-03-23】JS逆向之爱奇艺滑块
  18. win系统的VBS脚本简易教程
  19. 高阶组件HOC - 小试牛刀
  20. 肯定得想办法牵线搭桥

热门文章

  1. BERT源码分析PART III
  2. 郎朗和机器人合奏_从世界机器人大会看新中国70年科技发展成就
  3. 单例模式-Java实现-非延迟加载、延迟加载
  4. 最少硬币找零系列问题(01背包,完全背包,多重背包动态规划)
  5. 【二十二】win 10 :Jmeter 报告可视化 —— 配置 Jmeter 接口 HTML 可视化测试报告,Jenkins + Jmeter + Ant 自动化集成环境搭建
  6. mysql pdo insert_PDO数据库操作类——插入数据的实现
  7. 360解压电脑版安装包_迅捷pdf转换器电脑版安装包下载-迅迅捷pdf转换器安装包免费下载...
  8. Docker安装QuestDB教程
  9. dbexception.java,mysql – org.h2.jdbc.JdbcSQLException:找不到列“ID”
  10. python动力学建模与仿真_PyMC3中的简单动力学模型