装饰器的出现是广大**的福音, 可以无需修改原有代码结构,直接运行该代码函数,而获取该函数相关信息,如name,doc,run time等一系列信息,如下带你进入装饰器的世界.
1 入门
1.0 不带参数装饰器
【Demo】
from functools import wraps
def trace(func):
# @wraps(func)
def callf(*args, **kwargs):
'''A wrapper function.'''
print("Calling function: {}".format(func.__name__))
res = func(*args, **kwargs)
print("Return value: {}".format(res))
return res
return callf
@trace
def foo(x):
'''Return value square.'''
return x*x
if __name__ == "__main__":
print(foo(3))
print(foo.__doc__)# A wrapper function.
print(foo.__name__)# callf
- Result
Calling function: foo
Return value: 9
9
A wrapper function.
callf
【Analysis】
(1) python3.x中使用@调用函数作为装饰器,其中trace为装饰器函数名,调用装饰器和调用其他函数一样,执行函数功能,该trace函数返回的是callf函数,因此执行callf函数功能;
(2) 调用foo(3)即先执行callf函数,函数foo
就是callf
中的func
函数,所以第一个结果为Callling function:foo
,由此可知func
为函数foo'
,第二个结果为Return value: 9
,第三个结果为foo
函数功能,输出9
.
(3) foo
函数为装饰器函数,由foo.__name__
结果为callf
,装饰器信息丢失,foo.__doc__
结果A wrapper function
可知.
(4) 使用wrapper(func)
可避免抓装饰器信息丢失.
@trace
def foo(x)
等价于:
trace(foo)
1.2 带参数装饰器
【Demo】
class logging(object):
def __init__(self, level="INFO"):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(level=self.level, func=func.__name__))
func(*args, **kwargs)
# return func(*args, **kwargs)
return wrapper
@logging(level="INOF")
def say(x):
print("hello {}".format(x))
if __name__ == "__main__":
say("hahaha")
【Result】
[INOF]: enter function say()
hello hahaha
【Analysis】
(1) 原理同上,只是带参数的装饰器使用类(class)的初始化提供参数输入.
(2) 通过对类中的函数进行重写,如init初始化函数,
−
−
c
a
l
l
−
−
_{--}call_{--}
−−call−−调用函数,实现参数及函数使用.
2 提高
2.1 计时装饰器
【普通函数】plot_sin
def plot_sin():
PI = np.pi
x = np.linspace(-2*PI, 2*PI, 100)
y = np.sin(x)
plt.figure(figsize=(6, 6))
plt.grid()
plt.plot(x, y, 'r-', label="$y=sin(x)$")
plt.legend()
plt.show()
对于某一个函数,可能是自己写的,也可能不是自己写的,但是需要获取程序运行时间,发现函数是一个月前甚至更久之前写的,惊不惊喜,意不意外,此时装饰器即可解决问题,无需修改源码,直接写装饰器函数即可,代码如下:
import numpy as np
import matplotlib.pyplot as plt
import time
def time_cal(func):
def wrapper(*args, **kwargs):
time_start = time.time()
func()
time_end = time.time()
time_cost = time_end - time_start
return time_cost
return wrapper
@time_cal
def plot_sin():
PI = np.pi
x = np.linspace(-2*PI, 2*PI, 100)
y = np.sin(x)
plt.figure(figsize=(6, 6))
plt.grid()
plt.plot(x, y, 'r-', label="$y=sin(x)$")
plt.legend()
plt.show()
if __name__ == "__main__":
print("plot_sin function cost time: {}s".format(plot_sin()))
【Result】
plot_sin function cost time: 0.1657559871673584s
【Analysis】
(1) 已知函数plot_sin
欲测试函数运行时间,但是对函数不熟悉,可直接使用装饰器,不改变函数结构,在装饰器中实现时间测试的功能.
(2) 无需修改plot_sin函数结构,直接在装饰器函数下面即可.
functools
闭包
装饰器接口
2.2 wraps
【Demo】
from functools import wraps
def trace(func):
@wraps(func)
def callf(*args, **kwargs):
'''A wrapper function.'''
print("Calling function: {}".format(func.__name__))
res = func(*args, **kwargs)
print("Return value: {}".format(res))
return res
return callf
@trace
def foo(x):
'''Return value square.'''
return x*x
if __name__ == "__main__":
print(foo(3))
print(foo.__doc__)
print(foo.__name__)
【Result】
Calling function: foo
Return value: 9
9
Return value square.
foo
【Analysis】
(1) 使用wraps可避免装饰器信息丢失.
(2) foo.__name__
输出为函数名foo
.
2.3 装饰器链
【Demo】
def wrapper_1(func):
return lambda: "w1 "+func()+"w1 "
def wrapper_2(func):
return lambda:"w2"+func()+"w2 "
@wrapper_1
@wrapper_2
def foo():
return " hahah "
if __name__ == "__main__":
print(foo())
【Result】
w1 w2 hahah w2 w1
【Analysis】
(1) 一个函数同时可由多个装饰器使用,组成装饰器链;
(2) 执行顺序是由近及远,w1 w2 hahah w2 w1
,先执行wrapper_2
在执行wrapper_1
.
2.4 内置装饰器
2.4.1 @property
功能:将类的方法转换为同名称的属性,完成对属性的增删改,其中:
__intit__
增加属性;
@property获取属性;
@x.setter修改属性;
@x.deleter删除属性;
【Demo1】
class Foo(object):
def __init__(self):
self._x = None
@property
def x(self):
'''Get value.'''
print("get")
return self._x
@x.setter
def x(self, value):
'''Set value.'''
print("set")
if value > 10:
raise ValueError("invalid value")
self._x = value
@x.deleter
def x(self):
'''Delete value.'''
print("delete attribute")
del self._x
if __name__ == "__main__":
foo = Foo()
print(help(foo))
'''set'''
foo.x = 1
'''get'''
print("x value: {}".format(foo.x))
'''delete'''
del foo.x
'''get'''
print("x value: {}".format(foo.x))
【Result】
Help on Foo in module __main__ object:
class Foo(builtins.object)
| Methods defined here:
|
| __init__(self)
| Initialize self. See help(type(self)) for accurate signature.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| x
| Get value.
None
set
get
x value: 1
delete attribute
get
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-153-276bb444891e> in <module>
30 del foo.x
31 '''get'''
---> 32 print("x value: {}".format(foo.x))
33
<ipython-input-153-276bb444891e> in x(self)
6 '''Get value.'''
7 print("get")
----> 8 return self._x
9 @x.setter
10 def x(self, value):
AttributeError: 'Foo' object has no attribute '_x'
【Analysis】
(1) help(instantiation)即help(foo)输出类架构.
(2) @property有四个参数,fget, fset, fdel, doc
,其中:
序号 | 参数 | 描述 |
---|---|---|
1 | fget | 获取属性值 |
2 | fset | 设置属性值 |
3 | fdel | 删除属性 |
4 | doc | 属性描述 |
(3) @property默认属性为get,之后的二个为get属性的衍生,通过转换后的属性执行set,del
.
(4) 执行del
后,即删除创建的属性,因此不能获取该属性的值,所以报错AttributeError: 'Foo' object has no attribute '_x'
.
【Demo2】
class Foo(object):
def __init__(self, name, score):
self.name = name
self._score = score
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if value > 100:
raise ValueError('invalid score')
self.__score = value
@score.deleter
def score(self):
del self._score
if __name__ == "__main__":
foo = Foo("hahah", 100)
print(foo.score)
foo.score = 1000
foo.score
foo.score
【Reuslt】
100
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-142-99037b9e42d8> in <module>
19 foo = Foo("hahah", 100)
20 print(foo.score)
---> 21 foo.score = 1000
22 foo.score
23 foo.score
<ipython-input-142-99037b9e42d8> in score(self, value)
9 def score(self, value):
10 if value > 100:
---> 11 raise ValueError('invalid score')
12 self.__score = value
13 @score.deleter
ValueError: invalid score
【Analysis】
(1) 通过装饰器@property可直接通过类的方法修改的私有属性__score
;
(2) s.score
替代s.__score
.
(3) 通过setter
设定取值范围,限制取值,超出范围报错;
2.4.2 classmethod
使用@classmethod将类作为函数的第一参数,替代self传递当前类实例化对象.
【Demo1】
class Test(object):
def foo(self, x):
print("executing foo self:{}, x: {}".format(self, x))
@classmethod
def class_foo(cls, x):
print("executing class_foo cls: {}, x: {}".format(cls, x))
@staticmethod
def static_foo(x):
print("executing static_foo: {}".format(x))
t = Test()
t.foo(250)
t.class_foo(250)
t.static_foo(250)
Test.foo(t, 250)
Test.class_foo(250)
Test.static_foo(250)
【Result】
executing foo self:<__main__.Test object at 0x7fac7bfee518>, x: 250
executing class_foo cls: <class '__main__.Test'>, x: 250
executing static_foo: 250
executing foo self:<__main__.Test object at 0x7fac7bfee518>, x: 250
executing class_foo cls: <class '__main__.Test'>, x: 250
executing static_foo: 250
【Analysis】
(1) self返回的是类Test的对象,cls返回的是类Test;
(2) 使用@classmethod定义的函数可以通过类直接调用,无需实例化;
(3) self传递的是类实例化的对象,cls传递的是类,因为实例化t=Test()
调用普通函数形式为:t.foo
,通过实例t
调用函数,若使用类调用函数,需要将实例化的对象传递给self,如:Test.foo(t, 250)
;cls通过类调用函数:Test.class_foo
;
【Demo2】
class DateOut(object):
def __init__(self, year=0, month=0, day=0):
self.year = year
self.month = month
self.day = day
@classmethod
def get_date(cls, data):
year, month, day = map(int, data.split("-"))
date = cls(year, month, day)
return date
def output_date(self):
print("date: {}Y {}M {}D".format(self.year, self.month, self.day))
date = DateOut.get_date("2019-4-28")
print("date: {}".format(date))
date.output_date()
【Result】
date: <__main__.DateOut object at 0x7fac7bff3978>
date: 2019Y 4M 28D
3 总结
(1) 装饰器分为自定义装饰器和内置装饰器;
(2) 自定义装饰器形成函数闭包,可以不修改函数获取函数名称,运行时间;
(3) 内置装饰器常用的有@preperty和@classmethod;
(4) @property装饰器可将函数作为同名的属性使用,完成增删改查;
(5) @classmethod装饰器可将类作为第一个参数传给类中的函数,这样可以实现在类的函数中使用类的初始化功能;
(6) 类函数的第一个形参必须为类的实例,用self表示类的实例化对象,作为第一个形参,该参数约定俗成用self
,也可以使用其他命名,为了提高程序可读性,公认使用self;
(7) @staticmethod不用实例化,即可使用下面的方法,用法与@classmethod相同。
[参考文献]
[1]https://www.cnblogs.com/Neeo/p/8371826.html
[2]https://www.cnblogs.com/cicaday/p/python-decorator.html
[3]https://www.cnblogs.com/huchong/p/7725564.html
[4]https://www.cnblogs.com/lianyingteng/p/7743876.html
[5]https://blog.csdn.net/kangkanglou/article/details/79076357
[6]https://docs.python.org/3/library/functions.html#property
[7]https://www.cnblogs.com/elie/p/5876210.html
[8]https://blog.csdn.net/test_xhz/article/details/82024838
[9]https://www.cnblogs.com/jessonluo/p/4717140.html
[10]http://www.cnblogs.com/chownjy/p/8663024.html