一、应用场景
装饰模式有很多经典的使用场景,例如插入日志、性能测试、事物处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。好好利用装饰器可以方便代码结构,让代码更清晰可观。
二、几个简单的入门例子
现在我们需要通过代码来得到一个函数的大概执行时间,写法如下:
import time
def get_time(func):
startTime = time.time()
func()
endTime = time.time()
totalTime = (endTime - startTime) * 1000
print "it takes %f ms" %totalTime
def func():
print "start"
time.sleep(0.5)
print "end"
get_time(func)
func()
针对这个代码有个问题就是,我们在每次调用一个函数时,如果想知道这个函数的执行时间,就要把所有的函数调用处的代码”func()”改为get_time(func)
python中一切都是对象,函数也是,因此对代码稍作修改如下:
import time
def get_time(func):
def wrapper():
startTime = time.time()
func()
endTime = time.time()
totalTime = (endTime - startTime) *1000
print "it takes %f ms" %totalTime
return wrapper
def func():
print "start"
time.sleep(0.5)
print "end"
func =get_time(func)
func()
到这里,一个比较的完整的装饰器(get_time)就实现了,装饰器没有影响原来的函数,以及函数调用的代码。代码中改变了func对应的函数对象。
---------------引入@-------------------
下面我们引入装饰器的语法,用“@”来精简装饰器的代码,代码如下:
import time
def get_time(func):
def wrapper():
startTime = time.time()
func()
endTime = time.time()
totalTime = (endTime - startTime) *1000
print "it takes %f ms" %totalTime
return wrapper
@get_time
def func():
print "start"
time.sleep(0.5)
print "end"
func()
输出如下:
使用了“@”后,就不要额外代码来给func重新赋值了,也就是说在func上“@get_time”等同于“func = get_time(func)”
----------------被装饰的函数可以带参数--------------
被装饰的函数可以带参数,举个栗子如下:
import time
def get_time(func):
def wrapper(a, b):
startTime = time.time()
func(a, b)
endTime = time.time()
totalTime = (endTime - startTime) *1000
print "it takes %f ms" %totalTime
return wrapper
@get_time
def func(a, b):
print "start"
time.sleep(0.5)
print "a+b=%d" % (a+b)
print "end"
func(2, 3)
这时,func(2, 3) = get_time(func(2,3))
这时有另外一个问题,如果同时有多个函数,它们的参数都不一样,但是现在都需要使用get_time装饰器,要怎么办?
在python中,函数可以支持(*args, **kwargs)可变参数,所以装饰器可以通过可变参数形式来实现内嵌函数的签名。
注意:*args和**kwargs这两个魔法变量,其实并不一定要写成这样,只有变量前面的*(星号)才是必须的,也就是说你完全可以写成*var和**vars。而写成*args和**kwargs只是一个通俗的命名约定。
补充:
*args和**kwargs主要用于函数定义,你可以将不定数量的参数传递给一个函数。这里不定的意思是:预先不知道函数使用者会传递多少个参数给你,此时就使用这两个关键字。
*args是用来发送一个非键值对的可变数量的参数列表给一个函数。
例如:
def deco(arg,*argv):
print "first normal arg:", arg
for arg in argv:
print "another arg through*argv", arg
deco('a', 'b','c', 'd')
输出:
**kwargs允许你将不定长度的键值对,作为参数传递给一个函数。如果你需要在一个函数里处理带名字的参数,应该使用**kwargs
def deco(**kwargs):
for key, value in kwargs.items():
print "{0} =={1}".format(key, value)
deco(name ="hello")
输出:
------------装饰器本身可以带参数------------------
另外,装饰器本身也可以带参数,比如通过装饰器的参数来禁止计时功能。栗子如下:
import time
def get_time(arg=True):
if arg:
def deco(func):
def wrapper(*args, **kwargs):
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
totalTime = (endTime -startTime) * 1000
print "it takes %fms" % totalTime
return wrapper
else:
def deco(func):
return func
return deco
@get_time(True)
def func(a, b):
print "func start"
time.sleep(0.5)
print "a+b=%d" % (a + b)
print "func end"
@get_time(False)
def func1():
print "func1 start"
time.sleep(0.2)
print "func1 end"
func(2, 3)
func1()
输出:
如果装饰器本身需要支持参数,那么装饰器就需要多一层的内嵌函数。
这时 func(2, 3) = get_time(True)(func(2, 3))
func1() =get_time(False)(func1())
一个函数可以同时使用多个装饰器,然而装饰器的调用顺序与使用@语法声明的顺序相反。
import time
def deco1(func):
print "entered into deco1"
def wrapper(a, b):
print "entered intodeco1_wrapper"
func(a, b)
return wrapper
def deco2(func):
print "entered into deco2"
def wrapper(a, b):
print "entered intodeco2_wrapper"
func(a, b)
return wrapper
@deco1
@deco2
def func(a, b):
print "func start"
time.sleep(0.5)
print "a+b=%d" % (a + b)
print "func end"
func(2, 3)
输出:
这时 func(3, 8) = deco1(deco2(func(3, 8)))
调用顺序这一块要好好深究一下,待补充......
三、python内置装饰器
在python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod和property
(1)staticmethod是类静态方法,其跟成员方法的区别是没有self参数,并且可以在类不进行实例化的情况下调用
适用的场景:有时候在多个类中都会使用到相同的一些工具函数,或者在一个类中,有一个在别的函数中使用到的通用函数,那么这时候,如果每次使用都要实例化后再调用会造成多余的资源浪费。
此时,使用@staticmethod装饰器来装饰函数,就可以直接使用类名来调用函数。例如:
1、不使用staticmethod时
class A(object):
def func(self):
print “hello world!”
# 调用时要先实例化
a = A()
a.func()
2、使用staticmethod时,代码修改如下:
class A(object)
@staticmethod
def func(): # self去掉
print “hello world”
# 不需要实例化可直接调用
A.func()
(2)classmethod与成员方法的区别在于所接收的第一个参数不是self(类实例的指针),而是cls(当前类的具体类型)。用法和场景与staticmethod相似。因为参数中有cls参数,所以可以用来调用类的属性,类的方法,实例化对象等。
例子:
# coding=utf-8
classTest(object):
def func1(self):
print "func1"
@staticmethod
def func2():
print "func2"
@classmethod
def func3(cls):
cls().func1()
cls.func2()
print "func3"
a = Test()
a.func1()
Test.func2()
Test.func3()
输出:
(3)property是属性的意思,表示可以通过类实例直接访问的信息
对于一个Student类
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
s = Student(‘Bob’,59)
s.score=60
当我们要修改一个s的score属性时可以这么写:
s.score = 1000
但是这样直接给属性赋值无法检查分数的有效性
下面进行一些修改,添加get和set函数
classStudent(object):
def __init__(self, name, score):
self._name = name
self._score = score
def get(self):
return self._score
def set(self, score):
if score<0 or score>100:
raise ValueError(‘invalidscore’)
self._score = score
这时,如果要修改s的score属性时,使用s.set(1000)就会报错。
使用get/set方法来封装对一个属性的访问在许多面向编程的语言中都很常见。
python中还可使用装饰器函数把get/set方法装饰成属性调用
class Student(object):
def __init__(self, name, score):
self._name = name
self._score = score
@property
def score(self):
return self._score
@score.setter
def score(self, score):
if score<0 or score>100:
raise ValueError(‘invalidscore’)
self._score = score
这里,第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。
此时可以像使用属性一样设置score了。例如
s = Student(‘Bob’,60)
s.score = 1000 # 这一句会报错
注意这里s.score里的score对应于@property下面的函数名,如果函数名是
@property
def get(self):
return self._score
那么调用的时候就是s.get = 1000