探究装饰器参数

编写传参的装饰器

通常我们见到的简单装饰器这样的:

import json
import functools def json_output(func):
@functools.wraps(decorated)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return json.dumps(result)
return inner @json_output
def f():
return {'status': 'done'}

当装饰器应用于函数 f 上时,它接受 f 作为其参数,返回一个函数 inner ,且将他绑定到变量f上。

示例中我们编写的装饰器 json_output 只接受一个隐式参数——即被装饰的方法,在使用此装饰器时本身看上去是并没有参数的。然而有时候需要让装饰器自身带有一些需要的信息,从而使装饰器可以使用恰当的方式装饰方法。比如上面的例子中,我们想通过向装饰器传入不同的参数来控制输出结果的缩进(indent)和排序(sort)。我们可以这么做:

import json
import functools def json_output(indent=None, sort_keys=False):
def actual_decorator(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return json.dumps(result, indent=indent, sort_keys=sort_keys)
return inner
return actual_decorator @json_output(indent=4)
def f():
return {'status': 'done'}

理解传参的装饰器

初次看起来会觉得比较绕人,因为函数里嵌套了两个函数定义,然而实际上和之前一个版本的区别在于为了接收json序列化的参数多包装了一层,所以

@json_output(indent=4)
def f():
return {'status': 'done'} # 相当于
@actual_decorator
def f():
return {'status': 'done'}

这样看起来就会明晰很多。

实际上, 装饰器里的 @ 后接收一个函数,该函数以被装饰的函数(例子中是f)为参数,并且返回一个函数。当需要在装饰函数的同时传入参数的话,那么就需要多包装一层,先传入参数(例子中是 indent=4 )返回一个装饰的函数(例子中是 actual_decorator ), 这个返回的的函数 就跟以前一样接受被装饰的函数(f)作为参数并且返回一个函数作为装饰最后的方法供调用。

传参和不传参的兼容

然而当我们像上面那样定义装饰器时,就不能这样调用:

import json
import functools def json_output(indent=None, sort_keys=False):
def actual_decorator(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return json.dumps(result, indent=indent, sort_keys=sort_keys)
return inner
return actual_decorator @json_output
def f():
return {'status': 'done'}

在实际的项目过程中,有时会出现这样的状况: 一开始写的装饰器时不需要使用时传参数的,后来发现有必要传参数,改好后原来不传参的装饰器不能正常使用了,这是修改原来使用的地方是项痛苦的事情。

这时候就需要对装饰器做一个兼容,使它在以下情况都可用:

@json_output
@json_output()
@json_output(indent=4)

具体做法如下:

import json
import functools def json_output(decorated_=None, indent=None, sort_keys=False):
if decorated_ and (indent or sort_keys):
raise def actual_decorator(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return json.dumps(result, indent=indent, sort_keys=sort_keys)
return inner
if decorated_:
return actual_decorator(decorated_)
else:
return actual_decorator @json_output(indent=4)
def f1():
return {'status': 'done'} @json_output
def f2():
return {'status': 'done'} @json_output()
def f3():
return {'status': 'done'} print f1()
print f2()
print f3()

代码中关键的地方在于 json_output 在最后对参数 decorated 进行了判断,有的话证明是不传参调用,那么直接返回 actual_decorator 的调用;没有的话则代表是传参类型的调用(虽然参数可能不存在),那么返回 actual_decorator 。其中有点需要注意, josn_output 的传参需要使用关键字参数,如果像下面这样直接传一个位置参数,那么根据现在的实现会出现错误(因为它会被当成 decorated_ )。

@json_output(4)  #错误的使用方法
def f4():
return {'status': 'done'}

参考资料

  • 《Python高级编程》 by Luke Sneeriger

Python装饰器探究——装饰器参数的更多相关文章

  1. python装饰器探究与参数的领取

    首先上原文, 现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为"装饰器" ...

  2. Python装饰器AOP 不定长参数 鸭子类型 重载(三)

    1 可变长参数与关键字参数 *args代表任意长度可变参数 **kwargs代表关键字参数 用*args和**kwargs只是为了方便并没有强制使用它们. 缺省参数即是调用该函数时,缺省参数的值若未被 ...

  3. python——装饰器(不定长参数,闭包,装饰器)示例

    def func(functionName): print("正在装饰") def func_in(*args, **kargs): print("------func_ ...

  4. Python 第五天 装饰器

    装饰器 装饰器是函数,只不过该函数可以具有特殊的含义,装饰器用来装饰函数或类,使用装饰器可以在函数执行前和执行后添加相应操作. def wrapper(func): def result(): pri ...

  5. Python中利用函数装饰器实现备忘功能

    Python中利用函数装饰器实现备忘功能 这篇文章主要介绍了Python中利用函数装饰器实现备忘功能,同时还降到了利用装饰器来检查函数的递归.确保参数传递的正确,需要的朋友可以参考下   " ...

  6. python函数与方法装饰器

    之前用python简单写了一下斐波那契数列的递归实现(如下),发现运行速度很慢. def fib_direct(n): assert n > 0, 'invalid n' if n < 3 ...

  7. Python中的各种装饰器详解

    Python装饰器,分两部分,一是装饰器本身的定义,一是被装饰器对象的定义. 一.函数式装饰器:装饰器本身是一个函数. 1.装饰函数:被装饰对象是一个函数 [1]装饰器无参数: a.被装饰对象无参数: ...

  8. python基础5之装饰器

    内容概要: 一.装饰器前期知识储备 1.python解释函数代码过程: python解释器从上往下顺序解释代码,碰到函数的定义代码块不会立即执行它,而是将其放在内存中,等到该函数被调用时,才执行其内部 ...

  9. python函数式编程之装饰器(一)

    1.开放封闭原则 简单来说,就是对扩展开放,对修改封闭 在面向对象的编程方式中,经常会定义各种函数. 一个函数的使用分为定义阶段和使用阶段,一个函数定义完成以后,可能会在很多位置被调用 这意味着如果函 ...

随机推荐

  1. 好用的切换滑动焦点图框架jquery.superslide

    拿到学习网站:http://www.superslide2.com/

  2. 【翻译】Best Practices for User interface android 适配不同屏幕、不同分辨率

    地址:http://developer.android.com/training/multiscreen/screendensities.html#TaskProvideAltBmp 安卓支持不同的屏 ...

  3. 【起航计划 006】2015 起航计划 Android APIDemo的魔鬼步伐 05 App->Activity->Custom Title 自定义标题栏

    Android UI缺省的标题栏由android:label 定义,显示在屏幕左上角,Android允许Activity自定义标题栏,使用自定义Layout重新设置标题栏,比如实现Windows Mo ...

  4. JS兼用IE8的通过class名获取CSS对象组

    转自:Garon_InE 原生js方法“document.getElementsByClassName”在ie8及其以下浏览器中不能使用,所以写了一个兼容IE的方法. 完整的页面代码如下: testJ ...

  5. linux下使用iperf测试服务器带宽

    准备工具 1.2台Linux服务器(要求其中至少1台主机为腾讯云主机,另外一台任意主机均可,确保2台主机可以互相访问即可)2.Iperf软件为专业网络性能测试工具. 测试目标 上海地区主机外网带宽是否 ...

  6. org.springframework.beans.MethodInvocationException: Property 'cacheManager' threw exception; nested exception is org.apache.shiro.cache.CacheException: net.sf.ehcache.CacheException: Caches cannot be

    shiro cache manage配置报错: org.springframework.beans.MethodInvocationException: Property 'cacheManager' ...

  7. 如何用C# 动态创建Access数据库和表?

    记得以前要动态的创建Access数据库的mdb文件都是采用DAO,用VC开发,一大堆的API,很是麻烦.而且以前工作中需要全新的access数据库,可以复制数据库,也可以把新的数据库放到资源里面,用新 ...

  8. hp zbook15G2 nVidia K1100M显卡在ubuntu linux下闪屏问题

    我的hp zbook15G2有一块nVidia K1100M显卡. 故障现象 安装ubuntu 16.4之后,屏幕出现闪烁现象. 重启后,进入bios,屏幕依然在闪烁. 再重启,进入另一块硬盘的win ...

  9. cesium 显示视角高度以及鼠标经纬度

    HTML中的内容 <div id="cesiumContainer"> <!-- 设置经纬度显示 --> <span style="font ...

  10. IOS 制作常用代码的快捷方式

    输入可以变的对象或类型,要用<#name#>