Hay un bug que aparece una y otra vez en código Python, tan sutil que muchos no lo ven hasta que todo se quiebra:
if variable == None:
print("Es nulo")Esto parece correcto. Funciona. Pero un pythonista experimentado levantaría la mano y diría: “Deberías usar is, no ==.”
¿Por qué? ¿Acaso == None no funciona?
Funciona. Pero hay una diferencia entre “funciona” y “es lo correcto”. Y esa diferencia te causará dolores de cabeza si no la entiendes.
La confusión es real: == e is parecen hacer lo mismo (comparar dos cosas). Pero comparan cosas distintas. Y elegir la equivocada es la causa de algunos de los bugs más raros y difíciles de debuguear en Python.
En esta entrada vamos a ver:
- Qué compara cada uno.
- Por qué son distintos.
- Dónde está la trampa (los números pequeños y None).
- Cuál usar en cada situación.
- Por qué en algunos casos dan resultados “raros”.
La diferencia en una frase
==compara valor: “¿tienen el mismo contenido?”iscompara identidad: “¿son el mismo objeto en memoria?”
Eso es todo. Pero las consecuencias son enormes.
El ejemplo que muestra todo
a = [1, 2, 3] b = [1, 2, 3] print(a == b) # True ← mismo contenido print(a is b) # False ← diferentes objetos en memoria
Ahora lo mismo pero con tuplas:

c = (1, 2, 3) d = (1, 2, 3) print(c == d) # True ← mismo contenido print(c is d) # True ← ¡mismo objeto en memoria!
¿Qué? ¿Cómo es posible que dos tuplas distintas sean el mismo objeto?
Respuesta: Python cachea tuplas pequeñas por optimización. Crea una sola tupla en memoria y reutiliza la referencia.
Eso es el primer indicio de que == e is no son lo mismo, y que Python tiene trucos internos que hacen que el comportamiento sea raro a veces.
Entender la identidad: la dirección de memoria
Cada objeto en Python vive en una dirección de memoria. Dos objetos distintos tienen direcciones distintas, aunque tengan el mismo valor.
a = [1, 2, 3] b = [1, 2, 3] print(id(a)) # 140234567891520 (dirección de memoria) print(id(b)) # 140234567891648 (dirección distinta) print(a is b) # False (diferentes direcciones) print(a == b) # True (mismo contenido)
id() te da la dirección de memoria del objeto. Si dos variables apuntan a la misma dirección, is devuelve True.
c = a print(id(a)) # 140234567891520 print(id(c)) # 140234567891520 (¡misma dirección!) print(a is c) # True (mismo objeto) print(a == c) # True (y mismo contenido, obvio)
Cuando haces c = a, no copias el contenido. Haces que c apunte a la misma dirección que a. Son alias del mismo objeto.
Las 3 comparaciones que importan
Comparar con None (SIEMPRE usa is)
Este es el bug clásico y frecuente.
# Incorrecto (pero funciona a veces)
if variable == None:
print("Es nulo")
# Correcto
if variable is None:
print("Es nulo")¿Por qué? Porque None es un singleton en Python. Solo existe UNA instancia de None en toda tu aplicación. Es un objeto especial:
a = None b = None print(id(a)) # 94653618254496 print(id(b)) # 94653618254496 (¡la misma dirección!) print(a is b) # True
Usar == None funciona, pero es conceptualmente incorrecto. Preguntas “¿este objeto tiene el mismo valor que None?” cuando lo que realmente quieres preguntar es “¿este objeto ES None?”
# Esto "funciona" pero es engañoso
class Raro:
def __eq__(self, other):
if other is None:
return True # Siempre dice que es igual a None
return False
raro = Raro()
print(raro == None) # True (aunque no sea None)
print(raro is None) # False (que es la verdad)Si usas ==, alguien podría sobrescribir el comportamiento y tu código fallaría silenciosamente. Con is, es imposible engañar.
Regla de oro: siempre compara None con is, nunca con ==.
if variable is None:
pass
if variable is not None:
passComparar booleanos (cuidado)
Los booleanos True y False son también singletons:
a = True b = True print(a is b) # True (mismo objeto) print(a == b) # True (y mismo valor, obvio)
Así que para booleanos, == e is suelen dar el mismo resultado. Pero usa == por consistencia, no is:
# Correcto
if variable == True:
pass
if variable == False:
pass
# Aunque funcione, es un antipatrón
if variable is True:
passPor qué? Porque creas hábitos. Si siempre usas is para booleanos, uno de estos días lo harás con otro tipo de objeto donde is no es lo correcto.
Números pequeños (la trampa silenciosa)
Python cachea enteros pequeños (del -5 al 256) para optimización:
a = 5 b = 5 print(a is b) # True (¡Python reutiliza el mismo objeto!) print(a == b) # True # Pero números más grandes... c = 257 d = 257 print(c is d) # False (¡diferentes objetos!) print(c == d) # True (mismo valor)
a = 256 b = 256 print(a is b) # True (cacheado) c = 257 d = 257 print(c is d) # False (no cacheado)
Esto es una trampa invisible. Tu código funciona con números pequeños porque is accidentalmente devuelve True. Pero falla con números grandes.
# Código tramposo
if numero is 5:
print("Es 5")
# Funciona para 5 (cacheado), pero falla para 257 (no cacheado)
if numero is 257:
print("Es 257") # Probablemente falleNunca compares números con is. NUNCA.
# Correcto
if numero == 5:
pass
if numero == 257:
passTabla resumen: cuándo usar cada uno
| Situación | Usa | Ejemplo |
|---|---|---|
Comparar con None | is | if variable is None: |
Comparar con True/False | == | if variable == True: |
| Comparar números | == | if numero == 5: |
| Comparar strings | == | if nombre == "Juan": |
| Comparar listas/dicts | == | if lista == [1, 2, 3]: |
| Verificar si es el MISMO objeto | is | if lista is variable: |
| Comparar con negación de None | is not | if variable is not None: |
Casos donde is es correcto (y necesario)
Verificar que dos variables apuntan al MISMO objeto
original = [1, 2, 3] copia = original # No es copia, es alias print(original is copia) # True (mismo objeto) print(original == copia) # True (mismo contenido) # Ahora modifica uno original.append(4) print(copia) # [1, 2, 3, 4] ← cambió sin querer!
Usar is te ayuda a detectar cuándo dos variables son alias peligrosos.
Detectar cambios inesperados (por referencia)
def procesar_lista(lista):
# Alguien pasó la lista correcta?
if lista is None:
print("Error: lista es None")
return
# Modificar la lista afecta al original
lista.append(999)
datos = [1, 2, 3]
procesar_lista(datos)
print(datos) # [1, 2, 3, 999] ← cambióSingleton patterns
class Config:
_instance = None
def __new__(cls):
if cls._instance is None: # Usa is para verificar singleton
cls._instance = super().__new__(cls)
return cls._instance
c1 = Config()
c2 = Config()
print(c1 is c2) # True (mismo objeto en memoria)La trampa visual: código que parece funcionar pero no
def validar_usuario(edad):
# Esto "funciona" para algunos números
if edad is 18:
print("Eres adulto")
else:
print("No eres adulto")
validar_usuario(18) # Funciona (18 está cacheado)
validar_usuario(257) # Parece funcionar pero es casualidad
validar_usuario(1000) # Probablemente falleEl bug no se ve porque parece funcionar. El problema aparece solo con números grandes, en producción, cuando nadie espera el error.
Comparaciones especiales con is
is not
if variable is not None:
print("Es algo")
# Equivalente (pero menos legible)
if not (variable is None):
print("Es algo")Usa is not cuando quieras la negación clara.
Comparar con listas vacías (INCORRECTO)
# Incorrecto
if lista is []:
print("Lista vacía")
# Correcto
if lista == []:
print("Lista vacía")
# Aún mejor
if not lista:
print("Lista vacía")Cada [] es un objeto nuevo, así que is [] siempre devuelve False (comparas objetos distintos).
El debugging: cómo encontrar estos bugs
Si sospechas que alguien usó is donde debería usar ==:
# El código falla a veces pero no siempre
# Síntoma: funciona con números pequeños, falla con grandes
numero = 500
if numero is 500: # Falla
print("Es 500")
else:
print("No es 500") # Aquí entra, aunque numero == 500 es TrueBusca comparaciones is que no sean con None, True, False, o variables. Probablemente sea un bug.
# Probables bugs if edad is 18: if contador is 0: if resultado is "error": if lista is []: # Correcto if edad == 18: if contador == 0: if resultado == "error": if not lista:
Resumen: la regla práctica
Usa == por defecto. Solo usa is en estos casos:
- Comparar con
None:if variable is None: - Comparar con
not None:if variable is not None: - Verificar identidad:
if variable is otra_variable:(cuando necesitas saber si son el MISMO objeto) - Casos muy específicos como singletons.
Nunca uses is para:
- Números
- Strings
- Listas, diccionarios, tuplas
- Booleanos (usa
==) - Nada que no sea
None
Un ejemplo de bug real
# Pipeline de procesamiento de datos
def procesar_datos(datos, estado_esperado):
if datos is estado_esperado: # Bug sutil
print("Datos en el estado correcto")
return procesar(datos)
else:
print("Estado incorrecto")
return None
resultado = procesar_datos([1, 2, 3], [1, 2, 3])
# Falla: compara objetos (direcciones), no valores
# datos y estado_esperado son listas distintas, aunque igualesArreglo:
def procesar_datos(datos, estado_esperado):
if datos == estado_esperado: # Correcto
print("Datos en el estado correcto")
return procesar(datos)
else:
print("Estado incorrecto")
return NoneConclusiones
== e is parecen lo mismo, pero comparan cosas muy distintas:
==→ “¿tienen el mismo valor?” (lo que casi siempre quieres)is→ “¿son el mismo objeto en memoria?” (lo que rara vez quieres)
El 99% del tiempo, quieres ==.
Usa is solo para:
None: siempreif variable is None:- Identidad: cuando necesites saber si dos variables apuntan al MISMO objeto.
- Casos muy específicos y documentados.
La trampa es que Python cachea algunos valores (números pequeños, tuplas, None, True, False) para optimización. Así que is a veces devuelve True cuando no esperas, haciendo que parezca que tu código funciona cuando en realidad es incorrecto. Ese es el bug invisible.
Cuando estés debugueando algo raro y no encuentres la causa, busca is. Apuesto a que está ahí.
Imagen de Mediamodifier en Pixabay
Deja una respuesta