博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
python装饰器总结
阅读量:5947 次
发布时间:2019-06-19

本文共 5041 字,大约阅读时间需要 16 分钟。

一、装饰器是什么

python的装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。简单的说装饰器就是一个用来返回函数的函数。

它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

二、为什么需要装饰器

1、先来看一个简单例子:

def foo():print('i am foo')

2、增加需求

现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:

def foo():    print('i am foo')    print("foo is running")

3、又有需求

假设现在有100个函数需要增加这个需求,并且后续可能还要对这一百个函数都增加执行前打印日志的需求,怎么办?还一个个改吗?

当然不了,这样会造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码。

def use_logging(func):    print("%s is running" % func.__name__)    func() def bar():    print('i am bar')use_logging(bar)#result:#bar is running#i am bar

通过以上use_logging函数我们增加了日志功能,不管以后有多少函数需要增加日志或者修改日志的格式我们只需要修改use_logging函数,并执行use_logging(被装饰的函数)就达到了我们想要的效果。

 

def use_logging(func):    print("%s is running" % func.__name__)    return func@use_loggingdef bar():    print('i am bar')bar()

三、基础装饰器入门

1、装饰器语法糖

python提供了@符号作为装饰器的语法糖,使我们更方便的应用装饰函数。但使用语法糖要求装饰函数必须return一个函数对象。因此我们将上面的func函数使用内嵌函数包裹并return。

装饰器相当于执行了装饰函数use_loggin后又返回被装饰函数bar,因此bar()被调用的时候相当于执行了两个函数。等价于use_logging(bar)()

 

def use_logging(func):    def _deco():        print("%s is running" % func.__name__)        func()    return _deco@use_loggingdef bar():    print('i am bar')bar(

 2、对带参数的函数进行装饰

现在我们的参数需要传入两个参数并计算值,因此我们需要对内层函数进行改动传入我们的两个参数a和b,等价于use_logging(bar)(1,2)

def use_logging(func):    def _deco(a,b):        print("%s is running" % func.__name__)        func(a,b)    return _deco@use_loggingdef bar(a,b):    print('i am bar:%s'%(a+b))bar(1,2)

我们装饰的函数可能参数的个数和类型都不一样,每一次我们都需要对装饰器做修改吗?这样做当然是不科学的,因此我们使用python的变长参数*args和**kwargs来解决我们的参数问题。

3、函数参数数量不确定

不带参数装饰器版本,这个格式适用于不带参数的装饰器。

经过以下修改我们已经适应了各种长度和类型的参数。这个版本的装饰器已经可以任意类型的无参数函数。

def use_logging(func):    def _deco(*args,**kwargs):        print("%s is running" % func.__name__)        func(*args,**kwargs)    return _deco@use_loggingdef bar(a,b):    print('i am bar:%s'%(a+b))@use_loggingdef foo(a,b,c):    print('i am bar:%s'%(a+b+c))bar(1,2)foo(1,2,3) 

4、装饰器带参数

带参数的装饰器,这个格式适用于带参数的装饰器。

某些情况我们需要让装饰器带上参数,那就需要编写一个返回一个装饰器的高阶函数,写出来会更复杂。比如:

#! /usr/bin/env python# -*- coding:utf-8 -*-# __author__ = "TKQ"def use_logging(level):    def _deco(func):        def __deco(*args, **kwargs):            if level == "warn":                print "%s is running" % func.__name__            return func(*args, **kwargs)        return __deco    return _deco@use_logging(level="warn")def bar(a,b):    print('i am bar:%s'%(a+b))bar(1,3)# 等价于use_logging(level="warn")(bar)(1,3)

5、functools.wraps

 使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

def use_logging(func):    def _deco(*args,**kwargs):        print("%s is running" % func.__name__)        func(*args,**kwargs)    return _deco@use_loggingdef bar():    print('i am bar')    print(bar.__name__)bar()#bar is running#i am bar#_deco#函数名变为_deco而不是bar,这个情况在使用反射的特性的时候就会造成问题。因此引入了functools.wraps解决这个问题。

 

 使用functools.wraps:

import functoolsdef use_logging(func):    @functools.wraps(func)    def _deco(*args,**kwargs):        print("%s is running" % func.__name__)        func(*args,**kwargs)    return _deco@use_loggingdef bar():    print('i am bar')    print(bar.__name__)bar()#result:#bar is running#i am bar#bar  ,这个结果是我们想要的。OK啦!

6、实现带参数和不带参数的装饰器自适应

import functoolsdef use_logging(arg):    if callable(arg):#判断参入的参数是否是函数,不带参数的装饰器调用这个分支        @functools.wraps(arg)        def _deco(*args,**kwargs):            print("%s is running" % arg.__name__)            arg(*args,**kwargs)        return _deco    else:#带参数的装饰器调用这个分支        def _deco(func):            @functools.wraps(func)            def __deco(*args, **kwargs):                if arg == "warn":                    print "warn%s is running" % func.__name__                return func(*args, **kwargs)            return __deco        return _deco@use_logging("warn")# @use_loggingdef bar():    print('i am bar')    print(bar.__name__)bar()

 

三、类装饰器

使用类装饰器可以实现带参数装饰器的效果,但实现的更加优雅简洁,而且可以通过继承来灵活的扩展.

1、类装饰器

class loging(object):    def __init__(self,level="warn"):        self.level = level    def __call__(self,func):        @functools.wraps(func)        def _deco(*args, **kwargs):            if self.level == "warn":                self.notify(func)            return func(*args, **kwargs)        return _deco    def notify(self,func):        # logit只打日志,不做别的        print "%s is running" % func.__name__@loging(level="warn")#执行__call__方法def bar(a,b):    print('i am bar:%s'%(a+b))bar(1,3)

 

 2、继承扩展类装饰器

class email_loging(Loging):    '''    一个loging的实现版本,可以在函数调用时发送email给管理员    '''    def __init__(self, email='admin@myproject.com', *args, **kwargs):        self.email = email        super(email_loging, self).__init__(*args, **kwargs)    def notify(self,func):        # 发送一封email到self.email        print "%s is running" % func.__name__        print "sending email to %s" %self.email@email_loging(level="warn")def bar(a,b):    print('i am bar:%s'%(a+b))bar(1,3)

 

参考文章:   http://www.zhihu.com/question/26930016   http://www.liaoxuefeng.com

 

转载于:https://www.cnblogs.com/tkqasn/p/5856552.html

你可能感兴趣的文章
计算机字符编码从0/1到UTF-8
查看>>
[原]Jenkins(三)---Jenkins初始配置和插件配置
查看>>
Cache Plugin 实现过程
查看>>
HBase Compaction详解
查看>>
TCP服务器端口转发: netsh
查看>>
2016OSC源创会年终盛典-架构与数据专场-张千明
查看>>
nginx实现rtmp,flv,mp4流媒体服务器
查看>>
46.tornado绑定域名或者子域名泛域名的处理
查看>>
Elasticsearch 2.2.0 节点发现详解
查看>>
Elasticsearch 2.2.0 插件篇:安装
查看>>
文本过滤--sed 1
查看>>
PHP CURL并发,多线程
查看>>
ES 概念及动态索引结构和索引更新机制
查看>>
iOS 开发百问(2)
查看>>
MySQL for Mac 安装和基本操作(包含后期的环境变量设置)
查看>>
Linux及windows下常见压缩程序的压缩能力对比
查看>>
JAVAEE-junit测试hibernate里的方法(hibernate交给spring管理)的问题
查看>>
MOTO MB860 国行2.3.5优化增强ROM_Top_T5_end(经典收藏版)
查看>>
C#学习经典(二)---MVC框架(Model view Controller)
查看>>
我的友情链接
查看>>