python @ 装饰器(修饰器,语法糖)使用与不使用对比,应用记录

由于看python代码里面函数上的@ 不爽很久了,为了避免自己又忘记了这里来记录下。
简单总结:
@ 的作用就是在使用 @ 下面的函数(如下图的cs2)的时候,会在该函数执行前将该函数作为参数扔到@后跟着的处理函数先行处理(或预备处理)些东西,如下图的 time_cs 就是本文举的封装一个计时函数的装饰器。

阅读下面代码对比使用语法糖的使用,即可了解。
本篇博客自参考b站大佬up的视频
python @ 装饰器(修饰器,语法糖)使用与不使用对比,应用记录_第1张图片
python @ 装饰器(修饰器,语法糖)使用与不使用对比,应用记录_第2张图片

一.预备的python常识

1.python中函数加括号与不加返回的区别

区分a()和a如下
一个是: 函数a在电脑中的十六进制地址
一个是: 函数a返回的值

def a():
    return 1

print(a) # 函数a在电脑中的十六进制地址
print(a()) # 函数a返回的值

输出

<function a at 0x000001E37E1C6DC8>
1

2.在python中函数可以作为参数直接传入


def a():
    return "a的返回"

def b():
    return "b的返回"

def out(action):
    """传入函数对象"""
    print(action())

out(a)
out(b)

输出

a的返回
b的返回

二. 计时函数的例子说明

1.1 不使用装饰器的写法简单-0(无输入输出)

即检测的目标函数没有输入值也没有返回值的情况

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

1.2 不使用装饰器的写法简单-1(只有输出)

即目标函数拥有返回值的情况
这里比较麻烦需要在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

1.3.不使用装饰器的写法简单-2(有输入有输出)

即目标函数拥有有输入有输出返回值的情况,

1.3.1失败版

首先展示一个能运行但是,无法成功获得目标函数运行时间的写法,这里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

1.3.1成功版

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

1.3.不使用装饰器的写法简单-4(引入返回函数)

(目标函数拥有输入有输出)
这里将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

特别的加上 @functools.wraps(func) 以保证传输的函数名字不变

如下

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

2.使用装饰器的写法(有输入输出)

能够达到以上的效果最接近上面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

三.装饰器其他使用应用记录

1.自动重启调用

这里以一个简单的socket客户端与服务端举例,模拟服务端故障没有打开,而客户端连接失败时,在装饰器作用下客户端自动重连
以下代码参考视频27.58分说明

(1)简单socket客户端(带@重连)

#!/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秒

(2)简单服务端

#!/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()


你可能感兴趣的:(python小代码记录,python之坑,python,开发语言)