Equivalent of Python string.format in Go?

huangapple go评论79阅读模式
英文:

Equivalent of Python string.format in 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

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)
}

这似乎是一个冗长的方式来处理错误消息。是否有更合理的选项,可以让我独立于参数的顺序提供字符串参数?

英文:

In Python, you can do this:

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

or this:

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

In Go, the simplest option is:

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

which doesn't let you swap the order of the parameters in the format string, which you need to do for I18N. Go does have the template package, which would require something like:

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)
}

which seems like a long way to go for an error message. Is there a more reasonable option that allows me to give string parameters independent of order?

答案1

得分: 60

使用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))
}

输出结果:

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))
}

输出结果:

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))
}

输出结果:

File /data/test.txt had error 666

还有一种变体,可以接受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)
}

使用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)
}

输出结果:

File /data/test.txt has error 666

如果你想返回string(而不是将其打印到标准输出),可以像这样做:

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

使用显式的参数索引:

这已经在另一个答案中提到过,但为了完整起见,了解相同的显式参数索引可以任意多次使用,从而导致同一个参数多次替换。在这个问题中可以阅读更多相关信息:https://stackoverflow.com/questions/37001449/replace-all-variables-in-sprintf-with-same-variable

英文:

With strings.Replacer

Using strings.Replacer, implementing a formatter of your desire is very easy and compact.

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))
}

Output (try it on the Go Playground):

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

We can make it more pleasant to use by adding the brackets to the parameter names automatically in the log() function:

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))
}

Output (try it on the Go Playground):

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

Yes, you could say that this only accepts string parameter values. This is true. With a little more improvement, this won't be true:

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))
}

Output (try it on the Go Playground):

File /data/test.txt had error 666

A variant of this to accept params as a map[string]interface{} and return the result as a 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)
}

Try it on the Go Playground.

With text/template

Your template solution or proposal is also way too verbose. It can be written as compact as this (error checks omitted):

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)
}

Output (try it on the Go Playground):

File /data/test.txt has error 666

If you want to return the string (instead of printing it to the standard output), you may do it like this (try it on the Go Playground):

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

Using explicit argument indices

This was already mentioned in another answer, but to complete it, know that the same explicit argument index may be used arbitrary number of times and thus resulting in the same parameter substituted in multiple times. Read more about this in this question: https://stackoverflow.com/questions/37001449/replace-all-variables-in-sprintf-with-same-variable

答案2

得分: 26

我不知道有什么简单的方法来命名参数,但是你可以通过使用显式参数索引来轻松更改参数的顺序:

根据文档

在 Printf、Sprintf 和 Fprintf 中,默认行为是每个格式化动词按照调用中传递的参数顺序进行格式化。然而,在动词之前的 [n] 表示要格式化的第 n 个从一开始计数的参数。在宽度或精度之前的相同表示法选择保存值的参数索引。在处理了一个带括号的表达式 [n] 后,后续的动词将使用参数 n+1、n+2 等,除非另有指示。

然后你可以这样做:

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

I don't know of any easy way of naming the parameters, but you can easily change the order of the arguments, using explicit argument indexes:

From docs:

> In Printf, Sprintf, and Fprintf, the default behavior is for each formatting verb to format successive arguments passed in the call. However, the notation [n] immediately before the verb indicates that the nth one-indexed argument is to be formatted instead. The same notation before a '*' for a width or precision selects the argument index holding the value. After processing a bracketed expression [n], subsequent verbs will use arguments n+1, n+2, etc. unless otherwise directed.

Then you can, ie:

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

答案3

得分: 4

参数也可以是一个映射,所以如果你不介意每次使用时都解析每个错误格式,下面的函数也可以工作:

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",
}))
英文:

The parameter can also be a map, so the following function would work if you don't mind parsing every error format every time you use it:

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",
    }))
}

It's still a little wordier than I would have liked, but it's better than some other options, I suppose. You could turn map[string]interface{} into a local type and reduce it further to:

type P map[string]interface{}

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

答案4

得分: 2

哎呀,Go语言中还没有内置的带有命名参数的字符串插值函数(目前还没有)。但你并不是唯一一个遇到这个问题的人 Equivalent of Python string.format in Go? 一些包可能已经存在,例如:https://github.com/imkira/go-interpol。或者,如果你感兴趣,你可以自己编写这样一个辅助函数,因为这个概念实际上非常简单。

祝好,
Dennis

英文:

Alas, there's no built-in function in Go for string interpolation with named parameters (yet). But you are not the only one suffering out there Equivalent of Python string.format in Go? Some packages should exist, for example: https://github.com/imkira/go-interpol . Or, if feeling adventurous, you could write such a helper yourself, as the concept is actually quite simple.

Cheers,
Dennis

答案5

得分: 2

你可以尝试使用Go Formatter库,该库实现了类似于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)
}

输出结果:

Named placeholders dir/file:3:func1():
英文:

You can try the Go Formatter library that implements replacement fields surrounded by curly braces {} format strings similar to Python format.

Working code example 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)
}

Output:

Named placeholders dir/file:3:func1():

答案6

得分: 2

代替使用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))
}
英文:

Instead of using template.New, where you have to provide a template name, you
can just instantiate a template pointer:

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))
}

答案7

得分: 2

使用os.Expand函数来替换格式字符串中的字段。Expand函数使用一个func(string) string类型的映射函数,将字符串中的${var}或$var替换为相应的值。

下面是两种方便使用的包装os.Expand函数的方法:

func expandMap(s string, m map[string]string) string {
    return os.Expand(s, func(k string) string { return m[k] })
}

func expandArgs(s string, kvs ...string) string {
    return os.Expand(s, func(k string) string {
        for i := 1; i < len(kvs); i++ {
            if kvs[i-1] == k {
                return kvs[i]
            }
        }
        return ""
    })
}

示例用法:

s = expandMap("File ${file} had error ${error}",
           map[string]string{"file": "myfile.txt", "error": "Not found"})

s = expandArgs("File ${file} had error ${error}", 
          "file", "myfile.txt", "error", "Not found")

在 playground 上运行代码

英文:

Use os.Expand to replace fields in a format string. Expand replaces ${var} or $var in the string using a func(string) string mapping function.

Here are a couple of ways to wrap os.Expand in convenient to use functions:

func expandMap(s string, m map[string]string) string {
	return os.Expand(s, func(k string) string { return m[k] })
}

func expandArgs(s string, kvs ...string) string {
	return os.Expand(s, func(k string) string {
		for i := 1; i &lt; len(kvs); i++ {
			if kvs[i-1] == k {
				return kvs[i]
			}
		}
		return &quot;&quot;
	})
}

Example use:

s = expandMap(&quot;File ${file} had error ${error}&quot;,
       map[string]string{&quot;file&quot;: &quot;myfile.txt&quot;, &quot;error&quot;: &quot;Not found&quot;})

s = expandArgs(&quot;File ${file} had error ${error}&quot;, 
      &quot;file&quot;, &quot;myfile.txt&quot;, &quot;error&quot;, &quot;Not found&quot;))

Run the code on the playground.

答案8

得分: 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 make me a package

看到具有这样简单签名的 开发体验 潜力,我被诱惑了,上传了一个名为 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)
}

https://repl.it/@jossef/format

英文:

You can get quite close to that sweet python formatting experience:

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

Declare the following somewhere in your code:

type Items map[string]interface{}

func FormatString(template string, items Items) string {
    for key, value := range items {
        template = strings.ReplaceAll(template, fmt.Sprintf(&quot;{%v}&quot;, key), fmt.Sprintf(&quot;%v&quot;, value))
    }
    return template
}
  • 💡 note that my implementation is very naive and inefficient for high-performance needs

sudo make me a package

Seeing the development experience potential with having a simple signature like this, I've got tempted and uploaded a go package called format.

package main

import (
  &quot;fmt&quot;
  &quot;github.com/jossef/format&quot;
)

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

https://repl.it/@jossef/format

答案9

得分: 0

text/template 是一个有趣的库。我提供了一些以下的示例。

用法

func TestFString(t *testing.T) {
    // 示例 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()

    // 示例 2(使用 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 -}}" + // " - " 用于删除空格
        "{{ sayHello }} {{largest .Numbers}}", // 使用你创建的函数
        funcMap)
    fs.MustRender(Context{
        "Age":     90,
        "Numbers": []float32{3, 9, 13.9, 2.1, 7},
    })
    assert.Equal(t, "Old Hello 13.9", fs.Data)
}

package utils

import (
    "text/template"
)

type Context map[string]interface{}

type FString struct {
    Data     string
    template *template.Template
}

func (fs *FString) MustCompile(expr string, funcMap template.FuncMap) {
    fs.template = template.Must(template.New("f-string").
        Funcs(funcMap).
        Parse(expr))
}

func (fs *FString) Write(b []byte) (n int, err error) {
    fs.Data += string(b)
    return len(b), nil
}

func (fs *FString) Render(context map[string]interface{}) error {
    if err := fs.template.Execute(fs, context); err != nil {
        return err
    }
    return nil
}

func (fs *FString) MustRender(context Context) {
    if err := fs.Render(context); err != nil {
        panic(err)
    }
}

func (fs *FString) Clear() string {
    // 返回数据并清空
    out := fs.Data
    fs.Data = ""
    return out
}

Equivalent of Python string.format in Go?

重要文档

英文:

text/template is interesting. I Provide some example below

Usage

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

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

Lib

package utils

import (
    &quot;text/template&quot;
)

type Context map[string]interface{}

type FString struct {
    Data     string
    template *template.Template
}

func (fs *FString) MustCompile(expr string, funcMap template.FuncMap) {
    fs.template = template.Must(template.New(&quot;f-string&quot;).
        Funcs(funcMap).
        Parse(expr))
}

func (fs *FString) Write(b []byte) (n int, err error) {
    fs.Data += string(b)
    return len(b), nil
}

func (fs *FString) Render(context map[string]interface{}) error {
    if err := fs.template.Execute(fs, context); err != nil {
        return err
    }
    return nil
}

func (fs *FString) MustRender(context Context) {
    if err := fs.Render(context); err != nil {
        panic(err)
    }
}

func (fs *FString) Clear() string {
    // return the data and clear it
    out := fs.Data
    fs.Data = &quot;&quot;
    return out
}

Equivalent of Python string.format in Go?

important document

答案10

得分: 0

这是我写的一个函数,它可以在一个映射中将字段替换为字符串,类似于Python中的操作。它接受一个字符串作为输入,该字符串中的字段应该看起来像${field},然后将其替换为给定映射中的相应键值对,例如map['field']='value'

func replaceMap(s string, m *map[string]string) string {
    r := regexp.MustCompile("\\${[^}]*}")
    for x, i := range *m {
        s = strings.Replace(s, "${"+x+"}", i, -1)
    }
    // 移除缺失的参数
    s = r.ReplaceAllString(s, "")
    return s
}

Playground 示例:
https://go.dev/play/p/S5rF5KLooWq

英文:

Here is a function I wrote which replaces fields with strings in a map, similar to what you can do with Python. It takes a string which should have fields that look like ${field} and replaces them with any such keys in the given map like map[&#39;field&#39;]=&#39;value&#39;:

func replaceMap(s string,m *map[string]string) string {
r := regexp.MustCompile(&quot;\\${[^}]*}&quot;)
for x,i := range *m {
s = strings.Replace(s,&quot;${&quot;+x+&quot;}&quot;,i,-1)
}
// Remove missing parameters
s = r.ReplaceAllString(s,&quot;&quot;)
return s
}

Playground example:
https://go.dev/play/p/S5rF5KLooWq

huangapple
  • 本文由 发表于 2016年11月26日 03:10:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/40811117.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定