• golang路上的小学生系列--使用reflect查找package路径


    本文同时发布在个人博客chinazt.ccgitbook

    今日看到了一个有趣的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有关的有两个函数:

    • PkgPath()
    • String()

    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 path
    • String() 返回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的反射减少了翻译环节,同时借助于编译器进行了代码优化,虽然同样在反射时需要进行额外的安全检查和类型检查,但不会降低太多效率。 而且上面,我们也看到只有在调用时也只发生了一次反射调用,影响几乎可忽略不计。

  • 相关阅读:
    [WC2010]重建计划
    [POJ1741]Tree
    [NOI2008]志愿者招募
    [BZOJ2127]happiness
    「网络流 24 题」太空飞行计划
    [TJOI2015]线性代数
    [HDU2874]Connections between cities
    [POI2007]ZAP-Queries
    [SCOI2010]幸运数字
    POJ 2826 An Easy Problem?!
  • 原文地址:https://www.cnblogs.com/vikings-blog/p/7131618.html
Copyright © 2020-2023  润新知