go程序设计语言第十二章-反射

the go programming language

Go提供一种机制,能够在运行时更新变量、查看它们的值,调用它们的方法和它们内在的操作,所有的这些都不需要在编译时知道它们的类型。这种机制就叫做反射reflection。反射同样使我们将他们自身的类型type看作是第一类值(first-class value)。

在这一章中,我们将会探讨反射的特性来查看它们是如何增强语言的表现力,并且在两个重要的API中是如何使用反射的:fmt提供的string formatting和encoding\json和encoding/xml包提供的protocol encoding。反射对于text/template和html/template这两个模板机制也同样重要。
However, reflection is complex to reason about and not for casual use, 因此尽管这些包使用了反射,但并没有在接口API中暴露出反射。

12.1 为何需要反射

有时我们需要编写一个函数,它可以处理各种类型,如不满足一个常规接口、没有一个具体表达、或者在设计函数时并不存在此类型。
一个类似的例子是fmt.Fprintf中的格式化逻辑,它可以打印出任意类型的值,甚至是一个用户定义的类型。让我们来实现这样一个函数。为了简化,函数接收一个参数,返回一个类似fmt.Sprint函数样式的string类型,函数名为Sprint。

以type switch开始,来测试是否参数实现了String方法,如果是则调用。然后增加switch case语句,检查动态类型是否和basic types是否匹配–string, int, bool等等。

func Sprintf(x interface{}) string {
	type stringer interface {
		String() string
	}

	switch x := x.(type) {
	case stringer:
		return x.String()
	case string:
		return x
	case int:
		return strconv.Itoa(x)
	// ... similar cases for int16, unint32, and so on ...
	case bool:
		if x {
			return "true"
		}
		return "false"
	default:
		// array, chan, func, map, pointer, slice, struct
		return "?"
	}
}

但是我们如何处理其他的类型,比如[]float64, map[string][]string 等等?可以增加case语句,但是这种类型的数量是无限的。并且如何处理有名类型,如url.Values?尽管type switch可以有一个和它的底层类型map[string][]string相同的case语句,但是它不会匹配到url.Values,因为这两种类型并不是完全相同的,并且type switch也不会为每个类似url.Values这样的类型写一个case语句,因为这将依赖它们所在定义的库。

有没有一种方法能够在不知道类型时就可以探测内部的值?这就是反射。

一个函数内部对参数x的判断,不能尽然展示出所有类型type。

12.2 reflect.Type和reflect.Value

反射由reflect包提供,有两个重要类型reflect.Type和reflect.Value。一个 Type是一个接口,有很多方法来区分类型以及检查它们的组成部分,如结构体成员或函数的参数。
The sole implementation of reflect.Type is the type descriptor (§7.5), the same
entity that identifies the dynamic type of an interface value.
唯一能反映 reflect.Type 实现的是接口的类型描述信息(§7.5),也正是这个实体标识了接口值的动态类型。

type Type interface {
	// Methods applicable to all types.

	// Align returns the alignment in bytes of a value of
	// this type when allocated in memory.
	Align() int

	// FieldAlign returns the alignment in bytes of a value of
	// this type when used as a field in a struct.
	FieldAlign() int

	// Method returns the i'th method in the type's method set.
	// It panics if i is not in the range [0, NumMethod()).
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver,
	// and only exported methods are accessible.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	//
	// Methods are sorted in lexicographic order.
	Method(int) Method

	// MethodByName returns the method with that name in the type's
	// method set and a boolean indicating if the method was found.
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	MethodByName(string) (Method, bool)

	// NumMethod returns the number of methods accessible using Method.
	//
	// Note that NumMethod counts unexported methods only for interface types.
	NumMethod() int

	// Name returns the type's name within its package for a defined type.
	// For other (non-defined) types it returns the empty string.
	Name() string

	// PkgPath returns a defined type's package path, that is, the import path
	// that uniquely identifies the package, such as "encoding/base64".
	// If the type was predeclared (string, error) or not defined (*T, struct{},
	// []int, or A where A is an alias for a non-defined type), the package path
	// will be the empty string.
	PkgPath() string

	// Size returns the number of bytes needed to store
	// a value of the given type; it is analogous to unsafe.Sizeof.
	Size() uintptr

	// String returns a string representation of the type.
	// The string representation may use shortened package names
	// (e.g., base64 instead of "encoding/base64") and is not
	// guaranteed to be unique among types. To test for type identity,
	// compare the Types directly.
	String() string

	// Kind returns the specific kind of this type.
	Kind() Kind

	// Implements reports whether the type implements the interface type u.
	Implements(u Type) bool

	// AssignableTo reports whether a value of the type is assignable to type u.
	AssignableTo(u Type) bool

	// ConvertibleTo reports whether a value of the type is convertible to type u.
	// Even if ConvertibleTo returns true, the conversion may still panic.
	// For example, a slice of type []T is convertible to *[N]T,
	// but the conversion will panic if its length is less than N.
	ConvertibleTo(u Type) bool

	// Comparable reports whether values of this type are comparable.
	// Even if Comparable returns true, the comparison may still panic.
	// For example, values of interface type are comparable,
	// but the comparison will panic if their dynamic type is not comparable.
	Comparable() bool

	// Methods applicable only to some types, depending on Kind.
	// The methods allowed for each kind are:
	//
	//	Int*, Uint*, Float*, Complex*: Bits
	//	Array: Elem, Len
	//	Chan: ChanDir, Elem
	//	Func: In, NumIn, Out, NumOut, IsVariadic.
	//	Map: Key, Elem
	//	Ptr: Elem
	//	Slice: Elem
	//	Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

	// Bits returns the size of the type in bits.
	// It panics if the type's Kind is not one of the
	// sized or unsized Int, Uint, Float, or Complex kinds.
	Bits() int

	// ChanDir returns a channel type's direction.
	// It panics if the type's Kind is not Chan.
	ChanDir() ChanDir

	// IsVariadic reports whether a function type's final input parameter
	// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
	// implicit actual type []T.
	//
	// For concreteness, if t represents func(x int, y ... float64), then
	//
	//	t.NumIn() == 2
	//	t.In(0) is the reflect.Type for "int"
	//	t.In(1) is the reflect.Type for "[]float64"
	//	t.IsVariadic() == true
	//
	// IsVariadic panics if the type's Kind is not Func.
	IsVariadic() bool

	// Elem returns a type's element type.
	// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
	Elem() Type

	// Field returns a struct type's i'th field.
	// It panics if the type's Kind is not Struct.
	// It panics if i is not in the range [0, NumField()).
	Field(i int) StructField

	// FieldByIndex returns the nested field corresponding
	// to the index sequence. It is equivalent to calling Field
	// successively for each index i.
	// It panics if the type's Kind is not Struct.
	FieldByIndex(index []int) StructField

	// FieldByName returns the struct field with the given name
	// and a boolean indicating if the field was found.
	FieldByName(name string) (StructField, bool)

	// FieldByNameFunc returns the struct field with a name
	// that satisfies the match function and a boolean indicating if
	// the field was found.
	//
	// FieldByNameFunc considers the fields in the struct itself
	// and then the fields in any embedded structs, in breadth first order,
	// stopping at the shallowest nesting depth containing one or more
	// fields satisfying the match function. If multiple fields at that depth
	// satisfy the match function, they cancel each other
	// and FieldByNameFunc returns no match.
	// This behavior mirrors Go's handling of name lookup in
	// structs containing embedded fields.
	FieldByNameFunc(match func(string) bool) (StructField, bool)

	// In returns the type of a function type's i'th input parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumIn()).
	In(i int) Type

	// Key returns a map type's key type.
	// It panics if the type's Kind is not Map.
	Key() Type

	// Len returns an array type's length.
	// It panics if the type's Kind is not Array.
	Len() int

	// NumField returns a struct type's field count.
	// It panics if the type's Kind is not Struct.
	NumField() int

	// NumIn returns a function type's input parameter count.
	// It panics if the type's Kind is not Func.
	NumIn() int

	// NumOut returns a function type's output parameter count.
	// It panics if the type's Kind is not Func.
	NumOut() int

	// Out returns the type of a function type's i'th output parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumOut()).
	Out(i int) Type

	common() *rtype
	uncommon() *uncommonType
}

type Value struct {
	// typ holds the type of the value represented by a Value.
	typ *rtype

	// Pointer-valued data or, if flagIndir is set, pointer to data.
	// Valid when either flagIndir is set or typ.pointers() is true.
	ptr unsafe.Pointer

	// flag holds metadata about the value.
	// The lowest bits are flag bits:
	//	- flagStickyRO: obtained via unexported not embedded field, so read-only
	//	- flagEmbedRO: obtained via unexported embedded field, so read-only
	//	- flagIndir: val holds a pointer to the data
	//	- flagAddr: v.CanAddr is true (implies flagIndir)
	//	- flagMethod: v is a method value.
	// The next five bits give the Kind of the value.
	// This repeats typ.Kind() except for method values.
	// The remaining 23+ bits give a method number for method values.
	// If flag.kind() != Func, code can assume that flagMethod is unset.
	// If ifaceIndir(typ), code can assume that flagIndir is set.
	flag

	// A method value represents a curried method invocation
	// like r.Read for some receiver r. The typ+val+flag bits describe
	// the receiver r, but the flag's Kind bits say Func (methods are
	// functions), and the top bits of the flag give the method number
	// in r's type's method table.
}

reflect.TypeOf()函数接收一个interface{}类型,返回reflect.Type类型,其中包含了它的动态类型。

t := reflect.Typeof(3)   // a reflect.Type
fmt.Println(t.String())  // "int" 
fmt.Println(t)                // "int"

TypeOf(3) 将3赋值给interface{}参数,涉及到隐式接口转换,将一个具体类型转换为接口类型,转后后的接口值将包含动态类型(int)和动态值(3).
因为Typeof函数返回了接口的动态类型,因此它总是返回一个具体类型。例如下面代码打印出“*os.File”而不是“io.Write”。稍后看到reflect.Type也可以代表接口类型。

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w))   // "*os.File"

注意reflect.Type满足fmt.Stringer接口,因为打印一个接口的动态类型很有用,fmt.Printf提供一个简写%T,内部就是使用reflect.TypeOf:
fmt.Printf("%T\n", 3) // “int"

【总结,reflect.TypeOf()接收interface{}, 返回Type类型,其中包裹着动态类型,也可以是接口类型】

包中另一个重要的类型就是Value,它可以持有任何类型的值。reflect.ValueOf函数接收一个interface{}类型,返回一个包含动态值的reflect.Value类型。同reflect.TypeOf类似,它也总是返回一个具体类型的值,同时也可以返回一个接口值。
【总结,reflect.ValueOf()接口interface{}, 返回Value类型,其中包裹动态类型,也可以是接口类型】

同reflect.TypeOf一样,它也满足fmt.Stringer, 但是除非是包裹string,否则String方法只显示类型。可以使用fmt包的%v形式来显示具体的值:

v := reflect.ValueOf(3)  // a reflect.Value
fmt.Println(v)				// "3"
fmt.Printf("%v\n", v)    // "3"
fmt.Println(v.String())   // "<int Value>"

在Value对象上调用Type方法返回reflect.Type类型:

t := v.Type()     // a reflec.Type
fmt.Println(t.String())  // "int"

与reflect.ValueOf相反的方法是reflect.Value.Interface方法,它将返回一个包裹值的interface{}类型。

v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface()          // an interface{}
i := x.(int)
fmt.Println("%d\n", i)

reflect.Value和interface{}都可以保存任意值,不同之处在于空接口隐藏了值的操作,没有保留其方法,除非我们知道它的动态类型而使用类型断言。相反的,一个Value有许多方法可以探测其内容,而不必知道它的类型。让我们使用这些方法来应用于一个通用的formatting方法,我们称之为format.Any.

不使用type-swich,而采用reflect.Value的Kind方法来区分各种case语句。虽然type的种类是无限的,但type所属的kind是有限的:基本类型bool,string,数字;聚合类型Array和Struct;引用类型Chan、Func、Ptr、Slice和Map;接口类型;最后是Invalid类型,意味着没有值(reflect.Value的零值的kind就是Invalid)。

func formatAtom(v reflect.Value) string {
	switch v.Kind() {
	case reflect.Invalid:
		return "Invalid"
	case reflect.Int, reflect.Int8, reflect.Int16,
		reflect.Int32, reflect.Int64:
		return strconv.FormatInt(v.Int(), 10)
	case reflect.Uint, reflect.Uint8, reflect.Uint16,
		reflect.Uint32, reflect.Uint64:
		return strconv.FormatUint(v.Uint(), 10)
	// ... floating-point and complex cases omitted for brevity...
	case reflect.Bool:
		return strconv.FormatBool(v.Bool())
	case reflect.String:
		return strconv.Quote(v.String())
	case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
		return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
	default: // reflect.Array, reflect.Struct, reflect.Interface
		return v.Type().String() + " value"
	}
}

func Any(value interface{}) string {
	return formatAtom(reflect.ValueOf(value))
}

函数将每个value看作是不可分割的,且并不了解它的内在结构,因此函数名称为formatAtom。对于聚合类型Array、Struct和接口类型,只打印其type类型,对于引用类型(chan、function、ptr、slice、map),打印type类型和16进制的引用地址。功能虽然不够完美,但也是一个很大的提升。这因为Kind只和底层类型(underlying representation)有关,因此函数也可以接收命名类型:

	var xx int64 = 1
	var dd time.Duration = 1 * time.Nanosecond
	fmt.Println(Any(xx))								// "1"
	fmt.Println(Any(dd))								// "1"
	fmt.Println(Any([]int64{xx}))					// "[]int64 0xc00000a220"
	fmt.Println(Any([]time.Duration{dd}))   // "[]time.Duration 0xc00000a228"

12.3 递归展示一个Value

接下来我们让我们来看一下如何优化复杂类型的打印。不完全使用fmt.Sprint,我们创建一个Dispaly函数,接收任意一个复杂类型,打印它内在结构,输出路径下的每个元素。

尽可能的包的API中使用反射。将定义一个未导出函数display来做真正的递归,而导出函数Display将接收一个interface{}参数。

func Display(name string, x interface{}) {
	fmt.Printf("Display %s (%T):\n", name, x)
	display(name, reflect.ValueOf(x))
}

func display(path string, v reflect.Value) {
	switch v.Kind() {
	case reflect.Invalid:
		fmt.Printf("%s == invalid\n", path)
	case reflect.Slice, reflect.Array: // Len() and Index()
		for i := 0; i < v.Len(); i++ {
			display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
		}
	case reflect.Struct: // 使用Type().Field(i).Name才能获取到字段名
		for i := 0; i < v.NumField(); i++ {
			fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
			display(fieldPath, v.Field(i))
		}
	case reflect.Map:
		for _, key := range v.MapKeys() { // 一个由key组成的slice, 其中key也是Value类型
			display(fmt.Sprintf("%s[%s]", path, formatAtom(key)), v.MapIndex(key))
		}
	case reflect.Ptr: // Elem()获取指针指向的对象?
		if v.IsNil() {
			fmt.Printf("%s == nil\n", path)
		} else {
			display(fmt.Sprintf("(*%s)", path), v.Elem())
		}
	case reflect.Interface: // 是接口时也使用Elem()方法
		if v.IsNil() {
			fmt.Printf("%s == nil\n", path)
		} else {
			fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
			display(path+".value", v.Elem())
		}
	default:  //basic types, channels, funcs
		fmt.Printf("%s = %s\n", path, formatAtom(v))
	}
}
type Movie struct {
		Title, Subtitle string
		Year int
		Color bool
		Actor map[string]string
		Oscars []string
		Sequel *string
	}

	strangelove := Movie{
		Title: "Dr. Strangelove",
		Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
		Year: 1964,
		Color: false,
		Actor: map[string]string{
			"Dr. Strangelove": "Peter Sellers",
			"Grp. Capt. Lionel Mandrake": "Peter Sellers",
			"Pres. Merkin Muffley": "Peter Sellers",
			"Gen. Buck Turgidson": "George C. Scott",
			"Brig. Gen. Jack D. Ripper": "Sterling Hayden",
			`Maj. T.J. "King" Kong`: "Slim Pickens",
		},
		Oscars: []string{
			"Best Actor (Nomin.)",
			"Best Adapted Screenplay (Nomin.)",
			"Best Director (Nomin.)",
			"Best Picture (Nomin.)",
		},
	}

	Display("strangelove", strangelove)

结果如下:

Display strangelove (main.Movie):
strangelove.Title = "Dr. Strangelove"
strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
strangelove.Year = 1964
strangelove.Color = false
strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
strangelove.Oscars[0] = "Best Actor (Nomin.)"
strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
strangelove.Oscars[2] = "Best Director (Nomin.)"
strangelove.Oscars[3] = "Best Picture (Nomin.)"
strangelove.Sequel == nil

接下来查看各个case块:
Slices and arrays: 这两者的逻辑相同。Len()方法获取元素数量,Index(i)获取元素,取到的元素类型为reflect.Value类型。如果i超出则报panic。这两者类似于内置的len(a)和a[i]。函数中对于每个元素再递归display它自身,将”【i】“添加到path中。
尽管reflect.Value有很多方法,但只有一部分对于任意值是安全使用的。例如Index()函数只能在属于Slice,Array和String上的kind种类上调用,在另外kind上调用则产生panic。

Structs: NumField()函数返回结构体字段个数,Field(i)返回第i个字段值且为reflect.Value类型。fields列表包含了其中的匿名字段。为了在路径中使用”.f“形式,我们碧玺使用reflect.Type来获取字段名。

Maps: Mapkeys()方法返回类型为reflect.Values类型的slice,一个map的key对应一个元素。遍历的顺序是不确定的。MapIndex(key)返回key对应的值。我们将”【key】“形式添加到path中(这里走了一个捷径,map的key类型不严格满足formatAtom函数能处理的类型;arrays,structs和interfaces都可以称为map的key。扩展这种情况键12.1练习)。

Pointers: Elem()函数返回被一个指针指向的变量,也是一个reflect.Value类型。即使指针为nil这个操作也是安全的,其结果的kind类型为Invalid,但是我们使用IsNil来判断nil指针以便来输出更多合适的信息。将”*“放在path前来避免歧义。

interfaces: 同样的使用IsNil来判断接口是否为nil,如果不为nil,使用v.Elem()找回它的动态类型,打印它的type和value。

可以使用Display显示任意类型的内部结构,如*os.File:

Display("os.Stderr", os.Stderr)
// Output:
// Display os.Stderr (*os.File):
// (*(*os.Stderr).file).fd = 2
// (*(*os.Stderr).file).name = "/dev/stderr"
// (*(*os.Stderr).file).nepipe = = 0

我的centos显示:

Display("strangelove", strangelove)

Display os.Stderr (*os.File):
(*(*os.Stderr).file).pfd.fdmu.state = 0
(*(*os.Stderr).file).pfd.fdmu.rsema = 0
(*(*os.Stderr).file).pfd.fdmu.wsema = 0
(*(*os.Stderr).file).pfd.Sysfd = 2
(*(*os.Stderr).file).pfd.pd.runtimeCtx = uintptr value
(*(*os.Stderr).file).pfd.iovecs == nil
(*(*os.Stderr).file).pfd.csema = 0
(*(*os.Stderr).file).pfd.isBlocking = 1
(*(*os.Stderr).file).pfd.IsStream = true
(*(*os.Stderr).file).pfd.ZeroReadIsEOF = true
(*(*os.Stderr).file).pfd.isFile = true
(*(*os.Stderr).file).name = "/dev/stderr"
(*(*os.Stderr).file).dirinfo == nil
(*(*os.Stderr).file).nonblock = false
(*(*os.Stderr).file).stdoutOrErr = true
(*(*os.Stderr).file).appendMode = false

注意到即使是未导出的字段对于反射来讲也是可见的。例子中的输出可能随着平台不同而不同(这也是为什么他们是私有的)。甚至可以i将Display应用到reflect.Value上,查看*os.File类型的内部结构。结果如下,同样输出结果可能会不同:

Display rV (reflect.Value):
(*rV.typ).size = 8
(*rV.typ).hash = 871609668
(*rV.typ).align = 8
(*rV.typ).fieldAlign = 8
(*rV.typ).kind = 22
(*(*rV.typ).string) = "*os.File"

我的windows上结果:

Display rv (reflect.Value):
(*rv.typ).size = uintptr value
(*rv.typ).ptrdata = uintptr value
(*rv.typ).hash = 871609668
(*rv.typ).tflag = 9
(*rv.typ).align = 8
(*rv.typ).fieldAlign = 8
(*rv.typ).kind = 54
(*rv.typ).equal = func(unsafe.Pointer, unsafe.Pointer) bool 0x92ca0
(*(*rv.typ).gcdata) = 1
(*rv.typ).str = 4666
(*rv.typ).ptrToThis = 0
rv.ptr = unsafe.Pointer value
rv.flag = reflect.flag value

比较两个例子的不同:

var i interface{} = 3
Display("i", i)
// Output:
// Display i (int):
// i = 3

Display("&i", &i)
// Output:
// Display &i (*interface {}):
// (*&i).type = int
// (*&i).value = 3

在第一个例子中,Display调用reflect.ValueOf(i), 返回的是kind Int。reflect.ValueOf总是返回一个具体类型的值,它是从接口值中取出内容。
【因为Kind返回的是底层类型,尽管i是interface类型,它的底层是int】
在第二个例子中,Display调用reflect.ValueOf(&i), 它返回一个指向i的指针,kind种类是Ptr。switch语句中Ptr调用Elem()函数,返回一个Value类型,代表变量i本身,kind种类是Interface。
【ptr上调用Elem(),返回的是变量i本身的类型,即interface】
像这样间接地获得一个Value值,可以表示任意值,包括interface。display函数再递归地调用它自己,接着它打印出接口的动态类型和动态值。

当下Didplay函数遇到一个循环对象时将不会停止,例如下面这样:

// a struct that points to itself
type Cycle struct{ Value int; Tail *Cycle }
var c Cycle
c=Cycle{42, &c}
Display("c", c)

许多Go语言程序都包含了一些循环的数据。让Display支持这类带环的数据结构需要些技巧,需要额外记录迄今访问的路径;相应会带来成本。通用的解决方案是采用 unsafe 的语言特性,我们将在13.3节看到具体的解决方案。

带环的数据结构很少会对fmt.Sprint函数造成问题,因为它很少尝试打印完整的数据结构。例如,当它遇到一个指针的时候,它只是简单地打印指针的数字值。在打印包含自身的slice或map时可能卡住,但是这种情况很罕见,不值得付出为了处理回环所需的开销。

12.4 样例:编码S表达式

Display是一个展示结构化数据的debug程序,但它不能以简单的符号来编码或者marshal任意go对象为message, 而这在进程间通信中是很合适的。

go标准库支持很多形式,如JSON、XMl、ASN.1,但不支持Lisp语法的S-expression。因为它没有一个普遍的标准规范。
定义一个包,可以将任意go对象转换为S表达式,遵循一下结构:

42			integer
"hello"		string(with Go-style quotation)
foo			symbol(an unquoted name)
(1, 2, 3)	list(zero or more items enclosed in parentheses)

布尔类型使用symbol类型的t表示true值,空list ()或者symbol类型的bil来表示false,不过为了简单处理,我们的程序忽略他们。程序也胡磊channel和function, 因为它们的状态对于反射是不透明的。它也忽视real和复数、浮点数和接口。联系12.3将增加他们的支持。
如下是使用S表达式来表明Go类型。整数和strings以明显的方式编码。Nil值被编码为symbol的nil。array和slice使用列表记号来表示。
struct被编码为field binding的列表,每个field binging 是一个拥有两个元素的list,第一个元素(symbol类型)是字段名,第二个元素是字段值。map被编码为pair的列表,每个pair是map的key和value。通常,s表达式使用一个单独的cons cell(key . value)来表示每个pair,而不是两个元素的列表,但是为了简单起见,我们忽视点好标记符。

func encode(buf *bytes.Buffer, v reflect.Value, n int) error {
	switch v.Kind() {
	case reflect.Invalid:
		buf.WriteString("nil")
	case reflect.Int, reflect.Int8, reflect.Int16,
		reflect.Int32, reflect.Int64:
		fmt.Fprintf(buf, "%d", v.Int())
	case reflect.Uint, reflect.Uint8, reflect.Uint16,
		reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		fmt.Fprintf(buf, "%d", v.Uint())
	case reflect.String:
		fmt.Fprintf(buf, "%q", v.String())
	case reflect.Bool:
		if v.Bool() {
			buf.WriteByte('t')
		} else {
			buf.WriteString("nil")
		}
	case reflect.Float32, reflect.Float64:
		fmt.Fprintf(buf, "%f", v.Float())
	case reflect.Ptr:
		return encode(buf, v.Elem(), n)
	// array、slice、struct这三个的每个pari对需要换行
	case reflect.Array, reflect.Slice: // (value ...)
		buf.WriteByte('(')
		for i := 0; i < v.Len(); i++ {
			if i > 0 {
				//buf.WriteByte(' ')
				buf.WriteByte('\n')
				buf.WriteString(createNSpacke(n+1))
			}
			if err := encode(buf, v.Index(i), n); err != nil {
				return err
			}
		}
		buf.WriteByte(')')
	case reflect.Struct: // ((name value) ...)  // 每个struct filed后肯定换行, 聚合类型使用括号
		buf.WriteByte('(')
		for i := 0; i < v.NumField(); i++ {
			// 从第2项开始,换行且缩进
			if i > 0 {
				//buf.WriteByte(' ')
				buf.WriteByte('\n')
				buf.WriteString(createNSpacke(n+1))
			}
			name_pre := v.Type().Field(i).Name
			fmt.Fprintf(buf, "(%s ", v.Type().Field(i).Name)
			if err := encode(buf, v.Field(i), n+1+1+len(name_pre)+1); err != nil {
				return err
			}
			buf.WriteByte(')')
		}
		buf.WriteByte(')')

	case reflect.Map: // ((key value) ...)
		buf.WriteByte('(')
		for i, key := range v.MapKeys() {
			if i > 0 {
				//buf.WriteByte(' ')
				buf.WriteByte('\n')
				buf.WriteString(createNSpacke(n+1))
			}
			buf.WriteByte('(')

			// 这里假设key是string
			if key.Kind() != reflect.String {
				return fmt.Errorf("unsupported map.key type: %s", key.Type())
			}

			if err := encode(buf, key, n+1+1); err != nil {
				return err
			}

			// 这就是实际写入的内容
			str_pre := fmt.Sprintf("%q", key.String())

			buf.WriteString(": ")
			if err := encode(buf, v.MapIndex(key), n+1+1+len(str_pre)+2); err != nil {
				return err
			}
			buf.WriteByte(')')
		}
		buf.WriteByte(')')
	case reflect.Interface:
		buf.WriteByte('(')
		int_pre := fmt.Sprintf("%q", v.Elem().Type())
		fmt.Fprintf(buf, "%s ", int_pre)

		//buf.WriteString(v.Elem().Type().String())
		if err := encode(buf, v.Elem(), n+1+len(int_pre)+1); err != nil {
			return err
		}
		buf.WriteByte(')')
	default: // complex,  chan, func, interface
		return fmt.Errorf("unsupported type: %s", v.Type())
	}
	return nil
}

func Marshal(v interface{}) ([]byte, error) {
	var buf bytes.Buffer
	if err := encode(&buf, reflect.ValueOf(v), 0); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func createNSpacke(n int) string {
	rst := ""
	for i := 0; i < n; i++ {
		rst += " "
	}
	return rst
}
if rst, err := Marshal(strangelove); err == nil {
	fmt.Println(string(rst))
}

输出结果如下:

((Title "Dr. Strangelove")
 (Subtitle "How I Learned to Stop Worrying and Love the Bomb")
 (Year 1964)
 (Color nil)
 (Actor (("Grp. Capt. Lionel Mandrake": "Peter Sellers")
         ("Pres. Merkin Muffley": "Peter Sellers")
         ("Gen. Buck Turgidson": "George C. Scott")
         ("Brig. Gen. Jack D. Ripper": "Sterling Hayden")
         ("Maj. T.J. \"King\" Kong": "Slim Pickens")
         ("Dr. Strangelove": "Peter Sellers")))
 (Oscars ("Best Actor (Nomin.)"
          "Best Adapted Screenplay (Nomin.)"
          "Best Director (Nomin.)"
          "Best Picture (Nomin.)"))
 (Sequel nil)
 (Zepo ("[]int" (1
                 3
                 4)))
 (Score 12.340000)
 (Actor2 (("Dr. Strangelove2": (1
                                2
                                3))
          ("Maj. T.J. \"King\" Kong": (4
                                       5
                                       6)))))

Process finished with the exit code 0

我这里已经实现输出结果四聚合类型时,从第二个element开始会换行,并和第一个element元素的位置对齐(思路是: 每当encode一个Value时先指定要缩进多少,在输出聚合类型的第二个元素时,缩进的大小就是此Value的缩进n+小括号的占位长度1)。
这里map类型的key只处理了为string时的情况,另外的情况之后再补充(其他情况,如果key有换行,则key对应的value要进行encode时,缩进的字节数n要加上key打印出的最后一行占用的长度,比较复杂)。

12.5 通过reflect.Value 修改变量

到目前为止,反射只是读取值(interpret values)的方式,本章将修改这些值(change them)
一些go表达式如x, x.f[1], *p能表达式变量,而另一些如x+1, f(2)则不能表示变量。一个变量是一个可寻址的存储位置,包含了一个值,通过地址可以改变这个值。

reflect.Values也类似,一些可寻址,一些不可寻址。

x := 2 // value type variable?
a := reflect.ValueOf(2) // 2 int no
b := reflect.ValueOf(x) // 2 int no
c := reflect.ValueOf(&x) // &x *int no
d := c.Elem() // 2 int yes (x)

a中的值不可寻址,它只是整数2的一个副本。b也同样如此。c也是不可寻址,它只是指针值&x的副本。事实上,通过reflect.ValueOf(x)返回的Value类型都不可寻址。但是从c衍生出的d是可寻址的,因为它解引用了c内部的指针,指向了一个变量。我们可以使用这种方式,调用reflect.ValueOf(&x).Elem()来获取任意变量x对应的可取地址的Value。

通过CanAddr函数能够获取一个Value对象是否可寻址:

fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"

每当我们通过指针都可以间接地获取一个可寻址的reflect.Value对象,即使开始的是一个不可取地址的Value。在反射机制中,所有关于是否支持取地址的规则都是类似的。例如,因为slice的index表达式e[i]隐式地使用指针,那么即使e是不可寻址的,e[i]照样是可寻址的。类似的,reflect.ValueOf(e).Index(i)指向一个变量,因此即使reflect.ValueOf(e)是不可寻址的,它照样是可寻址的。

从一个可寻址的reflect.Value来recover 变量需要三步。首先,调用Addr()返回一个持有指针的Value类型,指针指向此变量。然后,在返回的Value上调用Interface(),返回一个包含此指针的interface{}。最后,如果我们知道变量类型,可以使用类型断言来找回interface中的内容,作为一个普通的指针。我们就可以通过此指针更新此变量:

x := 2
d := reflect.ValueOf(&x).Elem() // d refers to the variable x
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
fmt.Println(x) // "3"

或者,我们不使用指针,通过调用reflect.Value.Set()方法,直接更新一个可寻址的reflect.Value类型中包裹的变量。

d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4"

可赋值性约束检查通常由编译执行,但set方法将在运行时执行此检查。上文中,变量和value都是int类型,但如果变量为int64,程序将会panic,因此必须确保值可以被赋值给此类型的变量。

d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int

当然在不可寻址的Value类型上调用Set方法也会发生错误:

x := 2
b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3))  // panic : Set using unaddressable value

对于基本类型有多种Set方法,如SetInt, SetUint, SetString, SetFloat等等

d := reflect.ValueOf(&x).Elem()
d.SetInt(3)
fmt.Println(x) // "3

在某些方面,这些方法是比较宽容的。例如SetInt方法,只要变量类型是某种有符号数,或者是一个有名类型且底层类型是有符号数,都会成功。如果值太大会进行静默截断。但是请小心:在指向一个interface{}的Value上调用SetInt会panic,尽管调用Set会成功。

x := 1
rx := reflect.ValueOf(&x).Elem()
rx.SetInt(2) // OK, x = 2
rx.Set(reflect.ValueOf(3)) // OK, x = 3
rx.SetString("hello") // panic: string is not assignable to int
rx.Set(reflect.ValueOf("hello")) // panic: string is not assignable to int

var y interface{}
ry := reflect.ValueOf(&y).Elem()
ry.SetInt(2) // panic: SetInt called on interface Value
ry.Set(reflect.ValueOf(3)) // OK, y = int(3)
ry.SetString("hello") // panic: SetString called on interface Value
ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"

【总结】: SetInt()或SetString()这种待具体类型的Setxxx方法,函数类型和Value的类型必须一致。而Set()方法不需要,会内部进行转换。

当我们使用Display作用于os.Stdout时,发现反射可以读取到其中未导出的字段,比如Unix平台上os.Field结构体中int类型的fd字段。但是反射不能更新这些值:

stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var
fmt.Println(stdout.Type()) // "os.File"
fd := stdout.FieldByName("fd")
fmt.Println(fd.Int()) // "1"
fd.SetInt(2) // panic: unexported field

一个可寻址的reflect.Value会记录一个成员是否为未导出的成员,如果是,则不允许修改它的值。因此,CanAddr并不是在改变一个变量时正确的检查方法,与之相关方法CanSet记录了是否一个Value是可寻址的且可设置值的:
fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"

12.6 样例: 解码S-Expressions

对于标准包encoding/…packages中的每个Marshal函数,都有一个相对应的Unmarshal函数来进行解码。例如,对于包含一个JSON编码的byte slice,解码如下:

data := []byte(/* ... */)
var movie Movie
err := json.Unmarshal(data, &movie)

Unmarshal函数使用反射来改变movie变量的各个字段值,根据输入的data和Movie类型,创建新的map, struct,slice。

现在让我们实现一个简单的Unmarshal函数作用在S表达式,类似于上面的json.Unmarshal函数。必须注意,一个健壮的、通用的解码实现要求比例子中更多的代码,因此我们采用了精简的实现。我们只支持S表达式有限的子集并且处理错误不够优雅。样例是为了说明反射的用法,不是构造一个解码器。

词法分析器使用text/scanner包的Scanner类型和将输入字节流转化为类似注释、标记符、字符串字面量、数字字面量的标记序列。scanner的Scan()方法提前扫描并返回下一个标记的kind种类,其type是rune(或者翻译成对于rune类型?)。大多数的标记,比如’(', 包含一个rune,但是text/scanner包也可以用一个rune类型的较小的负数表示多字符的标记,如Ident,String,Int。调用Scan方法返回这些记号的kind,TokenText方法返回记号的text内容。

由于一个解析器需要多次探测当前的token记号,但是Scan方法提前调用scanner, 我们将scanner放在一个lexer类型中,追踪记录当前Scan方法返回的token。

type lexer struct{
	scan scanner.Scanner
	token rune
}

func (lex *lexer) next() { lex.token = lex.scan.Scan() }
func (lex *lexer) text() string { return lex.scan.TokenText() }
func (lex *lexer) consume(want rune) {
	if lex.token != want {
		panic(fmt.Sprintf("got %q, want %q", lex.text(), want))
	}
	lex.next()
}
// Unmarshal out是一个非nil指针
func Unmarshal(data []byte, out interface{}) (err error) {
	lex := &lexer{scan: scanner.Scanner{Mode: scanner.GoTokens}}
	lex.scan.Init(bytes.NewReader(data))
	lex.next()
	defer func() {
		if x := recover(); x != nil {
			err = fmt.Errorf("error at %s: %v", lex.scan.Position, x)
		}
	}()
	read(lex, reflect.ValueOf(out).Elem()) // 获取指针所对应的变量
	return nil
}

// read中v已经称为可修改的变量
func read(lex *lexer, v reflect.Value) {
	switch lex.token {
	case scanner.Ident:
		// go 的identify, 如果按照前面编码s表达式,有fieldname、nil、t这三个
		if lex.text() == "nil" {
			v.Set(reflect.Zero(v.Type()))
			lex.next()
			return
		}
	case scanner.String:
		s, _ := strconv.Unquote(lex.text())
		v.SetString(s)       // 这里没有校验v的kind,因为调用read时v会匹配?
		lex.next()
		return
	case scanner.Int:
		i, _ := strconv.Atoi(lex.text())
		v.SetInt(int64(i)) // 这里使用int64为什么?
		lex.next()
		return
	case '(': // Scan()方法返回rune类型,可能为其中的枚举值或者其他的unicode charactor
		lex.next()
		readList(lex, v)  // 发现小括号,读取整数列表
		lex.next()        // consume ')'
		return
	}
	panic(fmt.Sprintf("unexpected token %q", lex.text())) // 其他的字符不识别

}

func readList(lex *lexer, v reflect.Value) {
	switch v.Kind() {
	case reflect.Array:
		for i := 0; !endList(lex); i++ {
			read(lex, v.Index(i))
		}
	case reflect.Slice:
		for !endList(lex) {
			item := reflect.New(v.Type().Elem()).Elem()
			read(lex, item)
			v.Set(reflect.Append(v, item))
		}
	case reflect.Struct:
		for !endList(lex) {
			lex.consume('(')
			if lex.token != scanner.Ident{  // struct的field字段名称
				panic(fmt.Sprintf("got token %q, want field name", lex.text()))
			}
			name := lex.text()              // 获取field字段名
			lex.next()
			read(lex, v.FieldByName(name))  // 根据字段名获取field类型的Value,使用read设置其值
			lex.consume(')')
		}
	case reflect.Map:
		v.Set(reflect.MakeMap(v.Type()))
		for !endList(lex) {
			lex.consume('(')
			key := reflect.New(v.Type().Key()).Elem()
			read(lex, key)
			value := reflect.New(v.Type().Elem()).Elem()
			read(lex, value)
			v.SetMapIndex(key, value)
			lex.consume(')')
		}
	default:
		panic(fmt.Sprintf("cannot decode list info %v", v.Type()))
	}

}

func endList(lex *lexer) bool {
	switch lex.token {
	case scanner.EOF:
		panic("end of file")
	case ')':
		return true
	}
	return false
}

12.7 获取结构体Tags

在4.5节我们使用结构体的field tags来更改JSON编码。json field tag让我们选择字段名称和禁止空字段导出。本章我们将使用反射获取tag。
在web server中,http handler函数的第一件事是取出请求参数保存到本地变量中。我们将定义一个有效的函数,params.Unpack,使用结构体的tag标签来使HTTP handler更有效。

func search(resp http.ResponseWriter, req *http.Request) {
	var data struct {
		Lables []string `http:"l"`
		MaxResults int  `http:"max"`
		Exact      bool `http:"x"`
	}
	data.MaxResults = 10
	if err := MyUnpack(req, &data); err != nil {
		http.Error(resp, err.Error(), http.StatusBadRequest)
	}
	fmt.Fprintf(resp, "Search: %+v\n", data)
}

func MyUnpack(req *http.Request, ptr interface{}) error {
	if err := req.ParseForm(); err != nil {
		return err
	}

	fields := make(map[string]reflect.Value)

	v := reflect.ValueOf(ptr).Elem() // the struct variable
	for i := 0; i < v.NumField(); i++ {
		fieldInfo := v.Type().Field(i)
		tag := fieldInfo.Tag       // string
		name := tag.Get("http")
		if name == "" {
			name = strings.ToLower(fieldInfo.Name)
		}
		fields[name] = v.Field(i)
	}

	for name, values := range req.Form {
		f := fields[name]  			// f是Value类型
		if !f.IsValid() {
			continue
		}
		for _, value := range values {
			if f.Kind() == reflect.Slice {
				elem := reflect.New(f.Type().Elem()).Elem()
				if err := populate(elem ,value); err != nil {
					return fmt.Errorf("%s: %v", name, err)
				}
				f.Set(reflect.Append(f, elem))
			} else {
				if err := populate(f ,value); err != nil {
					return fmt.Errorf("%s: %v", name, err)
				}
			}
		}
	}
	return nil
}

12.8 Displaying the Methods of a Type

我们最后的反射例子使用reflect.Type来打印任意一个值的type,枚举出它的methods。

func MyPrint(x interface{}) {
	v := reflect.ValueOf(x)
	t := v.Type()
	fmt.Printf("type %s\n", t) // 打印type

	for i := 0; i < v.NumMethod(); i++ {
		methType := v.Method(i).Type()
		fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
			strings.TrimPrefix(methType.String(), "func"))
	}
}

reflect.Type和Reflect.Value都有一个一个method()方法。每一个t.Method(i)调用返回一个reflect.Method实例,它是一个描述方法名称和类型的结构体类型。每个v.Method(i)调用返回reflect.Value,代表一个方法的值,是一个绑定在它的receiver上的方法。使用reflect.Value.Call方法,将可能会调用各种方法的值,我们这里的程序只需要它的类型。

MyPrint(time.Hour)
// OutPut:
type time.Duration
func (time.Duration) Hours() float64
func (time.Duration) Microseconds() int64
func (time.Duration) Milliseconds() int64
func (time.Duration) Minutes() float64
func (time.Duration) Nanoseconds() int64
func (time.Duration) Round(time.Duration) time.Duration
func (time.Duration) Seconds() float64
func (time.Duration) String() string
func (time.Duration) Truncate(time.Duration) time.Duration
MyPrint((new(strings.Replacer)))
// OutPut
type *strings.Replacer
func (*strings.Replacer) Replace(string) string
func (*strings.Replacer) WriteString(io.Writer, string) (int, error)

注意

虽然反射API比我们展示的更多,但前面的程序指明了一个方向。反射是一个强有力powerful和富有表现力expressive的工具,但是必须小心使用,有三个原因:
第一,基于反射的代码是脆弱的fragile。对于每个导致编译器报类型错误的问题,在反射中都有一个相关的误用问题。不同的是编译报错是在编译期间,而反射报错可能是在程序已经运行之后才发生。

例如,readList函数应该从输入中读取一个string,但如果去存储一个int类型,SetString方法会panic。大多数使用反射的程序都有类似的问题,一个注意点就是检查每个Value类型的type、addressability可寻址性、settability可设值性。
避免这种脆弱性的最好方式是将反射的使用完全封装在包中,如果可能的话,在包的API中不使用Value类型而使用特定类型,来避免非法的输入。如果这不能满足,则在每个有风险的操作前进行额外的动态检查。例如标准库的fmt.Printf,当verb作用于一个不合法的操作数,它不会导致panic而是打印出一个error msg。程序仍然有buf,但会很容器诊断出来。

fmt.Printf("%d %s\n", "hello", 42) // "%!d(string=hello) %!s(int=42)"

反射也会降低自动重构和分析工具的安全性和准确性,因为他们不知道或不依赖类型信息。

第二个避免使用反射的原因是,即使对应类型提供了相同文档,但是反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。总是需要小心翼翼地未每个期望的类型和接收interface{}或Value类型的函数参数做说明文档。

第三个原因是基本反射的函数执行效率将比具体类型的函数低1到2个数量级。对于一个典型的项目,大部分函数的性能和程序的整体性能关系不大,所以当反射能使程序更加清晰的时候可以考虑使用。测试是一个特别适合使用反射的场景,因为每个测试的数据集都很小。但是对于性能关键路径的函数,最好避免使用反射。

关键函数总结

// 获取值
v.Int():   Int, Int8, Int16, Int32, Int64: 
v.Unit():
v.String():
v.Bool():

Slice, Array:
	i < v.Len()
	v.Index(i)  // 修改i位的elem,会反馈到整个array或slice上吗?

Struct:
	i < v.NumField()
	v.Field(i)  // Value类型的i'th field of the struct v, 包含整个fieldName和fieldValue吗?
	v.Type().Field(i).Name --> fieldName
	v.FieldByName("xxx")

Map:
	range _, key := v.MapKeys()  --> key
	v.MapIndex(key)              --> value,还是整体?

Ptr:
	v.Elem()        --> *p

Interface:
	v.Elem()        --> dynamic value


// 设置值
v.Set(reflect.Zero(v.Type))
v.SetString()
v.SetInt()

array:
	v.Index(i)
Slice:
	item := reflect.New(v.Type().Elem()).Elem()  // New()创建一个元素类型的指针,再Elem获取变量
	v.Set(reflect.Append(v, item))
	
Map:
	key := reflect.New(v.Type().Key()).Elem() // Type().Key()返回kind为map的中的元素type
	value := reflect.New(v.Type().Elem()).Elem()
	v.SetMapIndex(key, value)
	

// 获取方法
i < v.NumMethod()
v := reflect.ValueOf(x)
t := v.Type()
methType := v.Method(i).Type()
t.Method(i).Name
methType.String()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值