Как избежать "TypeError: unhashable type", когда Python Thrift декодирует карту, проиндексированную структурой - PullRequest
0 голосов
/ 20 сентября 2018

Файл model.thrift содержит следующую модель Thrift:

struct Coordinate {
    1: required i32 x;
    2: required i32 y;
}

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

Обратите внимание, что у нас есть карта (altitude_samples), проиндексированная структурой (Coordinate).

Я использую 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

1 Ответ

0 голосов
/ 20 сентября 2018

Проблема в том, что Thrift-компилятор не может автоматически сгенерировать хеш-функцию для класса Coordinate.

Вы должны добавить хеш-функцию вручную следующим образом:

#!/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 :: <в программу.Thrift генерирует объявление для Coordinate :: operator <, но не генерирует реализацию Coordinate :: operator <.Еще раз, причина этого в том, что Thrift не понимает семантику структуры и, следовательно, не может угадать правильную реализацию оператора сравнения.</p>

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;
    }
}
...