python装饰器的全面总结学习笔记

     

目录

一、什么是装饰器

二、为何要使用装饰器

三、装饰器的分类

1、固定参数的函数式装饰器

2、非固定参数的函数式装饰器

3、特殊的@deco(param)

4、类式装饰器

5、Python内置装饰器

四、装饰器的缺陷及解决方法

五、装饰器应用场景


 

        之前了解过Python中的装饰器、生成器和迭代器的概念,并没有系统的深入了解,平时写代码基本也没有碰到过这样的场景需求。最近在看tensorflow2.0框架相关的框架的时候,又看到了装饰器,作用不是很熟悉,觉得有必要做一个系统的学习,然后记录下来,加深理解同时以备后续复习。

一、什么是装饰器

什么是装饰器呢?为了有个直观的理解,直接上代码:

import  time
def deco(f):
    def wrapper():
        print('I am a decorator!,Founction run begin!')
        sta = time.time()
        f()
        end = time.time()
        print('Founction run time is:',(end-sta))
        print('Founction run finished!')
    return wrapper

@deco
def do_something():
    r = 0
    for i in range(1,100,2):
        r += i
    print('r is:',r)

if __name__ == '__main__':
    do_something()

上面的代码中@deco就是一个简单的装饰器,使用了语法糖@。顾名思义,装饰器就是对函数器装饰作用的一种函数。它能够在不改变原来函数代码的前提下,给函数增加额外的功能,只要是带有@语法糖的函数就是装饰器了。以上的代码中,原来函数只具备计算奇数和的功能,现在使用了@deco,就给它额外的增加了统计函数运行时间的功能。

看看输出结果:

I am a decorator!,Founction run begin!
r is: 2500
Founction run time is: 6.198883056640625e-06
Founction run finished!

二、为何要使用装饰器

        为何要使用装饰器呢?上面代码中的功能不是很简单的吗?我直接写在原来的函数里不同样可以达到效果吗?看代码不使用装饰器:

def do_something():
    print('I am a decorator!,Founction run begin!')
    sta = time.time()
    r = 0
    for i in range(1,100,2):
        r += i
    print('r is:',r)
    end = time.time()
    print('Founction run time is:', (end - sta))
    print('Founction run finished!')
if __name__ == '__main__':
    do_something()

同样的能实现上面的功能,且同样这个总体的代码行数还减少呢,比装饰器貌似要优越一点啊?!。那么装饰器的优越性怎么体现出来的呢?现在设想有这样一个场景:你一个项目里面有成百上前个函数,现在需要记录每个函数的运行时间和输出一些日志之类的。那么不用装饰器就得在原来的每个函数增加相应的代码,试想一下,是每个函数,这里的代码就增加量无疑是很大的。那这个时候装饰器的作用就体现出来了,写一个装饰器函数,然后给每个调用的函数,弄一个@帽子,在原来的代码中,函数调用方式也不用修改,是不是很方便和简洁呀?看代码:

import  time
def deco(f):
    def wrapper():
        print('I am a decorator!,Founction run begin!')
        sta = time.time()
        f()
        end = time.time()
        print('Founction run time is:',(end-sta))
        print('Founction run finished!')
    return wrapper

def log(f):
    def wrapper():
        f()
        print('输出日志!\n')
    return wrapper

@log
@deco
def do_something1():
    r = 0
    for i in range(1,100,2):
        r += i
    print('r1 is:',r)
@log
@deco
def do_something2():
    r = 0
    for i in range(1,100,3):
        r += i
    print('r2 is:',r)
@log
@deco
def do_something3():
    r = 0
    for i in range(1,100,4):
        r += i
    print('r3 is:',r)
if __name__ == '__main__':
    do_something1()
    do_something2()
    do_something3()

不使用装饰器的代码:

import  time
def do_something1():
    print('I am a decorator!,Founction run begin!')
    sta = time.time()
    r = 0
    for i in range(1,100,2):
        r += i
    print('r1 is:',r)
    end = time.time()
    print('Founction run time is:', (end - sta))
    print('Founction run finished!')
    print('输出日志!\n')

def do_something2():
    print('I am a decorator!,Founction run begin!')
    sta = time.time()
    r = 0
    for i in range(1, 100, 3):
        r += i
    print('r2 is:', r)
    end = time.time()
    print('Founction run time is:', (end - sta))
    print('Founction run finished!')
    print('输出日志!\n')

def do_something3():
    print('I am a decorator!,Founction run begin!')
    sta = time.time()
    r = 0
    for i in range(1, 100, 2):
        r += i
    print('r3 is:', r)
    end = time.time()
    print('Founction run time is:', (end - sta))
    print('Founction run finished!')
    print('输出日志!\n')
if __name__ == '__main__':
    do_something1()
    do_something2()
    do_something3()

可以看到使用装饰器的代码量确实要少一点,代码冗余也少很多,更加的简洁和清晰!当然也能增加代码的逼格!

三、装饰器的分类

装饰器可以由函数实现也可以由类实现,从这个角度我们可以把装饰器分为函数式装饰器和类装饰器;另外装饰器可以从是否带有参数来区分,区分为含参装饰器和不含参装饰器;还可以从Python是否内置的角度分为内置和非内置装饰器。下面来详细看看这些不同的装饰器。

1、固定参数的函数式装饰器

普通的无参函数装饰器,就是上面代码中提到的。那接下来就看有参的函数装饰器。有参数,就是函数在调用的时候参入参数,装饰器能接收到参数,那我们不给装饰器设置参数,会有什么样的效果呢?

import  time
def deco(f):
    def wrapper():
        print('I am a decorator!,Founction run begin!')
        sta = time.time()
        f()
        end = time.time()
        print('Founction run time is:',(end-sta))
        print('Founction run finished!')
    return wrapper

@deco
def do_something(a,b):
    print('a:',a)
    print('b:',b)


if __name__ == '__main__':
    do_something('王鸥泡泡袖','王凯猎狐')

直接报错,如下:

wrapper()中缺少参数,因此必须添加上。

import  time
def deco(f):
    def wrapper(a,b):
        print('I am a decorator!,Founction run begin!')
        sta = time.time()
        f(a,b)
        end = time.time()
        print('Founction run time is:',(end-sta))
        print('Founction run finished!')
    return wrapper

@deco
def do_something(a,b):
    print('a:',a)
    print('b:',b)


if __name__ == '__main__':
    do_something('王鸥泡泡袖','王凯猎狐')

运行成功!

2、非固定参数的函数式装饰器

现在有多个函数需要用到同一个装饰器,同时这里每个函数的参数个数不相同,类型也不一样。那这种情况是什么样的呢?这个时候就需要用到*args,**kwargs了。这样就能代表不定参数了,前者可以是列表元祖,后者是字典。直接上代码:

import  time
def deco(f):
    def wrapper(*args,**kwargs):
        print('I am a decorator!,Founction run begin!')
        sta = time.time()
        f(*args,**kwargs)
        end = time.time()
        print('Founction run time is:',(end-sta))
        print('Founction run finished!')
    return wrapper

@deco
def do_something(a,b):
    print('a:',a)
    print('b:',b)

@deco
def do_somethin1(text):
    text += ':IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?'
    print(text)

@deco
def do_something2(dic):
    for k,v in dic.items():
        print('%s:%s'%(k,v))

if __name__ == '__main__':
    do_something('王鸥泡泡袖','王凯猎狐')
    do_somethin1('英雄联盟大事件!')
    do_something2({'name':'夕小瑶','sex':'女','about':'NLPer大牛!'})

结果:

I am a decorator!,Founction run begin!
a: 王鸥泡泡袖
b: 王凯猎狐
Founction run time is: 8.106231689453125e-06
Founction run finished!
I am a decorator!,Founction run begin!
英雄联盟大事件!:IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?
Founction run time is: 2.86102294921875e-06
Founction run finished!
I am a decorator!,Founction run begin!
name:夕小瑶
sex:女
about:NLPer大牛!
Founction run time is: 9.059906005859375e-06
Founction run finished!

3、特殊的@deco(param)

还有一种特殊的参数形式,那就是在@deco中写参数的。形如:

def deco(param):
    def decorator(f):
        def wrapper(*args,**kwargs):
            if param == 'warning':
                print('I am a decorator!,%s founction run begin!warning!'%f.__name__)
                return f(*args, **kwargs)
            else:
                return f(*args, **kwargs)
        return wrapper
    return decorator

@deco(param='warning')
def do_something(a,b):
    print('a:',a)
    print('b:',b)

@deco(param='errors')
def do_something1(a):
    print('a:',a)

结果:

I am a decorator!,do_something founction run begin!warning!
a: 猎狐
b: 王鸥王凯CP
a: 明天免费去做核酸检测了,终于能白嫖一次公司了哈哈哈!

其实这种特殊参数的装饰器的应用场景不是特别能理解,这样的装饰器到底意义在哪里!

4、类式装饰器

顾名思义,类式装饰器就是实现形式是一个类,这个和函数式装饰器是不同的。这里的实现就必须用的__call__和__init__这两个函数,__call__用来实现装饰的逻辑,__init__则是用来接收外部调用函数的。具体看实例:

class Decorater(object):
    def __init__(self,fun):
        self.fun = fun

    def __call__(self, *args, **kwargs):
        print('function %s() is running!'%(self.fun.__name__))
        for ele in args:
            print(ele)
        return self.fun( *args, **kwargs)

@Decorater
def do_something(brand,msg):
    print('%s的显卡RTX 2080ti,%s'%(brand,msg))


if __name__ == '__main__':
    brand = 'Nvidia'
    msg = '价格好贵买不起!'
    do_something(brand,msg)

结果如下:

function do_something() is running!
Nvidia
价格好贵买不起!
Nvidia的显卡RTX 2080ti,价格好贵买不起!

上面的类装饰器是没有带参数的,同样的类装饰器也是可以带参数的。具体的实现形式如下:

class logger(object):
    def __init__(self, level='Error'):#接收参数
        self.level = level

    def __call__(self, func): # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..."\
                .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函数

@logger(level='INFO')
def say(something):
    print("speak{}!".format(something))


if __name__ == '__main__':
    say("小度小度,你好呀!")

5、Python内置装饰器

Python里面常用的内置装饰器是:@property,@staticmethod@classmethod

这里就简简单单的说一下它们的用法,具体的优势和区别就不做说明了。

@property

添加在方法上,可以使得方法变成类的属性,直接想访问属性的方式来访问;可以看到下面代码中有无@property访问方式的不一样。

class NLP(object):
    def __init__(self,model,task,tools):
        self.model = model
        self.task = task
        self.tools = tools

    def set_model(self):
        return self.model

    def set_task(self):
        return self.task
    
    @property
    def set_tools(self):
        return self.tools

if __name__ == '__main__':
    nlp = NLP('bert','classification','pytorch and python')
    print(nlp.set_model()) #没有@property,必须要调用set_model()方法来实现访问
    print(nlp.set_tools)  #有@property,可以类似访问属性的来访问——方法变成属性

@classmethod

类方法装饰器,在类中的方法中使用这个关键字,可以不用实例化类,就能直接调用该方法。它不需要传入self参数,只需要传入cls参数。具体实例,由上面的代码改造一下:

class NLP(object):
    model = 'bert'
    task = 'classification'
    tools = 'pytorch and python'


    @classmethod
    def set_model(cls):
        return cls.model

if __name__ == '__main__':
   print(NLP.set_model()) #这里并没有这样实例化NLP(),直接用NLP.来调用了

@classmethod

它可以使得类中的方法变为静态方法,不需要传入cls和self方法;调用的时候可以实例化类也可以不用实例化类,如下:

import time
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

    @staticmethod
    def now():#不需要传入cls和self参数
        t=time.localtime() #获取结构化的时间格式
        return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回


    @staticmethod
    def tomorrow():#不需要传入cls和self参数
        t=time.localtime(time.time()+86400)#明天时间增加了24*3600秒
        return Date(t.tm_year,t.tm_mon,t.tm_mday)

if __name__ == '__main__':
    now = Date.now() #Date类没有实例化
    tomorrow = Date.tomorrow()#Date类没有实例化
    print(now.year,now.month,now.day)
    print(tomorrow.year,tomorrow.month,tomorrow.day)
    my_date = Date('2222','02','02')#Date类实例化了
    print(my_date.year,my_date.month,my_date.day)
    print(my_date.now().year)#Date类实例化了

 

四、装饰器的缺陷及解决方法

以上是就装饰器的优点作用和分类做了一个总结,那么装饰器有什么缺陷吗?python的装饰器会导致被修饰函数的__doc__,__name__等属性丢掉。__doc__是每一个对象的一个属性,它是用来记录这个对象的一些描述,例如函数的参数说明和功能说明,__name__则是输出函数或者对象的名称。

当使用装饰器的时候就会导致__doc__,__name__等属性缺失,这个看具体例子:

import  time
def deco(f):
    def wrapper(*args,**kwargs):
        print('I am a decorator!,Founction run begin!')
        sta = time.time()
        f(*args,**kwargs)
        end = time.time()
        print('Founction run time is:',(end-sta))
        print('Founction run finished!')
    return wrapper


@deco
def do_somethin1(text):
    '装饰器应用'
    text += ':IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?'
    print(text)

def do_something2():
    'ordinary function'
    return None


if __name__ == '__main__':
    do_somethin1('英雄联盟大事件!')
    do_something2()
    print('do_somethin1:', do_somethin1.__name__)
    print('do_somethin1:', do_somethin1.__doc__)
    print('*'*20)
    print('do_somethin2:', do_something2.__name__)
    print('do_somethin2:', do_something2.__doc__)

结果是:

I am a decorator!,Founction run begin!
英雄联盟大事件!:IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?
Founction run time is: 2.6226043701171875e-06
Founction run finished!
do_somethin1: wrapper    #期望的应该是:do_somethin1
do_somethin1: None      #期望的应该是:装饰器应用
******************** 
do_somethin2: do_something2
do_somethin2: ordinary function

怎么解决这个缺陷呢?这里就需要用到另外的库了。直接使用functools.wraps,上述代码直接修改,wraps本质上也是一个装饰器。具体实例:

def deco(f):
    @wraps(f)
    def wrapper(*args,**kwargs):
        print('I am a decorator!,Founction run begin!')
        sta = time.time()
        f(*args,**kwargs)
        end = time.time()
        print('Founction run time is:',(end-sta))
        print('Founction run finished!')
    return wrapper

结果如同我们期待的那样了:

I am a decorator!,Founction run begin!
英雄联盟大事件!:IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?
Founction run time is: 2.86102294921875e-06
Founction run finished!
do_somethin1: do_somethin1
do_somethin1: 装饰器应用
********************
do_somethin2: do_something2
do_somethin2: ordinary function

其实这个缺陷个人认为是要看一定的业务场景来处理的,要是不需要被修饰函数的__doc__,__name__等属性,这个缺陷可以不处理;当然在有具体的需求场景的时候就是要处理的。

五、装饰器应用场景

具体的应用场景有哪些呢?它们主要是应用在如下的几个功能里面:

1、引入日志

2、函数时间统计

3、权限检测应用

我没有把装饰器用到过权限检测,给出一段别人总结的应用场景:否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。

from functools import wraps  

def requires_auth(f):    #  f 就是我们需要装饰的函数,一看就是不带参数的装饰器
    @wraps(f)     # 新版python写法 @functools.wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated    # 该装饰器需相关配置才能运行,这里是截取代码展示应用

4、@tf.function装饰器来将python代码转成图表示代码——这里完全是TensorFlow1.0和2.0之间一个差异,优劣互补的一个方法。

TensorFlow1.0中计算图代码:

g = tf.Graph() #初始化计算图
with g.as_default(): # 设置为默认计算图
    a = tf.constant([[10,10],[11.,1.]]) 
    x = tf.constant([[1.,0.],[0.,1.]])
    b = tf.Variable(12.)
    y = tf.matmul(a, x) + b # 描述计算图
    init_op = tf.global_variables_initializer() # 待执行节点

with tf.Session() as sess: # 配置会话
    sess.run(init_op) # 执行节点
    print(sess.run(y)) # 输出结果

这种结构的静态图的特点是效率高,因为很多计算图优化的方法只能用在数据流图上。TensorFlow2.0则采用了类似pytorch的动态计算图,缺点就是效率低,但是代码易懂。

a = tf.constant([[10,10],[11.,1.]])
x = tf.constant([[1.,0.],[0.,1.]])
b = tf.Variable(12.)
y = tf.matmul(a, x) + b
print(y.numpy())

为了使用TensorFlow1.0的那种静态图,这里在TensorFlow2.0中使用@tf.function来装饰即可。

@tf.function
def f():
    a = tf.constant([[10,10],[11.,1.]])
    x = tf.constant([[1.,0.],[0.,1.]])
    b = tf.Variable(12.)
    y = tf.matmul(a, x) + b
    print("PRINT: ", y)
    tf.print("TF-PRINT: ", y)
    return y

f()

 

 写在最后,本文总结了python的装饰器的用法分类和优势,可能理解不是很深刻。但是内容也算上还比较全面,这个对于基础薄弱的人(比如我)还是有一定的作用的。加油!继续学习TensorFlow2.0对比着pytorch来学习。

 

参考文章

python装饰器的详细解析

如何理解Python装饰器?

一篇文章搞懂装饰器所有用法(建议收藏)

python中常用的内置装饰器

55装饰器的写法以及应用场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值