「Python小脚本」基于装饰器的函数日志脚本
off999 2024-10-07 12:18 27 浏览 0 评论
写在前面
- 有个简单的小需求,选择用pythoh实现
- 有些打印方法业务日志,参数,执行时间的语句感觉有些冗余
- 所以想用类似AOP的方式实现
- 利用python里闭包函数实现的装饰器及提供的语法糖可以简单实现。
- 博文内容包括两部分: Python闭包&装饰器,装饰器设计模式简述基于Python装饰器的函数日志模块实现:日志提供函数执行时间,入参,函数业务信息的采集日志位置支持函数前,函数最终,函数异常时,环绕采集四种方式
- 理解错误的地方请小伙伴批评指正
「 我只是怕某天死了,我的生命却一无所有。----《奇幻之旅》」
理论准备
在介绍脚本前,我们简单介绍下用到的知识点
闭包
在一般的编程语言中,比如Java,C,C++,C#,Golang中,我们知道一个函数调用完,函数内定义的变量都销毁了,有时候需要保存函数内的这些变量,在这些变量的基础上完成一些操作。我们只能通过返回值的方式来处理
在一些解释型的语言中,比如JS,Python等,我们可以通过函数嵌套的方式,可以获取函数内部的一些变量信息。这个行为,我们称为闭包 JavaScript中的使用
// 定义一个外部函数
function outer(num1){
let name = 'liruilong'
// 定义一个内部函数
function inner(num2){
// 内部函数使用了外部函数的变量(num1)
console.log(num1+num2)
}
// 外部函数返回了内部函数,这里返回的内部函数就是闭包
return inner()
}
f = outer(1)
f(2)
Python中的使用
def func_out(num1):
def func_inner(num2):
# 内部函数使用了外部函数的变量(num1)
result = num1 + num2
print("结果是:", num1 + num2)
# 外部函数返回了内部函数,这里返回的内部函数就是闭包
return func_inner
# 创建闭包实例
f = func_out(1)
# 执行闭包
f(2)
f(3)
「闭包的定义:在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。」
闭包的构成条件
通过闭包的定义,我们可以得知闭包的形成条件:
- 在函数嵌套(函数里面再定义函数)的前提下
- 内部函数使用了外部函数的变量(还包括外部函数的参数)
- 外部函数返回了内部函数
闭包的作用
闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。同时,由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。
闭包的使用
- 闭包可以提高代码的可重用性,不需要再手动定义额外的功能函数。
- 闭包可以实现python装饰器,关于装饰器简单讲就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。,当然python也可以实现基于类的装饰器
装饰器的功能特点:
- 不修改已有函数的源代码
- 不修改已有函数的调用方式
- 给已有函数增加额外的功能
- 闭包函数有且只有一个参数,必须是函数类型,这样定义的函数才是装饰器。
为什么叫装饰器,这里我们简单讲讲面向对象中对象结构型设计模式装饰器设计模式,以及六大面向对象设计原则之一开闭原则(Open Close Principle)
关于装饰器设计模式的定义:即动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。遵循开闭原则,对扩展开放,对修改关闭。
关于装饰器设计模式的优点和缺点,GOF中这样描述:
优点
- 「比静态继承更灵活,与对象的静态继承(多重继承)相比, Decorator模式提供了更加灵活的向对象添加职责的方式」。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类(例如, BorderscrollableTextView, BorderedTextView ),这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的Decorator类,这就使得你可以对一些职责进行混合和匹配。使用Decorator模式可以很容易地重复添加一个特性,例如在TextView上添加双边框时,仅需将添加两个BorderDecorator即可。而两次继承Border类则极容易出错的.
- 「避免在层次结构高层的类有太多的特征, Decorator模式提供了一种“即用即付”的方法来添加职责」 。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。这样,应用程序不必为不需要的特征付出代价。同时也更易于不依赖于Decorator扩展(甚至是不可预知的扩展)的类而独立地定义新类型的Decorator。扩展一个复杂类的时候,很可能会暴露与添加的职责无关的细节。
缺点
- Decorator与Component不一样, Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识。
- 有许多小对象采用Decorator模式进行系统设计,往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。简单的讲,就是装饰器多了,容易混乱。
装饰器
Python装饰器的语法糖
Python给提供了一个装饰函数更加简单的写法,语法糖的书写格式是: @装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰.
def check(fn):
print("装饰器函数")
def inner():
print("run....")
fn()
return inner
# 使用语法糖方式来装饰函数
@check
def comment():
print("函数执行suss")
# 运行
comment()
装饰器的场景
- 实现函数执行时间的统计
- 实现函数输出日志的功能
装饰带有不定长参数的函数
# 添加输出日志的功能
def logging(fn):
def inner(*args, **kwargs):
print("--正在努力计算--")
fn(*args, **kwargs)
return inner
# 使用语法糖装饰函数
@logging
def sum_num(*args, **kwargs):
result = 0
for value in args:
result += value
for value in kwargs.values():
result += value
print(result)
sum_num(1, 2, a=10)
========================
>--正在努力计算--
13
多个装饰器的使用
多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程
def make_div(func):
"""对被装饰的函数的返回值 div标签"""
def inner(*args, **kwargs):
return "<div>" + func() + "</div>"
return inner
def make_p(func):
"""对被装饰的函数的返回值 p标签"""
def inner(*args, **kwargs):
return "<p>" + func() + "</p>"
return inner
# 装饰过程:
# 1 content = make_p(content)
# 2 content = make_div(content)
# content = make_div(make_p(content))
@make_div
@make_p
def content():
return "人生苦短"
print(content())
============
<div><p>人生苦短</p></div>
带有参数的装饰器
带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...)
# 添加输出日志的功能
def logging(flag):
def decorator(fn):
def inner(num1, num2):
if flag == "+":
print("--正在努力加法计算--")
elif flag == "-":
print("--正在努力减法计算--")
result = fn(num1, num2)
return result
return inner
# 返回装饰器
return decorator
# 使用装饰器装饰函数
@logging("+")
def add(a, b):
result = a + b
return result
@logging("-")
def sub(a, b):
result = a - b
return result
print(add(1, 2))
print(sub(1, 2))
类装饰器的使用
装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。
class Check(object):
def __init__(self, fn):
# 初始化操作在此完成
self.__fn = fn
# 实现__call__方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。
def __call__(self, *args, **kwargs):
# 添加装饰功能
print("请先登陆...")
self.__fn()
@Check
def comment():
print("发表评论")
comment()
==============
请先登陆...
发表评论
- @Check 等价于 comment = Check(comment), 所以需要提供一个init方法,并多增加一个fn参数。
- 要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。``在call方法里进行对fn函数的装饰,可以添加额外的功能。
具体的脚本
基于装饰器函数日志脚本
讲了这么多,我们来看看,如何在用装饰器实现函数的日志
这里需要注意一下@functools.wraps(func)这个装饰器,一般函数被装饰器装饰完之后,被装饰的函数的名字会变成装饰器函数,通过该装饰器,我们可以打印实际的函数名。
log_decorator.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : log_decorator.py
@Time : 2022/03/22 10:24:51
@Author : Li Ruilong
@Version : 1.0
@Contact : 1224965096@qq.com
@Desc : 方法日志装饰类
"""
# here put the import lib
import functools
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s: %(message)s')
def method_before(message="before message default"):
"""
@Time : 2022/03/22 11:01:46
@Author : Li Ruilong
@Version : 1.0
@Desc : 前置日志:方法执行前输出的日志
"""
def method_logging(func):
# 用于获取原来的函数名
@functools.wraps(func)
def wrapper(*args, **kw):
logging.info('[method] : [{}] , [param] : [{}],[message] : [{}],'.format(
func.__name__,args, message))
return func(*args, **kw)
return wrapper
return method_logging
def method_after(message="after message default"):
"""
@Time : 2022/03/22 16:01:21
@Author : Li Ruilong
@Version : 1.0
@Desc : 最终日志:不管方法是否执行成功,执行后都会输出的日志
"""
def method_logging(func):
@functools.wraps(func)
def wrapper(*args, **kw):
start = time.time()
try:
return func(*args, **kw)
finally:
logging.info('[method] : [{}] , [cost] : {:.1f}s, [param] : [{}],[message] : [{}],'.format(
func.__name__, time.time() - start, args, message))
return wrapper
return method_logging
def method_around(before="Before message default", afterReturning="AfterReturning message default"):
"""
@Time : 2022/03/22 11:09:24
@Author : Li Ruilong
@Version : 1.0
@Desc : 环绕日志:方法执行前后输出的日志
"""
def method_logging(func):
@functools.wraps(func)
def wrapper(*args, **kw):
start = time.time()
try:
logging.info('[method] : [{}] , [param] : [{}],[message] : [{}]'.format(
func.__name__, args, before))
return func(*args, **kw)
except Exception as e:
logging.error(e)
finally:
logging.info('[method] : [{}] , [cost] : {:.1f}s,[message] : [{}]'.format(
func.__name__, time.time() - start, afterReturning))
return wrapper
return method_logging
def method_after_throwing(message="After-Throwing message default"):
"""
@Time : 2022/03/22 11:37:56
@Author : Li Ruilong
@Version : 1.0
@Desc : 异常日志,方法执行异常后输出的日志
"""
def method_logging(func):
@functools.wraps(func)
def wrapper(*args, **kw):
start = time.time()
try:
return func(*args, **kw)
except Exception as e:
logging.error('[method] : [{}] , [cost] : {:.1f}s, [param] : [{}],[message] : [{}],,except[{}]'.format(
func.__name__, time.time() - start, args, message, e))
return wrapper
return method_logging
简单测试一下
@method_before("前置内容")
def __method_before_test(a='www', b=1, c=[1, 2]):
time.sleep(2)
print( "前置函数")
@method_around("前置内容", "后置内容")
def __method_around_test(a='www', b=1, c=[1, 2]):
time.sleep(3)
print( "环绕函数")
@method_after_throwing("异常日志内容")
def __method_after_throwing_test(a='www', b=1, c=[1, 2]):
time.sleep(3)
print( "异常函数")
raise
if __name__ == "__main__":
print(__method_before_test(1, 'hello', c=[5, 6]))
print(__method_around_test(1, 'hello', c=[5, 6]))
print(__method_after_throwing_test(1, 'hello', c=[5, 6]))
==============================
2022-04-01 15:00:09,888 - INFO: [method] : [__method_before_test] , [param] : [(1, 'hello')],[message] : [前置内容],
前置函数
2022-04-01 15:00:11,891 - INFO: [method] : [__method_around_test] , [param] : [(1, 'hello')],[message] : [前置内容]
环绕函数
2022-04-01 15:00:14,894 - INFO: [method] : [__method_around_test] , [cost] : 3.0s,[message] : [后置内容]
异常函数
2022-04-01 15:00:17,898 - ERROR: [method] : [__method_after_throwing_test] , [cost] : 3.0s, [param] : [(1, 'hello')],[message] : [异常日志内容],,except[No active exception to reraise]
脚本之外使用
.....
import log_decorator as log
....
@log.method_around("开始加载配置文件", "配置文件加载完成")
def __init__(self, file_name="config.yaml"):
config_temp = None
try:
# 获取当前脚本所在文件夹路径
cur_path = os.path.dirname(os.path.realpath(__file__))
# 获取yaml文件路径
yaml_path = os.path.join(cur_path, file_name)
f = open(yaml_path, 'r', encoding='utf-8')
config_temp = f.read()
except Exception as e:
logging.info("配置文件加载失败", e)
finally:
f.close()
self._config = yaml.safe_load(config_temp) # 用load方法转化
========================
2022-04-01 19:16:53,175 - INFO: [method] : [__init__] , [param] : [(<__main__.Yaml object at 0x01482118>,)],[message] : [开始加载配置文件]
2022-04-01 19:16:53,184 - INFO: [method] : [__init__] , [cost] : 0.0s,[message] : [配置文件加载完成]
相关推荐
- 阿里云国际站ECS:阿里云ECS如何提高网站的访问速度?
-
TG:@yunlaoda360引言:速度即体验,速度即业务在当今数字化的世界中,网站的访问速度已成为决定用户体验、用户留存乃至业务转化率的关键因素。页面加载每延迟一秒,都可能导致用户流失和收入损失。对...
- 高流量大并发Linux TCP性能调优_linux 高并发网络编程
-
其实主要是手里面的跑openvpn服务器。因为并没有明文禁p2p(哎……想想那么多流量好像不跑点p2p也跑不完),所以造成有的时候如果有比较多人跑BT的话,会造成VPN速度急剧下降。本文所面对的情况为...
- 性能测试100集(12)性能指标资源使用率
-
在性能测试中,资源使用率是评估系统硬件效率的关键指标,主要包括以下四类:#性能测试##性能压测策略##软件测试#1.CPU使用率定义:CPU处理任务的时间占比,计算公式为1-空闲时间/总...
- Linux 服务器常见的性能调优_linux高性能服务端编程
-
一、Linux服务器性能调优第一步——先搞懂“看什么”很多人刚接触Linux性能调优时,总想着直接改配置,其实第一步该是“看清楚问题”。就像医生看病要先听诊,调优前得先知道服务器“哪里...
- Nginx性能优化实战:手把手教你提升10倍性能!
-
关注△mikechen△,十余年BAT架构经验倾囊相授!Nginx是大型架构而核心,下面我重点详解Nginx性能@mikechen文章来源:mikechen.cc1.worker_processe...
- 高并发场景下,Spring Cloud Gateway如何抗住百万QPS?
-
关注△mikechen△,十余年BAT架构经验倾囊相授!大家好,我是mikechen。高并发场景下网关作为流量的入口非常重要,下面我重点详解SpringCloudGateway如何抗住百万性能@m...
- Kubernetes 高并发处理实战(可落地案例 + 源码)
-
目标场景:对外提供HTTPAPI的微服务在短时间内收到大量请求(例如每秒数千至数万RPS),要求系统可弹性扩容、限流降级、缓存减压、稳定运行并能自动恢复。总体思路(多层防护):边缘层:云LB...
- 高并发场景下,Nginx如何扛住千万级请求?
-
Nginx是大型架构的必备中间件,下面我重点详解Nginx如何实现高并发@mikechen文章来源:mikechen.cc事件驱动模型Nginx采用事件驱动模型,这是Nginx高并发性能的基石。传统...
- Spring Boot+Vue全栈开发实战,中文版高清PDF资源
-
SpringBoot+Vue全栈开发实战,中文高清PDF资源,需要的可以私我:)SpringBoot致力于简化开发配置并为企业级开发提供一系列非业务性功能,而Vue则采用数据驱动视图的方式将程序...
- Docker-基础操作_docker基础实战教程二
-
一、镜像1、从仓库获取镜像搜索镜像:dockersearchimage_name搜索结果过滤:是否官方:dockersearch--filter="is-offical=true...
- 你有空吗?跟我一起搭个服务器好不好?
-
来人人都是产品经理【起点学院】,BAT实战派产品总监手把手系统带你学产品、学运营。昨天闲的没事的时候,随手翻了翻写过的文章,发现一个很严重的问题。就是大多数时间我都在滔滔不绝的讲理论,却很少有涉及动手...
- 部署你自己的 SaaS_saas如何部署
-
部署你自己的VPNOpenVPN——功能齐全的开源VPN解决方案。(DigitalOcean教程)dockovpn.io—无状态OpenVPNdockerized服务器,不需要持久存储。...
- Docker Compose_dockercompose安装
-
DockerCompose概述DockerCompose是一个用来定义和管理多容器应用的工具,通过一个docker-compose.yml文件,用YAML格式描述服务、网络、卷等内容,...
- 京东T7架构师推出的电子版SpringBoot,从构建小系统到架构大系统
-
前言:Java的各种开发框架发展了很多年,影响了一代又一代的程序员,现在无论是程序员,还是架构师,使用这些开发框架都面临着两方面的挑战。一方面是要快速开发出系统,这就要求使用的开发框架尽量简单,无论...
- Kubernetes (k8s) 入门学习指南_k8s kubeproxy
-
Kubernetes(k8s)入门学习指南一、什么是Kubernetes?为什么需要它?Kubernetes(k8s)是一个开源的容器编排系统,用于自动化部署、扩展和管理容器化应用程序。它...
欢迎 你 发表评论:
- 一周热门
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
