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:
== compara valor: “¿tienen el mismo contenido?”is compara identidad: “¿son el mismo objeto en memoria?”Eso es todo. Pero las consecuencias son enormes.
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.
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.
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:
pass 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:
pass Por 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.
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 falle Nunca compares números con is. NUNCA.
# Correcto
if numero == 5:
pass
if numero == 257:
pass | 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: |
is es correcto (y necesario)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.
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ó 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) 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 falle El bug no se ve porque parece funcionar. El problema aparece solo con números grandes, en producción, cuando nadie espera el error.
isis notif 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.
# 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).
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 True Busca 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:
Usa == por defecto. Solo usa is en estos casos:
None: if variable is None:not None: if variable is not None:if variable is otra_variable: (cuando necesitas saber si son el MISMO objeto)Nunca uses is para:
==)None# 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 iguales Arreglo:
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 None == 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: siempre if variable is None: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
Durante décadas, cuando escribías una dirección web y pulsabas Intro, tu navegador hacía algo que…
La independencia financiera es el punto en el que tus inversiones generan suficientes rendimientos para…
Tienes dos grupos de datos y quieres saber si son estadísticamente distintos. O tienes una…
A primera vista, tuplas y listas son casi idénticas. Ambas guardan colecciones ordenadas de elementos.…
"Nuestros datos no tienen buena calidad." Escuchas esto constantemente en reuniones. El problema es que…
El artículo Data Leakage en Credit Scoring explicó por qué este es el error más…
This website uses cookies.