Ordenar y buscar en listas en Python

Ordenar en Python es una llamada, y el argumento key resuelve casi cualquier criterio. Lo que no resuelve solo es el español: los acentos van al final si no se lo dices.

sorted o .sort()

sorted(nums)                  # devuelve una lista nueva
sorted(nums, reverse=True)
nums.sort()                   # modifica la lista y devuelve None
salidasorted      [2, 2, 4, 7, 9]   (devuelve otra lista)
reverse     [9, 7, 4, 2, 2]
.sort()     [2, 2, 4, 7, 9]   (modifica, devuelve None: None)
Ese None del final es el error más repetido. Escribir lista = lista.sort() deja la variable valiendo None y el fallo aparece tres líneas después. La convención de Python es que lo que modifica en el sitio devuelve None; si quieres el resultado, sorted.

Y ahora, en español

noms = ["Marta", "ana", "Luis", "ángel"]
sorted(noms)
sorted(noms, key=str.lower)
salidapor omisión   ['Luis', 'Marta', 'ana', 'ángel']
key=str.lower ['ana', 'Luis', 'Marta', 'ángel']

El orden por omisión compara el valor numérico de cada carácter: las mayúsculas van antes y la «á» queda muy por detrás de la «z». str.lower arregla lo primero y deja «ángel» al final.

import unicodedata
quita = lambda s: unicodedata.normalize("NFKD", s.lower()).encode("ascii", "ignore")
sorted(noms, key=quita)
salidasin acentos   ['ana', 'ángel', 'Luis', 'Marta']
Lo «correcto» sería locale.strcoll, y por eso no es la primera opción. Lo probé en el contenedor y falla: «unsupported locale setting», porque la imagen no trae generada la configuración regional es_ES.UTF-8. Depende de la máquina, no del programa, y además locale es global al proceso. El truco de normalizar con NFKD y quitar los acentos funciona en todas partes y no depende de nada.

Por campos

from operator import attrgetter, itemgetter

sorted(ps, key=attrgetter("precio"))
sorted(ps, key=lambda x: (-x.uds, x.nombre))     # uds desc, luego nombre
max(ps, key=attrgetter("precio"))
sorted(datos, key=itemgetter(0, 1))              # para tuplas y listas
salidaprecio       ['bufanda', 'gorra', 'neceser', 'mochila']
uds desc,    ['gorra(7)', 'mochila(7)', 'neceser(3)', 'bufanda(2)']
max / min    mochila / bufanda
tuplas       [(1, 'c'), (2, 'a'), (2, 'b')]

Para ordenar por varios criterios se devuelve una tupla desde la key, y se comparan por orden. Para invertir uno solo —ahí, las unidades— se le pone un menos delante; con texto eso no vale, y entonces hay que ordenar dos veces, del criterio menos importante al más importante.

La ordenación es estable

salidaordenar por el 2º: [('c', 0), ('b', 1), ('a', 1)]   (b antes que a: se respeta el orden original)

Los elementos que empatan conservan su posición relativa. Eso es lo que permite el truco de ordenar varias veces seguidas, y es una garantía del lenguaje, no una casualidad.

Buscar

7 in lista          # recorre: lento, pero vale para todo
lista.index(4)

import bisect
bisect.bisect_left(ord_, 7)     # exige la lista ordenada
bisect.insort(copia, 5)         # inserta manteniendo el orden
salida7 in lista       True        index(4) 2
bisect_left(7)   3
bisect_left(5)   3   <- donde habría que insertarlo
insort(5)        [2, 2, 4, 5, 7, 9]

bisect no dice si el elemento está: dice dónde iría. Para saber si está hay que comprobar ord_[i] == x después. Y sobre una lista desordenada no da error, da una respuesta equivocada.

Quitar repetidos

list(dict.fromkeys(r))     # conserva el orden de aparición
sorted(set(r))             # los ordena
salidaconservando orden  ['b', 'a', 'c']
y ordenados        ['a', 'b', 'c']

El truco de dict.fromkeys funciona porque los diccionarios conservan el orden de inserción desde Python 3.7. Antes había que usar OrderedDict.

Todo el código se ejecutó con Python 3.14 en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.