装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的应用有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

1:装饰器实际就是函数。他们接受函数对象。对这些函数进行包装或修饰。因此,有了装饰器,可以在执行实际函数之前运行预备代码;也可以在执行实际函数代码之后做些清理工作。

装饰器只是语法糖。有时这样做更方便,尤其是做元编程(在运行时改变程序的行为)时。

2:装饰器的语法以@开头,接着是装饰器函数的名字和可选的参数。紧跟着定义被修饰的函数,所以,使用装饰器看起来会是这样:

@decorator(dec_opt_args)
def func2Bdecorated(func_opt_args):
...

3:装饰器的本质

在装饰器没有参数的情况下,如:

@deco
def foo(): pass 

该定义等价于:

foo = deco(foo)

在装饰器有参数的情况下,如:

@deco(deco_args)
def foo(): pass

该定义等价于:

foo = deco(deco_args)(foo) 

更复杂的,装饰器也可以“堆叠”,如:

@deco1(deco1_args)
@deco2
def foo(): pass

该定义等价于:

foo = deco1(deco1_args)(deco2(foo))

4:下面是不带参数的装饰器例子:

def  deco(func):
print 'this is deco'
def wrapfun():
print 'in wrapfun'
return func()
return wrapfun @deco
def foo():
print 'this is foo' print 'after def' foo()

结果是:

this is deco
after def
in wrapfun
this is foo

可见,在定义foo函数的时候,就会调用deco函数。下面是带参数的装饰器例子:

def deco(args):
print 'this is deco, args is ', args
def wrapfun1(func):
print 'in wrapfun1'
def wrapfun2(fargs):
print 'in wrapfun2'
return func(fargs)
return wrapfun2
return wrapfun1 @deco([1,2,3])
def foo(fargs):
print 'this is foo, fargs is ', fargs print 'after def' foo([4, 5, 6])

结果是:

this is deco, args is [1, 2, 3]
in wrapfun1
after def
in wrapfun2
this is foo, fargs is [4, 5, 6]

5:闭包。

装饰器其实用到了闭包。在了解闭包之前,我们知道python中变量分为全局变量和局部变量。比如下面的f1和f2:

def f1(a):
print(a)
print(b)

执行f1时,肯定会报错:"NameError: global name 'b' is not defined"。这是因为在f1中没有给b赋值(初始化),因此f1认为b是个全局变量。这可以通过字节码看出来:

>>> from dis import dis
>>> dis(f1)
2 0 LOAD_FAST 0 (a)
3 PRINT_ITEM
4 PRINT_NEWLINE 3 5 LOAD_GLOBAL 0 (b)
8 PRINT_ITEM
9 PRINT_NEWLINE
10 LOAD_CONST 0 (None)
13 RETURN_VALUE

上面的2,3分别对应print a和print b。LOAD_FAST表示加载局部变量,LOAD_GLOBAL表示加载全局变量。

下面是f2:

b = 6
def f2(a):
print(a)
print(b)
b = 9

执行该函数时也会报错:UnboundLocalError: local variable 'b' referenced before assignment。

这是因为Python编译函数的定义体时,它判断 b 是局部变量,因为在函数中给它赋值了。但是尝试获取局部变量 b 的值时,发现b没有绑定值。下面是f2的字节码:

>>> dis(f2)
2 0 LOAD_FAST 0 (a)
3 PRINT_ITEM
4 PRINT_NEWLINE 3 5 LOAD_FAST 1 (b)
8 PRINT_ITEM
9 PRINT_NEWLINE 4 10 LOAD_CONST 1 (9)
13 STORE_FAST 1 (b)
16 LOAD_CONST 0 (None)
19 RETURN_VALUE

闭包是指延伸了作用域的函数,闭包中包含函数定义体中引用、但是不在定义体中定义的非全局变量。

比如下面计算平均值的函数:

def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return average >>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

series 是 make_averager 函数的局部变量。可是,调用 avg(10) 时,make_averager 函数已经返回了,而它的本地作用域也一去不复返了。在 averager 函数中,series 是自由变量(free variable),自由变量指未在本地作用域中绑定的变量:

审查返回的 averager 对象,我们发现 Python 在 __code__ 属性(表示编译后的函数定义体)中保存局部变量和自由变量的名称。avg 的 __closure__ 属性中,各个元素对应于 avg.__code__.co_freevars 中的一个名称。这些元素是 cell 对象,有个cell_contents 属性,保存着自由变量真正的值:

>>> avg.__code__.co_varnames
('new_value', 'total') >>> avg.__code__.co_freevars
('series',) >>> avg.__closure__
(<cell at 0x7f43624260f8: list object at 0x7f43624202d8>,) >>> avg.__closure__[0].cell_contents
[10,11,12] 

综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量。

在Python 3 引入了 nonlocal 声明。它的作用是把变量标记为自由变量,如果为 nonlocal 声明的变量赋予新值,闭包中保存的绑定会更新。有了nonlocal后,可以设计效率更高的make_averager函数:

def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager 

Python 2 没有 nonlocal,因此需要变通方法,处理方式是把内部函数需要修改的变量(如 count 和 total)存储为可变对象(如字典或简单的实例)的元素或属性,并且把那个对象绑定给一个自由变量。

6:标准库中的装饰器

Python 内置了三个用于装饰方法的函数:property、classmethod 和staticmethod。

另一个常见的装饰器是 functools.wraps,它的作用是协助构建行为良好的装饰器。标准库中最值得关注的两个装饰器是 lru_cache (python3.2新增)和全新的singledispatch(Python 3.4 新增)。这两个装饰器都在 functools 模块中定义。接下来分别讨论它们。

functools.lru_cache 是非常实用的装饰器,它实现了备忘(memoization)功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。生成第 n 个斐波纳契数这种慢速递归函数适合使用 lru_cache,

import time

def fibonacci(n):
print('call fibonacci(%d)'%n)
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1) if __name__ == '__main__':
begin=time.time()
fibonacci(6)
end=time.time()
print('run time is ', end-begin) 

运行结果如下:

call fibonacci(6)
call fibonacci(4)
call fibonacci(2)
call fibonacci(0)
call fibonacci(1)
call fibonacci(3)
call fibonacci(1)
call fibonacci(2)
call fibonacci(0)
call fibonacci(1)
call fibonacci(5)
call fibonacci(3)
call fibonacci(1)
call fibonacci(2)
call fibonacci(0)
call fibonacci(1)
call fibonacci(4)
call fibonacci(2)
call fibonacci(0)
call fibonacci(1)
call fibonacci(3)
call fibonacci(1)
call fibonacci(2)
call fibonacci(0)
call fibonacci(1)
run time is 0.0003077983856201172 

浪费时间的地方很明显:fibonacci(1) 调用了 8 次,fibonacci(2) 调用了 5 次……但是,如果增加两行代码,使用 lru_cache,性能会显著改善,

import time
import functools #@functools.lru_cache()
def fibonacci(n):
print('call fibonacci(%d)'%n)
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1) if __name__ == '__main__':
begin=time.time()
fibonacci(6)
end=time.time()
print('run time is ', end-begin) 

结果如下:

call fibonacci(6)
call fibonacci(4)
call fibonacci(2)
call fibonacci(0)
call fibonacci(1)
call fibonacci(3)
call fibonacci(5)
run time is 8.440017700195312e-05

这样一来,执行时间减半了,而且 n 的每个值只调用一次函数。

lru_cache 可以使用两个可选的参数来配置。它的签名是:

functools.lru_cache(maxsize=128, typed=False)

maxsize 参数指定存储多少个调用的结果。缓存满了之后,旧的结果会被扔掉,腾出空间。为了得到最佳性能,maxsize 应该设为 2 的幂。typed 参数如果设为 True,把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整数参数(如 1 和 1.0)区分开。顺便说一下,因为 lru_cache 使用字典存储结果,而且键根据调用时传入的定位参数和关键字参数创建,所以被 lru_cache 装饰的函数,它的所有参数都必须是可散列的。

假设开发一个 Web 应用想生成 HTML,显示不同类型的 Python对象。我们可能会编写这样的函数:

import html
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content) 

这个函数适用于任何 Python 类型,但是现在我们想做个扩展,让它使用特别的方式显示某些类型。比如

str:把内部的换行符替换为 '<br>\n';不使用 <pre>,而是使用 <p>。

int:以十进制和十六进制显示数字。

list:输出一个 HTML 列表,根据各个元素的类型进行格式化。

因为 Python 不支持重载方法或函数,所以我们不能使用不同的签名定义htmlize 的变体,也无法使用不同的方式处理不同的数据类型。在 Python 中,一种常见的做法是把 htmlize 变成一个分派函数,使用一串 if/elif/elif,调用专门的函数,如htmlize_str、htmlize_int,等等。这样不便于模块的用户扩展,还显得笨拙:时间一长,分派函数 htmlize 会变得很大,而且它与各个专门函数之间的耦合也很紧密。

Python 3.4 新增的 functools.singledispatch 装饰器可以把整体方案拆分成多个模块,使用 @singledispatch 装饰的普通函数会变成泛函数(generic function):根据第一个参数的类型,以不同方式执行相同操作的一组函数。

functools.singledispatch 是 Python 3.4 增加的,PyPI 中的singledispatch 包可以向后兼容 Python 2.6 到 Python 3.3。

from functools import singledispatch
from collections import abc
import numbers
import html @singledispatch
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content) @htmlize.register(str)
def _(text):
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content) @htmlize.register(numbers.Integral)
def _(n):
return '<pre>{0} (0x{0:x})</pre>'.format(n) @htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>' if __name__ == '__main__':
print(htmlize(abc))
print(htmlize("hello, world"))
print(htmlize(42))
print(htmlize([1,2,3])) 

运行结果如下:

<pre>&lt;module &#x27;collections.abc' from '/usr/lib/python3.5/collections/abc.py'&gt;</pre>
<p>hello, world</p>
<pre>42 (0x2a)</pre>
<ul>
<li><pre>1 (0x1)</pre></li>
<li><pre>2 (0x2)</pre></li>
<li><pre>3 (0x3)</pre></li>
</ul> 

singledispatch 机制的一个显著特征是,你可以在系统的任何地方和任何模块中注册专门函数。如果后来在新的模块中定义了新的类型,可以轻松地添加一个新的专门函数来处理那个类型。此外,你还可以为不是自己编写的或者不能修改的类添加自定义函数。

只有理解了装饰器的本质,才能写出没有错误的装饰器。可以在python langugae reference, python2.4 中“What’s New in Python 2.4”的文档以及PEP 318 中来阅读更多关于装饰器的内容。

参考

http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

Python基础:13装饰器的更多相关文章

  1. python基础—函数装饰器

    python基础-函数装饰器 1.什么是装饰器 装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能. 装饰器的返回值是也是一个函数对象. 装饰器经常用于有切 ...

  2. 十. Python基础(10)--装饰器

    十. Python基础(10)--装饰器 1 ● 装饰器 A decorator is a function that take a function as an argument and retur ...

  3. [python基础]关于装饰器

    在面试的时候,被问到装饰器,在用的最多的时候就@classmethod ,@staticmethod,开口胡乱回答想这和C#的static public 关键字是不是一样的,等面试回来一看,哇,原来是 ...

  4. Day11 Python基础之装饰器(高级函数)(九)

    在python中,装饰器.生成器和迭代器是特别重要的高级函数   https://www.cnblogs.com/yuanchenqi/articles/5830025.html 装饰器 1.如果说装 ...

  5. 1.16 Python基础知识 - 装饰器初识

    Python中的装饰器就是函数,作用就是包装其他函数,为他们起到修饰作用.在不修改源代码的情况下,为这些函数额外添加一些功能,像日志记录,性能测试等.一个函数可以使用多个装饰器,产生的结果与装饰器的位 ...

  6. python基础-----函数/装饰器

    函数 在Python中,定义一个函数要使用def语句,依次写出函数名.括号.括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回. 函数的优点之一是,可以将代码块与主程 ...

  7. python基础之装饰器(实例)

    1.必备 #### 第一波 #### def foo(): print 'foo' foo #表示是函数 foo() #表示执行foo函数 #### 第二波 #### def foo(): print ...

  8. 【Python基础】装饰器的解释和用法

    装饰器的用法比较简单,但是理解装饰器的原理还是比较复杂的,考虑到接下来的爬虫框架中很多用到装饰器的地方,我们先来讲解一下. 函数 我们定义了一个函数,没有什么具体操作,只是返回一个固定值 请注意一下缩 ...

  9. 【Python成长之路】python 基础篇 -- 装饰器【华为云分享】

    [写在前面] 有时候看到大神们的代码,偶尔会用到@来装饰函数.当时查了资料,大致了解装饰器一般用于在不改变原函数的基础上 ,对原函数功能进行修改/增强.使用场景是:日志级别设置.权限校验.性能测试等. ...

  10. 学习PYTHON之路, DAY 5 - PYTHON 基础 5 (装饰器,字符格式化,递归,迭代器,生成器)

    ---恢复内容开始--- 一 装饰器 1 单层装饰器 def outer(func): def inner(): print('long') func() print('after') return ...

随机推荐

  1. Django项目:CRM(客户关系管理系统)--32--24PerfectCRM实现King_admin自定义操作数据

    #admin.py # ————————01PerfectCRM基本配置ADMIN———————— from django.contrib import admin # Register your m ...

  2. js的剪贴板事件

    定义 剪贴板操作包括剪切(cut).复制(copy)和粘贴(paste)这三个操作,快捷键分别是ctrl+x.ctrl+c.ctrl+v.当然也可以使用鼠标右键菜单进行操作 关于这3个操作共对应下列6 ...

  3. 在多版本python的pip的安装与对应包的安装

    最近花了好长时间在搞这个,由于Deepin下python有两个版本,并且都没有安装pip,之前的博文默认安装pip给python2.7,结果各种问题,在此将之前走过的弯路整合起来: 首先,安装pip ...

  4. drf模块及源码

    drf中的APIView请求生命周期 APIView的as_view(局部禁用csrf) => 调用父类view中的as_view返回view()方法 => 自己的类调用自己的dispat ...

  5. centos 安装redis2.8.9

    1没有安装gcc yum install gcc-c++ 2. 安装tcl yum install -y tcl 3.安装redis $ wget http://download.redis.io/r ...

  6. 【react】react-reading-track

    这是一个很有趣的图书阅读demo 先放github地址:https://github.com/onlyhom/react-reading-track 我觉得这个博主的项目很有意思呢 我们一起看看代码啊 ...

  7. Django基础内容整理

  8. web前端学习(二)html学习笔记部分(6)--fileAPI

    1.2.18 html5 File API的应用 1.2.18.1  实现可选择列表 通过为列表项增加一个选择框,进而实现列表的多选和对选择文件的删除.同时,在选择.取消选择时实现操作栏的切换. 1. ...

  9. 【JZOJ3216】【SDOI2013】淘金

    ╰( ̄▽ ̄)╭ 小 Z在玩一个 叫做<淘金者>的游戏.游戏的世界是一个 二维坐标 .X轴.Y轴坐标范围均为1..N.初始的时候,所有的整数坐标点上均有一块金子,共 N*N 块. 一阵风吹过 ...

  10. Leetcode922.Sort Array By Parity II按奇偶排序数组2

    给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数. 对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数:当 A[i] 为偶数时, i 也是偶数. 你可以返回任何满足上述条件的数组 ...