装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的,说白了所谓装饰器,就是在不影响函数本身的情况下,为函数增加其他的附带功能,用于方便记录信息以及其他
某程序员接到要优化之前做的项目,首先他需要计算出各个模块的运行时间。现在有两种方案,一个是把程序执行时间功能写到模块内部,另一个则是另外写一个函数,但是修改模块内部是程序员大忌,所以采取第二种方案
#!/usr/bin/env python3
#-*-coding:UTF-8-*-
import time
def program_times(userMain):
startTime = time.time()
userMain()
endTime = time.time()
msecsTime = (endTime - startTime) * 1000
print("程序运行时间:%s 毫秒" % msecsTime)
def main():
print("程序运行开始...")
time.sleep(0.5)
print("程序运行结束...")
program_times(main)
运行结果:
Geek-Mac:Downloads zhangyi$ python3 Decorator.py
程序运行开始...
程序运行结束...
程序运行时间:500.9748935699463 毫秒
Geek-Mac:Downloads zhangyi$
程序最后一行是将 main
函数作为参数传递给 program_times
函数
因为函数也是一个对象(或者叫做是一个指针,就是内存地址,C 程序员应该能理解),对象可以被赋值给变量,所以,通过变量也能调用该函数,也直接传递给其他函数(将 main 函数地址传给 program_times)
函数对象有一个 __name__
属性,可以拿到函数的名字,其实就是将 main 的内存地址传给 f,f 就是 main() 函数,只不过是指向内存地址相同,名字不同罢了
>>> def main():
... print("Hello World!")
...
>>> f = main
>>> f
>>> f()
Hello World!
>>> main()
Hello World!
>>> f.__name__
'main'
>>> main.__name__
'main'
上面的做法有一个问题,就是所有的 main
调用处都要改为 deco(myfunc)
,下面做一些改动来避免计时功能对 main
函数调用代码的影响
#!/usr/bin/env python3
#-*-coding:UTF-8-*-
import time
def deco(userMain):
def program_times():
startTime = time.time()
userMain()
endTime = time.time()
msecsTime = (endTime - startTime) * 1000
print("程序运行时间:%s 毫秒" % msecsTime)
return program_times
def main():
print("程序运行开始...")
time.sleep(0.5)
print("程序运行结束...")
print("此时 main 函数的名称是:%s " % main.__name__)
main = deco(main)
print("此时 main 函数的名称是:%s " % main.__name__)
main()
运行结果:
Geek-Mac:Downloads zhangyi$ python3 Decorator.py
此时 main 函数的名称是:main
此时 main 函数的名称是:program_times
程序运行开始...
程序运行结束...
程序运行时间:503.86500358581543 毫秒
Geek-Mac:Downloads zhangyi$
main = deco(main)
是将 main 作为参数传递给 deco 函数,deco 函数会将 program_times 函数返回,然后在将 program_times 函数赋值给 main。此时的 main 函数其实已经是 program_times 函数了上面的实例其实已经是装饰器了,但是在 python 中,我们可以使用 @
语法糖来精简装饰器代码
#!/usr/bin/env python3
#-*-coding:UTF-8-*-
import time
def deco(userMain):
def program_times():
startTime = time.time()
userMain()
endTime = time.time()
msecsTime = (endTime - startTime) * 1000
print("程序运行时间:%s 毫秒" % msecsTime)
return program_times
@deco
def main():
print("程序运行开始...")
time.sleep(0.5)
print("程序运行结束...")
print("此时 main 函数的名称是:%s " % main.__name__)
main()
运行结果:
Geek-Mac:Downloads zhangyi$ python3 Decorator.py
此时 main 函数的名称是:program_times
程序运行开始...
程序运行结束...
程序运行时间:501.5270709991455 毫秒
Geek-Mac:Downloads zhangyi$
通过装饰器,我们可以在需要装饰器的函数上方加上 @语法糖
就可以,然后正常调用 mian 函数了
程序中 @deco
和 main = deco(main)
是完全等价的,本质是一样的
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会
因为装饰器本质也是函数,是函数就有参数,接下来看一个有参数的装饰器
#!/usr/bin/env python3
#-*-coding:UTF-8-*-
import time
def deco(userMain):
def program_times(a, b):
startTime = time.time()
userMain(a, b)
endTime = time.time()
msecsTime = (endTime - startTime) * 1000
print("程序运行时间:%s 毫秒" % msecsTime)
return program_times
@deco
def main(a, b):
print("程序运行开始...")
time.sleep(0.5)
print("a + b = %s " % (a + b))
print("程序运行结束...")
print("此时 main 函数的名称是:%s " % main.__name__)
main(1, 2)
运行结果:
Geek-Mac:Downloads zhangyi$ python3 Decorator.py
此时 main 函数的名称是:program_times
程序运行开始...
a + b = 3
程序运行结束...
程序运行时间:501.7970542907715 毫秒
Geek-Mac:Downloads zhangyi$
从例子中可以看到,对于被装饰函数需要支持参数的情况,我们只要使装饰器的内嵌函数支持同样的签名即可
例子中 main(1, 2) 其实是 deco(main(1, 2)) 的形式,先把 main 作为参数传给 deco 返回一个 program_times,之后再给 program_times 赋值
上面例子还有两个问题:
在 Python 中,函数可以支持 (*args, **kwargs)
可变参数,所以装饰器可以通过可变参数形式来实现内嵌函数的签名
如果装饰器本身需要支持参数,那么装饰器就需要多一层的内嵌函数
#!/usr/bin/env python3
#-*-coding:UTF-8-*-
import time
def deco(text):
def _deco(userMain):
def program_times(*args, **kwargs):
print(text)
startTime = time.time()
userMain(*args, **kwargs)
endTime = time.time()
msecsTime = (endTime - startTime) * 1000
print("程序运行时间:%s 毫秒" % msecsTime)
return program_times
return _deco
@deco("这个是 deco 的参数")
def main(a, b, c):
print("程序运行开始...")
time.sleep(0.5)
print("a + b + c = %s " % (a + b + c))
print("程序运行结束...")
print("此时 main 函数的名称是:%s " % main.__name__)
main(1, 2, 3)
运行结果:
Geek-Mac:Downloads zhangyi$ python3 Decorator.py
此时 main 函数的名称是:program_times
这个是 deco 的参数
程序运行开始...
a + b + c = 6
程序运行结束...
程序运行时间:504.67705726623535 毫秒
Geek-Mac:Downloads zhangyi$
装饰器是可以叠加使用的,那么这是就涉及到装饰器调用顺序了。对于 Python 中的 @
语法糖,装饰器的调用顺序与使用 @
语法糖声明的顺序相反
#!/usr/bin/env python3
#-*-coding:UTF-8-*-
import time
def deco_1(userMain):
def program_times(*args, **kwargs):
startTime = time.time()
userMain(*args, **kwargs)
endTime = time.time()
msecsTime = (endTime - startTime) * 1000
print("这里是 @deco_1 ")
print("程序运行时间:%s 毫秒" % msecsTime)
return program_times
def deco_2(userMain):
def program_times(*args, **kwargs):
startTime = time.time()
userMain(*args, **kwargs)
endTime = time.time()
msecsTime = (endTime - startTime) * 1000
print("这里是 @deco_2 ")
print("程序运行时间:%s 毫秒" % msecsTime)
return program_times
@deco_1
@deco_2
def main(a, b):
print("程序运行开始...")
time.sleep(0.5)
print("a + b = %s " % (a + b))
print("程序运行结束...")
main(1, 2)
运行结果:
Geek-Mac:Downloads zhangyi$ python3 Decorator.py
程序运行开始...
a + b = 3
程序运行结束...
这里是 @deco_2
程序运行时间:505.2931308746338 毫秒
这里是 @deco_1
程序运行时间:505.4490566253662 毫秒
Geek-Mac:Downloads zhangyi$
staticmethod
、classmethod
、 property
staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
property 是属性的意思,表示可以通过通过类实例直接访问的信息
对于 staticmethod 和 classmethod 这里就不介绍了,通过一个例子看看 property
#!/usr/bin/env python3
#-*-coding:UTF-8-*-
class Deco(object):
def __init__(self, var):
super(Deco, self).__init__()
self._var = var
@property
def var(self):
return self._var
@var.setter
def var(self, var):
self._var = var
deco = Deco("Deco 1")
print(deco.var)
deco.var = "Deco 2"
print(deco.var)
运行结果:
Geek-Mac:Downloads zhangyi$ python3 Decorator.py
Deco 1
Deco 2
Geek-Mac:Downloads zhangyi$
@var.setter
装饰器所装饰的成员函数去掉,则 Foo.var
属性为只读属性,使用 foo.var = 'var 2'
进行赋值时会抛出异常。但是,对于 Python classic class
,所声明的属性不是 read-only
的,所以即使去掉 @var.setter
装饰器也不会报错。参考文章:http://python.jobbole.com/82344/