reflect
Лекция 11
Максим Иванов
Максим Иванов
reflect.Type и reflect.Valuereflect.Value и interface{}reflect: fmt, encoding/json.reflect в реализации, но не светят reflect в интерфейсе.
Иногда нам нужно API, которое работает со значениями, которые не
объединены общим интерфейсом.
func Sprint(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, uint32, and so on...
default:
// array, chan, func, map, pointer, slice, struct
return "???"
}
}reflect определяет два основных типа: reflect.Type и reflect.Value.reflect.Type - type descriptor.func TypeOf(interface{}) Typet := reflect.TypeOf(3) // a reflect.Type fmt.Println(t.String()) // "int" fmt.Println(t) // "int"
reflect.TypeOf всегда возвращает конкретный тип.var w io.Writer = os.Stdout fmt.Println(reflect.TypeOf(w)) // "*os.File"
Sprintf формат %T использует TypeOf внутри.fmt.Printf("%T\n", 3) // "int"reflect.Value хранит значение любого типа.v := reflect.ValueOf(3)
fmt.Println(v) // "3"
fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // NOTE: "<int Value>".Type() возвращает тип значения, хранящегося внутри Value.t := v.Type() // a reflect.Type fmt.Println(t.String()) // "int"
.Interface() - обратная операция к reflect.ValueOf.v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface() // an interface{}
i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"interface{} - хранит значение, но не даёт доступа к нему. Можно достать конкретный тип, через type assertion.reflect.Value - предоставляет доступ к значению внутри, вне зависимости от типа этого значения.func Any(value interface{}) string { return formatAtom(reflect.ValueOf(value)) }
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, reflect.Uintptr: 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" } }
var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(format.Any(x)) // "1"
fmt.Println(format.Any(d)) // "1"
fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"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",
// ...
},
// ...
}Display("strangelove", strangelove)Вывод:
Display strangelove (display.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["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.Actor["Dr. Strangelove"] = "Peter Sellers" strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers" strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers" 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
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 = 0func 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: for i := 0; i < v.Len(); i++ { display(fmt.Sprintf("%s[%d]", path, i), v.Index(i)) } case reflect.Struct: 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() { display(fmt.Sprintf("%s[%s]", path, formatAtom(key)), v.MapIndex(key)) }
case reflect.Ptr: if v.IsNil() { fmt.Printf("%s = nil\n", path) } else { display(fmt.Sprintf("(*%s)", path), v.Elem()) } case reflect.Interface: 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)) } }
x, x.f[1], *p.x + 1, f(2).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
CanAddrfmt.Println(a.CanAddr()) // "false" fmt.Println(b.CanAddr()) // "false" fmt.Println(c.CanAddr()) // "false" fmt.Println(d.CanAddr()) // "true"
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"
d.Set(reflect.ValueOf(4)) fmt.Println(x) // "4"
d := reflect.ValueOf(&x).Elem() d.SetInt(3) fmt.Println(x) // "3"
x := 2 b := reflect.ValueOf(x) b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value
Приватные поля можно читать, но нельзя менять.
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
fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"func search(resp http.ResponseWriter, req *http.Request) { var data struct { Labels []string `http:"l"` MaxResults int `http:"max"` Exact bool `http:"x"` } data.MaxResults = 10 // set default if err := params.Unpack(req, &data); err != nil { http.Error(resp, err.Error(), http.StatusBadRequest) // 400 return } // ...rest of handler... fmt.Fprintf(resp, "Search: %+v\n", data) }
func Unpack(req *http.Request, ptr interface{}) error { if err := req.ParseForm(); err != nil { return err }
// Build map of fields keyed by effective name. 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) // a reflect.StructField tag := fieldInfo.Tag // a reflect.StructTag name := tag.Get("http") if name == "" { name = strings.ToLower(fieldInfo.Name) } fields[name] = v.Field(i) }
// Update struct field for each parameter in the request. for name, values := range req.Form { f := fields[name] if !f.IsValid() { continue // ignore unrecognized HTTP parameters } 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 }
func populate(v reflect.Value, value string) error { switch v.Kind() { case reflect.String: v.SetString(value) case reflect.Int: i, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } v.SetInt(i) case reflect.Bool: b, err := strconv.ParseBool(value) if err != nil { return err } v.SetBool(b) default: return fmt.Errorf("unsupported kind %s", v.Type()) } return nil }
func Print(x interface{}) { v := reflect.ValueOf(x) t := v.Type() fmt.Printf("type %s\n", t) 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")) } }
methods.Print(time.Hour) // Output: // type time.Duration // func (time.Duration) Hours() float64 // func (time.Duration) Minutes() float64 // func (time.Duration) Nanoseconds() int64 // func (time.Duration) Seconds() float64 // func (time.Duration) String() string
Через Method.Func.Type().In(0) можно получить тип первого аргумента (ресивера).
type MyType struct{} func (m MyType) ValueMethod() {} // value receiver func (m *MyType) PtrMethod() {} // pointer receiver func printReceiverInfo(x interface{}) { t := reflect.TypeOf(x) fmt.Printf("t = %v, NumMethod = %d\n", t, t.NumMethod()) fmt.Printf("IsPtr = %v\n", t.Kind() == reflect.Ptr) for i := 0; i < t.NumMethod(); i++ { method := t.Method(i) receiverType := method.Func.Type().In(0) fmt.Printf(" %s: In(0) = %v, IsPtr = %v\n", method.Name, receiverType, receiverType.Kind() == reflect.Ptr) } fmt.Println() }
t = main.MyType, NumMethod = 1 IsPtr = false ValueMethod: In(0) = main.MyType, IsPtr = false t = *main.MyType, NumMethod = 2 IsPtr = true PtrMethod: In(0) = *main.MyType, IsPtr = true ValueMethod: In(0) = *main.MyType, IsPtr = true
Можно сравнить количество методов у T и *T:
type MyType struct{}
func (m MyType) ValueMethod() {} // value receiver
func (m *MyType) PtrMethod() {} // pointer receiver
t := reflect.TypeOf(MyType{})
pt := reflect.PtrTo(t) // *T
fmt.Println(t.NumMethod()) // 1 (только ValueMethod)
fmt.Println(pt.NumMethod()) // 2 (ValueMethod + PtrMethod)T, и для *T.*T.ValueOf - всегда конкретный тип.var w io.Writer
t := reflect.TypeOf(w) // t == ???
var v interface{} = w // v == nil
t = reflect.TypeOf(v) // t == ???reflect.Type равный интерфейсу, нужно использовать промежуточный указатель.var w io.Writer ptrT := reflect.TypeOf(&w) // ptrT == *io.Writer t := ptrT.Elem() // t == io.Writer
reflect создаёт хрупкий код. Там, где была бы ошибка компилятора, возникает panic во время исполнения.reflect кода нужно документировать отдельно, и оно не ясно из типов аргументов.reflect работает медленнее, чем специализация под конкретный тип.Максим Иванов