像python操作attr一样在go里用reflect 操作field (标题与内容弱相关)
用go快两年了,其实是基本没怎么用过反射。主要是感觉对上层的使用来说没啥用。
之前模仿python的getattr和setattr简单写过GetField和SetField,写完简单测了一下就丢一边了也没大量用(因为没有需求啊)
func SetField(obj any, attr string, value any) error {
_, field, err := checkObjAndGetField(obj, attr)
if err != nil {
return err
}
if !field.CanSet() {
return fmt.Errorf("cannot set %s field value in obj %T", attr, obj)
}
val := reflect.ValueOf(value)
if field.Type() != val.Type() {
return fmt.Errorf("provided value type %T didn't match obj field type %s", value, field.Type())
}
field.Set(val)
return nil
}
func GetField(obj any, attr string) (any, error) {
_, field, err := checkObjAndGetField(obj, attr)
if err != nil {
return nil, err
}
return field.Interface(), nil
}
func checkObjAndGetField(obj any, attr string) (ov reflect.Value, field reflect.Value, err error) {
if obj == nil {
err = fmt.Errorf("field set/get on nil")
return
}
k := reflect.TypeOf(obj).Kind()
if k != reflect.Struct && k != reflect.Ptr {
err = fmt.Errorf("cannot set/get field on a non-struct interface: %T", obj)
return
}
ov = reflect.Indirect(reflect.ValueOf(obj))
field = ov.FieldByName(attr)
if !field.IsValid() {
err = fmt.Errorf("no such field: %s in obj %T", attr, obj)
}
return
}
今天用SetField的时候发现出现了我意料外的错误。当然这个错误是应该发生的,也很合理,这个意料外只要是指这种情况没有在我考虑之内
type TaskState int
type Task struct {
State TaskState
}
func TestSetField(t *testing.T) {
a := &Task{}
err := SetField(a, "State", 3)
fmt.Println(err) // provided value type int didn't match obj field type TaskState
}
ok,开始处理这个问题(嗯,这里需要一些reflect的前置知识)
int和TaskState类型(Type)是不一样的,在go里面Type是无穷无尽的,不过反射里面还有个叫Kind的东西,这个Kind是有限的。所以int和TaskState的Kind是一样的,
所以我把
field.Type() != val.Type()改成了
field.Type().Kind() != val.Type().Kind()
结果发现在filed.Set的时候panic了,“panic: reflect.Set: value of type int is not assignable to type TaskState”
哦 所以这里我要做一下类型转换,所以要用到reflet.Value.Convert(reflet.Type)
然后想到如果Kind也不一样的话,Convert肯定会panic的(测试结果也是如此)
所以就有两个(我脑子里冒出来两个)选择了。1.先检查Kind,Kind相同才Convert 2. recover起来写个tryConvert。
嗯,写了个检查Kind的东西
val := reflect.ValueOf(value)
ft := field.Type()
vt := val.Type()
if ft.Kind() != vt.Kind() {
return fmt.Errorf("provided value %v(type:%s, kind:%s) didn't match obj(%T) field %s (type:%s, kind:%s)",
val, vt, vt.Kind(), obj, attr, ft, ft.Kind())
}
写完发现不对了,Kind里面有map,但是都是map,map[int]int和map[int]string肯定不能转换。(emm 应该是也有办法拿到kv的type,但是这太麻烦了就不弄了)
好的。(好像)只能用2了。
emm用类型转换的话,就可能会发生float到int这种转换,如果喜欢这样的话,这算是一个意外之喜了,但是对类型严格的话,这样就不好了
所以最后写了两个
func SetField(obj any, attr string, value any) error {
_, field, err := checkObjAndGetField(obj, attr)
if err != nil {
return err
}
if !field.CanSet() {
return fmt.Errorf("cannot set %s field value in obj %T", attr, obj)
}
val := reflect.ValueOf(value)
// 先尝试convert, convert失败就报错
// 这样会导致字段是int但是set float也会成功,因为float是可以convert到int的。如果偏爱这个行为的话,用这个挺好的
fuzzError := tryConvertAndSet(field, val)
if fuzzError == nil {
return nil
}
clearErr := checkKind(obj, attr, field, val)
if clearErr != nil {
return clearErr
}
return fuzzError
// 最初的版本 类型别名之间不能Set 这不能接受
//if field.Type() != val.Type() {
// return fmt.Errorf("provided value type %T didn't match obj field type %s", value, field.Type())
//}
//field.Set(val)
//return nil
}
func SetFieldStrict(obj any, attr string, value any) error {
_, field, err := checkObjAndGetField(obj, attr)
if err != nil {
return err
}
if !field.CanSet() {
return fmt.Errorf("cannot set %s field value in obj %T", attr, obj)
}
val := reflect.ValueOf(value)
// 1 先尝试检查Kind, Kind不一样肯定就不能接受Set 这样不会有"隐式"的类型转换
err = checkKind(obj, attr, field, val)
if err != nil {
return err
}
return tryConvertAndSet(field, val)
}
func checkKind(obj any, attr string, field reflect.Value, val reflect.Value) error {
vt := val.Type()
ft := field.Type()
if ft.Kind() != vt.Kind() {
return fmt.Errorf("provided value %v(type:%s, kind:%s) didn't match obj(%T) field %s (type:%s, kind:%s)",
val, vt, vt.Kind(), obj, attr, ft, ft.Kind())
}
return nil
}
func tryConvertAndSet(field reflect.Value, val reflect.Value) error {
if converted, er := tryConvert(field.Type(), val); er != nil {
return trySet(field, converted)
}
return fmt.Errorf("convert %s to %s error", val.Type(), field.Type())
}
// trySet 就是有点怕 其实这个应该是没有必要的
func trySet(field reflect.Value, v reflect.Value) (err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("%v", p)
}
}()
field.Set(v)
return
}
func tryConvert(t reflect.Type, oldV reflect.Value) (v reflect.Value, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("%v", p)
}
}()
v = oldV.Convert(t)
return
}
好的 这个就叫做hreflect了(hight reflect) 顺便放到github欢迎使用
go get github.com/xiaotushaoxia/hreflect
好晚了。最后一个疑问,有空再看
// reflect.ValueOf(value).Kind()
//
// reflect.TypeOf(value).Kind()
// 这两个东西有区别吗?怎么感觉一样的啊
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人