Cómo Filtrar una Lista en Python: la Función Filter

Cómo filtrar listas en Python

Como si se tratara de un buscador de oro que quiere separar las pepitas de oro de la tierra, en este artículo te voy a enseñar cómo puedes quedarte con aquellos valores que te interesan de una lista y desechar aquellos que no. Es decir, te cuento cómo filtrar una lista o cualquier colección de datos.

Se puede realizar usando comprensión de listas así: [ x for x in l if f(x) ] donde f es una función que decide mediante un booleano si un elemento de la lista l debe estar en la lista filtrada. También se puede usar la función filter de la siguiente manera: list(filter(f, l)).

Pero como siempre, vayamos por partes. Sigue leyendo y te cuento paso a paso y con ejemplos diferentes formas de realizar un filtrado.

Cómo filtrar una lista con comprensión de listas

Vamos a suponer que tenemos una lista con distintos números enteros y lo que queremos es filtrarla para terminar con una lista en la que solo tengamos los números pares. Este es un problema típico para aquellos que empiezan a aprender Python o programación en general.

Lo primero que nos viene a la mente en este caso es realizar un bucle para recorrer todos los elementos de la lista de origen.

Dentro de dicho bucle vamos comprobando si cada elemento cumple la condición para estar en la lista filtrada, es decir, comprobaríamos si el número es par.

Si el número es par lo añadimos a otra lista de destino, que en un principio está vacía y que conformará nuestra lista filtrada. Si el número no es par (es decir, no cumple la condición) lo ignoramos.

Al finalizar el bucle tendremos una lista que resulta ser la lista filtrada que necesitábamos.

Veamos el ejemplo:

lista = [3, 1, 4, 7, 2, 8, 9, 11, 10] # la lista que queremos filtrar

filtrada = [] # la lista que contendrá los elementos filtrados

for elemento in lista:
    if not elemento % 2:
        filtrada.append(elemento)

print(filtrada) # mostramos el resultado

Este código mostrará por pantalla el siguiente resultado:

[4, 2, 8, 10]

Como puedes ver, el código resulta bastante directo y sencillo, pero hablemos de algunos puntos:

  1. Es necesario crear de antemano la lista filtrada para poder añadir elementos cuando haga falta.
  2. Necesitamos incorporar un condicional en el bucle. En este caso el condicional resulta sencillo pero podría ser más complejo, cosa que haría que el código resultara menos claro y más difícil de leer y de entender.
  3. El código es relativamente largo para una operación que es muy común y, en principio, debería resultar más sencilla.

Veamos maneras para resolver estos puntos. Si no entiendes del todo el condicional que planteo en el ejemplo anterior, donde aparentemente no hay comparación, puedes mirar este artículo sobre condicionales en Python donde te explico, entre otras cosas, por qué se puede manejar un número entero (que es el resultado de elemento % 2) como si fuera un valor booleano, es decir, True o False.

Si la condición para que un elemento sea incluido en la lista filtrada se hace un poco más compleja, puede resultar interesante encapsularla dentro de una función. Así podemos llamar a esta función desde dentro del bucle haciendo que el propio bucle sea más sencillo.

Para ilustrar esto puedes imaginar que necesitamos aquellos elementos de la lista que sean pares o los que sean múltiplos de 3 o múltiplos de 11. De esta manera la condición quedaría así: not elemento % 2 or not elemento % 3 or not elemento % 11.

Como ya te imaginas, esto hará que el bucle quede un poco menos bonito:

lista = [3, 1, 4, 7, 2, 8, 9, 11, 10] # la lista que queremos filtrar

filtrada = [] # la lista que contendrá los elementos filtrados

for elemento in lista:
    if not elemento % 2 or not elemento % 3 or not elemento % 11:
        filtrada.append(elemento)

print(filtrada) # mostramos el resultado

Esto es todavía peor si la condición se complicara más (imagínate que queremos los números primos de la lista u otra cosa por el estilo). En estos casos lo mejor es sacar la condición fuera del bucle.

Esto lo podemos hacer poniéndola en una función, logrando así un código más modular y organizado y que facilita su comprensión y mantenimiento. Llamaremos condicion a dicha función:

def condicion(numero):
    return not numero % 2 or not numero % 3 or not numero % 11


lista = [3, 1, 4, 7, 2, 8, 9, 11, 10] # la lista que queremos filtrar

filtrada = [] # la lista que contendrá los elementos filtrados

for elemento in lista:
    if condicion(elemento):
        filtrada.append(elemento)

print(filtrada) # mostramos el resultado

Vale, ahora ya tenemos un código un poco más legible y además hemos separado responsabilidades (la comprobación por un lado y la construcción de la lista filtrada por otro).

De todas maneras, podemos decir que este código no es muy estilo Python. Hagamos uso de la compresión de listas y resolvamos los puntos 1 y 3 que veíamos más arriba, es decir, que no necesitamos crear una lista vacía previamente y vamos a simplificar el código todavía más.

La compresión de listas nos permite generar una lista al vuelo. Te lo explico en más detalle en otro artículo donde te cuento distintos usos de los corchetes [ ] en Python.

Con esto el ejemplo nos quedaría de la siguiente manera:

def condicion(numero):
    return not numero % 2 or not numero % 3 or not numero % 11


lista = [3, 1, 4, 7, 2, 8, 9, 11, 10] 

filtrada = [ elemento for elemento in lista if condicion(elemento) ]

print(filtrada)

Ya ves que usando la comprensión de listas nos ahorramos algunas líneas de código. El resultado de ejecutar este ejemplo es el siguiente:

[3, 4, 2, 8, 9, 11, 10]

Ten en cuenta que igual que usamos la comprensión para generar una lista, también podemos generar una tupla o un conjunto si usamos la notación de paréntesis ( ) o la de llaves { }.

De la misma manera, si el conjunto inicial de datos no es una lista, y es una tupla, un conjunto o, en general, cualquier elemento iterable, nuestro código funcionará igual.

Veamos un ejemplo en donde partiendo de una tupla con distintos elementos quiero quedarme solo con los pares sin repetidos, por lo que voy a generar un conjunto en lugar de una lista (los conjuntos no admiten repetidos). Aprovecho también para ponerle un nombre más identificativo a la función condicion:

def es_par(numero):
    return not numero % 2


tupla = (4, 3, 2, 6, 7, 2, 11, 2, 4, 13, 27, 2, 1)

conjunto = { elemento for elemento in tupla if es_par(elemento) }

print(conjunto)

Fíjate: en la tupla de origen tengo varios números repetidos y varios impares. El resultado final tendría que tener solo los pares y ningún repetido. Si ejecutas el código anterior obtendrás lo siguiente:

{2, 4, 6}

Pasemos ahora a ver otra manera, todavía mejor, de hacer un filtrado, que es por medio del uso de la función propia de Python filter.

Cómo filtrar una lista con la función filter

La función filter devuelve un objeto iterable con todos aquellos valores de una colección de datos que cumplen una determinada condición. Hay que invocarla con dos parámetros: una función que determina si un elemento dado cumple la condición y la colección de datos a filtrar: filter(funcion, datos).

Como ves, esta es una manera más cómoda de lograr el filtrado de una lista o en general de cualquier secuencia, elemento iterable o iterador.

Has de tener en cuenta que la llamada a la función filter devuelve un objeto y no directamente una lista o una secuencia a la que se pueda acceder mediante índices. Este objeto, que es de la clase filter es un objeto iterable. Esto quiere decir que puedes acceder secuencialmente a sus elementos utilizando la función next. También puedes recorrerlo con un bucle for.

Lo interesante es que si quieres lograr una lista, una tupla o un conjunto a partir del objeto filter puedes hacerlo invocando a las funciones list, tuple o set, respectivamente sobre el resultado del filtrado.

La función filter recibe dos parámetros:

  • El primero debe ser una función o elemento invocable que pueda recibir un parámetro. Esta función será invocada de manera secuencial en un bucle en donde en cada iteración se proporcione como argumento uno de los elementos de la lista. Esta función de filtro, que llamaremos condicion debe devolver un valor booleano, True o False, indicando si el elemento recibido por parámetro debe o no estar en la lista filtrada.
  • El segundo parámetro ha de ser la colección de elementos y puede ser una secuencia o cualquier elemento iterable, como una lista o una tupla.

Veamos, entonces, cómo lograr una lista filtrada a partir de otra que contenga solo los elementos que sean múltiplos de 2, de 3 o de 11:

def condicion(numero):
    return not numero % 2 or not numero % 3 or not numero % 11


lista = [3, 1, 4, 7, 2, 8, 9, 11, 10] # la lista a filtrar

filtrada = list(filter(condicion, lista)) # llamamos a filter y posteriormente a list

print(filtrada) # mostramos la lista filtrada

Y el resultado obtenido por pantalla será el siguiente:

[3, 4, 2, 8, 9, 11, 10]

¿Sencillo, no?

Si nos ponemos en la situación de que la condición para el filtro es sencilla, no necesitamos crear la función condicion aparte y podemos usar expresiones lambda.

Las expresiones lambda nos permiten crear funciones sin nombre que realizan una operación sencilla para unos argumentos dados y devuelven el resultado de la operación.

Por ejemplo, podría crear una expresión lambda que me dijera si un número es par de la siguiente manera, donde además, asigno esa expresión a una variable para darle nombre a la función y usarla cuando quiera. Esto es posible porque las funciones son elementos de primer orden en Python (lo que permite asignarlas a una variable, como ya he dicho, o proporcionarlas como argumento a otras funciones):

es_par = lambda x: not x % 2

print(es_par(2))
print(es_par(7))
print(es_par(12))

El resultado será, como ya te imaginas, el siguiente:

True
False
True

Lo que podemos hacer aquí es, por medio de una expresión lambda, filtrar una lista sin necesidad de crear una función de condición de filtro y, además, podemos proporcionar directamente la expresión lambda a la función filter para lograr un código muy compacto:

lista = [3, 1, 4, 7, 2, 8, 9, 11, 10]

filtrada = tuple(filter(lambda x: not x % 2, lista)) # filtrando con una expresión lambda

print(filtrada)

En esta ocasión hemos generado una tupla con lo que el resultado por pantalla será el siguiente:

(4, 2, 8, 10)

Ya ves lo cómodo, fácil y rápido que puede llegar a ser hacer un filtrado con la función filter. Pero… ¡todavía hay más!.

Cómo filtrar los elementos nulos de una lista

Es posible indicarle a la función filter, en lugar de una función que devuelva un booleano, el valor None. De esta manera obtendremos una lista filtrada donde aquellos elementos que se evalúan a False en Python (como False, 0, '', o [], por ejemplo) son los que se eliminan.

Así, y por ejemplo, si queremos eliminar de una lista de números enteros todos los ceros simplemente tenemos que hacer lo siguiente:

lista = [2, 0, 0, 4, 6, 1, 0, 0, 0, 3, 0]

filtrada = list(filter(None, lista)) # filtrando con None

print(filtrada)

Con este código obtenemos el siguiente resultado:

[2, 4, 6, 1, 3]

No obstante, y como ya te dije, de esta manera podemos eliminar de la lista filtrada cualquier elemento que se evalúe a False:

lista = [None, False, True, 0, 1, 'texto', '', [10, 11, 12], [], {10, 11, 12}, {}, (10, 11, 12), ()]

filtrada = list(filter(None, lista)) # filtrando con None

print(filtrada)

Con esto tendremos el siguiente resultado por pantalla:

[True, 1, 'texto', [10, 11, 12], {10, 11, 12}, (10, 11, 12)]

Cómo obtener los elementos eliminados por el filtrado

Hay veces en las que, además de querer la lista filtrada, también necesitamos una segunda lista con los elementos que se eliminaron durante el filtrado. O lo que es lo mismo, necesitamos invertir la condición del filtrado para quitar aquellos elementos que no cumplen la condición.

La función filter no nos permite hacer esto directamente, es decir, tendríamos que crear la condición contraria manualmente. Pero Python ya tiene esto en cuenta y nos proporciona la función filterfalse ubicada en el módulo itertools.

Así, si queremos filtrar una lista para quedarnos con todos los elementos que sean múltiplos de 2, de 3 o de 11 y, además, necesitamos otra lista con los elementos eliminados, es decir, aquellos que no sean múltiplos ni de 2, ni de 3 ni de 11, podemos hacer lo siguiente:

from itertools import filterfalse # importamos la función

def condicion(numero):
    return not numero % 2 or not numero % 3 or not numero % 11

datos = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

filtrada = list(filter(condicion, datos)) # filtramos la lista
eliminados = list(filterfalse(condicion, datos)) # filtramos la lista con la condición contraria

print(f'La lista filtrada es {filtrada}')
print(f'Los elementos eliminados son {eliminados}')

Y de esta manera tan cómoda obtenemos por separado los elementos que cumplen la condición de los que no la cumplen:

La lista filtrada es [2, 3, 4, 6, 8, 9, 10, 11, 12, 14, 15]
Los elementos eliminados son [1, 5, 7, 13]

Cómo filtrar una lista de objetos

Para complicar un poquito las cosas, y para ver que no siempre tenemos que filtrar una colección de valores simples, vamos a ponernos en la situación de que lo que tenemos es una colección de objetos.

Por ejemplo, vamos a trabajar con triángulos. Para ellos vamos a crear una clase Triangulo con tres atributos que son la longitud en milímetros de cada uno de los tres lados. Además, incorporaremos una función que nos devuelva el área del triángulo y otra que nos devuelva el perímetro.

También, para ser un poco formales, vamos a evitar la creación de un triángulo si sus lados son incorrectos, cosa que puede pasar si el valor de algunos de ellos es cero o menos o si la suma de dos lados es inferior al valor del tercer lado.

Veamos el código:

from math import sqrt

class Triangulo:

    def __init__(self, lado_a, lado_b, lado_c):
        if lado_a <= 0 or lado_b <= 0 or lado_c <= 0 or \
                lado_a + lado_b <= lado_c or \
                lado_b + lado_c <= lado_a or \
                lado_a + lado_c <= lado_b:
            raise ValueError('Triángulo con lados incorrectos.')

        self.lado_a = lado_a
        self.lado_b = lado_b
        self.lado_c = lado_c

    def __str__(self):
        return f'Triangulo: {self.lado_a} {self.lado_b} {self.lado_c}, área {self.area()}, perímetro {self.perimetro()}.'

    def area(self): # aplicamos la fórmula de Herón
        sp = self.perimetro() / 2 
        return sqrt(sp * (sp - self.lado_a) * (sp - self.lado_b) * (sp - self.lado_c))

    def perimetro(self):
        return self.lado_a + self.lado_b + self.lado_c

Para calcular el perímetro a partir de los lados del triángulo basta con sumarlos. Para calcular el área de un triángulo a partir del valor de sus lados podemos aplicar la fórmula de Herón.

Ahora podemos crear triángulos de manera sencilla de la siguiente manera:

t1 = Triangulo(2, 3, 4)
t2 = Triangulo(17, 21, 25)
t3 = Triangulo (0.5, 0.3, 0.47)

print(t1, t2, t3, sep='\n')

Con esto tendremos el siguiente resultado por pantalla:

Triangulo: 2 3 4, área 2.9047375096555625, perímetro 9.
Triangulo: 17 21 25, área 176.5593030684025, perímetro 63.
Triangulo: 0.5 0.3 0.47, área 0.06883639571476707, perímetro 1.27.

Con esto claro pasemos a ver un ejemplo en donde queremos filtrar una lista de objetos de clase Triangulo.

Vamos a suponer que queremos filtrar la lista de triángulos para quedarnos con aquellos que tengan un área máxima de 10 y un perímetro mínimo de 15.

Para realizar esto creamos unos cuantos triángulos y los añadimos a una lista. Después hacemos el filtrado utilizando una expresión lambda en donde llevamos a cabo la comparación con los valores indicados y haciendo las llamadas correspondientes a las funciones area y perimetro. Finalmente mostramos por pantalla los triángulos filtrados obtenidos:

# creamos unos cuantos triángulos y los añadimos a una lista
triangulos = []
triangulos.append(Triangulo(10, 7, 6))
triangulos.append(Triangulo(5, 4, 6))
triangulos.append(Triangulo(7, 8, 4))
triangulos.append(Triangulo(7, 9, 5))
triangulos.append(Triangulo(4, 4, 4))
triangulos.append(Triangulo(4, 5, 8))

print('Triangulos:')
for i, t in enumerate(triangulos):
    print(f'    {i}: {t}')

filtrados = list(filter(lambda t: t.area() <= 10 and t.perimetro() >= 15, triangulos))

print('Triangulos filtrados:')
for i, t in enumerate(filtrados):
    print(f'    {i}: {t}')

Fíjate en que el filtrado se realiza igualmente en una sola línea y tal cual vimos en un ejemplo anterior, solo que ahora se tiene en cuenta en la expresión lambda que el valor con el que trabajamos es un triángulo, de forma que podemos invocar a sus funciones.

La manera que he elegido para mostrar los triángulos por pantalla pasa por utilizar la función propia de Python enumerate que me permite obtener el índice de cada triángulo en la lista para mostrarlo también. Te hablo más de la función enumerate y de cómo utilizarla en otro artículo sobre cómo recorrer dos listas a la vez en Python.

La salida por pantalla que genera el código anterior es la siguiente, donde puedes ver que, efectivamente, los triángulos filtrados son aquellos que cumplen las condiciones establecidas para el área máxima de 10 y el perímetro mínimo de 15:

Triangulos:
     0: Triangulo: 10 7 6, área 20.662465970933866, perímetro 23.
     1: Triangulo: 5 4 6, área 9.921567416492215, perímetro 15.
     2: Triangulo: 7 8 4, área 13.997767679169419, perímetro 19.
     3: Triangulo: 7 9 5, área 17.41228014936585, perímetro 21.
     4: Triangulo: 4 4 4, área 6.928203230275509, perímetro 12.
     5: Triangulo: 4 5 8, área 8.181534085976786, perímetro 17.
Triangulos filtrados:
     0: Triangulo: 5 4 6, área 9.921567416492215, perímetro 15.
     1: Triangulo: 4 5 8, área 8.181534085976786, perímetro 17.

Bueno, podría seguir poniéndote ejemplos donde se filtren cadenas de texto que contengan un substring, o donde se obtengan aquellos clientes que tienen una deuda con nosotros, pero creo que con todo lo que te he contado aquí tienes las herramientas suficientes para hacer de manera cómoda y rápida un filtrado en una colección de datos.

La Hoja de Referencia de Python – ¡Gratis!

La Hoja de Referencia de Python - Código Pitón
Consigue trucos, consejos y actualizaciones y, por supuesto, la Hoja de Referencia de Python gratis.



Antes de suscribirte consulta aquí la Información Básica sobre Protección de Datos. Responsable de los datos: Laura Otero Moreira. Finalidad de la recogida y tratamiento de los datos personales: enviarte boletín informativo de Python y comunicaciones comerciales. Legitimación: tu consentimiento. Destinatarios: no se ceden a terceros. Los datos se almacenan en los servidores de marketing (MailRelay). Derechos: podrás ejercer tus derechos de acceso, rectificación, limitación y supresión de datos en info @ codigopiton.com así como presentar una reclamación ante una autoridad de control. Más información en nuestra política de privacidad, encontrarás información adicional sobre la recopilación y el uso de tu información personal, incluida información sobre acceso, conservación, rectificación, eliminación, seguridad y otros temas.