web-dev-qa-db-fra.com

Équivalent de Python string.format dans Go?

En Python, vous pouvez faire ceci:

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

ou ca:

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

Dans Go, l’option la plus simple est:

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

ce qui ne vous permet pas d'échanger l'ordre des paramètres dans la chaîne de format, ce que vous devez faire pour I18N . Go est-ce que a le paquetage template, qui nécessiterait quelque chose comme:

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

ce qui semble être un long chemin à parcourir pour un message d'erreur. Existe-t-il une option plus raisonnable qui me permet de donner des paramètres de chaîne indépendamment de l’ordre?

18
Scott Deerwester

Avec strings.Replacer

En utilisant strings.Replacer , implémenter un formateur de votre choix est très simple et 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))
}

Sortie (essayez-le sur Go Playground ):

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

Nous pouvons rendre son utilisation plus agréable en ajoutant les crochets aux noms de paramètres automatiquement dans la fonction 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))
}

Sortie (essayez-le sur Go Playground ):

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

Oui, vous pouvez dire que cela n’accepte que les valeurs du paramètre string. C'est vrai. Avec un peu plus d'amélioration, ce ne sera pas vrai:

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

Sortie (essayez-le sur Go Playground ):

File /data/test.txt had error 666

Une variante de cela pour accepter les paramètres en tant que map[string]interface{} et renvoyer le résultat en tant que 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)
}

Essayez-le sur le Go Playground .

Avec text/template

Votre solution ou proposition de modèle est également beaucoup trop détaillée. Il peut être écrit aussi compact que cela (vérification d'erreur omise):

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

Sortie (essayez-le sur Go Playground ):

File /data/test.txt has error 666

Si vous voulez renvoyer la string (au lieu de l’imprimer sur la sortie standard), vous pouvez le faire comme ceci (essayez-le sur le Go Playground ):

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

Utilisation d'indices d'arguments explicites

Cela a déjà été mentionné dans une autre réponse, mais pour le compléter, sachez que le même index d'argument explicite peut être utilisé un nombre de fois arbitraire, ce qui entraîne la substitution du même paramètre à plusieurs reprises. Lisez plus à ce sujet dans cette question: Remplacez toutes les variables de Sprintf par la même variable

29
icza

Je ne connais aucun moyen simple de nommer les paramètres, mais vous pouvez facilement changer l'ordre des arguments en utilisant des index d'arguments explicites:

De docs :

Dans Printf, Sprintf et Fprintf, le comportement par défaut est que chaque verbe de mise en forme formate les arguments successifs passés dans l'appel. Cependant, la notation [n] immédiatement avant le verbe indique que le nième argument à un index doit être formaté à la place. La même notation avant un '*' pour une largeur ou une précision sélectionne l'index d'argument contenant la valeur. Après avoir traité une expression entre crochets [n], les verbes suivants utiliseront les arguments n + 1, n + 2, etc., sauf indication contraire.

Ensuite, vous pouvez, à savoir:

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

Le paramètre peut également être une carte, donc la fonction suivante fonctionnerait si vous ne craignez pas d'analyser chaque format d'erreur chaque fois que vous l'utilisez:

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

C'est encore un peu plus verbeux que je l'aurais souhaité, mais c'est mieux que d'autres options, je suppose. Vous pouvez transformer map[string]interface{} en un type local et le réduire davantage à:

type P map[string]interface{}

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

Hélas, il n'y a pas de fonction intégrée dans Go pour l'interpolation de chaîne avec les paramètres nommés (pour le moment). Mais vous n'êtes pas le seul à en souffrir :) Certains paquets devraient exister, par exemple: https://github.com/imkira/go-interpol Ou, si vous vous sentez aventureux, vous pourriez écrire vous-même un tel assistant, car le concept est en réalité assez simple.

A bientôt, Dennis

1
oharlem