Files
dualfroz 518ab0a093 fix: make Delorean hashable by defining __hash__
Defining __eq__ without __hash__ sets __hash__ to None in Python 3, so
Delorean instances were unhashable and could not be used in sets or as
dict keys (unlike datetime). Hash on self.epoch, the same value __eq__
compares, so equal instances hash equal.
2026-09-04 09:39:40 +02:00

1.9 KiB

Make Delorean hashable again

Problem

Delorean instances cannot be put in a set or used as dict keys:

>>> from datetime import datetime
>>> from delorean import Delorean
>>> d = Delorean(datetime(2015, 1, 1), timezone="UTC")
>>> hash(d)
Traceback (most recent call last):
    ...
TypeError: unhashable type: 'Delorean'
>>> {d}
TypeError: unhashable type: 'Delorean'

This is surprising for a date/time value object: the standard library's datetime.datetime is hashable, and Delorean supports equality and ordering, so grouping instances in sets or keying dictionaries by them is a natural thing to want.

Root cause

delorean/dates.py defines __eq__ (and the rich-comparison operators) on Delorean, but does not define __hash__. In Python 3, defining __eq__ without __hash__ sets __hash__ to None, which makes the class unhashable:

>>> Delorean.__hash__ is None
True

Under Python 2 (the version delorean originally targeted) defining __eq__ left the inherited __hash__ in place, so this is effectively a regression introduced by the move to Python 3.

Fix

Add a __hash__ consistent with the existing __eq__. __eq__ compares self.epoch == other.epoch, so hashing on self.epoch guarantees the hash invariant (equal objects hash equal), including two instances describing the same instant in different timezones:

def __hash__(self):
    return hash(self.epoch)

Test

Added two tests to tests/delorean_tests.py:

  • test_hashable: hash() works and an instance can be stored in a set and used as a dict key.
  • test_hash_consistent_with_equality: two equal instances (same instant, different timezone) hash equal and collapse to one element in a set.

Gate

  • python -m pytest tests/ -> 232 passed, 44 subtests passed.

Counterfactual (fix reverted, tests kept): both new tests fail with TypeError: unhashable type: 'Delorean'.