'es' vs '==' en Python - Comparación de objetos

En este tutorial, profundizaremos en la diferencia entre los operadores 'es' y '==' en Python. Cubriremos los casos de uso de ambos y sobrecargaremos el operador '==' para la comparación de valores personalizados.

'es' frente a '==' en Python

Python tiene dos operadores muy similares para verificar si dos objetos son iguales. Estos dos operadores son es y ==.

Por lo general, se confunden entre sí porque con tipos de datos simples, como ints y strings (con los que muchas personas comienzan a aprender Python) parecen hacer lo mismo:

1
2
3
4
5
6
7
x = 5
s = "example"

print("x == 5: " + str(x == 5))
print("x is 5: " + str(x is 5))
print("s == 'example': " + str(s == "example"))
print("s is 'example': " + str(s is "example"))

Ejecutar este código dará como resultado:

1
2
3
4
x == 5: True
x is 5: True
s == 'example': True
s is 'example': True

Esto muestra que == y is devuelven el mismo valor (True) en estos casos. Sin embargo, si intentaste hacer esto con una estructura más complicada:

1
2
3
4
some_list = [1]

print("some_list == [1]: " + str(some_list == [1]))
print("some_list is [1]: " + str(some_list is [1]))

Esto daría como resultado:

1
2
some_list == [1]: True
some_list is [1]: False

Aquí se vuelve obvio que estos operadores no son lo mismo.

La diferencia proviene del hecho de que is comprueba la identidad (de los objetos), mientras que == comprueba la igualdad (de valor).

Aquí hay otro ejemplo que podría aclarar la diferencia entre estos dos operadores:

1
2
3
4
5
6
7
8
some_list1 = [1]
some_list2 = [1]
some_list3 = some_list1

print("some_list1 == some_list2: " + str(some_list1 == some_list2))
print("some_list1 is some_list2: " + str(some_list1 is some_list2))
print("some_list1 == some_list3: " + str(some_list1 == some_list3))
print("some_list1 is some_list3: " + str(some_list1 is some_list3))

Esto resulta en:

1
2
3
4
some_list1 == some_list2: True
some_list1 is some_list2: False
some_list1 == some_list3: True
some_list1 is some_list3: True

Como podemos ver, alguna_lista1 es igual a alguna_lista2 por valor (ambos son iguales a [1]]), pero no son idénticos, lo que significa que no son los mismos. mismo objeto, aunque tengan valores iguales.

Sin embargo, alguna_lista1 es tanto igual como idéntico a alguna_lista3 ya que hacen referencia al mismo objeto en la memoria.

Tipos de datos mutables frente a inmutables

Si bien esta parte del problema ahora podría estar clara (cuando hemos nombrado variables), podría surgir otra pregunta:

¿Por qué is y == se comportan igual con valores int y string sin nombre (como 5 y "example") pero no se comportan igual con listas sin nombre (como [1])?

Hay dos tipos de tipos de datos en Python: mutable e inmutable.

  • Los tipos de datos mutables son tipos de datos que puede "cambiar" con el tiempo
  • Los tipos de datos inmutables permanecen iguales (tienen la misma ubicación de memoria, que es lo que verifica is) una vez que se crean

Los tipos de datos mutables son: lista, diccionario, conjunto y clases definidas por el usuario.

Los tipos de datos inmutables son: int, float, decimal, bool, string, tuple y range.

Al igual que muchos otros lenguajes, Python maneja los tipos de datos inmutables de manera diferente a los tipos mutables, es decir, los guarda en la memoria solo una vez.

Entonces, cada 5 que usa en su código es exactamente el mismo 5 que usa en otros lugares de su código, y lo mismo ocurre con los literales de cadena que usa.

Si usa la cadena "example" una vez, cada vez que use "example" será exactamente el mismo objeto. Consulte esta Nota para obtener más aclaraciones.

Usaremos una función de Python llamada id() que imprime un identificador único para cada objeto, para observar más de cerca este concepto de mutabilidad en acción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
s = "example"
print("Id of s: " + str(id(s)))
print("Id of the String 'example': " + str(id("example")) + " (note that it's the same as the variable s)")
print("s is 'example': " + str(s is "example"))

print("Change s to something else, then back to 'example'.")
s = "something else"
s = "example"
print("Id of s: " + str(id(s)))
print("s is 'example': " + str(s is "example"))
print()

list1 = [1]
list2 = list1
print("Id of list1: " + str(id(list1)))
print("Id of list2: " + str(id(list2)))
print("Id of [1]: " + str(id([1])) + " (note that it's not the same as list1!)")
print("list1 == list2: " + str(list1 == list2))
print("list1 is list2: " + str(list1 is list2))

print("Change list1 to something else, then back to the original ([1]) value.")
list1 = [2]
list1 = [1]
print("Id of list1: " + str(id(list1)))
print("list1 == list2: " + str(list1 == list2))
print("list1 is list2: " + str(list1 is list2))

Esto da como resultado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Id of s: 22531456
Id of the String 'example': 22531456 (note that it's the same as the variable s)
s is 'example': True
Change s to something else, then back to 'example'.
Id of s: 22531456
s is 'example': True

Id of list1: 22103504
Id of list2: 22103504
Id of [1]: 22104664 (note that it's not the same as list1!)
list1 == list2: True
list1 is list2: True
Change list1 to something else, then back to the original ([1]) value.
Id of list1: 22591368
list1 == list2: True
list1 is list2: False

Podemos ver que en la primera parte del ejemplo, s regresó exactamente al mismo objeto "example" al que estaba asignado al principio, incluso si cambiamos el valor de s mientras tanto.

Sin embargo, list no devuelve el mismo objeto cuyo valor es [1], sino que se crea un objeto completamente nuevo, incluso si tiene el mismo valor que el primer [1].

Si ejecuta el código anterior, es probable que obtenga ID diferentes para los objetos, pero las igualdades serán las mismas.

¿Cuándo se usan respectivamente 'es' y '=='?

El operador ’es’ se usa más comúnmente cuando queremos comparar el objeto con ‘Ninguno’, y generalmente se recomienda restringir su uso a este escenario en particular a menos que realmente (y quiero decir realmente) desee comprobar si dos objetos Son identicos.

Además, is es generalmente más rápido que el operador == porque simplemente comprueba la igualdad de enteros de la dirección de memoria.

[Nota importante:]{#note} La única situación en la que is funciona exactamente como cabría esperar es con único clases/ objetos (como Ninguno). Incluso con objetos inmutables, hay situaciones en las que is no funciona como se esperaba.

Por ejemplo, para objetos string grandes generados por alguna lógica de código, o ints grandes, is puede (y lo hará) comportarse de manera impredecible. A menos que haga el esfuerzo de internar (es decir, asegurarse absolutamente de que solo existe una copia de una cadena/int/etc.), todos los diversos objetos inmutables que planea usar, is ser impredecible.

La conclusión es: use == en el 99% de los casos.

Si dos objetos son idénticos, también son iguales, y lo contrario no es necesariamente cierto.

Anulación de los operadores '==' y '!='

Los operadores != y is not se comportan de la misma manera que sus contrapartes "positivas". Es decir, != devuelve True si los objetos no tienen el mismo valor, mientras que is not devuelve True si los objetos no están almacenados en la misma dirección de memoria.

Otra diferencia entre estos dos operadores es que puede anular el comportamiento de ==/!= para una clase personalizada, mientras que no puede anular el comportamiento de is.

Si implementa un método __eq()__ personalizado en su clase, puede cambiar el comportamiento de los operadores ==/!=:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class TestingEQ:
    def __init__(self, n):
        self.n = n

    # using the '==' to check whether both numbers
    # are even, or if both numbers are odd
    def __eq__(self, other):
        if (self.n % 2 == 0 and other % 2 == 0):
            return True
        else:
            return False


print(5 == TestingEQ(1))
print(2 == TestingEQ(10))
print(1 != TestingEQ(2))

Esto resulta en:

1
2
3
False
True
True

Conclusión

En resumen, ==/!= comprueba la igualdad (por valor) y is/is not comprueba si dos objetos son idénticos, es decir, comprueba sus direcciones de memoria.

Sin embargo, evita usar is a menos que sepas exactamente lo que estás haciendo, o cuando trates con objetos únicos como Ninguno, ya que puede comportarse de manera impredecible.

Licensed under CC BY-NC-SA 4.0