""" Compatibility methods to support Python 2.7 style hashing in Python 3.X+ This is designed for compatibility not performance. """ import ctypes import math from functools import lru_cache def hash27(value): # type: ignore[no-untyped-def] """ Wrapper call to Hash.hash() Args: value: input value Returns: Python 2.7 hash """ return Hash.hash(value) class Hash: """ Various hashing methods using Python 2.7's algorithms """ _FLOAT_ZERO = 0.0 @staticmethod @lru_cache(maxsize=2048) def hash(value): # type: ignore[no-untyped-def] # noqa: builtin-attribute-shadowing """ Returns a Python 2.7 hash for a value. Args: value: input value Returns: Python 2.7 hash """ if isinstance(value, ("".__class__, bytes)) or type(value).__name__ == "buffer": return Hash.shash(value) # type: ignore[no-untyped-call] if isinstance(value, tuple): return Hash.thash(value) # type: ignore[no-untyped-call] if isinstance(value, float): return Hash.fhash(value) # type: ignore[no-untyped-call] if isinstance(value, int): return hash(value) raise TypeError("unhashable type: '%s'" % (type(value).__name__)) @staticmethod def thash(value): # type: ignore[no-untyped-def] """ Returns a Python 2.7 hash for a tuple. Logic ported from the 2.7 Python branch: cpython/Objects/tupleobject.c Method: static long tuplehash(PyTupleObject *v) Args: value: input tuple Returns: Python 2.7 hash """ length = len(value) mult = 1000003 x = 0x345678 for y in value: length -= 1 x = (x ^ Hash.hash(y)) * mult mult += 82520 + length + length x += 97531 if x == -1: x = -2 # Convert to C type return ctypes.c_long(x).value @staticmethod def fhash(value): # type: ignore[no-untyped-def] """ Returns a Python 2.7 hash for a float. Logic ported from the 2.7 Python branch: cpython/Objects/object.c Method: long _Py_HashDouble(double v) Args: value: input float Returns: Python 2.7 hash """ fpart = math.modf(value) if fpart[0] == Hash._FLOAT_ZERO: return hash(int(fpart[1])) v, e = math.frexp(value) # 2**31 v *= 2147483648.0 # Top 32 bits hipart = int(v) # Next 32 bits v = (v - float(hipart)) * 2147483648.0 x = hipart + int(v) + (e << 15) if x == -1: x = -2 # Convert to C long type return ctypes.c_long(x).value @staticmethod def shash(value): # type: ignore[no-untyped-def] """ Returns a Python 2.7 hash for a string. Logic ported from the 2.7 Python branch: cpython/Objects/stringobject.c Method: static long string_hash(PyStringObject *a) Args: value: input string Returns: Python 2.7 hash """ length = len(value) if length == 0: return 0 x = Hash.ordinal(value[0]) << 7 # type: ignore[no-untyped-call] for c in value: x = (1000003 * x) ^ Hash.ordinal(c) # type: ignore[no-untyped-call] x ^= length x &= 0xFFFFFFFFFFFFFFFF if x == -1: x = -2 # Convert to C long type return ctypes.c_long(x).value @staticmethod def ordinal(value): # type: ignore[no-untyped-def] """ Converts value to an ordinal or returns the input value if it's an int. Args: value: input Returns: ordinal for value """ return value if isinstance(value, int) else ord(value)