在Golang中实现与Python装饰器类似功能的方法

Python中的闭包与装饰器

  关于Python中的闭包与装饰器的知识笔者之前总结过一篇文章:Python装饰器的调用过程

  实际上,装饰器是Python中的的一个语法糖,使用@装饰器装饰的函数会将被装饰的函数作为参数传入装饰器函数中,然后在装饰器函数里面做一些统一的定制化的处理。

      也就是说,我们可以使用装饰器在被装饰函数执行之前或之后实现一些统一的自定制的逻辑。

  比如说,笔者在实际开发中重构代码时遇到了这样的问题:新函数与旧函数的入参数跟出参一模一样,只不过由于使用的工具的版本不同需要新函数中做一下特殊的处理,为了代码简洁笔者没有在调用旧函数的地方直接将旧的函数名修改成新函数名,而是做了一个装饰器装饰上层函数,使用反射的方式将旧函数替换为新函数,下面是实现的代码:

import sys

# 配置文件  老版本就不变了为True
is_old_flag = False


# 入参必须一样。。。
def no_print(msg):
    # 新逻辑
    print("no_print...")


def my_print(msg):
    # 旧逻辑
    print(">>> ", msg)


# 装饰器中动态修改函数
def wrapper(func):
    def inner(*args, **kwargs):
        if hasattr(sys.modules[__name__], "my_print"):
            if not is_old_flag:
                # setattr.....
                setattr(sys.modules[__name__], "my_print", no_print)
        func(*args, **kwargs)
    return inner


@wrapper
def t1():
    my_print("xxxxx")


t1()

  当然实际中我们可以选择各种方式实现自己的需求,笔者只是抛砖引玉大概说一下装饰器的用途。

在Golang中实现Python装饰器同样的效果

  Golang中也可以实现与装饰器同样的效果,简单的思路是我们可以把函数当作参数传入另外一个函数中,然后再在最外层的函数里面灵活的执行函数即可:

  最近写业务代码使用golang的xorm框架,在执行事务时会使用到Transaction方法,我们可以看一下它这个方法的实现:

package xorm

// Transaction Execute sql wrapped in a transaction(abbr as tx), tx will automatic commit if no errors occurred
func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interface{}, error) {
    
// 在f执行之前做一些处理
session :
= engine.NewSession() defer session.Close() if err := session.Begin(); err != nil { return nil, err } result, err := f(session) // 这里执行f if err != nil { return nil, err }
// 在f执行之后做一些处理
if err := session.Commit(); err != nil { return nil, err } return result, nil }

更简单的例子

  举一个更简单的例子,比如说如果我们想在函数执行之前打印一个“start...”,在函数执行完后打印一个"end...",可以这样写:

package test1

import (
    "fmt"
    "testing"
)

// 自己实现一个low版的  int -> str
func myService(ins int) (string, error) {
    retStr := fmt.Sprintf("int->str: %d", ins)
    return retStr, nil
}

// 接收一个函数作为参数的写法
func myTss(f func(i int) (string, error), ins int) error {

    fmt.Println("start...")

    strRet, err := f(ins)
    if err != nil {
        return err
    }
    fmt.Println("函数执行成功,结果是:", strRet)

    fmt.Println("end...")
    return nil
}

func TestClosure(t *testing.T) {
    if err := myTss(myService, 123); err != nil {
        fmt.Println("err: ", err)
    }
}

  结果如下:

  虽然代码很简单,而且将一个函数作为参数传入另外一个函数中的做法实际中不常用,但是这也算是一种技术积累把。

原文地址:https://www.cnblogs.com/paulwhw/p/14682097.html