Golang中表达式求值的实现与技巧

Golang中表达式求值的实现与技巧 背景: 我有多种用于数据库表的数据模型,它们基本上由结构体组成,这些结构体的字段映射到表的列。

当我需要对特定表进行查询时,我会执行以下操作:

tableStruct := []models.ModelName{} // 假设 ModelName 是一个模型,其结构体格式对应某个表的列。
models.DB.Where("column_name = ?", "abc").All(&tableName) // 忽略语法,这是 soda pop 的用法
// 我将从表中获取与此查询对应的所有数据,并存入 tableStruct

现在的问题: 如果我通过某个查询或 API 请求获取到模型名称,那么我该如何评估这个表达式。

modelName := "xyzModel"
tableStruct := []models. `modelName` {} // 如何评估这个表达式?

在 Python 中,这很简单

tableStruct = eval(f"models.{modelName}"+"{}")

在 Golang 中如何实现这个功能?如果不可能,请针对上述问题建议其他方法

谢谢


更多关于Golang中表达式求值的实现与技巧的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

谢谢 @skillian,这很有帮助。

更多关于Golang中表达式求值的实现与技巧的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好,

作为一种解释型语言,Python 允许在运行时将字符串作为代码来执行(顺便提一句,如果字符串来自未知来源,这样做可能很危险)。Go 语言没有这种便利性。

相反,你可以维护一个从名称到模型的映射,例如:

var models map[string]models.ModelName

并按名称注册每个模型:

models["xyzModel"] = <在此处插入 ModelName 实例>

并像这样使用它:

modelName := "xyzModel"
tableStruct := models[modelName]

你好 @christophberger,感谢你提供的解决方案。

关于实现,我仍然有一些问题,

christophberger:

var models map[string]models.ModelName

在这里,models.ModelName 对于每个模型(例如 Model1 和 Model2)会有不同的类型。因此,为所有模型建立映射会是一个问题。

var modelsMap map[string]models.Model1
modelsMap["xyzModel"] = []models.Model1{}

然后,

modelsMap["abcModel"] = []models.Model2{}

这会导致错误:

cannot use (models.Model2 literal) (value of type models.Model2) as models.Model1 value in assignment

对于如何解决这个问题有什么想法吗?

如果我在映射定义中使用接口,那么我将需要将其类型转换回所需的结构体,这又需要进行表达式求值。

再次感谢!

你能提供更多关于你将如何使用这个功能的背景信息吗?Python是一种动态编程语言,只要对象遵循相同的协议(具有相同的属性,如果它们可调用、可索引等),代码就不需要跟踪对象的类型。而Go不是动态语言,要求表达式产生静态类型的值。由于这一重大差异,有些模式在Python中有效,但在Go中无法工作,需要进行重构。如果你的模型没有任何公共方法(或者作为模型,可能根本没有方法),那么你必须将它们存储为interface{}

然而,看起来你并不是存储模型实例,而是存储它们的类型以便实例化。你可以在Go中做类似的事情,但正如Christoph所说,你需要手动维护一个类型映射。没有办法从包中查找类型或函数,即使使用reflect包也不行。

var modelTypes = map[string]reflect.Type{
    reflect.TypeOf(Model1{}),
    reflect.TypeOf(Model2{}),
    // ...
}

func query(modelName string) (interface{}, error) {
    modelType, ok := modelTypes[modelName]
    if !ok {
        return nil, errBadType
    }
    sliceType := reflect.SliceOf(modelType)
    slicePtr := reflect.New(sliceType) // pointer to ensure it's addressable
    slicePtr.Elem().Set(reflect.MakeSlice(sliceType, 0, 4)) // arbitrary initial capacity
    models.DB.Where("column_name = ?", "abc").All(slicePtr.Interface())
    // Because types must be static, and Go doesn't yet support
    // generics, you're stuck with an interface{} that holds the
    // proper []ModelType inside.  The caller will have to
    // type assert the result to the proper type.
    return slicePtr.Elem().Interface(), nil
}

如果我们能看到你对结果的处理方式,或许能提供一些替代方案,避免让调用者知道并转换类型。

在Go语言中,无法像Python那样使用eval()进行动态表达式求值,因为Go是编译型语言,类型在编译时就必须确定。不过,有几种方法可以实现类似的功能:

方法1:使用映射表(推荐)

创建模型名称到结构体类型的映射:

var modelRegistry = map[string]interface{}{
    "User":        []models.User{},
    "Product":     []models.Product{},
    "Order":       []models.Order{},
    "xyzModel":    []models.XYZModel{},
}

func GetModelInstance(modelName string) interface{} {
    if instance, exists := modelRegistry[modelName]; exists {
        // 创建新的切片实例
        sliceType := reflect.TypeOf(instance)
        return reflect.New(sliceType).Elem().Interface()
    }
    return nil
}

// 使用示例
func main() {
    modelName := "xyzModel"
    tableStruct := GetModelInstance(modelName)
    
    if tableStruct != nil {
        // 使用类型断言进行查询
        switch v := tableStruct.(type) {
        case []models.User:
            models.DB.Where("column_name = ?", "abc").All(&v)
        case []models.Product:
            models.DB.Where("column_name = ?", "abc").All(&v)
        case []models.XYZModel:
            models.DB.Where("column_name = ?", "abc").All(&v)
        }
    }
}

方法2:使用接口和工厂模式

定义通用接口:

type Model interface {
    TableName() string
}

type ModelFactory interface {
    CreateSlice() interface{}
    Query(condition string, args ...interface{}) error
}

// 为每个模型实现工厂
type XYZModelFactory struct{}

func (f *XYZModelFactory) CreateSlice() interface{} {
    return []models.XYZModel{}
}

func (f *XYZModelFactory) Query(condition string, args ...interface{}) error {
    slice := f.CreateSlice()
    return models.DB.Where(condition, args...).All(&slice)
}

// 注册工厂
var factories = map[string]ModelFactory{
    "xyzModel": &XYZModelFactory{},
    "user":     &UserModelFactory{},
}

func QueryModel(modelName, condition string, args ...interface{}) error {
    if factory, exists := factories[modelName]; exists {
        return factory.Query(condition, args...)
    }
    return fmt.Errorf("model not found: %s", modelName)
}

方法3:使用反射(更灵活但性能较低)

func CreateModelSlice(modelName string) (interface{}, error) {
    // 获取包信息
    pkg := reflect.TypeOf(models.User{}).PkgPath()
    
    // 通过反射获取类型
    modelType, err := getTypeByName(pkg, modelName)
    if err != nil {
        return nil, err
    }
    
    // 创建切片类型
    sliceType := reflect.SliceOf(modelType)
    
    // 创建切片实例
    sliceValue := reflect.New(sliceType).Elem()
    
    return sliceValue.Interface(), nil
}

func getTypeByName(pkgPath, typeName string) (reflect.Type, error) {
    // 这里需要遍历所有已注册的类型
    // 实际实现可能需要使用go:linkname或其他技巧
    // 或者维护一个类型注册表
}

方法4:使用代码生成(性能最佳)

创建代码生成工具:

// generate_models.go
package main

import (
    "text/template"
    "os"
)

func main() {
    tmpl := `package query

import "your-project/models"

func Get{{.Name}}Slice() []models.{{.Name}} {
    return []models.{{.Name}}{}
}

func Query{{.Name}}(condition string, args ...interface{}) ([]models.{{.Name}}, error) {
    var result []models.{{.Name}}
    err := models.DB.Where(condition, args...).All(&result)
    return result, err
}
`

    models := []string{"User", "Product", "Order", "XYZModel"}
    
    for _, model := range models {
        f, _ := os.Create(f"query_{model}.go")
        t := template.Must(template.New("").Parse(tmpl))
        t.Execute(f, struct{ Name string }{model})
        f.Close()
    }
}

使用生成的代码:

// 生成的代码会被编译,性能与硬编码相同
modelName := "xyzModel"
switch modelName {
case "User":
    tableStruct := query.GetUserSlice()
    result, _ := query.QueryUser("column_name = ?", "abc")
case "XYZModel":
    tableStruct := query.GetXYZModelSlice()
    result, _ := query.QueryXYZModel("column_name = ?", "abc")
}

方法5:使用soda pop的Model接口(如果适用)

如果使用pop库,可以利用其内置的模型接口:

func QueryDynamicModel(modelName string) error {
    // 假设所有模型都实现了pop.Model接口
    var model pop.Model
    
    switch modelName {
    case "User":
        model = &models.User{}
    case "XYZModel":
        model = &models.XYZModel{}
    default:
        return fmt.Errorf("unknown model: %s", modelName)
    }
    
    // 创建切片
    slice := reflect.New(reflect.SliceOf(reflect.TypeOf(model).Elem())).Interface()
    
    // 执行查询
    return models.DB.Where("column_name = ?", "abc").All(slice)
}

最推荐的是方法1(映射表),它在灵活性和性能之间取得了最佳平衡。对于需要极致性能的场景,可以考虑方法4(代码生成)

回到顶部