Li

Listas en Python

96 ejercicios

Acerca de Listas

Una list es una colección mutable de elementos en secuencia. Como la mayoría de las colecciones (consulta los tipos incorporados tuple, dict y set), las listas pueden contener referencias a cualquier tipo de datos, o a varios, incluida la posibilidad de contener otras listas. Como en cualquier secuencia, se puede acceder a los elementos mediante un número de 0-based index desde la izquierda y de -1-based index desde la derecha. Las listas se pueden copiar total o parcialmente mediante la notación de rebanadas o <list>.copy().

Las listas admiten tanto operaciones de secuencia comunes como mutables, tales como min()/max(), <list>.index(), .append() y .reverse(). Se puede iterar sobre los elementos de una lista con la construcción for item in <list>. La forma for index, item in enumerate(<list>) se puede usar cuando se necesitan tanto el índice como el valor del elemento.

Las listas se implementan como array dinámico, similar al tipo Arraylist de Java, y se usan con mayor frecuencia para almacenar grupos de datos similares (strings, números, sets, etc.) de longitud desconocida (la cantidad de entradas puede ampliarse o reducirse de forma arbitraria).

Acceder a los elementos, comprobar la pertenencia con in o añadir elementos al lado «derecho» de una lista son operaciones muy eficientes. Anteponer (añadir al lado «izquierdo») o insertar en el medio de una lista son mucho menos eficientes, porque esas operaciones requieren desplazar elementos para mantenerlos en secuencia. Para una estructura de datos similar que admita appends/pops eficientes en memoria desde ambos lados, consulta collections.deque, que tiene aproximadamente el mismo rendimiento O(1) en cualquiera de las dos direcciones.

Como las listas son mutables y pueden contener referencias a objetos arbitrarios de Python, también ocupan más espacio en memoria que un array.array o una tuple (que es inmutable) de la misma longitud aparente. A pesar de esto, las listas son una estructura de datos extremadamente flexible y útil, y muchos métodos y operaciones incorporados de Python producen listas como resultado.

Construcción

Una list se puede declarar como un literal con corchetes [] y comas entre los elementos:

>>> no_elements = []

>>> no_elements
[]

>>> one_element = ["Guava"]

>>> one_element
['Guava']

>>> elements_separated_with_commas = ["Parrot", "Bird", 334782]

>>> elements_separated_with_commas
['Parrot', 'Bird', 334782]

Para facilitar la lectura, se pueden usar saltos de línea cuando hay muchos elementos o estructuras de datos anidadas dentro de una lista:

>>> lots_of_entries = [
...    "Rose",
...    "Sunflower",
...    "Poppy",
...    "Pansy",
...    "Tulip",
...    "Fuchsia",
...    "Cyclamen",
...    "Lavender"
... ]

>>> lots_of_entries
['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender']


# Each data structure is on its own line to help clarify what they are.
>>> nested_data_structures = [
...    {"fish": "gold", "monkey": "brown", "parrot": "grey"},
...    ("fish", "mammal", "bird"),
...    ['water', 'jungle', 'sky']
... ]

>>> nested_data_structures
[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']]

El constructor list() se puede usar vacío o con un iterable como argumento. El constructor recorre los elementos del iterable y los añade a la lista en orden:

>>> no_elements = list()
>>> no_elements
[]

# The tuple is unpacked and each element is added.
>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782))

>>> multiple_elements_from_tuple
['Parrot', 'Bird', 334782]

# The set is unpacked and each element is added.
>>> multiple_elements_from_set = list({2, 3, 5, 7, 11})

>>> multiple_elements_from_set
[2, 3, 5, 7, 11]

Los resultados de usar un constructor de listas con un string o un dict pueden ser sorprendentes:

# String elements (Unicode code points) are iterated through and added *individually*.
>>> multiple_elements_string = list("Timbuktu")

>>> multiple_elements_string
['T', 'i', 'm', 'b', 'u', 'k', 't', 'u']

# Unicode separators and positioning code points are also added *individually*.
>>> multiple_code_points_string = list('अभ्यास')

>>> multiple_code_points_string
['अ', 'भ', '्', 'य', 'ा', 'स']

# The iteration default for dictionaries is over the keys, so only key data is inserted into the list.
>>> source_data = {"fish": "gold", "monkey": "brown"}
>>> list(source_data)
['fish', 'monkey']

Como el constructor list() solo acepta iterables (o nada) como argumentos, los objetos que no son iterables generarán un TypeError. Por eso, es mucho más sencillo crear una lista de un solo elemento con el método literal.

# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails.
>>> one_element = list(16)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward
>>> one_element_from_iterable = list((16,))

>>> one_element_from_iterable
[16]

Acceso a los elementos

Se puede acceder a los elementos dentro de las listas (así como a los elementos de otros tipos de secuencia como str y tuple) mediante la notación de corchetes. Los índices pueden ir de left --> right (empezando en cero) o de right --> left (empezando en -1).

índice desde la izquierda ⟹






0
👇🏾
1
👇🏾
2
👇🏾
3
👇🏾
4
👇🏾
5
👇🏾
P y t h o n
👆🏾
-6
👆🏾
-5
👆🏾
-4
👆🏾
-3
👆🏾
-2
👆🏾
-1





⟸ índice desde la derecha
>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"]

# Oatmeal is at index 0 or index -4.
>>> breakfast_foods[0]
'Oatmeal'

>>> breakfast_foods[-4]
'Oatmeal'

# Eggs are at index -2 or 2
>>> breakfast_foods[-2]
'Eggs'

>>> breakfast_foods[2]
'Eggs'

# Toast is at -1
>>> breakfast_foods[-1]
'Toast'

Se puede acceder a una sección de una lista mediante la notación de rebanadas (<list>[<start>:<stop>]). Una rebanada se define como una secuencia de elementos en la posición index, tal que start <= index < stop. El rebanado devuelve una copia de los elementos «rebanados» y no modifica la list original.

También se puede usar un parámetro step en la rebanada (<list>[<start>:<stop>:<step>]) para «saltar» o filtrar los elementos devueltos (por ejemplo, un step de 2 seleccionará uno de cada dos elementos de la sección):

>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]

# If there is no step parameter, the step is assumed to be 1.
>>> middle_colors = colors[2:6]

>>> middle_colors
['Green', 'Yellow', 'Orange', 'Pink']

# If the start or stop parameters are omitted, the slice will
# start at index zero, and will stop at the end of the list.
>>> primary_colors = colors[::3]

>>> primary_colors
['Red', 'Yellow', 'Blue']

Trabajar con listas

Las listas proporcionan un iterador y se pueden recorrer de la misma manera que otros tipos de secuencia, usando for item in <list> o for index, item in enumerate(<list>):

# Make a list, and then loop through it to print out the elements
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for item in colors:
...     print(item)

Orange
Green
Grey
Blue


# Print the same list, but with the indexes of the colors included
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for index, item in enumerate(colors):
...     print(item, ":", index)

Orange : 0
Green : 1
Grey : 2
Blue : 3


# Start with a list of numbers and then loop through and print out their cubes.
>>> numbers_to_cube = [5, 13, 12, 16]
>>> for number in numbers_to_cube:
...     print(number**3)

125
2197
1728
4096

Una forma común de componer una lista de valores es usar <list>.append() dentro de un bucle:

>>> cubes_to_1000 = []
>>> for number in range(11):
...    cubes_to_1000.append(number**3)

>>> cubes_to_1000
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

Las listas también se pueden combinar mediante diversas técnicas:

# Using the plus + operator unpacks each list and creates a new list, but it is not efficient.
>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"]

>>> new_via_concatenate
['George', 5, 'cat', 'Tabby']

# Likewise, using the multiplication operator * is the equivalent of using + n times.
>>> first_group = ["cat", "dog", "elephant"]
>>> multiplied_group = first_group * 3

>>> multiplied_group
['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant']

# Another method for combining 2 lists is to use slice assignment or a loop-append.
# This assigns the second list to index 0 in the first list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]
>>> first_one[0:0] = second_one

>>> first_one
['George', 5, 'cat', 'Tabby']

# This loops through the first list and appends its items to the end of the second list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]

>>> for item in first_one:
...      second_one.append(item)

>>> second_one
['George', 5, 'cat', 'Tabby']

Algunas advertencias

Recuerda que las variables en Python son etiquetas que apuntan a objetos subyacentes. Las lists añaden una capa más como objetos contenedor: guardan referencias a los objetos de sus elementos reunidos. Esto puede dar lugar a varios problemas potenciales al trabajar con listas, si no se manejan correctamente.

Asignar más de un nombre de variable

Asignar un objeto list a un nuevo nombre de variable no copia el objeto list ni sus elementos. Cualquier cambio que se haga en los elementos de la list bajo el nombre nuevo afecta a la original.

Hacer una shallow_copy mediante list.copy() o una rebanada evitará esta complicación de referencias de primer nivel. Una shallow_copy creará un nuevo objeto list, pero no creará objetos nuevos para los elementos de la lista contenidos. Este tipo de copia suele ser suficiente para que puedas añadir o quitar elementos de los dos objetos list de forma independiente y, en la práctica, tengas dos listas «separadas».

>>> actual_names = ["Tony", "Natasha", "Thor", "Bruce"]

# Assigning a new variable name does not make a copy of the container or its data.
>>> same_list = actual_names

#  Altering the list via the new name is the same as altering the list via the old name.
>>> same_list.append("Clarke")
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

>>> actual_names
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

#  Likewise, altering the data in the list via the original name will also alter the data under the new name.
>>> actual_names[0] = "Wanda"
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']

# If you copy the list, there will be two separate list objects which can be changed independently.
>>> copied_list = actual_names.copy()
>>> copied_list[0] = "Tony"

>>> actual_names
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']

>>> copied_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

Esta complicación de referencias se agrava cuando se trabaja con listas anidadas o multiplicadas (los siguientes ejemplos son del excelente artículo de blog de 2013 de Ned Batchelder, Nombres y valores: cómo hacer un tablero de juego):

from pprint import pprint

# This will produce a game grid that is 8x8, pre-populated with zeros.
>>> game_grid = [[0]*8]*8

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0]]

# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"

# This attempt doesn't work because all the rows are referencing the same underlying list object.
>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

Pero en esta circunstancia, una shallow_copy basta para lograr el comportamiento que queremos:

from pprint import pprint

# This loop will safely produce a game grid that is 8x8, pre-populated with zeros
>>> game_grid = []
>>> filled_row = [0] * 8
>>> for row in range(8):
...    game_grid.append(filled_row.copy()) # This is making a new shallow copy of the inner list object each iteration.

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0]]

# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"

# The game grid now works the way we expect it to!
>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

Como se mencionó antes, las listas son contenedores de referencias, así que hay una segunda capa de posible complicación. Si una lista contiene variables, objetos o estructuras de datos anidadas, esas referencias de segundo nivel no se copiarán con shallow_copy ni con una rebanada. Modificar los objetos subyacentes afectará entonces a todas y cada una de las copias, ya que cada objeto list solo contiene referencias que apuntan a los elementos contenidos.

from pprint import pprint

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

# We'd like a new board, so we make a shallow copy.
>>> new_game_grid = game_grid.copy()

# But a shallow copy doesn't copy the contained references or objects.
>>> new_game_grid[0][0] = 'X'

# So changing the items in the copy also changes the originals items.
>>>  pprint(game_grid)
[['X', 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

Tipos de datos relacionados

Las listas se usan a menudo como pilas y colas, aunque su implementación subyacente hace que anteponer e insertar sean operaciones lentas. El módulo collections ofrece una variante deque optimizada para añadir y quitar rápidamente desde cualquiera de los dos extremos, implementada como una lista doblemente enlazada. Las listas anidadas también se usan para modelar pequeñas matrices, aunque las bibliotecas Numpy y Pandas son mucho más robustas para manipular matrices y datos tabulares de forma eficiente. El módulo collections también ofrece un tipo UserList que se puede personalizar para adaptarlo a necesidades específicas de listas.

Editar en GitHub El enlace se abre en una ventana o pestaña nueva

Aprende Listas

La práctica está bloqueada

Desbloquea 5 ejercicios más para practicar Listas