由于看python代码里面函数上的@ 不爽很久了,为了避免自己又忘记了这里来记录下。
简单总结:
@ 的作用就是在使用 @ 下面的函数(如下图的cs2)的时候,会在该函数执行前将该函数作为参数扔到@后跟着的处理函数先行处理(或预备处理)些东西,如下图的 time_cs 就是本文举的封装一个计时函数的装饰器。
阅读下面代码对比使用语法糖的使用,即可了解。
本篇博客自参考b站大佬up的视频
区分a()和a如下
一个是: 函数a在电脑中的十六进制地址
一个是: 函数a返回的值
def a():
return 1
print(a) # 函数a在电脑中的十六进制地址
print(a()) # 函数a返回的值
输出
<function a at 0x000001E37E1C6DC8>
1
def a():
return "a的返回"
def b():
return "b的返回"
def out(action):
"""传入函数对象"""
print(action())
out(a)
out(b)
输出
a的返回
b的返回
即检测的目标函数没有输入值也没有返回值的情况
import time
def time_cs(func):
"""
:param func: 传入的你需要计时的函数
:return: 无
"""
print("开始计时,打下时间戳")
start = time.perf_counter()
func()
end = time.perf_counter()
print("完成计时,消耗时间" + str(end-start))
print("\n")
def cs():
print("\n")
print("*"*8 + "fun" + "*"*8)
print("只是打印当前信息的测试函数")
print("*" * 20)
print("\n")
time.sleep(0.25)
time_cs(cs)
输出
开始计时,打下时间戳
********fun********
只是打印当前信息的测试函数
********************
完成计时,消耗时间0.2602558
即目标函数拥有返回值的情况
这里比较麻烦需要在time_cs 函数中将返回值用一个变量存储,使得你需要预先知道目标函数返回值的个数
import time
def time_cs(func):
"""
:param func: 传入的你需要计时的函数
:return: func函数的输出
"""
print("开始计时,打下时间戳")
start = time.perf_counter()
num = func()
end = time.perf_counter()
print("完成计时,消耗时间" + str(end-start))
print("\n")
return num
def cs2():
print("\n")
print("*"*8 + "fun2" + "*"*8)
print("打印当前信息的测试函数,并返回一个整形的10")
print("*" * 20)
print("\n")
time.sleep(0.25)
return 10
def cs3(num):
print("cs3函数得到了cs2函数的输出:" + str(num))
cs3(time_cs(cs2))
输出
开始计时,打下时间戳
********fun2********
打印当前信息的测试函数,并返回一个整形的10
********************
完成计时,消耗时间0.257934
cs3函数得到了cs2函数的输出:10
进程已结束,退出代码 0
即目标函数拥有有输入有输出返回值的情况,
首先展示一个能运行但是,无法成功获得目标函数运行时间的写法,这里time_cs输出内部输出由result = func()改成了 result = func
import time
def time_cs(func):
"""
:param func: 传入的你需要计时的函数
:return: func函数的输出
"""
print("开始计时,打下时间戳")
start = time.perf_counter()
result = func # 注意这里!!不是 func()!!
print("传入的函数的id: " + str(id(func)))
end = time.perf_counter()
print("完成计时,消耗时间" + str(end - start))
print("\n")
return result
def cs2(num):
print("\n")
print("*"*8 + "fun2" + "*"*8)
print("打印当前信息的测试函数,并返回一个整形的" + str(num))
print("*" * 20)
print("\n")
time.sleep(0.25)
return num
def cs3(num):
print("cs3函数得到了cs2函数的输出:" + str(num))
num = time_cs(cs2(5))
cs3(num)
输出如下,可以看到该写法下的消耗时间统计是错误的
原因:传入的时候就是传入了一个 cs2(5) 即一个完成了执行了time.sleep(0.25) 之后的程序进入,因此下面输出的9.900000000007125e-06 代表的时间实际就可以理解成输入的func函数运行时间为0时,time_cs函数自身的运行时间
********fun2********
打印当前信息的测试函数,并返回一个整形的5
********************
开始计时,打下时间戳
传入的函数的id: 140720369607200
完成计时,消耗时间9.900000000007125e-06
cs3函数得到了cs2函数的输出:5
import time
def time_cs(func,num):
"""
:param func: 传入的你需要计时的函数
:return: func函数的输出
"""
print("开始计时,打下时间戳")
start = time.perf_counter()
result = func(num)
print("传入的函数的id: " + str(id(func)))
end = time.perf_counter()
print("完成计时,消耗时间" + str(end - start))
print("\n")
return result
def cs2(num):
print("\n")
print("*"*8 + "fun2" + "*"*8)
print("打印当前信息的测试函数,并返回一个整形的" + str(num))
print("*" * 20)
print("\n")
time.sleep(0.25)
return num
def cs3(num):
print("cs3函数得到了cs2函数的输出:" + str(num))
num = time_cs(cs2,5)
cs3(num)
输出
开始计时,打下时间戳
********fun2********
打印当前信息的测试函数,并返回一个整形的5
********************
传入的函数的id: 2832141314952
完成计时,消耗时间0.2586813
cs3函数得到了cs2函数的输出:5
(目标函数拥有输入有输出)
这里将time_cs这个统计输入函数运行时间的功能函数,改造成输入和输出都是函数 的功能函数,这里是最接下面近装饰器写法的,建议与下面的2.使用装饰器的写法对比看
import time
import functools
def time_cs(func):
"""
:param func: 传入的你需要计时的函数
:return: func函数的输出
"""
def my_wrapper(*args, **kwargs):
"""再创建一个装饰器函数"""
print("开始计时,打下时间戳")
start = time.perf_counter()
result = func(*args, **kwargs)
print("传入的函数的id: " + str(id(func)))
end = time.perf_counter()
print("完成计时,消耗时间" + str(end - start))
print("\n")
return result
return my_wrapper
def cs2(num):
print("\n")
print("*"*8 + "fun2" + "*"*8)
print("打印当前信息的测试函数,并返回一个整形的" + str(num))
print("*" * 20)
print("\n")
time.sleep(0.25)
return num
def cs3(num):
print("cs3函数得到了cs2函数的输出:" + str(num))
cs2_2 = time_cs(cs2)
cs3(cs2_2(5))
print("传出的函数的id: " + str(id(cs2_2)))
print("传出的函数的名字: " + str(cs2_2.__name__)) # 不加 @functools.wraps(func) 就是输出my_wrapper
输出
开始计时,打下时间戳
********fun2********
打印当前信息的测试函数,并返回一个整形的5
********************
传入的函数的id: 1392449547432
完成计时,消耗时间0.2594437
cs3函数得到了cs2函数的输出:5
传出的函数的id: 1392449545992
传出的函数的名字: my_wrapper
如下
import time
import functools
def time_cs(func):
"""
:param func: 传入的你需要计时的函数
:return: func函数的输出
"""
@functools.wraps(func) # 这里的作用是使得返回的 my_wrapper 的 .__name__ 名字
# 和传入的 func.__name__ 的相同,如下print(cs2_2.__name__)
def my_wrapper(*args, **kwargs):
"""再创建一个装饰器函数"""
print("开始计时,打下时间戳")
start = time.perf_counter()
result = func(*args, **kwargs)
print("传入的函数的id: " + str(id(func)))
end = time.perf_counter()
print("完成计时,消耗时间" + str(end - start))
print("\n")
return result
return my_wrapper
def cs2(num):
print("\n")
print("*"*8 + "fun2" + "*"*8)
print("打印当前信息的测试函数,并返回一个整形的" + str(num))
print("*" * 20)
print("\n")
time.sleep(0.25)
return num
def cs3(num):
print("cs3函数得到了cs2函数的输出:" + str(num))
cs2_2 = time_cs(cs2)
cs3(cs2_2(5))
print("传出的函数的id: " + str(id(cs2_2)))
print("传出的函数的名字: " + str(cs2_2.__name__)) # 不加 @functools.wraps(func) 就是输出my_wrapper
输出
开始计时,打下时间戳
********fun2********
打印当前信息的测试函数,并返回一个整形的5
********************
传入的函数的id: 2366053213960
完成计时,消耗时间0.2593284
cs3函数得到了cs2函数的输出:5
传出的函数的id: 2366053215688
传出的函数的名字: cs2
进程已结束,退出代码 0
能够达到以上的效果最接近上面1.3,且使用起来更加的方便
import time
import functools
def time_cs(func):
"""
:param func: 传入的你需要计时的函数
:return: func函数的输出
"""
@functools.wraps(func) # 这里的作用是使得返回的 my_wrapper 的 .__name__ 名字
# 和传入的 func.__name__ 的相同,如下print(cs2_2.__name__)
def my_wrapper(*args, **kwargs):
"""再创建一个装饰器函数"""
print("开始计时,打下时间戳")
start = time.perf_counter()
result = func(*args, **kwargs)
print("传入的函数的id: " + str(id(func)))
end = time.perf_counter()
print("完成计时,消耗时间" + str(end - start))
print("\n")
return result
return my_wrapper
@time_cs
def cs2(num):
print("\n")
print("*"*8 + "fun2" + "*"*8)
print("打印当前信息的测试函数,并返回一个整形的" + str(num))
print("*" * 20)
print("\n")
time.sleep(0.25)
return num
def cs3(num):
print("cs3函数得到了cs2函数的输出:" + str(num))
cs3(cs2(5))
print("传出的函数的名字: " + str(cs2.__name__)) # 不加 @functools.wraps(func) 就是输出my_wrapper
输出
开始计时,打下时间戳
********fun2********
打印当前信息的测试函数,并返回一个整形的5
********************
传入的函数的id: 2966589466664
完成计时,消耗时间0.26083870000000003
cs3函数得到了cs2函数的输出:5
传出的函数的名字: cs2
进程已结束,退出代码 0
这里以一个简单的socket客户端与服务端举例,模拟服务端故障没有打开,而客户端连接失败时,在装饰器作用下客户端自动重连
以下代码参考视频27.58分说明
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# -*-author:Chenhui
from socket import *
import time
import functools
def retry_and_show(exception: Exception, tries: int = 10, delay: int = 1, backoff: int = 2):
"""
:param exception: 错误;类型
:param tries: 重试次数
:param delay: 间隔重试时间间隔 秒
:param backoff: 间隔延时持续增加步长
:return:
"""
def decorator_retry(func):
@functools.wraps(func)
def run_with_retry_police(*args,**kwargs):
_tries, _delay = tries,delay
total_time = 1
while _tries > 1:
try:
return func(*args,**kwargs)
except exception:
time.sleep(_delay)
total_time = total_time + _delay
print("程序错误" + str(_delay) + "秒后自动重新尝试,剩余尝试次数:"
+ str(_tries) + "次, 增加等待时间为:" + str(_delay)
+ "秒, 已尝试用时:" + str(total_time) + "秒")
_tries -= 1
_delay += backoff
return func(* args, ** kwargs)
return run_with_retry_police
return decorator_retry
@retry_and_show(ConnectionRefusedError) # ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
def connect_server():
"""自定义连接服务端,并启动一个循环开启处理逻辑"""
IP = '127.0.0.1'
SERVER_PORT = 6666
BUFLEN = 512
# 实例化一个sokcet对象,指明协议
dataSocket = socket(AF_INET, SOCK_STREAM)
# 连接服务端socket
dataSocket.connect((IP, SERVER_PORT))
while True:
# 从终端读入用户输入的字符串
toSend = input('成功连接至socket服务端,请输入发送数据\n>>> ')
if toSend == 'exit':
break
dataSocket.send(toSend.encode())
recved = dataSocket.recv(BUFLEN)
# 如果返回空bytes,表示对方关闭了连接
if not recved:
break
# 打印读取的信息
print(recved.decode())
#dataSocket.close()
if __name__ == '__main__':
connect_server()
输出
程序错误1秒后自动重新尝试,剩余尝试次数:10次, 增加等待时间为:1秒, 已尝试用时:2秒
程序错误3秒后自动重新尝试,剩余尝试次数:9次, 增加等待时间为:3秒, 已尝试用时:5秒
程序错误5秒后自动重新尝试,剩余尝试次数:8次, 增加等待时间为:5秒, 已尝试用时:10秒
程序错误7秒后自动重新尝试,剩余尝试次数:7次, 增加等待时间为:7秒, 已尝试用时:17秒
程序错误9秒后自动重新尝试,剩余尝试次数:6次, 增加等待时间为:9秒, 已尝试用时:26秒
程序错误11秒后自动重新尝试,剩余尝试次数:5次, 增加等待时间为:11秒, 已尝试用时:37秒
程序错误13秒后自动重新尝试,剩余尝试次数:4次, 增加等待时间为:13秒, 已尝试用时:50秒
程序错误15秒后自动重新尝试,剩余尝试次数:3次, 增加等待时间为:15秒, 已尝试用时:65秒
程序错误17秒后自动重新尝试,剩余尝试次数:2次, 增加等待时间为:17秒, 已尝试用时:82秒
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# -*-author:Chenhui
"""这个服务器只做一件事,就是将接受到的消息原封不动的发送回去。"""
# 导入socket库
#import socket
from socket import *
# 主机地址为0.0.0.0,表示绑定本机所有网络接口IP地址
# 等待客户端来连接
IP='127.0.0.1'
#端口号
PORT=6666
#定义一次从socket缓冲区最多读入512个字节
BUFLEN=512
# 实例化一个socket对象
# 调用socket中的socket()来创建一个listenSocket,主要用户进行监听
# socket()有两个参数
# 第一个参数AF_INET表示socket网络层使用IP协议
# 第二参数SOCK_STREAM表示该socket传输层使用tcp协议
listenSocket = socket(AF_INET,SOCK_STREAM)
# bind()函数将我们创建的这个socket关联到我们主机的某一个网卡(网络接口,network interface)和端口上
# socket绑定地址和端口
listenSocket.bind((IP,PORT))
# 使socket处于监听状态,等待客户端的连接请求
# 参数5表示最多接受多少个等待连接的客户端
listenSocket.listen(5)
print(f'服务器端启动成功,在{PORT}端口等客户端的连接.....')
dataSocket,addr=listenSocket.accept()
print('接受一个客户端连接:',addr)
while True:
# 尝试读取对方发送的消息
# BUFFEN指定从接受收缓存里最多读取多少字节
recved = dataSocket.recv(BUFLEN)
#如果返回空bytes,表示对方关闭了连接
#退出循环,结束消息收发
if not recved:
break
# 读取的字节数据是bytes 类型,需要解码为字符串
info = recved.decode()
print(f'收到对方信息: {info}')
# 发送的数据类型必须为bytes,所以要编码
dataSocket.send(f'服务端收到了信息 {info}'.encode())
#服务端也调用close(),关闭socket
#dataSocket.close()
#listenSocket.close()