【问题标题】:Equivalent of Python string.format in Go?相当于Go中的Python string.format?
【发布时间】:2017-04-10 05:11:45
【问题描述】:

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

这似乎是一个很长的路要走的错误消息。有没有更合理的选项可以让我给出与顺序无关的字符串参数?

【问题讨论】:

    标签: python string go


    【解决方案1】:

    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
    

    此变体接受参数作为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

    【讨论】:

    • 我想您也可以使用下面的地图将我的答案中的方法与 log3 中的方法结合起来,将 args 放入地图而不是字符串数组中,然后按照您的方式对其进行格式化与 log4。我的示例中的很多复杂之处在于实现返回字符串。你能修改 log4 来适应吗?
    • @ScottDeerwester 是的,添加了一个log33() 变体,采用map 并将结果作为string 返回,还添加了一个log5() 变体返回结果而不是将其打印到标准输出。
    • @icza:感谢您的精彩回答。我想知道,差不多一年后,是否有更好的方法来做到这一点?最后,我选择了strings.Replacer,它似乎工作正常。
    • @elo80ka 我不知道。您的选择与一年前相同。
    【解决方案2】:

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

    来自docs

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

    那么你可以,即:

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

    【讨论】:

      【解决方案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",
          }))
      

      【讨论】:

        【解决方案4】:

        唉,Go 中还没有用于使用命名参数进行字符串插值的内置函数。但是你不是唯一一个受苦的人:) 应该存在一些包,例如:https://github.com/imkira/go-interpol。或者,如果你喜欢冒险,你可以自己写一个这样的助手,因为这个概念实际上很简单。

        干杯, 丹尼斯

        【讨论】:

          【解决方案5】:

          您可以试试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():
          

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

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

              Run the code on the playground.

              【讨论】:

                【解决方案8】:

                您可以非常接近那种美妙的 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)
                }
                

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

                【讨论】:

                  【解决方案9】:

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

                  用法

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

                  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 {
                      // return the data and clear it
                      out := fs.Data
                      fs.Data = ""
                      return out
                  }
                  

                  重要文件

                  【讨论】:

                    猜你喜欢
                    • 2013-05-09
                    • 2019-09-19
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-02-25
                    • 1970-01-01
                    • 2022-01-23
                    • 2012-02-18
                    • 1970-01-01
                    相关资源
                    最近更新 更多