web-dev-qa-db-ja.com

GoのPython string.formatと同等?

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 の場合は、フォーマット文字列のパラメータの順序を入れ替えることはできません。 Godoesには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)
}

エラーメッセージが表示されるまでの長い道のりのようです。順序に関係なく文字列パラメータを指定できるより合理的なオプションはありますか?

23

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

明示的な引数インデックスの使用

これはすでに別の回答で述べられていますが、それを完了するために、同じ明示的な引数インデックスが任意の回数使用される可能性があり、その結果、同じパラメーターが複数回置換される可能性があることを知ってください。詳細については、この質問をご覧ください。 Sprintfのすべての変数を同じ変数に置き換えます

29
icza

パラメーターに名前を付ける簡単な方法はわかりませんが、明示的な引数インデックスを使用して、引数の順序を簡単に変更できます。

docs から:

Printf、Sprintf、およびFprintfでは、デフォルトの動作は、各フォーマット動詞が呼び出しで渡される連続する引数をフォーマットすることです。ただし、動詞の直前の表記[n]は、n番目の1インデックスの引数が代わりにフォーマットされることを示しています。幅または精度の「*」の前の同じ表記は、値を保持する引数インデックスを選択します。括弧で囲まれた式[n]を処理した後、後続の動詞は、特に指示がない限り、引数n + 1、n + 2などを使用します。

次に、次のことができます。

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

パラメータはマップにすることもできるため、使用するたびにすべてのエラー形式を解析することを気にしない場合は、次の関数が機能します。

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

残念ながら、Goには、(まだ)名前付きパラメーターを使用した文字列補間用の組み込み関数はありません。しかし、そこに苦しんでいるのはあなただけではありません:)たとえば、次のようないくつかのパッケージが存在するはずです: https://github.com/imkira/go-interpol 。あるいは、冒険的な気分なら、コンセプトは実際には非常に単純なので、そのようなヘルパーを自分で書くこともできます。

乾杯、デニス

1
oharlem