¡Bienvenido(a)! Si deseas aprender a usar el método sort() en Python, este artículo es para ti. El método sort() es muy poderoso y puedes personalizarlo para que se ajuste a tus necesidades, así que veamos en detalle cómo funciona.

En este artículo aprenderás:

  • Cómo usar el método sort() en Python.
  • Cuándo usarlo.
  • Cómo llamarlo con distintas combinaciones de argumentos.
  • Cómo ordenar una lista en orden ascendente y descendente.
  • Cómo comparar los elementos de una lista en base a valores intermedios.
  • Cómo pasar funciones lambda al método sort().
  • Las diferencias entre el método sort() y la función sorted().
  • Por qué el método sort() realiza un proceso de ordenamiento estable.
  • Cómo funciona el proceso de mutación de una lista.

¡Comencemos! ✨

🔹 Propósito y Usos

Con el método sort() puedes ordenar una lista en:

  • Orden ascendente
  • Orden descendente

Este método ordena una lista "in-place", lo cual significa que la lista muta o cambia directamente en memoria sin crear copias adicionales. Por lo tanto, recuerda que:

image-29

Te prometo que en este artículo aprenderás cómo funciona el proceso de mutación y cómo se modifica un objeto en memoria, pero ahora es muy importante que sepas que el método sort() muta la lista y por lo tanto, la versión original de la lista se pierde.

Debido a esto, sólo deberías usar este método si:

  • Deseas modificar (ordenar) la lista permanentemente.
  • No necesitas mantener la versión original de la lista.

Si estás de acuerdo con estas condiciones, el método .sort() es exactamente lo que necesitas.

🔸 Sintaxis y Argumentos

Veamos cómo puedes llamar al método sort() y aprovechar todo su poder.

Esta es la llamada más básica que podemos hacer (sin argumentos):

image-30

Si no pasas ningún argumento, por defecto:

  • La lista será ordenada en orden ascendente.
  • Los elementos de la lista serán comparados directamente.

Por ejemplo:

>>> mi_lista = [6, 3, 8, 2, 7, 3, 9]

>>> mi_lista.sort()

>>> mi_lista
[2, 3, 3, 6, 7, 8, 9] # ¡Ordenada!

Personalizando los Argumentos

Para personalizar el efecto del método sort(), podemos pasar dos argumentos opcionales:

  • key (clave)
  • reverse (reversar)

Veamos cómo estos argumentos cambian el resultado de llamar al método sort(). Este es un ejemplo de una llamada con ambos argumentos:

image-31

Antes de explicarte cómo funcionan, me gustaría que viéramos en más detalle algo que probablemente notaste en el diagrama anterior: los nombres de los parámetros fueron incluidos en la llamada con sus valores correspondientes, de esta forma:

  • key=<función>
  • reverse=<valor>

Esto se debe a que son parámetros de un tipo especial denominados "parámetros sólo nombrados" (en inglés se denominan "keyword-only parameters"). Si deseas especificar un valor para estos parámetros, debes especificar sus nombres en la llamada, seguidos de un signo igual = y luego sus valores correspondientes, de esta forma:

<parámetro>=<valor>

image-32

Si no escribes los nombres de los parámetros e intentas pasar los valores directamente como lo haríamos normalmente con parámetros posicionales, verás el siguiente error porque la función no sabrá qué valor corresponde a cada parámetro:

TypeError: sort() takes no positional arguments

Parámetro reverse

Ahora que ya sabes más sobre los parámetros "sólo nombrados", veamos el argumento reverse.

El valor de reverse puede ser True (verdadero) o False (falso):

  • False significa que la lista será ordenada de forma ascendente.
  • True significa que la lista será ordenada de forma descendente.

💡 Dato: Por defecto, el valor de este argumento es False, así que si no pasas un argumento para este parámetro, la lista se ordenará de forma ascendente.

Aquí tenemos algunos ejemplos:

image-33
Por defecto, el valor de reverse es False (falso)
# Lista de Números Enteros.
>>> b = [6, 3, 8, 2, 7, 3, 9]
>>> b.sort()
>>> b
[2, 3, 3, 6, 7, 8, 9]


# Lista de Cadenas de Caracteres.
>>> c = ["A", "Z", "D", "T", "U"]
>>> c.sort()
>>> c
['A', 'D', 'T', 'U', 'Z']

💡 Dato: Si la lista contiene cadenas de caracteres, el método sort() ordena la lista en orden alfabético.

image-34
Ordenar una lista en orden descendente.

💡 Dato: La lista se ordena en orden descendente solo si el valor de reverse es True.

# Lista de Números Enteros.
>>> b = [6, 3, 8, 2, 7, 3, 9]
>>> b.sort(reverse=True)
>>> b
[9, 8, 7, 6, 3, 3, 2]

# Lista de Cadenas de Caracteres.
>>> c = ["A", "Z", "D", "T", "U"]
>>> c.sort(reverse=True)
>>> c
['Z', 'U', 'T', 'D', 'A']

Parámetro key

Ahora que ya sabes cómo usar el parámetro reverse, veamos el parámetro key.

Este parámetro require una explicación más detallada porque su valor determina cómo se comparan los elementos de la lista durante el proceso de ordenamiento.  

image-35
Sintaxis básica para especificar el parámetro key

Es valor de key puede ser:

  • None, lo cual significa que los elementos de la lista serán comparados directamente de acuerdo a su valor. Por ejemplo, en una lista de números enteros, el valor de los números es usado para comparar los elementos.
  • Una función que recibe un argumento y retorna un valor intermedio para cada elemento. Este valor intermedio se calcula una sola vez y se usa para realizar las comparaciones durante el proceso de ordenamiento. Usamos este parámetro cuando no queremos comparar los elementos directamente. Por ejemplo, cuando queremos comparar cadenas de caracteres en base a su tamaño (número de caracteres) en lugar de orden alfabético. El tamaño de la cadena de caracteres sería el valor intermedio generado por la función.  

💡 Dato: Por defecto, el valor de key es None y por lo tanto los elementos se comparan directamente.

Por ejemplo:

Digamos que queremos ordenar una lista de cadenas de caracteres en base su tamaño, desde la cadena más corta hasta la cadena más larga.

Para hacerlo, podemos pasar la función len como el valor de key, de esta forma:

>>> d = ["aaa", "bb", "c"]

>>> d.sort(key=len)

>>> d
['c', 'bb', 'aaa']

💡 Dato: Es importante resaltar que solo estamos pasando el nombre de la función (len) como argumento (sin paréntesis). Esto es muy importante porque no estamos llamando a la función, sino pasándola como argumento.

Nota la diferencia entre comparar las cadenas de caracteres directamente y compararlas en base a su tamaño. Esta diferencia se ilustra en el siguiente diagrama:

image-49
Izquierda: Ordenadas alfabéticamente. | Derecha: Ordenadas en base a su tamaño.
  • Si hubiésemos usado el valor asignado por defecto para el parámetro key (None), la lista hubiera sido ordenada alfabéticamente. Puedes ver este resultado a la izquierda del diagrama.
  • Pero ahora estamos ordenándolas en base a su tamaño. Puedes ver el resultado a la derecha del diagrama.

¿Qué cambió en el proceso ahora que estamos pasando len() como argumento?

Cada elemento es pasado a la función len() uno a uno y el valor retornado por esta función para cada elemento se usa para realizar las comparaciones durante el proceso de ordenamiento:

image-122
Generando los valores intermedios.

Esto resulta en una lista ordenada en base a un criterio diferente: el tamaño de las cadenas de caracteres.

Aquí tenemos otro ejemplo del método sort():

Podemos ordenar una lista de cadenas de caracteres como si todas estuvieran escritas en minúscula (por ejemplo, hacer que la cadena de caracteres "Aa" sea equivalente a "aa" en las comparaciones).

Según el orden alfabético establecido en Python, las letras mayúsculas preceden a las letras minúsculas. Por ejemplo:

>>> "E" < "e"
True

Así que la cadena de caracteres "Emma" se ubicaría antes que "emily" en una lista ordenada, incluso si sus versiones en minúscula hubiesen estado en el orden opuesto:

>>> "Emma" < "emily"
True
>>> "emma" < "emily"
False

Ordenar Sin Distinguir Mayúsculas y Minúsculas

Para evitar la distinción entre letras en mayúscula y letras en minúscula, podemos pasar el método str.lower como la clave (key). Esto genera una versión de cada una de las cadenas de caracteres en minúscula para realizar las comparaciones:

>>> e = ["Emma", "emily", "Amy", "Jason"]

>>> e.sort(key=str.lower)

>>> e
['Amy', 'emily', 'Emma', 'Jason']

Ahora, la cadena de caracteres "emily" se ubica antes que "Emma" en la lista ordenada, exactamente lo que queríamos porque no estamos distinguiendo entre mayúsculas y minúsculas.

💡 Dato: si realizamos el proceso de ordenamiento que se ejecuta por defecto, todas las cadenas de caracteres que comiencen con una letra mayúscula se ubicarán primero en la lista que todas las cadenas de caracteres que comienzan con una letra minúscula:

>>> e = ["Emma", "emily", "Amy", "Jason"]

>>> e.sort()

>>> e
['Amy', 'Emma', 'Jason', 'emily']

Aquí tenemos otro ejemplo con Programación Orientada a Objetos (POO):

Si definimos esta clase en Python:

>>> class Cliente:
	def __init__(self, edad):
		self.edad = edad

Y creamos cuatro instancias:

>>> cliente1 = Cliente(67)

>>> cliente2 = Cliente(23)

>>> cliente3 = Cliente(13)

>>> cliente4 = Cliente(35)

Podemos crear una lista que las contenga:

>>> clientes = [cliente1, cliente2, cliente3, cliente4]

Luego, si definimos una función que retorna el valor del atributo edad de estas instancias:

>>> def retornar_edad(cliente):
	return cliente.edad

Podemos ordenar la lista en base a la edad de las instancias si pasamos el nombre de la función get_age como el valor del parámetro key:

>>> clientes.sort(key=retornar_edad)

Luego de llamar al método tendremos la versión final y ordenada de la lista. Podemos usar un ciclo para mostrar la edad de las instancias en el orden en que aparecen en la lista ordenada:

>>> for cliente in clientes:
	print(cliente.edad)

	
13
23
35
67

Exactamente lo que queríamos – ahora la lista está ordenada en orden ascendente en base a las edades de las instancias.

💡 Dato: En lugar de definir la función retornar_edad, podríamos haber definido una función lambda para obtener la edad de cada instancia, de esta forma:

>>> clientes.sort(key=lambda x: x.edad)

Las funciones lambda son funciones pequeñas, simples, y anónimas, lo cual significa que no tiene nombre. Son muy útiles en estas situaciones, cuando sólo queremos usarlas en lugares particulares de nuestro código sin necesidad de asignarles un nombre y usarlas nuevamente.

Esta es la estructura básica de la función lambda que podemos definir para ordenar la lista en base a la edad de las instancias:

image-36
Estructura Básica de una Función Lambda

El resultado será el mismo que obtuvimos previamente si llamamos al método sort() con esta función lambda como el valor del parámetro key.

Pasando Ambos Argumentos

¡Genial! Ahora puedes personalizar el efecto del método sort(). Pero si quieres llevar tus habilidades al siguiente nivel, puedes combinar el efecto de key y reverse en la misma llamada:

>>> f = ["A", "a", "B", "b", "C", "c"]

>>> f.sort(key=str.lower, reverse=True)

>>> f
['C', 'c', 'B', 'b', 'A', 'a']
Ordenar la lista en orden decreciente como si todas las cadenas de caracteres estuvieran escritas en minúscula.

Estas son las posibles combinaciones de argumentos y su efecto:

image-37

El Orden de los Argumentos "Solo Nombrados" (Keyword-Only) No Importa

Quizás te preguntes si existe un orden específico para escribir los parámetros de sort().

La respuesta es no.

Como estamos especificando el nombre de los parámetros al llamar a sort(), el programa puede determinar qué valor corresponde a cada parámetro, así que podemos incluirlos en cualquier orden en la lista de argumentos y el efecto será exactamente el mismo.

Así que esta llamada al método sort():

image-38

Es equivalente a esta:

image-39

Aquí tenemos un ejemplo:

>>> a = ["Zz", "c", "y", "o", "F"]

>>> a.sort(key=str.lower, reverse=True)

>>> a
['Zz', 'y', 'o', 'F', 'c']

Si invertimos el orden de los argumentos, obtenemos exactamente el mismo resultado:

>>> a = ["Zz", "c", "y", "o", "F"]

>>> a.sort(reverse=True, key=str.lower)

>>> a
['Zz', 'y', 'o', 'F', 'c']

🔹 Valor Retornado por el Método sort()

Ahora que sabes cómo funciona el método sort(), hablemos un poquito sobre el valor que retorna. Este método retorna el valor Noneno retorna una versión ordenada de la lista, como podríamos pensar inicialmente.

Según la documentación de Python:

Este método modifica la lista in situ, para ahorrar espacio cuando se ordena una secuencia muy grande. Para recordar a los usuarios que funciona de esta manera, no se retorna la secuencia ordenada.

Básicamente, se retorna None para recordarnos que este método modifica la lista original en memoria y que no genera una nueva copia de la lista.

Este es un ejemplo del valor retornado por el método sort():

>>> mi_lista = [6.5, 2.4, 7.3, 3.5, 2.6, 7.4]

# Asignamos el valor retornado a una variable.
>>> valor_retornado = mi_lista.sort()

# Verificamos el valor retornado.
>>> print(valor_retornado)
None

¿Ves? La llamada retornó el valor None.

💡 Dato: es muy importante no confundir el método sort() con la función sorted(), la cual es una función que genera un efecto similar, pero no modifica la lista original, sino que genera y retorna una copia nueva y ordenada de la lista.

Este es un ejemplo que podemos usar para comparar ambos métodos:

# El método sort() retorna el valor None.

>>> mi_lista = [6.5, 2.4, 7.3, 3.5, 2.6, 7.4]

>>> valor_retornado = mi_lista.sort()

>>> print(valor_retornado)
None
Ejemplo del método sort()
# sorted() returna una copia ordenada de la lista 
# pero no modifica la lista original.
>>> mi_lista = [6.5, 2.4, 7.3, 3.5, 2.6, 7.4]

>>> valor_retornado = sorted(mi_lista)

>>> valor_retornado
[2.4, 2.6, 3.5, 6.5, 7.3, 7.4]

>>> mi_lista
[6.5, 2.4, 7.3, 3.5, 2.6, 7.4]
Ejemplo de la función sorted()

Esto es muy importante porque su efecto es muy diferente. Llamar al método sort() cuando en realidad necesitas usar la función sorted() puede generar "bugs" o problemas importantes en el código ya que quizás no detectes inmediatamente que la lista fue mutada.

🔸 El Método sort() Realiza un Proceso de Ordenamiento Estable

Ahora hablemos sobre el algoritmo de ordenamiento usado por el método sort().

Este método realiza un proceso de ordenamiento estable porque ordena la lista con el algoritmo TimSort, un algoritmo de ordenamiento muy eficiente y estable.

Según la documentación de Python:

El método sort() es estable. Un algoritmo de ordenación es estable si garantiza que no se cambia el orden relativo que mantienen inicialmente los elementos que se consideran iguales — Esto es útil para realizar ordenaciones en múltiples fases (Por ejemplo, ordenar por departamento y después por salario).

Esto significa que si dos elementos tienen el mismo valor o valor intermedio (clave), está garantizado que se mantendrán en el mismo orden relativo entre ellos.

Veamos a qué me refiero con esto.

Por favor analiza el siguiente ejemplo:

>>> d = ["BB", "AA", "CC", "A", "B", "AAA", "BBB"]

>>> d.sort(key=len)

>>> d
['A', 'B', 'BB', 'AA', 'CC', 'AAA', 'BBB']

Estamos comparando las cadenas de caracteres en base a su tamaño (número de caracteres) porque pasamos la función len como el valor de key.

Podemos ver que hay tres elementos de tamaño 2: "BB", "AA", y"CC". Ahora mira el resultado del ejemplo anterior y nota que estos elementos están en el mismo orden relativo entre ellos luego del proceso de ordenamiento.

image-40

Esto ocurre porque el algoritmo realiza un proceso de ordenamiento estable. Estos tres elementos tienen el mismo valor intermedio (clave) porque su tamaño es 2 y por lo tanto, su clave es 2.

💡 Dato: Ocurre lo mismo con "A" y "B" (de tamaño 1) y con "AAA" y "BBB" (de tamaño 3) y por lo tanto su orden relativo se mantiene en la versión final de la lista.

Ahora que ya sabes cómo funciona el método sort(), hablemos sobre el proceso de mutación y cómo puede afectar el programa.

🔹 Mutación y Riesgos

Como lo prometí al principio del artículo, veamos cómo funciona el proceso de mutación.

Cuando defines una lista en Python como esta:

a = [1, 2, 3, 4]

Estás creando un objeto en una ubicación específica de la memoria de tu dispositivo. Esta ubicación se denomina la "dirección de memoria" del objeto y se representa con un número entero único llamado id en inglés ("identificación" sería una traducción literal del término en español).

El id de un objeto es como una "etiqueta" especial utilizada para representar una ubicación específica en la memoria del dispositivo en el cual se ejecuta el programa.

image-41

Puedes acceder al id de la lista con la función id(), pasando la lista como argumento:

>>> a = [1, 2, 3, 4]

>>> id(a)
60501512

En el artículo hemos visto que cuando se muta una lista, se cambia el objeto en memoria. Debido a esto, estoy segura de que debes estar preguntándote sobre los riesgos del proceso de mutación en general.

El proceso de mutación conlleva ciertos riesgos porque al mutar un objeto estamos afectando cada línea de código que accede o modifica ese objeto luego de la mutación, por lo que puedes pensar que estás trabajando con una lista completamente distinta de la lista con la que estás trabajando en realidad.

Por eso debes tener mucho cuidado con los métodos que mutan objetos. Específicamente, el método sort() muta una lista.

Este es un ejemplo de su efecto:

image-42

Aquí tenemos el ejemplo en código:

# Definimos una lista.
>>> a = [7, 3, 5, 1]

# Verificamos su id.
>>> id(a)
67091624

# Ordenamos la lista con .sort()
>>> a.sort()

# Verificamos su id (es el mismo, así que la lista es el mismo objeto en memoria).
>>> id(a)
67091624

# Ahora la lista está ordenada. ¡Fue mutada!
>>> a
[1, 3, 5, 7]

La lista fue mutada porque llamamos al método sort().

Cada línea de código que accede a la lista o la modifica en el programa luego de la mutación estará trabajando con la nueva versión ordenada de la lista.

Si esta no era tu intención, quizás no detectes que el programa está usando la nueva versión hasta que haya un problema visible o funcional en el programa (lo que se conoce en inglés como un "bug").

Aquí tenemos otro ejemplo de los riesgos de mutar una lista en una función:

>>> mi_lista = [7, 3, 5, 1]

# Función que muestra los elementos de la lista en orden ascendente.
>>> def ordenar_lista(x):
	x.sort()
	for elem in x:
		print(elem)

# Llamando a la función pasando 'a' como argumento.
>>> ordenar_lista(mi_lista)
1
3
5
7

# ¡Ups! La lista fue mutada al llamar a la función.
>>> mi_lista
[1, 3, 5, 7]

La lista mi_lista que pasamos como argumento fue mutada y quizás este no era el efecto o resultado que esperabas cuando definiste la función, lo cual podría resultar en "bugs".

💡 Dato: una función debería estar claramente documentada y mencionar si muta alguno de sus argumento para evitar que se generen problemas o "bugs" en el programa a causa de esta mutación.

🔸 En Resumen

  • El método sort() nos permite ordenar una lista en orden ascendente o descendente.
  • Recibe dos argumentos "solo nombrados": reverse y key.
  • reverse determina si la lista será ordenada de forma ascendente o descendente.
  • key es una función que toma un solo argumento y retorna un valor intermedio para cada elemento de la lista. Este valor es usado para realizar las comparaciones durante el proceso de ordenamiento.
  • El método sort() muta la lista y por lo tanto genera cambios permanentes en la lista original. Debes tener mucho cuidado y solo usarlo si no necesitas mantener la versión original de la lista.

Espero que te haya gusta mi artículo y que te haya sido de utilidad. Ahora puedes ordenar tus listas en Python con el método sort(). Te invito a seguirme en Twitter @EstefaniaCassN