1

What is the reason that hash never returns -1?

Example 1:

for i in range(-3, 4):
    print(hash(i))

Outputs:

-3
-2
-2  <----- Notice this
0
1
2
3

Example 2:

class A(int):
    def __hash__(self) -> int:
        return -super().__hash__()


for i in range(-3, 4):
    print(hash(A(i))

Outputs:

3
2
2   <----- Notice this
0
-2  <----- Notice this
-2
-3

In this case, both the super().__hash__() method is replacing its -1 results to -2, and A.__hash__() is doing the same, so we end up with two -2s and two 2s.

Usage

If somneone is curious why I need this, I have two classes which are subclasses of int for limited ranges, lets say 1-20 and 1-100. Instances of this classes need to be used as keys in some dicts so they need to be hashable. I was trying to avoid having a nested dict by negating the hash of one of the ranges, so that one uses positive integers as hashes and the other negative ones, therefore being different keys:

from abc import ABCMeta, abstractmethod


class Range(int, meta=ABCMeta):
    def __init__(self, *args, **kwargs):
        super().__init__():
        if not (self.min <= self <= self.max):
            raise ValueError

    @classmethod
    @property
    @abstractmethod
    def min(self) -> int:
        raise NotImplementedError

    @classmethod
    @property
    @abstractmethod
    def max(self) -> int:
        raise NotImplementedError


class Range100(Range):
    min = 1
    max = 100


class Range20(Range):
    min = 1
    max = 20

    def __hash__(self) -> int:
        return -super().__hash__()

The Range class and subclasses have been simplified, there are several aditional common methods and several other abstract methods.

Obviosuly, my case has a simple solution, offsetting the negative values so that they avoid the conflicting -1, but I was curious why this happens at all.

Adirio
  • 5,040
  • 1
  • 14
  • 26

0 Answers0