Go中的Python string.format的等价物?

时间:2016-11-25 19:10:31

标签: python string go

在Python中,你可以这样做:

"File {file} had error {error}".format(file=myfile, error=err)

或者这个:

"File %(file)s had error %(error)s" % {"file": myfile, "error": err}

在Go中,最简单的选项是:

fmt.Sprintf("File %s had error %s", myfile, err)

,它不允许您交换格式字符串中的参数顺序,您需要为I18N执行此操作。 Go 包含template包,其中包含以下内容:

package main

import (
    "bytes"
    "text/template"
    "os"
)

func main() {
    type Params struct {
        File string
        Error string
    }

    var msg bytes.Buffer

    params := &Params{
        File: "abc",
        Error: "def",
    }

    tmpl, _ := template.New("errmsg").Parse("File {{.File}} has error {{.Error}}")
    tmpl.Execute(&msg, params)
    msg.WriteTo(os.Stdout)
}

这似乎是一个很长的路要走错误信息。是否有更合理的选项允许我提供独立于订单的字符串参数?

8 个答案:

答案 0 :(得分:29)

使用strings.Replacer

使用strings.Replacer,实现您想要的格式化程序非常简单紧凑。

func main() {
    file, err := "/data/test.txt", "file not found"

    log("File {file} had error {error}", "{file}", file, "{error}", err)
}

func log(format string, args ...string) {
    r := strings.NewReplacer(args...)
    fmt.Println(r.Replace(format))
}

输出(在Go Playground上尝试):

File /data/test.txt had error file not found

我们可以通过在log()函数中自动添加括号到参数名称来使用起来更加愉快:

func main() {
    file, err := "/data/test.txt", "file not found"

    log2("File {file} had error {error}", "file", file, "error", err)
}

func log2(format string, args ...string) {
    for i, v := range args {
        if i%2 == 0 {
            args[i] = "{" + v + "}"
        }
    }
    r := strings.NewReplacer(args...)
    fmt.Println(r.Replace(format))
}

输出(在Go Playground上尝试):

File /data/test.txt had error file not found

是的,你可以说这只接受string参数值。这是真的。稍微改善一点,这是不正确的:

func main() {
    file, err := "/data/test.txt", 666

    log3("File {file} had error {error}", "file", file, "error", err)
}

func log3(format string, args ...interface{}) {
    args2 := make([]string, len(args))
    for i, v := range args {
        if i%2 == 0 {
            args2[i] = fmt.Sprintf("{%v}", v)
        } else {
            args2[i] = fmt.Sprint(v)
        }
    }
    r := strings.NewReplacer(args2...)
    fmt.Println(r.Replace(format))
}

输出(在Go Playground上尝试):

File /data/test.txt had error 666

此变体接受params为map[string]interface{}并将结果作为string返回:

type P map[string]interface{}

func main() {
    file, err := "/data/test.txt", 666

    s := log33("File {file} had error {error}", P{"file": file, "error": err})
    fmt.Println(s)
}

func log33(format string, p P) string {
    args, i := make([]string, len(p)*2), 0
    for k, v := range p {
        args[i] = "{" + k + "}"
        args[i+1] = fmt.Sprint(v)
        i += 2
    }
    return strings.NewReplacer(args...).Replace(format)
}

Go Playground上尝试。

使用text/template

您的模板解决方案或提案也过于冗长。它可以写成紧凑的(省略错误检查):

type P map[string]interface{}

func main() {
    file, err := "/data/test.txt", 666

    log4("File {{.file}} has error {{.error}}", P{"file": file, "error": err})
}

func log4(format string, p P) {
    t := template.Must(template.New("").Parse(format))
    t.Execute(os.Stdout, p)
}

输出(在Go Playground上尝试):

File /data/test.txt has error 666

如果您想要返回string(而不是将其打印到标准输出),您可以这样做(在Go Playground上尝试):

func log5(format string, p P) string {
    b := &bytes.Buffer{}
    template.Must(template.New("").Parse(format)).Execute(b, p)
    return b.String()
}

使用显式参数索引

在另一个答案中已经提到过,但要完成它,要知道相同的显式参数索引可以使用任意次数,从而导致多次替换相同的参数。请在此问题中详细了解此问题:Replace all variables in Sprintf with same variable

答案 1 :(得分:13)

我不知道命名参数的简单方法,但您可以使用显式参数索引轻松更改参数的顺序:

来自docs

  

在Printf,Sprintf和Fprintf中,默认行为是每个格式化动词格式化调用中传递的连续参数。但是,紧接在动词之前的符号[n]表示要格式化第n个单索引参数。 ' *'之前的相同符号对于宽度或精度,选择保存该值的参数索引。在处理括号内的表达式[n]之后,除非另有指示,否则后续动词将使用参数n + 1,n + 2等。

然后你可以,即:

fmt.Printf("File %[2]s had error %[1]s", err, myfile)

答案 2 :(得分:3)

该参数也可以是一个映射,因此如果您不介意每次使用它时解析每个错误格式,以下函数都可以使用:

package main

import (
    "bytes"
    "text/template"
    "fmt"
)

func msg(fmt string, args map[string]interface{}) (str string) {
    var msg bytes.Buffer

    tmpl, err := template.New("errmsg").Parse(fmt)

    if err != nil {
        return fmt
    }

    tmpl.Execute(&msg, args)
    return msg.String()
}

func main() {
    fmt.Printf(msg("File {{.File}} has error {{.Error}}\n", map[string]interface{} {
        "File": "abc",
        "Error": "def",
    }))
}

它仍然比我想要的有点啰嗦,但我认为它比其他一些选项更好。您可以将map[string]interface{}转换为本地类型,然后将其进一步缩小为:

type P map[string]interface{}

fmt.Printf(msg("File {{.File}} has error {{.Error}}\n", P{
        "File": "abc",
        "Error": "def",
    }))

答案 3 :(得分:1)

唉,Go中没有内置函数用于指定参数的字符串插值(尚未)。但是你并不是唯一受苦的人:)有些软件包应该存在,例如:https://github.com/imkira/go-interpol。或者,如果你喜欢冒险,你可以自己写一个这样的帮手,因为这个概念实际上非常简单。

干杯, 丹尼斯

答案 4 :(得分:0)

您可以非常接近这种甜美的 python格式化体验

message := FormatString("File {file} had error {error}", Items{"file"=myfile, "error"=err})

在代码中的某处声明以下内容:

type Items map[string]interface{}

func FormatString(template string, items Items) string {
    for key, value := range items {
        template = strings.ReplaceAll(template, fmt.Sprintf("{%v}", key), fmt.Sprintf("%v", value))
    }
    return template
}
  • ?请注意,我的实现非常幼稚且效率低下,无法满足高性能需求

sudo给我一个包裹

看到具有这种简单签名的开发经验的潜力,我很想吸引并上传了一个名为format的go包。

package main

import (
  "fmt"
  "github.com/jossef/format"
)

func main() {
  formattedString := format.String(`hello "{name}". is lizard? {isLizard}`, format.Items{"name": "Mr Dude", "isLizard": false})
  fmt.Println(formattedString)
}

stm32l011_it.c file

答案 5 :(得分:0)

您可以尝试使用Go Formatter库,该库实现替换字段,并用大括号dependencies { modules { module("my.artifact.group:bar") { replacedBy(project(":bar-project:bar"), "using subproject-dependencies for now") } } } 格式的字符串包围,类似于Python格式。

工作代码示例Go Playground

{}

输出:

package main

import (
    "fmt"

    "gitlab.com/tymonx/go-formatter/formatter"
)

func main() {
    formatted, err := formatter.Format("Named placeholders {file}:{line}:{function}():", formatter.Named{
        "line":     3,
        "function": "func1",
        "file":     "dir/file",
    })

    if err != nil {
        panic(err)
    }

    fmt.Println(formatted)
}

答案 6 :(得分:0)

text/template 很有趣。我在下面提供一些例子

用法

echo "Please enter the start date in dd/Mmm/yyyy format: "
read x
echo "Please enter the end date in dd/Mmm/yyyy format: "
read y
cat access.log | grep -E 'x|y' | awk '{print $1}' | sort -n | uniq -c | sort -nr 

图书馆

func TestFString(t *testing.T) {
    // Example 1
    fs := &FString{}
    fs.MustCompile(`Name: {{.Name}} Msg: {{.Msg}}`, nil)
    fs.MustRender(map[string]interface{}{
        "Name": "Carson",
        "Msg":  123,
    })
    assert.Equal(t, "Name: Carson Msg: 123", fs.Data)
    fs.Clear()

    // Example 2 (with FuncMap)
    funcMap := template.FuncMap{
        "largest": func(slice []float32) float32 {
            if len(slice) == 0 {
                panic(errors.New("empty slice"))
            }
            max := slice[0]
            for _, val := range slice[1:] {
                if val > max {
                    max = val
                }
            }
            return max
        },
        "sayHello": func() string {
            return "Hello"
        },
    }
    fs.MustCompile("{{- if gt .Age 80 -}} Old {{else}} Young {{- end -}}"+ // "-" is for remove empty space
        "{{ sayHello }} {{largest .Numbers}}", // Use the function which you created.
        funcMap)
    fs.MustRender(Context{
        "Age":     90,
        "Numbers": []float32{3, 9, 13.9, 2.1, 7},
    })
    assert.Equal(t, "Old Hello 13.9", fs.Data)
}

Go Playground

重要文件

答案 7 :(得分:0)

不是使用 template.New,您必须在其中提供模板名称,而是 可以实例化一个模板指针:

package main

import (
   "strings"
   "text/template"
)

func format(s string, v interface{}) string {
   t, b := new(template.Template), new(strings.Builder)
   template.Must(t.Parse(s)).Execute(b, v)
   return b.String()
}

func main() {
   params := struct{File, Error string}{"abc", "def"}
   println(format("File {{.File}} has error {{.Error}}", params))
}