Inmutabilidad y Hashabilidad
Los objetos hashables pueden vivir en conjuntos y como claves de diccionario. La inmutabilidad es la forma habitual de garantizar la estabilidad del hash: si el valor de un objeto cambia después de la inserción, las búsquedas fallan silenciosamente.
Receta
key = (42, "ada") # tupla de hashables - OK
tags = frozenset(["a", "b"]) # conjunto hashable
# NO hashable:
# bad = ([1, 2],) # lista dentro de tupla
# { [1, 2]: "x" } # lista como claveCuándo usar esto:
- Al usar objetos como claves de diccionario o miembros de conjuntos.
- Al construir cachés indexadas por argumentos.
- Al diseñar objetos de valor en modelos de dominio.
- Al depurar
TypeError: unhashable type
Ejemplo de Trabajo
from dataclasses import dataclass
@dataclass(frozen=True)
class UserId:
value: int
def cache_key(*parts: object) -> tuple:
return tuple(parts)
def dedupe_records(records: list[tuple[str, int]]) -> list[tuple[str, int]]:
seen: set[tuple[str, int]] = set()
unique: list[tuple[str, int]] = []
for record in records:
if record not in seen:
seen.add(record)
unique.append(record)
return unique
class Point:
__slots__ = ("x", "y")
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def __eq__(self, other: object) -> bool:
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self) -> int:
return hash((self.x, self.y))
if __name__ == "__main__":
index: dict[UserId, str] = {UserId(1): "Ada"}
print(index[UserId(1)])
print(dedupe_records([("a", 1), ("a", 1), ("b", 2)]))Lo que esto demuestra:
- Una dataclass con
frozen=Truees hashable cuando sus campos son hashables. - Las claves de caché de tuplas combinan partes hashables.
- Una clase personalizada implementa
__eq__y__hash__coincidentes. __slots__reduce la memoria para muchos objetos pequeños.
Profundización
Cómo Funciona
__hash__: Digest entero utilizado para localizar el bucket en una tabla hash.__eq__: La igualdad debe coincidir con el hash: objetos iguales deben tener el mismo hash.- Builtins inmutables:
int,str,bytes,tuple(si los elementos son hashables),frozenset. - Builtins mutables:
list,dict,set,bytearray- no tienen hash estable. - Clases de usuario: El hash de instancia predeterminado es por
ida menos que se anule.
Reglas de Hashabilidad
| Objeto | ¿Hashable? |
|---|---|
tuple de hashables | Sí |
tuple que contiene una lista | No |
frozenset | Sí |
set | No |
frozen dataclass | Sí, si los campos son hashables |
Notas de Python
# dataclasses con unsafe_hash genera __hash__ a partir de los campos
@dataclass(frozen=True)
class Coord:
x: int
y: int
# deshabilita el hash cuando se necesitan semánticas mutables
@dataclass(eq=True, frozen=False)
class Buffer:
data: bytearrayTrampas
- Dataclass mutable en un conjunto: Dataclass mutable → no hashable por defecto. Solución:
frozen=Trueo excluir de conjuntos. __eq__personalizado sin__hash__: Python establece__hash__ = None. Solución: Implementar__hash__consistente o usar patronesfunctools.cached_propertycon cuidado.- El hash cambia cuando los campos cambian: Viola el contrato de diccionario/conjunto. Solución: Solo objetos inmutables como claves.
- NaN floats:
float('nan')es hashable peronan != nan. Solución: Evitar claves NaN. - Tuplas con listas mutables dentro: La tupla es hashable pero el contenido de la lista puede mutar, rompiendo invariantes si se expone. Solución: No mutar las mutables anidadas utilizadas en las claves.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
Diccionario id(obj) | Mapa de identidad para objetos mutables | Deduplicación basada en valor |
Clave json.dumps | Claves de diccionario serializables | Necesidad de velocidad o tipos no JSON |
Digest hashlib | Huella digital de grandes bloques | Necesidad de semántica de igualdad de objetos |
| Índice único de BD | Unicidad persistente | Pipeline solo en memoria |
Preguntas Frecuentes
¿Por qué una lista no es hashable?
Las listas mutan en el lugar; el hash se volvería inválido después de la inserción en un conjunto/diccionario.
¿Son los diccionarios alguna vez hashables?
No. Usa frozenset de elementos o una tupla de pares si necesitas una clave hashable similar a un mapeo.
¿Una dataclass congelada es siempre hashable?
Solo si todos los campos son hashables. Los tipos de campo mutables bloquean el hashing.
¿Puedo hacer hash de mi clase personalizada?
Sí, con __hash__ y __eq__ consistentes. Prefiere dataclass congelada para registros.
¿Qué significa `__hash__ = None`?
Marca la clase explícitamente como no hashable; las instancias no pueden ser claves de conjunto/diccionario.
¿Tupla vs. frozenset como clave?
Tupla para una clave compuesta ordenada. Frozenset para conjuntos de etiquetas desordenados.
¿El hash es igual a la identidad?
El hash de objeto predeterminado puede usar id; las clases basadas en valor deben implementar el hash de valor.
¿`lru_cache` usa hash?
Las claves de caché usan el hash de los argumentos; los argumentos deben ser hashables.
¿Es `bytearray` hashable?
No, es mutable. Usa bytes para una clave binaria hashable.
¿Los miembros de Enum son hashables?
Sí, los miembros de Enum son singletons con identidad y hash estables.
Relacionado
- Diccionarios - requisitos de clave
- Conjuntos y Frozensets - uso de frozenset
- dataclasses - registros congelados
- Métodos Dunder / Mágicos - eq y hash
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.