image

Introducción a Python

Python es uno de los lenguajes más utilizados hoy en día y es multiparadigma, lo que quiere decir que podemos programar con varios estilos distintos, como orientado a objetos, funcional, declarativo, etc. También se puede utilizar para una gran variedad de plataformas y le podemos dar muchos usos, como creación de páginas web, análisis de datos, inteligencia artificial, programas de escritorio, entre otros.

Este resumen de como programar en Python es en parte extraído del libro "Python Crash Course" de Eric Matthes. Si te interesa su versión original puedes comprarlo aquí.

Variables y tipos de datos simples

Variables

Las variables son como cajas que contienen información. Esta información podría ser cualquier cosa, como un número, un fragmento de texto o grandes datos. Esta información se denomina valor.

message = "Hola Mundo"

Cuando Python procesa la primera línea, asocia el "Hola Mundo" con la variable message. Las variables son como cajas: mantienes la información oculta y puedes abrir la caja para ver lo que hay dentro cuando quieras.

print(message)
Hola Mundo

Esta línea está llamando a esta "caja" para abrirla y luego en su interior se mostrará el texto "Hola mundo". Después de abrirlo, la función print mostrará el mensaje en la pantalla. Al igual que una caja, también puede recoger la información que contiene, sacarla y colocar nueva información en la caja.

message = "Hola Mundo"
print(message)
message = "Hola a todos"
print(message)
Hola Mundo
Hola a todos

La nueva salida será "Hola a todos" en lugar de "Hola mundo".

Hay algunas reglas estrictas que debe seguir si no deseas obtener errores en tu programa:

  • El nombre de las variables solo puede contener letras, números y guiones bajos. Pueden comenzar con una letra o subrayado pero no con un número.
  • Evite usar palabras clave de Python y nombres de funciones como nombres de variables. Puede ver todos los nombres reservados aquí: https://docs.python.org/2.5/ref/keywords.html
  • El nombre de la variable debe ser corto pero descriptivo.

Tipos de valores

Strings

Una cadena es simplemente una serie de caracteres. Cualquier cosa dentro de las comillas se considera como texto, aunque hayan números dentro. Puedes utilizar comillas simples o dobles.

"Esto es un string"
'Esto también es un string'

Esta flexibilidad permite hacer comillas y apóstrofes dentro de sus cadenas:

'Le dije a mi amigo: "¡Python es mi lenguaje favorito!"'
"El lenguaje 'Python' lleva el nombre de Monty Python, no de la serpiente"

Si usa el mismo apóstrofe para decirle a Python que este valor es una cadena y para encerrar un fragmento de texto, terminará con un error de sintaxis.

'My dad's house is big'

Este es un error de sintaxis porque Python ve que el string termina en la palabra "dad" y luego, de repente, continúa rompiendo la estructura del string.

Una de las tareas más simples que puedes hacer con los strings es cambiar de mayúsculas a minúsculas o viceversa.

nombre = "ada lovelace"
print(nombre.title())
Ada Lovelace

La palabra "title()" que viene despues de la variable nombre y del punto es un método. Un método es una acción que Python puede realizar en un dato. El punto (.) después del nombre en name.title() le dice a Python que haga que el método title() actúe sobre el nombre de la variable. Cada método va seguido de un conjunto de paréntesis, porque a menudo necesitan información adicional para hacer su trabajo. El método title no necesita información adicional, por lo que sus paréntesis están vacíos.

También tienes otros métodos como lower() y upper(). El método lower() (cada letra en minúscula) es útil para almacenar datos recibidos por el usuario para comparar información, por ejemplo.

Combinar o concatenar cadenas

A menudo es útil combinar strings, por ejemplo, para unir un nombre y un apellido almacenados en diferentes variables. Python usa el símbolo "+" para combinar strings

first_name = "ada"
last_name = "lovelace"
full_name = first_name + " " + last_name
print(full_name)
ada lovelace

Este método de combinar strings se llama concatenación.

Agregar espacio y tabulación

A veces en nuestro programa necesitaremos imprimir caracteres especiales dentro del texto, como tabulaciones, saltos de líneas u otros que modifiquen la apariencia de nuestro texto. Para poder ejecutar secuencias de escape necesitamos escribir primero un slash inverso (\). Por ejemplo, el texto "\t" es una secuencia de escape de tabulación, vale decir, nuestro texto se tabulará. Veamos un ejemplo:

print("Python")
print("\tPython")
Python
    Python

Vemos que el segundo Python está mucho más separado que el primero debido a la tabulación, a este caracter especial. Veamos ahora como crear nuevas lineas utilizando secuencias de escape.

print("Languages:\nPython\nC\nJavaScript")
Languages:
Python
C
JavaScript

Con una sola linea de código estamos imprimiendo varios lenguajes de programación en distintas lineas. Esto es posible gracias a la secuencia de escape "\n" utilizada para crear nuevas líneas de texto.

Estos caracteres especiales se llaman secuencias de escape y son muy utilizados para cambiar el aspecto de nuestro texto.

Eliminación de espacios en blanco

Los espacios en blanco adicionales pueden ser confusos en un programa. "hola" no es lo mismo que "hola ". Si comparas dos cadenas para probar si son iguales, su prueba fallará si tienes un espacio adicional en un string. Para eliminar el espacio adicional, es posible que desees utilizar el método rstrip().

favorite_language = "python     "
favorite_language.rstrip()
'python'

El método rstrip() actúa sobre la variable eliminando el espacio extra, pero temporalmente. Si vuelve a solicitar el valor de favorite_language, puede ver que la cadena se ve igual que cuando se ingresó, incluidos los espacios en blanco.

favorite_language
'python     '

Para eliminar el espacio en blanco de la cadena de forma permanente, debe almacenar el valor en una variable. Si la variable tiene el mismo nombre que la anterior, "pisaremos' su valor anterior, como cuando reemplazamos un archivo por otro en una carpeta.

favorite_language = " python"
favorite_language = favorite_language.rstrip()
favorite_language
' python'

También puede eliminar los espacios en blanco adicionales en el lado izquierdo usando el método lstrip(). Puedes usar ambos juntándolos así:

favorite_language = "          python            "
favorite_language = favorite_language.rstrip().lstrip()
favorite_language
'python'

Esto es lo mismo que utilizar el método strip() que elimina todos los espacios extremos de un string.

favorite_language = "          python            "
favorite_language = favorite_language.strip()
favorite_language
'python'
Números

Los números son muy útiles en la programación para mantener la puntuación en los juegos, representar la visualización de datos, almacenar información en aplicaciones web, etc.

Enteros

Puedes sumar (+), restar (-), multiplicar (*) y dividir (/) enteros en Python. A este tipo de dato se le conoce como int que viene de integer (entero en inglés).

print(2 + 3)
print(3 - 2)
print(2 * 3)
print(3 / 2)
5
1
6
1.5

Python también usa dos símbolos de multiplicación para representar exponentes: 3 ** 2 = 9 o 3 ** 3 = 27.

Python también admite el orden de las operaciones, por lo que puede usar varias operaciones en una expresión. También puede usar paréntesis para modificar el orden de las operaciones para que Python pueda evaluar su expresión en el orden que especifique:

print(2 + 3 * 4)
print((2 + 3) * 4)
14
20
Flotantes

Python llama flotante a cualquier número con un punto decimal. Se refiere al hecho de que un punto decimal puede aparecer en cualquier posición de un número. Sin embargo, ten en cuenta que a veces puedo obtener resultados que no esperabas:

0.8 + 0.9
1.7000000000000002

Esto sucede en todos los lenguajes, pero hay formas de lidiar con los números adicionales, como ese 2. La razón por la que ocurre tiene que ver en como trata el computador los números flotantes y es a un nivel de hardware. No detallaremos ese asunto aquí por ser introductorio, pero ya veremos como tratar con estos resultados. Sin embargo, si quieres información detallada al respecto, puedes ver aqui las razones

Evitar errores de tipo con la función str()

A menudo, querrá usar el valor de una variable dentro de un mensaje como uno de feliz cumpleaños.

edad = 23
mensaje = "Feliz cumpleaños N° " + edad
print(mensaje)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[16], line 2
        1 edad = 23
----> 2 mensaje = "Feliz cumpleaños N° " + edad
        3 print(mensaje)

TypeError: can only concatenate str (not "int") to str

Si ejecuta este código, verás que se genera un error de tipo o Type Error. Esto significa que Python no puede reconocer el tipo de información que estás usando Cuando usas números enteros dentro de strings, debes especificar explícitamente que se utilice el número entero como una cadena de caracteres porque el valor 23 puede ser expresado como el número 23 o como los caracteres 2 y 3.

Podemos resolver este error envolviendo la variable en la función str() para informar a Python que queremos representar esa información como un string.

edad = 23
mensaje = "Feliz cumpleaños N° " + str(edad)
print(mensaje)
Feliz cumpleaños N° 23

Comentarios

Un comentario nos permite escribir notas. Es muy útil porque se puede usar para describir el problema que se está tratando de resolver y explicar qué hace una parte del código. Puedes realizar un seguimiento del programa cuando se vuelve más complicado usando notas.

# Di hola a todos
print("Hola a todos!")
Hola a todos!

Python ignora la primera línea y ejecuta la segunda línea.

El Zen de Python

La filosofía de la comunidad de Python está contenida en "The Zen of Python" de Tim Peters. Puede acceder a este breve conjunto de principios para escribir un buen código de Python ingresando "import this" en tu intérprete.

import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Una de las filosofías que aparecen allí son:

  • Hermoso es mejor que feo: Los programadores siempre han respetado las soluciones bien diseñadas, eficientes e incluso hermosas a los problemas.
  • Lo simple es mejor que lo complejo: si puede elegir entre una solución simple y una compleja, y ambas funcionan, use la solución simple. Su código será más fácil de mantener y será más fácil para ti y para otros desarrollar ese código más adelante.
  • Lo complejo es mejor que lo complicado: la vida real es desordenada y, a veces, una solución simple a un problema es inalcanzable. En ese caso, use la solución más simple que funcione.
  • La legibilidad cuenta: concéntrese en escribir comentarios y elija un buen nombre de variable para que sea legible.

Introduciendo las listas

Las listas son un tipo de valor que recoge datos de cualquier tipo, como números y strings. Es una colección de elementos en un orden determinado. Puedes poner lo que quieras en una lista, y los elementos de tu lista no tienen por qué estar relacionados de ninguna manera en particular. Dado que una lista suele contener más de un elemento, es una buena idea hacer que el nombre de tu lista sea plural, como letras, dígitos o nombres.

En Python, los corchetes ([]) indican una lista, y los elementos individuales de la lista están separados por comas.

bicicletas = ['trek', 'cannondale', 'redline', 'specialized']
print(bicicletas)
['trek', 'cannondale', 'redline', 'specialized']

Si lo imprimes se incluirán los corchetes por lo que es mejor tener acceso individual a los elementos de una lista.

Acceso a los elementos de una lista

Puedes acceder a cualquier elemento de una lista indicando a Python la posición, o índice, del elemento deseado. Para acceder a un elemento de una lista, escriba el nombre de la lista seguido del índice del elemento entre corchetes.

print(bicicletas[0])
trek

Como puedes ver, Python considera que el primer elemento de una lista está en la posición 0, no en la posición 1. Esto tiene que ver en cómo se implementan las operaciones de lista en un nivel inferior. Por lo tanto, el segundo elemento es el 1 y así sucesivamente. Usando este sencillo sistema de conteo, puedes obtener cualquier elemento que quieras de una lista restando uno de su posición en la lista.

Python tiene una sintaxis especial para acceder al último elemento de una lista. Preguntando por el elemento en el índice -1, Python siempre devuelve el último elemento de la lista.

print(bicicletas[-1])
specialized

Esta convención se extiende también a otros valores de índice negativos. El índice -2 devuelve el segundo elemento desde el final de la lista, el índice -3 devuelve el tercer elemento desde el final, y así sucesivamente. Puede utilizar valores individuales de una lista como lo haría con cualquier otra variable. Por ejemplo, puede utilizar la concatenación para crear un mensaje basado en un valor de una lista.

print('Mi primera bicicleta fue una ' + bicicletas[0].title() + '.')
Mi primera bicicleta fue una Trek.

Modificar, añadir y eliminar elementos

La mayoría de las listas que generes serán dinámicas, lo que significa que construirás una lista y podrás añadir y eliminar elementos de ella a medida que tu programa siga su curso.

Modificar elementos de una lista

Para modificar un elemento se hace lo mismo que cuando se llama a un elemento de una lista. La única diferencia es que pones otro valor igual que si cambiaras el valor de una variable.

motos = ['honda', 'yamaha', 'susuki']
motos[0] = 'ducati'
print(motos)
['ducati', 'yamaha', 'susuki']

Añadir elementos a una lista

Python proporciona diferentes formas de añadir nuevos datos a las listas existentes.

Añadir elementos al final de una lista

La forma más sencilla de añadir un nuevo elemento a una lista es con el método append(). Cuando utilizas este método el nuevo elemento se añade al final de la lista.

motos = ['honda', 'yamaha', 'susuki']
motorcycles.append('ducati')
print(motos)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[25], line 2
        1 motos = ['honda', 'yamaha', 'susuki']
----> 2 motorcycles.append('ducati')
        3 print(motos)

NameError: name 'motorcycles' is not defined

El método append() añade 'ducati' al final de la lista sin afectar a ninguno de los otros elementos de la lista. Construir listas de esta manera es muy común, porque a menudo no sabrás los datos que tus usuarios quieren almacenar en un programa hasta después de que el programa se esté ejecutando. Para que tus usuarios tengan el control, empieza por definir una lista vacía ([]) que contendrá los valores de los usuarios. A continuación, añada cada nuevo valor proporcionado por los usuarios a la lista que acabas de crear.

Insertar elementos en una lista

Puedes añadir un nuevo elemento en cualquier posición de tu lista utilizando el método insert(). Esto se hace especificando el índice del nuevo elemento y el valor del nuevo elemento.

motos = ['honda', 'yamaha', 'susuki']
motos .insert(0, 'ducati')
print(motos)
['ducati', 'honda', 'yamaha', 'susuki']

El método insert() abre un espacio en la posición 0 y almacena el valor 'ducati' en ese lugar. Esta operación desplaza todos los demás valores de la lista una posición hacia la derecha.

Eliminar elementos de una lista

Puede eliminar un elemento de una lista por muchas razones. Por ejemplo, cuando un usuario elimina su cuenta o cuando destruye uno de los castillos contrarios en un juego. Puedes eliminar un elemento según su posición en la lista o según su valor.

Eliminar por posición

Declaración del

Podemos utilizar la palabra clave del para eliminar un elemento por su ubicación o índice

motos = ['honda', 'yamaha', 'susuki']
del motos[0]
print(motos)
['yamaha', 'susuki']

Método pop()

A veces querrás utilizar un valor después de que lo hayas eliminado de una lista. Por ejemplo, en una aplicación web, es posible que desees eliminar un usuario de una lista de miembros activos y, a continuación, añadir ese usuario a una lista de miembros inactivos.

El método pop() elimina el último elemento de una lista, pero te permite trabajar con ese elemento después de eliminarlo.

motos = ['honda', 'yamaha', 'susuki']
moto_dañada = motos.pop()
print(motos)
print(moto_dañada)
['honda', 'yamaha']
susuki

También puedes hacer pop() item para eliminar un elemento de una lista en cualquier posición incluyendo el elemento en el parámetro. Por ejemplo en un juego de colgado cuando el usuario no eligió la letra correcta puede querer mostrar la letra primero y removerla.

motos = ['honda', 'yamaha', 'susuki']
moto_dañada = motos.pop(1)
print(moto_dañada)
yamaha
Eliminar por valor

Método remove()

A veces no sabrás la posición de un valor que quieres eliminar de una lista. Si sólo conoces el valor del elemento que quieres eliminar, puedes utilizar el método remove().

motos = ['honda', 'yamaha', 'susuki', 'ducati']
motos.remove('ducati')
print(motos)
['honda', 'yamaha', 'susuki']

También puedes utilizar el método remove() para trabajar con un valor que se elimina de una lista guardándolo en una variable

motos = ['honda', 'yamaha', 'susuki', 'ducati']
demasiado_caro = 'ducati'
motos.remove(demasiado_caro)
print("Un " + demasiado_caro.title() + " es demasiado cara para mí.")
Un Ducati es demasiado cara para mí.

Organizar una lista

A menudo, tus listas se crearán en un orden impredecible, porque no siempre puedes controlar el orden en que tus usuarios proporcionan sus datos. Por eso es posible que quieras presentar la información en un orden determinado. A veces querrás preservar el orden original de tu lista, y otras veces querrás cambiar el orden original. Python proporciona varias formas diferentes de organizar tus listas, dependiendo de la situación.

Ordenar una lista permanentemente con el método sort()

El método sort() ordena tu lista en un orden creciente (a --> z, 0 --> oo) y el cambio es permanente lo que significa que pierdes el orden original de la lista.

autos = ['bmw', 'audi', 'toyota', 'subaru']
autos.sort()
print(autos)
['audi', 'bmw', 'subaru', 'toyota']

También puedes ordenar esta lista en orden inverso al alfabético pasando el argumento reverse=True al método sort().

autos.sort(reverse=True)
print(autos)
['toyota', 'subaru', 'bmw', 'audi']

Ordenar una lista temporalmente con la función sorted()

Para mantener el orden original de una lista pero presentarla ordenada, podrias utilizar la función sorted(). La función sorted() permite mostrar una lista en un orden determinado, pero no afecta al orden real de la lista.

autos = ['bmw', 'audi', 'toyota', 'subaru']
print(sorted(autos))
print(autos)
['audi', 'bmw', 'subaru', 'toyota']
['bmw', 'audi', 'toyota', 'subaru']

La función sorted() también puede aceptar un argumento reverse=True si quieres mostrar una lista en orden alfabético inverso. Hay que tener cuidado con las minúsculas y las mayúsculas aunque sean la misma letra, porque las minúsculas son "más altas" que las mayúsculas.

Imprimir una lista en orden inverso.

Para invertir el orden original de una lista, puedes utilizar el método reverse().

autos = ['bmw', 'audi', 'toyota', 'subaru']
autos.reverse()
print(autos)
['subaru', 'toyota', 'audi', 'bmw']

El método reverse() cambia el orden de una lista permanentemente, pero puedes volver al orden original en cualquier momento aplicando reverse() a la misma lista por segunda vez.

Determinar la longitud de una lista

Puede averiguar rápidamente la longitud de una lista utilizando la función len(). La lista de este ejemplo tiene cuatro elementos, por lo que su longitud es 4.

autos = ['bmw', 'audi', 'toyota', 'subaru']
len(autos)
4

Trabajando con listas

Bucle a través de una lista completa

A menudo querrás revisar todas las entradas de una lista, realizando la misma tarea con cada elemento. Cuando quieras realizar la misma acción con todos los elementos de una lista, puede utilizar el bucle for de Python. Si no tuviéramos un bucle para cada elemento de una lista, sería repetitivo con una larga lista de nombres. Además, tendríamos que cambiar nuestro código cada vez que la longitud de la lista se modifique.

magos = ['alicia', 'david', 'carolina']
for mago in magos:
    print(mago)
alicia
david
carolina

Este código dice: "Para cada mago en la lista de magos, escribe el nombre del mago". La segunda línea le dice a Python que extraiga un nombre de la lista de magos y lo almacene en la variable mago. El concepto de bucle es importante porque es una de las formas más comunes en que un computador automatiza tareas repetitivas.

Tenga en cuenta que el bucle for tiene una variable temporal al momento de realizar el bucle, como en el ejemplo anterior, donde la variable temporal es "mago". Se recomienda utilizar nombres significativos para estas variables y de esta forma, nuestro programa se podrá entender mucho mejor.

Hacer más trabajo dentro de un bucle for

Puede hacer casi cualquier cosa con cada elemento en un bucle for, como agregar texto o hacer operaciones matemáticas. Por ejemplo:

magos = ['alicia', 'david', 'carolina']
for mago in magos:
    print(mago.title() + ", ¡fue un gran truco!")
Alicia, ¡fue un gran truco!
David, ¡fue un gran truco!
Carolina, ¡fue un gran truco!

También puede escribir tantas líneas de código como quieras en el bucle for. Cada línea que está indentada después del bucle for es considerada como dentro del bucle y este bucle es ejecutado linea a linea hasta que el bucle finalice, por lo que podrias hacer lo que quieras con cada valor de la lista.

Hacer algo después de un bucle for

¿Qué sucede una vez que un ciclo for ha terminado de ejecutarse? Por lo general, querrás pasar a otro trabajo que su programa deba realizar.

magos = ['alicia', 'david', 'carolina']
for mago in magos:
    print(mago.title() + ", ¡fue un gran truco!")
    print("No puedo esperar a ver tu próximo truco, " + mago.title() + "\n")
print("Gracias a todos. ¡Fue un gran espectáculo de magia!")
Alicia, ¡fue un gran truco!
No puedo esperar a ver tu próximo truco, Alicia

David, ¡fue un gran truco!
No puedo esperar a ver tu próximo truco, David

Carolina, ¡fue un gran truco!
No puedo esperar a ver tu próximo truco, Carolina

Gracias a todos. ¡Fue un gran espectáculo de magia!

Como puedes ver, cualquier línea de código después del bucle for que no esté identada se ejecuta una vez sin repetición. Cuando estés procesando datos dentro de un bucle for, encontraras que esta es una buena manera de resumir una operación que se realizó en un conjunto de datos completo. Por ejemplo, puede usar un bucle for para iniciar un juego ejecutando una lista de caracteres y mostrando cada carácter en la pantalla.

Python usa sangría para determinar cuándo una línea de código está conectada a la línea superior, por lo que es muy importante usarla solo cuando una parte del código pertenece a una división.

Hacer listas numéricas

Las listas son ideales para almacenar conjuntos de números y Python proporciona una serie de herramientas que nos ayudan a trabajar de manera eficiente con listas de números. Se puede usar, por ejemplo, para realizar un seguimiento de la posición de cada personaje en un juego o usar la visualización de datos como temperaturas, distancias, tamaños de población, etc.

Usando la función range()

La función range() de Python facilita la generación de una serie de números.

for valor in range(1, 5):
    print(valor)
1
2
3
4

En este ejemplo, range() imprime solo los números del 1 al 4. Este es otro resultado del comportamiento off-by-one que veras a menudo en los lenguajes de programación. La función range() hace que Python comience a contar en el primer valor que le proporciones y se detenga cuando alcance el segundo valor. Debido a que se detiene en ese segundo valor, la salida nunca contiene el valor final, que es 5 en este caso.

Usando range() para hacer una lista de números

Si quieres hacer una lista de números, puedes convertir los resultados de range() directamente en una lista usando la función list(). La función list () convierte cualquier valor iterable en un valor de lista.

numeros = list(range(1, 6))
print(numeros)
[1, 2, 3, 4, 5]

También podemos usar la función range() para decirle a Python que omita números en un rango dado. Por ejemplo, así es como enumeraríamos los números pares del 1 al 10:

numeros_pares = list(range(2, 11, 2))
print(numeros_pares)
<[2, 4, 6, 8, 10]

En el ejemplo anterior, se añade un tercer valor dentro de los paréntesis que dice cómo tenemos que contar los números. En este caso de 2 en 2. También puedes hacer cálculos extra a tus elementos en cada bucle, por ejemplo:

cuadrados = []
for valor in range(1, 11):
    cuadrado = valor ** 2
    cuadrados.append(cuadrado)
print(cuadrados)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Lo que hacemos en el ejemplo anterior es crear una lista vacía y luego, en cada bucle, elevamos cada elemento a la segunda potencia (**2) y lo almacenamos en la variable cuadrado. Cada nuevo valor de cuadrado se añade a la lista cuadrados y finalmente se imprime.

Estadística simple con una lista de números

Algunas funciones de Python son específicas para listas de números. Por ejemplo, puedes encontrar fácilmente el mínimo, el máximo y la suma de una lista de números.

digitos = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
print('El valor minimo de la lista es ', min(digitos))
print('El valor máximo de la lista es ', max(digitos))
print('La suma de todos los números de la lista es ', sum(digitos))
El valor minimo de la lista es  0
El valor máximo de la lista es  9
La suma de todos los números de la lista es  45
Comprensión de listas

La aproximación descrita anteriormente para generar la lista cuadrados consistía en utilizar tres o cuatro líneas de código. Una comprensión de lista permite generar esa misma lista en una sola línea de código. Una comprensión de lista combina el bucle for y la creación de nuevos elementos en una sola línea, añadiendo automáticamente cada nuevo elemento.

El siguiente ejemplo construye la misma lista de números cuadrados que vimos antes, pero utiliza una comprensión de lista:

cuadrados = [valor**2 for valor in range(1,11)]
print(cuadrados)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Los valores finales que deseamos almacenar está dentro de los corchetes. En este ejemplo la expresión valor**2 es el procesamiento de los valores de entrada que están en range(1, 11). No utilizamos dos puntos, porque está todo dentro de una misma expresión.

También podemos añadir una expresión if dentro de la comprensión de la lista:

pares = [valor for valor in range(1, 101) if valor % 2 == 0]
print(pares)
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100]
Trabajar con partes de una lista

Al igual como podemos acceder a valores individuales de una lista, también podemos acceder a un grupo específico de elementos en una lista. Eso se llama "slice".

Para hacer un slice, se especifica el índice del primer y último elemento con el que se quiere trabajar. Al igual que con la función range(), Python se detiene un elemento antes del segundo índice que especifiques.

jugadores = ['charles', 'martina', 'michael', 'florence', 'eli']
print(jugadores[0:3])
['charles', 'martina', 'michael']

La sección [0:3] es la porción de elementos que queremos seleccionar, en este caso, entre el índice cuya posición es cero y el índice cuya posición es 3. Puedes generar cualquier subconjunto de una lista, por ejemplo seleccionar sólo el segundo y tercer elemento de esta forma:

jugadores[1:2]
['martina']

Si omites el primer índice en un slice, Python comienza a procesar los elementos que están al principio de la lista:

print(jugadores[:4])
['charles', 'martina', 'michael', 'florence']

Una sintaxis similar funciona si quieres un slice que incluya el final de la lista. Por ejemplo, si quieres todos los elementos desde el tercero hasta el último, puedes empezar por el índice 2 y omitir el segundo índice:

print(jugadores[2:])
['michael', 'florence', 'eli']

Esto funciona también con números negativos. Por ejemplo, si queremos obtener los tres últimos elementos de una lista, podemos utilizar lo siguiente:

print(jugadores[-3:])
['michael', 'florence', 'eli']
Recorrer un slice dentro de un bucle

Puedes utilizar un slice en un bucle for si quieres recorrer un subconjunto de elementos de una lista

jugadores = ['charles', 'martina', 'michael', 'florence', 'eli']
print("Aquí están los tres primeros jugadores de mi equipo:")
for jugador in jugadores[:3]:
    print(jugador.title())
Aquí están los tres primeros jugadores de mi equipo:
Charles
Martina
Michael

En lugar de recorrer toda la lista de jugadores, Python recorre sólo los tres primeros nombres, que son Charles, Martina y Michael.

Sentencia If

La programación a menudo implica examinar un conjunto de condiciones y decidir qué acción tomar en función de esas condiciones. La declaración if de Python nos permite examinar el estado actual de un programa y responder apropiadamente a ese estado.

autos = ['audi', 'bmw', 'subaru', 'toyota']
for auto in autos:
    if auto == 'bmw':
        print(auto.upper())
    else:
        print(auto.title())
Audi
BMW
Subaru
Toyota

Este código imprime solo 'bmw' en letras mayúsculas. El resto de ellos están capitalizadas.

En cada declaración if hay una expresión que se puede evaluar como verdadera o falsa. A esto se le llama prueba condicional. Si una prueba condicional se evalúa como verdadera, Python ejecuta el código que sigue a la instrucción if. Si la prueba se evalúa como falsa, Python ignora el código que sigue a la instrucción if. La prueba condicional puede ser verdadera o falsa cuando comparas dos valores.

auto = "bmw"
auto == "bmw"
True

Este operador de igualdad (==) devuelve True si los valores del lado izquierdo y derecho del operador coinciden y False si no coinciden. Cuando el valor del automóvil es diferente a 'bmw', esta prueba devuelve False.

auto = "Audi"
auto == "audi"
False

De la misma forma podemos comparar dos valores para comprobar si son diferentes con el operador de desigualdad expresado como !=

auto = "Ferrari"
auto != "Lamborghini"
True

También podemos incluir varias comparaciones matemáticas en sus declaraciones condicionales, como menor que, menor que o igual a, mayor que y mayor que o igual a.

edad = 19
print(edad < 21)
print(edad <= 21)
print(edad > 21)
print(edad >= 21)
True
True
False
False

Comprobar de varias condiciones

Para verificar varias condiciones al mismo tiempo, deberemos crear dos o más pruebas condicionales combinadas con los operadores "and", "or" o ambas. Por ejemplo para comprobar si dos personas son adultas o no, escribimos lo siguiente:

edad_0 = 21
edad_1 = 18
edad_0 >= 18 and edad_1 >= 18
True

A veces es importante comprobar si una lista contiene un determinado valor antes de realizar una acción. Para saber si un valor en particular ya está en una lista, utilizamos la palabra clave in.

usuarios = ['machine_1995', 'super_soaker_3000', 'bellakítá']
'super_soaker_3000' in usuarios
True

Otras veces, es importante saber si un valor no aparece en una lista. Para eso podemos utilizar la palabra clave not in.

'coti123' not in usuarios
True

Expresión booleana

Como hemos notado, los valores True y False siempre se usan para verificar si algo es verdadero o falso. Este tipo de valor se llama booleano o expresión booleana y así como los enteros y las cadenas de texto, los booleanos también son un tipo de dato.

type(True)
bool

La función type() permite saber qué tipo de dato es el valor que estamos trabajando.

Los valores booleanos brindan una forma eficiente de rastrear el estado de un programa o una condición particular, por ejemplo se puede utilizar en un juego para saber si es tu turno o si cierto usuario puede editar una página web.

Declaraciones condicionales

Declaraciones if

Cuando comprendas las pruebas condicionales, puedes comenzar a escribir sentencias if. Existen varios tipos diferentes de declaraciones if, y su elección de cuál usar depende de la cantidad de condiciones que necesitas probar. Por ejemplo, para verificar si alguien tiene la edad suficiente para votar, puedes usar este código:

if edad >= 18:
    print("¡Tienes la edad suficiente para votar!")
¡Tienes la edad suficiente para votar!

Si la variable de edad es 18 años o más, se ejecutará la función de impresión. De lo contrario, no pasa nada. La sangría juega el mismo papel en las sentencias if que en los bucles for. Todas las líneas con sangría después de una declaración if se ejecutarán si la prueba pasa, y todo el bloque de líneas con sangría se ignorarán si la prueba no pasa.

Declaraciones if-else

A menudo, querrás realizar una acción cuando pase una prueba condicional y una acción diferente en todos los demás casos. (dos posibilidades). Un bloque if-else es similar a una declaración if simple, pero la declaración else le permite definir una acción o un conjunto de acciones que se ejecutan cuando falla la prueba condicional.

if edad >= 18:
    print("¡Tienes la edad suficiente para votar!")
else:
    print("Vete a casa chico")
¡Tienes la edad suficiente para votar!

La cadena if-elif-else

Si tiene más de dos posibilidades, deberás usar la cadena if-elif-else. Esta estructura ejecuta cada prueba condicional en orden hasta que una se cumpla. Cuando pasa una prueba, se ejecuta el código que sigue a esa prueba y Python omite el resto de las pruebas.

if edad < 4:
    precio = 0
elif edad < 18:
    precio = 5
else:
    precio = 10
print("Su costo de entrada es $" + str(precio) + ".")
Su costo de entrada es $10.

Puedes usar tantos bloques elif en tu código como quieras dependiendo de las posibilidades que tengas. Python no requiere un bloque else al final de una cadena if-elif. A veces, un bloque else es útil solo si tenemos cubiertas todas las posibilidades. De lo contrario, una declaración elif adicional nos será de utilidad.

if edad < 4:
    precio = 0
elif edad < 18:
    precio = 5
elif edad < 65:
    precio = 10
elif edad >= 65:
    precio = 5

El bloque else es una declaración general. Coincide con cualquier condición que no haya coincidido con una prueba if o elif específica, y que a veces puede incluir datos no válidos o incluso maliciosos.

Prueba de múltiples condiciones

A veces, puedes tener dos o más posibilidades que sean verdaderas, pero con la cadena if-elif-else solo se ejecutará una declaración verdadera. Si queremos que más de una cadena de texto se ejecute si una sentencia es verdadera, debemos usar una serie de declaraciones if simples sin bloques elif o else. Esta técnica tiene sentido cuando más de una condición puede ser verdadera.

ingredientes_requeridos = ['champiñones', 'extra queso']
if 'champiñones' in ingredientes_requeridos:
    print("Añadir champiñones.")
if 'pepperoni' in ingredientes_requeridos:
    print("Añadir pepperoni.")
if 'extra queso' in ingredientes_requeridos:
    print("Añadir extra queso.")
print("\nTerminé de hacer tu pizza!")
Añadir champiñones.
Añadir extra queso.

Terminé de hacer tu pizza!

Podemos añadir a la pizza más de un ingrediente dependiendo de si están en la lista _ingredientesrequeridos. Este código no funcionaría correctamente si usáramos un bloque if-elif-else, porque el código dejaría de ejecutarse después de que solo pasara una prueba.

Comprobar que una lista no está vacía

Es útil comprobar si una lista está vacía antes de ejecutar un bucle for. Puedes hacer un trabajo interesante cuando combinas listas y sentencias if, pero nada tendrá sentido si nuestra lista está vacía.

ingredientes_pizza = []
if ingredientes_pizza:
    for ingrediente in ingredientes_requeridos:
        print("Añadir " + ingrediente + ".")
    print("\nFinished making your pizza!")
else:
    print("¿Estás seguro que deseas una pizza de masa?")
¿Estás seguro que deseas una pizza de masa?

En lugar de saltar directamente a un bucle for, hacemos una verificación rápida. Cuando el nombre de una lista se usa en una declaración if, Python devuelve True si la lista contiene al menos un elemento; una lista vacía se evalúa como False. Si la lista no está vacía, la salida mostrará cada ingrediente solicitado que se agregará a la pizza.

Diccionarios

Los diccionarios permiten crear piezas de información relacionada. Comprender los diccionarios te permitirá modelar una variedad de objetos del mundo real con mayor precisión.

alien_0 = {'color': 'green', 'points': 5}
print(alien_0['color'])
print(alien_0['points'])
>green
5

El diccionario alien_0 almacena el color y los puntos del alienígena. Las dos declaraciones print() acceden y muestran esa información. (green y 5).

Un diccionario en Python es una colección de pares clave-valor. Cada clave está conectada a un valor y puedes usar una clave para acceder al valor asociado a esa clave. El valor de una clave puede ser un número, una cadena, una lista o incluso otro diccionario.

Un par clave-valor es un conjunto de valores asociados entre sí. Cuando proporcionas una clave, Python devuelve el valor asociado con esa clave.

diccionario = {clave: valor}

Acceder y agregar valores en el diccionario

Para obtener el valor asociado de una clave, primero escribimos el nombre del diccionario y luego la clave dentro de un conjunto de corchetes como el primer ejemplo de alien_0.

Para agregar nuevos pares clave-valor en un diccionario seguimos la misma estructura que las listas, pero en lugar de un índice viene una clave:

alien_0['x_position'] = 0
alien_0['y_position'] = 25
print(alien_0)
{'color': 'green', 'points': 5, 'x_position': 0, 'y_position': 25}

Si esta utilizando una versión anterior a la 3.7 de Python, podrás ver que el orden de los pares clave-valor no coincide con el orden en que los agregamos. Python no se preocupa por el orden en que almacena cada par clave-valor; solo se preocupa por la conexión entre cada clave y su valor. Sin embargo, después de la versión 3.7 en Python, el orden fue implementado en los diccionarios, aunque lo esencial sigue siendo la relación entre clave-valor.

A veces es conveniente, o incluso necesario, comenzar con un diccionario vacío ({}) y luego agregarle cada elemento nuevo.

Modificación de valores en un diccionario

La modificación de un diccionario sigue la misma estructura que las listas pero en lugar de un índice se utiliza una clave:

alien_0 = {'color': 'verde'}
print("El alien es " + alien_0['color'] + ".")
alien_0['color'] = 'amarillo'
print("El alien ahora es " + alien_0['color'] + ".")
El alien es verde.
El alien ahora es amarillo.

Primero definimos un diccionario para alien_0 que contiene solo el color del alienígena; luego cambiamos el valor asociado a la clave 'color' a 'amarillo'.

Eliminación de pares clave-valor

Cuando ya no necesitemos una parte de la información almacenada en un diccionario, podemos usar la instrucción del para eliminar por completo un par clave-valor.

alien_0 = {'color': 'verde', 'puntos': 5}
del alien_0['puntos']
print(alien_0)
{'color': 'verde'}

Tenga en cuenta que el par clave-valor eliminado se elimina de forma permanente.

También podemos eliminar el elemento usando el método pop() guardando así el último elemento eliminado del diccionario.

Bucle a través de un diccionario

Los diccionarios se pueden utilizar para almacenar información de diversas formas; por lo tanto, existen varias formas diferentes de recorrerlos. Podemos recorrer todos los pares clave-valor de un diccionario, sus claves o sus valores.

Bucle a través de todos los pares clave-valor

Para ver toda la información almacenada en un diccionario, es posible que queramos utilizar un bucle for:

user_0 = {'username': 'eulelio_risopatron', 'first': 'eulelio', 'last': 'risopatron',}
for key, value in user_0.items():
    print("\nKey: " + key)
    print("Value: " + value)
Key: username
Value: eulelio_risopatron

Key: first
Value: eulelio

Key: last
Value: risopatron

Este código funcionaría de la misma forma a que si hubiésemos usado abreviaturas para los nombres de las variables, como esta:

for k, v in user_0.items():
    print("\nKey: " + k)
    print("Value: " + v)
Key: username
Value: eulelio_risopatron

Key: first
Value: eulelio

Key: last
Value: risopatron

El método items() devuelve una lista de pares clave-valor. El bucle for luego almacena cada uno de estos pares en las dos variables proporcionadas (clave y valor). Intente usar nombres descriptivos para las variables clave y de valor, como nombres y edades, por ejemplo.

Recorriendo todas las claves de un diccionario

El método keys() es útil cuando no necesitamos trabajar con los valores en un diccionario.

favorite_languages = {'mateo': 'python', 'nicolas': 'c', 'felipe': 'ruby', 'gustavo': 'python'}
for name in favorite_languages.keys():
    print(name.title())
Mateo
Nicolas
Felipe
Gustavo

Recorrer las claves es en realidad el comportamiento predeterminado cuando se recorre un diccionario, por lo que este código tendría exactamente el mismo resultado si escribieras lo siguiente:

for name in favorite_languages:
    print(name.title())
Mateo
Nicolas
Felipe
Gustavo

Puede optar por usar el método keys() explícitamente si hace que tu código sea más fácil de leer, o puede omitirlo si lo deseas. También puedes usar el método keys() para averiguar si se encuestó a una persona en particular:

if 'lucas' not in favorite_languages.keys():
    print("Lucas no ha sido encuestado!")
Lucas no ha sido encuestado!

El método keys() no es solo para bucles: en realidad devuelve una lista de todas las claves.

Recorriendo las claves de un diccionario en orden

Si estamos utilizando una versión de Python anterior a 3.7 y queremos recorrer los elementos en orden, podemos ordenar las claves a medida que se devuelven en el bucle for. Para ello, podemos usar la función sorted() para obtener una copia de las claves en orden.

for name in sorted(favorite_languages.keys()):
    print(name.title() + ", gracias por hacer esta encuesta!.")
Felipe, gracias por hacer esta encuesta!.
Gustavo, gracias por hacer esta encuesta!.
Mateo, gracias por hacer esta encuesta!.
Nicolas, gracias por hacer esta encuesta!.

Esta declaración for es como otras declaraciones for excepto que hemos envuelto la función sorted() alrededor del método dictionary.keys().

Recorriendo todos los valores en un diccionario

Podemos usar el método values() para devolver una lista de valores sin ninguna clave.

for language in favorite_languages.values():
    print(language.title())
Python
C
Ruby
Python

La declaración for extrae cada valor del diccionario y lo almacena en variable language.

Para diccionarios más grandes donde la repetición es más probable, podemos usar la función incorporada llamada set(). Este tipo de dato es similar a una lista, excepto que cada elemento del conjunto debe ser único.

for language in set(favorite_languages.values()):
    print(language.title())
C
Python
Ruby

Cuando envolvemos set() alrededor de una lista que contiene elementos duplicados, Python identifica los elementos únicos en la lista y crea un conjunto a partir de esos elementos.

Anidamiento

A veces querrás almacenar un conjunto de diccionarios en una lista o una lista de elementos como un valor en un diccionario. Esto se llama anidamiento. Puedes anidar un conjunto de diccionarios dentro de una lista, una lista de elementos dentro de un diccionario o incluso un diccionario dentro de otro diccionario.

Una lista de diccionarios

Hice un ejemplo sobre un alien, pero ¿y si queremos crear más? Una forma de hacerlo es creando una lista de diccionarios.

alien_0 = {'color': 'green', 'points': 5}
alien_1 = {'color': 'yellow', 'points': 10}
alien_2 = {'color': 'red', 'points': 15}
aliens = [alien_0, alien_1, alien_2]
for alien in aliens:
    print(alien)
{'color': 'green', 'points': 5}
{'color': 'yellow', 'points': 10}
{'color': 'red', 'points': 15}

Un ejemplo real más es crear tantos extraterrestres como quieras comenzando con una lista vacía y un bucle for.

aliens = []
for alien_number in range(30):
    new_alien = {'color': 'green', 'points': 5, 'speed': 'slow'}
    aliens.append(new_alien)
print(aliens)
[{'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}, {'color': 'green', 'points': 5, 'speed': 'slow'}]

Una lista en un diccionario

En lugar de poner un diccionario dentro de una lista, a veces es útil poner una lista dentro de un diccionario.

pizza  = {'masa':'delgada', 'ingredientes':['champiñones', 'extra queso']}
print("Pediste una pizza " + pizza['masa'] + " con los siguientes ingredientes:")
for ingrediente in pizza['ingredientes']:
    print("\t" + ingrediente)
Pediste una pizza delgada con los siguientes ingredientes:
    champiñones
    extra queso

No debes anidar listas y diccionarios a niveles muy profundos. Mantén ojo en eso para no comprometer la legibilidad.

Un diccionario en un diccionario

Puede anidar un diccionario dentro de otro diccionario, pero su código puede complicarse rápidamente si haces esto. Por ejemplo, si tiene varios usuarios para un sitio web, cada uno con un nombre de usuario único, puedes usar los nombres de usuario como las claves en un diccionario. A continuación, puedes almacenar información sobre cada usuario mediante el uso de un diccionario como el valor asociado con su nombre de usuario.

usuarios = {
    'aeinstein': {
        'nombre': 'albert',
        'apellido': 'einstein',
        'ubicacion': 'princeton',
        },
    'mcurie': {
        'nombre': 'marie',
        'apellido': 'curie',
        'ubicacion': 'paris',
        },
    }

for usuario, info_usuario in usuarios.items():
    print("\nUsuario: " + usuario)
    nombre_completo = info_usuario['nombre'] + " " + info_usuario['apellido']
    ubicacion = info_usuario['ubicacion']
    print("\tNombre completo: " + nombre_completo.title())
print("\tUbicación: " + ubicacion.title())
Usuario: aeinstein
    Nombre completo: Albert Einstein

Usuario: mcurie
    Nombre completo: Marie Curie
    Ubicación: Paris

Tuplas

Las listas son importantes cuando trabajas con elementos que puedes modificar. Incluso si no quieres cambiar ninguna información de los datos es mejor usar algo diferente. Las tuplas te permiten hacer precisamente eso.

Los valores que Python no puede cambiar se conocen como inmutables, y una lista inmutable se llama tupla. Una tupla se parece a una lista, excepto en que se utilizan paréntesis en lugar de llaves. Se puede acceder a los elementos individuales de la misma manera que a una lista.

dimensiones = (200, 50)
print(dimensiones[0])
print(dimensiones[1])
200
50

La única forma de "cambiar" una tupla es creando una nueva variable con el mismo nombre que la anterior que contenga valores diferentes. También puedes recorrer todos los valores de una tupla utilizando un bucle for, igual como se hace con una lista.

Para crear una tupla con un solo elemento, debe incluir una coma final:

t = 'a',

Otra similitud con una lista es que puedes crear una tupla utilizando su propia función incorporada llamada tuple(). Si no incorporamos argumentos en esta función se crea una tupla vacía y si el argumento es una secuencia (cadena, lista o tupla), el resultado es una tupla con los elementos de la secuencia como listas.

Asignación de tupla

El número de variables a la izquierda y el número de valores a la derecha tiene que ser el mismo:

a, b = 1, 2, 3
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[86], line 1
----> 1 a, b = 1, 2, 3

ValueError: too many values to unpack (expected 2)

A menudo las tuplas son útiles para intercambiar los valores de dos variables

a, b = 1, 2
a, b = b, a
print(a, b)
2 1

Tuplas como valores de retorno

Estrictamente hablando, una función solo puede devolver un valor, pero si el valor es una tupla, el efecto es el mismo que devolver múltiples valores. Por ejemplo, la función integrada divmod() toma dos argumentos y devuelve una tupla de dos valores:

t = divmod(7, 3)
print(t)
(2, 1)

O podemos utilizar las tuplas para almacenar dos elementos por separado:

quot, rem = divmod(7, 3)
print(quot)
print(rem)
2
1

Tuplas de argumento de longitud variable

Las funciones pueden tomar una cantidad variable de argumentos si usa *args en ellas. Esto se llama parámetro de recopilación y puede tener cualquier nombre que desee, pero args es convencional.

def print_all(*argumentos):
    print(argumentos)

print(1, 2.0, '3')
1 2.0 3

Si tienes una secuencia de valores y deseas pasarla a una función en múltiples argumentos, puedes usar el operador *. Por ejemplo, divmod toma exactamente dos argumentos. Si le pasáramos una tupla directamente al argumento no funcionaria, pero si funcionaria si utilizamos el asterisco.

t = (7, 3)
divmod(t)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[91], line 2
        1 t = (7, 3)
----> 2 divmod(t)

TypeError: divmod expected 2 arguments, got 1
divmod(*t)
(2, 1)

Muchas de las funciones integradas utilizan tuplas de argumentos de longitud variable. Por ejemplo, max() y min() pueden tomar cualquier número de argumentos. La función sum no lo hace, pero puedes usar el parámetro de recopilación o asterisco y funcionará.

Listas y Tuplas

La función zip() es una función integrada en Python que toma dos o más secuencias y las "comprime" en una lista de tuplas donde cada tupla contiene un elemento de cada secuencia.

s = 'abc'
t = [0, 1, 2]
list(zip(s, t))
[('a', 0), ('b', 1), ('c', 2)]

Si las secuencias no tienen la misma longitud, el resultado será del mismo tamaño que la secuencia de menor tamaño.

list(zip('mar', 'maremoto'))
[('m', 'm'), ('a', 'a'), ('r', 'r')]

Si necesitas recorrer los elementos de una secuencia y sus índices, puede usar la función integrada enumerate().

for indice, elemento in enumerate('abc'):
    print(indice, elemento)
0 a
1 b
2 c

Diccionarios y tuplas

Los diccionarios tienen un método llamado **items()** que devuelve una lista de tuplas, donde cada tupla es un par clave-valor.

d = {'a':0, 'b':1, 'c':2}
t = list(d.items())
print(t)
[('a', 0), ('b', 1), ('c', 2)]

La combinación de dict con zip produce una forma concisa de crear un diccionario

d = dict(zip('abc', range(3)))
print(d)
{'a': 0, 'b': 1, 'c': 2}

Comparando tuplas

Los operadores relacionales trabajan con tuplas y otras secuencias donde Python comienza comparando el primer elemento de cada secuencia. Si son iguales, pasa a los siguientes elementos, y así sucesivamente, hasta encontrar elementos que difieren.

a = (0, 1, 2) < (0, 3, 4)
b = (0, 1, 2000000) < (0, 3, 4)
print(a)
print(b)
True
True

Funciones

Las funciones son bloques de código con nombre que están diseñados para realizar un trabajo específico. Cuando deseas realizar una tarea particular que has definido en una función, debes llamar al nombre de la función responsable de ello.

def saludar_usuario():
    """Mostrar un saludo simple."""
    print("¡Hola!")
saludar_usuario()
¡Hola!

La primera línea usa la palabra clave def para informar a Python que estás definiendo una función. La segunda línea es el cuerpo de la función. Las comillas triples se denominan cadena de documentación, que describe lo que hace la función. El resto del cuerpo es el código real que queremos ejecutar.

Cuando queremos usar esta función debemos llamarla. Una llamada de función le dice a Python que ejecute el código que está dentro de la función. Para llamar a una función, escribimos el nombre de la función, seguido de cualquier información necesaria entre paréntesis.

Pasar información a una función

Puedes agregar una entrada o información dentro del paréntesis para hacer algo con ella. En el siguiente ejemplo, la función espera que proporcionemos un valor para el nombre de usuario cada vez que llamemos a la función. Cuando llamas a saludo_usuario(), puedes pasarle un nombre, como 'tomás', entre paréntesis.

def saludar_usuario(nombre):
    """Mostrar un saludo simple."""
    print("¡Hola, " + nombre.title() + "!")

saludar_usuario('tomás')
¡Hola, Tomás!

En la última línea llamamos a la función saludar_usuario. Si no lo llamamos, la función nunca se ejecutará. La función acepta el nombre que le damos y muestra el saludo para ese nombre. Esto hace que un programa sea flexible, porque podemos ingresar cualquier nombre dentro de la función y hacer lo que queramos.

Argumentos y Parámetros

La variable "nombre" en la definición de saludar_usuario() es un ejemplo de un parámetro, una información que la función necesita para hacer su trabajo. El valor 'tomás' en saludar_usuario('tomás') es un ejemplo de un argumento. Un argumento es una pieza de información que le damos a la llamada de la función. En este caso, el argumento 'tomás' se utilizó en la función saludar_usuario() y el valor se almacenó en el parámetro nombre.

Pasar Argumentos

Hay tres formas de pasar un argumento a una función. Veamos cada uno de ellos.

Argumentos posicionales

Cuando llamamos a una función, Python debe hacer coincidir cada argumento en la llamada de la función con los parámetros en la definición de la función y coincidir también con el argumento posicional, lo que significa que el orden en que están escritos los argumentos sí importa.

def mi_mascota(animal, nombre):
    print('Mi mascota es un ' + animal + ' y se llama ' + nombre.title() + '.')

mi_mascota('hámster', 'harry')
Mi mascota es un hámster y se llama Harry.
Llamadas de función múltiple

Podemos llamar a una función tantas veces como sea necesario. Describir una segunda mascota diferente requiere solo una llamada más a mi_mascota()

mi_mascota('hámster', 'harry')
mi_mascota('perro', 'polo')
mi_mascota('pez', 'nemo')
Mi mascota es un hámster y se llama Harry.
Mi mascota es un perro y se llama Polo.
Mi mascota es un pez y se llama Nemo.
Argumento de palabra clave

Un argumento de palabra clave es un par de nombre y valor que pasa a una función. Asocias directamente el nombre y el valor dentro del argumento.

mi_mascota(animal = 'hamster', nombre = 'harry')
Mi mascota es un hamster y se llama Harry.

Los argumentos de palabras clave lo liberan de tener que preocuparse por ordenar correctamente sus argumentos en la llamada a la función y aclaran el rol de cada valor en la llamada a la función.

Valores predeterminados

Al escribir una función, podemos definir un valor predeterminado para cada parámetro. Si se proporciona un argumento para un parámetro en la llamada a la función, Python usa el valor del argumento.

def nombre_mascota(nombre, animal='perro'):
    print('Mi mascota es un ' + animal + ' y se llama ' + nombre.title() + '.')

nombre_mascota(nombre = 'willie')
Mi mascota es un perro y se llama Willie.

Cuando se llama a la función sin especificar el argumento "animal", Python sabe que debe usar el valor por defecto, es decir, debe usar el valor 'perro' para este parámetro.

El primer parámetro debe estar en la primera posición porque Python todavía lo interpreta como un argumento posicional, por lo que si se llama a la función solo con el nombre de una mascota, ese argumento coincidirá con el primer parámetro enumerado en la definición de la función.

Tenga cuidado: solo asegúrese de proporcionar la cantidad exacta de argumentos al parámetro. Los argumentos que no coinciden ocurren cuando proporcionas menos o más argumentos de los que necesita una función para hacer su trabajo.

Hacer un argumento opcional

A veces tiene sentido hacer que un argumento sea opcional para que las personas que usan la función puedan optar por proporcionar información adicional solo si así lo desean. Podemos usar valores predeterminados para hacer que un argumento sea opcional, por ejemplo, para un segundo nombre. Podemos darle al argumento middle_name un valor predeterminado vacío e ignorar el argumento a menos que el usuario proporcione un valor.

def nombre_completo(nombre, apellido, segundo_nombre=''):
    """Retorna un nombre completo, con un formato ordenado."""
    if segundo_nombre:
        nombre_completo = nombre + ' ' + segundo_nombre + ' ' + apellido
    else:
        nombre_completo = nombre + ' ' + apellido
    print(nombre_completo.title())

nombre_completo('john', 'rockefeller', 'davison')
John Davison Rockefeller

Python interpreta las cadenas no vacías como verdaderas, por lo que si se ha proporcionado un segundo nombre, se ejecutará la primera instrucción if. Al declarar un parámetro como predeterminado con una cadena vacía, no necesitamos proporcionar los tres argumentos, solo dos si así lo queremos.

Mucho ojo: 1) Una función puede devolver cualquier tipo de valor que necesites, incluidas estructuras de datos más complicadas como listas y diccionarios. 2) Cada función debe tener un trabajo específico.

Pasar un número arbitrario de argumentos

A veces, no sabremos de antemano cuántos argumentos debe aceptar una función. Afortunadamente, Python permite que una función recopile un número arbitrario de argumentos de la declaración de llamada.

def hacer_pizza(*ingredientes):
    """Imprimir la lista de ingredientes que se han solicitado."""
    print(ingredientes)
    
hacer_pizza('pepperoni')
hacer_pizza('champiñones', 'pimentones verdes', 'queso extra')
('pepperoni',)
('champiñones', 'pimentones verdes', 'queso extra')

En este ejemplo, no sabemos cuántos ingredientes agregará el cliente a su pizza. El asterisco en el nombre del parámetro *ingredientes le dice a Python que cree una tupla vacía llamada ingredientes y empaquete cualquier valor que reciba en esta tupla.

Mezclar argumentos posicionales y arbitrarios

Si deseas que una función acepte diferentes argumentos de varios tipos, el parámetro que acepta un número arbitrario de argumentos debe colocarse en último lugar en la definición de la función.

def hacer_pizza(tamaño, *ingredientes):
    """Resume la pizza que estamos a punto de hacer."""
    print("\nPreparando una pizza de " + str(tamaño) + " pulgadas con los siguientes ingredientes:")
    for ingrediente in ingredientes:
        print("- " + ingrediente)

hacer_pizza(16, 'pepperoni')
hacer_pizza(12, 'champiñones', 'pimientos verdes', 'queso extra')
Preparando una pizza de 16 pulgadas con los siguientes ingredientes:
- pepperoni

Preparando una pizza de 12 pulgadas con los siguientes ingredientes:
- champiñones
- pimientos verdes
- queso extra

Las llamadas para la función "hacer_pizza" incluyen un argumento para el tamaño seguido de tantos ingredientes como sea necesario.

Uso de argumentos de palabras clave arbitrarias

De la misma manera que con los argumentos arbitrarios, los argumentos de palabras clave se usan cuando no se sabe de antemano qué tipo de información se pasará a la función. En este caso, podemos escribir funciones que acepten tantos pares clave-valor como proporcione la declaración de llamada.

def crear_perfil(primer_nombre, segundo_nombre, **información_usuario):
    """Crear un diccionario que contenga todo lo que sabemos sobre un usuario."""
    perfil = {}
    perfil['primer_nombre'] = primer_nombre
    perfil['segundo_nombre'] = segundo_nombre
    for clave, valor in información_usuario.items():
        perfil[clave] = valor
    print(perfil)
    
crear_perfil('albert', 'einstein', ubicación='princeton', campo='física')
{'primer_nombre': 'albert', 'segundo_nombre': 'einstein', 'ubicación': 'princeton', 'campo': 'física'}

Los asteriscos dobles antes del parámetro _**crearperfil hacen que Python cree un diccionario vacío llamado user_info y empaquete cualquier par de clave-valor que reciba este diccionario.

Valores devueltos

Una función no siempre tiene que mostrar su salida directamente. En su lugar, puede procesar algunos datos y luego devolver un valor o un conjunto de valores. El valor que devuelve la función se llama valor de retorno. La declaración de retorno toma un valor desde dentro de una función y lo envía de vuelta a la línea que llamó a la función.

def nombre_completo(nombre, apellido):
    """Retorna un nombre completo, con un formato ordenado."""
    nombre_completo = nombre + ' ' + apellido
    return nombre_completo.title()

musico = nombre_completo('jimi', 'hendrix')
print(musico)
Jimi Hendrix

La función devuelve un valor que se puede utilizar en otras funciones o variables. Esa es la idea de devolver un valor. Una buena práctica: una función debe devolver el valor que espera o de lo contrario debería devolver None.

Modulos

Los modulos son archivos de python ".py" que podemos incorporar a nuestros proyectos. Son trabajos que hemos hecho nosotros u otros programadores que nos ayudan con el flujo y orden de nuestro programa.

Almacenamiento de funciones en módulos

Podemos almacenar funciones en un archivo separado ".py" llamado módulo y luego importar ese módulo a su programa principal. Una declaración de importación le dice a Python que haga que el código en un módulo esté disponible en el archivo de programa que se está ejecutando actualmente. Almacenar sus funciones en un archivo separado permitirá ocultar los detalles del código de su programa y enfocarse en la lógica de nivel superior.

Hay varias formas de importar un módulo, pero en cada una de ellas debemos utilizar la palabra clave import al inicio de nuestro código.

Importación de un módulo completo
import pizza # Nombre del módulo pizza.py

pizza.hacer_pizza(16, 'pepperoni') # nombre de la función hacer_pizza dentro de pizza.py
pizza.hacer_pizza(12, 'champiñones', 'pimientos verdes', 'queso extra')
Preparando una pizza de 16 pulgadas con los siguientes ingredientes:
- pepperoni

Preparando una pizza de 12 pulgadas con los siguientes ingredientes:
- champiñones
- pimientos verdes
- queso extra

Este programa debe estar en el mismo directorio que pizza.py para funcionar. Cuando Python lee este archivo, la línea import pizza le dice a Python que abra el archivo pizza.py y copie todas las funciones que estén en ese programa al programa actual.

Si utilizas este tipo de declaración de importación para importar un módulo completo, por ejemplo, nombre_modulo.py, cada función en el módulo estará disponible a través de la siguiente sintaxis: nombre_módulo.nombre_función().

Importación de funciones específicas

También podemos importar una función específica o más de una función de un módulo.

from pizza import hacer_pizza

Con esta sintaxis, no necesitamos usar la notación de puntos cuando llamamos a una función. Porque hemos importado explícitamente la función hacer_pizza() en la declaración de importación.

Utilizar la palabra clave "as" para darle un alias a una función

Si el nombre de la función que se está importando puede entrar en conflicto con un nombre existente en su programa o si el nombre de la función es largo, puede usar un alias corto y único, un nombre alternativo similar a un apodo para la función.

from pizza import hacer_pizza as hp

hp(16, 'salchichones')
hp(12, 'champiñones', 'pimientos verdes', 'queso extra')
Preparando una pizza de 16 pulgadas con los siguientes ingredientes:
- salchichones

Preparando una pizza de 12 pulgadas con los siguientes ingredientes:
- champiñones
- pimientos verdes
- queso extra

La declaración de importación que se muestra aquí cambia el nombre de la función _hacerpizza() a hp() para este programa.

Utilizar la palabra clave "as" para darle un alias a un módulo

De la misma forma podemos proporcionar un alias para un nombre de módulo. Darle a un módulo un alias corto, como p para pizza, le permite llamar a las funciones del módulo más rápidamente.

import pizza as p

p.hacer_pizza(16, 'pepperoni')
p.hacer_pizza(12, 'champiñones', 'pimientos verdes', 'queso extra')
Preparando una pizza de 16 pulgadas con los siguientes ingredientes:
- pepperoni

Preparando una pizza de 12 pulgadas con los siguientes ingredientes:
- champiñones
- pimientos verdes
- queso extra

El módulo pizza recibe el alias "p" en la declaración de importación, pero todas las funciones del módulo conservan sus nombres originales.

Importación de todas las funciones en un módulo

Puede decirle a Python que importe todas las funciones dentro de un módulo utilizando el operador de asterisco (*):

from pizza import *

hacer_pizza(16, 'pepperoni')
hacer_pizza(12, 'champiñones', 'pimientos verdes', 'queso extra')
Preparando una pizza de 16 pulgadas con los siguientes ingredientes:
- pepperoni

Preparando una pizza de 12 pulgadas con los siguientes ingredientes:
- champiñones
- pimientos verdes
- queso extra

Debido a que cada función se importa, podemos llamar a cada función por su nombre sin usar la notación de puntos. Sin embargo, es mejor no usar este enfoque cuando trabajamos con módulos más grandes, porque le perdemos el seguimiento a cada función para saber de qué modulo viene.

Recursividad

Es legal que una función llame a otra; también es legal que una función se llame a sí misma. Esto hace que el programa se ejecute nuevamente, pero la idea es pasarle distintos argumentos para que el programa tenga sentido. Por ejemplo:

def cuenta_regresiva(n):
    if n <= 0:
        print('¡Despegue!')
    else:
        print(n)
        cuenta_regresiva(n-1)

cuenta_regresiva(10)
10
9
8
7
6
5
4
3
2
1
¡Despegue!

Esta función imprime el número n y luego pasa n-1 como argumento a sí misma. Esto hace que n sea cada vez menor hasta que sea igual o menor a cero. Cuando eso sucede la función deja de llamarse a sí misma y se termina. Una función que se llama a sí misma se conoce como función recursiva y el proceso de ejecución se llama recursividad.

Input de usuario y bucle While

Si somos capaces de trabajar con la entrada del usuario y de controlar por cuánto tiempo se ejecutan los programas, podremos ser capaces de escribir programas totalmente interactivos.

Cómo funciona la función input()

La mayoría de los programas se escriben para resolver un problema del usuario final. Para ello, normalmente se necesita obtener alguna información del usuario. La función input() pausa tu programa y espera a que el usuario introduzca algún texto. Una vez que Python recibe la entrada del usuario, la almacena en una variable para que te resulte más cómodo trabajar con ella.

mensaje = input("Dime algo y te lo repetiré: ")
print(mensaje)

Esto envía un mensaje al usuario y el programa espera hasta que el usuario escriba algo y pulse Enter. Esta respuesta se almacena en la variable "mensaje" y luego se imprime.

Usando int() para aceptar entradas numéricas

Cuando usas la función input(), Python interpreta todo lo que el usuario introduce como un string. Si todo lo que quieres hacer es imprimir la entrada, esto funciona bien. Pero si intentas utilizar la entrada como un número, obtendrá un error.

edad = input("Cuál es tu edada? ")
edad >= 18
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[118], line 2
        1 edad = input("Cuál es tu edada? ")
----> 2 edad >= 18

TypeError: '>=' not supported between instances of 'str' and 'int'

No podemos comparar un string con un número y por eso necesitamos convertir una representación de string de un número a una representación numérica utilizando la función int().

edad = input("Cuál es tu edada? ")
edad = int(edad)
edad >= 18

El operador módulo

Una herramienta útil para trabajar con información numérica es el operador módulo (%), que divide un número por otro y devuelve el resto:

print(4 % 3)
print(5 % 3)
print(6 % 3)
1
2
0

Podemos utilizar este operador de muchas maneras. Por ejemplo, para ver si un número es par o impar.

numero = 5
if numero % 2 == 0:
    print("\nEl número " + str(numero) + " is par.")
else:
    print("\nEl número " + str(numero) + " is impar.")
El número 5 is impar.

Introducción a los bucles while

El bucle for toma una colección de elementos y ejecuta un bloque de código una vez por cada elemento de la colección. Por el contrario, el bucle while se ejecuta mientras se cumpla una determinada condición.

numero_actual = 1
while numero_actual <= 5:
    print(numero_actual)
    numero_actual += 1
1
2
3
4
5

Este trozo de código toma la variable número_actual, comprueba si es menor o igual que 5 y en caso afirmativo se imprimirá y añadirá un número más a la variable. Este proceso se repite hasta que la prueba condicional deje de ser cierta. La salida son los números impresos 1, 2, 3, 4 y 5.

Dejar que el usuario elija cuándo abandonar

Otro ejemplo es dejar que el usuario cierre el programa cuando quiera. Esto hace que nuestro programa sea más interactivo.

entrada = "\nDime algo y te lo repetiré:"
entrada += "\Ingresa 'quit' para terminar el programa. "
mensaje = ""
while mensaje != 'quit':
    mensaje = input(entrada)
    print(mensaje)
hola
hola
quit

Este bucle while se ejecuta mientras el valor de la variable mensaje no sea 'quit'. Este programa funciona bien, excepto que imprime la palabra 'quit' como si fuera un mensaje real. Una simple prueba if lo soluciona.

entrada = "\nDime algo y te lo repetiré:"
entrada += "\Ingresa 'quit' para terminar el programa. "
mensaje = ""
while mensaje != 'quit':
    mensaje = input(entrada)
    if mensaje != 'quit':
        print(mensaje)
hola
hola

Utilizar una bandera

En el ejemplo anterior, hicimos que el programa realizara ciertas tareas mientras se cumplía una determinada condición. Pero, ¿qué pasa con programas más complicados en los que muchos eventos diferentes pueden hacer que el programa deje de ejecutarse? Por ejemplo en los juegos varias cosas hacen que el programa termine, como cuando se acaba el tiempo o que nuestra barra de vida esté vacía. Intentar probar todas las condiciones en un bucle while es complicado y difícil.

Para resolver esto puedes definir una variable, llamada bandera o flag, que actúa como señal del programa.

activo = True
while activo:
    mensaje = input(entrada)
    if mensaje == 'quit':
        activo = False
    else:
        print(mensaje)
hola
hola

Podemos escribir nuestros programas de manera que se ejecuten mientras la bandera sea verdadera y dejen de ejecutarse cuando uno de varios eventos pongan el valor de la bandera como falso.

Utilizar la sentencia break para salir del bucle While

Para salir inmediatamente de un bucle while sin ejecutar ningún código restante, podemos utilizar la sentencia break.

while True:
    ciudad = input(entrada)
    if ciudad == 'quit':
        break
    else:
        print("Me encantaría ir a " + ciudad.title() + "!")
Me encantaría ir a Chicago!
Me encantaría ir a Tokio!

Un bucle que comienza con while True se ejecutará para siempre a menos que llegue a una sentencia break.

Puedes usar la sentencia break en cualquiera de los bucles de Python. Por ejemplo, puedes usar break para salir de un bucle for que está trabajando a través de una lista o un diccionario.

Utilizar la sentencia continue para salir del bucle While

Puede utilizar la sentencia continue para volver al principio del bucle basándose en el resultado de una prueba condicional.

numero_actual = 0
while numero_actual < 10:
    numero_actual += 1
    if numero_actual % 2 == 0:
        continue
    print(numero_actual)
1
3
5
7
9

Si la variable numero_actual no es divisible por 2, el resto del bucle se ejecuta y Python imprime numero_actual. Incluso si el módulo es 0 (lo que significa que la variable numero_actual es divisible por 2), la sentencia continue le dice a Python que ignore el resto del bucle y vuelva al principio. En otras palabras, este programa sólo imprime los números impares (1, 3, 5, 7, 9).

Uso de un bucle while con listas y diccionarios

Un bucle for es efectivo para recorrer una lista, pero no deberías modificar una lista dentro de un bucle for porque Python tendrá problemas para seguir la pista de los elementos de la lista. Para modificar una lista a medida que la recorres, utiliza un bucle while.

Usar bucles while con listas y diccionarios te permite recoger, almacenar y organizar mucha información para examinarla y elaborar informes más tarde.

Mover elementos de una lista a otra

Podemos utilizar un bucle while para pasar los elementos de una lista de usuarios no confirmados a otra de usuarios confirmados utilizando el método pop(). Cuando la lista de usuarios no confirmados quede vacía salimos del bucle.

usuarios_no_confirmados = ['Alicia', 'Benjamín', 'Candelaria']
usuarios_confirmados = []
while usuarios_no_confirmados:
    usuario_actual = usuarios_no_confirmados.pop()
    print("Usuario confirmado: " + usuario_actual.title())
    usuarios_confirmados.append(usuario_actual)
usuarios_confirmados
Usuario confirmado: Candelaria
Usuario confirmado: Benjamín
Usuario confirmado: Alicia
['Candelaria', 'Benjamín', 'Alicia']

Eliminar todas las instancias de valores específicos de una lista

Utilizando un bucle while seremos capaces de eliminar un elemento de una lista hasta que no quede ninguno.

mascotas = ["perro", "gato", "perro", "pez de colores", "gato", "conejo", "gato"]
print(mascotas)
while 'gato' in mascotas:
    mascotas.remove("gato")
print(mascotas)
['perro', 'gato', 'perro', 'pez de colores', 'gato', 'conejo', 'gato']
['perro', 'perro', 'pez de colores', 'conejo']

Rellenar un diccionario con datos introducidos por el usuario

Podemos agregar elementos en el diccionario hasta que el usuario decida dejar de hacerlo. De esta creamos información y hacemos que el programa sea interactivo.

respuestas = {}
sondeo_activo = True
while sondeo_activo:
    nombre = input("¿Cómo te llamas?")
    respuesta = input("¿Qué montaña te gustaría escalar algún día?")
    respuestas[nombre] = respuesta
    repetir = input("¿Quieres que responda otra persona? (si/no) ")
    if repetir == 'no':
        sondeo_activo = False
print("\n--- Resultados de la encuesta ---")
for nombre, respuesta in respuestas.items():
    print(nombre.title() + " quiere subir el " + respuesta.title() + ".")
--- Resultados de la encuesta ---
Francisco quiere subir el Everest.
Antonio quiere subir el K2.

Clases

La programación orientada a objetos es uno de los métodos más eficaces para escribir software. En la programación orientada a objetos se escriben clases que representan cosas y situaciones del mundo real, y se crean objetos basados en esas clases. Al escribir una clase, se define el comportamiento general que puede tener toda una categoría de objetos.

Crear y utilizar una clase

Podemos modelar casi cualquier cosa utilizando clases. Empecemos escribiendo una clase sencilla llamada Perro.

class Perro:
    """Un simple intento de modelar un perro."""

    def __init__(self, nombre, edad):
        """Inicializar los atributos nombre y edad."""
        self.nombre = nombre
        self.edad = edad
            
    def sentarse(self):
        """Simular que un perro se sienta en respuesta a una orden."""
        print(self.nombre.title() + ' ahora está sentado.')
            
    def rodar(self):
        """Simular que un perro se da la vuelta en respuesta a una orden."""
        print(self.nombre.title() + ' se ha dado la vuelta!')

Por convención, los nombres en mayúsculas se refieren a clases en Python.

El método __init__()

Una función que forma parte de una clase es un método. Hay también métodos especiales como __init__() que se le consideran también como "métodos mágicos". En el caso particular de __init__(), Python ejecuta este método automáticamente cada vez que creamos una nueva instancia basada en la clase Perro. El parámetro self es necesario en la definición del método, y debe ir primero antes que los otros parámetros porque cuando Python llame al método __init__() más tarde (para crear una instancia de Dog), la llamada al método pasará automáticamente al argumento self. Le da a la instancia individual acceso a los atributos y métodos de la clase.

Las dos variables definidas tienen el prefijo self. Cualquier variable prefijada con self está disponible para todos los métodos de la clase, y también podremos acceder a estas variables a través de cualquier instancia creada a partir de la clase.

Haciendo la instancia de un perro

Los perros pueden ser una clase, asi como un perro en especifico, por ejemplo, un Golden Retriever o un Pastor Alemán, pueden ser objetos. Ambos son distintos, pero comparten ciertas similaridades que todos los perros tienen y esas similaridades están especificadas en la clase perro.

Piensa en una clase como un conjunto de instrucciones sobre cómo crear una instancia. La clase Perro es un conjunto de instrucciones que le dice a Python cómo crear instancias individuales que representan perros específicos.

mi_perro = Perro('polo', 6) # creamos una instancia de la clase perro
print('El nombre de mi perro es ' + mi_perro.nombre.title() + '.')
print('Mi perro tiene ' + str(mi_perro.edad) + ' años')
El nombre de mi perro es Polo.
Mi perro tiene 6 años

Cuando Python lee esas líneas, llama al método __init__() en Dog con los argumentos 'polo' y 6. El método __init__() crea una instancia que representa a este perro en particular y establece los atributos nombre y edad utilizando los valores que proporcionamos. Normalmente podemos asumir que un nombre en mayúsculas como Perro se refiere a una clase, y un nombre en minúsculas como mi_perro se refiere a una única instancia creada a partir de una clase.

Acceso a los atributos.

Aquí Python busca la instancia mi_perro y luego encuentra el nombre del atributo asociado a mi_perro, separando la instancia con el método a través de un punto.

mi_perro.nombre
'polo'

Métodos de llamada

Para llamar a los métodos utilizamos la misma sintaxis que para los atributos: separamos la instancia del método con un punto. La única diferencia es que como estamos hablando de funciones, al final debemos de poner los paréntesis y argumentos en caso que apliquen.

mi_perro.sentarse()
mi_perro.rodar()
Polo ahora está sentado.
Polo se ha dado la vuelta!

Cuando Python lee _miperro.sentarse(), busca el método sentarse() en la clase Perro y ejecuta ese código. Lo mismo sucede con los demás métodos como rodar().

Creación de varias instancias

Puedes crear tantas instancias de una clase como necesites (¡muchos perros!)

tu_perro = Perro('Bruno', 2)
perro_del_vecino = Perro('Olivia', 10)

En este ejemplo creamos un perro llamado Bruno y un perro llamado Olivia. Cada perro es una instancia independiente con su propio conjunto de atributos.

Trabajar con clases e instancias

Establecer un valor por defecto para un atributo

Cada atributo en una clase necesita un valor inicial, incluso si ese valor es 0 o una cadena vacía. En algunos casos, como cuando se establece un valor por defecto, tiene sentido especificar este valor inicial en el cuerpo del método __init__().

class Auto:	
    def __init__(self, marca, modelo, año): 
        """Inicializar atributos para describir un auto."""
        self.marca = marca
        self.modelo = modelo
        self.año = año
        self.lectura_odometro = 0
                        
    def leer_odometro(self):
        """Imprime un extracto mostrando el kilometraje del auto."""
        print("Este auto tiene " + str(self.lectura_odometro) + " kilómetros.")

    def obtener_nombre_descriptivo(self):
        """ Imprimimos el nombre, maraca y año del auto """
        print('Marca: ', self.marca.title())
        print('Modelo: ', self.modelo.title())
        print('Año: ', self.año)

Notar que no incluimos "lectura_odometro" como argumento para __init__() porque lo establecemos con un valor. Para este ejemplo, Python crea un nuevo atributo llamado lectura_odometro y establece su valor inicial en 0.

Modificación de los valores de los atributos

Puedes cambiar el valor de un atributo de tres maneras:

Modificación directa del valor de un atributo

La forma más sencilla de modificar el valor de un atributo es acceder a él directamente a través de una instancia.

def __init__(self, marca, modelo, año):
    self.lectura_odometro = 23

Modificar el valor de un atributo mediante un método

Puede ser útil disponer de métodos que actualicen ciertos atributos por usted.

def actualizar_odometro(self, kilometraje):
        """Establece la lectura del cuentakilómetros en el valor dado."""
        self.leer_odometro = kilometraje

mi_auto_nuevo = Auto('audi', 'a4', 2016) # No añadimos lectura_odometro porque fijamos su valor
mi_auto_nuevo.actualizar_odometro(23)
mi_auto_nuevo.leer_odometro()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[130], line 6
        3         self.leer_odometro = kilometraje
        5 mi_auto_nuevo = Auto('audi', 'a4', 2016) # No añadimos lectura_odometro porque fijamos su valor
----> 6 mi_auto_nuevo.actualizar_odometro(23)
        7 mi_auto_nuevo.leer_odometro()

AttributeError: 'Auto' object has no attribute 'actualizar_odometro'

Incrementar el valor de un atributo a través de un método

def incrementar_odometro(self, kilometros):
    """Añade una cantidad de kilometros dado a la lectura del odometro."""
    self.lectura_odometro += kilometros
    
mi_auto_usado = auto('subaru', 'outback', 2013)
print(mi_auto_usado.obtener_nombre_descriptivo())
mi_auto_usado.actualizar_odometro(23500)
mi_auto_usado.leer_odometro()
mi_auto_usado.incrementar_odometro(100)
mi_auto_usado.leer_odometro()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[131], line 5
        2     """Añade una cantidad de kilometros dado a la lectura del odometro."""
        3     self.lectura_odometro += kilometros
----> 5 mi_auto_usado = auto('subaru', 'outback', 2013)
        6 print(mi_auto_usado.obtener_nombre_descriptivo())
        7 mi_auto_usado.actualizar_odometro(23500)

TypeError: 'str' object is not callable

Herencia

No siempre tienes que empezar de cero cuando escribes una clase. Si la clase que estás escribiendo es una versión especializada de otra clase que escribiste, puedes utilizar la herencia. Cuando una clase se hereda de otra, automáticamente toma todos los atributos y métodos de la primera clase. La clase original se denomina clase padre, y la nueva clase es la clase hijo.

El método __init__() para una clase hija

La primera tarea de Python al crear una instancia a partir de una clase hija es asignar valores a todos los atributos de la clase padre. El método __init__() de una clase hija necesita la ayuda de su clase padre.

class AutoElectrico(Auto):
    """Representar aspectos de un auto eléctrico."""
    def __init__(self, marca, modelo, año):
        """Inicializar atributos de la clase padre."""
        super().__init__(marca, modelo, año)

mi_tesla = AutoElectrico('tesla', 'model s', 2016)
mi_tesla.obtener_nombre_descriptivo()
Marca:  Tesla
Modelo:  Model S
Año:  2016

AutoElectrico es una clase hija de la clase Auto (ver el argumento de AutoElectrico).

La función super() es una función especial que ayuda a Python a establecer conexiones entre la clase padre y la clase hija. Esta línea le dice a Python que llame al método __init__() de la clase padre de AutoElectrico, que le da a una instancia de ElectricCar todos los atributos de su clase padre. Super() es para la superclase o clase padre. filep La instancia de ElectricCar funciona igual que una instancia de Car, así que ahora podemos empezar a definir atributos y métodos específicos para los autos eléctricos. (2016 Tesla Modelo S)

Definición de atributos y métodos para la clase hija

Para diferenciar la clase hija de la clase padre puedes añadir los atributos y métodos nuevos que sean necesarios.

class AutoElectrico(Auto):
    """Representar aspectos de un auto eléctrico."""
    def __init__(self, marca, modelo, año):
        """Inicializar atributos de la clase padre. 
        Luego inicializa los atributos específicos de un coche eléctrico."""
        super().__init__(marca, modelo, año)
        self.tamaño_bateria = 70
        
    def describir_bateria(self):
        """Imprime una declaración describiendo el tamaño de la batería."""
        print("Este auto tiene una batería de " + str(self.tamaño_bateria) + "-kWh.")

El atributo self.describir_bateria se asociará a todas las instancias creadas a partir de la clase AutoElectrico pero no se asociará a ninguna instancia de Auto. Un atributo o método que pertenezca a Auto formará parte para todas las clases hijas de Auto, por lo que deben ser métodos y atributos que puedan pertenecer a cualquier auto.

Sobreescribiendo Métodos de la Clase Padre

Puedes anular cualquier método de la clase padre que no se ajuste a lo que estás intentando modelar con la clase hija. Para ello, se define un método en la clase hija con el mismo nombre que el método que deseas anular en la clase padre. Digamos que la clase Auto tiene un método llamado llenar_tanque(). Este método no tiene sentido para un vehículo totalmente eléctrico, por lo que es posible que quieras anular ese método.

class AutoElectrico(Auto):
    """Representar aspectos de un auto eléctrico."""
    def __init__(self, marca, modelo, año):
        """Inicializar atributos de la clase padre. 
        Luego inicializa los atributos específicos de un coche eléctrico."""
        super().__init__(marca, modelo, año)
        self.tamaño_bateria = 70
        
    def describir_bateria(self):
        """Imprime una declaración describiendo el tamaño de la batería."""
        print("Este auto tiene una batería de " + str(self.tamaño_bateria) + "-kWh.")

    def llenar_tanque(self):
        """Los autos eléctricos no tienen tanque de bencina."""
        print("¡Este auto no necesita tanque de bencina!")

Ahora, si alguien intenta llamar a llenar_tanque() con un coche eléctrico, Python ignorará el método llenar_tanque() en Auto y ejecutará este código en su lugar.

Instancias como atributos

Cuando tu clase se hace más grande puedes crear clases separadas de una clase.

Puedes dividir tu clase grande en clases más pequeñas que trabajen juntas. Por ejemplo cuando añadimos muchos atributos y métodos a la batería del coche podemos crear una clase separada llamada Batería. Puedes dividir tu clase grande en clases más pequeñas que trabajen juntas.

class Bateria:
    """Un simple intento de modelar una batería para un coche eléctrico."""
    def __init__(self) -> None:
        self.tamaño_bateria = 70
    
    def describir_bateria(self):
        """Imprime una declaración describiendo el tamaño de la batería."""
        print("Este auto tiene una batería de " + str(self.tamaño_bateria) + "-kWh.")
                            
class AutoElectrico(Auto):
    """Representar aspectos de un auto eléctrico."""
    def __init__(self, marca, modelo, año):
        """Inicializar atributos de la clase padre. 
        Luego inicializa los atributos específicos de un coche eléctrico."""
        super().__init__(marca, modelo, año)
        self.bateria = Bateria()

    def llenar_tanque(self):
        """Los autos eléctricos no tienen tanque de bencina."""
        print("¡Este auto no necesita tanque de bencina!")
                                    
mi_tesla = AutoElectrico('tesla', 'modelo s', 2016)
mi_tesla.obtener_nombre_descriptivo()
mi_tesla.bateria.describir_bateria()
Marca:  Tesla
Modelo:  Modelo S
Año:  2016
Este auto tiene una batería de 70-kWh.

En la clase AutoElectrico, ahora añadimos un atributo llamado self.bateria. Esa línea le dice a Python que cree una nueva instancia de Bateria y almacene esa instancia en el atributo self.bateria. Cuando queramos describir la batería, tenemos que trabajar a través del atributo bateria del auto: mi_tesla.bateria.describir_bateria().

Importar clases

Cuando estás tratando con grandes cantidades de código es adecuado importar segmentos de vez en cuando para mantener tus archivos tan despejados como sea posible. Para ayudarte, Python te permite almacenar clases en módulos y luego importar las clases que necesites en tu programa principal.

Sintaxis de importación ¿Qué hace?
from auto import Auto Abre el módulo 'auto' e importa la clase Auto
from auto import Auto, AutoElectrico Importa varias clases de un módulo
import auto Importa un módulo completo. Accede a las clases usando la notación de punto
from auto import * Importa todas las clases de un módulo. No se recomienda, ya que puede ser menos legible al no saber de dónde provienen los métodos
import pandas as pd Importa el módulo completo y lo renombra como 'pd'

Si tiene dudas sobre el comportamiento de una clase, puede utilizar la función de help(). Verás documentación bien ordenada para la clase.

La biblioteca estándar de Python

La biblioteca estándar de Python es un conjunto de módulos incluidos en cada instalación de Python, como el módulo math, el módulo de string o el módulo collections.

Archivos y excepciones

Lectura de un archivo

La lectura de un archivo es especialmente útil en aplicaciones de análisis de datos, pero también es aplicable a cualquier situación en la que se desee analizar o modificar información almacenada en un archivo. Existen tres formas diferentes:

Lectura de todo el archivo

Cuando se quiere trabajar con la información de un archivo de texto, el primer paso es leer el archivo en memoria. Puedes leer todo el contenido de un fichero o trabajar con él línea a línea.

with open('digitos_pi.txt') as obj:
    contenido = obj.read()
    print(contenido)
3.141592653589793238462643383279502884

Aquí, open('pi_digits.txt') devuelve un objeto que representa pi_digits.txt. Python almacena este objeto en obj, con el que trabajaremos más adelante en el programa.

La palabra clave with cierra el archivo una vez que ya no es necesario acceder a él. Observa cómo llamamos a open() en este programa pero no a close(). Podrías abrir y cerrar el archivo llamando a open() y close(), pero si un error en tu programa impide que se ejecute la sentencia close(), puede que el archivo nunca se cierre.

El método read() se utiliza para leer todo el contenido del archivo y almacenarlo como un string en contenido. Este método crea una línea en blanco extra al final de la salida. Si quieres eliminar la línea de blanco extra a la derecha, puedes utilizar rstrip() en la sentencia print.

print(contenido.rstrip())
3.141592653589793238462643383279502884

Lectura línea por línea.

Cuando esté leyendo un archivo, a menudo vamos a querer examinar cada línea del archivo para modificarlo o buscar algo.

with open('digitos_pi.txt') as obj:
    for linea in obj:
        print(linea)
3.141592653589793238462643383279502884

Aparecerán líneas en blanco en la salida porque hay un carácter de nueva línea invisible al final de cada línea del archivo de texto. Para eliminar estas líneas en blanco utilice rstrip() en la sentencia print: print(contents.rstrip())

Hacer una lista de líneas de un fichero

Si quieres conservar el acceso al contenido de un archivo fuera del bloque with, puedes almacenar las líneas del archivo en una lista. Después de leer un fichero en memoria, puede hacer lo que quiera con esos datos.

with open('digitos_pi.txt') as obj:
    texto = obj.readlines()
for linea in texto:
    print(linea.rstrip())
3.141592653589793238462643383279502884

el método readlines() toma cada línea del archivo y la almacena en una lista. A continuación, esta lista se almacena la variable texto.

Escribir en un archivo

Una de las formas más sencillas de guardar datos es escribirlos en un archivo. Al escribir texto en un archivo, la salida seguirá estando disponible después de cerrar el terminal que contiene la salida del programa.

Escribir en un archivo vacío

Para escribir texto en un archivo, se debe llamar a open() con un segundo argumento que indique a Python que deseas escribir en el archivo.

archivo = 'programacion.txt'
with open(archivo, 'w') as obj:
    obj.write("Amo programar. \n")
    obj.write("Me gusta andar en bicicleta. \n")

Si nos fijamos, veremos que aparecerá un nuevo archivo llamado programación.txt con el texto que le habíamos proporcionado. También puedes utilizar espacios, caracteres de tabulación y líneas en blanco para dar formato a la salida.

Añadir a un archivo

Si quieres añadir contenido a un archivo en lugar de escribir sobre el contenido existente, puedes abrir un archivo en modo append. Las nuevas líneas que escribas en el archivo se añadirán al final del mismo. Si el archivo aún no existe, Python creará un archivo vacío.

with open(archivo, 'a') as obj:
    obj.write('También me gusta entender como funcionan los computadores\n')
    obj.write('Y trabajar con grandes bases de datos\n')

Utilizamos el argumento "a" para abrir un archivo en modo appending.

Excepciones

Python utiliza objetos especiales llamados excepciones para gestionar los errores que surgen durante la ejecución de un programa. Cada vez que se produce un error que hace que Python no sepa qué hacer a continuación, crea un objeto de excepción. Esto evita que un programa se cierre. Las excepciones se manejan con bloques try-except. Un bloque try-except pide a Python que haga algo, pero también le dice qué hacer si caemos en una excepción.

Manejo de la excepción ZeroDivisionError

Por supuesto que no puedes dividir números por cero pero si lo haces de todas formas se produce una excepción.

3/0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[142], line 1
----> 1 3/0

ZeroDivisionError: division by zero

El error del que se informa en el traceback es el ZeroDivisionError. Este es un objeto de excepción y hay muchos otros. Para atraparlo utilizamos un bloque try-except.

try:
    3/0
except ZeroDivisionError:
    print('¡No puedes dividir por cero!')
¡No puedes dividir por cero!

Si el bloque try funciona Python se salta el bloque except, pero si el código en el bloque try causa un error, Python busca un bloque except cuyo error coincida con el que se planteó y ejecuta el código en ese bloque. Si hubiera más código después del bloque try-except, el programa continuaría ejecutándose porque le dijimos a Python cómo manejar el error.

El bloque else

El bloque else va después del bloque except y se utiliza cuando queremos ejecutar algo en caso que try no haya dado ningún error.

try:
    division = 3/1
except ZeroDivisionError:
    print('¡No puedes dividir por cero!')
else:
    print('El resultado de la división es ', division)
El resultado de la división es  3.0

En este caso, si la operación de división tiene éxito, utilizamos el bloque else para imprimir el resultado.

Otras excepciones comunes

Exception Descripción
IndexError Se gatilla cuando un índice de secuencia está fuera de rango.
NameError Se gatilla cuando no se encuentra una variable local o global.
OverflowError Se gatilla cuando el resultado de una operación aritmética es demasiado grande para ser representado.
RuntimeError Se gatilla cuando se detecta un error que no cae en ninguna categoría específica.
SyntaxError Se gatilla cuando el analizador encuentra un error de sintaxis.
FileNotFoundError Se gatilla cuando no se encuentra un archivo.


Análisis de textos

Puede analizar archivos de texto que contengan libros enteros. Puedes encontrar muchos gratis en distintos formatos en el Proyecto Gutenberg.

Para contar el número de palabras de un texto es importante separarlo primero con el método split(). Este método permite separar un string en partes siempre que encuentra un espacio y almacena todas las partes del string en una lista. Por ejemplo:

titulo = 'Alicia en el país de las maravillas'
titulo.split()
['Alicia', 'en', 'el', 'país', 'de', 'las', 'maravillas']

A continuación, contaremos los elementos de la lista para hacernos una idea aproximada del número de palabras del libro "Alicia en el país de las maravillas".

archivo = 'alice.txt'
try:
    with open(archivo, encoding="utf8") as obj:
        contenido = obj.read()
except FileNotFoundError:
    mensaje = "Disculpa, el archivo " + archivo + " no existe."
    print(mensaje)
else:
    palabras = contenido.split()
    num_palabras = len(palabras)
print("El archivo " + archivo + " contiene alrededor de " + str(num_palabras) + " palabras.")
El archivo alice.txt contiene alrededor de 17868 palabras.

Trabajar con varios archivos

Es una buena idea encerrar el código anterior en una función llamada contar_palabras(nombre_archivo) para aceptar diferentes tipos de libros. Podemos hacer un bucle a través de la variable nombre_archivo y analizar uno por uno.

def contar_palabras(archivo):
    try:
        with open(archivo, encoding="utf8") as obj:
            contenido = obj.read()
    except FileNotFoundError:
        mensaje = "Disculpa, el archivo " + archivo + " no existe."
        print(mensaje)
    else:
        palabras = contenido.split()
        num_palabras = len(palabras)
    print("El archivo " + archivo + " contiene alrededor de " + str(num_palabras) + " palabras.")
archivos = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for archivo in archivos:
    contar_palabras(archivo)
El archivo alice.txt contiene alrededor de 17868 palabras.
Disculpa, el archivo siddhartha.txt no existe.
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[148], line 3
        1 archivos = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
        2 for archivo in archivos:
----> 3     contar_palabras(archivo)

Cell In[147], line 11, in contar_palabras(archivo)
        9     palabras = contenido.split()
        10     num_palabras = len(palabras)
---> 11 print("El archivo " + archivo + " contiene alrededor de " + str(num_palabras) + " palabras.")

UnboundLocalError: cannot access local variable 'num_palabras' where it is not associated with a value

Fracasar en silencio

En general, se informa de cualquier excepción que se captura, pero para otros contextos es posible que desee que el programa falle silenciosamente cuando se produce una excepción y continúe como si nada hubiera pasado. Python tiene una sentencia llamada pass que le dice que no haga nada en un bloque.

def contar_palabras(archivo):
    try:
        with open(archivo, encoding="utf8") as obj:
            contenido = obj.read()
    except FileNotFoundError:
        pass
    else:
        palabras = contenido.split()
        num_palabras = len(palabras)
    print("El archivo " + archivo + " contiene alrededor de " + str(num_palabras) + " palabras.")

Ahora, cuando se produce un error FileNotFoundError, el código del bloque except se ejecuta, pero no ocurre nada.

La sentencia pass también actúa como un marcador de posición. Es un recordatorio de que estás eligiendo no hacer nada en un punto específico de la ejecución de tu programa y que podrías querer hacer algo allí más tarde.

Almacenamiento de datos

Cuando los usuarios cierran un programa, casi siempre querrás guardar la información que han introducido. Una forma sencilla de hacerlo consiste en almacenar los datos utilizando el módulo json (Javascript Object Notation).

El módulo json te permite volcar estructuras de datos simples de Python en un archivo y cargar los datos desde ese archivo la próxima vez que se ejecute el programa. Puedes compartir datos en el formato JSON con muchas personas que utilizan diferentes lenguajes de programación: Es un formato útil y portátil, y es fácil de aprender.

Guardar información de un programa

El método json.dump() te permite guardar información del usuario para un posterior uso. Veamos un ejemplo a continuación:

import json

numeros = [2, 3, 5, 7, 11, 13]
nombre_archivo = 'numeros.json'
with open(nombre_archivo, 'w') as obj:
    json.dump(numeros, obj)

Primero, lo que debemos hacer es importar el modulo json. Este modulo tiene un método llamado dump que nos permite guardar información en formato json. En este caso, guardamos una lista de números que podremos usar más adelante en el programa. Este método está dentro de un with para ir guardando linea por linea y cuando termine de guardar todas las líneas, cerramos el archivo.

Descargar información de un programa

Ahora que hemos guardado información, podemos recuperarla gracias al método load de la librería json. En este caso, también utilizamos with para poder abrir el archivo json guardado anteriormente.

with open(nombre_archivo) as obj:
    numeros = json.load(obj)
print(numeros)
[2, 3, 5, 7, 11, 13]

Esta vez, cuando abrimos el archivo, lo abrimos en modo lectura porque Python sólo necesita leer del archivo. No especificamos el argumento "r" dentro de open(), porque es el valor que tiene open() por defecto, aunque igual lo podemos poner si asi lo queremos.

Utilizamos la función json.load() para cargar la información almacenada en numberos.json, y la almacenamos en la variable numeros para luego imprimirla.

Guardar y leer datos generados por el usuario

Guardar datos con json es útil cuando trabajas con datos generados por el usuario, porque si no almacenas la información de tu usuario de alguna manera, la perderás cuando el programa deje de ejecutarse.

usuario = input("¿Cuál es tu nombre? ")
archivo = 'usuario.json'
with open(archivo, 'w') as obj:
    json.dump(usuario, obj)
    print("Nos acordaremos de tu nombre cuando vuelvas, " + usuario + "!")
Nos acordaremos de tu nombre cuando vuelvas, Ignacio!

Ahora escribamos un nuevo programa que salude a un usuario cuyo nombre ya ha sido almacenado:

with open(archivo) as obj:
    usuario = json.load(obj)
print("Bienvenido de nuevo, " + usuario + "!")
Bienvenido de nuevo, Ignacio!

Recuperamos el nombre del usuario cargando la información almacenada en usuario.json con el método json.load.

Podemos combinar json.dump y json.load en un solo archivo para guardar y recuperar la información del usuario. Se recomienda utilizar el bloque try..except..else para atrapar las excepciones de tipo FileNotFoundError.

Refactorización

A menudo, llegarás a un punto en el que tu código funcionará, pero reconocerás que podrías mejorar el código dividiéndolo en una serie de funciones que tienen tareas específicas. Este proceso se llama refactorización y tiene que ver con no repetir tu código. Este concepto en inglés se llama DRY (Don't Repeat Yourself).

Probar el código

Todos los programadores cometen errores, por lo que deben probar su código a menudo para detectar problemas antes de que los usuarios los encuentren.

Probar una función

El módulo unittest de la biblioteca estándar de Python proporciona herramientas para probar el código. Una prueba unitaria verifica que un aspecto específico del comportamiento de una función es correcto. Un caso de prueba es una colección de pruebas unitarias, que juntas, prueban si una función se comporta como se supone que debe hacerlo.

Un caso de prueba con cobertura total incluye una gama completa de pruebas unitarias que cubren todas las formas posibles de utilizar una función.

Una prueba exitosa

La sintaxis para configurar un caso de prueba requiere un poco de tiempo para acostumbrarse, pero una vez que hayas configurado el caso de prueba es sencillo añadir más pruebas unitarias para tus funciones. Para escribir un caso de prueba para una función, importa el módulo unittest y la función que deseas probar.

import unittest

class MiPrimerTest(unittest.TestCase):
    """Clase donde podremos incorporar varias pruebas unitarias."""

    def test_nombre_completo(self):
        """¿Funcionará la función nombre_completo con el nombre Janis Joplin?"""
        formato_nombre = nombre_completo('Janis', 'Joplin')
        self.assertEqual(formato_nombre, 'Janis Joplin')


if __name__ == '__main__':
    unittest.main()
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Cualquier método que empiece por test_ se ejecutará automáticamente cuando ejecutemos test_nombre_funcion.py

Al final utilizamos una de las características más útiles de unittest: un método assert. Los métodos assert verifican que un resultado que has recibido coincide con el resultado que esperabas recibir. Cuando ejecutamos test_nombre_funcion.py, obtenemos la siguiente salida:

.
----------------------------------------------------------------------.
Ran 1 test in 0.000s

OK

El punto nos dice que la prueba pasó. La siguiente línea nos dice que Python ejecutó una prueba, y tardó menos de 0.001 segundos en ejecutarse. El OK final nos dice que todas las pruebas unitarias del caso de prueba se han superado.

Una prueba fallida

Imaginemos que llamamos a una función con dos argumentos y la función a la que llamamos tiene tres. Se trata de una prueba fallida.

class MiPrimerTest(unittest.TestCase):
    """Clase donde podremos incorporar varias pruebas unitarias."""

    def test_nombre_completo(self):
        """¿Funcionará la función nombre_completo con el nombre Janis Joplin?"""
        formato_nombre = nombre_completo('Janis', 'Joplin', 'Lyn')
        self.assertEqual(formato_nombre, 'Janis Joplin')

if __name__ == '__main__':
    unittest.main()
E
======================================================================
ERROR: test_nombre_completo (__main__.MiPrimerTest.test_nombre_completo)
¿Funcionará la función nombre_completo con el nombre Janis Joplin?
----------------------------------------------------------------------
Traceback (most recent call last):
    File "C:\Users\PC\AppData\Local\Temp\ipykernel_16132\646333358.py", line 6, in test_nombre_completo
    formato_nombre = nombre_completo('Janis', 'Joplin', 'Lyn')
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: nombre_completo() takes 2 positional arguments but 3 were given

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Esto muestra en qué parte del código se cometió este error, que es la función test_nombre_completo. También muestra cuántos errores encontró.

Cuando una prueba falla, no cambies la prueba. En su lugar, corrige el código que causó el fallo. Por ejemplo si añadimos un segundo nombre para el ejemplo anterior es mejor hacerlo opcional.

def nombre_completo(nombre, apellido, segundo_nombre=''):
    """Retorna un nombre completo, con un formato ordenado."""
    if segundo_nombre:
        nombre_completo = nombre + ' ' + segundo_nombre + ' ' + apellido
    else:
        nombre_completo = nombre + ' ' + apellido
    print(nombre_completo.title())

Arreglar nuestra función fue fácil porque la prueba fallida nos ayudó a identificar el nuevo código que rompía el comportamiento existente.

Probar una clase

La siguiente tabla describe seis métodos assert de uso común. Con estos métodos puede verificar que los valores devueltos son iguales o no a los valores esperados, que los valores son True o False, y que los valores están o no en una lista dada.

Sólo puedes utilizar estos métodos en una clase que herede unittest.TestCase

Método Uso
assertEqual(a, b) Verifica que a == b
assertNotEqual(a, b) Verifica que a != b
assertTrue(x) Verifica que x es Verdadero
assertFalse(x) Verifica que x es Falso
assertIn(item, lista) Verifica que item está en lista
assertNotIn(item, lista) Verifica que item no está en lista

Probar una clase es similar a probar una función: gran parte del trabajo consiste en comprobar el comportamiento de los métodos de la clase. Pero hay algunas diferencias.

class EncuestaAnonima():
    """Recopila respuestas anónimas a preguntas de una encuesta."""

    def __init__(self, pregunta):
        """Almacena una pregunta y se prepara para almacenar respuestas."""
        self.pregunta = pregunta
        self.respuestas = []

    def mostrar_pregunta(self):
        """Muestra la pregunta de la encuesta."""
        print( self.pregunta)

    def almacenar_respuesta(self, nueva_respuesta):
        """Almacena una sola respuesta a la encuesta."""
        self.respuestas.append(nueva_respuesta)

    def mostrar_resultados(self):
        """Muestra todas las respuestas que se han dado."""
        print("Resultados de la encuesta:")
        for respuesta in respuestas:
            print('- ' + respuesta)
class PruebaEncuestaAnonima(unittest.TestCase):
    """Pruebas para la clase EncuestaAnonima"""

    def prueba_almacenar_respuesta_individual(self):
        """Prueba que una única respuesta se almacene correctamente."""
        pregunta = "¿Qué idioma aprendiste primero al hablar?"
        mi_encuesta = EncuestaAnonima(pregunta)
        mi_encuesta.almacenar_respuesta('Español')

        self.assertIn('Español', mi_encuesta.respuestas)

if __name__ == '__main__':
    unittest.main()
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Esto está bien, pero una encuesta sólo es útil si genera más de una respuesta.

El método setUp()

Para añadir diferentes respuestas podemos utilizar un método setUp(). Cualquier objeto creado en el método setUp() estará disponible en cada método de prueba que escriba.

class PruebaEncuestaAnonima(unittest.TestCase):   
    def configurar(self):
        """Crear una encuesta y un conjunto de respuestas para usar en todos los métodos de prueba."""
        
        pregunta = "¿En qué idioma aprendiste a hablar primero?"
        self.mi_encuesta = EncuestaAnonima(pregunta)
        self.respuestas = ['Inglés', 'Español', 'Mandarín']
        
    def prueba_almacenar_respuesta_individual(self):
        """Probar que una respuesta individual se almacene correctamente."""
        self.mi_encuesta.almacenar_respuesta(self.respuestas[0])
        self.assertIn(self.respuestas[0], self.mi_encuesta.respuestas)

    def prueba_almacenar_tres_respuestas_individuales(self):
        """Probar que tres respuestas individuales se almacenen correctamente."""
        for respuesta in self.respuestas:
            self.mi_encuesta.almacenar_respuesta(respuesta)
        for respuesta in self.respuestas:
            self.assertIn(respuesta, self.mi_encuesta.respuestas)
        
if __name__ == '__main__':
    unittest.main()
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

El método setUp() hace dos cosas: crea una instancia de encuesta y crea una lista de respuestas. Cada uno de ellos lleva el prefijo self, por lo que puede utilizarse en cualquier parte de la clase. Esto hace que los dos métodos de prueba sean más sencillos, porque ninguno tiene que crear una instancia de encuesta o una respuesta.

Estas pruebas serían particularmente útiles cuando se trata de ampliar PruebaEncuestaAnonima() para manejar múltiples respuestas para cada persona. Esto es mucho más fácil que hacer un nuevo conjunto de instancias y atributos en cada método de prueba.

Nota: Cuando se ejecuta un caso de prueba, Python imprime un carácter por cada prueba unitaria que se completa. Una prueba que pasa imprime un punto, una prueba que resulta en un error imprime una E, y una prueba que resulta en una aserción fallida imprime una F.

Si has llegado hasta acá te agradezco mucho por haberte tomado el tiempo de leer mi trabajo. Puedes ver otros blogs y proyectos interesantes, donde hablo de finanzas, economia y programación. Espero haberte servido de ayuda y que tengas un excelente porvenir con tus futuros proyectos.



Deja un comentario