百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

「Python小脚本」基于装饰器的函数日志脚本

off999 2024-10-07 12:18 43 浏览 0 评论

写在前面

  • 有个简单的小需求,选择用pythoh实现
  • 有些打印方法业务日志,参数执行时间的语句感觉有些冗余
  • 所以想用类似AOP的方式实现
  • 利用python闭包函数实现的装饰器及提供的语法糖可以简单实现。
  • 博文内容包括两部分: Python闭包&装饰器装饰器设计模式简述基于Python装饰器函数日志模块实现:日志提供函数执行时间入参,函数业务信息的采集日志位置支持函数前函数最终函数异常时环绕采集四种方式
  • 理解错误的地方请小伙伴批评指正

「 我只是怕某天死了,我的生命却一无所有。----《奇幻之旅》」


理论准备

在介绍脚本前,我们简单介绍下用到的知识点

闭包

在一般的编程语言中,比如Java,C,C++,C#,Golang中,我们知道一个函数调用完,函数内定义的变量都销毁了,有时候需要保存函数内的这些变量,在这些变量的基础上完成一些操作。我们只能通过返回值的方式来处理

在一些解释型的语言中,比如JSPython等,我们可以通过函数嵌套的方式,可以获取函数内部的一些变量信息。这个行为,我们称为闭包 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中这样描述:

优点

  1. 「比静态继承更灵活,与对象的静态继承(多重继承)相比, Decorator模式提供了更加灵活的向对象添加职责的方式」。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类(例如, BorderscrollableTextView, BorderedTextView ),这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的Decorator类,这就使得你可以对一些职责进行混合和匹配。使用Decorator模式可以很容易地重复添加一个特性,例如在TextView上添加双边框时,仅需将添加两个BorderDecorator即可。而两次继承Border类则极容易出错的.
  2. 「避免在层次结构高层的类有太多的特征, Decorator模式提供了一种“即用即付”的方法来添加职责」 。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。这样,应用程序不必为不需要的特征付出代价。同时也更易于不依赖于Decorator扩展(甚至是不可预知的扩展)的类而独立地定义新类型的Decorator。扩展一个复杂类的时候,很可能会暴露与添加的职责无关的细节。

缺点

  1. DecoratorComponent不一样, Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识
  2. 有许多小对象采用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] : [配置文件加载完成]        

相关推荐

安全教育登录入口平台(安全教育登录入口平台官网)

122交通安全教育怎么登录:122交通网的注册方法是首先登录网址http://www.122.cn/,接着打开网页后,点击右上角的“个人登录”;其次进入邮箱注册,然后进入到注册页面,输入相关信息即可完...

大鱼吃小鱼经典版(大鱼吃小鱼经典版(经典版)官方版)

大鱼吃小鱼小鱼吃虾是于谦跟郭麒麟的《我的棒儿呢?》郭德纲说于思洋郭麒麟作诗的相声,最后郭麒麟做了一首,师傅躺在师母身上大鱼吃小鱼小鱼吃虾虾吃水水落石出师傅压师娘师娘压床床压地地动山摇。...

谷歌地球下载高清卫星地图(谷歌地球地图下载器)
  • 谷歌地球下载高清卫星地图(谷歌地球地图下载器)
  • 谷歌地球下载高清卫星地图(谷歌地球地图下载器)
  • 谷歌地球下载高清卫星地图(谷歌地球地图下载器)
  • 谷歌地球下载高清卫星地图(谷歌地球地图下载器)
哪个软件可以免费pdf转ppt(免费的pdf转ppt软件哪个好)
哪个软件可以免费pdf转ppt(免费的pdf转ppt软件哪个好)

要想将ppt免费转换为pdf的话,我们建议大家可以下一个那个wps,如果你是会员的话,可以注册为会员,这样的话,在wps里面的话,就可以免费将ppt呢转换为pdfpdf之后呢,我们就可以直接使用,不需要去直接不需要去另外保存,为什么格式转...

2026-02-04 09:03 off999

电信宽带测速官网入口(电信宽带测速官网入口app)

这个网站看看http://www.swok.cn/pcindex.jsp1.登录中国电信网上营业厅,宽带光纤,贴心服务,宽带测速2.下载第三方软件,如360等。进行在线测速进行宽带测速时,尽...

植物大战僵尸95版手机下载(植物大战僵尸95 版下载)

1可以在应用商店或者游戏平台上下载植物大战僵尸95版手机游戏。2下载教程:打开应用商店或者游戏平台,搜索“植物大战僵尸95版”,找到游戏后点击下载按钮,等待下载完成即可安装并开始游戏。3注意:确...

免费下载ppt成品的网站(ppt成品免费下载的网站有哪些)

1、Chuangkit(chuangkit.com)直达地址:chuangkit.com2、Woodo幻灯片(woodo.cn)直达链接:woodo.cn3、OfficePlus(officeplu...

2025世界杯赛程表(2025世界杯在哪个国家)

2022年卡塔尔世界杯赛程公布,全部比赛在卡塔尔境内8座球场举行,2022年,决赛阶段球队全部确定。揭幕战于当地时间11月20日19时进行,由东道主卡塔尔对阵厄瓜多尔,决赛于当地时间12月18日...

下载搜狐视频电视剧(搜狐电视剧下载安装)

搜狐视频APP下载好的视频想要导出到手机相册里方法如下1、打开手机搜狐视频软件,进入搜狐视频后我们点击右上角的“查找”,找到自已喜欢的视频。2、在“浏览器页面搜索”窗口中,输入要下载的视频的名称,然后...

pubg免费下载入口(pubg下载入口官方正版)
  • pubg免费下载入口(pubg下载入口官方正版)
  • pubg免费下载入口(pubg下载入口官方正版)
  • pubg免费下载入口(pubg下载入口官方正版)
  • pubg免费下载入口(pubg下载入口官方正版)
永久免费听歌网站(丫丫音乐网)

可以到《我爱音乐网》《好听音乐网》《一听音乐网》《YYMP3音乐网》还可以到《九天音乐网》永久免费听歌软件有酷狗音乐和天猫精灵,以前要跳舞经常要下载舞曲,我从QQ上找不到舞曲下载就从酷狗音乐上找,大多...

音乐格式转换mp3软件(音乐格式转换器免费版)

有两种方法:方法一在手机上操作:1、进入手机中的文件管理。2、在其中选择“音乐”,将显示出手机中的全部音乐。3、点击“全选”,选中所有音乐文件。4、点击屏幕右下方的省略号图标,在弹出菜单中选择“...

电子书txt下载(免费的最全的小说阅读器)

1.Z-library里面收录了近千万本电子书籍,需求量大。2.苦瓜书盘没有广告,不需要账号注册,使用起来非常简单,直接搜索预览下载即可。3.鸠摩搜书整体风格简洁清晰,书籍资源丰富。4.亚马逊图书书籍...

最好免费观看高清电影(播放免费的最好看的电影)

在目前的网上选择中,IMDb(互联网电影数据库)被认为是最全的电影网站之一。这个网站提供了各种类型的电影和电视节目的海量信息,包括剧情介绍、演员表、评价、评论等。其还提供了有关电影制作背后的详细信息,...

孤单枪手2简体中文版(孤单枪手2简体中文版官方下载)

要将《孤胆枪手2》游戏的征兵秘籍切换为中文,您可以按照以下步骤进行操作:首先,打开游戏设置选项,通常可以在游戏主菜单或游戏内部找到。然后,寻找语言选项或界面选项,点击进入。在语言选项中,选择中文作为游...

取消回复欢迎 发表评论: