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

特性

  1. Python风格语法:Starlark语法与Python非常相似,学习曲线平缓
  2. 确定性执行:保证相同的输入产生相同的输出
  3. 并行执行:独立线程可以并行执行
  4. 可嵌入性:可以轻松嵌入到Go应用程序中
  5. 可扩展性:可以定义自定义函数和数据类型

适用场景

  • 作为结构化数据的表达符号
  • 消除重复配置
  • 为现有应用添加脚本功能
  • 构建工具配置(如Bazel)

Starlark是一个小型简单的语言,具有熟悉且高度可读的语法,非常适合需要嵌入脚本功能的Go应用程序。


更多关于golang实现Python风格确定性评估的脚本语言插件库starlark-go的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于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应用程序中嵌入这种轻量级脚本语言。

主要特性

  1. Python语法兼容:语法与Python 3高度相似
  2. 确定性执行:没有随机性、时间依赖或全局状态
  3. 沙盒环境:严格控制脚本访问权限
  4. 线程安全:适合并发环境
  5. 轻量级:设计简单,资源占用少

基本使用示例

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

实际应用场景

  1. 配置脚本:允许用户编写复杂的配置逻辑
  2. 规则引擎:定义业务规则和流程
  3. 插件系统:让用户扩展应用程序功能
  4. 模板引擎:动态生成内容
  5. 教育工具:教授Python语法的基础知识

注意事项

  1. Starlark是Python的子集,不是所有Python特性都支持
  2. 性能关键代码仍应该用Go编写
  3. 需要仔细设计API边界,确保安全性
  4. 错误处理需要同时考虑Starlark和Go的错误

Starlark-go提供了一种安全、可控的方式在Go应用中嵌入脚本功能,特别适合需要用户自定义逻辑但又需要严格控制执行环境的场景。

回到顶部