How to avoid "TypeError: unhashable type" when Python Thrift decodes a map indexed by a struct
How to avoid "TypeError: unhashable type" when Python Thrift decodes a map indexed by a struct
文件 model.thrift 包含以下 Thrift 模型:
struct Coordinate {
1: required i32 x;
2: required i32 y;
}
struct Terrain {
1: required map<Coordinate, i32> altitude_samples;
}
请注意,我们有一个由结构(坐标)索引的地图 (altitude_samples)。
我使用Thrift编译器生成Python编解码类:
thrift -gen py model.thrift
我使用以下 Python 代码从文件中解码 Terrain 对象:
#!/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
问题是 Thrift 编译器无法自动生成 Coordinate class 的散列函数。
您必须手动添加哈希函数如下:
#!/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++ 的情况下,您需要手动将 Coordinate::operator< 添加到程序中。 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;
}
}
文件 model.thrift 包含以下 Thrift 模型:
struct Coordinate {
1: required i32 x;
2: required i32 y;
}
struct Terrain {
1: required map<Coordinate, i32> altitude_samples;
}
请注意,我们有一个由结构(坐标)索引的地图 (altitude_samples)。
我使用Thrift编译器生成Python编解码类:
thrift -gen py model.thrift
我使用以下 Python 代码从文件中解码 Terrain 对象:
#!/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
问题是 Thrift 编译器无法自动生成 Coordinate class 的散列函数。
您必须手动添加哈希函数如下:
#!/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++ 的情况下,您需要手动将 Coordinate::operator< 添加到程序中。 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;
}
}