代码之家  ›  专栏  ›  技术社区  ›  Bruno Rijsman

如何在Python节俭解码由结构索引的映射时避免“TypeError:unhashable type”

  •  0
  • Bruno Rijsman  · 技术社区  · 7 年前

    文件model.thrift 包含以下节俭模式:

    struct Coordinate {
        1: required i32 x;
        2: required i32 y;
    }
    
    struct Terrain {
        1: required map<Coordinate, i32> altitude_samples;
    }
    

    注意,我们有一个由结构(坐标)索引的地图(海拔高度样本)。

    我使用Thrift编译器生成Python编码和解码类:

    thrift -gen py model.thrift
    

    我使用以下Python代码从文件中解码地形对象:

    #!/usr/bin/env python
    
    import sys
    sys.path.append('gen-py')
    
    import thrift.protocol.TBinaryProtocol
    import thrift.transport.TTransport
    import model.ttypes
    
    def decode_terrain_from_file():
        file = open("terrain.dat", "rb")
        transport = thrift.transport.TTransport.TFileObjectTransport(file)
        protocol = thrift.protocol.TBinaryProtocol.TBinaryProtocol(transport)
        terrain = model.ttypes.Terrain()
        terrain.read(protocol)
        print(terrain)
    
    if __name__ == "__main__":
        decode_terrain_from_file()
    

    运行此程序时,出现以下错误:

    (env) $ python py_decode.py 
    Traceback (most recent call last):
      File "py_decode.py", line 19, in <module>
        decode_terrain_from_file()
      File "py_decode.py", line 15, in decode_terrain_from_file
        terrain.read(protocol)
      File "gen-py/model/ttypes.py", line 119, in read
        self.altitude_samples[_key5] = _val6
    TypeError: unhashable type: 'Coordinate
    
    1 回复  |  直到 7 年前
        1
  •  0
  •   Bruno Rijsman    7 年前

    问题是节俭编译器不能自动为坐标类生成哈希函数。

    必须手动添加哈希函数,如下所示:

    #!/usr/bin/env python
    
    import sys
    sys.path.append('gen-py')
    
    import thrift.protocol.TBinaryProtocol
    import thrift.transport.TTransport
    import model.ttypes
    
    model.ttypes.Coordinate.__hash__ = lambda self: hash((self.x, self.y))
    
    def decode_terrain_from_file():
        file = open("terrain.dat", "rb")
        transport = thrift.transport.TTransport.TFileObjectTransport(file)
        protocol = thrift.protocol.TBinaryProtocol.TBinaryProtocol(transport)
        terrain = model.ttypes.Terrain()
        terrain.read(protocol)
        print(terrain)
    
    if __name__ == "__main__":
        decode_terrain_from_file()
    

    注意,C++生成的代码也会出现类似的问题。在C++的情况下,需要手动添加坐标::去参加节目。Thrift会为Coordinate::operator生成声明<但不生成Coordinate::operator<的实现;。再次说明,原因是Thrift不理解结构的语义,因此无法猜测比较运算符的正确实现。

    bool Coordinate::operator<(const Coordinate& other) const
    {
        if (x < other.x) {
            return true;
        } else if (x > other.x) {
            return false;
        } else if (y < other.y) {
            return true;
        } else {
            return false;
        }
    }