您的位置:首頁>正文

golang路上的小學生系列--使用reflect查找package路徑

本文同時發佈在個人博客chinazt.cc和gitbook

今日看到了一個有趣的golang項目--kolpa(https://github.com/malisit/kolpa)。 這個項目可以用來生成偽造的姓名, 地址, 時間, User-Agent等等資訊, 在需要大量亂數據的測試環境中非常合適。

點擊fork之後, 放在本地環境中build, run結果失敗。 運行項目中提供的demo也失敗, 按道理來說官方提供的demo應該都會成功, 而且自己也沒有修改任何一行代碼, 失敗是不科學的。

所以只能剖解代碼, 查找失敗原因。

一查不知道, 原來此專案需要依賴各個語言環境下的範本檔, 而範本檔都放在專案的data目錄中。 在代碼中, 通過硬編碼來確定範本檔位置:

// Reads the file "fName" and returns its content as a slice of strings. func (g *Generator) fileToSlice(fName string) ([]string, error) { var res string path := os.Getenv("GOPATH") + "/src/github.com/malisit/kolpa/data/" + g.Locale + "/" + fName file, err := os.Open(path) if err != nil { return nil, err } defer file.Close scanner := bufio.NewScanner(file) for scanner.Scan { res = append(res, scanner.Text) } if err := scanner.Err; err != nil { //log.Println("Inteded generation is not valid for selected language. Switching to en_US.") return g.fileToSlice(fName) } return res, nil }

因為我是通過fork,

然後clone到本地的方式來運行demo, 本地此時packge路徑已經不是上面的路徑了, 所以導致運行失敗。 很難說這是一個bug, 但的確影響到了程式運行。 所以說不良的代碼風格更為恰當吧。

既然找到了問題, 那下一步就是如何解決問題。 應該如何在Runtime時即時獲取package位置呢?

說到Runtime, 那麼一定就少不了Reflect package。

Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. The typical use is to take a value with static type interface{} and extract its dynamic type information by calling TypeOf, which returns a Type.

從Reflect的介紹上看, Reflect package推薦的使用方式是通過TypeOf返回一個帶有interface{}所有動態類型資訊的Type類型, 然後通過Type類型來獲取各種程式需要的資訊。

type Type interface { // 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. // // For an interface type, the returned Method's Type field gives the // method signature, without a receiver, and the Func field is nil. 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 exported methods in the type's method set. NumMethod int // Name returns the type's name within its package. // It returns an empty string for unnamed types. Name string // PkgPath returns a named 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 unnamed (*T, struct{}, []int), // 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. ConvertibleTo(u Type) bool // Comparable reports whether values of this type are comparable. Comparable bool // 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 anonymous 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 anonymous 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 // contains filtered or unexported methods }

在Type介面定義中, 和Package Path有關的有兩個函數:

PkgPathString

PkgPath 返回指定類型的import package path, 也就是說, 如果代碼中有import encoding/base64這樣的語句, 那麼通過PkgPath就會返回encoding/base64, 而不是base64package所在的實際路徑。 反言之, PkgPath返回的是import package path。

而如果繼續使用上例中的encoding/base64來說, String返回的是base64, 而不是encoding/base64。 String返回的是實際使用的package name。

所以總結如下:

PkgPath 返回import package pathString 返回import package name

顯然, PkgPath更適合我們的需求。 簡單改造代碼如下:

1.增加 Pkg path

// Generator struct to access various generator functions type Generator struct { Locale string Pkg string }

2.獲取Pkg

// C is the creator function, initiates kolpa with or without locale // setting. The default locale setting is "en_US". // Returns a generator type that will be used to call generator methods. func C(localeVar ...string) Generator { newGenerator := Generator{} if len(localeVar) > 0 { newGenerator.Locale = localeVar[0] } else { newGenerator.Locale = "en_US" } // newGenerator.populateFunctions newGenerator.Pkg = reflect.TypeOf(newGenerator).PkgPath return newGenerator }

3.替換硬編碼

// Reads the file "fName" and returns its content as a slice of strings. func (g *Generator) fileToSlice(fName string) ([]string, error) { var res string path := os.Getenv("GOPATH") + "/src/" + g.Pkg + "/data/" + g.Locale + "/" + fName file, err := os.Open(path) if err != nil { return nil, err } defer file.Close scanner := bufio.NewScanner(file) for scanner.Scan { res = append(res, scanner.Text) } if err := scanner.Err; err != nil { //log.Println("Inteded generation is not valid for selected language. Switching to en_US.") return g.fileToSlice(fName) } return res, nil }

4.運行demo

⋊> ~/S/g/g/s/t/kopla ./kopla 石潔玉 幸和平

完美生成兩個隨機姓名, 話說"和平"也是一代人經常起的名字。

最後提一句, 有的地方曾經說到儘量少使用Reflect package。 因此反射使用多了, 影響效率。 我想這種想法應該是從Java VM那裡流傳出來的吧。 因為Java VM負責將位元組碼翻譯成機器碼, 因此頻繁調用反射會加重VM切換上下文的代價,也就是把裝載期做的事情搬到了運行期, 勢必降低運行效率。 golang的反射減少了翻譯環節, 同時借助於編譯器進行了代碼優化, 雖然同樣在反射時需要進行額外的安全檢查和類型檢查, 但不會降低太多效率。 而且上面, 我們也看到只有在調用時也只發生了一次反射調用,

影響幾乎可忽略不計。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示