用中文的方式展开Golang字符串中的变量(Go语言的插值方法)

我想在Go语言中展开字符串中的变量并使用。

我想用Go语言实现与以下相同的功能。

在bash中,等同于hoge=”fuga ${piyo:-piyopiyo} mogera”。

在php中,等同于$hoge = “fuga ${piyo} mogera”。

在ruby中,等同于hoge = “fuga #{piyo} mogera”。

在python中,等同于hoge = f’fuga {piyo} mogera’ (仅限于python >= 3.6)。

由于搜索”golang 字符串 变量 展开”,我发现大部分信息都说只能使用连接操作符(+)进行操作,这增加了自己的搜索能力。

    関連記事: 改行などをエスケープして表示したい。【代入済み変数の文字列をエスケープ出力】 @ Qiita

简而言之

确实,Go语言(以下简称为Golang)没有插值(interpolation)功能。
过去曾有需求和提议,但由于多数人的否定而被拒绝。
然而,只要有足够的创意,是有可能实现的。

由于没有内插,所以… … … 也就是说,标准中没有内插(展开)变量的功能。由于这是一种编译型语言,需要通过函数等方式来实现。

考虑将变量展开后的文本拼接在一起,截至2023年9月,主要有以下四种情况可考虑。

    1. 与 Python 3.6 之前的版本不同,现在可以使用加号 (+) 连接变量和字符串。

 

    1. 可以使用字符串格式化函数 Sprintf。

 

    1. 如果需要进行大量的替换,可以使用文本或 HTML 库中的模板函数。

 

    1. 考虑使用外部包(例如以下两个)。

go.uber.org/yarpc 的 interpolate 包

如果想要使用常见的 Parse(“My name is ${name}”) 方式进行拼接

github.com/buildkite 的 interpolate 包

如果想要设置默认值,类似 bash 中的 ${piyo:-piyopiyo} 的用法。

package main

import (
	"fmt"

	"github.com/buildkite/interpolate"
)

func main() {
	myInfo := Data{
		Name: "Giovanni Giorgio",
		Nick: "Giorgio",
	}

	myInfo.Printf("My name is ${Name}, but everybody calls me ${Nick}.\nI am ${Age:-82} yeas old.\n")

	// Output:
	// My name is Giovanni Giorgio, but everybody calls me Giorgio.
	// I am 82 yeas old.
}

type Data struct {
	Name string
	Nick string
	Age  int
}

func (d Data) Printf(template string) {
	env := interpolate.NewSliceEnv([]string{
		"Name=" + d.Name,
		"Nick=" + d.Nick,
	})

	output, _ := interpolate.Interpolate(env, template)

	fmt.Print(output)
}

オンラインで動作をみる @ Go Playground

使用模板和外部库的方法需要牺牲性能以换取可维护性,因为它们会尝试对类型不确定的内容进行插值。然而,并不是要否定插值的存在,而是在尝试了上述方法后,应该在自己的应用程序中判断是否需要插值。

入了Go的坑就要遵循Go的方式(“In Golang, do what the Gophers do”)
作者曾经回到了fmt.Sprintf()和模板的原点。起初,从JavaScript、PHP和Bash等脚本语言过来时,作者感到非常困惑,但是考虑到长远来看理性缺失的问题。因为要进行维护,就必须了解Go语言。而且随着经验的积累,坚持使用Go的简单基本语法,就不需要关心外部库的共享语法或类型的问题。

太长没看;简短总结

func GetHogeByGlue(piyo string) string {
    return "fuga " + piyo + " mogera" + "\n"
}
import "fmt"

func GetHogeBySprintf(piyo string) string {
    return fmt.Sprintf("fuga %s mogera\n", piyo)
}
import "text/template"

type FieldsToReplace struct {
    Replace1 string
}

func GetHogeByTemplate(piyo string) string {
    var msg_result bytes.Buffer

    msg_tpl, msg_err := template.New("myTemplate").Parse("fuga {{.Replace1}} mogera\n")    
    replace_to := FieldsToReplace {
        Replace1: piyo,
    }
    msg_err = msg_tpl.Execute(&msg_result, replace_to)

    return msg_result.String()
}

上記の動作をオンラインで見る @ paiza.IO

使用外部库的示例

如果想要使用 POSIX参数扩展形式,例如在bash中使用${hoge:-fuga}来指定变量为空时的默认值,有以下两种包可以使用。而后面的github.com/buildkite的包更接近所需的用法。

前往 go.uber.org/yarpc 的 interpolate 包。

go.uber.org/yarpc @ pkg.go.dev

正直言ってこのパッケージは使いづらいです。後述の github.com/buildkite の方が使いやすいです。内挿処理を行う interpolate パッケージが internal に設置されているため、go get しても interpolate を直接触れません。これは Uber 社のメッセージング・プラットホーム yarpc 用の内部パッケージとして使われているためです。そのため、リポジトリをクローンやコピーして interpolate パッケージを直接自分のプロジェクトに組み込むなどの手間が必要になります。しかも Ragel という謎技術で肝心の Parse() 関数を自動生成しているため、メンテもたいへん。Glide パッケージマネージャーに慣れている人向けです。

s, err := interpolate.Parse("My name is ${name}")
if err != nil {
	panic(err)
}

out, err := s.Render(resolver)
if err != nil {
	panic(err)
}

fmt.Println(out)

s, err := interpolate.Parse("My name is ${name:what}")
if err != nil {
	panic(err)
}

out, err := s.Render(emptyResolver)
if err != nil {
	panic(err)
}

fmt.Println(out)
# My name is what

github.com/buildkite的插值包

github.com/buildkite @ pkg.go.dev

// 要インストール go get "github.com/buildkite/interpolate"

import "github.com/buildkite/interpolate"

func GetHogeByInterpolate(piyo string) string {
	env := interpolate.NewSliceEnv([]string{
		"Replace2="+piyo,
	})

	output, _ := interpolate.Interpolate(env, "fuga ${Replace2} mogera ${Replace3:-?}\n")

	return output
}

动作示例

这是一个示例,其中排除了上述 Uber 库的“在字符串中展开变量的方法”。

package main

import (
	"bytes"
	"fmt"
	"text/template"

	"github.com/buildkite/interpolate"
)

func main() {
	fmt.Println(GetHogeByGlue("foo"))        // 文字列結合子(+)を使ったサンプル
	fmt.Println(GetHogeBySprintf("foo"))     // fmt パッケージを使ったサンプル
	fmt.Println(GetHogeByTemplate("foo"))    // template パッケージを使ったサンプル
	fmt.Println(GetHogeByInterpolate("foo")) // interpolate パッケージを使ったサンプル
}

func GetHogeByGlue(piyo string) string {
	return "fuga " + piyo + " mogera"
}

func GetHogeBySprintf(piyo string) string {
	return fmt.Sprintf("fuga %s mogera", piyo)
}

type FieldsToReplace struct {
	Replace1 string
}

func GetHogeByTemplate(piyo string) string {
	msg_tpl, msg_err := template.New("myTemplate").Parse("fuga {{.Replace1}} mogera")

	replace_to := FieldsToReplace{
		Replace1: piyo,
	}

	var msg_result bytes.Buffer

	if msg_err = msg_tpl.Execute(&msg_result, replace_to); msg_err != nil {
		fmt.Println(msg_err)
	}

	return msg_result.String()
}

func GetHogeByInterpolate(piyo string) string {
	env := interpolate.NewSliceEnv([]string{
		"Replace2=" + piyo,
	})

	output, _ := interpolate.Interpolate(env, "fuga ${Replace2} mogera ${Replace3:-?}")

	return output
}
$ go get -u github.com/buildkite/interpolate
...
$ ls
sample.go
$ go run sample.go
fuga foo mogera
fuga foo mogera
fuga foo mogera
fuga foo mogera ?

オンラインで動作を見る @ GoPlayground

为什么 Golang 没有字符串内变量替换的功能

如果你看看PHP或Ruby的例子,你可能会意识到它们更适用于动态类型系统,因为它确实关注你传递给插值字符串中的数据的类型,比如年、月和日。然而在像Go这样的静态编译语言中,我们需要明确指定变量的类型。

结论
这可能对你来说看起来很奇怪,但如果你考虑到静态类型语言在编译时进行错误检查的好处,这就很有道理了,比动态语言中的字符串插值更方便、更容易调试。

我明白了。确实,Golang以编译时间短为卖点,可能字符串内插实现的麻烦要比好处大。由于在Web应用中需要经常展开字符串中的变量,所以我大致理解可以在Golang中使用text/template或html/template。

PHP和Python3也进入了JIT时代,现在已经成为接近编译型(预编译型)语言。以前PHP虽然被嘲笑为不稳定的语言,不过自从PHP7之后,更重视类型的使用,可能是为了加快编译和开发(调试)的速度。

然而,由于最新的PHP比Golang更快(不是说PHP很快),所以无需运行时环境可以分发二进制文件可能是Golang的最大优点。也就是说,这更多涉及策略而不是编译等问题。

在Golang的Issues上也有长期的讨论,因为“语言的开发者”和“语言的使用者”在重视的点上存在不同,所以很难解决。

proposal: string interpolation #34174 | issues | Golang @ GitHub

然而,Python 在 v3.6 版本中实现了内插,而 Rust 作为相同的静态类型语言在 v1.58 版本中也实现了内插,所以我还是希望 Golang 能够将变量字符串内插作为标准。

因为从 v1.18 开始, Go 语言可以使用泛型(一种用于描述无类型处理的机制),所以很可能会在不久的将来被实现。

验证版本

$ go version
go version go1.12.5 linux/amd64

请参考参考文献

    • 「Goで文字列中で変数展開する」 @ Qiita

 

    「String interpolation in golang」@ Medium
广告
将在 10 秒后关闭
bannerAds