像python操作attr一样在go里用reflect 操作field (标题与内容弱相关)

Posted xiaotushaoxia

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了像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()
// 这两个东西有区别吗?怎么感觉一样的啊

在linux里用python(2.7)的matplotlib后,调用 show()之后无法显示图像,不会出现图形窗口。

代码如下:
In [1]: import matplotlib.pyplot as plt
In [2]: plt.plot([1,2,3])
Out[2]: [<matplotlib.lines.Line2D at 0xad9a66c>]
In [3]: plt.ylabel('some numbers')
Out[3]: <matplotlib.text.Text at 0xae3792c>
In [4]: plt.show()
之后就像什么事也没发生一样,让我输入下一条命令了。
2个必要的包都已装了(numpy,matplotlib)。研究了好几天了。

1. 确保是在Linux的桌面环境下运行而不是从纯终端或ssh客户端
2. plt.show()依赖一个图形环境, 如tk, wxpython等, 要先安装一个
参考技术A 请问你这个问题有答案了吗?我也是这个问题,研究了好久天,没有结果。

以上是关于像python操作attr一样在go里用reflect 操作field (标题与内容弱相关)的主要内容,如果未能解决你的问题,请参考以下文章

是否有可能在Python attr中自引用类型

Blazor 是不是有一些机制像 vue 中的 $attrs 一样工作?

像go 一样 打造.NET 单文件应用程序的编译器项目bflat 发布 7.0版本

golang怎么引入其它文件,像php的include一样

像 Awesome-Go 一样提升企业 Go 项目代码质量

代码规范和Android项目中的一些可用工具