【python】详解装饰器@的使用:性能测试、装饰器参数、调用顺序、内置装饰器

python中使用’@’ 作为函数的修饰符,可以在模块或者类的定义层内对函数进行修饰,出现在函数定义的前一行,不允许和函数定义在同一行。一个修饰符就是一个函数,它将被修饰的函数作为参数,并返回修饰后的同名函数或其他可调用的东西。

装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。

1、通过装饰器重用功能对多对象进行性能测试:
我们可以直接把计时逻辑方法my_func内部,但是这样的话,如果要给另一个函数计时,就需要重复计时的逻辑。所以比较好的做法是把计时逻辑放到另一个函数中(timecount),如下:

# -*- coding: utf-8 -*-
"""
Created on Tue Jan 23 09:28:44 2018
python装饰器
@author: BruceWong
"""

import time

#设定计时器函数

def timecount(func):
    timestart = time.time()
    print(timestart)
    func()
    timeend = time.time()
    print(timeend)
    timecounts = (timeend - timestart)
    print('func costs time %s'%timecounts)

#设定功能函数
#@timecount
def my_func():
    timestart_ = time.time()
    print('_____start_____',timestart_)
    time.sleep(3)
    timeend_ = time.time()
    print('_____end_____',timeend_)

timecount(my_func)
my_func()

输出结果:

1516674631.0664797
_____start_____ 1516674631.0664797
_____end_____ 1516674634.066553
1516674634.066553
func costs time 3.0000734329223633
_____start_____ 1516674634.066553
_____end_____ 1516674637.069286

但是,上面的做法也有一个问题,就是所有的my_func调用处都要改为timecount(my_func)。下面,做一些改动,来避免计时功能对my_func函数调用代码的影响:

import time

#设定计时器函数

def timecount(func):   #定义函数的变量只需写入函数的名称
    def wrapper():
        timestart = time.time()
        print(timestart)
        func()      #在函数中调用函数一定要写完整的函数
        timeend = time.time()
        print(timeend)
        timecounts = (timeend - timestart)
        print('func costs time %s'%timecounts)
    return wrapper

#设定功能函数
#@timecount
def my_func():
    timestart_ = time.time()
    print('_____start_____',timestart_)
    time.sleep(3)
    timeend_ = time.time()
    print('_____end_____',timeend_)
print('my func name is %s'%my_func.__name__)    
my_func = timecount(my_func)   #此时得到的my_func只是一个wrapper函数,需要函数化运行得到结果
print('my func name is %s'%my_func.__name__)    

my_func()  #函数化运行得到结果

输出结果:

my func name is my_func
my func name is wrapper
1516674769.831145
_____start_____ 1516674769.831145
_____end_____ 1516674772.8370035
1516674772.8380172
func costs time 3.0068721771240234

经过了上面的改动后,一个比较完整的装饰器(timecount)就实现了,装饰器没有影响原来的函数,以及函数调用的代码。例子中值得注意的地方是,Python中一切都是对象,函数也是,所以代码中改变了”my_func”对应的函数对象。

2、装饰器语法糖,在Python中,可以使用”@”语法糖来精简装饰器的代码

  • 初始计时使用@语法糖表达:
def timecount(func):   #定义函数的变量只需写入函数的名称
    timestart = time.time()
    print(timestart)
    func()      #在函数中调用函数一定要写完整的函数
    timeend = time.time()
    print(timeend)
    timecounts = (timeend - timestart)
    print('func costs time %s'%timecounts)

#设定功能函数
@timecount
def my_func():
    timestart_ = time.time()
    print('_____start_____',timestart_)
    time.sleep(3)
    timeend_ = time.time()
    print('_____end_____',timeend_)

输出结果:

1516675437.0982182
_____start_____ 1516675437.0982182
_____end_____ 1516675440.1111493
1516675440.1111493
func costs time 3.0129311084747314
  • 加入wrapper后的表达:
import time

#设定计时器函数

def timecount(func):   #定义函数的变量只需写入函数的名称
    def wrapper():
        timestart = time.time()
        print(timestart)
        func()      #在函数中调用函数一定要写完整的函数
        timeend = time.time()
        print(timeend)
        timecounts = (timeend - timestart)
        print('func costs time %s'%timecounts)
    return wrapper

#设定功能函数
#使用装饰器@timecount
@timecount
def my_func():
    timestart_ = time.time()
    print('_____start_____',timestart_)
    time.sleep(3)
    timeend_ = time.time()
    print('_____end_____',timeend_)
print('my func name is %s'%my_func.__name__)    
#my_func = timecount(my_func)
print('my func name is %s'%my_func.__name__)    

my_func()

输出结果:

my func name is wrapper
1516675022.025378
_____start_____ 1516675022.025378
_____end_____ 1516675025.0261602
1516675025.0261602
func costs time 3.0007822513580322

使用了”@”语法糖后,我们就不需要额外代码来给”my_func”重新赋值了,其实@timecount的本质就是my_func = timecount(my_func),当认清了这一点后,后面看带参数的装饰器就简单了。

3、被装饰的函数带参数

对于被装饰函数需要支持参数的情况,我们只要使装饰器的内嵌函数支持同样的表达式即可。

def timecount(func):   #定义函数的变量只需写入函数的名称
    timestart = time.time()
    print(timestart)
    func(3,4)      #在函数中调用函数一定要写完整的函数
    timeend = time.time()
    print(timeend)
    timecounts = (timeend - timestart)
    print('func costs time %s'%timecounts)

#设定功能函数
@timecount
def my_func(a,b):
    timestart_ = time.time()
    print('_____start_____',timestart_)
    time.sleep(3)
    print('result is %d'%(a*b))
    timeend_ = time.time()
    print('_____end_____',timeend_)

输出结果:

1516675735.2867255
_____start_____ 1516675735.2867255
result is 12
_____end_____ 1516675738.29806
1516675738.29806
func costs time 3.0113344192504883

这里还有一个问题,如果多个函数拥有不同的参数形式,怎么共用同样的装饰器?在Python中,函数可以支持(*args, **kwargs)可变参数,所以装饰器可以通过可变参数形式来实现内嵌函数的签名

'''
注意在 #1 处函数 inner 接收任意数量和任意类型的参数,然后在 #2 处将他们传递给被包装的函数。这样一来我们可以包装或装饰任意函数,而不用管它的签名。
'''
def logger(func):
    def inner(*args, **kwargs): #1
        print("Arguments were: %s, %s" % (args, kwargs))
        return func(*args, **kwargs) #2
    return inner
@logger
def foo1(x, y=1):
    return x * y
@logger
def foo2():
    return 2 

输出结果:

Arguments were: (5, 4), {}
Arguments were: (1,), {}
Arguments were: (), {}
20 1 2

4、带参数的装饰器

  • 如果装饰器本身需要支持参数,那么装饰器就需要多一层的内嵌函数
# -*- coding: utf-8 -*-
"""
Created on Tue Jan 23 09:28:44 2018
python装饰器
@author: BruceWong
"""

import time

#设定计时器函数
def timecount(arg = True):
    if arg:
        def timecount(func):   #定义函数的变量只需写入函数的名称
            timestart = time.time()
            print(timestart)
            func(3,4)      #在函数中调用函数一定要写完整的函数
            timeend = time.time()
            print(timeend)
            timecounts = (timeend - timestart)
            print('func_one costs time %s'%timecounts)
    else:
        def timecount(func):
            timestart = time.time()
            print(timestart)
            func()      #在函数中调用函数一定要写完整的函数
            timeend = time.time()
            print(timeend)
            timecounts = (timeend - timestart)
            print('func_two costs time %s'%timecounts)
    return timecount
#设定功能函数
@timecount(True)
def my_func_one(a,b):
    timestart_ = time.time()
    print('_____start_____',timestart_)
    time.sleep(3)
    print('result is %d'%(a*b))
    timeend_ = time.time()
    print('_____end_____',timeend_)
@timecount(False)
def my_func_two():
    timestart_ = time.time()
    print('_____start_____',timestart_)
    time.sleep(1)
    timeend_ = time.time()
    print('_____end_____',timeend_)

输出结果:


1516676322.4453025
_____start_____ 1516676322.4453025
result is 12
_____end_____ 1516676325.4502711
1516676325.4502711
func_one costs time 3.0049686431884766
1516676325.4502711
_____start_____ 1516676325.4502711
_____end_____ 1516676326.4581466
1516676326.4581466
func_two costs time 1.0078754425048828

5、装饰器调用顺序

  • 装饰器是可以叠加使用的,那么这是就涉及到装饰器调用顺序了。对于Python中的”@”语法糖,装饰器的调用顺序与使用 @ 语法糖声明的顺序相反
#装饰器调用顺序
import time
def timecount(func):
    timestart = time.time()
    print(timestart)
    func()      #在函数中调用函数一定要写完整的函数
    timeend = time.time()
    print(timeend)
    timecounts = (timeend - timestart)
    print('func costs time %s'%timecounts)
def printf(func):
    print('kneel')

@printf
@timecount

def my_func():
    timestart_ = time.time()
    print('_____start_____',timestart_)
    time.sleep(3)
    timeend_ = time.time()
    print('_____end_____',timeend_)

输出结果:

1516677094.5291004
_____start_____ 1516677094.5291004
_____end_____ 1516677097.5339239
1516677097.5339239
func costs time 3.0048234462738037
kneel

Python内置装饰器

在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。

  • staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
class func():
    tall = 180
    hobbies = ['football','codemaker']
    def __init__(self,name,age):
        self.name = name
        self.age = age
    @staticmethod  
    def info():    #没有 self 参数,并且可以在类不进行实例化的情况下调用
        print('I am %s and my hobbies are %s'%(func.tall,func.hobbies))

Bruce = func('Bruce',25)
Bruce.info()

输出结果:

I am 180 and my hobbies are ['football', 'codemaker']
  • classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
  • property 是属性的意思,表示可以通过通过类实例直接访问的信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值