golang实现Python风格确定性评估的脚本语言插件库starlark-go的使用
Golang实现Python风格确定性评估的脚本语言插件库starlark-go的使用
Starlark简介
Starlark是Go语言实现的一个Python风格的脚本语言解释器,原名Skylark。Go包的导入路径是"go.starlark.net/starlark"
。
Starlark是Python的一种方言,旨在用作配置语言。与Python类似,它是一种无类型的动态语言,具有高级数据类型、具有词法作用域的一等函数和垃圾回收。但与CPython不同,独立的Starlark线程可以并行执行,因此Starlark工作负载在并行机器上扩展良好。
快速开始
安装
# 检查代码和依赖项,并将解释器安装在$GOPATH/bin中
$ go install go.starlark.net/cmd/starlark@latest
运行脚本
创建一个名为coins.star
的文件:
coins = {
'dime': 10,
'nickel': 5,
'penny': 1,
'quarter': 25,
}
print('By name:\t' + ', '.join(sorted(coins.keys())))
print('By value:\t' + ', '.join(sorted(coins.keys(), key=coins.get)))
然后运行:
$ starlark coins.star
By name: dime, nickel, penny, quarter
By value: penny, nickel, dime, quarter
交互式REPL
$ starlark
>>> def fibonacci(n):
... res = list(range(n))
... for i in res[2:]:
... res[i] = res[i-2] + res[i-1]
... return res
...
>>> fibonacci(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>>
按Ctrl-D
退出REPL。
在Go程序中嵌入Starlark
以下是一个完整的Go程序示例,展示如何嵌入Starlark解释器并执行脚本:
package main
import (
"fmt"
"go.starlark.net/starlark"
)
func main() {
// 创建一个Starlark线程
thread := &starlark.Thread{Name: "my thread"}
// 定义要执行的Starlark脚本
script := `
def fibonacci(n):
res = list(range(n))
for i in res[2:]:
res[i] = res[i-2] + res[i-1]
return res
result = fibonacci(10)
`
// 执行脚本
globals, err := starlark.ExecFile(thread, "fibonacci.star", script, nil)
if err != nil {
fmt.Printf("Error executing script: %v\n", err)
return
}
// 获取脚本中定义的result变量
result := globals["result"]
fmt.Printf("fibonacci(10) = %v\n", result) // 输出: fibonacci(10) = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// 调用脚本中定义的函数
fibonacci := globals["fibonacci"]
v, err := starlark.Call(thread, fibonacci, starlark.Tuple{starlark.MakeInt(5)}, nil)
if err != nil {
fmt.Printf("Error calling function: %v\n", err)
return
}
fmt.Printf("fibonacci(5) = %v\n", v) // 输出: fibonacci(5) = [0, 1, 1, 2, 3]
}
特性
- Python风格语法:Starlark语法与Python非常相似,学习曲线平缓
- 确定性执行:保证相同的输入产生相同的输出
- 并行执行:独立线程可以并行执行
- 可嵌入性:可以轻松嵌入到Go应用程序中
- 可扩展性:可以定义自定义函数和数据类型
适用场景
- 作为结构化数据的表达符号
- 消除重复配置
- 为现有应用添加脚本功能
- 构建工具配置(如Bazel)
Starlark是一个小型简单的语言,具有熟悉且高度可读的语法,非常适合需要嵌入脚本功能的Go应用程序。
更多关于golang实现Python风格确定性评估的脚本语言插件库starlark-go的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现Python风格确定性评估的脚本语言插件库starlark-go的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Starlark-go: 在Go中实现Python风格确定性评估
Starlark-go是Google开发的一个Go库,它实现了Starlark语言(原名Skylark),这是一种类似Python的确定性脚本语言,最初为Bazel构建系统开发。Starlark-go允许你在Go应用程序中嵌入这种轻量级脚本语言。
主要特性
- Python语法兼容:语法与Python 3高度相似
- 确定性执行:没有随机性、时间依赖或全局状态
- 沙盒环境:严格控制脚本访问权限
- 线程安全:适合并发环境
- 轻量级:设计简单,资源占用少
基本使用示例
package main
import (
"fmt"
"log"
"go.starlark.net/starlark"
)
func main() {
// 定义一个简单的Starlark脚本
script := `
def factorial(n):
if n <= 1:
return 1
return n * factorial(n-1)
result = factorial(5)
`
// 创建线程和全局环境
thread := &starlark.Thread{Name: "my thread"}
globals := starlark.StringDict{}
// 执行脚本
_, err := starlark.ExecFile(thread, "factorial.star", script, globals)
if err != nil {
log.Fatal(err)
}
// 获取结果
result := globals["result"]
fmt.Printf("Factorial of 5 is: %v\n", result)
}
与Go类型交互
从Go调用Starlark函数
func callStarlarkFunc() {
script := `
def greet(name):
return "Hello, " + name + "!"
`
thread := &starlark.Thread{Name: "greet"}
globals := starlark.StringDict{}
_, err := starlark.ExecFile(thread, "greet.star", script, globals)
if err != nil {
log.Fatal(err)
}
fn, ok := globals["greet"].(*starlark.Function)
if !ok {
log.Fatal("greet is not a function")
}
// 调用函数
result, err := starlark.Call(thread, fn, starlark.Tuple{starlark.String("Gopher")}, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // 输出: "Hello, Gopher!"
}
向Starlark暴露Go函数
func exposeGoFunctions() {
// 定义一个Go函数
toUpper := starlark.NewBuiltin("to_upper", func(
thread *starlark.Thread,
fn *starlark.Builtin,
args starlark.Tuple,
kwargs []starlark.Tuple,
) (starlark.Value, error) {
var s string
if err := starlark.UnpackArgs("to_upper", args, kwargs, "s", &s); err != nil {
return nil, err
}
return starlark.String(strings.ToUpper(s)), nil
})
// 准备全局环境
globals := starlark.StringDict{
"to_upper": toUpper,
}
// 执行使用该函数的脚本
script := `
message = to_upper("hello world")
`
thread := &starlark.Thread{Name: "expose"}
_, err := starlark.ExecFile(thread, "expose.star", script, globals)
if err != nil {
log.Fatal(err)
}
fmt.Println(globals["message"]) // 输出: "HELLO WORLD"
}
高级用法
模块系统
func moduleSystem() {
// 定义一个模块加载器
loader := func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
if module == "math" {
return starlark.StringDict{
"pi": starlark.Float(math.Pi),
"sqrt": starlark.NewBuiltin("sqrt", sqrtFunc),
}, nil
}
return nil, fmt.Errorf("module %s not found", module)
}
// 创建线程并设置模块加载器
thread := &starlark.Thread{
Name: "module example",
Load: loader,
}
// 使用模块的脚本
script := `
load("math", "pi", "sqrt")
result = sqrt(pi * 4)
`
globals := starlark.StringDict{}
_, err := starlark.ExecFile(thread, "module.star", script, globals)
if err != nil {
log.Fatal(err)
}
fmt.Println(globals["result"])
}
// sqrtFunc 实现平方根函数
func sqrtFunc(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x float64
if err := starlark.UnpackArgs("sqrt", args, kwargs, "x", &x); err != nil {
return nil, err
}
return starlark.Float(math.Sqrt(x)), nil
}
自定义类型
// 定义一个自定义类型
type Person struct {
Name string
Age int
}
// 实现starlark.Value接口
func (p *Person) String() string { return fmt.Sprintf("Person(%q, %d)", p.Name, p.Age) }
func (p *Person) Type() string { return "Person" }
func (p *Person) Freeze() {} // 不可变对象
func (p *Person) Truth() starlark.Bool { return starlark.True }
func (p *Person) Hash() (uint32, error) { return 0, fmt.Errorf("Person is unhashable") }
// 为Person类型定义方法
func (p *Person) Attr(name string) (starlark.Value, error) {
switch name {
case "name":
return starlark.String(p.Name), nil
case "age":
return starlark.MakeInt(p.Age), nil
case "greet":
return starlark.NewBuiltin("greet", p.greet), nil
default:
return nil, nil
}
}
func (p *Person) greet(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
return starlark.String(fmt.Sprintf("Hello, I'm %s, %d years old", p.Name, p.Age)), nil
}
func customTypeExample() {
// 创建Person实例
p := &Person{Name: "Alice", Age: 30}
// 将Person暴露给Starlark
globals := starlark.StringDict{
"person": p,
}
// 执行脚本
script := `
print(person)
print("Name:", person.name)
print("Age:", person.age)
print(person.greet())
`
thread := &starlark.Thread{Name: "custom type"}
_, err := starlark.ExecFile(thread, "person.star", script, globals)
if err != nil {
log.Fatal(err)
}
}
实际应用场景
- 配置脚本:允许用户编写复杂的配置逻辑
- 规则引擎:定义业务规则和流程
- 插件系统:让用户扩展应用程序功能
- 模板引擎:动态生成内容
- 教育工具:教授Python语法的基础知识
注意事项
- Starlark是Python的子集,不是所有Python特性都支持
- 性能关键代码仍应该用Go编写
- 需要仔细设计API边界,确保安全性
- 错误处理需要同时考虑Starlark和Go的错误
Starlark-go提供了一种安全、可控的方式在Go应用中嵌入脚本功能,特别适合需要用户自定义逻辑但又需要严格控制执行环境的场景。