7

I have a class Table:

@dataclass(frozen=True, eq=True)
class Table:
    name: str
    signature: Dict[str, Type[DBType]]
    prinmary_key: str
    foreign_keys: Dict[str, Type[ForeignKey]]
    indexed: List[str]

And need to create such dictionary:


table = Table(*args)
{table: 'id'}

TypeError: unhashable type: 'dict'

Don't understand what's the problem.

Pavel Antspovich
  • 1,111
  • 1
  • 11
  • 27
  • 1
    `frozen=True` prevents you from assigning *new* values to the attributes; it does not prevent you from mutating the existing mutable values. – chepner Feb 18 '20 at 21:42
  • @chepner, sorry, don't understand you. I don't see any mutations. I only want to create dict with Table keys – Pavel Antspovich Feb 18 '20 at 21:46
  • 1
    The fact that you *can* mutate, for example, `indexed`, means that you can change an existing `Table` object without changing what the attributes refer to. `t.indexed = []` may be forbidden, but `t.indexed.append("foo")` is not. I admit I'm somewhat surprised that `dataclass` will generate a `__hash__` function based solely on the `frozen` and `eq` arguments, as I don't see how it avoids using the mutable attributes (nor does the documentation mention an issue with mutable attributes). – chepner Feb 18 '20 at 21:49
  • 1
    The generated `hash` is trying to hash each instance attribute separately, and the `dict` and `list` values aren't hashable. As far as I can tell, you need to implement your own `__hash__` method that ignores the mutable attributes, rather than relying on the autogenerated `__hash__` method. – chepner Feb 18 '20 at 21:52
  • @chepner, thanks. Got it – Pavel Antspovich Feb 18 '20 at 21:55

1 Answers1

14

The autogenerated hash method isn't safe, since it tries to hash the unhashable attributes signature, primary_key, and indexed. You need to define your own __hash__ method that ignores those attributes. One possibility is

def __hash__(self):
    return hash((self.name, self.primary_key))

Both self.name and self.primary_key are immutable, so a tuple containing those values is also immutable and thus hashable.


An alternative to defining this method explicitly would be to use the field function to turn off the mutable fields for hashing purposes.

@dataclass(frozen=True, eq=True)
class Table:
    name: str
    signature: Dict[str, Type[DBType]] = field(compare=False)
    prinmary_key: str
    foreign_keys: Dict[str, Type[ForeignKey]] = field(compare=False)
    indexed: List[str] = field(compare=False)

field has a hash parameter whose default value is the value of compare, and the documentation discourages using a different value for hash. (Probably to ensure that equal items hash identically.) It's unlikely that you really want to use these three fields for the purposes of comparing two tables, so you this should be OK.

I would consult the documentation rather than relying on my relatively uninformed summary of it.

chepner
  • 497,756
  • 71
  • 530
  • 681