it-swarm.com.de

Entspricht Python string.format in Go?

In Python können Sie dies tun:

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

oder dieses:

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

In Go ist die einfachste Option:

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

damit können Sie die Reihenfolge der Parameter in der Formatzeichenfolge nicht vertauschen, was für I18N erforderlich ist. Go does hat das Paket template, für das Folgendes erforderlich wäre:

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

das scheint ein langer Weg für eine Fehlermeldung zu sein. Gibt es eine vernünftigere Option, mit der ich Zeichenfolgenparameter unabhängig von der Reihenfolge angeben kann?

18

Mit strings.Replacer

Mit strings.Replacer ist die Implementierung eines Formatierers nach Ihren Wünschen sehr einfach und kompakt.

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

Ausgabe (probiere es auf dem Go Playground ):

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

Wir können die Verwendung angenehmer gestalten, indem Sie den Parameternamen in der Funktion log() die Klammern automatisch hinzufügen:

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

Ausgabe (probiere es auf dem Go Playground ):

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

Ja, man könnte sagen, dass dies nur string Parameterwerte akzeptiert. Das ist wahr. Mit ein wenig mehr Verbesserung wird dies nicht zutreffen:

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

Ausgabe (probiere es auf dem Go Playground ):

File /data/test.txt had error 666

Eine Variante davon, um Parameter als map[string]interface{} zu akzeptieren und das Ergebnis als string zurückzugeben:

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

Versuchen Sie es auf dem Go Playground .

Mit text/template

Ihre Vorlagenlösung oder Ihr Vorschlag ist auch viel zu ausführlich. Es kann so kompakt geschrieben werden (Fehlerprüfungen weggelassen):

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

Ausgabe (probiere es auf dem Go Playground ):

File /data/test.txt has error 666

Wenn Sie die string zurückgeben möchten (anstatt sie in die Standardausgabe zu drucken), können Sie dies wie folgt tun (versuchen Sie es mit dem Go Playground ):

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

Verwendung expliziter Argumentindizes

Dies wurde bereits in einer anderen Antwort erwähnt. Um es jedoch zu vervollständigen, sollten Sie wissen, dass derselbe explizite Argumentindex beliebig oft verwendet werden kann, sodass derselbe Parameter mehrfach verwendet wird. Lesen Sie dazu mehr in dieser Frage: Ersetzen Sie alle Variablen in Sprintf durch dieselbe Variable

29
icza

Ich kenne keine einfache Möglichkeit, die Parameter zu benennen, aber Sie können die Reihenfolge der Argumente leicht ändern, indem Sie explizite Argumentindizes verwenden:

Von docs :

In Printf, Sprintf und Fprintf gilt das Standardverhalten für jedes Formatierungsverb zum Formatieren aufeinanderfolgender Argumente, die im Aufruf übergeben werden. Die Notation [n] unmittelbar vor dem Verb zeigt jedoch an, dass das n-te Argument mit einem Index stattdessen formatiert werden soll. Dieselbe Schreibweise vor einem '*' für eine Breite oder Genauigkeit wählt den Argumentindex aus, der den Wert enthält. Nach der Verarbeitung eines Klammerausdrucks [n] verwenden nachfolgende Verben die Argumente n + 1, n + 2 usw., sofern nicht anders angegeben.

Dann können Sie, dh:

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

Der Parameter kann auch eine Map sein. Die folgende Funktion kann also verwendet werden, wenn Sie jedes Mal, wenn Sie sie verwenden, nichts falsches analysieren müssen:

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

Es ist immer noch ein bisschen wortreicher als ich es mir gewünscht hätte, aber es ist besser als andere Optionen, nehme ich an. Sie könnten aus map[string]interface{} einen lokalen Typ machen und ihn weiter reduzieren auf:

type P map[string]interface{}

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

Leider gibt es in Go keine integrierte Funktion für die String-Interpolation mit benannten Parametern (noch nicht). Aber Sie sind nicht die Einzigen, die leiden müssen :) Einige Pakete sollten existieren, zum Beispiel: https://github.com/imkira/go-interpol . Oder, wenn Sie sich abenteuerlustig fühlen, können Sie selbst einen solchen Helfer schreiben, da das Konzept eigentlich ganz einfach ist.

Prost, Dennis

1
oharlem