Guía Completa de Python para Principiantes
Guía Completa de Python para Principiantes
El libro está dividido en 9 secciones. Empieza explicando las herramientas y después las pone en
práctica.
• 1 Presentación
• 7 Testea tu Código
FAQ
No, el libro está diseñado para que puedas leerlo sin saber Python.
No es necesario tener conocimientos avanzados de programación pero si una base mínima. Saber que
es una variable, un condicional o un bucle.
Introducción a Python
• 📗📗 ¿Qué es Python?
• 📗📗 Sintaxis Básica
• 📗📗 Nombrando variables I
• 📙📙 Palabras reservadas
• 📙📙 Alcance variables
• 📙📙 Ejecutando scripts
• 📙📙 Funciones built-in
• 📙📙 Unpacking en Python
Si has llegado hasta aquí es posible que quieras aprender Python, o tal vez simplemente tengas
curiosidad por saber algo más de este lenguaje de programación tan popular. En este post te
intentaremos convencer de porqué debes aprender Python, sin importar en que trabajes o cual sea tu
sector.
Aprovechamos de paso a darte la bienvenida a nuestro libro, un lugar donde podrás encontrar tutoriales
de Python completamente gratis desde lo más básico a conceptos avanzados, y con diferentes
aplicaciones: análisis de datos, automatización de tareas, web scraping, machine learning, data
science, desarrollo de videojuegos, interfaces gráficos o finanzas.
Dicho esto, empecemos. A diferencia de lo que mucha gente puede pensar, Python es un lenguaje que
data de los años 1990s, y su creación se le atribuye al neerlandés Guido van Rossum[1]. Recibió su
nombre por los humoristas Monty Python.
Su última versión es Python 3, y es la que te recomendamos usar ya que las anteriores ya no tienen
soporte oficial. En este blog usaremos esta siempre para todo.
De acuerdo con StackOverflow insights[2] en la siguiente gráfica podemos ver el número de preguntas
vistas en la plataforma acerca de Pyhton. Podemos ver que Python lleva casi dos años en el podio, una
auténtica burrada.
De hecho un estudio realizado hace un tiempo en el 2017[3] ya ponía a Python como uno de los favoritos
en los países con mayores ingresos como Alemania, Reino Unido o Estados Unidos entre otros.
Python was the most visited tag on Stack Overflow within high-income nations
Podemos ver por lo tanto que existe una tendencia clara y seguramente el interés en Python vaya a seguir
creciendo en los próximos años. Estos son los motivos por lo que ha crecido tanto y lo seguirá haciendo:
• Se trata de un lenguaje fácil de aprender, con una sintaxis muy sencilla que se asemeja bastante
al pseudocódigo. En otras palabras, poco código hace mucho.
• Su uso no está ligado a un sector concreto. Por ejemplo el lenguaje R es útil para análisis de
datos, pero no puede ser usado para desarrollo web. Python vale para todo.
• Tiene una comunidad enorme, además de gran cantidad de librerías para hacer prácticamente
cualquier cosa, literalmente.
• Por lo general se puede hacer desarrollos en Python más rápidamente que en otros lenguajes,
acortando la duración de los proyectos.
Usos de Python
Como hemos dicho Python es un lenguaje muy transversal, usado en diferentes industrias y para
diferentes fines. Veamos algunos de las empresas que usan Python:
• YouTube usa Python[4] en la parte del servidor, unida a otros lenguajes como Java o Go.
• Netflix usa Python[5] par automatizar tareas, explorar datos y labores de aprendizaje automático
entre otras.
• JPMorgan[7] ya dijo hace varios años que se esperaba de sus analistas financieros supieran
Python.
Python es también usado para fines muy diversos como son los siguientes:
• Desarrollo Web: Existen frameworks como Django, Pyramid, Flask o Bottle que permiten
desarrollar páginas web a todos los niveles.
• Ciencia y Educación: Debido a su sintaxis tan sencilla, es una herramienta perfecta para enseñar
conceptos de programación a todos los niveles. En lo relativo a ciencia y cálculo numérico,
existen gran cantidad de librerías como SciPy o Pandas.
• Desarrollo de Interfaces Gráficos: Gran cantidad de los programas que utilizamos tienen un
interfaz gráfico que facilita su uso. Python también puede ser usado para desarrollar GUIs con
librerías como Kivy o pyqt.
• Desarrollo Software: También es usado como soporte para desarrolladores, como para testing.
• Visualización de Datos: Existen varias librerías muy usadas para mostrar datos en gráficas,
como matplotlib, seaborn o plotly.
• Finanzas y Trading: Gracias a librerías como QuantLib o qtpylib y a su facilidad de uso, es cada
vez más usado en estos sectores.
De hecho a día de hoy prácticamente cualquier API, librería o servicio que existe en el mundo tiene una
versión para Python, bien sea de manera nativa o a través de un wrapper.
Comunidad
La comunidad de Python es inmensa, con alrededor de 8.2 millones de personas en el mundo (a fecha
de 2019)[8], una cifra que supera ya a los usuarios de Java.
Es importante el tamaño de la comunidad, porque cuanto más grande sea, mayor soporte se le dará al
lenguaje y mayor número de personas compartirán sus problemas y se ayudarán a resolverlos.
Otra de las características de la comunidad Python son las famosas PyCon, unas convenciones anuales
llevadas a cabo en gran número de países, donde los desarrolladores se reúnen para compartir ideas.
Es también importante mencionar la Python Software Foundation, una organización sin ánimo de lucro
que se dedica a promover, proteger y desarrollar el lenguaje Python.
Características de Python
Como cualquier otro lenguaje, Python tiene una serie de características que lo hacen diferente al resto.
Las explicamos a continuación:
• Usa tipado dinámico, lo que significa que una variable puede tomar valores de distinto tipo.
• Es fuertemente tipado, lo que significa que el tipo no cambia de manera repentina. Para que se
produzca un cambio de tipo tiene que hacer una conversión explícita.
Tal vez algunos de estos conceptos puedan resultarte extraños si estás empezando en el mundo de la
programación. El siguiente código pretende ilustrar algunas de las características de Python.
def funcion(entrada):
return entrada/2
x = "Hola"
x = 7.0
x = int(x)
x = funcion(x)
print(x)
print(type(x))
# 3.5
# <class 'float'>
El Zen de Python es una colección de los 19 principios que influyen en el diseño del lenguaje. De alguna
manera, muestran la filosofía del mismo y pueden ser encontrados en la PEP20[9] Las PEP o Python
Enhancement Prososals son unos documentos que ofrecen información a la comunidad de Python, bien
describiendo alguna característica nueva o dando información en general.
Son las siguientes, y aunque alguna pueda parecer lógica, a veces resultan no serlo tanto cuando no se
cumplen:
7. La legibilidad es importante.
8. Los casos especiales no son lo suficientemente especiales como para romper las reglas.
13. Debería haber una, y preferiblemente solo una, manera obvia de hacerlo.
14. A pesar de que esa manera no sea obvia a menos que seas Holandés (el creador lo era)
16. A pesar de que nunca es muchas veces mejor que ahora mismo.
19. Los namespaces son una gran idea, ¡tengamos más de esos!
¿Qué opinas? ¿Te hemos convencido para aprender Python? A continuación te explicaremos cómo
instalar Python para que puedas empezar a dar tus primeros pasos.
Si quieres empezar a programar en Python, en este post te damos dos alternativas de como puedes
empezar a hacerlo:
• La primera es usar Python sin ningún tipo de instalación. Sin duda la más sencilla y rápida. Para
ello usaremos la versión online de JupyterLab.
• Y la segunda es usando Python con PyCharm, para lo que tendremos que instalar el propio
lenguaje Python y el entorno de desarrollo PyCharm.
¡Empecemos!
Si quieres empezar a programar en Python de manera rápida sin tener que instalar nada, existe una
alternativa muy útil llamada JupyterLab. Se trata de una herramienta desarrollada por Jupyter, un
proyecto open-source sin ánimo de lucro que nació en 2014 del proyecto IPython.
JupyterLab es un entorno de desarrollo web (se accede a el a través de Firefox, Chrome u otro navegador)
y además de poder instalártelo en tu ordenador, ofrecen un servicio online gratis de usar. Con tan sólo
entrar en una dirección web puedes empezar a programar.
En estos ficheros ipynb puedes escribir código Python y ejecutarlo, además de poder mezclarlo con
texto, imágenes, animaciones y otras herramientas.
• Permiten ejecutar código fragmento a fragmento, viendo el resultado justo en la siguiente línea.
• Se puede mezclar código con otros recursos como imágenes o texto con formato markdown,
entre otros recursos.
• Si usamos la versión web, estaremos limitados por los recursos del servidor en el que se ejecute.
La rapidez será mayor en nuestro ordenador.
Por lo tanto es una herramienta perfecta para empezar, pero si crees que necesitas más, te explicamos
como instalar Python y el entorno de desarrollo PyCharm en la siguientes secciones.
Si por lo contrario buscas algo más completo, deberás instalar Python y un entorno de desarrollo (IDE) en
tu ordenador. Necesitarás dos cosas:
• Por un lado necesitarás Python, es decir, el propio lenguaje de programación. Con esto y
cualquier editor de texto ya podrías programar, pero no es demasiado agradable.
• Por otro, es conveniente también instalar un entorno de desarrollo, ya que hace que programar
sea una tarea mucho más fácil. Existe muchos, como Atom, Sublime Text o Visual Studio Code,
pero nosotros usaremos PyCharm.
Para instalar Python en Windows debes ir a la sección de descargas de la web oficial y seleccionar la
última versión. Te recomendamos utilizar la versión 3.x ya que aunque también existen versiones
anteriores como las 2.x, Python ya ha dejado de dar soporte a ellas.
Ambas versiones son relativamente similares, pero hay detalles o alguna que otra funcionalidad que
varía. En este blog nos centramos en la versión 3, por lo que todo el código que veas será compatible con
la misma.
Una vez hayas descargado el ejecutable, ábrelo y realiza la instalación. Es importante que verifiques que
se haya seleccionado la opción de “Add Python 3.x to PATH”
Una vez hayas finalizado, si abres el terminal de comandos de Windows (busca por la aplicación cmd o
símbolo de sistema) puedes verificar que se ha instalado correctamente ejecutando el siguiente
comando.
python -V
Python 3.8.3
De hecho como hemos indicado, con esto ya podrías empezar a programar en Python en tu ordenador,
pero la verdad que no es demasiado cómodo. En el último apartado te explicaremos como instalar
PyCharm, un entorno de desarrollo que nos hará la vida mucho más fácil.
Método 1
Existen dos formas diferentes de instalar Python en mac. Empezamos por la más sencilla. Accede a la
sección de descargas de la web oficial de Python y descarga la última versión. Te recomendamos usar la
versión 3, que es la que usamos en todo este blog y la más reciente.
Una vez lo hayas instalado y hayas terminado el proceso de instalación, puedes abrir el terminal y
verificar que efectivamente se ha instalado con el siguiente comando.
python -V
Y si el comando devuelve algo así como Python 3.6.8, ya estaría instalado. Si tecleas python en el
terminal ya podrías empezar a teclear comandos Python, pero no es muy cómodo. Más adelante te
explicaremos como usar Python con PyCharm.
Método 2
La segunda forma de instalar Python en mac es a través del gestor de paquetes homebrew. Primero
necesitarás instalar XCode con el siguiente comando.
xcode-select --install
Una vez instalado XCode deberás instalar Homebrew con el siguiente comando.
Una vez instalado Homebrew ya podemos instalar Python a través de su gestor de paquetes.
Simplemente ejecuta.
python3 -V
apt-get update
Y una vez finalizada la instalación puedes comprobar que se ha instalado correctamente con el siguiente
comando.
python -V
python3 -V
Instalando PyCharm
Llegados a este punto debemos ya tener instalado Python en nuestro ordenador, por lo que vamos a
proceder ya a instalar PyCharm.
Antes de nada, una breve introducción a PyCharm. Se trata de un entorno de desarrollo o IDE (Integrated
Development Environment) de la empresa JetBrains. Está disponible en las plataformas más comunes
como Windows, Linux o macOS y es una herramienta perfecta para escribir código en Python.
Para instalar PyCharm, accede a la sección de descargas y selecciona la versión Community, que es la
versión gratis de desarrollo. Una vez el proceso de instalación haya acabado, deberías ver algo así al
abrirlo.
A continuación explicaremos como crear y configurar un proyecto, para que puedas empezar a
programar en Python dentro del IDE PyCharm.
Configurando PyCharm
Una de las características más útiles en PyCharm es que se pueden instalar paquetes de manera muy
sencilla a través de su interfaz gráfico. Pongamos que por ejemplo quisieras instalar la librería numpy. Ve
a Preferencias, Proyecto y en Proyect Interpreter podrás añadir librerías con haciendo click en el+.
En cualquier introducción a un nuevo lenguaje de programación, no puede faltar el famoso Hola Mundo.
Se trata del primer programa por el que se empieza, que consiste en programar una aplicación que
muestra por pantalla ese texto. Si ejecutas el siguiente código, habrás cumplido el primer hito de la
programación en Python.
print("Hola Mundo")
Por lo tanto ya te puedes imaginar que la función print() sirve para imprimir valores por pantalla.
Imprimirá todo lo que haya dentro de los paréntesis. Fácil ¿verdad? A diferencia de otros lenguajes de
programación, en Python se puede hacer en 1 línea.
Vamos a complicar un poco más las cosas. Creemos una variable que almacene un número. A diferencia
de otros lenguajes de programación, no es necesario decirle a Python el tipo de dato que queremos
almacenar en x. En otros lenguajes es necesario especificar que x almacenará un valor entero, pero no
es el caso. Python es muy listo y al ver el número 5, sabrá de que tipo tiene que ser la x.
x=5
Ahora podemos juntar el print() que hemos visto con la x que hemos definido, para en vez de imprimir
el Hola Mundo, imprimir el valor de la x.
print(x)
# Salida: 5
En el anterior fragmento habrás visto el uso #. Se trata de la forma que tiene Python de crear los
denominados comentarios. Un comentario es un texto que acompaña al código, pero que no es código
propiamente dicho. Se usa para realizar anotaciones sobre el código, que puedan resultar útiles a otras
personas. En nuestro caso, simplemente lo hemos usado para decirte que la salida de ese comando
será 5, ya que x valía 5.
Vamos a sumar dos variables e imprimir su valor. Lo primero vamos a declararlas, con nombres a y b.
Declarar una variable significa “crearla”.
a=3
b=7
Ahora Python ya conoce a y b y sus respectivos valores. Podemos hacer uso de + para sumarlos, y una
vez más de print() para mostrar su valor por pantalla.
print(a+b)
Es importante que sólo usemos variables que hayan sido definidas, porque de lo contrario tendremos un
error. Si hacemos:
print(z)
Tendremos un error porque Python no sabe que es z, ya que no ha sido declarada con anterioridad.
Ejemplo condicional
Podemos empezar a complicar un poco más las cosas con el uso de una sentencia condicional. Te lo
explicamos más adelante en este post sobre el if.
El siguiente código hace uso del if para comprobar si la a es igual == a 10. Si lo es, se imprimirá “Es 10” y
si no lo es “No es 10”. Es importante el uso de ==, que es el operador relacional que veremos en otros
posts.
a = 10
if a == 10:
print("Es 10")
else:
print("No es 10")
Decimales y cadenas
De la misma forma que hemos visto que una variable puede almacenar un valor entero como 10, es
posible también almacenar otros tipos como decimales o incluso cadenas de texto.
Si queremos almacenar un valor decimal, basta con indicarlo usando la separación con .
valor_decimal = 10.3234
Y si queremos almacenar una cadena, es necesario indicar su contenido entre comillas simples 'o
dobles ".
Esperamos que te haya resultado útil esta introducción, y con ella ya estas list@ para continuar al
siguiente tutorial, donde veremos más acerca de la sintaxis de Python.
Sintaxis Python
A continuación veremos la sintaxis de Python, viendo como podemos empezar a usar el lenguaje
creando nuestras primeras variables y estructuras de control.
El termino sintaxis hace referencia al conjunto de reglas que definen como se tiene que escribir el
código en un determinado lenguaje de programación. Es decir, hace referencia a la forma en la que
debemos escribir las instrucciones para que el ordenador, o más bien lenguaje de programación, nos
entienda.
En la mayoría de lenguajes existe una sintaxis común, como por ejemplo el uso de = para asignar un dato
a una variable, o el uso de {} para designar bloques de código, pero Python tiene ciertas particularidades.
La sintaxis es a la programación lo que la gramática es a los idiomas. De la misma forma que la frase
“Yo estamos aquí” no es correcta, el siguiente código en Python no sería correcto, ya que no respeta las
normas del lenguaje.
if ($variable){
x=9;
Lo veremos a continuación en detalle, pero Python no soporta el uso de $ ni hace falta terminar las líneas
con ; como en otros lenguajes, y tampoco hay que usar {} en estructuras de control como en el if.
Por otro lado, de la misma forma que un idioma no se habla con simplemente saber todas sus palabras,
en la programación no basta con saber la sintaxis de un lenguaje para programar correctamente en él. Es
cierto que sabiendo la sintaxis podremos empezar a programar y a hacer lo que queramos, pero el uso
de un lenguaje de programación va mucho más allá de la sintaxis.
Para empezar a perderle el miedo a la sintaxis de Python, vamos a ver un ejemplo donde
vemos cadenas, operadores aritméticos y el uso del condicional if.
El siguiente código simplemente define tres valores a, b y c, realiza unas operaciones con ellos y muestra
el resultado por pantalla.
a, b, c = 4, 3, 2
d = (a + b) * c
imprimir = True
# Si imprimir, print()
if imprimir:
print(x, d)
Como puedes observar, la sintaxis de Python es muy parecida al lenguaje natural o pseudocódigo, lo que
hace que sea relativamente fácil de leer. Otra ventaja es que no necesitamos nada más, el código
anterior puede ser ejecutado tal cual está. Si conoces otros lenguajes como C o Java, esto te resultará
cómodo, ya que no es necesario crear la típica función main().
Comentarios
Los comentarios son bloques de texto usados para comentar el código. Es decir, para ofrecer a otros
programadores o a nuestro yo futuro información relevante acerca del código que está escrito. A efectos
prácticos, para Python es como si no existieran, ya que no son código propiamente dicho, solo
anotaciones.
Los comentarios se inician con # y todo lo que vaya después en la misma línea será considerado un
comentario.
# Esto es un comentario
Al igual que en otros lenguajes de programación, podemos también comentar varias líneas de código.
Para ello es necesario hacer uso de triples comillas bien sean simples ''' o dobles """. Es necesario
usarlas para abrir el bloque del comentario y para cerrarlo.
'''
Esto es un comentario
de varias líneas
de código
'''
En Python los bloques de código se representan con indentación, y aunque hay un poco de debate con
respecto a usar tabulador o espacios, la norma general es usar cuatro espacios.
En el siguiente código tenemos un condicional if. Justo después tenemos un print() indentado con cuatro
espacios. Por lo tanto, todo lo que tenga esa indentación pertenecerá al bloque del if.
if True:
print("True")
Esto es muy importante ya que el código anterior y el siguiente no son lo mismo. De hecho el siguiente
código daría un error ya que el if no contiene ningún bloque de código, y eso es algo que no se puede
hacer en Python.
if True:
print("True")
Por otro lado, a diferencia de en otros lenguajes de programación, no es necesario utilizar ; para terminar
cada línea.
x = 10;
x=5
y = 10
Pero se puede usar el punto y coma ; para tener dos sentencias en la misma línea.
x = 5; y = 10
Múltiples líneas
En algunas situaciones se puede dar el caso de que queramos tener una sola instrucción en varias línea
de código. Uno de los motivos principales podría ser que fuera demasiado larga, y de hecho en la
especificación PEP8 se recomienda que las líneas no excedan los 79 caracteres.
Haciendo uso de \ se puede romper el código en varias líneas, lo que en determinados casos hace que
el código sea mucho más legible.
x = 1 + 2 + 3 + 4 +\
5+6+7+8
Si por lo contrario estamos dentro de un bloque rodeado con paréntesis (), bastaría con saltar a la
siguiente línea.
x = (1 + 2 + 3 + 4 +
5 + 6 + 7 + 8)
return a+b+c
d = funcion(10,
23,
3)
Creando variables
Anteriormente ya hemos visto como crear una variable y asignarle un valor con el uso de =. Existen
también otras formas de hacerlo de una manera un poco más sofisticada.
Podemos por ejemplo asignar el mismo valor a diferentes variables con el siguiente código.
x = y = z = 10
x, y = 4, 2
x, y, z = 1, 2, 3
Nombrando variables
Puedes nombrar a tus variables como quieras, pero es importante saber que las mayúsculas y
minúsculas son importantes. Las variables x y X son distintas.
# Válido
_variable = 10
vari_able = 20
variable10 = 30
variable = 60
variaBle = 10
# No válido
2variable = 10
var-iable = 10
var iable = 10
Una última condición para nombrar a una variable en Python, es no usar nombres reservados para
Python. Las palabras reservadas son utilizadas por Python internamente, por lo que no podemos usarlas
para nuestras variables o funciones.
import keyword
print(keyword.kwlist)
De hecho con el siguiente comando puedes ver todas las palabras clave que no puedes usar.
import keyword
print(keyword.kwlist)
Uso de paréntesis
Python soporta todos los operadores matemáticos más comunes, conocidos como operadores
aritméticos. Por lo tanto podemos realizar sumas, restas, multiplicaciones, exponentes (usando **) y
otros que no vamos a explicar por ahora. En el siguiente ejemplo realizamos varias operaciones en la
misma línea, y almacenamos su resultado en y.
x = 10
y = x*3-3**10-2+3
Pero el comportamiento del código anterior y el siguiente es distinto, ya que el uso de paréntesis () da
prioridad a unas operaciones sobre otras.
x = 10
y = (x*3-3)**(10-2)+3
El uso de paréntesis no solo se aplica a los operadores aritméticos, sino que también pueden ser
aplicados a otros operadores como los relacionales o de membresía que vemos en otros posts.
Variables y alcance
Un concepto muy importante cuando definimos una variable, es saber el alcance o scope que tiene. En
el siguiente ejemplo la variable con valor 10 tiene un alcance global y la que tiene el valor 5 dentro de la
función, tiene un alcance local. Esto significa que cuando hacemos print(x), estamos accediendo a la
variable global x y no a la x definida dentro de la función.
x = 10
def funcion():
x=5
funcion()
print(x)
Por último, en cualquier lenguaje de programación es importante saber lo que va pasando a medida que
se ejecutan las diferentes instrucciones. Por ello, es interesante hacer uso de print() en diferentes
secciones del código, ya que nos permiten ver el valor de las variables y diferente información útil.
Existen muchas formas de usar la función print() y te las explicamos en detalle en este post, pero por
ahora basta con que sepas lo básico.
Como ya hemos visto se puede usar print() para imprimir por pantalla el texto que queramos.
x = 10
print(x)
Y separando por comas , los valores, es posible imprimir el texto y el contenido de variables.
x = 10
y = 20
Nombrando variables
Crear variables
Las variables en Python se pueden crear asignando un valor a un nombre sin necesidad de declararla
antes.
x = 10
y = "Nombre"
z = 3.9
Nombres de variables
Podemos asignar el nombre que queramos, respetando no usar las palabras reservadas de Python ni
espacios, guiones o números al principio.
# Válido
_variable = 10
vari_able = 20
variable10 = 30
variable = 60
variaBle = 10
# No válido
2variable = 10
var-iable = 10
var iable = 10
x, y, z = 10, 20, 30
Imprimir variables
x = 10
y = "Nombre"
print(x)
print(y)
Python tiene un conjunto de palabras reservadas que no podemos utilizar para nombrar variables ni
funciones, ya que las reserva internamente para su funcionamiento.
Por ejemplo, no podemos llamar a una función True, y si intentamos hacerlo, tendremos un SyntaxError.
Esto es lógico ya que Python usa internamente True para representar el tipo booleano.
def True():
pass
Análogamente, no podemos llamar a una variable is ya que se trata del operador de identidad.
is = 4
Resulta lógico que no se nos permita realizar esto, ya que de ser posible, podríamos romper el lenguaje.
Algo muy importante a tener en cuenta es que palabras como list no están reservadas, y esto es algo que
puede generar problemas. El siguiente código crea una lista usando la función estándar de Python list().
a = list("letras")
print(a)
Sin embargo, y aunque pueda parece extraño, podemos crear una función con ese nombre. Al hacer
esto, nos estamos cargando la función list() de Python, y por lo tanto al intentar hacer la llamada anterior
falla, ya que nuestra función en este caso no acepta argumentos. Mucho cuidado con esto.
def list():
print("Funcion list")
a = list("letras")
Pero volviendo a las palabras reservadas, Python nos ofrece una forma de acceder a estas
palabras programmatically, es decir, a través de código. Aquí tenemos un listado con todas las palabras
reservadas.
import keyword
print(keyword.kwlist)
Vistas ya las palabras reservadas de Python, a continuación explicaremos para que sirve cada una de
ellas y las pondremos en contexto.
En el siguiente ejemplo podemos ver su uso. De los tres bloques, sólo se ejecutará uno de ellos, el cual
cumpla la condición establecida sobre lenguaje.
lenguaje = "Python"
if lenguaje == "Python":
else:
El while y for permiten crear bucles que ejecutan una sección de código repetidas veces. Por otro lado
el continue y el break permiten realizar alteraciones sobre el bucle.
x=0
while x < 3:
print(x)
x += 1
# Salida: 0, 1, 2
El for permite iterar clases iterables, ejecutando la sección de código tantas veces como elementos
tiene el iterable.
for i in range(3):
print(i)
# Salida: 0, 1, 2
El continue salta hasta el final del bloque, dejando sin ejecutar lo restante, pero continúa en la
siguiente iteración.
for i in range(3):
if i == 1:
continue
print(i)
# Salida: 0, 2
Por último, el break rompe la ejecución del bucle, saliendo del mismo.
x=0
while True:
print(x)
if x == 2:
break
x += 1
# Salida: 0, 1, 2
False, True y None son valores que pueden ser asignados a variables, siendo los dos
primeros booleanos y el último algo parecido al null de otros lenguajes de programación.
Si realizamos una comparación usando el operador relacional == se nos devolverá True o False.
x = (5 == 1)
print(x)
# Salida: False
x = True
if x:
print("Python!")
# Salida: Python!
Por otro lado None se devuelve por defecto cuando una función no cuenta con un return.
def mi_funcion():
pass
print(mi_funcion())
# Salida: None
El and, or y not son operadores lógicos que actúan sobre valores booleanos. El primero es verdadero si y
solo si todos los operandos son verdaderos. El segundo devuelve verdadero si al menos un elemento es
verdadero. Y por último, el not invierte verdadero por falso y viceversa.
Todas ellas relacionadas con las funciones. El uso de def nos permite crear una función.
funcion_suma(3, 5)
# Salida: La suma es 8
Si queremos que la función devuelva uno o varios valores, podemos usar return.
return a + b
suma = funcion_suma(3, 5)
El uso de lambda nos permite crear funciones lambda, una especie de funciones “para vagos”. Dichas
funciones no tienen un nombre per se, salvo asignado explícitamente.
# Salida: La suma es 8
Por otro lado, podemos usar pass cuando no queramos definir la función, es decir si la queremos dejar
en blanco por el momento. Nótese que también puede ser usado en clases, estructuras de control, etc.
pass
Por último, yield está asociado a los generadores y las corrutinas, un concepto un tanto avanzado pero
muy interesante. En el siguiente generador vemos como se generan tres valores, obteniendo uno cada
vez que iteramos el generador.
def generador():
n=1
yield n
n += 1
yield n
n += 1
yield n
for i in generador():
print(i)
# Salida: 1, 2, 3
Los generadores pueden ser usados para generar secuencias infinitas de valores, sin que tengan que ser
almacenados a priori, siendo creados bajo demanda. Este es una utilidad muy importante trabajando
con listas muy grandes, cuyo almacenamiento en memoria sería muy costoso.
Clases: class
El uso de class nos permite crear clases. Las clases son el núcleo de la programación orientada objetos,
y son una especie de estructura de datos que agrupa un conjunto de funciones (métodos) y variables
(atributos).
class MiClase:
def __init__(self):
objeto = MiClase()
Las palabras clave assert, try, except, finally y raise están relacionadas con las excepciones, y nos
permiten tratar el qué hacer cuando las cosas no salen como esperamos. El siguiente código intenta
hacer un cast de cadena a entero, manejando un posible error.
• Si x="10" el casteo se realiza sin problemas, ya que es posible representar esa cadena como un
entero. Sin embargo hay que estar preparados siempre para lo peor.
• Si x="a" no se podría hacer int() y tendríamos un error. Si no manejamos este error, el programa se
pararía, y esto no es algo deseable. El uso de try, except y finally nos permite controlar dicho error
y actuar en consecuencia sin que el programa se pare.
x = "10"
valor = None
try:
valor = int(x)
except Exception as e:
print("Hubo un error:", e)
finally:
# Salida: El valor es 10
a=0
def suma_uno():
global a
a=a+1
suma_uno()
print(a)
# Salida: 1
El uso de nonlocal es útil cuando tenemos funciones anidadas. En el siguiente ejemplo podemos ver
como cuando funcion_b modifica x, también afecta a la x de la funcion_a, ya que la hemos declarado
como nonlocal. Te invitamos a que elimines el nonlocal y veas el comportamiento.
def funcion_a():
x = 10
def funcion_b():
nonlocal x
x = 20
print("funcion_b", x)
funcion_b()
print("funcion_a", x)
funcion_a()
# Salida:
# funcion_b 20
# funcion_a 20
Sin nonlocal, Python trataría x dentro de funcion_b como una variable local a esa función, y el valor
de x en funcion_a no se modificaría.
El uso de from e import nos permite importar módulos o librerías, tanto estándar de Python como
externas o definidas por nosotros. En ejemplos como este es donde podemos ver que la sintaxis de
Python se asemeja bastante al lenguaje natural: de collections importa namedtuple.
El uso de in nos permite saber si un determinado elemento está en una clase iterable,
devolviendo True en el caso de que sea cierto.
print("a" in lista)
# Salida: True
El uso de is nos permite saber si dos variables apuntan en realidad al mismo objeto. Por debajo se usa la
función id() y es importante notar que la igualdad == no implica que is sea True.
a = [1, 2]
b = [1, 2]
c=a
print(a is b) # False
print(a is c) # True
El uso de del nos permite eliminar una variable del scope, pudiendo resultar útil cuando trabajamos con
variables que almacenan gran cantidad de datos. Es una manera explícita de indicar que ya no queremos
una variable, pero no olvidemos que Python tiene garbage collector.
a = 10
del a
print(a)
El uso de with y as es muy utilizado a la hora de manejar ficheros, pero en realidad pertenecen a
los context managers o gestores de contexto, un concepto algo avanzado.
print(file.read())
El uso de async y await nos permite ejecutar procesos de manera concurrente en vez de secuencial.
Imaginemos un proceso() que tarda 10 segundos en ejecutarse, ya que realiza una petición a una base
de datos que lo bloquea durante ese tiempo. Sin esta herramienta, si quisiéramos ejecutar 3 veces el
proceso tardaríamos 30 segundos, ya que por defecto se ejecutan de manera secuencial, hasta que uno
no acaba no pasamos al siguiente.
Sin embargo, creando una función async y usando await, podemos paralelizar la ejecución de los
procesos, aprovechando el tiempo “muerto” mientras se retorna al await. En el siguiente ejemplo
podemos ver como se tarda unos 10 segundos en ejecutar los 3 procesos.
import asyncio
await asyncio.sleep(10)
asyncio.run(main())
# Salida:
# Empieza proceso: 1
# Empieza proceso: 2
# Empieza proceso: 3
# Acaba proceso: 1
# Acaba proceso: 2
# Acaba proceso: 3
Python clasifica los alcances de acuerdo con el modelo LEGB, que determina el orden en que se buscan
las variables. Las siglas LEGB corresponden a:
Local: Variables definidas dentro de una función o bloque, accesibles únicamente dentro de ese
contexto. Se crean al inicio de la ejecución de la función y desaparecen cuando esta termina.
def saludo():
print(mensaje)
saludo()
En este caso, la variable mensaje se define y utiliza dentro de la función saludo. Fuera de esta función, la
variable no existe y no se puede acceder.
Enclosing: Se refiere a las variables definidas en una función exterior que encapsula otra función. Estas
variables no son locales para la función más interna, pero tampoco son globales.
def funcion_exterior():
def funcion_interior():
funcion_interior()
funcion_exterior()
def imprimir_mensaje():
imprimir_mensaje()
En este caso, mensaje se define fuera de cualquier función(/funciones-python), por lo que es global y
puede ser utilizada tanto dentro como fuera de las funciones(/funciones-python).
Built-in: Variables y funciones predefinidas de Python que están disponibles en cualquier parte del
código, como len(), range() y print().
from math import sqrt # 'sqrt' es una función built-in del módulo 'math'
numero = 16
En este caso, utilizamos una función(/funciones-python) built-in del módulo estándar math, que amplía
las funcionalidades predefinidas de Python.
En este ejemplo vemos como trabajar con ficheros usando os. Vemos como:
• � Crear ficheros
� Veamos como crear varios ficheros. Los creamos vacíos, pero puedes usar cambiar el write para
añadir contenido si lo deseas.
if not os.path.exists(ruta):
os.makedirs(ruta)
for i in range(cantidad):
fichero.write('')
print(f"Creado: {archivo_ruta}")
� Veamos como modificar el nombre de los ficheros. Esta función añade un prefijo al nombre de cada
fichero en la ruta. Es decir:
if os.path.isfile(archivo_ruta):
os.rename(archivo_ruta, nueva_ruta)
renombra_ficheros("./ejemplo", "prefijo_")
� Veamos como eliminar ficheros y carpetas. Esta función elimina todos los ficheros de ruta.
def elimina_ficheros(ruta):
os.remove(os.path.join(root, file))
os.rmdir(ruta)
elimina_ficheros("./ejemplo")
� Ejercicios:
• En vez de añadir prefijo_ a todos los ficheros, modifica la función para añadir un número que
indique su orden alfabético. Por ejemplo para ejemplo.txt y archivo.txt el prefijo a añadir
sería 1_archivo.txt y 2_ejemplo.txt. La a va antes que la e alfabéticamente.
Veamos como trabajar con bases de datos para persistir nuestra información en el disco y que pueda ser
usada una vez nuestro código ha terminado. Sin algún tipo de persistencia, toda la información que tu
programa sabe es olvidada una vez este acaba.
Hay muchas formas de hacerlo. Puedes almacenar la información en un simple fichero. O puedes usar
bases de datos relacionales y lenguajes como SQL.
En este ejemplo usaremos sqlite3 para almacenar nuestros gastos mensuales en una base de
datos gastos.db. Se trata de un paquete muy sencillo de utilizar y a diferencia de otros no requiere un
servidor externo.
Empezamos creando la base de datos y una tabla llamada gastos con dos campos:
Como puedes ver exigimos que en ambos casos el campo no puede ser NULL. Es decir, que no lo puedes
dejar vacío. Todo el contenido de nuestra base de datos se almacenará en el fichero gastos.db.
import sqlite3
def conecta_db():
return sqlite3.connect('gastos.db')
def crea_tabla():
conn.execute('''
)''')
conn.execute('''
También una función para obtener los gastos. Esta función consulta la base de datos y nos devuelve los
gastos que han sido almacenados. También puedes usar categoria para filtrar los gastos por categoría. Si
no lo usas, se devolverán todos los gastos.
def get_gastos(categoria=None):
cursor = conn.cursor()
if categoria:
condiciones.append("categoria = ?")
parametros.append(categoria)
return cursor.fetchall()
Ahora vamos a crear nuestros gastos. Creamos la tabla y usamos la función add_gasto para añadir
nuestros gastos.
crea_tabla()
add_gasto("Transporte", 5)
add_gasto("Comida", 5)
add_gasto("Comida", 7)
add_gasto("Alquiler", 300)
Comprobamos que han sido almacenados correctamente. Deberíamos obtener los mismos que hemos
introducido.
gastos = get_gastos()
print(gasto)
gastos = get_gastos(categoria="Comida")
print(gasto)
Como puedes ver tus gastos se persisten en el disco duro aunque tu código haya terminado.
� Ejercicios:
• Añade a la tabla gastos de tu base de datos un campo llamado fecha, que almacene cuando se
realizó el gasto.
• Añade a get_gastos la posibilidad de filtrar por fecha. Por ejemplo, los gastos de un mes concreto.
• Añade todas las operaciones CRUD sobre la base de datos. Las operaciones CRUD son Create,
Remove, Update y Delete. Tenemos add_gasto y get_gastos. Añade remove_gasto y update_gasto.
Patrones con re
Veamos cómo validar si una dirección de correo está bien escrita. Aunque es algo que el ser humano ve
en un golpe de vista, validar esto en programación es un poco más complicado.
Para resolver este problema usaremos las expresiones regulares o regular expressions. Estas nos
permiten buscar patrones en texto. Este ejemplo nos permite detectar números en una cadena. Como
puedes ver se detectó el 2 y el 3.
import re
También podemos reemplazar texto. En este caso reemplazamos 2 y 3 por dos y tres respectivamente.
print(r2)
Pero también validar que un texto cumple con unas normas. Por ejemplo, sabemos que un correo
electrónico como usuario@gmail.comestá compuesto por:
• � Un nombre de usuario.
• � Seguido de @.
Podemos por tanto escribir una expresión que nos diga si una dirección de correo es válida o no de la
siguiente manera.
def valida_email(email):
usuario = r"[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+"
arroba = r"@"
dominio = r"[a-zA-Z0-9-]+"
tld = r"(?:\.[a-zA-Z0-9-]+)*"
email_regex = rf"^{usuario}{arroba}{dominio}{tld}$"
print(valida_email("juan@gmail.com.ar")) # True
print(valida_email("653fsaasd")) # False
print(valida_email("juan@gma@.dom.com")) # False
print(valida_email("@@@")) # False
• � tld: Requiere que empiece con un . y permitimos múltiples. Es decir, .com y co.uk son válidos.
� Ejercicios:
En este ejemplo vamos a representar un mapa de calor o heatmap desde donde se iniciaron más pases
en el partido de fútbol España-Inglaterra del 14 de julio de 2024.
Primero obtenemos los datos usando el match_id de ese partido. Después filtramos los datos para
obtener el origen de los pases que realizó Inglaterra a lo largo del partido.
ev = sb.events(match_id=3943043)
ev = ev.loc[
Llegados aquí, en ev tenemos todos los eventos relativos a pases de Inglaterra. Pero lo más importante
para el ejemplo es:
Ahora con seaborn representamos el heatmap de donde se iniciaron los pases. Esto nos permite
hacernos una idea de los puntos calientes desde donde se inician los pases. Podemos ver que el portero
inició muchos saques.
plt.figure(figsize=(8, 4))
plt.subplot(1, 2, 1)
ax1 = sns.histplot(
x=ev['location'].str[0],
y=ev['location'].str[1],
ax1.set_aspect('equal', 'box')
plt.title('España/Inglaterra 14/07/2024\n'
Realizamos lo mismo con los puntos donde se finalizaron los pases. Como puedes ver
usamos str[0] y str[1]. Estas son las coordenadas x e y en el campo.
plt.subplot(1, 2, 2)
ax2 = sns.histplot(
x=ev['pass_end_location'].str[0],
y=ev['pass_end_location'].str[1],
ax2.set_aspect('equal', 'box')
plt.title('España/Inglaterra 14/07/2024\n'
'Heatmap fin de pases')
plt.tight_layout()
plt.show()
� Ejercicios:
• Realiza la misma representación para España y compáralo con el heatmap que acabamos de ver
de Inglaterra.
Antes de entrar a explicar las estructuras de control, vamos a ponernos un poco en contexto.
Un código es una secuencia de instrucciones, que por norma general son ejecutadas una tras otra.
Podemos verlo como una receta de cocina, en la que tenemos unos pasos a seguir. Empezamos por el
principio, vamos realizando cada tarea, y una vez hemos llegado al final, hemos terminado. Nuestra
receta en código podría ser la siguiente:
poner_agua_hervir()
echar_arroz_hervir()
cortar_pollo()
freir_pollo()
mezclar_pollo_arroz()
Sin embargo, en muchas ocasiones no basta con ejecutar las instrucciones una tras otra desde el
principio hasta llegar al final.
Puede ser que ciertas instrucciones se tengan que ejecutar si y sólo si se cumple una determinada
condición. ¿Que pasa si nuestro comensal es vegetariano? No hay problema, podemos usar el
condicional if. Si no es vegetariano usamos pollo, de lo contrario zanahoria.
poner_agua_hervir()
echar_arroz_hervir()
if not vegetariano:
cortar_pollo()
freir_pollo()
mezclar_pollo_arroz()
else:
cortar_zanahoria()
freir_zanahoria()
mezclar_zanahoria_arroz()
Por otro lado, la receta que teníamos era para una persona. ¿Qué pasa si queremos cocinar para 3?
¿Tenemos que escribir el código repetido 3 veces?
poner_agua_hervir()
echar_arroz_hervir()
cortar_pollo()
freir_pollo()
mezclar_pollo_arroz()
poner_agua_hervir()
echar_arroz_hervir()
cortar_pollo()
freir_pollo()
mezclar_pollo_arroz()
# Y para la tercera
poner_agua_hervir()
echar_arroz_hervir()
cortar_pollo()
freir_pollo()
mezclar_pollo_arroz()
Pero ¿y si queremos para 100? Te puedes ya imaginar que repetir el código tantas veces no parece ser la
mejor idea. Es aquí donde entran en juego el for y el while.
Estas estructuras de control nos permiten repetir un determinado bloque de código tantas veces como
queramos.
for i in range(100):
# Cocinamos la receta
poner_agua_hervir()
echar_arroz_hervir()
cortar_pollo()
freir_pollo()
mezclar_pollo_arroz()
Como puedes ver, el código anterior haría lo mismo que copiar y pegar la receta 100 veces, pero es
mucho más compacto y elegante.
Sabido esto, ya estas en condiciones de empezar a leer este capítulo, donde aprenderás básico
conceptos como el if/else/for/while y también algo más avanzados, como lo son los iteradores, clases
iterables y uso del break/continue/try.
• 📗📗 Condicional if
• 📗📗 Bucle for
• 📗📗 Range
• 📗📗 Bucle while
• 📙📙 Switch
• 📙📙 Match
• 📙📙 Break
• 📙📙 Continue
• 📙📙 List comprehensions
• 📕📕 Iteradores e Iterables
Condicional en Python
De no ser por las estructuras de control, el código en cualquier lenguaje de programación sería
ejecutado secuencialmente hasta terminar. Un código, no deja de ser un conjunto de instrucciones que
son ejecutadas unas tras otra. Gracias a las estructuras de control, podemos cambiar el flujo de
ejecución de un programa, haciendo que ciertos bloques de código se ejecuten si y solo si se dan unas
condiciones particulares.
Uso del if
Un ejemplo sería si tenemos dos valores a y b que queremos dividir. Antes de entrar en el bloque de
código que divide a/b, sería importante verificar que b es distinto de cero, ya que la división por cero no
está definida. Es aquí donde entran los condicionales if.
a=4
b=2
if b != 0:
print(a/b)
En este ejemplo podemos ver como se puede usar un if en Python. Con el operador != se comprueba que
el número b sea distinto de cero, y si lo es, se ejecuta el código que está indentado. Por lo tanto
un if tiene dos partes:
• La condición que se tiene que cumplir para que el bloque de código se ejecute, en nuestro
caso b!=0.
Es muy importante tener en cuenta que la sentencia if debe ir terminada por : y el bloque de código a
ejecutar debe estar indentado. Si usas algún editor de código, seguramente la indentación se producirá
automáticamente al presionar enter. Nótese que el bloque de código puede también contener más de
una línea, es decir puede contener más de una instrucción.
if b != 0:
c = a/b
d=c+1
print(d)
Todo lo que vaya después del if y esté indentado, será parte del bloque de código que se ejecutará si la
condición se cumple. Por lo tanto el segundo print() “Fuera if” será ejecutado siempre, ya que está fuera
del bloque if.
if b != 0:
c = a/b
print("Dentro if")
print("Fuera if")
Existen otros operadores que se verán en otros capítulos, como el de comparar si un número es mayor
que otro. Su uso es igual que el anterior.
if b > 0:
print(a/b)
Se puede también combinar varias condiciones entre el if y los :. Por ejemplo, se puede requerir que un
número sea mayor que 5 y además menor que 15. Tenemos en realidad tres operadores usados
conjuntamente, que serán evaluados por separado hasta devolver el resultado final, que será True si la
condición se cumple o False de lo contrario.
a = 10
Es muy importante tener en cuenta que a diferencia de en otros lenguajes, en Python no puede haber un
bloque if vacío. El siguiente código daría un SyntaxError.
if a > 5:
Por lo tanto si tenemos un if sin contenido, tal vez porque sea una tarea pendiente que estamos dejando
para implementar en un futuro, es necesario hacer uso de pass para evitar el error. Realmente pass no
hace nada, simplemente es para tener contento al interprete de código.
if a > 5:
pass
Algo no demasiado recomendable pero que es posible, es poner todo el bloque que va dentro del if en la
misma línea, justo a continuación de los :. Si el bloque de código no es muy largo, puede ser útil para
ahorrarse alguna línea de código.
Si tu bloque de código tiene más de una línea, se pueden poner también en la misma línea separándolas
con ;.
Es posible que no solo queramos hacer algo si una determinada condición se cumple, sino que además
queramos hacer algo de lo contrario. Es aquí donde entra la cláusula else. La parte del if se comporta de
la manera que ya hemos explicado, con la diferencia que si esa condición no se cumple, se ejecutará el
código presente dentro del else. Nótese que ambos bloque de código son excluyentes, se entra o en uno
o en otro, pero nunca se ejecutarán los dos.
x=5
if x == 5:
print("Es 5")
else:
print("No es 5")
Hasta ahora hemos visto como ejecutar un bloque de código si se cumple una instrucción, u otro si no
se cumple, pero no es suficiente. En muchos casos, podemos tener varias condiciones diferentes y para
cada una queremos un código distinto. Es aquí donde entra en juego el elif.
x=5
if x == 5:
print("Es 5")
elif x == 6:
print("Es 6")
elif x == 7:
print("Es 7")
Con la cláusula elif podemos ejecutar tantos bloques de código distintos como queramos según la
condición. Traducido al lenguaje natural, sería algo así como decir: si es igual a 5 haz esto, si es igual a 6
haz lo otro, si es igual a 7 haz lo otro.
Se puede usar también de manera conjunta todo, el if con el elif y un else al final. Es muy importante
notar que if y else solamente puede haber uno, mientras que elif puede haber varios.
x=5
if x == 5:
print("Es 5")
elif x == 6:
print("Es 6")
elif x == 7:
print("Es 7")
else:
print("Es otro")
Si vienes de otros lenguajes de programación, sabrás que el switch es una forma alternativa de elif, sin
embargo en Python esta cláusula no existe.
Operador ternario
El operador ternario o ternary operator es una herramienta muy potente que muchos lenguajes de
programación tienen. En Python es un poco distinto a lo que sería en C, pero el concepto es el mismo. Se
trata de una cláusula if, else que se define en una sola línea y puede ser usado por ejemplo, dentro de
un print().
x=5
#Es 5
Existen tres partes en un operador ternario, que son exactamente iguales a los que había en un if else.
Tenemos la condición a evaluar, el código que se ejecuta si se cumple, y el código que se ejecuta si no se
cumple. En este caso, tenemos los tres en la misma línea.
Es muy útil y permite ahorrarse algunas líneas de código, además de aumentar la rapidez a la que
escribimos. Si por ejemplo tenemos una variable a la que queremos asignar un valor en función de una
condición, se puede hacer de la siguiente manera. Siguiendo el ejemplo anterior, en el siguiente código
intentamos dividir a entre b. Si b es diferente a cero, se realiza la división y se almacena en c, de lo
contrario se almacena -1. Ese -1 podría ser una forma de indicar que ha habido un error con la división.
a = 10
b=5
print(c)
#2
Ejemplos if
x=6
if x % 2 == 0:
print("Es par")
else:
print("Es impar")
# Decrementa x en 1 unidad si es mayor que cero
x=5
x -= 1 if x > 0 else x
print(x)
Bucle for
A continuación explicaremos el bucle for y sus particularidades en Python, que comparado con otros
lenguajes de comparación, tiene ciertas diferencias.
El for es un tipo de bucle, parecido al while pero con ciertas diferencias. La principal es que el número de
iteraciones de un for esta definido de antemano, mientras que en un while no. La diferencia principal
con respecto al while es en la condición. Mientras que en el while la condición era evaluada en cada
iteración para decidir si volver a ejecutar o no el código, en el for no existe tal condición, sino
un iterable que define las veces que se ejecutará el código. En el siguiente ejemplo vemos un
bucle for que se ejecuta 5 veces, y donde la i incrementa su valor “automáticamente” en 1 en cada
iteración.
print(i)
# Salida:
#0
#1
#2
#3
#4
Si has leído el capítulo del while, tal vez ya empieces a ver ventajas en el uso del for. Si por ejemplo,
queremos tener un número que va creciendo de 0 a n, hacerlo con for nos ahorra alguna línea de código,
porque no tenemos que escribir código para incrementar el número.
En Python se puede iterar prácticamente todo, como por ejemplo una cadena. En el siguiente ejemplo
vemos como la i va tomando los valores de cada letra. Mas adelante explicaremos que es esto de
los iterables e iteradores.
for i in "Python":
print(i)
# Salida:
#P
#y
#t
#h
#o
#n
Iterables e iteradores
Para entender al cien por cien los bucles for, y como Python fue diseñado como lenguaje de
programación, es muy importante entender los conceptos de iterables e iteradores. Empecemos con un
par de definiciones:
• Los iterables son aquellos objetos que como su nombre indica pueden ser iterados, lo que dicho
de otra forma es, que puedan ser indexados. Si piensas en un array (o una list en Python),
podemos indexarlo con lista[1] por ejemplo, por lo que sería un iterable.
• Los iteradores son objetos que hacen referencia a un elemento, y que tienen un método next que
permite hacer referencia al siguiente.
Para saber más: Si quieres saber más sobre los iteradores te dejamos este enlace a la documentación
oficial.
Ambos son conceptos un tanto abstractos y que pueden ser complicados de entender. Veamos unos
ejemplos. Como hemos comentado, los iterables son objetos que pueden ser iterados o accedidos con
un índice. Algunos ejemplos de iterables en Python son las listas, tuplas, cadenas o diccionarios.
Sabiendo esto, lo primero que tenemos que tener claro es que en un for, lo que va después del in deberá
ser siempre un iterable.
# <Código>
Tiene bastante sentido, porque si queremos iterar una variable, esta variable debe ser iterable, todo muy
lógico. Pero llegados a este punto, tal vez de preguntes ¿pero cómo se yo si algo es iterable o no?. Bien
fácil, con la siguiente función isinstance() podemos saberlo. No te preocupes si no entiendes muy bien lo
que estamos haciendo, fíjate solo en el resultado, True significa que es iterable y False que no lo es.
lista = [1, 2, 3, 4]
cadena = "Python"
numero = 10
print(isinstance(lista, Iterable)) #True
Por lo tanto las listas y las cadenas son iterables, pero numero, que es un entero no lo es. Es por eso por
lo que no podemos hacer lo siguiente, ya que daría un error. De hecho el error sería TypeError: int' object
is not iterable.
numero = 10
#for i in numero:
# print(i)
Una vez entendidos los iterables, veamos los iteradores. Para entender los iteradores, es importante
conocer la función iter() en Python. Dicha función puede ser llamada sobre un objeto que sea iterable, y
nos devolverá un iterador como se ve en el siguiente ejemplo.
lista = [5, 6, 3, 2]
it = iter(lista)
Vemos que al imprimir it es un iterador, de la clase list_iterator. Esta variable iteradora, hace referencia a
la lista original y nos permite acceder a sus elementos con la función next(). Cada vez que llamamos
a next() sobre it, nos devuelve el siguiente elemento de la lista original. Por lo tanto, si queremos acceder
al elemento 4, tendremos que llamar 4 veces a next(). Nótese que el iterador empieza apuntando fuera
de la lista, y no hace referencia al primer elemento hasta que no se llama a next() por primera vez.
lista = [5, 6, 3, 2]
it = iter(lista)
print(next(it))
# [5, 6, 3, 2]
# ^
# |
# it
print(next(it))
# [5, 6, 3, 2]
# ^
# |
# it
print(next(it))
# [5, 6, 3, 2]
# ^
# |
# it
Dado que el iterador hace referencia a nuestra lista, si llamamos más veces a next() que la longitud de la
lista, se nos devolverá un error StopIteration. Lamentablemente no existe ninguna opción de volver al
elemento anterior.
lista = [5, 6]
it = iter(lista)
print(next(it))
print(next(it))
Es perfectamente posible tener diferentes iteradores para la misma lista, y serán totalmente
independientes. Tan solo dependerán de la lista, como es evidente, pero no entre ellos.
lista = [5, 6, 7]
it1 = iter(lista)
it2 = iter(lista)
print(next(it1)) #5
print(next(it1)) #6
print(next(it1)) #7
print(next(it2)) #5
For anidados
Es posible anidar los for, es decir, meter uno dentro de otro. Esto puede ser muy útil si queremos iterar
algún objeto que en cada elemento, tiene a su vez otra clase iterable. Podemos tener por ejemplo, una
lista de listas, una especie de matriz.
[12, 4, 5],
[9, 4, 3]]
Si iteramos usando sólo un for, estaremos realmente accediendo a la segunda lista, pero no a los
elementos individuales.
for i in lista:
print(i)
#[56, 34, 1]
#[12, 4, 5]
#[9, 4, 3]
Si queremos acceder a cada elemento individualmente, podemos anidar dos for. Uno de ellos se
encargará de iterar las columnas y el otro las filas.
for i in lista:
for j in i:
print(j)
# Salida: 56,34,1,12,4,5,9,4,3
Ejemplos for
Iterando cadena al revés. Haciendo uso de [::-1] se puede iterar la lista desde el último al primer
elemento.
texto = "Python"
for i in texto[::-1]:
print(i) #n,o,h,t,y,P
Itera la cadena saltándose elementos. Con [::2] vamos tomando un elemento si y otro no.
texto = "Python"
for i in texto[::2]:
print(i) #P,t,o
Un ejemplo de for usado con comprehensions lists.
# Salida: 45
Range en Python
Uno de las iteraciones mas comunes que se realizan, es la de iterar un número entre por ejemplo 0 y n. Si
ya programas, estoy seguro de que estas cansado de escribir esto, aunque sea en otro lenguaje.
Pongamos que queremos iterar una variable i de 0 a 5. Haciendo uso de lo que hemos visto
anteriormente, podríamos hacer lo siguiente.
print(i) #0, 1, 2, 3, 4, 5
Se trata de una solución que cumple con nuestro requisito. El contenido después del in se trata de una
clase que como ya hemos visto antes, es iterable, y es de hecho una tupla. Sin embargo, hay otras
formas de hacer esto en Python, haciendo uso del range().
for i in range(6):
print(i) #0, 1, 2, 3, 4, 5
El range() genera una secuencia de números que van desde 0 por defecto hasta el número que se pasa
como parámetro menos 1. En realidad, se pueden pasar hasta tres parámetros separados por coma,
donde el primer es el inicio de la secuencia, el segundo el final y el tercero el salto que se desea entre
números. Por defecto se empieza en 0 y el salto es de 1.
Por lo tanto, si llamamos a range() con (5,20,2), se generarán números de 5 a 20 de dos en dos. Un truco
es que el range() se puede convertir en list.
print(i) #5,7,9,11,13,15,17,19
Se pueden generar también secuencias inversas, empezando por un número mayor y terminando en uno
menor, pero para ello el salto deberá ser negativo.
print(i) #5,4,3,2,1
Bucles while
Anteriormente hemos visto el uso del if y el for para modificar el flujo de ejecución del código. A
continuación vemos otra forma de hacerlo con el while.
While
El uso del while nos permite ejecutar una sección de código repetidas veces, de ahí su nombre. El
código se ejecutará mientras una condición determinada se cumpla. Cuando se deje de cumplir, se
saldrá del bucle y se continuará la ejecución normal. Llamaremos iteración a una ejecución completa
del bloque de código.
Cabe destacar que existe dos tipos de bucles, los que tienen un número de iteraciones no definidas, y
los que tienen un número de iteraciones definidas. El while estaría dentro del primer tipo. Mas adelante
veremos los for, que se engloban en el segundo.
x=5
while x > 0:
x -=1
print(x)
# Salida: 4,3,2,1,0
En el ejemplo anterior tenemos un caso sencillo de while. Tenemos una condición x>0 y un bloque de
código a ejecutar mientras dure esa condición x-=1 y print(x). Por lo tanto mientras que x sea mayor que
0, se ejecutará el código. Una vez se llega al final, se vuelve a empezar y si la condición se cumple, se
ejecuta otra vez. En este caso se entra al bloque de código 5 veces, hasta que en la sexta, x vale cero y
por lo tanto la condición ya no se cumple. Por lo tanto el while tiene dos partes:
Ten cuidado ya que un mal uso del while puede dar lugar a bucles infinitos y problemas. Cierto es que en
algún caso tal vez nos interese tener un bucle infinito, pero salvo que estemos seguros de lo que
estamos haciendo, hay que tener cuidado. Imaginemos que tenemos un bucle cuya condición siempre
se cumple. Por ejemplo, si ponemos True en la condición del while, siempre que se evalúe esa expresión,
el resultado será True y se ejecutará el bloque de código. Una vez llegado al final del bloque, se volverá a
evaluar la condición, se cumplirá, y vuelta a empezar. No te recomiendo que ejecutes el siguiente
código, pero puedes intentarlo.
while True:
print("Bucle infinito")
Es posible tener un while en una sola línea, algo muy útil si el bloque que queremos ejecutar es corto. En
el caso de tener mas de una sentencia, las debemos separar con ;.
x=5
También podemos usar otro tipo de operación dentro del while, como la que se muestra a continuación.
En este caso tenemos una lista que mientras no este vacía, vamos eliminando su primer elemento.
while x:
x.pop(0)
print(x)
#['Dos', 'Tres']
#['Tres']
#[]
Else y while
x=5
while x > 0:
x -=1
print(x) #4,3,2,1,0
else:
Podemos ver como si el bucle termina por el break, el print() no se ejecutará. Por lo tanto, se podría decir
que si no hay realmente ninguna sentencia break dentro del bucle, tal vez no tenga mucho sentido el uso
del else, ya que un bloque de código fuera del bucle cumplirá con la misma funcionalidad.
x=5
while True:
x -= 1
print(x) #4, 3, 2, 1, 0
if x == 0:
break
else:
# El print no se ejecuta
Bucles anidados
Ya hemos visto que los bucles while tienen una condición a evaluar y un bloque de código a ejecutar.
Hemos visto ejemplos donde el bloque de código son operaciones sencillas como la resta -, pero
podemos complicar un poco mas las cosas y meter otro bucle while dentro del primero. Es algo que
resulta especialmente útil si por ejemplo queremos generar permutaciones de números, es decir, si
queremos generar todas las combinaciones posibles. Imaginemos que queremos generar todas las
combinaciones de de dos números hasta 2. Es decir, 0-0, 0-1, 0-2,… hasta 2-2.
# Permutación a generar
i=0
j=0
while i < 3:
while j < 3:
print(i,j)
j += 1
i += 1
j=0
Vamos a analizara el ejemplo paso por paso. El primer bucle genera números del 0 al 2, lo que
corresponde a la variable i. Por otro lado el segundo bucle genera también número del 0 al 2,
almacenados en la variable j. Al tener un bucle dentro de otro, lo que pasa es que por cada i se generan
3 j. Muy importante no olvidar que al finalizar el bucle de la j, debemos resetear j=0 para que en la
siguiente iteración la condición de entrada se cumpla.
Podemos complicar las cosas aún más y tener tres bucles anidados, generando combinaciones de 3
elementos con número 0, 1, 2. En este caso tendremos desde 0,0,0 hasta 2,2,2.
i, j, k = 0, 0, 0
while i < 3:
while j < 3:
while k < 3:
print(i,j,k)
k += 1
j += 1
k=0
i += 1
j=0
Ejemplos while
Árbol de navidad en Python. Imprime un árbol de navidad formado con * haciendo uso del while y de la
multiplicación de un entero por una cadena, cuyo resultado en Python es replicar la cadena.
z=7
x=1
while z > 0:
x+=2
z-=1
# *
# ***
# *****
# *******
# *********
# ***********
Aunque esta no sea tal vez la mejor forma de iterar una cadena es un buen ejemplo para el uso
del while e introducir el indexado de listas con [], que veremos en otros capítulos.
text = "Python"
i=0
print(text[:i + 1])
i += 1
#P
# Py
# Pyt
# Pyth
# Pytho
# Python
a, b = 0, 1
print(b)
a, b = b, a + b
#1, 1, 2, 3, 5, 8, 13, 21
Switch en Python
El switch es una herramienta que nos permite ejecutar diferentes secciones de código dependiendo de
una condición. Su funcionalidad es similar a usar varios if, pero por desgracia Python no tiene un
switch propiamente dicho. Sin embargo, hay formas de simular su comportamiento que te explicamos a
continuación. También ofrece el match, algo parecido que se introdujo en Python 3.10.
Introducción al switch
Ya sabemos que el uso del if junto con else y elif nos permite ejecutar un código determinado
dependiendo de una condicion, como podemos ver en el siguiente código.
if condicion == 1:
print("Haz a")
elif condicion == 2:
print("Haz b")
elif condicion == 3:
print("Haz c")
else:
print("Haz d")
La misma funcionalidad se podría escribir de la siguiente manera haciendo uso del switch. Como
puedes ver su uso tal vez resulte algo más limpio, y de hecho en determinadas ocasiones es más rápido.
// Ojo, esto no es Python
switch(condicion) {
case 1:
// haz a
break;
case 2:
// haz b
break;
case 3:
// haz c
break;
default:
// haz x
Pero tenemos un pequeño problema. En Python no existe el switch, por lo que si intentas ejecutar el
código anterior, tendrás un error.
Uno tal vez podría decir: bueno, que más da, uso if/elif/else y ya esta. La verdad que en la mayoría de los
casos, sería indiferente usar if o switch, pero si analizamos el comportamiento que existe por debajo,
funcionan de manera distinta. A pesar de que en Python no existe, te damos un truco que puede en
cierto modo emular su funcionamiento.
Diferencia if y switch
Una de las principales diferencias, es que usando if con elif, no todos los bloques tienen el mismo
tiempo de acceso. Todas las condiciones van siendo evaluadas hasta que se cumple y se sale.
Imaginemos que tenemos 100 condiciones.
if condicion == 1:
print("1")
elif condicion == 2:
print("2")
else:
print("x")
• Si es 70: Se va evaluando cada condición, hasta llegar al 70. Es decir, tienen que evaluarse 70
condiciones.
Sin embargo, en el switch todos los elementos tienen el mismo tiempo de acceso. Esto se debe a que
por debajo esta normalmente implementado con lookup tables.
Si trabajamos con un gran número de condiciones, el uso del switch sobre el if podría notarse.
Dado que en Python no tenemos esta herramienta, te explicamos un truco para simularlo. No obstante,
si realmente te preocupan estas micro optimizaciones, tal vez no deberías usar Python.
Una forma de tener una especie de switch en Python es haciendo uso de un diccionario. Por lo tanto
podríamos convertir el siguiente código.
if operador == 'suma':
return a + b
return a - b
return a * b
return a / b
else:
return None
En el siguiente:
return {
'suma': lambda: a + b,
'resta': lambda: a - b,
'multiplica': lambda: a * b,
'divide': lambda: a / b
Es importante notar que opera2 necesita de () para realizar la llamada a la función, ya que lo que se
devuelve en realidad es una función lambda.
opera1('suma', 5, 9)
# Salida: 14
opera2('suma', 5, 9)()
# Salida: 14
Como hemos indicado anteriormente, usar switch en vez de if puede ser más rápido. Aunque Python no
nos ofrece un switch propiamente dicho, lo podemos implementar con diccionarios. A continuación
veremos varios experimentos donde analizaremos el tiempo de ejecución de dos opciones distintas.
Empecemos con un problema a resolver. Imaginemos que queremos convertir números de decimal a
binario. Aunque esta no es la forma correcta de hacerlo, usaremos este ejemplo con fines didácticos.
Empecemos por definir las dos formas de hacerlo.
def usa_if(decimal):
if decimal == '0':
return "000"
return "001"
return "010"
return "011"
return "101"
return "110"
return "111"
else:
return "NA"
tabla_switch = {
'0': '000',
'1': '001',
'2': '010',
'3': '011',
'4': '100',
'5': '101',
'6': '110',
'7': '111',
def usa_switch(decimal):
Ambas funciones usa_if y usa_switch realizan lo mismo, pero están implementadas de manera distinta.
A continuación mediremos el tiempo de ejecución de ambas para saber cuál es más rápida. Vamos a
crear primero un decorador que nos permita medir el tiempo que una función tarda en ejecutarse.
import time
def mide_tiempo(funcion):
inicio = time.time()
c = funcion(*args, **kwargs)
return c
return funcion_medida
Y ahora vamos a crear una función que llame miles de veces a otra. Esto es debido a que necesitamos
realizar varias llamadas a la función para obtener un resultado fiable. Dado que si midiéramos una sola
ejecución de un if, a penas tardaríamos unos microsegundos.
@mide_tiempo
Ahora que ya tenemos todo lo que necesitamos para el experimento, vamos a llamar a nuestra funciones
con diferentes parámetros. Estos son los resultados:
for i in range(8):
repite_funcion(usa_if, str(i))
for i in range(8):
repite_funcion(usa_switch, str(i))
# Usando if:
# Usando switch:
• Usando if el tiempo de ejecución no es siempre el mismo, ya que dependiendo del valor (0-7)
tendrán que evaluarse hasta 8 condiciones.
• Usando switch obtenemos un tiempo ligeramente mayor para el "0", pero el tiempo de ejecución
apenas varía cuando cambiamos la entrada.
Visto esto, no se puede concluir que una forma sea mejor que otra, todo dependerá del problema que
necesites resolver, pero la próxima vez que te enfrentes a un problema similar, ya tendrás las
herramientas para tomar una decisión razonada al respecto.
Introducción al break
La sentencia break nos permite alterar el comportamiento de los bucles while y for. Concretamente,
permite terminar con la ejecución del bucle.
Esto significa que una vez se encuentra la palabra break, el bucle se habrá terminado.
Veamos como podemos usar el break con bucles for. El range(5) generaría 5 iteraciones, donde
la i valdría de 0 a 4. Sin embargo, en la primera iteración, terminamos el bucle prematuramente.
El break hace que nada más empezar el bucle, se rompa y se salga sin haber hecho nada.
for i in range(5):
print(i)
break
# No llega
# Salida: 0
Un ejemplo un poco más útil, sería el de buscar una letra en una palabra. Se itera toda la palabra y en el
momento en el que se encuentra la letra que buscábamos, se rompe el bucle y se sale.
Esto es algo muy útil porque si ya encontramos lo que estábamos buscando, no tendría mucho sentido
seguir iterando la lista, ya que desperdiciaríamos recursos.
cadena = 'Python'
if letra == 'h':
break
print(letra)
# Salida:
#P
#y
#t
# Se encontró la h
El break también nos permite alterar el comportamiento del while. Veamos un ejemplo.
La condición while True haría que la sección de código se ejecutara indefinidamente, pero al hacer uso
del break, el bucle se romperá cuando x valga cero.
x=5
while True:
x -= 1
print(x)
if x == 0:
break
#4, 3, 2, 1, 0
Por norma general, y salvo casos muy concretos, si ves un while True, es probable que haya
un break dentro del bucle.
Como hemos dicho, el uso de break rompe el bucle, pero sólo aquel en el que está dentro.
Es decir, si tenemos dos bucles anidados, el break romperá el bucle anidado, pero no el exterior.
break
print(i, j)
#00
#10
#20
#30
Sentencia continue
Introducción al continue
El uso de continue al igual que el ya visto break, nos permite modificar el comportamiento de de los
bucles while y for.
Concretamente, continue se salta todo el código restante en la iteración actual y vuelve al principio en el
caso de que aún queden iteraciones por completar.
La diferencia entre el break y continue es que el continue no rompe el bucle, si no que pasa a la siguiente
iteración saltando el código pendiente.
En el siguiente ejemplo vemos como al encontrar la letra P se llama al continue, lo que hace que se salte
el print(). Es por ello por lo que no vemos la letra P impresa en pantalla.
cadena = 'Python'
if letra == 'P':
continue
print(letra)
# Salida:
#y
#t
#h
#o
#n
A diferencia del break, el continue no rompe el bucle sino que finaliza la iteración actual, haciendo que
todo el código que va después se salte, y se vuelva al principio a evaluar la condición.
En el siguiente ejemplo podemos ver como cuando la x vale 3, se llama al continue, lo que hace que se
salte el resto de código de la iteración (el print()). Por ello, vemos como el número 3 no se imprime en
pantalla.
x=5
while x > 0:
x -= 1
if x == 3:
continue
print(x)
#Salida: 4, 2, 1, 0
La función zip() de Python viene incluida por defecto en el namespace, lo que significa que puede ser
usada sin tener que importarse.
Returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the argument
sequences or iterables. The iterator stops when the shortest input iterable is exhausted.
Dicho de otra manera, si pasamos dos listas a zip como entrada, el resultado será una tupla donde cada
elemento tendrá todos y cada uno de los elementos i-ésimos de las pasadas como entrada.
Veamos un ejemplo. Como podemos ver, el resultado tras aplicar zip es una lista con a[0]b[0] en el
primer elemento y a[1]b[1] como segundo.
a = [1, 2]
b = ["Uno", "Dos"]
c = zip(a, b)
print(list(c))
A priori puede parecer una función no muy relevante, pero es realmente útil combinada con un for para
iterar dos listas en paralelo.
a = [1, 2]
b = ["Uno", "Dos"]
c = zip(a, b)
Ya hemos visto el uso de zip con dos listas, pero dado que está definida como zip(*iterables), es posible
pasar un número arbitrario de iterables como entrada.
Veamos un ejemplo con varias listas. Es importante notar que todas tienen la misma longitud, dos.
numeros = [1, 2]
print(n, e, i, f)
# 1 Uno One Un
También podemos usar zip usando iterables de diferentes longitudes. En este caso lo que pasará es que
el iterador para cuando la lista más pequeña se acaba.
numeros = [1, 2, 3, 4, 5]
print(n, e)
# 1 Uno
# 2 Dos
Resulta lógico que este sea el comportamiento, porque de no ser así y se continuara, no tendríamos
valores para usar.
Como cabría esperar, dado que zip está definido para un número arbitrario de parámetros de entrada, es
posible también posible usar un único valor. El resultado son tuplas de un elemento.
numeros = [1, 2, 3, 4, 5]
zz = zip(numeros)
print(list(zz))
Hasta ahora nos hemos limitado a usar zip con listas, pero la función está definida para cualquier
clase iterable. Por lo tanto podemos usarla también con diccionarios.
Si realizamos lo siguiente, a,b toman los valores de las key del diccionario. Tal vez algo no demasiado
interesante.
print(a, b)
#11
#22
#33
Sin embargo, si hacemos uso de la función items, podemos acceder al key y value de cada elemento.
# 1 Uno One
# 2 Dos Two
# 3 Tres Three
Deshacer el zip()
Con un pequeño truco, es posible deshacer el zip en una sola línea de código. Supongamos que hemos
usado zip para obtener c.
a = [1, 2, 3]
c = zip(a, b)
print(list(c))
print(a) # (1, 2, 3)
Enumerate en Python
El uso del for en Python nos permite iterar colecciones, recorriendo todos los elementos de la misma.
for l in lista:
print(l)
# Salida:
#A
#B
#C
Sin embargo, existen situaciones en las que no solo queremos acceder al elemento i-ésimo de la
colección, sino que además queremos el índice. Una forma de hacerlo sería la siguiente.
indice = 0
for l in lista:
print(indice, l)
indice += 1
# Salida:
#0A
#1B
#2C
Aunque se trata de una forma perfectamente válida, no es demasiado pythónica, y es precisamente
donde entra en juego el enumerate(). Su uso nos permite ahorrar alguna que otra línea de código,
obteniendo un resultado mucho más limpio y claro.
print(indice, l)
# Salida:
#0A
#1B
#2C
Por último, es importante notar que su uso no se limita únicamente a bucles for. Podemos convertir el
tipo enumerate en una lista de tuplas, donde cada una contiene un elemento de la colección inicial y el
índice asociado.
en = list(enumerate(lista))
print(en)
# Salida;
Por lo tanto recuerda, la próxima vez que quieras acceder a los índices de una colección, piensa si tal
vez enumerate puede resolver tu problema de manera más clara y con menos código.
List comprehensions
Una de las principales ventajas de Python es que una misma funcionalidad puede ser escrita de
maneras muy diferentes, ya que su sintaxis es muy rica en lo que se conoce como expresiones
idiomáticas o idiomatic expressions. Las list comprehension o comprensión de listas son una de ellas.
Vayamos al grano, las list comprehension nos permiten crear listas de elementos en una sola línea de
código. Por ejemplo, podemos crear una lista con los cuadrados de los primeros 5 números de la
siguiente forma
De no existir, podríamos hacer lo mismo de la siguiente forma, pero necesitamos alguna que otra línea
más de código.
cuadrados = []
for i in range(5):
cuadrados.append(i**2)
#[0, 1, 4, 9, 16]
El resultado es el mismo, pero resulta menos claro. Antes de continuar, veamos la sintaxis general de las
comprensiones de listas.
Es decir, por un lado tenemos el for elemento in iterable, que itera un determinado iterable y “almacena”
cada uno de los elementos en elemento como vimos en este otro post sobre el for. Por otro lado,
tenemos la expresión, que es lo que será añadido a la lista en cada iteración.
La expresión puede ser una operación como hemos visto anteriormente i**2, pero también puede ser un
valor constante. El siguiente ejemplo genera una lista de cinco unos.
#[1, 1, 1, 1, 1]
La expresión también puede ser una llamada a una función. Se podría escribir el ejemplo anterior del
cálculo de cuadrados de la siguiente manera.
def eleva_al_2(i):
return i**2
#[0, 1, 4, 9, 16]
Como puedes observar, las posibilidades son bastante amplias. Cualquier elemento que sea iterable
puede ser usado con las list comprehensions. Anteriormente hemos iterado range() pero podemos hacer
lo mismo para una lista. En el siguiente ejemplo vemos como dividir todos los números de una lista entre
10.
Añadiendo condicionales
En el apartado anterior hemos visto como modificar todos los elementos de un iterable (como una lista)
de diferentes maneras. La primera elevando cada elemento al cuadrado, y la segunda dividiendo cada
elemento por diez.
Pero, ¿y si quisiéramos realizar la operación sobre el elemento sólo si una determinada condición se
cumple? Pues tenemos buenas noticias, porque es posible añadir un condicional if. La expresión
genérica sería la siguiente.
Por lo tanto la expresión sólo se aplicará al elemento si se cumple la condición. Veamos un ejemplo con
una frase, de la que queremos saber el número de erres que tiene.
Lo que hace el código anterior es iterar cada letra de la frase, y si es una r, se añade a la lista. De esta
manera el resultado es una lista con tantas letras r como la frase original tiene, y podemos calcular las
veces que se repite con len().
print(len(erres))
#4
Sets comprehension
Las set comprehensions son muy similares a las listas que hemos visto con anterioridad. La única
diferencia es que debemos cambiar el () por {}. Como resulta evidente, dado que los set no permiten
duplicados, si intentamos añadir un elemento que ya existe en el set, simplemente no se añadirá.
#{'r'}
Dictionary comprehension
Y por último, también tenemos las comprensiones de diccionarios. Son muy similares a las anteriores,
con la única diferencia que debemos especificar la key o llave. Veamos un ejemplo.
Conclusiones
Las comprensiones de listas, sets o diccionarios son una herramienta muy útil para hacer que nuestro
código resulte más compacto y fácil de leer. Siempre que tengamos una colección iterable que
queramos modificar, son una buena opción para evitar tener que escribir bucles for.
Las comprensiones están también muy relacionadas con el concepto de programación funcional y otra
funciones que Python nos ofrece como filter o map, por lo que si no las conoces te recomendamos que
leas sobre ellas.
En ciertas ocasiones, las comprensiones no resultan sólo útiles por que puedan ser escritas en una sola
línea de código, sino que también pueden llegar a ser más rápidas que otros métodos. Es muy
importante por lo tanto medir su tiempo de ejecución para saber si son una buena elección.
Y por último, aunque su uso resulte de lo más Pythónico y elegante (algo que a muchos programadores
de Python les encanta), hay que tener cuidado con su uso y no abusar de ellas. Resulta fácil caer en la
tentación de acabar escribiendo comprensiones que son tan largas que prácticamente son imposibles
de leer, algo que puede no ser muy buena idea.
Iteradores e Iterables
Si ya entiendes el uso del while y el for, entonces sin duda estás listo para continuar con los iterables.
Sin duda son una herramienta muy potente de Python que nos permite como su nombre
indica, iterar colecciones que sean iterables. A continuación veremos estos dos conceptos en detalle.
Antes de nada planteemos el problema que queremos resolver. Tenemos una determinada colección de
datos, en este caso una lista con varios valores, y queremos mostrar sus valores uno a uno por pantalla.
Si eres nuevo en Python o vienes de otros lenguajes de programación, tal vez lo resolverías de la
siguiente manera con un while.
# Mal uso
lista = [5, 4, 9, 2]
i=0
elemento = lista[i]
print(elemento)
i += 1
# Salida: 5, 4, 9, 2
Aunque es una solución válida y que funciona perfectamente, tal vez sea mejor usar un bucle for, ya que
nos podemos ahorrar alguna línea de código.
# Mal uso
lista = [5, 4, 9, 2]
for i in range(len(lista)):
elemento = lista[i]
print(elemento)
# Salida: 5, 4, 9, 2
Aunque esta segunda forma es también válida, en Python existe una forma mucho más fácil de iterar una
lista. Dicha forma es la siguiente.
lista = [5, 4, 9, 2]
print(elemento)
# Salida 5, 4, 9, 2
Si saberlo, ya has hecho uso de los iteradores, usando la clase lista que es una clase iterable. Como
puedes ver, se trata de una solución mucho más sencilla. A continuación veremos lo que es un iterable y
cómo puede ser usado.
Iterables
Una clase iterable es una clase que puede ser iterada. Dentro de Python hay gran cantidad de clases
iterables como las listas, strings, diccionarios o ficheros. Si tenemos una clase iterable, podemos usarla
a la derecha del for de la siguiente manera.
# ...
Si usamos el for como acabamos de mostrar, la variable elemento irá tomando los valores de cada
elemento presente en la clase iterable. De esta manera, ya no tenemos que ir accediendo manualmente
con [] a cada elemento.
Anteriormente hemos visto un ejemplo iterando una lista, pero también podemos iterar una cadena, ya
que es una clase iterable. Al iterar una cadena se nos devuelve cada letra presente en la misma. Como
puedes ver, la sintaxis se asemeja bastante al lenguaje natural, sería algo así como decir “pon en c cada
elemento presenta en la cadena”.
cadena = "Hola"
for c in cadena:
print(c)
# Salida: H o l a
Llegados a este punto, tal vez te preguntes ¿y cómo se yo si una clase es iterable o no? Pues bien, tienes
dos opciones. La primera sería consultar la documentación oficial de Python. La segunda es ver si la
clase u objeto en cuestión hereda de Iterable (aquí te explicamos la herencia por si aún no la tienes
clara). Con isinstance() podemos comprobar si una clase hereda de otra.
cadena = "Hola"
numero = 3
# Salida
# cadena True
# numero False
Podemos ver como efectivamente la cadena es iterable y el número no. Es por ello por lo que podemos
iterar la cadena, pero el siguiente código daría un error.
numero = 3
for x in numero:
print(x)
Python nos ofrece también diferentes métodos que pueden ser usados sobre clases iterables como los
que se muestran a continuación:
• join() permite unir cada elemento de una clase iterable con el primer argumento usado.
print(list("Hola"))
print(sum([1, 2, 3]))
print("-".join("Hola"))
# Salida:
#6
#H-o-l-a
De la misma forma que iteramos una cadena o una lista, también podemos iterar un diccionario. El
iterador del diccionario devuelve las claves o keys del mismo.
for i in mi_dict:
print(i)
# Salida: a, b, c
Una vez hemos entendido lo que es una clase iterable, veamos lo que es un iterador.
Iteradores
Se podría explicar la diferencia entre iteradores e iterables usando un libro como analogía. El libro sería
nuestra clase iterable, ya que tiene diferentes páginas a las que podemos acceder. El libro podría ser una
lista, y cada página un elemento de la lista. Por otro lado, el iterador sería un marcapáginas, es decir, una
referencia que nos indica en qué posición estamos del libro, y que puede ser usado para “navegar” por
él.
Es posible obtener un iterador a partir de una clase iterable con la función iter(). En el siguiente ejemplo
podemos ver como obtenemos el iterador del libro.
marcapaginas = iter(libro)
Llegados a este punto, nuestro marcapaginas almacena un iterador. Se trata de un objeto que podemos
usar para navegar a través del libro. Usando la función next() sobre el iterador, podemos ir accediendo
secuencialmente a cada elemento de nuestra lista (las páginas de libro).
print(next(marcapaginas))
print(next(marcapaginas))
print(next(marcapaginas))
print(next(marcapaginas))
# página1
# página2
# página3
# página4
Algo parecido a esto es lo que sucede por debajo cuando usamos el for sobre una clase iterable. Se va
accediendo secuencialmente a los elementos hasta que la excepción StopIteration es lanzada. Dicha
excepción se lanza cuando hemos llegado al final, y no existen más elementos que iterar.
print(next(marcapaginas))
# Salida: StopIteration
Una nota muy importante es que cuando el iterador es obtenido con iter() como hemos visto, apunta por
defecto fuera de la lista. Es decir, si queremos acceder al primer elemento de la lista, deberemos llamar
una vez a next().
Por otro lado, a diferencia de un marcapáginas de un libro, el iterador sólo puede ir hacia delante. No es
posible retroceder.
Llegados a este punto ya entendemos perfectamente los iterables e iteradores y hemos visto como
pueden ser usados con diferentes clases de Python como las cadenas o listas. Sin embargo, tal vez
quieras dar un paso más y definir tu propia clase. En este post ya te explicamos cómo definir tus clases.
A continuación te explicaremos cómo hacer que tu clase sea iterable.
Empecemos desde cero. Vamos a definir una clase MiClase y crear un objeto con ella. Si intentamos
usar la función iter() para obtener su iterador, tendremos un error ya que nuestra clase por defecto no es
iterable.
class MiClase:
pass
miobjeto = MiClase()
iterador = iter(miobjeto)
# Salida
Para poder llamar a la función iter() sobre la clase, debemos implementar el método dunder __iter__().
Dicho método debe devolver un iterable, que será usado cuando la clase intente ser iterada.
class MiClase:
def __init__(self, items):
self.lista = items
def __iter__(self):
return iter(self.lista)
Podemos ver como tenemos el método __init__() que es llamado cuando se crea una nueva instancia de
la clase. Simplemente pasamos una lista como parámetro de entrada y la almacenamos como atributo
en .lista.
Por otro lado, el método __iter__() devuelve un iterador, que simplemente es el iterador de la propia lista.
Ahora que nuestra clase ya es iterable, podemos hacer lo siguiente.
print(item)
# Salida: 5, 4, 3
Cabe destacar que el ejemplo mostrado tiene fines didácticos y poca aplicación práctica, ya que
simplemente se está encapsulando una lista dentro de una clase. Sin embargo sirve para ejemplificar
cómo una clase se puede convertir en iterable, y seguramente con esta base encuentres aplicaciones
prácticas en tus proyectos.
TIPOS DE ESTRUCTURA
• 📗📗 Entero o int
• 📗📗 Booleano
• 📗📗 Float
• 📗📗 Números complejos
• 📗📗 Cadenas o strings
• 📗📗 Listas
• 📙📙 Set
• 📙📙 Tupla o tuple
• 📙📙 Diccionario
• 📙📙 Frozenset
• 📙📙 Castings
• 📙📙 Colecciones
• 📙📙 Mutabilidad
Entero o int
Los enteros en Python o también conocidos como int, son un tipo de datos que permite representar
números enteros, es decir, positivos y negativos no decimales. Si vienes de otros lenguajes de
programación, olvídate de los int8 , uint16 y demás. Python nos da un sólo tipo que podemos usar
sin preocuparnosdel tamaño del número almacenado por debajo.
Introducción a int
Los tipos enteros o int en Python permiten almacenar un valor numérico no decimal ya sea
positivo o negativo de cualquier valor. La función type() nos devuelve el tipo de la variable, y
podemos ver com efectivamente es de la clase int .
i = 12
print(i) #12
print(type(i)) #<class 'int'>
En otros lenguajes de programación, los int tenían un valor máximo que pueden representar.
Dependiendo de la longitud de palabra o wordsize de la arquitectura del ordenador, existen unos
números mínimos y máximos que era posible representar. Si por ejemplo se usan enteros de 32 bits
el rango a representar es de -2^31 a 2^31–1, es decir, -2.147.483.648 a 2.147.483.647. Con 64 bits,
el rango es de -9.223.372.036.854.775.808 hasta 9.223.372.036.854.775.807. Una gran ventaja de
Python es que ya no nos tenemos que preocupar de esto, ya que por debajo se encarga de asignar
más o menos memoria al número, y podemos representar prácticamente cualquier número.
x = 250**250
print(x)
print(type(x))
#3054936363499604682051979393213617699789402740572326663893613909281291626524720457701857235108015
22825687515269359046715531785342780428396973513311420091788963072442053377285222203558881953188370
08165086679301794879136633899370525163649789227021200352450820912190874482021196014946372110934030
79855076782836518362040933993739599827677011489868164062500000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000
#<class 'int'>
Para saber más: Si vienes de otras versiones de Python, tal vez eches de menos el tipo long. Puedes
leer la PEP237, donde se explica porqué se unificó los long y los int.
Ejemplos
En el primer ejemplo hemos asignado un número decimal a una variable, pero también es posible
asignar valores en binario , hexadecimal y octal . El prefijo 0b indica que lo que viene a
continuación será interpretado como un número binario. Para el caso hexadecimal es con 0x y
octal con 0c . Al imprimir, el número se convierte en decimal para todos los casos.
a = 0b100
b = 0x17
c = 0o720
print(a, type(a)) #4 <class 'int'>
print(b, type(b)) #23 <class 'int'>
print(c, type(c)) #464 <class 'int'>
Con el siguiente ejemplo podemos ver como Python asigna diferente número de bits a las
variables en función del número que quieren representar. La función getsizeof() devuelve el
tamaño de una variable en memoria.
import sys
x = 5**10000
y = 10
print(sys.getsizeof(x), type(x))
print(sys.getsizeof(y), type(y))
Aunque como hemos dicho, Python puede representar casi cualquier número, hay casos límite en
los que nos podemos encontrar con una excepción como la que mostramos a continuación.
print(5e200**2)
# OverflowError
Un caso curioso es que si intentamos representar un número aún mayor, nos encontraremos con
lo siguiente en vez de con una excepción.
print(2e2000**2)
# inf
Convertir a int
El posible convertir a int otro tipo. Como hemos explicado, el tipo int no puedo contener
decimales, por lo que si intentamos convertir un número decimal, se truncará todo lo que tengamos
a la derecha de la coma.
b = int(1.6)
print(b) #1
Booleanos en Python
Al igual que en otros lenguajes de programación, en Python existe el tipo bool o booleano. Es un tipo de
dato que permite almacenar dos valores True o False.
y = False
Evaluar expresiones
Un valor booleano también puede ser el resultado de evaluar una expresión. Ciertos operadores como el
mayor que, menor que o igual que devuelven un valor bool.
print(9 == 9) #True
Función bool
print(bool(10)) # True
print(bool(-10)) # True
print(bool("Hola")) # True
print(bool(0.1)) # True
print(bool([])) # False
Uso con if
a=1
b=2
if b > a:
La expresión que va después del if es siempre evaluada hasta que se da con un booleano.
if True:
print("Es True")
Es importante notar que aunque estemos listando el tipo bool como si fuese un tipo más, es en realidad
una subclase del int visto anteriormente. De hecho lo puedes comprobar de la siguiente manera.
isinstance(True, int)
#True
issubclass(bool, int)
#True
Float
El tipo numérico float permite representar un número positivo o negativo con decimales, es decir,
números reales. Si vienes de otros lenguajes, tal vez conozcas el tipo doble, lo que significa que tiene el
doble de precisión que un float. En Python las cosas son algo distintas, y los float son en realidad double.
Para saber más: Los valores float son almacenados de una forma muy particular, denominada
representación en coma flotante. En el estándar IEEE 754 se explica con detalle.
Por lo tanto si declaramos una variable y le asignamos un valor decimal, por defecto la variable será de
tipo float.
f = 0.10093
print(f) #0.10093
Conversión a float
También se puede declarar usando la notación científica con e y el exponente. El siguiente ejemplo sería
lo mismo que decir 1.93 multiplicado por diez elevado a -3.
f = 1.93e-3
También podemos convertir otro tipo a float haciendo uso de float(). Podemos ver como True es en
realidad tratado como 1, y al convertirlo a float, como 1.0.
a = float(True)
b = float(1)
Rango representable
Alguna curiosidad es que los float no tienen precisión infinita. Podemos ver en el siguiente ejemplo como
en realidad f se almacena como si fuera 1, ya que no es posible representar tanta precisión decimal.
f = 0.99999999999999999
print(f) #1.0
print(1 == f) #True
Los float a diferencia de los int tienen unos valores mínimo y máximos que pueden representar. La
mínima precisión es 2.2250738585072014e-308 y la máxima 1.7976931348623157e+308, pero si no nos
crees, lo puedes verificar tu mismo.
import sys
print(sys.float_info.min) #2.2250738585072014e-308
print(sys.float_info.max) #1.7976931348623157e+308
De hecho si intentas asignar a una variable un valor mayor que el max, lo que ocurre es que esa variable
toma el valor inf o infinito.
f = 1.7976931348623157e+310
print(f) #inf
Si quieres representar una variable que tenga un valor muy alto, también puedes crear directamente una
variable que contenga ese valor inf.
f = float('inf')
print(f) #inf
Desafortunadamente, los ordenadores no pueden representar cualquier número, y menos aún si este es
uno irracional. Debido a esto, suceden cosas curiosas como las siguientes.
Dividir 1/3 debería resultar en 0.3 periódico, pero para Python es imposible representar tal número.
print("{:.20f}".format(1.0/3.0))
# 0.33333333333333331483
Por otro lado, la siguiente operación debería tener como resultado cero, pero como puedes ver esto no
es así.
# 5.551115123125783e-17
En pocas palabras, los números complejos son aquellos que tienen dos partes:
Como puedes ver, la parte imaginaria viene acompañada de j, aunque es también común usar la i (de
imaginario). Un número complejo tiene la siguiente forma:
parte_real + parte_imaginaria*j
b = 1.3 - 7j
También podemos tener un número complejo con parte real igual a cero.
c = 10.3j
Una vez vistos cómo son, vamos a ponerlos en contexto con el resto de conjuntos numéricos:
• Los números enteros hacen referencia a los números naturales, es decir, todos aquellos números
que no contienen parte decimal: 3, -1, 10.
• Los números racionales son el cociente de dos números naturales: 3/10, 7/9.
• Los números irracionales son aquellos que no pueden ser expresados como una fracción m/n.
Por ejemplo, el número pi es irracional.
• Los números reales son el conjunto de todos los anteriores, es decir, cualquier número que se te
ocurra sería un número real.
• Y por último los números complejos son la suma de un número real y otro imaginario, dando
lugar a los ya vistos 5+5j.
Llegados a este punto, tal vez te preguntes ¿y qué es i? Pues bien, i es simplemente un nombre que se le
da a la raíz cuadrada de -1, ya que si recuerdas, los números negativos no tienen raíces cuadradas. Esta
notación se la debemos al famoso Leonhard Euler.
Por lo tanto, la raíz cuadrada de -5, podría expresarse como 5i, y aunque pueda parecer algo poco
relevante, se trata de una herramienta muy potente en el mundo de las matemáticas, física e ingeniería.
Los números complejos son muy utilizados en las telecomunicaciones y electrónica, ya que son muy
útiles para describir las ondas electromagnéticas y la corriente eléctrica.
También son usados en diferentes dominios de las matemáticas y en física, donde destaca su uso en la
mecánica cuántica.
Un número complejo puede representarse en un plano, donde el eje x representa la parte real y el eje y la
imaginaria. Es decir, se puede ver a un número complejo 5+5i como un punto de coordenadas.
Una vez representado en esta gráfica, cualquier número complejo formará un ángulo con el eje x. Todo
número complejo tiene también un módulo, que es la distancia que une el punto del origen de
coordenadas 0+0i.
Ahora sólo tienes que imaginarte a este punto dando vueltas a una determinada frecuencia alrededor del
plano, y ya estarías describiendo a una onda sin haberte dado cuenta.
Los números complejos en Python pueden ser creados sin tener que importar ninguna librería. Basta con
hacer lo siguiente:
c = 3 + 5j
print(c) #(3+5j)
Podemos ver como la clase que representa a los complejos en Python se llama complex. Una vez
creado, es posible acceder a la parte real con real y a la imaginaria con imag.
c = 3 + 5j
print(c.real) #3.0
print(c.imag) #5.0
También se puede crear un número complejo haciendo uso de complex, pero sin usar la j.
c = complex(3,5)
print(c) #(3+5j)
Usando variables del tipo complex, podemos realizar las operaciones más comunes típicas de los
números complejos.
Suma de complejos
Para sumar números complejos, se suman las partes reales por un lado, y las imaginarias por otro.
a = 1 + 3j
b = 4 + 1j
print(a+b) #(5+4j)
Resta de complejos
Para restar, se restan las partes reales por un lado y las imaginarias por otro.
a = 1 + 3j
b = 4 + 1j
print(a-b) #(-3+2j)
Multiplicación de complejos
La multiplicación es algo más compleja. Si multiplicamos a+bj por c+dj, se puede demostrar fácilmente
que el resultado es (ac-bd) para la parte real y (ad+bc) para la imaginaria.
a = 1 + 3j
b = 4 + 1j
print(a*b) #(1+13j)
División de complejos
También podemos hacer la división. Si quieres saber cómo se dividen dos números complejos y su
demostración matemática, te dejamos este enlace.
a = 1 + 3j
b = 4 + 1j
print(a/b) #(0.41+0.64j)
Conjugado de complejos
Por último, podemos realizar el conjugado de un número complejo en Python con el método conjugate().
Calcular el conjugado consiste en negar la parte imaginaria, es decir, cambiar si signo de + a - y
viceversa.
a = 1 + 1j
print(a.conjugate()) #(1-1j)
Librería cmath
Si quieres realizar más operaciones con número complejos, tal vez quieras echar un vistazo a la
librería cmath, que es mucho más completa, y compleja.
Algunas de las cosas que puedes hacer son las siguientes:
• Calcular la fase, que es el ángulo que forma el vector con el eje x, en radianes.
import cmath
Cadenas Python
Las cadenas en Python o strings son un tipo inmutable que permite almacenar secuencias de
caracteres. Para crear una, es necesario incluir el texto entre comillas dobles ". Puedes obtener más
ayuda con el comando help(str).
También es valido declarar las cadenas con comillas simples simples '.
Las cadenas no están limitadas en tamaño, por lo que el único límite es la memoria de tu ordenador. Una
cadena puede estar también vacía.
s = ''
Una situación que muchas veces se puede dar, es cuando queremos introducir una comilla, bien sea
simple ' o doble " dentro de una cadena. Si lo hacemos de la siguiente forma tendríamos un error, ya que
Python no sabe muy bien donde empieza y termina.
Para resolver este problema debemos recurrir a las secuencias de escape. En Python hay varias, pero las
analizaremos con más detalle en otro capítulo. Por ahora, la más importante es \", que nos permite
incrustar comillas dentro de una cadena.
print(s)
#Primer linea
#Segunda linea
También podemos usar \ acompañado de un número, lo que imprimirá el carácter asociado. En este
caso imprimimos el carácter 110 que se corresponde con la H.
print("\110\110") #HH
Para saber más: Te recomendamos que busques información sobre ASCI y Unicode. Ambos son
conceptos muy útiles a la hora de entender los strings.
Se puede definir una cadena que ocupe varias líneas usando triple """ comilla. Puede ser muy útil si
tenemos textos muy largo que no queremos tener en una sola línea.
Existe también otra forma de declarar cadenas llamado raw strings. Usando como prefijo r, la cadena
ignora todos las secuencias de escape, por lo que la salida es diferente a la anterior.
print(r"\110\110") #\110\110
print("""La siguiente
cadena ocupa
varias lineas""")
Formateo de cadenas
Tal vez queramos declarar una cadena que contenga variables en su interior, como números o incluso
otras cadenas. Una forma de hacerlo sería concatenando la cadena que queremos con otra usando el
operador +. Nótese que str() convierte en string lo que se pasa como parámetro.
x=5
Otra forma es usando %. Por un lado tenemos %s que indica el tipo que se quiere imprimir, y por otro a la
derecha del % tenemos la variable a imprimir. Para imprimir una cadena se usaría %s o %f para un valor
en coma flotante.
Para saber más: En el siguiente enlace puedes encontrar más información sobre el uso de %.
x=5
Si tenemos más de una variable, también se puede hacer pasando los parámetros dentro de (). Si vienes
de lenguajes como C, esta forma te resultará muy familiar. No obstante, esta no es la forma preferida de
hacerlo, ahora que tenemos nuevas versiones de Python.
Una forma un poco más moderna de realizar lo mismo, es haciendo uso de format().
Es posible también darle nombre a cada elemento, y format() se encargará de reemplazar todo.
Por si no fueran pocas ya, existe una tercera forma de hacerlo introducida en la versión 3.6 de Python.
Reciben el nombre de cadenas literales o f-strings. Esta nueva característica, permite incrustar
expresiones dentro de cadenas.
a = 5; b = 10
a = 5; b = 10
s = f"a + b = {a+b}"
print(s) #a + b = 15
def funcion():
return 20
Ejemplos string
Para entender mejor la clase string, vamos a ver unos ejemplos de como se comportan. Podemos sumar
dos strings con el operador +.
s1 = "Parte 1"
s2 = "Parte 2"
Se puede multiplicar un string por un int. Su resultado es replicarlo tantas veces como el valor del
entero.
s = "Hola "
Con chr() and ord() podemos convertir entre carácter y su valor numérico que lo representa y viceversa.
El segundo sólo función con caracteres, es decir, un string con un solo elemento.
print(chr(8364)) #€
print(ord("€")) #110
La longitud de una cadena viene determinada por su número de caracteres, y se puede consultar con la
función len().
print(len("Esta es mi cadena"))
Como hemos visto al principio, se puede convertir a string otras clases, como int o float.
x = str(10.4)
print(x) #10.4
x = "abcde"
print(x[0]) #a
print(x[-1]) #e
Del mismo modo, se pueden crear cadenas más pequeñas partiendo de una grande, usando indicando
el primer elemento y el último que queremos tomar menos uno.
x = "abcde"
print(x[0:2])
x = "abcde"
print(x[2:])
Es posible también crear subcadenas que contengan elementos salteados y no contiguos añadiendo un
tercer elemento entre []. Indica los elementos que se saltan. En el siguiente ejemplo se toman elementos
del 0 al 5 de dos en dos.
x = "abcde"
print(x[0:5:2]) #ace
Tampoco es necesario saber el tamaño de la cadena, y el segundo valor se podría omitir. El siguiente
ejemplo es igual al anterior.
x = "abcde"
print(x[0::2]) #ace
Métodos string
capitalize()
El método capitalize() se aplica sobre una cadena y la devuelve con su primera letra en mayúscula.
s = "mi cadena"
lower()
s = "MI CADENA"
swapcase()
El método swapcase() convierte los caracteres alfabéticos con mayúsculas en minúsculas y viceversa.
s = "mI cAdEnA"
upper()
s = "mi cadena"
print(s.upper())
print(s.count("llo")) #2
isalnum()
El método isalnum() devuelve True si la cadena esta formada únicamente por caracteres
alfanuméricos, False de lo contrario. Caracteres como @ o & no son alfanumericos.
s = "correo@dominio.com"
print(s.isalnum())
isalpha()
El método isalpha() devuelve True si todos los caracteres son alfabéticos, False de lo contrario.
s = "abcdefg"
print(s.isalpha())
strip([<chars>])
El método strip() elimina a la izquierda y derecha el carácter que se le introduce. Si se llama sin
parámetros elimina los espacios. Muy útil para limpiar cadenas.
print(s.strip()) #abc
zfill(<width>)
El método zfill() rellena la cadena con ceros a la izquierda hasta llegar a la longitud pasada como
parámetro.
s = "123"
print(s.zfill(5)) #00123
join(<iterable>)
El método join() devuelve la primera cadena unida a cada uno de los elementos de la lista que se le pasa
como parámetro.
print(s) #1 y 2 y 3
split(sep=None, maxsplit=-1)
El método split() divide una cadena en subcadenas y las devuelve almacenadas en una lista. La división
es realizada de acuerdo a el primer parámetro, y el segundo parámetro indica el número máximo de
divisiones a realizar.
s = "Python,Java,C"
Listas en Python
Las listas en Python son un tipo de dato que permite almacenar datos de cualquier tipo. Son mutables y
dinámicas, lo cual es la principal diferencia con los sets y las tuplas.
Las listas en Python son uno de los tipos o estructuras de datos más versátiles del lenguaje, ya que
permiten almacenar un conjunto arbitrario de datos. Es decir, podemos guardar en ellas prácticamente
lo que sea. Si vienes de otros lenguajes de programación, se podría decir que son similares a los arrays.
lista = [1, 2, 3, 4]
lista = list("1234")
Una lista sea crea con [] separando sus elementos con comas ,. Una gran ventaja es que pueden
almacenar tipos de datos distintos.
Si tenemos una lista a con 3 elementos almacenados en ella, podemos acceder a los mismos usando
corchetes y un índice, que va desde 0 a n-1 siendo n el tamaño de la lista.
print(a[0]) #90
print(a[1]) #Python
print(a[2]) #3.87
print(a[-1]) #3.87
De la misma manera, al igual que [-1] es el último elemento, podemos acceder a [-2] que será el
penúltimo.
print(a[-2]) #Python
Y si queremos modificar un elemento de la lista, basta con asignar con el operador = el nuevo valor.
a[2] = 1
Un elemento puede ser eliminado con diferentes métodos como veremos a continuación, o con del y la
lista con el índice a eliminar.
l = [1, 2, 3, 4, 5]
del l[1]
print(l) #[1, 3, 4, 5]
También podemos tener listas anidadas, es decir, una lista dentro de otra. Incluso podemos tener una
lista dentro de otra lista y a su vez dentro de otra lista. Para acceder a sus elementos sólo tenemos que
usar [] tantas veces como niveles de anidado tengamos.
print(x[3][0]) #p
print(x[3][2][0]) #5
print(x[3][2][2]) #7
También es posible crear sublistas más pequeñas de una más grande. Para ello debemos de usar : entre
corchetes, indicando a la izquierda el valor de inicio, y a la izquierda el valor final que no está incluido.
Por lo tanto [0:2] creará una lista con los elementos [0] y [1] de la original.
l = [1, 2, 3, 4, 5, 6]
print(l[0:2]) #[1, 2]
print(l[2:6]) #[3, 4, 5, 6]
l = [1, 2, 3, 4, 5, 6]
l[0:3] = [0, 0, 0]
print(l) #[0, 0, 0, 4, 5, 6]
Hay ciertos operadores como el + que pueden ser usados sobre las listas.
l = [1, 2, 3]
l += [4, 5]
print(l) #[1, 2, 3, 4, 5]
Y una funcionalidad muy interesante es que se puede asignar una lista con n elementos a n variables.
l = [1, 2, 3]
x, y, z = l
print(x, y, z) #1 2 3
Iterar listas
En Python es muy fácil iterar una lista, mucho más que en otros lenguajes de programación.
for l in lista:
print(l)
#5
#9
#10
Si necesitamos un índice acompañado con la lista, que tome valores desde 0 hasta n-1, se puede hacer
de la siguiente manera.
print(index, l)
#0 5
#1 9
#2 10
O si tenemos dos listas y las queremos iterar a la vez, también es posible hacerlo.
#5 Jazz
#9 Rock
#10 Djent
Y por supuesto, también se pueden iterar las listas usando los índices como hemos visto al principio, y
haciendo uso de len(), que nos devuelve la longitud de la lista.
print(lista1[i])
#5
#9
#10
Métodos listas
append(<obj>)
l = [1, 2]
l.append(3)
print(l) #[1, 2, 3]
extend(<iterable>)
l = [1, 2]
l.extend([3, 4])
print(l) #[1, 2, 3, 4]
insert(<index>, <obj>)
l = [1, 3]
l.insert(1, 2)
print(l) #[1, 2, 3]
remove(<obj>)
El método remove() recibe como argumento un objeto y lo borra de la lista.
l = [1, 2, 3]
l.remove(3)
print(l) #[1, 2]
pop(index=-1)
El método pop() elimina por defecto el último elemento de la lista, pero si se pasa como parámetro
un índice permite borrar elementos diferentes al último.
l = [1, 2, 3]
l.pop()
print(l) #[1, 2]
reverse()
l = [1, 2, 3]
l.reverse()
print(l) #[3, 2, 1]
sort()
l = [3, 1, 2]
l.sort()
print(l) #[1, 2, 3]
l = [3, 1, 2]
l.sort(reverse=True)
print(l) #[3, 2, 1]
index(<obj>[,index])
El método index() recibe como parámetro un objeto y devuelve el índice de su primera aparición. Como
hemos visto en otras ocasiones, el índice del primer elemento es el 0.
print(l.index("Intervals"))
También permite introducir un parámetro opcional que representa el índice desde el que comenzar la
búsqueda del objeto. Es como si ignorara todo lo que hay antes de ese índice para la búsqueda, en este
caso el 4.
l = [1, 1, 1, 1, 2, 1, 4, 5]
print(l.index(1, 4)) #5
Set
Los sets en Python son una estructura de datos usada para almacenar elementos de una manera similar
a las listas, pero con ciertas diferencias.
Los set en Python son un tipo que permite almacenar varios elementos y acceder a ellos de una forma
muy similar a las listas pero con ciertas diferencias:
• Los elementos de un set son único, lo que significa que no puede haber elementos duplicados.
• Los set son desordenados, lo que significa que no mantienen el orden de cuando son
declarados.
Para entender mejor los sets, es necesario entender ciertos conceptos matemáticos como la teoría de
conjuntos.
Para crear un set en Python se puede hacer con set() y pasando como entrada cualquier tipo iterable,
como puede ser una lista. Se puede ver como a pesar de pasar elementos duplicados como dos 8 y en
un orden determinado, al imprimir el set no conserva ese orden y los duplicados se han eliminado.
s = set([5, 4, 6, 8, 8, 1])
print(s) #{1, 4, 5, 6, 8}
Se puede hacer lo mismo haciendo uso de {} y sin usar la palabra set() como se muestra a continuación.
s = {5, 4, 6, 8, 8, 1}
print(s) #{1, 4, 5, 6, 8}
A diferencia de las listas, en los set no podemos modificar un elemento a través de su índice. Si lo
intentamos, tendremos un TypeError.
s = set([5, 6, 7, 8])
s = set([5, 6, 7, 8])
for ss in s:
print(ss) #8, 5, 6, 7
Con la función len() podemos saber la longitud total del set. Como ya hemos indicado, los duplicados
son eliminados.
s = set([1, 2, 2, 3, 4])
print(len(s)) #4
También podemos saber si un elemento está presente en un set con el operador in. Se el valor existe en
el set, se devolverá True.
s = set(["Guitarra", "Bajo"])
print("Guitarra" in s) #True
Los sets tienen además diferentes funcionalidades, que se pueden aplicar en forma de operador o de
método. Por ejemplo, el operador | nos permite realizar la unión de dos sets, lo que equivale a juntarlos.
El equivalente es el método union() que vemos a continuación.
s1 = set([1, 2, 3])
s2 = set([3, 4, 5])
Métodos sets
s.add(<elem>)
l = set([1, 2])
l.add(3)
print(l) #{1, 2, 3}
s.remove(<elem>)
El método remove() elimina el elemento que se pasa como parámetro. Si no se encuentra, se lanza la
excepción KeyError.
s = set([1, 2])
s.remove(2)
print(s) #{1}
s.discard(<elem>)
El método discard() es muy parecido al remove(), borra el elemento que se pasa como parámetro, y si no
se encuentra no hace nada.
s = set([1, 2])
s.discard(3)
print(s) #{1, 2}
s.pop()
s = set([1, 2])
s.pop()
print(s) #{2}
s.clear()
s = set([1, 2])
s.clear()
print(s) #set()
Otros
Los sets cuentan con una gran cantidad de métodos que permiten realizar operaciones con dos o más,
como la unión o la intersección.
Podemos calcular la unión entre dos sets usando el método union(). Esta operación representa la
“mezcla” de ambos sets. Nótese que el método puede ser llamado con más parámetros de entrada, y su
resultado será la unión de todos los sets.
s1 = {1, 2, 3}
s2 = {3, 4, 5}
print(s1.union(s2)) #{1, 2, 3, 4, 5}
También podemos calcular la intersección entre dos o más set. Su resultado serán aquellos elementos
que pertenecen a ambos sets.
s1 = {1, 2, 3}
s2 = {3, 4, 5}
print(s1.intersection(s2)) #{3}
Los set en Python tiene gran cantidad de métodos, por lo que lo dejaremos para otro capítulo, pero aquí
os dejamos con un listado de ellos:
• s1.union(s2[, s3 ...])
• s1.intersection(s2[, s3 ...])
• s1.difference(s2[, s3 ...])
• s1.symmetric_difference(s2)
• s1.isdisjoint(s2)
• s1.issubset(s2)
• s1.issuperset(s2)
• s1.update(s2[, s3 ...])
• s1.intersection_update(s2[, s3 ...])
• s1.difference_update(s2[, s3 ...])
• s1.symmetric_difference_update(s2)
Tupla (tuple)
Las tuplas en Python son un tipo o estructura de datos que permite almacenar datos de una manera
muy parecida a las listas, con la salvedad de que son inmutables.
Al igual que las listas, las tuplas también pueden ser anidadas.
tupla = 1, 2, ('a', 'b'), 3
print(tupla) #(1, 2, ('a', 'b'), 3)
print(tupla[2][0]) #a
Y también es posible convertir una lista en tupla haciendo uso de al función tuple() .
lista = [1, 2, 3]
tupla = tuple(lista)
print(type(tupla)) #<class 'tuple'>
print(tupla) #(1, 2, 3)
Se puede iterar una tupla de la misma forma que se hacía con las listas.
tupla = [1, 2, 3]
for t in tupla:
print(t) #1, 2, 3
Aunque tal vez no tenga mucho sentido a nivel práctico, es posible crear una tupla de un solo
elemento. Para ello debes usar , antes del paréntesis, porque de lo contrario (2) sería
interpretado como int .
tupla = (2,)
print(type(tupla)) #<class 'tuple'>
Métodos tuplas
count(<obj>)
El método count() cuenta el número de veces que el objeto pasado como parámetro se ha
encontrado en la lista.
l = [1, 1, 1, 3, 5]
print(l.count(1)) #3
index(<obj>[,index])
El método index() busca el objeto que se le pasa como parámetro y devuelve el índice en el que se
ha encontrado.
l = [7, 7, 7, 3, 5]
print(l.index(5)) #4
l = [7, 7, 7, 3, 5]
#print(l.index(35)) #Error! ValueError
El método index() también acepta un segundo parámetro opcional, que indica a partir de que
índice empezar a buscar el objeto.
l = [7, 7, 7, 3, 5]
print(l.index(7, 2)) #2
Diccionario
Los diccionarios en Python son una estructura de datos que permite almacenar su contenido en forma
de llave y valor.
Un diccionario en Python es una colección de elementos, donde cada uno tiene una llave key y un
valor value. Los diccionarios se pueden crear con paréntesis {} separando con una coma cada par key:
value. En el siguiente ejemplo tenemos tres keys que son el nombre, la edad y el documento.
d1 = {
"Nombre": "Sara",
"Edad": 27,
"Documento": 1003882
print(d1)
Otra forma equivalente de crear un diccionario en Python es usando dict() e introduciendo los pares key:
value entre paréntesis.
d2 = dict([
('Nombre', 'Sara'),
('Edad', 27),
('Documento', 1003882),
])
print(d2)
d3 = dict(Nombre='Sara',
Edad=27,
Documento=1003882)
print(d3)
• Son indexados, los elementos del diccionario son accesibles a través del key.
print(d1['Nombre']) #Sara
print(d1.get('Nombre')) #Sara
Para modificar un elemento basta con usar [] con el nombre del key y asignar el valor que queremos.
d1['Nombre'] = "Laura"
print(d1)
print(d1)
Iterar diccionario
Los diccionarios se pueden iterar de manera muy similar a las listas u otras estructuras de datos. Para
imprimir los key.
print(x)
#Nombre
#Edad
#Documento
#Direccion
for x in d1:
print(d1[x])
#Laura
#27
#1003882
#Calle 123
for x, y in d1.items():
print(x, y)
#Nombre Laura
#Edad 27
#Documento 1003882
Diccionarios anidados
Los diccionarios en Python pueden contener uno dentro de otro. Podemos ver como los valores anidado
uno y dos del diccionario d contienen a su vez otro diccionario.
d={
"anidado1" : anidado1,
"anidado2" : anidado2
print(d)
clear()
d = {'a': 1, 'b': 2}
d.clear()
print(d) #{}
get(<key>[,<default>])
El método get() nos permite consultar el value para un key determinado. El segundo parámetro es
opcional, y en el caso de proporcionarlo es el valor a devolver si no se encuentra la key.
d = {'a': 1, 'b': 2}
print(d.get('a')) #1
items()
El método items() devuelve una lista con los keys y values del diccionario. Si se convierte en list se puede
indexar como si de una lista normal se tratase, siendo los primeros elementos las key y los segundos
los value.
d = {'a': 1, 'b': 2}
it = d.items()
print(list(it)[0][0]) #a
keys()
El método keys() devuelve una lista con todas las keys del diccionario.
d = {'a': 1, 'b': 2}
k = d.keys()
values()
El método values() devuelve una lista con todos los values o valores del diccionario.
d = {'a': 1, 'b': 2}
print(list(d.values())) #[1, 2]
pop(<key>[,<default>])
El método pop() busca y elimina la key que se pasa como parámetro y devuelve su valor asociado. Daría
un error si se intenta eliminar una key que no existe.
d = {'a': 1, 'b': 2}
d.pop('a')
print(d) #{'b': 2}
También se puede pasar un segundo parámetro que es el valor a devolver si la key no se ha encontrado.
En este caso si no se encuentra no habría error.
d = {'a': 1, 'b': 2}
d.pop('c', -1)
popitem()
d = {'a': 1, 'b': 2}
d.popitem()
print(d)
#{'a': 1}
update(<obj>)
El método update() se llama sobre un diccionario y tiene como entrada otro diccionario. Los value son
actualizados y si alguna key del nuevo diccionario no esta, es añadida.
d1 = {'a': 1, 'b': 2}
d1.update(d2)
print(d1)
Los frozenset en Python son una estructura de datos muy similar a los set, con la salvedad de que
son inmutables, es decir, no pueden ser modificados una vez declarados.
Los frozensets en Python son un tipo muy parecido a los sets con la salvedad de que son inmutables, es
decir, están congelados y no pueden ser modificados una vez inicializados.
fs = frozenset([1, 2, 3])
Ejemplos frozenset
Dado que son inmutables, cualquier intento de modificación con los métodos que ya hemos visto en
otros capítulos como add() o clear() dará un error, ya que los frozenset no disponen de esos métodos.
fs = frozenset([1, 2, 3])
Los frozenset pueden ser útiles cuando queremos usar un set pero se requiere que el tipo sea inmutable.
Algo no muy común, pero que podría darse, es crear un set de sets. Es decir, un ser que contiene como
elementos a otros sets. El siguiente código daría un TypeError ya que los elementos de un set deben ser
por definición inmutables.
s1 = {1, 2}
s2 = {3, 4}
Para resolver este problema, podemos crear un set de frozensets. Esto si es posible ya que
el frozenset es un tipo inmutable.
s1 = frozenset([1, 2])
s2 = frozenset([3, 4])
s3 = {s1, s2}
Lo mismo aplica a los diccionarios, ya que su key debe ser un tipo inmutable. Si intentamos crear un
diccionario con set como key, obtendremos un TypeError.
s1 = set([1, 2])
s2 = set([3, 4])
Pero si podemos crear un diccionario donde sus key son frozenset, ya que son un tipo inmutable.
s1 = frozenset([1, 2])
s2 = frozenset([3, 4])
Tal vez te interese leer acerca de otras estructuras de datos similares como los sets o las listas.
Cast en Python
Hacer un cast o casting significa convertir un tipo de dato a otro. Anteriormente hemos visto tipos como
los int, string o float. Pues bien, es posible convertir de un tipo a otro.
Pero antes de nada, veamos los diferentes tipos de cast o conversión de tipos que se pueden hacer.
Existen dos:
• Conversión explícita: Es realizada expresamente por nosotros, como por ejemplo convertir
de str a int con str().
Conversión implícita
Esta conversión de tipos es realizada automáticamente por Python, prácticamente sin que nos demos
cuenta. Aún así, es importante saber lo que pasa por debajo para evitar problemas futuros.
a = 1 # <class 'int'>
a=a+b
print(a) # 3.3
• a es un int
• b es un float
a=1
b = "2.3"
c=a+b
Si te das cuenta, es lógico que esto sea así, ya que en este caso b era "2.3", pero ¿y si fuera "Hola"?
¿Cómo se podría sumar eso? No tiene sentido.
Conversión explicita
Por otro lado, podemos hacer conversiones entre tipos o cast de manera explícita haciendo uso de
diferentes funciones que nos proporciona Python. Las más usadas son las siguientes:
Para convertir de float a int debemos usar float(). Pero mucho cuidado, ya que el tipo entero no puede
almacena decimales, por lo que perderemos lo que haya después de la coma.
a = 3.5
a = int(a)
print(a)
# Salida: 3
Podemos convertir un float a string con str(). Podemos ver en el siguiente código como cambia el tipo
de a después de hacer el cast.
a = 3.5
a = str(a)
a = "35.5"
print(float(a))
# Salida: 35.5
El siguiente código daría un error, dado que , no se reconoce por defecto como separador decimal.
a = "35,5"
print(float(a))
Y por último, resulta obvio pensar que el siguiente código dará un error también.
a = "Python"
print(float(a))
Al igual que la conversión a float del caso anterior, podemos convertir de string a int usando int().
a = "3"
a = int(a)
a = "Python"
a = int(a)
A diferencia de otras conversiones, esta puede hacerse siempre, ya que cualquier valor entero que se
nos ocurra poner en a, podrá ser convertido a string.
a = 10
a = str(a)
print(type(a)) # <class 'str'>
Convertir a list
Es también posible hacer un cast a lista, desde por ejemplo un set. Para ello podemos usar list().
a = {1, 2, 3}
b = list(a)
Convertir a set
Y de manera completamente análoga, podemos convertir de lista a set haciendo uso de set().
a = ["Python", "Mola"]
b = set(a)
Esperamos que esto haya servido para aclarar el concepto de casts o conversiones de tipos en Python.
Es muy importante tener bien claro el tipo de datos con el que estamos trabajando en cada momento, ya
que Python es un lenguaje con tipado dinámico, algo que puede ser una gran ventaja, pero también
causa de muchos quebraderos de cabeza.
Colecciones
Colecciones
Una colección permite agrupar varios objetos bajo un mismo nombre. Por ejemplo, si necesitamos
almacenar en nuestro programa los nombres de los alumnos de un curso de programación, será más
conveniente ubicarlos a todos dentro de una misma colección de nombre alumnos, en lugar de crear los
objetos alumno1, alumno2, etc.
En Python existen tres colecciones básicas, a saber: las listas, las tuplas y los diccionarios.
Listas
Una lista es un conjunto ordenado de objetos. Por objetos entendemos cualquiera de los tipos de dato ya
mencionados, incluso otras listas.
Para crear una lista, especificamos sus elementos entre corchetes y separados por comas.
Dado que se trata de una colección ordenada, accedemos a cada uno de los elementos a partir de su
posición, comenzando desde el 0.
>>> lenguajes[0]
'Python'
>>> lenguajes[1]
'Java'
(En Python, las cadenas de caracteres pueden estar formadas tanto por comillas dobles como por
comillas simples).
La posición de un elemento, también conocida como índice, aumenta de izquierda a derecha cuando
éste es positivo. Indicando un número negativo, los elementos se obtienen de derecha a izquierda.
>>> lenguajes[-1]
'C++'
>>> lenguajes[-2]
'C'
>>> lenguajes
>>> lenguajes
Nótese que cuando un elemento es eliminado, a excepción del último, todos los que lo suceden se
corren una posición hacia la izquierda.
Por último, la operación más común es la de agregar un elemento al final de la lista. Para ello
empleamos append().
>>> lenguajes.append("Haskell")
>>> lenguajes
¡Genial! Baste por el momento con lo que se ha dicho sobre las listas. Conoceremos más propiedades
en la sección de Slicing.
Python es un lenguaje de programación orientado a objetos, y como tal, trata a todos los tipos de datos
como objetos. Un simple entero es un objeto.
# x es un objeto
x=5
# x es un objeto
def x():
pass
Sabido esto, tal vez te preguntes ¿y entonces cuál es la diferencia? ¿en qué se diferencian los objetos?
Pues bien, cada objeto viene identificado por su identidad, tipo y valor:
• Identidad: Nunca cambia e identifica de manera unívoca al objeto. El operador is nos permite
saber si dos objetos son en realidad el mismo. Es decir, si dos variables hacen referencia al
mismo.
• Tipo: Nos indica el tipo al que pertenece, como un float o una lista. La función type() nos indica el
tipo de un determinado objeto. Es la clase a la que pertenece.
• Valor: Todo objeto tiene unas características particulares. Si estas características pueden ser
modificadas, diremos que es un tipo mutable. De lo contrario, que es inmutable.
• Podemos ver con id(), que se trata de un identificador único. Es importante notar que si
ejecutamos el código diferentes veces, su valor no tiene porqué se el mismo.
print("Identidad:", id(x))
print("Tipo:", type(x))
print("Value:", x)
# Identidad: 4474035136
# Value: 10
Los enteros en Python, son un tipo inmutable y a continuación explicaremos las implicaciones que tiene
esto.
Mutabilidad
Los diferentes tipos de Python u otros objetos en general, pueden ser clasificados atendiendo a su
mutabilidad. Pueden ser:
• Listas
• Bytearray
• Memoryview
• Diccionarios
• Sets
Y son inmutables:
• Booleanos
• Complejos
• Enteros
• Float
• Frozenset
• Cadenas
• Tuplas
• Range
• Bytes
Sabida ya la clasificación, tal vez te preguntes porqué es esto relevante. Pues bien, Python trata de
manera diferente a los tipos mutables e inmutables, y si no entiendes bien este concepto, puedes llegar
a tener comportamientos inesperados en tus programas.
La forma más sencilla de ver la diferencia, es usando las listas en oposición a las tuplas. Las primeras
son mutables, las segundas no.
l = [1, 2, 3]
l[0] = 0
Como hemos explicado antes, id() nos devuelve un identificador único del objeto. Como puedes
observar, es el mismo antes y después de realizar la modificación.
l = [1, 2, 3]
print(id(l)) #4383854144
l[0] = 0
print(id(l)) #4383854144
Sin embargo, una tupla es inmutable, por lo que la siguiente asignación dará un error.
t = (1, 2, 3)
t[0] = 0
Aunque la tupla es inmutable, si que habría una forma de modificar el valor de t, pero lo que en realidad
hacemos es crear una nueva tupla y asignarle el mismo nombre.
Se podría hacer algo como lo siguiente, convertir la tupla en lista, modificar la lista y convertir a tupla
otra vez.
t = (1, 2, 3)
print(id(t)) #4483581184
t = list(t)
# Modificar elemento
t[0] = 0
t = tuple(t)
print(t) #(0, 2, 3)
print(id(t)) #4483953088
Como puedes ver, hemos conseguido modificar el valor de t, pero id(t) ya no nos devuelve el mismo
valor. El nombre de la variable es el mismo, pero el objeto al que “apunta” ha cambiado.
Lo mismo pasa con los sets (mutables) y los frozenset (no mutables).
Tenemos una variable x con su id y le añadimos una unidad. El resultado es que el identificador cambia,
ya que Python no puede cambiar el 5 al ser el entero un tipo inmutable.
x=5
print(id(x)) # 4525173536
x=x+1
Sin embargo, si usamos una lista, que es un tipo mutable, al intentar modificar la x, dicha modificación
puede ser realizada en la misma variable, por lo que el id() es el mismo
x = [1, 2]
print(id(x)) # 4382432768
x.append(3)
Las principales diferencias entre tipos mutables e inmutables son las siguientes:
• Los tipos inmutables son generalmente más rápidos de acceder. Por lo que si no piensas
modificar una lista, es mejor que uses una tupla.
• Los tipos mutables son perfectos cuando quieres cambiar su contenido repetidas veces.
• Los tipos inmutables son caros de cambiar, ya que lo que se hace en realidad es hacer una copia
de su contenido en un nuevo objeto con las modificaciones.
Excepciones de mutabilidad
Aunque tampoco se trata de una excepción propiamente dicha ya que no rompe con lo explicado
anteriormente, hay casos en los que si podría parecer que se modifica un tipo inmutable.
Imaginemos que tenemos una tupla (inmutable) compuesta por varios elementos, donde hay una lista
(mutable).
l = [4, 5, 6]
t = (1, 2, 3, l)
l[0] = 0
Aunque parece que hayamos modificado la tupla t, lo que en realidad hemos modificado es la lista l, y
como la tupla referencia a ella, también se ve modificada.
La mutabilidad de los objetos es una característica muy importante cuando tratamos con funciones, ya
que Python los tratará de manera distinta.
Si conoces lenguajes de programación como C, los conceptos de paso por valor o referencia te
resultarán familiares:
• Los tipos inmutables son pasados por valor, por lo tanto dentro de la función se accede a una
copia y no al valor original.
• Los tipos mutables son pasados por referencia, como es el caso de las listas y los diccionarios.
Algo similar a como C pasa las array como punteros.
En otros posts te explicamos con más detalle el paso por valor y referencia, pero veamos un ejemplo.
a = 10
b[0] = 10
# x es un entero
x=5
# y es una lista
y = [5]
Si llamamos a la función con ambas variables, vemos como el valor de x no ha cambiado, pero el de y sí.
funcion(x, y)
print(x) # 5
print(y) # 10
Esto se debe a que a=10 trabaja con un valor de a local a la función, al ser el entero un tipo inmutable.
Sin embargo b[0]=10 actúa sobre la variable original.
Ejercicios y ejemplos
T = (1, 2, 3, 4, 5)
T[2] = 0
Si por ejemplo queremos modificar el índice T[2], podemos hacer uso del slicing.
T = (1, 2, 3, 4, 5)
print(T) #(1, 2, 0, 4, 5)
Funciones en Python
• 📗📗 Funciones en Python
• 📙📙 Anotaciones en Funciones
• 📙📙 Funciones Lambda
• 📙📙 Recursividad
• 📕📕 Decoradores
• 📕📕 Generadores
• 📕📕 Corrutinas
• 📕📕 Caching Funciones
• 📕📕 Programación Funcional
Funciones en Python
Anteriormente hemos usado funciones nativas que vienen con Python como len() para calcular la
longitud de una lista, pero al igual que en otros lenguajes de programación, también podemos
definir nuestras propias funciones. Para ello hacemos uso de def.
def nombre_funcion(argumentos):
código
return retorno
def f(x):
return 2*x
y = f(3)
print(y) # 6
Algo que diferencia en cierto modo las funciones en el mundo de la programación, es que no sólo
realizan una operación con sus entradas, sino que también parten de los siguientes principios:
• El principio de reusabilidad, que nos dice que si por ejemplo tenemos un fragmento de código
usado en muchos sitios, la mejor solución sería pasarlo a una función. Esto nos evitaría tener
código repetido, y que modificarlo fuera más fácil, ya que bastaría con cambiar la función una vez.
• Y el principio de modularidad, que defiende que en vez de escribir largos trozos de código, es
mejor crear módulos o funciones que agrupen ciertos fragmentos de código en funcionalidades
específicas, haciendo que el código resultante sea más fácil de leer.
Empecemos por la función más sencilla de todas. Una función sin parámetros de entrada ni parámetros
de salida.
def di_hola():
print("Hola")
Hemos declarado o definido la función. El siguiente paso es llamarla con di_hola(). Si lo realizamos
veremos que se imprime Hola.
di_hola() # Hola
Vamos a complicar un poco las cosas pasando un argumento de entrada. Ahora si pasamos como
entrada un nombre, se imprimirá Hola y el nombre.
def di_hola(nombre):
print("Hola", nombre)
di_hola("Juan")
# Hola Juan
Python permite pasar argumentos también de otras formas. A continuación las explicamos todas.
Los argumentos por posición o posicionales son la forma más básica e intuitiva de pasar parámetros. Si
tenemos una función resta() que acepta dos parámetros, se puede llamar como se muestra a
continuación.
return a-b
resta(5, 3) # 2
Tampoco es posible usar mas argumentos de los tiene la función definidos, ya que no sabría que hacer
con ellos. Por lo tanto si lo intentamos, Python nos dirá que toma 2 posicionales y estamos pasando 3, lo
que no es posible.
#resta(5,4,3) # Error
Otra forma de llamar a una función, es usando el nombre del argumento con = y su valor. El siguiente
código hace lo mismo que el código anterior, con la diferencia de que los argumentos no son
posicionales.
resta(a=3, b=5) # -2
resta(b=5, a=3) # -2
Como es de esperar, si indicamos un argumento que no ha sido definido como parámetro de entrada,
tendremos un error.
return a+b+c
suma(5,5,3) # 13
Dado que el parámetro c tiene un valor por defecto, la función puede ser llamada sin ese valor.
suma(4,3) # 7
Podemos incluso asignar un valor por defecto a todos los parámetros, por lo que se podría llamar a la
función sin ningún argumento de entrada.
return a+b+c
suma() # 8
suma(1) #6
suma(4,5) # 9
suma(5,3,2) # 10
O haciendo uso de lo que hemos visto antes y usando los nombres de los argumentos.
suma(a=5, b=3) #8
En el ejemplo con argumentos por defecto, hemos visto que la función puede ser llamada con diferente
número de argumentos de entrada, pero esto no es realmente una función con argumentos de longitud
variable, ya que existe un número máximo.
Imaginemos que queremos una función suma() como la de antes, pero en este caso necesitamos que
sume todos los números de entrada que se le pasen, sin importar si son 3 o 100. Una primera forma de
hacerlo sería con una lista.
def suma(numeros):
total = 0
for n in numeros:
total += n
return total
suma([1,3,5,4]) # 13
La forma es válida y cumple nuestro requisito, pero realmente no estamos trabajando con argumentos
de longitud variable. En realidad tenemos un solo argumento que es una lista de números.
Por suerte, Python tiene una herramienta muy potente. Si declaramos un argumento con *, esto hará que
el argumento que se pase sea empaquetado en una tupla de manera automática. No confundir * con los
punteros en otros lenguajes de programación, no tiene nada que ver.
def suma(*numeros):
print(type(numeros))
# <class 'tuple'>
total = 0
for n in numeros:
total += n
return total
suma(1, 3, 5, 4) # 13
El resultado es igual que el anterior, y podemos ver como efectivamente numeros es de la clase tuple.
También podemos hacer otras llamadas con diferente número de argumentos
suma(6) # 6
suma(6, 4, 10) # 20
Usando doble ** es posible también tener como parámetro de entrada una lista de elementos
almacenados en forma de clave y valor. En este caso podemos iterar los valores haciendo uso de items().
def suma(**kwargs):
suma = 0;
print(key, value)
suma += value
return suma
suma = 0
print(key, value)
suma += value
return suma
suma(**di) # 30
Sentencia return
En lo relativo a lo primero, una vez se llama a return se para la ejecución de la función y se vuelve o
retorna al punto donde fue llamada. Es por ello por lo que el código que va después del return no es
ejecutado en el siguiente ejemplo.
def mi_funcion():
print("Entra en mi_funcion")
return
print("No llega")
Por ello, sólo llamamos a return una vez hemos acabado de hacer lo que teníamos que hacer en la
función.
Por otro lado, se pueden devolver parámetros. Normalmente las funciones son llamadas para realizar
unos cálculos en base a una entrada, por lo que es interesante poder devolver ese resultado a quien
llamó a la función.
def di_hola():
return "Hola"
di_hola()
# 'Hola'
También es posible devolver mas de una variable, separadas por ,. En el siguiente ejemplo tenemos una
función que calcula la suma y media de tres números, y devuelve su resultado.
def suma_y_media(a, b, c):
suma = a+b+c
media = suma/3
print(suma) # 18
print(media) # 6.0
Documentación
Ahora que ya tenemos nuestras propias funciones creadas, tal vez alguien se interese en ellas y
podamos compartírselas. Las funciones pueden ser muy complejas, y leer código ajeno no es tarea fácil.
Es por ello por lo que es importante documentar las funciones. Es decir, añadir comentarios para indicar
como deben ser usadas.
"""
"""
return a+b
Para ello debemos usar la triple comilla """ al principio de la función. Se trata de una especie de
comentario que podemos usar para indicar como la función debe ser usada. No se trata de código, es un
simple comentario un tanto especial, conocido como docstring.
Ahora cualquier persona que tenga nuestra función, podrá llamar a la función help() y obtener la ayuda
de como debe ser usada.
help(mi_funcion_suma)
print(mi_funcion_suma.__doc__)
Para saber más: Las descripciones de las funciones suelen ser un poco mas detalladas de lo que
hemos mostrado. En la PEP257 se define en detalle como debería ser.
Anotaciones en funciones
Existe una funcionalidad relativamente reciente en Python llamada function annotation o anotaciones
en funciones. Dicha funcionalidad nos permite añadir metadatos a las funciones, indicando los tipos
esperados tanto de entrada como de salida.
def multiplica_por_3(numero: int) -> int:
return numero*3
multiplica_por_3(6) # 18
Las anotaciones son muy útiles de cara a la documentación del código, pero no imponen ninguna norma
sobre los tipos. Esto significa que se puede llamar a la función con un parámetro que no sea int, y no
obtendremos ningún error.
multiplica_por_3("Cadena")
# 'CadenaCadenaCadena'
En muchos lenguajes de programación existen los conceptos de paso por valor y por referencia que
aplican a la hora de como trata una función a los parámetros que se le pasan como entrada. Su
comportamiento es el siguiente:
• Si usamos un parámetro pasado por valor, se creará una copia local de la variable, lo que implica
que cualquier modificación sobre la misma no tendrá efecto sobre la original.
• Con una variable pasada como referencia, se actuará directamente sobre la variable pasada, por
lo que las modificaciones afectarán a la variable original.
En Python las cosas son un poco distintas, y el comportamiento estará definido por el tipo de variable
con la que estamos tratando. Veamos un ejemplo de paso por valor.
x = 10
def funcion(entrada):
entrada = 0
funcion(x)
print(x) # 10
No pasa lo mismo si por ejemplo x es una lista como en el siguiente ejemplo. En este caso Python lo trata
como si estuviese pasada por referencia, lo que hace que se modifique la variable original. La variable
original x ha sido modificada.
entrada.append(40)
funcion(x)
El ejemplo anterior nos podría llevar a pensar que si en vez de añadir un elemento a x, hacemos x=[],
estaríamos destruyendo la lista original. Sin embargo esto no es cierto.
def funcion(entrada):
entrada = []
funcion(x)
print(x)
Una forma muy útil de saber lo que pasa por debajo de Python, es haciendo uso de la función id(). Esta
función nos devuelve un identificador único para cada objeto. Volviendo al primer ejemplo podemos ver
como los objetos a los que “apuntan” x y entrada son distintos.
x = 10
print(id(x)) # 4349704528
def funcion(entrada):
entrada = 0
print(id(entrada)) # 4349704208
funcion(x)
Sin embargo si hacemos lo mismo cuando la variable de entrada es una lista, podemos ver que en este
caso el objeto con el que se trabaja dentro de la función es el mismo que tenemos fuera.
print(id(x)) # 4422423560
def funcion(entrada):
entrada.append(40)
print(id(entrada)) # 4422423560
funcion(x)
Si alguna vez has tenido que definir una función con un número variable de argumentos y no has sabido
como hacerlo, a continuación te explicamos cómo gracias a los args y kwargs en Python.
Vamos a suponer que queremos una función que sume un conjunto de números, pero no sabemos a
priori la cantidad de números que se quieren sumar. Si por ejemplo tuviéramos tres, la función sería tan
sencilla como la siguiente.
return a+b+c
suma(2, 4, 6)
#Salida: 12
El problema surge si por ejemplo queremos sumar cuatro números. Como es evidente, la siguiente
llamada a la función anterior daría un error ya que estamos usando cuatro argumentos mientras que la
función sólo soporta tres.
suma(2, 4, 6, 1)
Introducida ya la problemática, veamos como podemos resolver este problema con *args y **kwargs en
Python.
Uso de *args
Gracias a los *args en Python, podemos definir funciones cuyo número de argumentos es variable. Es
decir, podemos definir funciones genéricas que no aceptan un número determinado de parámetros, sino
que se “adaptan” al número de argumentos con los que son llamados.
De hecho, el args viene de arguments en Inglés, o argumentos. Haciendo uso de *args en la declaración
de la función podemos hacer que el número de parámetros que acepte sea variable.
def suma(*args):
s=0
s += arg
return s
suma(1, 3, 4, 2)
#Salida 10
suma(1, 1)
#Salida 2
Antes de nada, el uso del nombre args es totalmente arbitrario, por lo que podrías haberlo llamado como
quisieras. Es una mera convención entre los usuarios de Python y resulta frecuente darle ese nombre. Lo
que si es un requisito, es usar * junto al nombre.
En el ejemplo anterior hemos visto como *args puede ser iterado, ya que en realidad es una tupla. Por lo
tanto iterando la tupla podemos acceder a todos los argumentos de entrada, y en nuestro caso sumarlos
y devolverlos.
Nótese que es un mero ejemplo didáctico. En realidad podríamos hacer algo como lo siguiente, lo que
sería mucho más sencillo.
def suma(*args):
return sum(args)
suma(5, 5, 3)
#Salida 13
Con esto resolvemos nuestro problema inicial, en el que necesitábamos un número variable de
argumentos. Sin embargo, hay otra forma que nos proporciona además un nombre asociado al
argumento, con el uso de **kwargs. La explicamos a continuación.
Uso de **kwargs
Al igual que en *args, en **kwargs el nombre es una mera convención entre los usuarios de Python.
Puedes usar cualquier otro nombre siempre y cuando respetes el **.
En este caso, en vez de tener una tupla tenemos un diccionario. Puedes verificarlo de la siguiente forma
con type().
def suma(**kwargs):
print(type(kwargs))
suma(x=3)
#<class 'dict'>
Pero veamos un ejemplo más completo. A diferencia de *args, los **kwargs nos permiten dar un nombre
a cada argumento de entrada, pudiendo acceder a ellos dentro de la función a través de un diccionario.
def suma(**kwargs):
s=0
s += value
return s
#Salida
#a = 3
#b = 10
#c = 3
#16
Como podemos ver, es posible iterar los argumentos de entrada con items(), y podemos acceder a la
clave key (o nombre) y el valor o value de cada argumento.
El uso de los **kwargs es muy útil si además de querer acceder al valor de las variables dentro de la
función, quieres darles un nombre que de una información extra.
Una vez entendemos el uso de *args y **kwargs, podemos complicar las cosas un poco más. Es posible
mezclar argumentos normales con *args y **kwargs dentro de la misma función. Lo único que necesitas
saber es que debes definir la función en el siguiente orden:
Veamos un ejemplo.
print("a =", a)
print("b =", b)
for arg in args:
#Salida
#a = 10
#b = 20
#args = 1
#args = 2
#args = 3
#args = 4
#x = Hola
#y = Que
#z = Tal
Y por último un truco que no podemos dejar sin mencionar es lo que se conoce como tuple unpacking.
Haciendo uso de *, podemos extraer los valores de una lista o tupla, y que sean pasados como
argumentos a la función.
print("a =", a)
print("b =", b)
args = [1, 2, 3, 4]
#Salida
#a = 10
#b = 20
#args = 1
#args = 2
#args = 3
#args = 4
#x = Hola
#y = Que
#z = Tal
Anotaciones en Funciones
Las anotaciones en funciones o function annotations de Python nos permiten añadir el tipo de los
argumentos de entrada y salida de una función. A continuación podemos ver un ejemplo con la
función suma(), que recibe dos argumentos a, b y cuyo tipo se espera que sea int.
return a + b
print(suma(7, 3))
# Salida: 10
Sin embargo es muy importante notar que Python ignora las anotaciones. Es decir, son una mera nota
en el código que indica el tipo esperado, pero el siguiente código no daría ningún error. Más adelante
explicaremos cómo realizar el chequeo de tipos.
suma(7.0, 3.0)
Las anotaciones en funciones fueron introducidas en la PEP3107 para Python 3, y más adelante se
introdujo la PEP484 especificando la semántica que se debe usar.
Python es un lenguaje de programación con tipado dinámico y duck typing, lo que significa que los tipos
(int, string, etc) le dan igual. Precisamente esto es lo que hace que el siguiente código funcione. La
función imprime puede ser llamada con cualquier tipo, ya que Python no realiza ninguna comprobación
del tipo de var.
def imprime(var):
print(var)
imprime(1.0) # 1.0
imprime(3) #3
imprime("Python") # Python
Sin embargo, en ciertas ocasiones esto nos puede traer problemas. ¿Y si queremos que la
función imprime sólo acepte que var sea de un tipo concreto? Pues bien, las anotaciones en
funciones o function annotations como acabamos de ver nos permiten especificar los tipos que se
esperan recibir.
Antes de nada es importante notar que las anotaciones en funciones no definen per se una semántica
propia. Es decir, podemos escribir lo que se nos ocurra después de cada argumento. Las anotaciones
pueden ser accedidas usando __annotations__.
return a + b
print(suma.__annotations__)
# Salida:
# 'return': 'retorno'}
Aunque como hemos dicho se puedan realizar anotaciones arbitrarias, suele ser común usar tipos de
Python como int, str o float. En el siguiente ejemplo podemos ver como se combina una anotación con
un valor por defecto [].
print(filtrar_pares([1, 2, 3, 4, 5, 6]))
# Salida: [2, 4, 6]
También es posible usar como anotaciones clases definidas por nosotros, como ClaseA.
class ClaseA:
pass
return a
a = ClaseA()
funcion(a)
Por último, las anotaciones no están limitadas a los argumentos de las funciones, sino que también se
pueden asignar a variables que declaremos.
print(pi)
# Salida: 3.14
Sin embargo, como ya hemos visto anteriormente, Python no da error cuando los tipos no se
corresponden como en el ejemplo anterior.
print(pi)
# Salida: 3.14
Entonces uno se puede preguntar, ¿y para que sirven las function annotation, si Python las ignora? Pues
bien, aunque las ignore, tenemos herramientas que nos permiten saber cuando no se están respetando.
Lo vemos a continuación con el análisis estático del código.
Una primera forma de verificar que las funciones se llaman con los parámetros especificados por las
anotaciones, sería lo siguiente. Sin embargo el error que obtendríamos sería en tiempo de ejecución. Es
decir, nos encontraríamos con el error una vez el código estuviera ejecutándose. Por lo tanto, no
recomendamos el uso del siguiente código.
# Nota: Ejemplo didáctico, no recomendado
return a + b
else:
print(suma(7, 3))
# Salida: 10
print(suma(7.0, 3.0))
Afortunadamente, tenemos herramientas como mypy que nos permiten hacer un chequeo estático de
los tipos, obteniendo el error antes de que el código se ejecute. Lo podemos instalar de la siguiente
manera.
Y volviendo al ejemplo anterior de la suma, podemos ver como el siguiente código si que pasaría los
checks de mypy.
# suma_correcta.py
return a + b
print(suma(7, 3))
$ mypy suma_correcta.py
Sin embargo si cambiamos los tipos de los parámetros de entrada, obtendremos un error.
# suma_incorrecta.py
return a + b
print(suma(7.0, "3"))
$ mypy suma_incorrecta.py
suma_incorrecta.py:5: error: Argument 1 to "suma" has incompatible type "float"; expected "int"
suma_incorrecta.py:5: error: Argument 2 to "suma" has incompatible type "str"; expected "int"
Como hemos indicado, la ventaja de mypy es que realiza un análisis estático, es decir, sin ejecutar el
código. Esto es algo muy importante ya que si de verdad queremos reforzar que se verifiquen los tipos,
no tendría mucho sentido hacerlo en tiempo de ejecución, ya que sería demasiado tarde y tendríamos
un error.
Funciones lambda
Las funciones lambda o anónimas son un tipo de funciones en Python que típicamente se definen en
una línea y cuyo código a ejecutar suele ser pequeño. Resulta complicado explicar las diferencias, y para
que te hagas una idea de ello te dejamos con la siguiente cita sacada de la documentación oficial.
“Python lambdas are only a shorthand notation if you’re too lazy to define a function.”
Lo que significa algo así como, “las funciones lambda son simplemente una versión acortada, que
puedes usar si te da pereza escribir una función”
Lo que sería una función que suma dos números como la siguiente.
return a+b
lambda a, b : a + b
La primera diferencia es que una función lambda no tiene un nombre, y por lo tanto salvo que sea
asignada a una variable, es totalmente inútil. Para ello debemos.
suma = lambda a, b: a + b
Una vez tenemos la función, es posible llamarla como si de una función normal se tratase.
suma(2, 4)
Si es una función que solo queremos usar una vez, tal vez no tenga sentido almacenarla en una variable.
Es posible declarar la función y llamarla en la misma línea.
(lambda a, b: a + b)(2, 4)
## Ejemplos
return lambda_func(2,4)
mi_funcion(lambda a, b: a + b)
Y una función normal también puede ser la entrada de una función lambda. Nótese que son ejemplo
didácticos y sin demasiada utilidad práctica per se.
return a + b
A pesar de que las funciones lambda tienen muchas limitaciones frente a las funciones normales,
comparten gran cantidad de funcionalidades. Es posible tener argumentos con valor asignado por
defecto.
Al igual que en las funciones se puede tener un número variable de argumentos haciendo uso de *, lo
conocido como tuple unpacking.
Y si tenemos los parámetros de entrada almacenados en forma de key y value como si fuera un
diccionario, también es posible llamar a la función.
x = lambda a, b: (b, a)
print(x(3, 9))
# Salida (9,3)
Recursividad
¿En qué trabajas? Estoy intentando arreglar los problemas que creé cuando intentaba arreglar los
problemas que creé cuando intentaba arreglar los problemas que creé. Y así nació la recursividad.
La recursividad o recursión es un concepto que proviene de las matemáticas, y que aplicado al mundo
de la programación nos permite resolver problemas o tareas donde las mismas pueden ser divididas en
subtareas cuya funcionalidad es la misma. Dado que los subproblemas a resolver son de la misma
naturaleza, se puede usar la misma función para resolverlos. Dicho de otra manera, una función
recursiva es aquella que está definida en función de sí misma, por lo que se llama repetidamente a sí
misma hasta llegar a un punto de salida.
• Por otro lado, tiene que existir siempre una condición en la que la función retorna sin volver a
llamarse. Es muy importante porque de lo contrario, la función se llamaría de manera indefinida.
Calcular factorial
Uno de los ejemplos mas usados para entender la recursividad, es el cálculo del factorial de un
número n!. El factorial de un número n se define como la multiplicación de todos sus números
predecesores hasta llegar a uno. Por lo tanto 5!, leído como cinco factorial, sería 5*4*3*2*1.
def factorial_normal(n):
r=1
i=2
while i <= n:
r *= i
i += 1
return r
factorial_normal(5) # 120
Dado que el factorial es una tarea que puede ser dividida en subtareas del mismo tipo
(multiplicaciones), podemos darle un enfoque recursivo y escribir la función de otra manera.
def factorial_recursivo(n):
if n == 1:
return 1
else:
return n * factorial_recursivo(n-1)
factorial_recursivo(5) # 120
Lo que realmente hacemos con el código anterior es llamar a la función factorial_recursivo() múltiples
veces. Es decir, 5! es igual a 5 * 4! y 4! es igual a 4 * 3! y así sucesivamente hasta llegar a 1.
Algo muy importante a tener en cuenta es que si realizamos demasiadas llamadas a la función,
podríamos llegar a tener un error del tipo RecursionError. Esto se debe a que todas las llamadas van
apilándose y creando un contexto de ejecución, algo que podría llegar a causar un stack overflow. Es por
eso por lo que Python lanza ese error, para protegernos de llegar a ese punto.
Serie de Fibonacci
Otro ejemplo muy usado para ilustrar las posibilidades de la recursividad, es calcular la serie de
fibonacci. Dicha serie calcula el elemento n sumando los dos anteriores n-1 + n-2. Se asume que los dos
primeros elementos son 0 y 1.
def fibonacci_recursivo(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
Podemos ver que siempre que la n sea distinta de cero o uno, se llamará recursivamente a la función,
buscando los dos elementos anteriores. Cuando la n valga cero o uno se dejará de llamar a la función y
se devolverá un valor concreto. Podemos calcular el elemento 7, que será 0,1,1,2,3,5,8,13, es decir, 13.
fibonacci_recursivo(7)
# 13
Decoradores
Los decoradores son funciones que modifican el comportamiento de otras funciones, ayudan a
acortar nuestro código y hacen que sea más Pythonic. Si alguna vez has visto @, estás ante un decorador
o decorator, bien sea uno que Python ofrece por defecto o uno que puede haber sido creado ex profeso.
Si aún no controlas las funciones te recomendamos que empieces con este post.
Veamos un ejemplo muy sencillo. Tenemos una función suma() que vamos a decorar
usando mi_decorador(). Para ello, antes de la declaración de la función suma, hacemos uso
de @mi_decorador.
def mi_decorador(funcion):
c = funcion(a, b)
print("Se ha llamado")
return c
return nueva_funcion
@mi_decorador
return a + b
suma(5,8)
# Se va a llamar
# Se ha llamado
Lo que realiza mi_decorador() es definir una nueva función que encapsula o envuelve la función que se
pasa como entrada. Concretamente lo que hace es hace uso de dos print(), uno antes y otro después de
la llamada la función.
Por lo tanto, cualquier función que use @mi_decorador tendrá dos print, uno y al principio y otro al final,
dando igual lo que realmente haga la función.
@mi_decorador
return a - b
resta(5, 3)
# Se va a llamar
# Entra en funcion resta
# Se ha llamado
Una vez entendido esto, vamos a entrar un poco más en detalle viendo como Python trata a las
funciones, como se puede definir un decorador sin @, como se pueden pasar argumentos a los
decoradores, y para finalizar, un ejemplo práctico.
Definiendo decoradores
Antes de nada hay que entender que todo en Python es un objeto, incluso una función. De hecho se
puede asignar una función a una variable. Nótese la diferencia entre:
def di_hola():
print("Hola")
Entendido esto, demos un paso más. En Python se pueden definir funciones dentro de otras funciones.
La función operaciones define suma() y resta(), y dependiendo del parámetro de entrada op, se devolverá
una u otra.
def operaciones(op):
def suma(a, b):
return a + b
return a - b
if op == "suma":
return suma
elif op == "resta":
return resta
funcion_suma = operaciones("suma")
print(funcion_suma(5, 7)) # 12
funcion_suma = operaciones("resta")
print(funcion_suma(5, 7)) # -2
Si llamamos a la función devuelta con dos operandos, se realizará una operación distinta en función de
si se uso suma o resta.
Ahora ya podemos dar la última vuelta de tuerca y escribir nuestro propio decorador sin hacer uso
de @. Por un lado tenemos el decorador, que recibe como entrada una función y devuelve otra función
decorada. Por el otro la función suma() que queremos decorar.
def decorador(func):
c = func(a, b)
return c
return envoltorio_func
print("Dentro de suma")
return a + b
funcion_decorada = decorador(suma)
funcion_decorada(5, 8)
Entonces, haciendo uso de decorador y pasando como entrada suma, recibimos una nueva función
decorada con una funcionalidad nueva, lista para ser usada. Sería el equivalente al uso de @.
Tal vez quieras que tu decorador tenga algún parámetro de entrada para modificar su comportamiento.
Se puede hacer envolviendo una vez más la función como se muestra a continuación.
def mi_decorador(arg):
def decorador_real(funcion):
print(arg)
c = funcion(a, b)
print(arg)
return c
return nueva_funcion
return decorador_real
return a + b
suma(5,8)
Es importante recalcar que los ejemplos mostrados hasta ahora son didácticos, y no tienen mucha
utilidad práctica. Veamos un ejemplo más práctico.
Ejemplo: logger
Una de las utilidades más usadas de los decoradores son los logger. Su uso nos permite escribir en un
fichero los resultados de ciertas operaciones, que funciones han sido llamadas, o cualquier información
que en un futuro resulte útil para ver que ha pasado.
En el siguiente ejemplo tenemos un uso más completo del decorador log() que escribe en un fichero los
resultados de las funciones que han sido llamadas.
def log(fichero_log):
def decorador_log(func):
opened_file.write(f"{output}\n")
return decorador_funcion
return decorador_log
@log('ficherosalida.log')
return a + b
@log('ficherosalida.log')
return a - b
@log('ficherosalida.log')
return a*b/c
suma(10, 30)
resta(7, 23)
multiplicadivide(5, 10, 2)
Nótese que el decorador puede ser usado sobre funciones que tienen diferente número de parámetros
de entrada, y su funcionalidad será siempre la misma. Escribir en el fichero pasado como parámetro los
resultados de las operaciones.
Otro caso de uso muy importante y ampliamente usado en Flask, que es un framework de desarrollo
web, es el uso de decoradores para asegurarse de que una función es llamada cuando el usuario se ha
autenticado.
Realizando alguna simplificación, podríamos tener un decorador que requiriera que una
variable autenticado fuera True. La función se ejecutará solo si dicha variable global es verdadera, y se
dará un error de lo contrario.
autenticado = True
def requiere_autenticación(f):
if not autenticado:
else:
return funcion_decorada
@requiere_autenticación
def di_hola():
print("Hola")
di_hola()
Y esto es todo. En otros posts veremos el uso de functools y porque nos puede ser muy útil.
Generators en Python
Si alguna vez te has encontrado con una función en Python que no sólo tiene una sentencia return, sino
que además devuelve un valor haciendo uso de yield, ya has visto lo que es un generador o generator. A
continuación te explicamos cómo se crean, para qué sirven y sus ventajas. Es muy importante también
no confundir los generadores con las corrutinas, que también usan yield pero de otra manera, sin
embargo estas las dejamos para otro post.
Empecemos por lo básico. Seguramente ya sepas lo que es una función y cómo puede devolver un valor
con return.
def funcion():
return 5
Como hemos explicado, los generadores usan yield en vez de return. Vamos a ver que pasaría si
cambiamos el return por el yield.
def generador():
yield 5
print(funcion())
print(generador())
# Salida: 5
Podemos ver ya la primera diferencia al usar el yield. En el primer caso, se devuelve un 5, pero en el
segundo lo que se devuelve es en realidad un objeto de la clase generator. En realidad el número 5
también puede ser accedido en el caso del generador, pero esto lo veremos más adelante.
Entonces, si una función contiene al menos una sentencia yield, se convertirá en una función
generadora. Una función generadora se diferencia de una función normal en que tras ejecutar el yield, la
función devuelve el control a quién la llamó, pero la función es pausada y el estado (valor de las
variables) es guardado. Esto permite que su ejecución pueda ser reanudada más adelante.
A continuación vamos a ver cómo acceder a los valores del generador. Para entenderlo mejor, te
recomendamos que leas antes más acerca de iterables e iteradores.
Otra de las características que hacen a los generators diferentes, es que pueden ser iterados, ya que
codifican los métodos __iter__() y __next__(), por lo que podemos usar next() sobre ellos. Dado que son
iterables, lanzan también un StopIteration cuando se ha llegado al final.
print(next(a))
# Salida: 5
Como te prometimos antes, el 5 también se podía acceder ¿has visto?. Pero vamos a ver que pasa ahora
si intentamos llamar otra vez a next(). Si recuerdas, sólo tenemos una llamada a yield.
a = generador()
print(next(a))
print(next(a))
# Salida: 5
Como era de esperar, tenemos una excepción del tipo StopIteration, ya que el generador no devuelve
más valores. Esto se debe a que cada vez que usamos next() sobre el generador, se llama y se continúa
su ejecución después del último yield. Y en este caso cómo no hay más código, no se generan más
valores.
Creando Generadores
Vamos a ver otro ejemplo más completo donde tengamos un generador que genere varios valores. En la
siguiente función podemos ver como tenemos una variable n que incrementada en 1, y después retorna
con yield. Lo que pasará aquí, es que el generador generará un total de tres valores.
def generador():
n=1
yield n
n += 1
yield n
n += 1
yield n
Y haciendo uso de next() al igual que hacíamos antes, podemos ver los valores que han sido generados.
Lo que pasa por debajo, sería lo siguiente:
• Se entra en la función generadora, n=1 y se devuelve ese valor. Como ya hemos visto, el estado de
la función se guarda (el valor de n es guardado para la siguiente llamada)
• La segunda vez que usamos next() se entra otra vez en la función, pero se continúa su ejecución
donde se dejó anteriormente. Se suma 1 a la n y se devuelve el nuevo valor.
• Una cuarta llamada daría un error, ya que no hay más código que ejecutar.
g = generador()
print(next(g))
print(next(g))
print(next(g))
# Salida: 1, 2, 3
Otra forma más cómoda de realizar lo mismo, sería usando un simple bucle for, ya que el generador es
iterable.
for i in generador():
print(i)
# Salida: 1, 2, 3
Forma alternativa
Los generadores también pueden ser creados de una forma mucho más sencilla y en una sola línea de
código. Su sintaxis es similar a las list comprehension, pero cambiando el corchete [] por paréntesis ().
El ejemplo con list comprehensions sería el siguiente. Simplemente se generan los valores de una lista
elevados al cuadrado.
print(al_cuadrado)
print(al_cuadrado_generador)
Y como hemos visto los valores pueden ser generados de la siguiente forma.
for i in al_cuadrado_generador:
print(i)
# Salda: 4, 16, 36, 64, 100
La diferencia entre el ejemplo usando list compregensions y generators es que en el caso de los
generadores, los valores no están almacenados en memoria, sino que se van generando al vuelo. Esta es
una de las principales ventajas de los generadores, ya que los elementos sólo son generados cuando se
piden, lo que hace que sean mucho más eficientes en lo relativo a la memoria.
Ventajas y ejemplos
Llegados a este punto tal vez te preguntes para qué sirven los generadores. Lo cierto es que aunque no
existieran, podría realizarse lo mismo creando una clase que implemente los
métodos __iter__() y __next__(). Veamos un ejemplo de una clase que genera los primeros 10 números
(0,9) al cuadrado.
class AlCuadrado:
def __init__(self):
self.i = 0
def __iter__(self):
return self
def __next__(self):
if self.i > 9:
raise StopIteration
result = self.i ** 2
self.i += 1
return result
Y de la misma forma que usábamos los generadores, podemos usar nuestra clase AlCuadrado. Creamos
un objeto de ella, y la iteramos. En cada iteración generará un nuevo valor nuevo hasta que se llegue al
final.
eleva_al_cuadrado = AlCuadrado()
for i in eleva_al_cuadrado:
print(i)
#0,1,4,9,16,25,36,49,64,81
Sin embargo esta forma es un tanto larga y tal vez confusa. Como hemos visto antes, podemos llegar a
hacer lo mismo en una sola línea de código. ¿Para que complicarse la vida?
Por otro lado, ya hemos mencionado que el uso de los generadores hace que no todos los valores estén
almacenados en memoria sino que sean generados al vuelo. Vamos a ver un ejemplo donde se puede ver
mejor. Supongamos que queremos sumar los primeros 100 números naturales (referencia). Una opción
podría ser crear una lista de todos ellos y después sumarla. En este caso, todos los valores son
almacenados en memoria, algo que podría ser un problema si por ejemplo intentamos sumar los
primeros 1e10 números.
def primerosn(n):
nums = []
for i in range(n):
nums.append(i)
return nums
print(sum(primerosn(100)))
# Salida: 4950
Sin embargo, podemos realizar lo mismo con un generador. En este caso los valores serán generados
uno por uno según se vayan necesitando.
def primerosn(n):
num = 0
for i in range(n):
yield num
num += 1
print(sum(primerosn(100)))
# Salida 4950
Nótese que es un ejemplo con fines didácticos, por lo que si quieres hacer esto, la mejor manera sería
usando un simple range() asumiendo que usas Python 3.
print(sum(range(100)))
# Salida: 4950
Corrutinas
>>>
... print('hello')
... print('world')
>>> asyncio.run(main())
hello
world
Tenga en cuenta que simplemente llamando a una corrutina no programará para que se ejecute:
>>>
>>> main()
Para ejecutar realmente una corutina, asyncio proporciona los siguientes mecanismos:
• import asyncio
• import time
• await asyncio.sleep(delay)
• print(what)
•
• print(f"started at {time.strftime('%X')}")
• print(f"finished at {time.strftime('%X')}")
• asyncio.run(main())
Salida esperada:
started at 17:13:52
hello
world
finished at 17:13:55
task1 = asyncio.create_task(
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# around 2 seconds.)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
Tenga en cuenta que la salida esperada ahora muestra que el fragmento de código se ejecuta 1
segundo más rápido que antes:
started at 17:14:32
hello
world
finished at 17:14:34
• task1 = tg.create_task(
• say_after(1, 'hello'))
• task2 = tg.create_task(
• say_after(2, 'world'))
• print(f"started at {time.strftime('%X')}")
• print(f"finished at {time.strftime('%X')}")
El tiempo y la salida deben ser los mismos que para la versión anterior.
Esperables
Decimos que un objeto es un objeto esperable si se puede utilizar en una expresión await. Muchas
API de asyncio están diseñadas para aceptar los valores esperables.
Hay tres tipos principales de objetos esperables: corrutinas, Tareas y Futures.
Corrutinas
Las corrutinas de Python son esperables y por lo tanto se pueden esperar de otras corrutinas:
import asyncio
return 42
asyncio.run(main())
Importante
En esta documentación se puede utilizar el término «corrutina» para dos conceptos estrechamente
relacionados:
Tareas
Cuando una corrutina se envuelve en una Tarea con funciones como asyncio.create_task() la
corrutina se programa automáticamente para ejecutarse pronto:
import asyncio
async def nested():
return 42
# with "main()".
task = asyncio.create_task(nested())
await task
asyncio.run(main())
Futures
Cuando un objeto Future es esperado significa que la corrutina esperará hasta que el Future se
resuelva en algún otro lugar.
Los objetos Future de asyncio son necesarios para permitir que el código basado en retro llamada
se use con async/await.
Los objetos Future, a veces expuestos por bibliotecas y algunas API de asyncio, pueden ser
esperados:
await function_that_returns_a_future_object()
await asyncio.gather(
function_that_returns_a_future_object(),
some_python_coroutine()
Un buen ejemplo de una función de bajo nivel que retorna un objeto Future
es loop.run_in_executor().
Creando Tareas
Envuelve una coroutine coro en una Task y programa su ejecución. Retorna el objeto Tarea.
Nota
asyncio.TaskGroup.create_task() es una alternativa más nueva que permite una espera conveniente
para un grupo de tareas relacionadas.
Importante
Guarde una referencia al resultado de esta función, para evitar que una tarea desaparezca en
medio de la ejecución. El bucle de eventos solo mantiene referencias débiles a las tareas. Una
tarea a la que no se hace referencia en ningún otro lugar puede ser recolectada por el recolector de
basura en cualquier momento, incluso antes de que se complete. Para tareas confiables en
segundo plano, de tipo «lanzar y olvidar», reúnalas en una colección:
background_tasks = set()
for i in range(10):
task = asyncio.create_task(some_coro(param=i))
# make each task remove its own reference from the set after
# completion:
task.add_done_callback(background_tasks.discard)
Cancelación de tareas
Las tareas se pueden cancelar de forma fácil y segura. Cuando se cancela una tarea, se
generará asyncio.CancelledError en la tarea en la próxima oportunidad.
Se recomienda que las corrutinas utilicen bloques try/finally para realizar de forma sólida la lógica
de limpieza. En caso de que asyncio.CancelledError se detecte explícitamente, generalmente
debería propagarse cuando se complete la limpieza. asyncio.CancelledError subclasifica
directamente a BaseException, por lo que la mayor parte del código no necesitará tenerlo en
cuenta.
Grupos de tareas
Los grupos de tareas combinan una API de creación de tareas con una forma conveniente y
confiable de esperar a que finalicen todas las tareas del grupo.
class asyncio.TaskGroup
Un asynchronous context manager que contiene un grupo de tareas. Las tareas se pueden agregar
al grupo usando create_task(). Se esperan todas las tareas cuando sale el administrador de
contexto.
Distinto en la versión 3.13: Close the given coroutine if the task group is not active.
Ejemplo:
task1 = tg.create_task(some_coro(...))
task2 = tg.create_task(another_coro(...))
La instrucción async with esperará a que finalicen todas las tareas del grupo. Mientras espera, aún
se pueden agregar nuevas tareas al grupo (por ejemplo, pasando tg a una de las corrutinas y
llamando a tg.create_task() en esa corrutina). Una vez finalizada la última tarea y salido del
bloque async with, no se podrán añadir nuevas tareas al grupo.
La primera vez que alguna de las tareas pertenecientes al grupo falla con una excepción que no
sea asyncio.CancelledError, las tareas restantes del grupo se cancelan. No se pueden añadir más
tareas al grupo. En este punto, si el cuerpo de la instrucción async with aún está activo (es decir,
aún no se ha llamado a __aexit__()), la tarea que contiene directamente la
instrucción async with también se cancela. El asyncio.CancelledError resultante interrumpirá
un await, pero no saldrá de la instrucción async with que lo contiene.
Una vez que todas las tareas han finalizado, si alguna tarea ha fallado con una excepción que no
sea asyncio.CancelledError, esas excepciones se combinan en
un ExceptionGroup o BaseExceptionGroup (según corresponda; consulte su documentación) que
luego se genera.
Si el cuerpo de la instrucción async with finaliza con una excepción (por lo que se llama
a __aexit__() con un conjunto de excepciones), esto se trata igual que si una de las tareas fallara: las
tareas restantes se cancelan y luego se esperan, y las excepciones de no cancelación se agrupan
en un grupo de excepción y se generan. La excepción pasada a __aexit__(), a menos que
sea asyncio.CancelledError, también se incluye en el grupo de excepciones. Se hace el mismo caso
especial para KeyboardInterrupt y SystemExit que en el párrafo anterior.
Task groups are careful not to mix up the internal cancellation used to «wake up»
their __aexit__() with cancellation requests for the task in which they are running made by other
parties. In particular, when one task group is syntactically nested in another, and both experience
an exception in one of their child tasks simultaneously, the inner task group will process its
exceptions, and then the outer task group will receive another cancellation and process its own
exceptions.
In the case where a task group is cancelled externally and also must raise an ExceptionGroup, it will
call the parent task’s cancel() method. This ensures that a asyncio.CancelledError will be raised at
the next await, so the cancellation is not lost.
Distinto en la versión 3.13: Improved handling of simultaneous internal and external cancellations
and correct preservation of cancellation counts.
While terminating a task group is not natively supported by the standard library, termination can be
achieved by adding an exception-raising task to the task group and ignoring the raised exception:
import asyncio
class TerminateTaskGroup(Exception):
raise TerminateTaskGroup()
await asyncio.sleep(sleep_time)
try:
group.create_task(job(1, 0.5))
group.create_task(job(2, 1.5))
await asyncio.sleep(1)
group.create_task(force_terminate_task_group())
except* TerminateTaskGroup:
pass
asyncio.run(main())
Expected output:
Task 1: start
Task 2: start
Task 1: done
Durmiendo
sleep() siempre suspende la tarea actual, permitiendo que se ejecuten otras tareas.
Establecer el retraso en 0 proporciona una ruta optimizada para permitir que se ejecuten otras
tareas. Esto puede ser utilizado por funciones de ejecución prolongada para evitar bloquear el
bucle de eventos durante toda la duración de la llamada a la función.
Ejemplo de una rutina que muestra la fecha actual cada segundo durante 5 segundos:
import asyncio
import datetime
loop = asyncio.get_running_loop()
end_time = loop.time() + 5.0
while True:
print(datetime.datetime.now())
break
await asyncio.sleep(1)
asyncio.run(display_date())
Si cualquier esperable en aws es una corrutina, se programa automáticamente como una Tarea.
Si todos los esperables se completan correctamente, el resultado es una lista agregada de valores
retornados. El orden de los valores de resultado corresponde al orden de esperables en aws.
Si return_exceptions es True, las excepciones se tratan igual que los resultados correctos y se
agregan en la lista de resultados.
Si gather() es cancelado, todos los esperables enviados (que aún no se han completado) también
se cancelan.
Nota
Una forma más moderna de crear y ejecutar tareas simultáneamente y esperar a que se completen
es asyncio.TaskGroup.
Ejemplo:
import asyncio
f=1
await asyncio.sleep(1)
f *= i
return f
L = await asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
print(L)
asyncio.run(main())
# Expected output:
# Task A: factorial(2) = 2
# Task B: factorial(3) = 6
# Task C: factorial(4) = 24
# [2, 6, 24]
Nota
If return_exceptions is false, cancelling gather() after it has been marked done won’t cancel any
submitted awaitables. For instance, gather can be marked done after propagating an exception to
the caller, therefore, calling gather.cancel() after catching an exception (raised by one of the
awaitables) from gather won’t cancel any other awaitables.
Un ejemplo común en el que esto resulta beneficioso son las rutinas que emplean almacenamiento
en caché o memorización para evitar E/S reales cuando sea posible.
Nota
asyncio.create_eager_task_factory(custom_task_constructor)
custom_task_constructor debe ser un callable con la firma que coincida con la firma
de Task.__init__. El invocable debe devolver un objeto compatible con asyncio.Task.
Esta función devuelve un callable destinado a ser utilizado como fábrica de tareas de un bucle de
eventos a través de loop.set_task_factory(factory)).
awaitable asyncio.shield(aw)
La declaración:
task = asyncio.create_task(something())
es equivalente a:
excepto que si la corrutina que lo contiene se cancela, la tarea que se ejecuta en something() no se
cancela. Desde el punto de vista de something(), la cancelación no ocurrió. Aunque su invocador
siga cancelado, por lo que la expresión «await» sigue generando un CancelledError.
Si something() se cancela por otros medios (es decir, desde dentro de sí mismo) eso también
cancelaría shield().
Si se desea ignorar por completo la cancelación (no se recomienda) la función shield() debe
combinarse con una cláusula try/except, como se indica a continuación:
task = asyncio.create_task(something())
try:
except CancelledError:
res = None
Importante
Guarde una referencia a las tareas pasadas a esta función, para evitar que una tarea desaparezca a
mitad de la ejecución. El bucle de eventos solo mantiene referencias débiles a las tareas. Una tarea
a la que no se hace referencia en ningún otro lugar puede ser recolectada por el recolector de
basura en cualquier momento, incluso antes de que se complete.
Tiempo agotado
asyncio.timeout(delay)
Un asynchronous context manager que se puede usar para limitar la cantidad de tiempo que se
pasa esperando algo.
delay puede ser None o un número flotante/int de segundos de espera. Si delay es None, no se
aplicará ningún límite de tiempo; esto puede ser útil si se desconoce el retraso cuando se crea el
administrador de contexto.
Ejemplo:
await long_running_task()
Nota
try:
async with asyncio.timeout(10):
await long_running_task()
except TimeoutError:
El administrador de contexto producido por asyncio.timeout() puede reprogramarse para una fecha
límite diferente e inspeccionarse.
class asyncio.Timeout(when)
when debe ser un tiempo absoluto en el que el contexto debe expirar, según lo medido por el reloj
del bucle de eventos:
• Si when < loop.time(), el tiempo de espera se activará en la próxima iteración del bucle de
eventos
Retorna la fecha límite actual, o None si la fecha límite actual no está establecida.
expired() → bool
Ejemplo:
try:
new_deadline = get_running_loop().time() + 10
cm.reschedule(new_deadline)
await long_running_task()
except TimeoutError:
pass
if cm.expired():
asyncio.timeout_at(when)
Similar a asyncio.timeout(), excepto que when es el tiempo absoluto para dejar de esperar, o None.
Ejemplo:
loop = get_running_loop()
deadline = loop.time() + 20
try:
await long_running_task()
except TimeoutError:
timeout puede ser None o punto flotante o un número entero de segundos a esperar.
Si timeout es None, se bloquea hasta que Future se completa.
Ejemplo:
await asyncio.sleep(3600)
print('yay!')
try:
except TimeoutError:
print('timeout!')
asyncio.run(main())
# Expected output:
# timeout!
Distinto en la versión 3.7: Cuando se cancela aw debido a un tiempo de espera, wait_for espera a
que se cancele aw. Anteriormente, lanzaba TimeoutError inmediatamente.
Esperando primitivas
Ejecuta instancias Future y Task en el iterable aws simultáneamente y bloquea hasta la condición
especificada por return_when.
Uso:
timeout (un punto flotante o int), si se especifica, se puede utilizar para controlar el número
máximo de segundos que hay que esperar antes de retornar.
Tenga en cuenta que esta función no lanza TimeoutError. Los futuros o las tareas que no se realizan
cuando se agota el tiempo de espera simplemente se retornan en el segundo conjunto.
return_when indica cuándo debe retornar esta función. Debe ser una de las siguientes constantes:
Constante Descripción
Distinto en la versión 3.11: Está prohibido pasar objetos de rutina a wait() directamente.
Distinto en la versión 3.12: Se agregó soporte para generadores que generan tareas.
asyncio.as_completed(aws, *, timeout=None)
Run awaitable objects in the aws iterable concurrently. The returned object can be iterated to
obtain the results of the awaitables as they finish.
if earliest_connect is ipv6_connect:
else:
During asynchronous iteration, implicitly-created tasks will be yielded for supplied awaitables that
aren’t tasks or futures.
When used as a plain iterator, each iteration yields a new coroutine that returns the result or raises
the exception of the next completed awaitable. This pattern is compatible with Python versions
older than 3.13:
A TimeoutError is raised if the timeout occurs before all awaitables are done. This is raised by
the async for loop during asynchronous iteration or by the coroutines yielded during plain iteration.
Obsoleto desde la versión 3.10: Se emite una advertencia de obsolescencia si no todos los objetos
en espera en el iterable aws son objetos de tipo Future y no hay un bucle de eventos en ejecución.
Distinto en la versión 3.12: Se agregó soporte para generadores que generan tareas.
Distinto en la versión 3.13: The result can now be used as either an asynchronous iterator or as a
plain iterator (previously it was only a plain iterator).
Ejecutando en hilos
Cualquier *args y **kwargs suministrados para esta función se pasan directamente a func. Además,
el current contextvars.Context se propaga, lo que permite acceder a las variables de contexto del
subproceso del bucle de eventos en el subproceso separado.
Retorna una corrutina que se puede esperar para obtener el resultado final de func.
Esta función de rutina está diseñada principalmente para ejecutar funciones/métodos vinculados a
IO que, de otro modo, bloquearían el bucle de eventos si se ejecutaran en el subproceso principal.
Por ejemplo:
def blocking_io():
time.sleep(1)
await asyncio.gather(
asyncio.to_thread(blocking_io),
asyncio.sleep(1))
asyncio.run(main())
# Expected output:
Nota
Debido a GIL, asyncio.to_thread() generalmente solo se puede usar para hacer que las funciones
vinculadas a IO no bloqueen. Sin embargo, para los módulos de extensión que lanzan GIL o
implementaciones alternativas de Python que no tienen uno, asyncio.to_thread() también se puede
usar para funciones vinculadas a la CPU.
asyncio.run_coroutine_threadsafe(coro, loop)
Esta función está pensada para llamarse desde un hilo del SO diferente al que se ejecuta el bucle
de eventos. Ejemplo:
# Create a coroutine
assert future.result(timeout) == 3
Si se lanza una excepción en la corrutina, el Future retornado será notificado. También se puede
utilizar para cancelar la tarea en el bucle de eventos:
try:
result = future.result(timeout)
except TimeoutError:
future.cancel()
else:
A diferencia de otras funciones asyncio, esta función requiere que el argumento loop se pase
explícitamente.
Introspección
asyncio.current_task(loop=None)
Retorna la instancia Task actualmente en ejecución o None si no se está ejecutando ninguna tarea.
asyncio.all_tasks(loop=None)
asyncio.iscoroutine(obj)
Las tareas se utilizan para ejecutar corrutinas en bucles de eventos. Si una corrutina aguarda en un
Future, la Tarea suspende la ejecución de la corrutina y espera la finalización del Future. Cuando el
Future termina, se reanuda la ejecución de la corrutina envuelta.
Los bucles de eventos usan la programación cooperativa: un bucle de eventos ejecuta una tarea a
la vez. Mientras una Tarea espera para la finalización de un Future, el bucle de eventos ejecuta otras
tareas, retorno de llamada o realiza operaciones de E/S.
Utilice la función de alto nivel asyncio.create_task() para crear Tareas, o las funciones de bajo
nivel loop.create_task() o ensure_future(). Se desaconseja la creación de instancias manuales de
Tareas.
Para cancelar una Tarea en ejecución, utilice el método cancel(). Llamarlo hará que la tarea lance
una excepción CancelledError en la corrutina contenida. Si una corrutina está esperando en un
objeto Future durante la cancelación, se cancelará el objeto Future.
cancelled() se puede utilizar para comprobar si la Tarea fue cancelada. El método retorna True si la
corrutina contenida no suprimió la excepción CancelledError y se canceló realmente.
Un argumento eager_start opcional de solo palabra clave permite iniciar con entusiasmo la
ejecución de asyncio.Task en el momento de la creación de la tarea. Si se establece en True y el
bucle de eventos se está ejecutando, la tarea comenzará a ejecutar la corrutina inmediatamente,
hasta la primera vez que la corrutina se bloquee. Si la rutina regresa o se activa sin bloquearse, la
tarea finalizará con entusiasmo y saltará la programación al bucle de eventos.
done()
result()
If the Task’s result isn’t yet available, this method raises an InvalidStateError exception.
exception()
Si la Tarea aún no está terminada, este método lanza una excepción InvalidStateError.
add_done_callback(callback, *, context=None)
Agrega una retro llamada que se ejecutará cuando la Tarea esté terminada.
Este método solo se debe usar en código basado en retrollamada de bajo nivel.
remove_done_callback(callback)
Este método solo se debe usar en código basado en retrollamada de bajo nivel.
get_stack(*, limit=None)
El argumento opcional limit establece el número máximo de marcos que se retornarán; de forma
predeterminada se retornan todos los marcos disponibles. El orden de la lista retornada varía en
función de si se retorna una pila o un traceback: se retornan los marcos más recientes de una pila,
pero se retornan los marcos más antiguos de un traceback. (Esto coincide con el comportamiento
del módulo traceback.)ss
Esto produce una salida similar a la del módulo traceback para los marcos recuperados
por get_stack().
El argumento file es un flujo de E/S en el que se escribe la salida; por defecto, la salida se escribe
en sys.stdout.
get_coro()
Nota
Esto devolverá None para las tareas que ya se han completado con entusiasmo. Consulte el Eager
Task Factory.
Distinto en la versión 3.12: La ejecución ansiosa de la tarea recientemente agregada significa que el
resultado puede ser None.
get_context()
get_name()
set_name(value)
El argumento value puede ser cualquier objeto, que luego se convierte en una cadena.
En la implementación de Task predeterminada, el nombre será visible en la salida repr() de un
objeto de tarea.
cancel(msg=None)
Esto hace que una excepción CancelledError sea lanzada a la corrutina contenida en el próximo
ciclo del bucle de eventos.
Distinto en la versión 3.11: El parámetro msg se propaga desde la tarea cancelada a su espera.
try:
await asyncio.sleep(3600)
except asyncio.CancelledError:
raise
finally:
task = asyncio.create_task(cancel_me())
# Wait for 1 second
await asyncio.sleep(1)
task.cancel()
try:
await task
except asyncio.CancelledError:
asyncio.run(main())
# Expected output:
cancelled()
La tarea se cancela cuando se solicitó la cancelación con cancel() y la corrutina contenida propagó
la excepción CancelledError que se le ha lanzado.
uncancel()
Tenga en cuenta que una vez que se completa la ejecución de una tarea cancelada, las llamadas
posteriores a uncancel() no son efectivas.
Este método lo usan los componentes internos de asyncio y no se espera que lo use el código del
usuario final. En particular, si una Tarea se cancela con éxito, esto permite que elementos de
concurrencia estructurada como Grupos de tareas y asyncio.timeout() continúen ejecutándose,
aislando la cancelación al bloque estructurado respectivo. Por ejemplo:
try:
await make_request()
await make_another_request()
except TimeoutError:
await unrelated_code()
If end-user code is, for some reason, suppressing cancellation by catching CancelledError, it needs
to call this method to remove the cancellation state.
When this method decrements the cancellation count to zero, the method checks if a
previous cancel() call had arranged for CancelledError to be thrown into the task. If it hasn’t been
thrown yet, that arrangement will be rescinded (by resetting the internal _must_cancel flag).
Distinto en la versión 3.13: Changed to rescind pending cancellation requests upon reaching zero.
cancelling()
Tenga en cuenta que si este número es mayor que cero pero la tarea aún se está
ejecutando, cancelled() aún retornará False. Esto se debe a que este número se puede reducir
llamando a uncancel(), lo que puede provocar que la tarea no se cancele después de todo si las
solicitudes de cancelación se reducen a cero.
Este método lo utilizan las partes internas de asyncio y no se espera que lo utilice el código del
usuario final. Consulte uncancel() para obtener más detalles.
Caching de Funciones
El caché es un término muy usado en informática, y hace referencia al almacenamiento de
resultados previos para su posterior reutilización, lo que permite reducir el tiempo de respuesta.
Por ejemplo, si llamamos a una función con un determinado parámetro y acto seguido realizamos
la misma llamada, sería interesante reutilizar el primer resultado para no tener que calcularlo otra
vez. Existen por lo tanto dos posibilidades:
Por suerte, Python nos permite añadir caching a nuestras funciones, pero antes de implementarlo
es conveniente hacer un análisis sobre nuestro programa y determinar si merece la pena. Algunas
cosas a tener en cuenta:
• Es necesario conocer (a nivel estadístico) la distribución de los argumentos con los que se
llama la función. Si la función bajo estudio se llama con valores muy dispares y apenas
repetidos, el caching poco ayudará, ya que apenas tendremos un cache hit.
def es_primo(num):
if num % n == 0:
return False
return True
La primera forma de realizarlo es usando un diccionario como caché. Nótese que este es un
ejemplo didáctico, y que obvia algunos factores. Como puedes ver tenemos claramente
diferenciado el cache hit y el cache miss. Si el valor no está en el caché se calcula y se devuelve.
def es_primo_concache(num, _cache={}):
_cache[num] = True
if num % n == 0:
_cache[num] = False
break
return _cache[num]
Dado que es_primo es bastante intensivo en cálculo, cuando usamos números grandes el ahorro
puede ser muy significativo. En el siguiente código podemos ver como la primera vez que
ejecutamos la función, se tardan 3.5 segundos, ya que el resultado tiene que ser calculado. Sin
embargo la segunda vez que la llamamos con la misma entrada, tenemos un cache hit, por lo que el
valor ya no es calculado sino recuperado del caché, tardando microsegundos.
import time
tic = time.time()
es_primo_concache(25565479)
print(time.time() - tic)
tic = time.time()
es_primo_concache(25565479)
print(time.time() - tic)
# 3.5551438331604004
# 4.0531158447265625e-06
La segunda forma de realizarlo, y un poco más sofisticada es usando lru_cache, un decorador que
viene con la librería estándar functools. La mayor ventaja es que no necesitamos modificar la
función. Nótese que maxsize nos permite indicar el número máximo de valores que queremos
almacenar en el caché.
def es_primo_concache(num):
if num % n == 0:
return False
return True
Por lo tanto si ahora llamamos a nuestra función con los mismos valores, podemos ver como la
primera vez tarda 3.9 segundos, pero la segunda apenas tarda unos microsegundos.
import time
tic = time.time()
es_primo_concache(25565479)
print(time.time() - tic)
tic = time.time()
es_primo_concache(25565479)
print(time.time() - tic)
# 3.9316678047180176
# 3.0994415283203125e-06
En el caso de que queramos limpiar el caché de nuestra función, podemos realizar lo siguiente.
es_primo_concache.cache_clear()
¿Un lenguaje de programación sin bucles? Aunque pueda parecer una locura, también tiene sus
ventajas, y ofrece ciertas características muy importantes que veremos a continuación.
A pesar de que Python no es un lenguaje puramente funcional, nos ofrece algunas primitivas
propias de lenguajes funcionales, como map, filter y reduce. Todas ellas ofrecen una alternativa al
uso de bucles para resolver ciertos problemas. Veamos unos ejemplos.
map en Python
• Una función, que será aplicada a cada uno de los elementos de la lista o iterable anterior.
Nos devuelve una nueva lista donde todos y cada uno de los elementos de la lista original han sido
pasados por la función.
map(funcion_a_aplicar, entrada_iterable)
Imaginemos que queremos multiplicar por dos todos los elementos de una lista. La primera forma
que se nos podría ocurrir sería la siguiente. Nótese que también podría usarse list comprehension,
pero eso lo dejamos para otro artículo.
lista = [1, 2, 3, 4, 5]
lista_pordos = []
for l in lista:
lista_pordos.append(l*2)
print(lista_pordos)
# [2, 4, 6, 8, 10]
Pero veamos como darle un enfoque funcional. Como hemos dicho, map toma una función y un
iterable como entrada, aplicando la función a cada elemento. Entonces podemos definir una
función por_dos, que pasaremos a map junto con nuestra lista inicial.
lista = [1, 2, 3, 4, 5]
def por_dos(x):
return x * 2
print(list(lista_pordos))
# [2, 4, 6, 8, 10]
Como podemos observar, ya no usamos bucles. Simplemente le pasamos a map la función y la lista
y ya hace el trabajo por nosotros. Dado que por_dos se trata de una función muy sencilla, es posible
usar funciones lambda para compactar un poco el código, quedando lo siguiente:
lista = [1, 2, 3, 4, 5]
print(list(lista_pordos))
# [2, 4, 6, 8, 10]
filter en Python
La función filter también recibe una función y una lista pero el resultado es la lista inicial filtrada.
Es decir, se pasa cada elemento de la lista por la función, y sólo si su resultado es True, se incluye
en la nueva lista.
filter(funcion_filtrar, entrada_iterable)
Al igual que hacíamos antes, usamos las funciones lambda que nos permiten declarar y asignar
una función en la misma línea de código. En el siguiente ejemplo filtramos los números pares.
print(list(pares))
# [4, 16, 8]
def es_par(x):
return x % 2 == 0
print(list(pares))
# [4, 16, 8]
Una vez más, resaltar que no estamos usando bucles. Simplemente damos la entrada y la función a
aplicar a cada elemento, y filter se encarga del resto. Esta es una de las características clave de la
programación funcional. Se centra más en el qué hacer que en el cómo hacerlo. Para ello se usan
funciones predefinidas como las que estamos viendo, a las que sólo tenemos que pasar las
entradas y hacer el trabajo por nosotros.
reduce en Python
Por último, podemos usar reduce para reducir todos los elementos de la entrada a un único
valor aplicando un determinado criterio. Por ejemplo, podemos sumar todos los elementos de una
lista de la siguiente manera.
lista = [1, 2, 3, 4, 5]
print(suma) # 15
lista = [1, 2, 3, 4, 5]
print(suma) # 15
lista = [1, 2, 3, 4, 5]
print(multiplicacion) # 120
Es importante notar que la función recibe dos argumentos: el acumulador y cada uno de los valores
de la lista.
• El valor es cada uno de los elementos de nuestra lista, que en nuestro caso vamos
añadiendo al acumulador.
El uso de reduce es especialmente útil cuando tenemos por ejemplo una lista de diccionarios y
queremos sumar todos los valores de un campo en concreto. Veamos un ejemplo donde
calculamos la edad media de varias personas.
personas = [
print(suma_edad/len(personas)) # 28.0
Nótese que llamamos a reduce con un valor adicional 0, que es el valor inicial del acumulador. Una
vez más, hemos resuelto un problema en el que tradicionalmente usaríamos bucles con las
primitivas de la programación funcional.
Una vez entendido el funcionamiento de map, filter y reduce, ya tenemos unas nociones básicas de
la programación funcional. Sus características más importantes son las siguientes:
• Se dice que en la programación funcional, las funciones son “ciudadanas de primera clase”,
lo que nos viene a decir que prácticamente todo se hace con funciones, y no con bucles.
• La programación funcional prefiere también las funciones puras, es decir, funciones sin side
effects. Las funciones puras no dependen de variables externas o globales. Esto significa
que para las mismas entradas, siempre se producen las mismas salidas.
• Por otro lado, en la programación funcional se prefiere variables inmutables, lo que significa
que una vez creadas no pueden ser modificadas. Esto es algo que verdaderamente ahorra
problemas, aunque la eficiencia puede ser discutible.
• En general, se considera que los lenguajes de programación funcionales son más seguros, y
ofrecen formal verification.
Por último, resaltar una vez más que aunque Python no es un lenguaje puramente funcional, ofrece
algunas funciones propias de lenguajes funcionales como map, filter y reduce. Si estás interesado
en más, puedes echar un vistazo a el paquete itertools.
• Y calcular la media
personas = [
media_edad = suma_edades/(len(hombres))
print(media_edad) # 33.0
Tal vez no muy legible, pero todo lo anterior se podrá expresar en una única línea de código.
print(media_edades) # 33.0
OPERADORES
• Operadores de asignación
• 📗📗 Operadores Aritméticos
• 📗📗 Operadores Relacionales
• 📗📗 Operadores Lógicos
• 📙📙 Operadores Bitwise
• 📙📙 Operadores Identidad
• 📙📙 Operadores Membresía
• 📙📙 Operador walrus
Operadores de asignación
Anteriormente hemos visto los operadores aritméticos, que usaban dos números para calcular una
operación aritmética (como suma o resta) y devolver su resultado. En este caso, los operadores de
asignación o assignment operators nos permiten realizar una operación y almacenar su resultado
en la variable inicial. Podemos ver como realmente el único operador nuevo es el = . El resto son
abreviaciones de otros operadores que habíamos visto con anterioridad. Ponemos un ejemplo
con x=7
= x=7 x=7
+= x+=2 x=x+2 = 7
-= x-=2 x=x-2 = 5
*= x*=2 x=x*2 = 14
%= x%=2 x=x%2 = 1
|= x|=2 x=x|2 = 7
^= x^=2 x=x^2 = 5
a=7; b=2
print("Operadores de asignación")
x=a; x+=b; print("x+=", x) # 9
x=a; x-=b; print("x-=", x) # 5
x=a; x*=b; print("x*=", x) # 14
x=a; x/=b; print("x/=", x) # 3.5
x=a; x%=b; print("x%=", x) # 1
x=a; x//=b; print("x//=", x) # 3
x=a; x**=b; print("x**=", x) # 49
x=a; x&=b; print("x&=", x) # 2
x=a; x|=b; print("x|=", x) # 7
x=a; x^=b; print("x^=", x) # 5
x=a; x>>=b; print("x>>=", x) # 1
x=a; x<<=b; print("x<<=", x) # 28
Operador =
El operador = prácticamente no necesita explicación, simplemente asigna a la variable de la
izquierda el contenido que le ponemos a la derecha. Ponemos en negrita variable porque si
hacemos algo del tipo 3=5 tendremos un error. Como siempre, nunca te fíes de nada y experimenta
con ello.
x=2 # Uso correcto del operador =
print(x) # 2
#3=5 # Daría error, 3 no es una variable
Tal vez pienses que el operador = es trivial y apenas merezca explicación, pero es importante
explorar los límites del lenguaje. Si sabes lo que es un puntero, o una referencia tal vez el ejemplo
siguiente tenga sentido para tí. Vamos a ver, si todo lo que hemos visto anteriormente es
cierto, a=[1, 2, 3] asigna [1, 2, 3] a a , por lo que si no tocamos a , el valor de a deberá ser
siempre [1, 2, 3] . Bueno, pues en el siguiente ejemplo vemos como eso no es así, el valor de a ha
cambiado. Se podría decir que no es lo mismo x=3 con un número que x=[1, 2, 3] con una lista.
No te preocupes si no lo has seguido, en otros capítulos lo explicaremos mejor.
a = [1, 2, 3]
b = a
b += [4]
print(a)
# [1, 2, 3, 4]
Operador +=
Como podemos ver, todos los operadores de asignación no son más que atajos para escribir otros
operadores de manera más corta, y asignar su resultado a la variable inicial. El
operador += en x+=1 es equivalente a x=x+1 . Sabiendo esto, sería justo preguntarse ¿realmente
merece la pena crear un operador nuevo que hace algo que ya podemos hacer pero de manera mas
corta? Bien, la pregunta no es fácil de responder y en cierto modo viene heredado de lenguajes
como C que en los años 1970s introdujeron esto.
x=5 # Ejemplo de como incrementar
x+=1 # en una unidad x
print(x)
# 6
Para saber más: Aunque se podría decir que el operador x+=1 es igual que x=x+1, no es del todo
cierto. De hecho el operador que Python invoca por debajo es "__iadd__" en el primer caso frente
a "__add__" para el segundo. A efectos prácticos, se podría considerar lo mismo, pero aquí puedes
leer más sobre esto
Se puede jugar un poco con el operador += y aplicarlo sobre variables que no necesariamente son
números. Como vimos en otros capítulos, se podría emplear sobre una lista.
x=[1,2,3] # En este caso la x es una lista
x+=[4,5] # Se aplica el operador sobre otra lista
print(x) # Y el resultado de la unión de ambas
# [1, 2, 3, 5, 6]
Es muy importante, que si x es una lista, no podemos aplicar el operador += con un elemento que
no sea una lista, como por ejemplo, un número. El siguiente código daría error porque el operador
no esta definido para un elemento lista y otro entero.
x=[1,2,3] #
#x+=3 # ERROR! TypeError
Operador -=
El operador -= es equivalente a restar y asignar el resultado a la variable inicial. Es decir, x-=1 es
equivalente a x=x-1 . Si vienes de otros lenguajes de programación, tal vez conozcas la forma x-- ,
pero en Python no existe. El operador es muy usado para decrementar el valor de una variable.
i = 5
i -= 1
print(i) # 4
Y algo que nunca se haría en la realidad, pero nos permite explorar los límites del lenguaje, sería
restar -1, lo que equivale a sumar uno. Pero de verdad, no hagas esto, en serio.
i = 0
i-=-1 # Aumenta el contador
print(i) # 1
Operador *=
El operador *= equivale a multiplicar una variable por otra y almacenar el resultado en la primera,
es decir x*=2 equivale a x=x*2 . Hasta ahora hemos usado todos los operadores de asignación con
una variable y un número, pero es totalmente correcto hacerlo con dos variables.
a=10; b=2 # Inicializamos a 10 y 20
a*=b # Usando dos variables
print(a) # 20
Operador /=
El operador /= equivale a dividir una variable por otra y almacenar el resultado en la primera, es
decir, x/=2 equivale a x=x/2 . Acuérdate que en otros capítulos vimos como 5/3 en versiones
antiguas de Python, podía causar problemas ya que el resultado no era un numero entero. En el
siguiente ejemplo podemos ver como Python hace el trabajo por nosotros, y cambia el tipo de la
variable x de lo que inicialmente era int a un float con el objetivo de que el nuevo valor pueda
ser almacenado.
x = 10
print(type(x)) # <class 'int'>
x/=3
print(type(x)) # <class 'float'>
Operador %=
El operador %= equivale a hacer el módulo de la división de dos variables y almacenar su resultado
en la primera.
x = 3
x%=2
print(x) # 1
Una curiosidad a tener en cuenta, es que el operador módulo tiene diferentes comportamientos en
Python del que tiene en otros lenguajes como C cuando se usan números negativos tanto de
dividendo como de divisor. Así que ten cuidado si haces cosas como las siguientes.
print(-5%-3) # -2
print(5%-3) # -1
print(-5%3) # 1
print(5%3) # 2
Operador //=
El operador //= realiza la operación cociente entre dos variables y almacena el resultado en la
primera. El equivalente de x//=2 sería x=x//2 .
x=5 # El resultado es el cociente
x//=3 # de la división
print(x) # 1
Operador **=
El operador **= realiza la operación exponente del primer número elevado al segundo, y almacena
el resultado en la primera variable. El equivalente de x**=2 sería x=x**2 .
x=5 # Eleva el número al cuadrado
x**=2 # y guarda el resultado en la misma
print(x) # 25
Otro ejemplo similar, sería empleando un exponente negativo, algo que es totalmente válido y
equivale matemáticamente al inverso del número elevado al exponente en positivo. Dicho de otra
forma, x−2 equivale a 1/x2.
x=5 # Elevar 5 a -2 equivale a dividir
x**=-2 # uno entre 25.
print(x) # 0.04
Operador &=
El operador &= realiza la comparación & bit a bit entre dos variables y almacena su resultado en la
primera. El equivalente de x&=1 sería x=x&1
a = 0b101010
a&= 0b111111
print(bin(a))
# 0b101010
Operador |=
El operador |= realiza el operador | elemento a elemento entre dos variables y almacena su
resultado en la primera. El equivalente de x|=2 sería x=x|2
a = 0b101010
a|= 0b111111
print(bin(a))
# 0b111111
Operador ^=
El operador ^= realiza el operador ^ elemento a elemento entre dos variables y almacena su
resultado en la primera. El equivalente de x^=2 sería x=x^2
a = 0b101010
a^= 0b111111
print(bin(a))
# 0b10101
Operador »=
El operador >>= es similar al operador >> pero permite almacenar el resultado en la primera
variable. Por lo tanto x>>=3 sería equivalente a x=x>>3
x = 10
x>>=1
print(x) # 5
Es importante tener cuidado y saber el tipo de la variable x antes de aplicar este operador, ya que
se podría dar el caso de que x fuera una variable tipo float . En ese caso, tendríamos un error
porque el operador >> no esta definido para float .
x=10.0 # Si la x es float
print(type(x)) # <class 'float'>
#x>>=1 # ERROR! TypeError
Operador «=
Muy similar al anterior, <<= aplica el operador << y almacena su contenido en la primera variable.
El equivalente de x<<=1 sería x=x<<1
x=10 # Inicializamos a 10
x<<=1 # Desplazamos 1 a la izquierda
print(x) # 20
Sería justo pensar que si << realiza un desplazamiento de bits a la izquierda y >> lo realiza a la
derecha, tal vez un desplazamiento << una unidad, podría equivaler a -1 desplazamiento a la
derecha.
#x<<=-1 # ERROR! Python no define un desplazamiento negativo a la izquierda
#x>>=-1 # ERROR! Python no define un desplazamiento negativo a la derecha
Operadores aritméticos
Los operadores aritméticos o arithmetic operators son los más comunes que nos podemos
encontrar, y nos permiten realizar operaciones aritméticas sencillas, como pueden ser la suma,
resta o exponente. A continuación, condensamos en la siguiente tabla todos ellos con un ejemplo,
donde x=10 y y=3.
+ Suma x + y = 13
- Resta x-y=7
* Multiplicación x * y = 30
% Módulo x%y = 1
** Exponente x ** y = 1000
// Cociente 3
x = 10; y = 3
print("Operadores aritméticos")
Operador +
El operador + suma los números presentes a la izquierda y derecha del operador. Recalcamos lo de
números porque no tendría sentido sumar dos cadenas de texto, o dos listas, pero en Python es
posible hacer este tipo de cosas.
print(10 + 3) # 13
Es posible sumar también dos cadenas de texto, pero la suma no será aritmética, sino que se
unirán ambas cadenas en una. También se pueden sumar dos listas, cuyo resultado es la unión de
ambas.
print("2" + "2") # 22
Operador -
El operador - resta los números presentes a la izquierda y derecha del operador. A diferencia el
operador + en este caso no podemos restar cadenas o listas.
print(10 - 3) #7
Operador *
print(10 * 3) #30
Como también pasaba con el operador + podemos hacer cosas “raras” con *. Explicar porque pasan
estas cosas es un poquito más complejo, por lo que lo dejamos para otro capítulo, donde
explicaremos como definir el comportamiento de determinados operadores para nuestras clases.
print("Hola" * 3) #HolaHolaHola
Operador /
El operador / divide los números presentes a la izquierda y derecha del operador. Un aspecto
importante a tener en cuenta es que si realizamos una división cuyo resultado no es entero (es
decimal) podríamos tener problemas. En Python 3 esto no supone un problema porque el mismo se
encarga de convertir los números y el resultado que se muestra si es decimal.
print(10/3) #3.3333333333333335
print(1/2) #0.5
Sin embargo, en Python 2, esto hubiera tenido un resultado diferente. El primer ejemplo 10/3=3 y el
segundo 1/2=0. El comportamiento realmente sería el de calcular el cociente y no la división.
Para saber más: Si quieres saber más acerca de este cambio del operador de división, puedes leer
la la PEP238
Operador %
El operador % realiza la operación módulo entre los números presentes a la izquierda y la derecha.
Se trata de calcular el resto de la división entera entre ambos números. Es decir, si dividimos 10
entre 3, el cociente sería 3 y el resto 1. Ese resto es lo que calcula el módulo.
print(10%3) # 1
print(10%2) # 0
Operador **
print(10**3) #1000
print(2**2) #4
Si ya has usado alguna vez Python, tal vez hayas oido hablar de la librería math. En esta librería
también tenemos una función llamada pow() que es equivalente al operador **.
import math
Para saber más: Puedes consultar más información de la librería math en la documentación oficial
de Python
Operador //
Por último, el operador // calcula el cociente de la división entre los números que están a su
izquierda y derecha.
print(10//3) #3
print(10//10) #1
Tal vez te hayas dado cuenta que el operador cociente // está muy relacionado con el operador
módulo %. Volviendo a las lecciones del colegio sobre la división, recordaremos que el
Dividendo D es igual al divisor d multiplicado por el cociente c y sumado al resto r, es decir D=d*c+r.
Se puede ver como en el siguiente ejemplo, 10//3 es el cociente y 10%3 es el resto. Al aplicar la
fórmula, verificamos que efectivamente 10 era el dividendo.
Orden de aplicación
En los ejemplos anteriores simplemente hemos aplicado un operador a dos números sin
mezclarlos entre ellos. También es posible tener varios operadores en la misma línea de código, y
en este caso es muy importante tener en cuenta las prioridades de cada operador y cual se aplica
primero. Ante la duda siempre podemos usar paréntesis, ya que todo lo que está dentro de un
paréntesis se evaluará conjuntamente, pero es importante saber las prioridades.
El orden de prioridad sería el siguiente para los operadores aritméticos, siendo el primero el de
mayor prioridad:
• () Paréntesis
• ** Exponente
• -x Negación
• + - Suma, Resta
# 80
# 53
#10.4
#-16
Para saber más: Si quieres saber más sobre el orden de prioridad de diferentes operadores, aquí
tienes la documentación oficial de Python
Operadores relacionales
Los operadores relacionales, o también llamados comparison operators nos permiten saber
la relación existente entre dos variables. Se usan para saber si por ejemplo un número es mayor o
menor que otro. Dado que estos operadores indican si se cumple o no una operación, el valor que
devuelven es True o False. Veamos un ejemplo con x=2 e y=3
== Igual x == y = False
!= Distinto x != y = True
x=2; y=3
print("Operadores Relacionales")
Operador ==
El operador == permite comparar si las variables introducidas a su izquierda y derecha son iguales.
Muy importante no confundir con =, que es el operador de asignación.
print(4==4) # True
print(4==5) # False
print(4==4.0) # True
print(0==False) # True
print("asd"=="asd") # True
print("asd"=="asdf") # False
print(2=="2") # False
Operador !=
El operador != devuelve True si los elementos a comparar son iguales y False si estos son distintos.
De hecho, una vez definido el operador ==, no sería necesario ni explicar != ya que hace
exactamente lo contrario. Definido primero, definido el segundo. Es decir, si probamos con los
mismos ejemplo que el apartado anterior, veremos como el resultado es el contrario, es
decir False donde era True y viceversa.
print(4!=4) # False
print(4!=5) # True
print(4!=4.0) # False
print(0!=False) # False
print("asd"!="asd") # False
print("asd"!="asdf") # True
print(2!="2") # True
El operador > devuelve True si el primer valor es mayor que el segundo y False de lo contrario.
print(5>3) # True
print(5>5) # False
Algo bastante curioso, es como Python trata al tipo booleano. Por ejemplo, podemos ver
como True es igual a 1, por lo que podemos comprar el tipo True como si de un número se tratase.
print(True==1) # True
print(True>0.999) # True
Para saber más: De hecho, el tipo bool en Python hereda de la clase int. Si quieres saber más
acerca del tipo bool en Python puedes leer la PEP285
También se pueden comparar listas. Si los elementos de la lista son numéricos, se comparará
elemento a elemento.
Operador <
El operador < devuelve True si el primer elemento es mayor que el segundo. Es totalmente válido
aplicar operadores relacionales como < sobre cadenas de texto, pero el comportamiento es un
tanto difícil de ver a simple vista. Por ejemplo abc es menor que abd y A es menor que a
print("A"<"a") # True
Para el caso de A y a la explicación es muy sencilla, ya que Python lo que en realidad está
comparando es el valor entero Unicode que representa tal caracter. La función ord() nos da ese
valor. Por lo tanto cuando hacemos "A"<"a" lo que en realidad hacemos es comprar dos números.
print(ord('A')) # 65
print(ord('a')) # 97
Para saber más: En el siguiente enlace tienes más información de como Python compara variables
que no son números.
Operador >=
Similar a los anteriores, >= permite comparar si el primer elemento es mayor o igual que es
segundo, devolviendo True en el caso de ser cierto.
print(3>=3) # True
Operdor <=
De la misma manera, <= devuelve True si el primer elemento es menor o igual que el segundo. Nos
podemos encontrar con cosas interesantes debido a la precisión numérica existente al representar
valores, como el siguiente ejemplo.
print(3<=2.99999999999999999)
Operadores lógicos
Los operadores lógicos o logical operators nos permiten trabajar con valores de tipo booleano. Un
valor booleano o bool es un tipo que solo puede tomar valores True o False. Por lo tanto, estos
operadores nos permiten realizar diferentes operaciones con estos tipos, y su resultado será otro
booleano. Por ejemplo, True and True usa el operador and, y su resultado será True. A continuación
lo explicaremos mas en detalle.
and Devuelve True si ambos elementos son True True and True = True
Operador and
El operador and evalúa si el valor a la izquierda y el de la derecha son True, y en el caso de ser
cierto, devuelve True. Si uno de los dos valores es False, el resultado será False. Es realmente un
operador muy lógico e intuitivo que incluso usamos en la vida real. Si hace sol y es fin de semana,
iré a la playa. Si ambas condiciones se cumplen, es decir que la variable haceSol=True y la
variable finDeSemana=True, iré a la playa, o visto de otra forma irALaPlaya=(haceSol and
finDeSemana).
Operador or
El operador or devuelve True cuando al menos uno de los elementos es igual a True. Es decir, evalúa
si el valor a la izquierda o el de la derecha son True.
# |-----------|
# |------------------|
# False
También podemos mezclar los operadores. En el siguiente ejemplo empezamos por False and
True que es False, después False or True que es True y por último True or False que es True. Es decir,
el resultado final de toda esa expresión es True.
# True
Operador not
Y por último tenemos el operador not, que simplemente invierte True por False y False por True.
También puedes usar varios not juntos y simplemente se irán aplicando uno tras otro. La verdad
que es algo difícil de ver en la realidad, pero simplemente puedes contar el número de not y si es
par el valor se quedará igual. Si por lo contrario es impar, el valor se invertirá.
Dado que estamos tratando con booleanos, hemos considerado que usar True y False es lo mejor y
más claro, pero es totalmente válido emplear 1 y 0 respectivamente para representar ambos
estados. Y por supuesto los resultados no varían
print(not 0) # True
print(not 1) # False
Ejemplos
Antes de entrar con los ejemplos, es importante notar que el orden de aplicación de los operadores
puede influir en el resultado, por lo que es importante tener muy claro su prioridad de aplicación.
De mayor a menor prioridad, el primer sería not, seguido de and y or.
Para saber más: Puedes leer la especificación oficial si quieres saber el orden de aplicación de
todos los operadores en este enlace
Pero, imaginemos que no sabemos el orden de aplicación de los operadores. Vamos a intentar con
ingeniería inversa descubrirlo con unos cuantos ejemplos. En el ejemplo que se muestra a
continuación, habría dos posibilidades. La primera sería (False and False) or True cuyo resultado
sería True y la segunda False and (False or True) cuyo resultado sería False. Dado que el resultado
que Python da es True, podemos concluir que el equivalente es la primera opción, es decir
que and tiene prioridad sobre el or
# True
Lo mismo con el siguiente ejemplo. Como ya sabemos de antes que and es evaluado primero, la
siguiente expresión sería en realidad equivalente a True or (False and False) por lo que su resultado
es True
# True
# True
Operadores bitwise
Los operadores a nivel de bit o bitwise operators son operadores que actúan sobre números
enteros pero usando su representación binaria. Si aún no sabes como se representa un número en
forma binaria, a continuación lo explicamos.
Operador Nombre
| Or bit a bit
Para entender los operadores de bit, es importante antes entender como se representa un número
de manera binaria. Todos estamos acostumbrados a lo que se denomina representación decimal.
Se llama así porque se usan diez números, del 0 al 9 para representar todos los valores. Nuestro
decimal, es también posicional, ya que no es lo mismo un 9 en 19 que en 98. En el primer caso, el
valor es de simplemente 9, pero en el segundo, en realidad ese 9 vale 90.
Lo mismo pasa con la representación binaria pero con algunas diferencias. La primera diferencia es
que sólo existen dos posibles números, el 0 y el 1. La segunda diferencia es que a pesar de ser un
sistema que también es posicional, los valores no son como en el caso decimal, donde el valor se
multiplica por 1 en la primera posición, 10 en la segunda, 100 en la tercera, y así sucesivamente. En
el caso binario, los valores son potencias de 2, es decir 1, 2, 4, 8, 16 o lo que es lo mismo $$2^0, 2^1,
2^2, 2^3, 2^4$$
# Sistema decimal
# 7264
# +---------
# Sistema binario
# 11011
# 1-> En realidad vale 1*16 = 16
# +---------
# Sumando todo 27
Usando la función bin() podemos convertir un número decimal en binario. Podemos comprobar
como el número anterior 11011 es efectivamente 27 en decimal. Fíjate que al imprimirlo
con Python, se añade el prefijo 0b antes del número. Esto es muy importante, y nos permite
identificar que estamos ante un número binario.
print(bin(27))
# 0b11011
Operador &
El operador & realiza la operación que vimos en otros capítulos and, pero por cada bit existente en
la representación binaria de los dos números que le introducimos. Es decir, recorre ambos
números en su representación binaria elemento a elemento, y hace una operación and con cada
uno de ellos. En el siguiente ejemplo se estaría haciendo lo siguiente. Primer elemento de a con
primer elemento de b, sería 1 and 1 por lo que el resultado es 1. Ese valor se almacena en la salida.
Continuamos con el segundo 1 and 0 que es 0, tercero 0 and 1 que es 0 y por último 1 and 1 que es 1.
Por lo tanto el número resultante es 0b1001, lo que representa el 9 en decimal.
a = 0b1101
b = 0b1011
#0b1001
Operador |
El operador | realiza la operación or elemento a elemento con cada uno de los bits de los números
que introducimos. En el siguiente ejemplo podemos ver como el resultado es 1111 ya que siempre
uno de los dos elementos es 1. Se harían las siguientes comparaciones 1 or 1, 1 or 0, 0 or 1 y 1 or 1.
a = 0b1101
b = 0b1011
print(bin(a | b))
# 0b1111
Operador ~
El operador ~ realiza la operación not sobre cada bit del número que le introducimos, es decir,
invierte el valor de cada bit, poniendo los 0 a 1 y los 1 a 0. El comportamiento en Python puede ser
algo distinto del esperado. En el siguiente ejemplo, tenemos el número 40 que en binario es 101000.
Si hacemos ~101000 sería de esperar que como hemos dicho, se inviertan todos los bits y el
resultado sea 010111, pero en realidad el resultado es 101001. Para entender porque pasa esto, te
invitamos a leer más información sobre el complemento a uno y el complemento a dos.
a = 40
print(bin(a))
print(bin(~a))
0b101000
-0b101001
Para saber más: Para entender este operador mejor, es necesario saber que es el complemento a
uno y a dos. Te dejamos este enlace con mas información.
Si vemos el resultado con números decimales, es equivalente a hacer ~a sería -a-1 como se puede
ver en el siguiente ejemplo. En este caso, en vez de mostrar el valor binario mostramos el decimal,
y se puede ver como efectivamente si a=40, tras aplicar el operador ~ el resultado es -40-1.
a = 40
print(a)
print(~a)
Operador ^
El operador ^ realiza la función xor con cada bit de las dos variables que se le proporciona.
Anteriormente en otros capítulos hemos hablado de la and o or, que son operadores bastante
usados y comunes. Tal vez xor sea menos común, y lo que hace es devolver True o 1 cuando hay al
menos un valor True pero no los dos. Es decir, solo devuelve 1 para las
combinaciones 1,0 y 0,1 y 0 para las demás.
x = 0b0110 ^ 0b1010
print(bin(x))
# 0 xor 1 = 1
# 1 xor 0 = 1
# 1 xor 1 = 0
# 0 xor 0 = 0
# 0b1100
Para saber más: Si quieres saber más sobre la puerta XOR, te dejamos un enlace donde se explica.
Operador >>
El operador >> desplaza todos los bit n unidades a la derecha. Por lo tanto es necesario
proporcionar dos parámetros, donde el primer es el número que se desplazará o shift y el segundo
es el número de posiciones. En el siguiente ejemplo tenemos 1000 en binario, por lo que si
aplicamos un >>2, deberemos mover cada bit 2 unidades a la derecha. Lo que queda por la
izquierda se rellena con ceros, y lo que sale por la derecha se descarta. Por lo
tanto 1000>>2 será 0010. Es importante notar que Python por defecto elimina los ceros a la
izquierda, ya que igual que en el sistema decimal, son irrelevantes.
a=0b1000
print(bin(a>>2))
# 0b10
Operador <<
El operador << es análogo al >> con la diferencia que en este caso el desplazamiento es realizado a
la izquierda. Por lo tanto si tenemos 0001 y desplazamos <<3, el resultado será 1000.
a=0b0001
print(bin(a<<3))
# 0b1000
Es importante que no nos dejemos engañar por el número de bits que ponemos a la entrada. Si por
ejemplo desplazamos en 4 o en mas unidades nuestra variable a el número de bits que se nos
mostrará también se incrementará. Con esto queremos destacar que aunque la entrada sean 4 bits,
Python internamente rellena todo lo que está a la izquierda con ceros.
a=0b0001
print(bin(a<<10))
# 0b10000000000
Operadores de Identidad
El operador de identidad o identity operator is nos indica si dos variables hacen referencia al mismo
objeto. Esto implica que si dos variables distintas tienen el mismo id(), el resultado de aplicar el
operador is sobre ellas será True.
Operador Nombre
Operador is
a = 10
b = 10
print(a is b) # True
Esto es debido a que Python reutiliza el mismo objeto que almacena el valor 10 para ambas
variables. De hecho, si usamos la función id(), podemos ver que el objeto es el mismo.
print(id(a)) # 4397849536
print(id(b)) # 4397849536
Podemos ver como también, ambos valores son iguales con el operador relacional ==, pero esto es
una mera casualidad como veremos a continuación. Que dos variables tengan el mismo contenido,
no implica necesariamente que hagan referencia a el mismo objeto.
print(a == b) # True
En el siguiente ejemplo, podemos ver como a y b almacenan el mismo valor, por lo que == nos
indica True.
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True
print(a is b) # False
Sin embargo, por como Python funciona por debajo, almacena el contenido en dos objetos
diferentes. Al tratarse de objetos diferentes, esto hace que el operador is devuelva False.
A diferencia de antes, podemos ver como la función id() en este caso nos devuelve un valor
diferente.
print(id(a)) # 4496626880
print(id(b)) # 4496626816
Esta diferencia puede resultar algo liosa, por lo que te recomendamos que leas más acerca de
la mutabilidad en Python.
Para saber más: Si quieres saber más acerca del operador id() te dejamos este enlace a la
documentación oficial.
Operador is not
Una vez definido is, es trivial definir is not porque es exactamente lo contrario.
Devuelve True cuando ambas variables no hacen referencia al mismo objeto.
a = [1, 2, 3]
b = [1, 2, 3]
a=5
b=5
Para saber más: Te dejamos un enlace muy interesante con más información sobre el is y is not.
Operadores de membresía
Los operadores de membresía o membership operators son operadores que nos permiten saber si
un elemento esta contenido en una secuencia. Por ejemplo si un número está contenido en una
lista de números.
Operador in
El operador in nos permite ver si un elemento esta contenido dentro de una secuencia, como podría
ser una lista. En el siguiente ejemplo se ve un caso sencillo donde se verifica si 3 esta contenido en
la lista [1, 2, 3]. Como efectivamente lo está, el resultado es True.
print(3 in [1, 2, 3])
# True
Vamos a complicar las cosas un poco y explorar los límites del operador. Que pasaría si
intentásemos hacer algo como lo que se ve en el siguiente ejemplo. Podría ser lógico pensar que 3
in 3 sería True, porque realmente si que parece que el 3 esta contenido en el segundo 3. Pues no, el
siguiente código daría un error, diciendo que la clase int no es iterable. En otros capítulos
exploraremos más acerca de esto. Por ahora nos basta con decir que el elemento a la derecha
del in debe ser un objeto tipo lista
Vamos a darle una última vuelta de tuerca. Podríamos también ver si una lista está contenida en
otra lista. En este caso, la lista de la derecha del in es una lista embebida dentro de otra lista.
Como [1, 2] está dentro de la segunda lista, el resultado es True
# True
Operador not in
Por último, el operador not in realiza lo contrario al operador in. Verifica que un elemento no está
contenido en otra secuencia. En el siguiente ejemplo se puede ver como 3 no es parte de la
secuencia, por lo que el resultado es False
# True
La verdad que ambos operadores in y not in son muy útiles y nos ahorran mucho trabajo. Es
importante tenerlo en cuenta, porque no otros lenguajes de programación no existen tales
operadores, y debemos escribir código extra para obtener tal funcionalidad. Una forma de
implementar nuestro operador in y is not con una función sería la siguiente. Simplemente iteramos
la lista y si encontramos el elemento que estábamos buscando devolvemos True, de lo
contrario False.
a=3
lista=[1, 2, 3, 4, 5]
for l in lista:
if a==l:
return True
return False
print(estaContenido(a, lista))
Para saber más: Te dejamos un enlace a la documentación oficial acerca de los operadores de
membresía.
Operador Walrus
Introducción
El operador walrus o walrus operator se introdujo con la PEP572 en Python 3.8, y se trata de un
operador de asignación con una funcionalidad extra que veremos a continuación. El operador se
representa con dos puntos seguidos de un igual :=, lo que tiene cierto parecido a una morsa,
siendo : los ojos y = los colmillos, por lo que de ahí viene su nombre (walrus significa morsa en
Inglés).
El problema que dicho operador intenta resolver es el siguiente. Podemos simplificar las siguientes
tres líneas de código.
x = "Python"
print(x)
print(type(x))
# Salida:
# Python
# <class 'str'>
En sólo dos líneas. Como podemos ver, el uso de := asigna y devuelve el contenido de la variable.
print(x := "Python")
print(type(x))
# Python
# <class 'str'>
Para gente que venga de otros lenguajes de programación como C, tal vez le resulte raro este
operador, ya que C permite realizar lo siguiente. Podemos ver como (x = 5) puede ser comparado
con == 5. Sin embargo esta sintaxis no es válida en Python, y el operador wallrus es precisamente lo
que intenta resolver.
#include <stdio.h>
int main(){
int x;
if ((x = 5) == 5) {
printf("Hola");
return 0;
En el siguiente programa pedimos al usuario que introduzca un texto y lo vamos añadiendo a una
lista hasta que el texto introducido sea “terminar”. Sin el operador walrus se podría escribir de la
siguiente forma.
lista = []
lista.append(entrada)
print(lista)
Sin embargo aprovechando que el operador walrus := devuelve el contenido que es asignado,
podemos usar entrada para comparar con != "terminar". Es decir, podemos unir la asignación = y la
comparación en la misma línea. Esto nos permite ahorrar alguna línea de código.
lista = []
lista.append(entrada)
print(lista)
También nos podemos ahorrar una línea de código si queremos primero asignar a una variable y
luego emplear dicha variable en, por ejemplo, un if.
a = 20*[1]
Por otro lado puede ser útil cuando usemos list comprehensions donde el valor que usamos para
filtrar es modificado y se necesita también en el cuerpo del bucle.
De manera similar, podemos reutilizar el resultado de una expresión evitando tener que volver a
computarla.
Por otro lado, aunque el operador walrus sea muy similar al operador de asignación =, su uso no es
consistente. Por ejemplo, el siguiente código no es correcto usando :=.
# Incorrecto
class Example:
De hecho, el propio creador Christoph Groth sugirió en el siguiente hilo que tal vez sería
conveniente implementar el uso de = como en C/C++ (recuerda el ejemplo que hemos visto
anteriormente). Si esto se llega a producir, no sabemos que podría pasar con el operador walrus,
pero tal vez ya no sería necesario.
• 📙📙 Tipos de métodos
• 📙📙 Herencia
• 📙📙 Decorador Property
• 📙📙 Métodos dunder o mágicos
• 📗📗 Abstracción
• 📗📗 Acoplamiento
• 📗📗 Crear clase
• 📗📗 Encapsulamiento
• 📙📙 Polimorfismo
• 📗📗 Cohesión
Este modo o paradigma de programación nos permite organizar el código de una manera que se
asemeja bastante a como pensamos en la vida real, utilizando las famosas clases. Estas nos
permiten agrupar un conjunto de variables y funciones que veremos a continuación.
Cosas de lo más cotidianas como un perro o un coche pueden ser representadas con clases. Estas
clases tienen diferentes características, que en el caso del perro podrían ser la edad, el nombre o
la raza. Llamaremos a estas características, atributos.
Por otro lado, las clases tienen un conjunto de funcionalidades o cosas que pueden hacer. En el
caso del perro podría ser andar o ladrar. Llamaremos a estas funcionalidades métodos.
Por último, pueden existir diferentes tipos de perro. Podemos tener uno que se llama Toby o el del
vecino que se llama Laika. Llamaremos a estos diferentes tipos de perro objetos. Es decir, el
concepto abstracto de perro es la clase, pero Toby o cualquier otro perro particular será el objeto.
• Herencia
• Cohesión
• Abstracción
• Polimorfismo
• Acoplamiento
• Encapsulamiento
Motivación
Una vez explicada la programación orientada a objetos puede parecer bastante lógica, pero no es
algo que haya existido siempre, y de hecho hay muchos lenguajes de programación que no la
soportan.
En parte surgió debido a la creciente complejidad a la que los programadores se iban enfrentando
conforme pasaban los años. En el mundo de la programación hay gran cantidad de aplicaciones
que realizan tareas muy similares y es importante identificar los patrones que nos permiten no
reinventar la rueda. La programación orientada a objetos intentaba resolver esto.
Uno de los primeros mecanismos que se crearon fueron las funciones, que permiten agrupar
bloques de código que hacen una tarea específica bajo un nombre. Algo muy útil ya que permite
también reusar esos módulos o funciones sin tener que copiar todo el código, tan solo la llamada.
Las funciones resultaron muy útiles, pero no eran capaces de satisfacer todas las necesidades de
los programadores. Uno de los problemas de las funciones es que sólo realizan unas operaciones
con unos datos de entrada para entregar una salida, pero no les importa demasiado conservar esos
datos o mantener algún tipo de estado. Salvo que se devuelva un valor en la llamada a la función o
se usen variables globales, todo lo que pasa dentro de la función queda olvidado, y esto en muchos
casos es un problema.
Imaginemos que tenemos un juego con naves espaciales moviéndose por la pantalla. Cada nave
tendrá una posición (x,y) y otros parámetros como el tipo de nave, su color o tamaño. Sin hacer uso
de clases y POO, tendremos que tener una variable para cada dato que queremos almacenar:
coordenadas, color, tamaño, tipo. El problema viene si tenemos 10 naves, ya que nos podríamos
juntar con un número muy elevado de variables. Todo un desastre.
En el mundo de la programación existen tareas muy similares al ejemplo con las naves, y en
respuesta a ello surgió la programación orientada a objetos. Una herramienta perfecta que permite
resolver cierto tipo de problemas de una forma mucho más sencilla, con menos código y más
organizado. Agrupa bajo una clase un conjunto de variables y funciones, que pueden ser
reutilizadas con características particulares creando objetos.
Definiendo clases
Vista ya la parte teórica, vamos a ver como podemos hacer uso de la programación orientada a
objetos en Python. Lo primero es crear una clase, para ello usaremos el ejemplo de perro.
class Perro:
pass
Se trata de una clase vacía y sin mucha utilidad práctica, pero es la mínima clase que podemos
crear. Nótese el uso del pass que no hace realmente nada, pero daría un error si después de los : no
tenemos contenido.
Ahora que tenemos la clase, podemos crear un objeto de la misma. Podemos hacerlo como si de
una variable normal se tratase. Nombre de la variable igual a la clase con (). Dentro de los
paréntesis irían los parámetros de entrada si los hubiera.
mi_perro = Perro()
Definiendo atributos
A continuación vamos a añadir algunos atributos a nuestra clase. Antes de nada es importante
distinguir que existen dos tipos de atributos:
• Atributos de clase: Se trata de atributos que pertenecen a la clase, por lo tanto serán
comunes para todos los objetos.
Empecemos creando un par de atributos de instancia para nuestro perro, el nombre y la raza. Para
ello creamos un método __init__ que será llamado automáticamente cuando creemos un objeto. Se
trata del constructor.
class Perro:
# Atributos de instancia
self.nombre = nombre
self.raza = raza
Ahora que hemos definido el método init con dos parámetros de entrada, podemos crear el objeto
pasando el valor de los atributos. Usando type() podemos ver como efectivamente el objeto es de la
clase Perro.
print(type(mi_perro))
# <class '__main__.Perro'>
Seguramente te hayas fijado en el self que se pasa como parámetro de entrada del método. Es una
variable que representa la instancia de la clase, y deberá estar siempre ahí.
El uso de __init__ y el doble __ no es una coincidencia. Cuando veas un método con esa forma,
significa que está reservado para un uso especial del lenguaje. En este caso sería lo que se conoce
como constructor. Hay gente que llama a estos métodos mágicos.
Por último, podemos acceder a los atributos usando el objeto y .. Por lo tanto.
print(mi_perro.nombre) # Toby
print(mi_perro.raza) # Bulldog
Hasta ahora hemos definido atributos de instancia, ya que son atributos que pertenecen a cada
perro concreto. Ahora vamos a definir un atributo de clase, que será común para todos los perros.
Por ejemplo, la especie de los perros es algo común para todos los objetos Perro.
class Perro:
# Atributo de clase
especie = 'mamífero'
# Atributos de instancia
self.nombre = nombre
self.raza = raza
Dado que es un atributo de clase, no es necesario crear un objeto para acceder al atributos.
Podemos hacer lo siguiente.
print(Perro.especie)
# mamífero
mi_perro.especie
# 'mamífero'
De esta manera, todos los objetos que se creen de la clase perro compartirán ese atributo de clase,
ya que pertenecen a la misma.
Definiendo métodos
En realidad cuando usamos __init__ anteriormente ya estábamos definiendo un método, solo que
uno especial. A continuación vamos a ver como definir métodos que le den alguna funcionalidad
interesante a nuestra clase, siguiendo con el ejemplo de perro.
Vamos a codificar dos métodos, ladrar y caminar. El primero no recibirá ningún parámetro y el
segundo recibirá el número de pasos que queremos andar. Como hemos indicado
anteriormente self hace referencia a la instancia de la clase. Se puede definir un método con def y
el nombre, y entre () los parámetros de entrada que recibe, donde siempre tendrá que estar self el
primero.
class Perro:
# Atributo de clase
especie = 'mamífero'
# Atributos de instancia
self.nombre = nombre
self.raza = raza
def ladra(self):
print("Guau")
Por lo tanto si creamos un objeto mi_perro, podremos hacer uso de sus métodos llamándolos
con . y el nombre del método. Como si de una función se tratase, pueden recibir y devolver
argumentos.
mi_perro.ladra()
mi_perro.camina(10)
# Creando perro Toby, Bulldog
# Guau
# Caminando 10 pasos
En otros posts hemos visto como se pueden crear métodos con def dentro de una clase, pudiendo
recibir parámetros como entrada y modificar el estado (como los atributos) de la instancia. Pues
bien, haciendo uso de los decoradores, es posible crear diferentes tipos de métodos:
En la siguiente clase tenemos un ejemplo donde definimos los tres tipos de métodos.
class Clase:
def metodo(self):
@classmethod
def metododeclase(cls):
@staticmethod
def metodoestatico():
Métodos de instancia
Los métodos de instancia son los métodos normales, de toda la vida, que hemos visto
anteriormente. Reciben como parámetro de entrada self que hace referencia a la instancia que
llama al método. También pueden recibir otros argumentos como entrada.
Para saber más: El uso de "self" es totalmente arbitrario. Se trata de una convención acordada por
los usuarios de Python, usada para referirse a la instancia que llama al método, pero podría ser
cualquier otro nombre. Lo mismo ocurre con "cls", que veremos a continuación.
class Clase:
mi_clase = Clase()
mi_clase.metodo("a", "b")
• Dado que desde el objeto self se puede acceder a la clase con ` self.class`, también pueden
modificar el estado de la clase
A diferencia de los métodos de instancia, los métodos de clase reciben como argumento cls, que
hace referencia a la clase. Por lo tanto, pueden acceder a la clase pero no a la instancia.
class Clase:
@classmethod
def metododeclase(cls):
Clase.metododeclase()
mi_clase.metododeclase()
Por último, los métodos estáticos se pueden definir con el decorador @staticmethod y no aceptan
como parámetro ni la instancia ni la clase. Es por ello por lo que no pueden modificar el estado ni
de la clase ni de la instancia. Pero por supuesto pueden aceptar parámetros de entrada.
class Clase:
@staticmethod
def metodoestatico():
mi_clase = Clase()
Clase.metodoestatico()
mi_clase.metodoestatico()
# 'Método estático'
# 'Método estático'
Por lo tanto el uso de los métodos estáticos pueden resultar útil para indicar que un método no
modificará el estado de la instancia ni de la clase. Es cierto que se podría hacer lo mismo con un
método de instancia por ejemplo, pero a veces resulta importante indicar de alguna manera estas
peculiaridades, evitando así futuros problemas y malentendidos.
En otras palabras, los métodos estáticos se podrían ver como funciones normales, con la salvedad
de que van ligadas a una clase concreta.
Herencia en Python
La herencia es un proceso mediante el cual se puede crear una clase hija que hereda de una
clase padre, compartiendo sus métodos y atributos. Además de ello, una clase hija puede
sobreescribir los métodos o atributos, o incluso definir unos nuevos.
Se puede crear una clase hija con tan solo pasar como parámetro la clase de la que queremos
heredar. En el siguiente ejemplo vemos como se puede usar la herencia en Python, con la
clase Perro que hereda de Animal. Así de fácil.
# Definimos una clase padre
class Animal:
pass
class Perro(Animal):
pass
De hecho podemos ver como efectivamente la clase Perro es la hija de Animal usando __bases__
print(Perro.__bases__)
# (<class '__main__.Animal'>,)
De manera similar podemos ver que clases descienden de una en concreto con __subclasses__.
print(Animal.__subclasses__())
# [<class '__main__.Perro'>]
¿Y para que queremos la herencia? Dado que una clase hija hereda los atributos y métodos de la
padre, nos puede ser muy útil cuando tengamos clases que se parecen entre sí pero tienen ciertas
particularidades. En este caso en vez de definir un montón de clases para cada animal, podemos
tomar los elementos comunes y crear una clase Animal de la que hereden el resto, respetando por
tanto la filosofía DRY. Realizar estas abstracciones y buscar el denominador común para definir una
clase de la que hereden las demás, es una tarea de lo más compleja en el mundo de la
programación.
Para saber más: El principio DRY (Don't Repeat Yourself) es muy aplicado en el mundo de la
programación y consiste en no repetir código de manera innecesaria. Cuanto más código duplicado
exista, más difícil será de modificar y más fácil será crear inconsistencias. Las clases y la herencia
a no repetir código.
Continuemos con nuestro ejemplo de perros y animales. Vamos a definir una clase
padre Animal que tendrá todos los atributos y métodos genéricos que los animales pueden tener.
Esta tarea de buscar el denominador común es muy importante en programación. Veamos los
atributos:
Definimos la clase padre, con una serie de atributos comunes para todos los animales como
hemos indicado.
class Animal:
self.especie = especie
self.edad = edad
def hablar(self):
# Método vacío
pass
def moverse(self):
# Método vacío
pass
def describeme(self):
Tenemos ya por lo tanto una clase genérica Animal, que generaliza las características y
funcionalidades que todo animal puede tener. Ahora creamos una clase Perro que hereda
del Animal. Como primer ejemplo vamos a crear una clase vacía, para ver como los métodos y
atributos son heredados por defecto.
class Perro(Animal):
pass
mi_perro.describeme()
Con tan solo un par de líneas de código, hemos creado una clase nueva que tiene todo el contenido
que la clase padre tiene, pero aquí viene lo que es de verdad interesante. Vamos a crear varios
animales concretos y sobreescrbir algunos de los métodos que habían sido definidos en la
clase Animal, como el hablar o el moverse, ya que cada animal se comporta de una manera
distinta.
Podemos incluso crear nuevos métodos que se añadirán a los ya heredados, como en el caso de
la Abeja con picar().
class Perro(Animal):
def hablar(self):
print("Guau!")
def moverse(self):
class Vaca(Animal):
def hablar(self):
print("Muuu!")
def moverse(self):
class Abeja(Animal):
def hablar(self):
print("Bzzzz!")
def moverse(self):
print("Volando")
# Nuevo método
def picar(self):
print("Picar!")
Por lo tanto ya podemos crear nuestros objetos de esos animales y hacer uso de sus métodos que
podrían clasificarse en tres:
mi_abeja = Abeja('insecto', 1)
mi_perro.hablar()
mi_vaca.hablar()
# Guau!
# Muuu!
mi_vaca.describeme()
mi_abeja.describeme()
mi_abeja.picar()
# Picar!
Uso de super()
En pocas palabras, la función super() nos permite acceder a los métodos de la clase padre desde
una de sus hijas. Volvamos al ejemplo de Animal y Perro.
class Animal:
def __init__(self, especie, edad):
self.especie = especie
self.edad = edad
def hablar(self):
pass
def moverse(self):
pass
def describeme(self):
Tal vez queramos que nuestro Perro tenga un parámetro extra en el constructor, como podría ser
el dueño. Para realizar esto tenemos dos alternativas:
• Podemos crear un nuevo __init__ y guardar todas las variables una a una.
• O podemos usar super() para llamar al __init__ de la clase padre que ya aceptaba
la especie y edad, y sólo asignar la variable nueva manualmente.
class Perro(Animal):
# Alternativa 1
# self.especie = especie
# self.edad = edad
# self.dueño = dueño
# Alternativa 2
super().__init__(especie, edad)
self.dueño = dueño
mi_perro.especie
mi_perro.edad
mi_perro.dueño
Herencia múltiple
En Python es posible realizar herencia múltiple. En otros posts hemos visto como se podía crear
una clase padre que heredaba de una clase hija, pudiendo hacer uso de sus métodos y atributos. La
herencia múltiple es similar, pero una clase hereda de varias clases padre en vez de una sola.
Veamos un ejemplo. Por un lado tenemos dos clases Clase1 y Clase2, y por otro tenemos
la Clase3 que hereda de las dos anteriores. Por lo tanto, heredará todos los métodos y atributos de
ambas.
class Clase1:
pass
class Clase2:
pass
pass
Es posible también que una clase herede de otra clase y a su vez otra clase herede de la anterior.
class Clase1:
pass
class Clase2(Clase1):
pass
class Clase3(Clase2):
pass
Llegados a este punto nos podemos plantear lo siguiente. Vale, como sabemos de otros posts las
clases hijas heredan los métodos de las clases padre, pero también pueden reimplementarlos de
manera distinta. Entonces, si llamo a un método que todas las clases tienen en común ¿a cuál se
llama?. Pues bien, existe una forma de saberlo.
La forma de saber a que método se llama es consultar el MRO o Method Order Resolution. Esta
función nos devuelve una tupla con el orden de búsqueda de los métodos. Como era de esperar se
empieza en la propia clase y se va subiendo hasta la clase padre, de izquierda a derecha.
class Clase1:
pass
class Clase2:
pass
pass
print(Clase3.__mro__)
Una curiosidad es que al final del todo vemos la clase object. Aunque pueda parecer raro, es
correcto ya que en realidad todas las clases en Python heredan de una clase genérica object,
aunque no lo especifiquemos explícitamente.
Y como último ejemplo,…el cielo es el límite. Podemos tener una clase heredando de otras tres.
Fíjate en que el MRO depende del orden en el que las clases son pasadas: 1, 3, 2.
class Clase1:
pass
class Clase2:
pass
class Clase3:
pass
pass
print(Clase4.__mro__)
Decorador Property
En otros tutoriales hemos visto como se crean y usan los decoradores en Python. A continuación
veremos el decorador @property, que viene por defecto con Python, y puede ser usado para
modificar un método para que sea un atributo o propiedad. Es importante que conozcan antes la
programación orientada a objetos.
El decorador puede ser usado sobre un método, que hará que actúe como si fuera un atributo.
class Clase:
def __init__(self, mi_atributo):
self.__mi_atributo = mi_atributo
@property
def mi_atributo(self):
return self.__mi_atributo
mi_clase = Clase("valor_atributo")
mi_clase.mi_atributo
# 'valor_atributo'
Muy importante notar que aunque mi_atributo pueda parecer un método, en realidad no lo es, por
lo que no puede ser llamado con ().
Tal vez te preguntes para que sirve esto, ya que el siguiente código hace exactamente lo mismo sin
hacer uso de decoradores.
class Clase:
self.mi_atributo = mi_atributo
mi_clase = Clase("valor_atributo")
mi_clase.mi_atributo
# 'valor_atributo'
La primera diferencia que vemos entre los códigos anteriores es el uso de __ antes de mi_atributo.
Cuando nombramos una variable de esta manera, es una forma de decirle a Python que queremos
que se “oculte” y que no pueda ser accedida como el resto de atributos.
class Clase:
def __init__(self, mi_atributo):
self.__mi_atributo = mi_atributo
mi_clase = Clase("valor_atributo")
# mi_clase.__mi_atributo # Error!
Esto puede ser importante con ciertas variables que no queremos que sean accesibles desde el
exterior de una manera no controlada. Al definir la propiedad con @property el acceso a ese
atributo se realiza a través de una función, siendo por lo tanto un acceso controlado.
class Clase:
self.__mi_atributo = mi_atributo
@property
def mi_atributo(self):
return self.__mi_atributo
Otra utilidad podría ser la consulta de un parámetro que requiera de muchos cálculos. Se podría
tener un atributo que no estuviera directamente almacenado en la clase, sino que precisara de
realizar ciertos cálculos. Para optimizar esto, se podrían hacer los cálculos sólo cuando el atributo
es consultado.
Por último, existen varios añadidos al decorador @property como pueden ser el setter. Se trata de
otro decorador que permite definir un “método” que modifica el contenido del atributo que se esté
usando.
class Clase:
self.__mi_atributo = mi_atributo
@property
def mi_atributo(self):
return self.__mi_atributo
@mi_atributo.setter
if valor != "":
print("Modificando el valor")
self.__mi_atributo = valor
else:
De esta forma podemos añadir código al setter, haciendo que por ejemplo realice comprobaciones
antes de modificar el valor. Esto es una cosa que de usar un atributo normal no podríamos hacer, y
es muy útil de cara a la encapsulación.
mi_clase = Clase("valor_atributo")
mi_clase.mi_atributo
# 'valor_atributo'
mi_clase.mi_atributo = "nuevo_valor"
mi_clase.mi_atributo
# 'nuevo_valor'
mi_clase.mi_atributo = ""
Resulta lógico pensar que si un determinado atributo pertenece a una clase, si queremos
modificarlo debería de tener la “aprobación” de la clase, para asegurarse que ninguna entidad
externa está “haciendo cosas raras”.
Los métodos mágicos son métodos Python que definen cómo se comportan los objetos Python
cuando se realizan operaciones comunes sobre ellos. Estos métodos se definen claramente con
guiones bajos dobles antes y después del nombre del método.
Como resultado, son comúnmente llamados métodos dunder, como en double underscore. Un
método dunder común que puede que ya haya encontrado es el método __init__() que se utiliza para
definir constructores de clases.
Normalmente, los métodos dunder no están pensados para ser llamados directamente en su
código; más bien, serán llamados por el intérprete mientras se ejecuta el programa.
Los métodos mágicos son un concepto útil en la Programación Orientada a Objetos en Python.
Utilizándolos, usted especifica el comportamiento de sus tipos de datos personalizados cuando se
utilizan con operaciones comunes incorporadas. Estas operaciones incluyen:
� Operaciones aritméticas
� Operaciones de comparación
� Operaciones de representación
La siguiente sección tratará sobre cómo implementar métodos mágicos que definan cómo se
comporta la aplicación cuando se utiliza en todas las categorías anteriores.
Pueden tomar argumentos adicionales dependiendo de cómo vayan a ser llamados por el
intérprete. También se definen claramente con dos guiones bajos antes y después de sus nombres.
Implementación
Gran parte de lo que hemos discutido hasta ahora parece teórico y abstracto. En esta sección,
implementaremos una simple clase Rectángulo.
Esta clase tendrá propiedades de longitud y anchura. Utilizando el método __init__, podrá
especificar estas propiedades en la instanciación. Además, podrá comparar diferentes rectángulos
para ver si es igual, menor o mayor que otro utilizando los operadores ==, < y >. Por último, el
rectángulo debe ser capaz de proporcionar una representación de cadena significativa.
Para seguir este tutorial, necesitará un entorno de ejecución Python. Puede utilizar uno local, o
puede utilizar el compilador de Python en línea de Geekflare.
clase Rectángulo:
pass
Copy
A continuación, vamos a crear nuestro primer método mágico, el método constructor de la clase.
Este método tomará la altura y la anchura y las almacenará como atributos en la instancia de la
clase.
clase Rectángulo:
self.altura = altura
self.anchura = anchura
Copy
A continuación, queremos crear un método que permita a nuestra clase generar una cadena legible
por humanos para representar el objeto. Este método será llamado siempre que llamemos a la
función str( ) pasando una instancia de la clase Rectángulo como argumento. Este método también
será llamado cuando llamemos a funciones que esperan un argumento de cadena, como la
función print.
clase Rectángulo:
self.altura = altura
self.anchura = anchura
def __str__(self):
return f'Rectángulo({altura.self}, {anchura.self})'
Copy
El método __str__( ) debe devolver una cadena que usted quiera que represente al objeto. En este
caso, estamos devolviendo una cadena del formato Rectángulo(<altura>, <anchura>) donde altura y
anchura son las dimensiones almacenadas del rectángulo.
A continuación, queremos crear operadores de comparación para las operaciones igual a, menor
que y mayor que. Esto se denomina sobrecarga de operadores. Para crearlos, utilizaremos los
métodos mágicos __eq__, __lt__ y __gt__ respectivamente. Estos métodos devolverán un valor
booleano después de comparar las áreas de los rectángulos.
clase Rectángulo:
self.altura = altura
self.anchura = anchura
def __str__(self):
Copy
Como puede ver, estos métodos reciben dos parámetros. El primero es el rectángulo actual, y el
segundo es el otro valor con el que se está comparando. Este valor puede ser otra instancia de
Rectángulo o cualquier otro valor. La lógica de la comparación y las condiciones bajo las cuales la
comparación devolverá verdadero dependen completamente de usted.
En la siguiente sección, discutiremos los métodos mágicos comunes que encontrará y utilizará.
Menor o igual que __le__ >= Implementa la comparación menor o igual que
Mayor o igual que __ge__ <= Implementa la comparación mayor o igual que
Este método es llamado cada vez que se borra un objeto de la clase para la que está
Borrado __del__ para realizar acciones de limpieza como cerrar cualquier archivo que haya abierto.
NombreMétodo Descripción
Devuelve una representación en forma de cadena legible por humanos del objeto. Se llama
se llama a la función str( ), pasando una instancia de la clase como argumento. También se
instancia a las funciones print() y format(). Su finalidad es proporcionar una cadena compr
Str __str__ final de la aplicación.
Devuelve una representación en forma de cadena del objeto que utiliza el desarrollador. Lo
devuelta sea rica en información, de forma que se pueda construir una instancia idéntica d
Repr __repr__la cadena.
Los métodos mágicos son increíbles y simplificarán su código. Sin embargo, es importante que
tenga en cuenta lo siguiente cuando los utilice.
• Utilícelos con moderación – Implementar demasiados métodos mágicos en sus clases hace
que su código sea difícil de entender. Limítese a implementar sólo los esenciales.
Palabras finales
En este artículo, he introducido los métodos mágicos como una forma de crear clases que se
pueden utilizar con operaciones incorporadas. También hablé de cómo se definen y repasé un
ejemplo de una clase que implementa métodos mágicos. A continuación, mencioné los diferentes
métodos que probablemente va a utilizar y necesitar antes de compartir algunas de las mejores
prácticas a tener en cuenta.
Todo programador de Python que trabaje creando (y, en menor medida, también utilizando) clases
debe estar al tanto de los «métodos mágicos». Son aquellos que comienzan y terminan con doble
guión bajo; ya estarás al tanto de algunos, como __init__(), que no están pensados para ser
invocados manualmente sino que son llamados por Python en situaciones particulares (por
ejemplo, cuando se realiza una comparación entre dos instancias vía los operadores >, <, ==, etc. o
bien cuando se ejecuta alguna operación aritmética). Estaremos trabajando con varios de ellos y de
paso daremos un vistazo a las propiedades, que sirven para encapsular atributos de una clase.
Un conocimiento más o menos básico sobre las clases en general y cómo funcionan en Python en
particular sería una buena base para seguir este artículo. Si no estás muy familiarizado con estos
conceptos, te invito a chequear el artículo sobre Clases y orientación a objetos y la sección
de Clases en nuestro tutorial del lenguaje.
Primeros pasos
Iremos aprendiendo por ejemplos. Así que trabajaremos con una clase que llamaré Time, que
contendrá tres atributos: horas, minutos y segundos. Nuestro objetivo no es representar una hora
del día en particular, sino una unidad de tiempo en general, de modo que permitiremos valores
mayores a 24 horas y menores a 0.
1. class Time:
2.
4. self.h = h
5. self.m = m
6. self.s = s
Hasta aquí tenemos una primera definición de nuestra clase, que toma los argumentos que
mencionamos recién y los guarda en la instancia para que puedan ser accedidos como atributos.
Probémosla: intentemos representar las 14 horas con 23 minutos y 10 segundos.
Los argumentos tienen el valor cero por defecto; así, también es válido:
1. # Media hora.
2. b = Time(m=30)
Ahora bien, sería interesante poder imprimir directamente cualquier instancia de Time y que nos
muestre sus tres atributos. Si hacemos eso ahora, veremos que el resultado se ve más o menos así.
Lo cual no nos dice mucho del objeto más que su dirección de memoria, lo cual no es muy
relevante. Modifiquemos eso definiendo el método __repr__() debajo del inicializador.
1. def __repr__(self):
(Si tienes dudas sobre la sintaxis empleada aquí para constituir la cadena véase Formando
cadenas de caracteres).
El resultado de este método será retornado por la función str() al intentar convertir una instancia de
nuestra clase a una cadena, que es justamente lo que hace print(). Por ello, si ahora imprimimos
nuestro objeto, veremos una representación más clara.
Propiedades
Las propiedades nos permiten encapsular atributos dentro de una clase. ¿Cuál es su propósito?
Bien, consideremos lo siguiente.
3. print(a) # ValueError!
Aquí básicamente el problema es que no hay nada que evite que se asigne una cadena a un atributo
que se espera sea un número entero. Para solucionarlo vamos a emplear el decorador
incorporado property(), que nos permite controlar de qué forma se obtiene y se altere el valor de un
atributo. Lo haremos para los tres atributos que nos competen.
Empecemos por las horas. Lo primero que debemos hacer es definir una función que será llamada
por Python cuando se intente acceder al valor de nuestro atributo.
1. @property
2. def h(self):
3. return self._h
De modo que print(a.h) imprimirá el valor retornado por el método h(). Pero aún no hemos definido
el atributo _h, esto es, el valor que queremos encapsular para evitar que le sea asignado otro tipo
de dato que no sea un entero. Entonces, lo siguiente es definir otra función que será llamada
cuando se asigne un nuevo valor al atributo h.
1. @h.setter
3. self._h = value
Así, hacer a.h = 50 será equivalente a invocar a la función anterior. Esto nos permite establecer
restricciones en los valores asignados, por ejemplo, chequear su tipo de dato.
1. @h.setter
5. self._h = value
Y lo mismo ocurrirá si los argumentos no son del tipo correcto, por cuanto el método __init__() se
ocupa de asignar los valores pasados a sus respectivos atributos.
Hagamos, ahora, esto mismo para los otros dos atributos, y creemos un decorador adicional que se
ocupe de chequear que siempre el argumento value sea un entero.
2.
3.
4. def _int_required(f):
5. @wraps(f)
11.
12.
14. # [...]
15.
16. @property
19.
20. @h.setter
21. @_int_required
24.
25. @property
28.
29. @m.setter
30. @_int_required
33.
34. @property
37.
38. @s.setter
39. @_int_required
Ahora ocupémonos de este otro tema. Sabemos que 60 segundos equivalen a un minuto y que 60
minutos equivalen a una hora. Debemos asegurar que nuestra clase se encargue de hacer el
balance de los datos de forma automática: por ejemplo, convertir 80 segundos en 1 minuto y 20
segundos, asignando los atributos correspondientes. Para ello añadamos un balance en los
métodos invocados cuando se asigna a los atributos m y s (setters).
1. @m.setter
2. @_int_required
4. self._m = value
7. # [...]
8.
9. @s.setter
10. @_int_required
2. if b >= 0:
4. a += 1
5. b -= 60
6. elif b < 0:
7. while b < 0:
8. a -= 1
9. b += 60
10. return a, b
Evito la explicación sobre cómo opera la función pues no es el tema central del artículo. Sí
comprobemos que ahora el balance ocurre en forma automática.
Operaciones aritméticas
Sería útil que podamos sumar y restar instancias de Time usando los operadores + y -. Python nos
permite implementarlo de una forma muy sencilla: definiendo los métodos __add__() y __sub__(),
respectivamente. De igual forma ─aunque no lo haremos aquí pues no nos sirve a nuestro
propósito─ están disponibles __mul__() y __truediv__() para la multiplicación (*) y la división (/).
3. m = self.m + other.m
4. s = self.s + other.s
5. return Time(h, m, s)
La lógica es bastante fácil: si a y b son dos instancias de Time, al hacer a + b Python devolverá el
resultado de a.__add__(b).
2. h = self.h - other.h
3. m = self.m - other.m
4. s = self.s - other.s
5. return Time(h, m, s)
También podemos expresarlo de una forma más funcional del siguiente modo.
1. import operator
2.
3. # [...]
4.
6. h = method(self.h, other.h)
7. m = method(self.m, other.m)
8. s = method(self.s, other.s)
9. return Time(h, m, s)
10.
13.
14. def __sub__(self, other):
(El módulo estándar operator define funciones que actúan del mismo modo que los operadores de
Python, por ejemplo, operator.add(a, b) es igual a a + b).
Ahora bien, para evitar que se intente sumar o restar una instancia de Time con un objeto de
cualquier otro tipo, debemos chequear el tipo de dato del argumento. Creemos un decorador para
ello.
1. def _time_required(f):
2. @wraps(f)
7. return wrapper
1. @_time_required
4.
5. @_time_required
Ejemplo:
¡Perfecto! Veamos a continuación cómo permitir comparaciones entre dos instancias de nuestra
clase.
Comparaciones
Los métodos mágicos para cada una de las operaciones de comparación son los siguientes.
• __ne__() para a != b.
• __eq__() para a == b.
Implementemos primero el último, que es más sencillo. Sabemos que una instancia de Time será
igual a otra cuando coincidan las horas, los minutos y los segundos.
1. @_time_required
5. self.s == other.s)
Algunas pruebas:
¡Sencillo! Ahora sigamos con la operación a < b. En este caso, a será menor a b si a.h < b.h. Si las
horas son iguales, debemos chequear los minutos. Y si estos son iguales, los segundos.
1. @_time_required
4. return True
6. return False
8. return True
Otras pruebas:
1. print(Time(2, 16, 48) < Time(2, 16, 48)) # False
Bien. Podríamos definir el resto de los métodos de comparación tal como hemos hecho con estos.
Pero dado que las otras operaciones pueden inferirse a partir de estas dos que definimos (por
ejemplo, a != b es igual a not a == b), no es necesario que lo hagamos manualmente. El
decorador functools.total_ordering() se encargará de ello.
2.
3. # [...]
4.
5. @total_ordering
6. class Time:
7.
8. # [...]
3. print(a == b) # False
4. print(a != b) # True
Código completo
¡Hemos llegado al final! Muchos otros son los métodos mágicos disponibles en Python; la lista
completa está en la documentación oficial. A continuación el código al que hemos arribado a lo
largo del artículo.
1. #!/usr/bin/env python
3.
4. import operator
6.
7.
8. def _time_required(f):
9. @wraps(f)
15.
16.
18. @wraps(f)
24.
25.
27. if b >= 0:
29. a += 1
30. b -= 60
34. b += 60
35. return a, b
36.
37.
38. @total_ordering
40.
42. self.h = h
43. self.m = m
44. self.s = s
45.
46. @property
49.
50. @h.setter
51. @_int_required
54.
55. @property
58.
59. @m.setter
60. @_int_required
64.
65. @property
68.
69. @s.setter
70. @_int_required
74.
80.
81. @_time_required
84.
85. @_time_required
88.
89. @_time_required
94.
95. @_time_required
106.
Artículos relacionados
Curso online �
¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender
Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.
Consultoría �
Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y
empresas. Consultanos por tu proyecto.
En la programación orientada a objetos, un interfaz define al conjunto de métodos que tiene que
tener un objeto para que pueda cumplir una determinada función en nuestro sistema. Dicho de otra
manera, un interfaz define como se comporta un objeto y lo que se puede hacer con el.
Piensa en el mando a distancia del televisor. Todos los mandos nos ofrecen el mismo interfaz con
las mismas funcionalidades o métodos. En pseudocódigo se podría escribir su interfaz como:
# Pseudocódigo
interface Mando{
def siguiente_canal():
def canal_anterior():
def subir_volumen():
def bajar_volumen():
Es importante notar que los interfaces no poseen una implementación per se, es decir, no llevan
código asociado. El interfaz se centra en el qué y no en el cómo.
Se dice entonces que una determinada clase implementa una interfaz, cuando añade código a los
métodos que no lo tenían (denominados abstractos). Es decir, implementar un interfaz consiste en
pasar del qué se hace al cómo se hace.
Podríamos decir entonces que los mandos de Samsung y LG implementan nuestro interfaz Mando,
ya que ambos tienen los métodos definidos, pero con implementaciones diferentes. Esto es debido
a que cada empresa resuelve el mismo problema con un enfoque diferente, pero lo que se ofrece
visto desde el exterior es lo mismo.
• Interfaces informales
• Interfaces formales
Dependiendo de la magnitud y tipo del proyecto en el que trabajemos, es posible que los interfaces
informales sean suficientes. Sin embargo, a veces no bastan, y es donde entran los interfaces
formales y las metaclases, ambos conceptos bastante avanzados pero que la mayoría de
programadores tal vez pueda ignorar.
Interfaces informales
Los interfaces informales pueden ser definidos con una simple clase que no implementa los
métodos. Volviendo al ejemplo de nuestro interfaz mando a distancia, lo podríamos escribir en
Python como:
class Mando:
def siguiente_canal(self):
pass
def canal_anterior(self):
pass
def subir_volumen(self):
pass
def bajar_volumen(self):
pass
Una vez definido nuestro interfaz informal, podemos usarlo mediante herencia. Las
clases MandoSamsung y MandoLG implementan el interfaz Mando con código particular en los
métodos. Recuerda, pasamos del qué hace al cómo se hace.
class MandoSamsung(Mando):
def siguiente_canal(self):
print("Samsung->Siguiente")
def canal_anterior(self):
print("Samsung->Anterior")
def subir_volumen(self):
print("Samsung->Subir")
def bajar_volumen(self):
print("Samsung->Bajar")
class MandoLG(Mando):
def siguiente_canal(self):
print("LG->Siguiente")
def canal_anterior(self):
print("LG->Anterior")
def subir_volumen(self):
print("LG->Subir")
def bajar_volumen(self):
print("LG->Bajar")
Como hemos dicho, esto es una solución perfectamente válida en la mayoría de los casos, pero
existe un problema con el que entenderás perfectamente porqué lo llamamos interfaz informal.
Si un método queda sin implementar, podríamos tener problemas en el futuro, ya que al llamar a
dicho método no tendríamos código que ejecutar. Es cierto que se podría resolver
cambiando pass por raise NotImplementedError(), pero el error lo obtendríamos en tiempo de
ejecución.
Hasta aquí los interfaces informales. Nótese que este tipo de interfaces es posible en Python
debido a una de sus características estrella, el duck typing, por lo que te recomendamos que leas
acerca de este concepto tan importante en Python.
Interfaces formales
Una vez tenemos el contexto de lo que son los interfaces informales, ya estamos en condiciones de
entender los interfaces formales.
Los interfaces formales pueden ser definidos en Python utilizando el módulo por defecto llamado
ABC (Abstract Base Classes). Los abc fueron añadidos a Python en la PEP3119.
Simplemente definen una forma de crear interfaces (a través de metaclases) en los que se definen
unos métodos (pero no se implementan) y donde se fuerza a las clases que usan ese interfaz a
implementar los métodos. Veamos unos ejemplos.
El interfaz más sencillo que podemos crear es de la siguiente manera, heredando de abc.ABC.
class Mando(ABC):
pass
La siguiente sintaxis es también válida, y aunque se sale del contenido de este capítulo, es
importante que asocies el módulo abc con las metaclases.
pass
Pero veamos un ejemplo concreto continuando con nuestro ejemplo del mando a distancia.
Podemos observar como se usa el decorador @abstractmethod.
Un método abstracto es un método que no tiene una implementación, es decir, que no lleva código.
Un método definido con este decorador, forzará a las clases que implementen dicho interfaz a
codificarlo.
class Mando(metaclass=ABCMeta):
@abstractmethod
def siguiente_canal(self):
pass
@abstractmethod
def canal_anterior(self):
pass
@abstractmethod
def subir_volumen(self):
pass
@abstractmethod
def bajar_volumen(self):
pass
Lo primero a tener en cuenta es que no se puede crear un objeto de una clase interfaz, ya que sus
métodos no están implementados.
mando = Mando()
# TypeError: Can't instantiate abstract class Mando with abstract methods bajar_volumen,
canal_anterior, siguiente_canal, subir_volumen
Sin embargo si que podemos heredar de Mando para crear una clase MandoSamsung. Es muy
importante que implementemos todos los métodos, o de lo contrario tendremos un error. Esta es
una de las diferencias con respecto a los interfaces informales.
class MandoSamsung(Mando):
def siguiente_canal(self):
print("Samsung->Siguiente")
def canal_anterior(self):
print("Samsung->Anterior")
def subir_volumen(self):
print("Samsung->Subir")
def bajar_volumen(self):
print("Samsung->Bajar")
mando_samsung = MandoSamsung()
mando_samsung.bajar_volumen()
# Samsung->Bajar
class MandoLG(Mando):
def siguiente_canal(self):
print("LG->Siguiente")
def canal_anterior(self):
print("LG->Anterior")
def subir_volumen(self):
print("LG->Subir")
def bajar_volumen(self):
print("LG->Bajar")
mando_lg = MandoLG()
mando_lg.bajar_volumen()
# LG->Bajar
Llegados a este punto tenemos por lo tanto dos conceptos diferentes claramente identificados:
• Por un lado tenemos nuestro interfaz Mando. Se trata de una clase que define el
comportamiento de un mando genérico, pero sin centrarse en los detalles de cómo
funciona. Se centra en el qué.
• Por otro lado tenemos dos clases MandoSamsung y MandoLG que implementan/heredan el
interfaz anterior, añadiendo un código concreto y diferente para cada mando. Ambas clases
representan el cómo.
Hasta aquí hemos visto como crear un interfaz formal sencilla usando abc con métodos abstractos,
pero existen más funcionalidades que merece la pena ver. Vamos a por ello.
Clases virtuales
Como ya sabemos, se considera que una clase es subclase o issubclass de otra si hereda de la
misma, como podemos ver en el siguiente ejemplo.
class ClaseA:
pass
class ClaseB(ClaseA):
pass
print(issubclass(ClaseB, ClaseA))
# True
Pero, ¿y si queremos que se considere a una clase la padre cuando no existe herencia entre ellas?
Es aquí donde entran las clases virtuales. Usando register() podemos registrar a una ABC como
clase padre de otra. En el siguiente ejemplo FloatABC se registra como clase virtual padre de float.
class FloatABC(metaclass=ABCMeta):
pass
FloatABC.register(float)
# True
Análogamente podemos realizar lo mismo con una clase definida por nosotros.
@FloatABC.register
class MiFloat():
pass
x = MiFloat()
print(issubclass(MiFloat, FloatABC))
# True
Métodos abstractos
Como ya hemos visto los métodos abstractos son aquellos que son declarados pero no tienen una
implementación. También hemos visto como Python nos obliga a implementarlos en la clases que
heredan de nuestro interfaz. Esto es posible gracias al decorador @abstractmethod.
class Clase(metaclass=ABCMeta):
@abstractmethod
def metodo_abstracto(self):
pass
Como recordatorio, un método de clase es llamado sobre la clase y no sobre el objeto, pudiendo
modificar la clase pero no el objeto.
class Clase(ABC):
@classmethod
@abstractmethod
def metodo_abstracto_de_clase(cls):
pass
@staticmethod
@abstractmethod
def metodo_abstracto_estatico():
pass
class Clase(ABC):
@property
@abstractmethod
def metodo_abstracto_propiedad(self):
pass
Python nos ofrece un conjunto de Abstract Base Classes que podemos usar para crear nuestras
propias clases, denominado collections.abc. Es por tanto importante echarles un vistazo, ya que
tal vez exista ya la que necesitemos.
Podemos por ejemplo crear una clase MiSet que use abc.Set, pero que tenga un comportamiento
ligeramente distinto. En este caso, deberemos implementar los métodos
mágicos __iter__, __contains__ y __len__, ya que son definidos como abstractos en el abc.
class MiSet(abc.Set):
self.elements = []
self.elements.append(value)
def __iter__(self):
return iter(self.elements)
def __len__(self):
return len(self.elements)
def __str__(self):
Como podemos ver, heredamos ciertas funcionalidades como los operadores & y | que pueden ser
usados sobre nuestra nueva clase.
s1 = MiSet("abcdefg")
s2 = MiSet("efghij")
print(s1 | s2)
# efg
# abcdefghij
Abstracción en programación
Una analogía del mundo real podría ser la televisión. Se trata de un dispositivo muy complejo
donde han trabajado gran cantidad de ingenieros de diversas disciplinas como telecomunicaciones
o electrónica. ¿Os imagináis que para cambiar de canal tuviéramos que saber todos los entresijos
del aparato?. Pues bien, se nos ofrece una abstracción de la televisión, un mando a distancia. El
mando nos abstrae por completo de la complejidad de los circuitos y señales, y nos da una interfaz
sencilla que con unos pocos botones podemos usar.
Es posible crear métodos abstractos en Python con decoradores como @absttractmethod, pero
esto lo dejamos para otro post.
Existen otros conceptos muy importantes y relacionados con la programación orientada a objetos.
Aquí te los dejamos:
• Herencia
• Cohesión
• Abstracción
• Polimorfismo
• Acoplamiento
• Encapsulamiento
Acoplamiento en programación
• Acoplamiento débil, que indica que no existe dependencia de un módulo con otros. Esto
debería ser la meta de nuestro software.
• Acoplamiento fuerte, que por lo contrario indica que un módulo tiene dependencias internas
con otros.
El término acoplamiento está muy relacionado con la cohesión, ya que acoplamiento débil suele ir
ligado a cohesión fuerte. En general lo que buscamos en nuestro código es que tenga acoplamiento
débil y cohesión fuerte, es decir, que no tenga dependencias con otros módulos y que las tareas
que realiza estén relacionadas entre sí. Un código así es fácil de leer, de reusar, mantener y tiene
que ser nuestra meta. Nótese que se suele emplear alta y baja para designar fuerza y débil
respectivamente.
Si aún no te hemos convencido de porque buscamos código débilmente acoplado, veamos lo que
pasaría con un código fuertemente acoplado:
• Debido a las dependencias con otros módulo, un cambio en un modulo ajeno al nuestro
podría tener un “efecto mariposa” en nuestro código, aún sin haber modificado
directamente nuestro módulo.
• Si un módulo tiene dependencias con otros, reduce la reusabilidad, ya que para reusarlo
deberíamos copiar también las dependencias.
Veamos un ejemplo usando clases y objetos en Python. Tenemos una Clase1 que define un atributo
de clase x. Por otro lado la Clase2 basa el comportamiento del método mi_metodo() en el valor
de x de la Clase1. En este ejemplo existe acoplamiento fuerte, ya que existe una dependencia con
una variable de otro módulo.
class Clase1:
x = True
pass
class Clase2:
if Clase1.x:
self.valor = valor
mi_clase = Clase2()
mi_clase.mi_metodo("Hola")
mi_clase.valor
Puede parecer un ejemplo trivial, pero cuando el software se va complicando, no es nada raro
acabar haciendo cosas de este tipo casi sin darnos cuenta. Hay veces que dependencias externas
pueden estar justificadas, pero hay que estar muy seguro de lo que se hace.
Este tipo de dependencias también puede hacer el código muy difícil de depurar. Imaginemos que
nuestro código de la Clase2 funciona perfectamente, pero de repente alguien hace un cambio en
la Clase1. Un cambio tan sencillo como el siguiente.
Clase1.x = False
Existen otros conceptos muy importantes y relacionados con la programación orientada a objetos.
Aquí te los dejamos:
• Herencia
• Cohesión
• Abstracción
• Polimorfismo
• Acoplamiento
• Encapsulamiento
Crear una clase en Python se puede hacer un tan sólo dos líneas de código haciendo uso de la
palabra class.
class MiClase:
pass
Podemos añadir algún atributo de clase. En este caso tenemos atributos generales que pertenecen
a la clase y no a la instancia.
class MiClase:
atributo1 = "valor1"
atributo2 = "valor2"
Se podría decir que toda clase tiene un constructor, que recibe unos parámetros de entrada cuando
el objeto es creado. Creamos por lo tanto el constructor __init__. Nótese la diferencia
entre atributo1 y argumento1 (pista, atributo de clase vs instancia).
class MiClase:
atributo1 = "valor1"
atributo2 = "valor2"
self.argumento1 = argumento1
A parte de atributos y el constructor, toda clase tiene un conjunto de funciones o métodos que
realizan diferentes funcionalidades. Creamos la funcion1().
class MiClase:
atributo1 = "valor1"
atributo2 = "valor2"
def __init__(self, argumento1):
self.argumento1 = argumento1
def funcion1(self):
Crear objeto
A diferencia de la clase, un objeto define una clase particular, con unos atributos particulares para
ese objeto. Es decir, el objeto es la instancia de la clase. Se puede crear usando () sobre la clase y
pasando los argumentos de entrada separados por ,.
mi_clase = MiClase("Hola")
Una vez tenemos el objeto mi_clase, podemos acceder a todo su contenido, tanto métodos como
atributos de clase o de instancia. Simplemente hay que usar el objeto . y el método o atributo.
mi_clase.atributo1
mi_clase.atributo2
mi_clase.argumento1
mi_clase.funcion1()
Encapsulamiento en programación
Para la gente que conozca Java, le resultará un termino muy familiar, pero en Python es algo
distinto. Digamos que Python por defecto no oculta los atributos y métodos de una clase al exterior.
Veamos un ejemplo con el lenguaje Python.
class Clase:
atributo_clase = "Hola"
mi_clase.atributo_clase
mi_clase.atributo_instancia
# 'Hola'
# 'Que tal'
Ambos atributos son perfectamente accesibles desde el exterior. Sin embargo esto es algo que tal
vez no queramos. Hay ciertos métodos o atributos que queremos que pertenezcan sólo a la clase o
al objeto, y que sólo puedan ser accedidos por los mismos. Para ello podemos usar la doble __ para
nombrar a un atributo o método. Esto hará que Python los interprete como “privados”, de manera
que no podrán ser accedidos desde el exterior.
class Clase:
def __mi_metodo(self):
print("Haz algo")
self.__variable = 0
def metodo_normal(self):
self.__mi_metodo()
mi_clase = Clase()
mi_clase.metodo_normal() # Ok!
Y como curiosidad, podemos hacer uso de dir para ver el listado de métodos y atributos de nuestra
clase. Podemos ver claramente como tenemos el metodo_normal y el atributo de clase, pero no
podemos encontrar __mi_metodo ni __atributo_clase.
print(dir(mi_clase))
mi_clase._Clase__atributo_clase
# 'Hola'
mi_clase._Clase__mi_metodo()
# 'Haz algo'
Existen otros conceptos muy importantes y relacionados con la programación orientada a objetos.
Aquí te los dejamos:
• Herencia
• Cohesión
• Abstracción
• Polimorfismo
• Acoplamiento
• Encapsulamiento
Polimorfismo en programación
El polimorfismo es uno de los pilares básicos en la programación orientada a objetos, por lo que
para entenderlo es importante tener las bases de la POO y la herencia bien asentadas.
El término polimorfismo tiene origen en las palabras poly (muchos) y morfo (formas), y aplicado a la
programación hace referencia a que los objetos pueden tomar diferentes formas. ¿Pero qué
significa esto?
Pues bien, significa que objetos de diferentes clases pueden ser accedidos utilizando el mismo
interfaz, mostrando un comportamiento distinto (tomando diferentes formas) según cómo sean
accedidos.
En lenguajes de programación como Python, que tiene tipado dinámico, el polimorfismo va muy
relacionado con el duck typing.
Sin embargo, para entender bien este concepto, es conveniente explicarlo desde el punto de vista
de un lenguaje de programación con tipado estático como Java. Vamos a por ello.
Polimorfismo en Java
// Código Java
class Animal{
public Animal() {}
// Código Java
public Perro() {}
public Gato() {}
// Código Java
Recuerda que Java es un lenguaje con tipado estático, lo que significa que el tipo tiene que ser
definido al crear la variable.
Sin embargo estamos asignando a una variable Animal un objeto de la clase Perro. ¿Cómo es esto
posible?
Pues ahí lo tienes, el polimorfismo es lo que nos permite usar ambas clases de forma indistinta, ya
que soportan el mismo “interfaz” (no confundir con el interface de Java).
El siguiente código es también correcto. Tenemos un array de Animal donde cada elemento toma la
forma de Perro o de Gato.
// Código Java
Sin embargo, no es posible realizar lo siguiente, ya que OtraClase no comparte interfaz con Animal.
Tendremos un error error: incompatible types.
// Código Java
class OtraClase {
public OtraClase() {}
Polimorfismo en Python
El término polimorfismo visto desde el punto de vista de Python es complicado de explicar sin
hablar del duck typing, por lo que te recomendamos la lectura.
Al ser un lenguaje con tipado dinámico y permitir duck typing, en Python no es necesario que los
objetos compartan un interfaz, simplemente basta con que tengan los métodos que se quieren
llamar.
class Animal:
def hablar(self):
pass
Por otro lado tenemos otras dos clases, Perro, Gato que heredan de la anterior. Además,
implementan el método hablar() de una forma distinta.
class Perro(Animal):
def hablar(self):
print("Guau!")
class Gato(Animal):
def hablar(self):
print("Miau!")
A continuación creamos un objeto de cada clase y llamamos al método hablar(). Podemos observar
que cada animal se comporta de manera distinta al usar hablar().
animal.hablar()
# Guau!
# Miau!
En el caso anterior, la variable animal ha ido “tomando las formas” de Perro y Gato. Sin embargo,
nótese que al tener tipado dinámico este ejemplo hubiera funcionado igual sin que existiera
herencia entre Perro y Gato, pero esta explicación la dejamos para el capítulo de duck typing
Existen otros conceptos muy importantes y relacionados con la programación orientada a objetos.
Aquí te los dejamos:
• Herencia
• Cohesión
• Abstracción
• Polimorfismo
• Acoplamiento
• Encapsulamiento
Cohesión en Programación
La cohesión hace referencia al grado de relación entre los elementos de un módulo. En el diseño de
una función, es importante pensar bien la tarea que va a realizar, intentando que sea única y bien
definida. Cuantas más cosas diferentes haga una función sin relación entre sí, más complicado
será el código de entender. Existen por lo tanto dos tipos de cohesión:
• Por un lado tenemos la cohesión débil que indica que la relación entre los elementos es
baja. Es decir, no pertenecen a una única funcionalidad.
• Por otro la cohesión fuerte, que debe ser nuestro objetivo al diseñar programas. La cohesión
fuerte indica que existe una alta relación entre los elementos existentes dentro del módulo.
Existe también otro concepto llamado acoplamiento que explicamos en otro post. Normalmente
acoplamiento débil se relaciona con cohesión fuerte o alta.
Veámoslo con un ejemplo. Tenemos una función suma() que suma dos números. El problema es
que además de sumar dos números, los convierte a float() y además pide al usuario que introduzca
por pantalla el número. Podría parecer que esas otras dos funcionalidades no son para tanto, pero
si por ejemplo una persona quiere usar nuestra función suma() pero ya tiene los números y no
quiere pedirlos por pantalla, no le serviría nuestra función.
def suma():
print(suma)
suma()
Para que la función tuviese una cohesión fuerte, sería conveniente que la suma realizara una única
tarea bien definida, que es sumar.
def suma(numeros):
total = 0
for i in numeros:
total = total + i
return total
• Herencia
• Cohesión
• Abstracción
• Polimorfismo
• Acoplamiento
• Encapsulamiento
Excepciones
• 📗📗 Excepciones en Python
• 📙📙 Definiendo Excepciones
• 📕📕 Context Managers
Excepciones en Python
Las excepciones en Python son una herramienta muy potente que la gran mayoría de lenguajes de
programación modernos tienen. Se trata de una forma de controlar el comportamiento de un
programa cuando se produce un error.
Esto es muy importante ya que salvo que tratemos este error, el programa se parará, y esto es algo
que en determinadas aplicaciones no es una opción válida.
Imaginemos que tenemos el siguiente código con dos variables a y b y realizamos su división a/b.
a=4
b=2
c = a/b
Pero imaginemos ahora que por cualquier motivo las variables tienen otro valor, y que por
ejemplo b tiene el valor 0. Si intentamos hacer la división entre cero, este programa dará un error y
su ejecución terminará de manera abrupta.
a = 4; b = 0
print(a/b)
Veamos un ejemplo con otra excepción. ¿Que pasaría si intentásemos sumar un número con un
texto? Evidentemente esto no tiene ningún sentido, y Python define una excepción para esto
llamada TypeError.
print(2 + "2")
En base a esto es muy importante controlar las excepciones, porque por muchas comprobaciones
que realicemos es posible que en algún momento ocurra una, y si no se hace nada el programa se
parará.
¿Te imaginas que en un avión, un tren o un cajero automático tiene un error que lanza raise una
excepción y se detiene por completo?
Una primera aproximación al control de excepciones podría ser el siguiente ejemplo. Podemos
realizar una comprobación manual de que no estamos dividiendo por cero, para así evitar tener un
error tipo ZeroDivisionError.
Sin embargo es complicado escribir código que contemple y que prevenga todo tipo de
excepciones. Para ello, veremos más adelante el uso de except.
a=5
b=0
if b!=0:
print(a/b)
else:
Uso de raise
También podemos ser nosotros los que levantemos o lancemos una excepción. Volviendo a los
ejemplos usados en el apartado anterior, podemos ser nosotros los que
levantemos ZeroDivisionError o NameError usando raise. La sintaxis es muy fácil.
raise ZeroDivisionError
Visto esto, ya sabemos como una excepción puede ser lanzada. Existen dos maneras
principalmente:
• Hacemos una operación que no puede ser realizada (como dividir por cero). En este caso
Python se encarga de lanzar automáticamente la excepción.
• Habría un tercer caso que sería lanzar una excepción que no pertenece a las definidas por
defecto en Python. Pero eso te lo explicamos aquí.
A continuación veremos que podemos hacer para controlar estas excepciones, y que hacer cuando
se lanza una para que no se interrumpa la ejecución del programa.
La buena noticia es que las excepciones que hemos visto antes, pueden ser capturadas y
manejadas adecuadamente, sin que el programa se detenga. Veamos un ejemplo con la división
entre cero.
a = 5; b = 0
try:
c = a/b
except ZeroDivisionError:
En este caso no verificamos que b!=0. Directamente intentamos realizar la división y en el caso de
que se lance la excepción ZeroDivisionError, la capturamos y la tratamos adecuadamente.
La diferencia con el ejemplo anterior es que ahora no se para el programa y se puede seguir
ejecutando. Prueba a ejecutar el código y ver que pasa. Verás como el programa ya no se para.
Entonces, lo que hay dentro del try es la sección del código que podría lanzar la excepción que se
está capturando en el except. Por lo tanto cuando ocurra una excepción, se entra en
el except pero el programa no se para.
try:
except ZeroDivisionError:
except TypeError:
print("Problema de tipos!")
Puedes también hacer que un determinado número de excepciones se traten de la misma manera
con el mismo bloque de código. Sin embargo suele ser más interesante tratar a diferentes
excepciones de diference manera.
try:
print("Excepcion ZeroDivisionError/TypeError")
Otra forma si no sabes que excepción puede saltar, puedes usar la clase genérica Exception. En
este caso se controla cualquier tipo de excepción. De hecho todas las excepciones heredan
de Exception. Ver documentación.
try:
except Exception:
No obstante hay una forma de saber que excepción ha sido la que ha ocurrido.
try:
Uso de else
Al ya explicado try y except le podemos añadir un bloque más, el else. Dicho bloque se ejecutará si
no ha ocurrido ninguna excepción. Fíjate en la diferencia entre los siguientes códigos.
try:
x = 2/0
except:
else:
Sin embargo en el siguiente código la división se puede realizar sin problema, por lo que el
bloque except no se ejecuta pero el else si es ejecutado.
try:
x = 2/2
except:
else:
Uso de finally
A los ya vistos bloques try, except y else podemos añadir un bloque más, el finally. Dicho bloque se
ejecuta siempre, haya o no haya habido excepción.
Este bloque se suele usar si queremos ejecutar algún tipo de acción de limpieza. Si por ejemplo
estamos escribiendo datos en un fichero pero ocurre una excepción, tal vez queramos borrar el
contenido que hemos escrito con anterioridad, para no dejar datos inconsistenes en el fichero.
En el siguiente código vemos un ejemplo. Haya o no haya excepción el código que haya dentro
de finally será ejecutado.
try:
# Forzamos excepción
x = 2/0
except:
finally:
Ejemplos
Un ejemplo muy típico de excepciones es en el manejo de ficheros. Se intenta abrir, pero se captura
una posible excepción. De hecho si entras en la documentación de open se indica que OSError es
lanzada si el fichero no puede ser abierto.
try:
read_data = file.read()
except:
Como ya hemos comentado, en el except también se puede capturar una excepción concreta.
Dependiendo de nuestro programa, tal vez queramos tratar de manera distinta diferentes tipos de
excepciones, por lo que es una buena práctica especificar que tipo de excepción estamos tratando.
try:
with open('fichero.txt') as file:
read_data = file.read()
except OSError:
En este otro ejemplo vemos el uso de los bloques try, except, else y finally todos juntos.
try:
# Se fuerza excepción
x = 2/0
except:
else:
finally:
También se puede capturar una excepción de tipo SyntaxError, que hace referencia a errores de
sintaxis. Sin embargo el código debería estar libre de este tipo de fallos, por lo que tal vez nunca
deberías usar esto.
try:
print("Hola"))
except SyntaxError:
Assert en Python
El uso de assert en Python nos permite realizar comprobaciones. Si la expresión contenida dentro
del mismo es False, se lanzará una excepción, concretamente AssertionError. Veamos un ejemplo:
assert(1==2)
# AssertionError
Es decir, si el contenido existente dentro del assert es igual a False, se lanzará la excepción. Se
podría conseguir el mismo resultado haciendo lo siguiente, pero el uso de assert() resulta más
cómodo.
if condicion:
raise AssertionError()
Podemos también añadir un texto con información relevante acerca del assert().
# INCORRECTO
Por otro lado, también se puede hacer uso del assert() sin usar paréntesis como se muestra a
continuación.
x = "ElLibroDePython"
assert x == "ElLibroDePython"
assert() en testing
La función assert() puede ser también muy útil para realizar testing de nuestro código,
especialmente para test unitarios o unit tests. Imagínate que tenemos una
función calcula_media() que como su nombre indica calcula la media de un conjunto de números.
def calcula_media(lista):
return sum(lista)/len(lista)
assert(calcula_media([4, 8]) == 6)
Por lo que si hacemos que estas comprobaciones sean parte de nuestro código, podríamos
proteger nuestra función, asegurándonos de que nadie la “rompa”.
assert() en funciones
Puede resultar útil usar assert() cuando queremos realizar alguna comprobación, como podría
ser dentro de una función. En el siguiente ejemplo tenemos una función suma() que sólo suma las
variables si son números enteros.
assert(type(a) == int)
assert(type(b) == int)
return a+b
suma(3.0, 5.0)
suma(3, 5)
Otro ejemplo podría verificar que un objeto pertenece a una clase determinada.
class MiClase():
pass
class MiOtraClase():
pass
mi_objeto = MiClase()
mi_otro_objeto = MiOtraClase()
# Ok
assert(isinstance(mi_objeto, MiClase))
# Ok
assert(isinstance(mi_otro_objeto, MiOtraClase))
assert(isinstance(mi_objeto, MiOtraClase))
# Error, mi_otro_objeto no pertenece a MiClase
assert(isinstance(mi_otro_objeto, MiClase))
Deshabilitar assert
# ejemplo.py
assert(1==2)
Si ejecutamos nuestro script de la siguiente manera, los assert se eliminarán, por lo que no se
producirá ninguna excepción.
$ python -O ejemplo.py
Definiendo Excepciones
Antes de ver como se define una excepción, te recomendamos otros de nuestros posts que te
ayudarán a entenderlo mejor:
• Herencia en Python
• Excepciones en Python
En los posts anteriores verás como lanzar y capturar las excepciones. A continuación explicamos
como definirlas.
A pesar de que Python define un conjunto de excepciones por defecto, podrían no ser suficientes
para nuestro programa. En ese caso, deberíamos definir nuestra propia excepción.
Si queremos crear una excepción, solamente tenemos que crear una clase que herede de la
clase Exception. Es tan sencillo como el siguiente ejemplo.
class MiExcepcionPersonalizada(Exception):
pass
raise MiExcepcionPersonalizada()
También se pueden pasar parámetros de entrada al lanzarla. Esto es muy útil para dar información
adicional a la excepción. En el siguiente ejemplo se pasan dos parámetros. Para ello tenemos que
modificar la función __init__() de la siguiente manera.
# Creamos nuestra propia excepción heredando
# de la clase Exception
class MiExcepcionPersonalizada(Exception):
self.parametro1 = parametro1
self.parametro2 = parametro2
Y una vez hemos creado nuestra excepción, podemos lanzarla con raise como ya hemos visto.
También es posible acceder a los parámetros pasados como argumentos al lanzar la excepción.
try:
p1, p2 = ex.args
print(type(ex))
#<class '__main__.MiExcepcionPersonalizada'>
#parametro1 = ValorPar1
#parametro2 = ValorPar2
## Ejemplos Hay un truco muy interesante que nos permite pasar a la excepción un argumento en
forma de diccionario como se muestra a continuación.
class MiExcepcion(Exception):
pass
# Se lanza
try:
except MiExcepcion as e:
detalles = e.args[0]
print(detalles)
print(detalles["mensaje"])
print(detalles["informacion"])
# Mi Mensaje
# Mi Informacion
Como se puede ver, los parámetros son accesibles con [] ya que se trata de un diccionario.
Una forma un poco más larga de hacer lo mismo sería se la siguiente manera. En este caso los
parámetros que se pasan no son accesibles como si fueran un diccionario sino como se de un
objeto normal se tratase con .mensaje y .informacion
class MiExcepcion(Exception):
self.mensaje = mensaje
self.informacion = informacion
try:
except MiExcepcion as e:
print(e.mensaje)
print(e.informacion)
Nótese que para los ejemplos hemos usado mensaje en informacion, pero estas variables pueden
ser las que se te ocurran, y por supuesto tampoco tienen porque ser dos, podrían ser mas.
Gestores de contexto
Tal vez nunca hayas oído hablar de los gestores de contexto o context managers, pero si has
trabajado con ficheros ya los has usado sin darte cuenta. Si alguna vez has visto la cláusula with,
todo lo que pasa por debajo hace uso de los gestores de contexto.
Realmente no ofrecen ninguna funcionalidad nueva, pero permiten ahorrar código eliminando todo
lo que sea repetitivo o boilerplate. En pocas palabras, permiten ejecutar dos tareas de manera
automática, la primera al entrar al with y la segunda al salir del mismo.
fichero.write('Hola!')
¿Cómo que lo cerramos? Pues sí, aunque no se especifique expresamente, por debajo Python
ejecutará la función close() cuando se salga del bloque with. Es importante notar también que la
variable fichero no será accesible fuera del with, únciamente dentro.
El siguiente código es totalmente equivalente al anterior, pero sin hacer uso de los context
managers, simplemente de las excepciones.
try:
fichero.write('Hola!')
finally:
fichero.close()
Como puedes ver, nos podemos ahorrar algunas líneas de código usando los gestores de contexto.
Su uso también nos permite dotar a nuestro código de mayor expresivadad, una de las grandes
ventajas de Python.
Su uso se extiende también a otras clases como Lock y es también común verlos en bases de
datos. Siempre que tengamos unos recursos que son bloqueados para ser usados, y después
necesiten ser liberados pase lo que pase (aunque ocurra una excepción), los gestores de contexto
serán una buena idea.
Llegados a este punto ya sabemos usar los gestores de contexto que vienen con Python, pero ¿y si
quisiéramos definir uno nosotros? A continuación lo vemos.
Veamos la primera forma usando clases. Lo primero que tenemos que hacer es definir nuestra
clase, e implementar los siguientes métodos:
• __init__: Este método es llamado automáticmente al entrar al bloque with. Lo que devuelva
este método será asignado a la variable que especifiquemos a continuación del as. Es
común que esto sea el recurso que vamos a utilizar, un fichero por ejemplo.
• __exit__: Este método será llamado al salir del with y contiene las tareas de limpieza que
queremos ejecutar. Lo más importante es que este método se llama siempre, incluso
aunque ocurra una excepción. Sería por lo tanto equivalente al uso del bloque except.
Trabajando con ficheros, aquí se cerraría el archivo que ha sido abierto anteriormente.
Veamos un ejemplo. Implementamos los métodos descritos con un simple print() para ver lo que
pasa. Podemos ver como efectivamente son llamados.
class MiGestor:
def __enter__(self):
print("Entra en __enter__")
print("Entra en __exit__")
with MiGestor() as f:
print("Hola")
# Entra en __enter__
# Hola
# Entra en __exit__
Como se puede ver, Python llama por debajo a ambos métodos, primero al __enter__ y después
al __exit__.
Vamos a complicarlo un poco más. Como hemos indicado, el método __exit__ es ejecutado aunque
ocurra una excepción. Vamos por lo tanto a forzar una y ver que pasa.
with MiGestor() as f:
raise Exception
# Entra en __enter__
# Entra en __exit__
Como era de esperar, el contenido del método __exit__ también es ejecutado. Tal vez te hayas fijado
en los atributos de entrada del método. Son usados para obtener más información sobre la
excepción que ocurrió y poder actuar en consecuencia. Son los siguientes:
• exc_type: Tipo de excepción que fue lanzada. En nuestro ejemplo sería <class 'Exception'>
Una vez sabido esto, ya estamos en condiciones de implementar nuestro propio gestor de contexto
con un ejemplo un poco más realista. Vamos a crear nuestro propia clase que envuelva a un fichero
con un gestor de contexto. Esta clase abrirá y cerrará un fichero haciendo uso de los gestores de
contexto.
class MiClaseFichero:
self.nombre_fichero = nombre_fichero
def __enter__(self):
return self.fichero
if self.fichero:
self.fichero.close()
• En el __init__ guardamos el nombre del fichero que queremos crear, nada nuevo.
Una vez definida la clase, ya estamos en condiciones de usarla como hemos visto anteriormente.
fichero.write("Hola!")
Por supuesto se trata de un ejemplo didáctico, si quieres leer un fichero simplemente usa las
funciones que Python proporciona por defecto.
La programación orientada a objetos es muy útil, pero no conviene abusar de ella. Tal vez te
encuentres en una situación donde no sea realmente necesario crear una clase. Por suerte,
también podemos definir gestores de contexto con decoradores.
Para ello puedes usar la librería contextlib. Su uso es muy similar pero tal vez sea un poco más
complejo si no conoces los generadores y el uso del yield.
@contextmanager
def gestor_fichero(nombre_fichero):
try:
yield fichero
finally:
fichero.close()
Como puedes ver, el contenido del try sería el equivalente al contenido del __enter__ y el finally al
del __exit__. Una vez tenemos definida nuestra función, podemos usarla de la misma forma que
hemos visto anteriormente.
fichero.write("Hola!")
Es posible también anidar diferentes with, es decir, realizar una nueva llamada al with sin haber
salido del bloque anterior.
Esto puede dar lugar a códigos de lo más creativos como el que mostramos a continuación. Se trata
de un generador de índices, como el que se podría encontrar en un libro. Cada vez que se crea un
nuevo bloque with, se añade un nuevo nivel y se van numerando de cero a n, lo que modifica la
función print.
class Indice:
def __init__(self):
self.level = -1
self.numeracion = [0]
def __enter__(self):
self.level += 1
self.numeracion.append(0)
return self
#self.numeracion[self.level] = 0
self.numeracion.pop()
self.level -= 1
self.numeracion[self.level] += 1
Usando la clase Indice, podemos generar el índice de un libro. La llamada a la función print del
índice tendrá una funcionalidad distinta dependiendo de en que bloque se encuentre su llamada.
Es decir, la función imprime algo diferente dependiendo del contexto en el que se esté,
entendiendo por contexto el número de bloques with que haya anidados.
indice.print('Apartado')
with indice:
indice.print('Apartado')
indice.print('Apartado')
indice.print('Apartado')
indice.print('Apartado')
with indice:
indice.print('Apartado')
indice.print('Apartado')
with indice:
indice.print('Apartado')
indice.print('Apartado')
indice.print('Apartado')
indice.print('Apartado')
# 1 Apartado
# 1.1 Apartado
# 1.2 Apartado
# 1.3 Apartado
# 1.4 Apartado
# 1.4.1 Apartado
# 1.4.2 Apartado
# 1.4.2.1 Apartado
# 1.4.2.2 Apartado
# 2 Apartado
# 3 Apartado
Ficheros
• 📙📙 Leer archivos
• 📙📙 Escribir archivos
Al igual que en otros lenguajes de programación, en Python es posible abrir ficheros y leer su
contenido. Los ficheros o archivos pueden ser de lo más variado, desde un simple texto a contenido
binario. Para simplificar nos centraremos en leer un fichero de texto. Si quieres aprender como
escribir en un fichero te lo explicamos en este otro post.
Imagínate entonces que tienes un fichero de texto con unos datos, como podría ser un .txt o
un .csv, y quieres leer su contenido para realizar algún tipo de procesado sobre el mismo. Nuestro
fichero podría ser el siguiente.
Podemos abrir el fichero con la función open() pasando como argumento el nombre del fichero que
queremos abrir.
fichero = open('ejemplo.txt')
Método read()
Con open() tendremos ya en fichero el contenido del documento listo para usar, y podemos
imprimir su contenido con read(). El siguiente código imprime todo el fichero.
print(fichero.read())
Método readline()
Es posible también leer un número de líneas determinado y no todo el fichero de golpe. Para ello
hacemos uso de la función readline(). Cada vez que se llama a la función, se lee una línea.
fichero = open('ejemplo.txt')
print(fichero.readline())
print(fichero.readline())
Es muy importante saber que una vez hayas leído todas las línea del archivo, la función ya no
devolverá nada, porque se habrá llegado al final. Si quieres que readline() funcione otra vez, podrías
por ejemplo volver a leer el fichero con open().
Otra forma de usar readline() es pasando como argumento un número. Este número leerá
un determinado número de caracteres. El siguiente código lee todo el fichero carácter por carácter.
fichero = open('ejemplo.txt')
caracter = fichero.readline(1)
#print(caracter)
caracter = fichero.readline(1)
## Método readlines()
Existe otro método llamado readlines(), que devuelve una lista donde cada elemento es una línea
del fichero.
fichero = open('ejemplo.txt')
lineas = fichero.readlines()
print(lineas)
De manera muy sencilla podemos iterar las líneas e imprimirlas por pantalla.
fichero = open('ejemplo.txt')
lineas = fichero.readlines()
print(linea)
Argumentos de open()
Hasta ahora hemos visto la función open() con tan sólo un argumento de entrada, el nombre del
fichero. Lo cierto es que existe un segundo argumento que es importante especificar. Se trata
del modo de apertura del fichero. En la documentación oficial se explica en detalle.
Por lo tanto lo estrictamete correcto si queremos leer el fichero sería hacer lo siguiente. Aunque el
modo r sea por defecto, es una buena práctica indicarlo para darle a entender a otras personas que
lean nuestro código que no queremos modificarlo, tan solo leerlo.
fichero = open('ejemplo.txt', 'r')
## Cerrando el fichero
Otra cosa que debemos hacer cuando trabajamos con ficheros en Python, es cerrarlos una vez que
ya hemos acabado con ellos. Aunque es verdad que el fichero normalmente acabará siendo
cerrado automáticamente, es importante especificarlo para evitar tener comportamientos
inesperados.
Por lo tanto si queremos cerrar un fichero sólo tenemos que usar la función close() sobre el mismo.
Por lo tanto tenemos tres pasos:
# Cerrar el fichero
fichero.close()
Existen otras formas de hacerlo, como con el uso de excepciones que veremos en otros posts. Un
ejemplo sería el siguiente. No pasa nada si aún no entiendes el uso del try y finally, por ahora
quédate con que la sección finally se ejecuta siempre sin importar si hay un error o no. De esta
manera el close() siempre será ejecutado.
fichero = open('ejemplo.txt')
try:
# Usar el fichero
pass
finally:
fichero.close()
Y por si no fuera poco, existe otra forma de cerrar el fichero automáticamente. Si hacemos uso
se with(), el fichero se cerrará automáticamente una vez se salga de ese bloque de código.
pass
Ejemplos
Como ya hemos visto readline() lee línea por línea el fichero. También hacemos uso de un bucle
while para leer líneas mientras que no se haya llegado al final. Es por eso por lo que
comparamos linea != '', ya que se devuelve un string vació cuando se ha llegado al final.
linea = fichero.readline()
print(linea, end='')
linea = fichero.readline()
Nos podemos ahorrar alguna línea de código si hacemos lo siguiente, ya que readlines() nos
devuelve directamente una lista que podemos iterar con las líneas.
print(linea, end='')
Pero puede ser simplificado aún más de la siguiente manera. Nótese que usamos el end='' para
decirle a Python que no imprima el salto de línea \n al final del print.
print(linea, end='')
A continuación te explicamos como escribir datos en un fichero usando Python. Imagínate que
tienes unos datos que te gustaría guardar en un fichero para su posterior análisis. Te explicamos
como guardarlos en un fichero, por ejemplo, .txt. Si también quieres aprender como leer un fichero
en Python te lo explicamos en este otro post.
Lo primero que debemos de hacer es crear un objeto para el fichero, con el nombre que queramos.
Al igual que vimos en el post de leer ficheros, además del nombre se puede pasar un segundo
parámetro que indica el modo en el que se tratará el fichero. Los más relevantes en este caso son
los siguientes. Para más información consulta la documentación oficial.
• ‘w’: Borra el fichero si ya existiese y crea uno nuevo con el nombre indicado.
• ‘a’: Añadirá el contenido al final del fichero si ya existiese (append end Inglés)
Por lo tanto con la siguiente línea estamos creando un fichero con el nombre datos_guardados.txt.
Método write()
Ya hemos visto como crear el fichero. Veamos ahora como podemos añadir contenido. Empecemos
escribiendo un texto.
fichero.write("Contenido a escribir")
fichero.close()
Es muy importante el uso de close() ya que si dejamos el fichero abierto, podríamos llegar a tener
un comportamiento inesperado que queremos evitar. Por lo tanto, siempre que se abre un
fichero es necesario cerrarlo cuando hayamos acabado.
Compliquemos un poco más las cosas. Ahora vamos a guardar una lista de elementos en el fichero,
donde cada elemento de la lista se almacenará en una línea distinta.
# Abrimos el fichero
fichero.write(linea + "\n")
# Cerramos el fichero
fichero.close()
Si te fijas, estamos almacenando la línea mas \n. Es importante añadir el salto de línea porque por
defecto no se añade, y si queremos que cada elemento de la lista se almacena en una línea
distinta, será necesario su uso.
Método writelines()
También podemos usar el método writelines() y pasarle una lista. Dicho método se encargará de
guardar todos los elementos de la lista en el fichero.
fichero.writelines(lista)
fichero.close()
# Se guarda
# ManzanaPeraPlátano
Tal vez te hayas dado cuenta de que en realidad lo que se guarda es ManzanaPeraPlátano, todo
junto. Si queremos que cada elemento se almacene en una línea distinta, deberíamos añadir el
salto de línea en cada elemento de la lista como se muestra a continuación.
fichero = open("datos_guardados.txt", 'w')
fichero.writelines(lista)
fichero.close()
# Se guarda
# Manzana
# Pera
# Plátano
Podemos ahorrar una línea de código si hacemos uso de lo siguiente. En este caso nos podemos
ahorrar la llamada al close() ya que se realiza automáticamente. El código anterior se podría
reescribir de la siguiente manera.
fichero.writelines(lista)
El uso de ‘x’ hace que si el fichero ya existe se devuelve un error. En el siguiente código creamos un
fichero e inmediatamente después intentamos crear un fichero con el mismo nombre con la opción
‘x’. Por lo tanto se devolverá un error.
f = open("mi_fichero.txt", "w")
# f = open("mi_fichero.txt", "x")
# Error! Ya existe
En este otro ejemplo vamos a usar un fichero para establecer una comunicación entre dos
funciones. A efectos prácticos puede no resultar muy útil, pero es un buen ejemplo para mostrar la
lectura y escritura de ficheros.
Tenemos por lo tanto una función escribe_fichero() que recibe un mensaje y lo escribe en un fichero
determinado. Y por otro lado tenemos una función lee_fichero() que devuelve el mensaje que está
escrito en el fichero.
Date cuenta lo interesante del ejemplo, ya que podríamos tener estos dos códigos ejecutándose en
diferentes maquinas o procesos, y podrían comunicarse a través del fichero. Por un lado se escribe
y por el otro se lee.
def escribe_fichero(mensaje):
fichero.write(mensaje)
def lee_fichero():
mensaje = ""
mensaje = fichero.read()
f = open('fichero_comunicacion.txt', 'w')
f.close()
return mensaje
escribe_fichero("Esto es un mensaje")
print(lee_fichero())
• 📙📙 Python PEP8
• 📙📙 Nombrando Variables
• 📙📙 Argparse en Python
• 📙📙 Errores Comunes
• 📙📙 Código Pythonico
Introducción
La PEP8 es una guía que indica las convenciones estilísticas a seguir para escribir código Python.
Se trata de un conjunto de recomendaciones cuyo objetivo es ayudar a escribir código más legible y
abarca desde cómo nombrar variables, al número máximo de caracteres que una línea debe tener.
De acuerdo con Guido van Rossum, el código es leído más veces que escrito, por lo que resulta
importante escribir código que no sólo funcione correctamente, sino que además pueda ser leído
con facilidad. Esto es precisamente lo que veremos en este artículo.
Code is read much more often than it is written, Guido van Rossum
Dos mismos códigos pueden realizar lo mismo funcionalmente, pero si no se siguen unas
directrices estilísticas, se puede acabar teniendo un código muy difícil de leer. Los problemas más
frecuentes suelen ser:
Aunque es cierto que ciertas directrices pueden resultar arbitrarias, Python define en la PEP8 las
normas estilísticas a seguir para cualquier código parte de la librería estándar, por lo que queda al
criterio de cada uno usar estas recomendaciones o no. Sin embargo, prácticamente cualquier
código o librería usado por gran cantidad de personas, emplea estas recomendaciones, al haber un
amplio consenso en la comunidad.
A veces puede resultar complicado acordarnos de todas y cada una de las normas de la PEP8, por
lo que hay herramientas que nos ayudan a corregir automáticamente o indicarnos donde hay
problemas en nuestro código. Hay dos tipos de herramientas:
Los autoformatters se limitan a indicarnos donde nuestro código no cumple con las normas, y en
ciertos casos realiza las correcciones automáticamente. Por ejemplo, podemos
instalar autopep8 y se puede instalar de la siguiente manera:
Veamos un ejemplo. Para alguien recién iniciado en Python, tal vez el siguiente código parezca
válido, sin embargo alguien que conozca la PEP8 podrá identificar varios problemas.
# script.py
resultado=A+B+C
if imprime != False:
print(resultado)
return resultado
a =4
variable_b = 5
var_c = 10
Usando el comando anterior, se nos informará de todas las reglas que nuestro código no cumple,
para que las podamos corregir. Es importante notar que existen reglas que pueden ser corregidas
automáticamente, y otras que no.
• Sin embargo autopep8 nunca modificará el nombre de una variable, por lo que si
incumplimos alguna norma en lo relativo a nombrar variables deberemos corregir de forma
manual las ocurrencias.
• E305: Después de la declaración de una función debemos dejar dos espacios en blanco.
• También tenemos otros problemas relacionados con cómo nombrar a funciones y variables.
Las funciones y variables deben ir en snake case. Lo veremos en detalle más adelante.
Teniendo en cuenta lo mencionado, podemos implementar las correcciones para tener un código
que cumple con la PEP8.
# script.py
resultado = a + b + a
if imprime:
print(resultado)
return resultado
a=4
variable_b = 5
var_c = 10
Visto ya un ejemplo concreto, a continuación veremos las normas más importantes introducidas
en la PEP8.
Líneas en Blanco
El uso de líneas en blanco mejora notablemente la legibilidad. Mucho código seguido puede ser
difícil de leer, pero un uso excesivo de líneas en blanco puede ser molesto. Python deja su uso a
nuestro criterio, siempre y cuando cumplamos lo siguiente:
• Rodear las funciones y clases con dos líneas en blanco. Cada vez que definamos una clase o
una función es necesario dejar dos líneas en blanco por arriba y dos por abajo.
• Dejar una línea en blanco entre los métodos de una clase. Los métodos de una clase
deberán tener una línea en blanco entre ellos.
• Usar líneas en blanco para agrupar pasos similares. Si tenemos un conjunto de código que
realiza una función concreta, es conveniente delimitarlo con una línea en blanco, de la
misma manera que un libro separa ideas en párrafos.
def metodo_a(self):
pass
def metodo_b(self):
pass
class ClaseB:
def metodo_a(self):
pass
def metodo_b(self):
pass
def funcion():
pass
También resulta conveniente separar con una línea diferentes funcionalidades. La siguiente
función calcula la media y la mediana, por lo que las separamos con una línea en blanco.
def calcula_media_mediana(valores):
# Calculamos la media
suma_valores = 0
suma_valores += valor
# Calculamos la mediana
valores_ordenados = sorted(valores)
indice = len(valores) // 2
if len(valores) % 2:
mediana = valores_ordenados[indice]
else:
mediana = (valores_ordenados[indice]
+ valores_ordenados[indice + 1]) / 2
Espacios en Blanco
El uso de espacios en blanco puede resultar clave para mejorar la legibilidad de nuestro código, y
es por lo que la PEP8 nos dice dónde debemos usar espacios y dónde no. Se trata de buscar un
punto de equilibrio entre un código demasiado disperso y con gran cantidad de espacios, y un
código demasiado junto donde no se identifican sus partes.
# Correcto
x=5
# Incorrecto
x=5
# Correcto
if x == 5:
pass
# Incorrecto
if x==5:
pass
Pero cuando tengamos funciones con argumentos por defecto, no debemos dejar espacios.
# Correcto
def mi_funcion(parameto_por_defecto=5):
print(parameto_por_defecto)
# Incorrecto
print(parameto_por_defecto)
def duplica(a):
return a * 2
# Correcto
duplica(2)
# Incorrecto
duplica( 2 )
# Correcto
lista = [1, 2, 3]
# Incorrecto
my_list = [ 1, 2, 3, ]
El uso de los espacios resulta muy útil cuando se combinan varios operadores utilizando diferentes
variables, utilizando los espacios para agrupar por orden de mayor prioridad. Es por ello por lo que
no dejamos espacios en x**2 ni (x-y) dado que la potencia y el uso de paréntesis son los operadores
con mayor prioridad.
# Correcto
y = x**2 + 1
z = (x-y) * (x+y)
# Incorrecto
y = x ** 2 + 5
z = (x - y) * (x + y)
Siguiendo la misma filosofía de agrupar por orden de ejecución, tenemos los siguiente ejemplos,
siendo el primero el preferido por algunos linters.
# Correcto
if x > 0 and x % 2 == 0:
print('...')
# Correcto
print('...')
# Incorrecto
if x% 2 == 0:
print('...')
# Correcto
print(x, y)
# Incorrecto
print(x , y)
Cuando usemos listas no usar espacios antes del índice o entre el índice y los [].
# Correcto
lista[0]
# Incorrecto
lista [1]
# Incorrecto
lista [ 1 ]
Tampoco usando diccionarios.
# Correcto
diccionario['key'] = lista[indice]
# Incorrecto
Por último y aunque pueda parecer raro para la gente que venga de otros lenguajes de
programación, no se recomienda alinear las variables como se muestra a continuación.
# Correcto
var_a = 0
variable_b = 10
otra_variable_c = 3
# Incorrecto
var_a =0
variable_b = 10
otra_variable_c = 3
Como ya hemos visto en otros artículos, Python no usa {} para designar bloques de código como
otros lenguajes de programación, sino que usa bloques identados para indicar que un determinado
bloque de código pertenece a por ejemplo un if.
if x > 5:
pass
Un bloque identado se representa usando cuatro espacios y aunque el uso del tabulador pueda
parecer lo mismo, Python 3 no recomienda su uso. Como regla de oro:
Por otro lado, también se puede identar el código para evitar tener líneas muy largas, que resultan
difíciles de leer. Es importante recordar que la PEP8 limita el tamaño de línea a 79 caracteres.
# Correcto
tercer_parametro, cuarto_parametro,
quinto_parametro):
print("Python")
# Incorrecto
print("Python")
Lo siguiente sería incorrecto ya que no se diferencian los argumentos de entrada del bloque de
código a ejecutar por la función.
# Incorrecto
tercer_parametro, cuarto_parametro,
quinto_parametro):
print("Python")
Análogamente se puede romper un if en diferentes líneas, útil cuando se usan gran cantidad de
condiciones que no entran una una línea.
# Correcto
if (condicion_a and
condicion_b):
print("Python")
Tamaño de linea
Se recomienda limitar el tamaño de cada línea a 79 caracteres, para evitar tener que hacer scroll a
la derecha. Este límite también permite tener abiertos múltiples ficheros en la misma pantalla, uno
al lado de otro. Por otro lado se limita el uso de docstrings y comentarios a 72 caracteres.
En los casos que tengamos una línea que no sea posible romper, podemos usar \ para continuar
con la línea en una nueva. Esto es algo que a veces puede darse en los context managers.
# Correcto
fichero_2.write(fichero_1.read())
Operaciones largas
Si queremos realizar una operación muy larga que no entra en una línea, tendremos que dividirla en
múltiples. Lo recomendado es usar el operador al principio de cada línea, ya que resulta mas fácil
de leer.
# Recomendado
income = (variable_a
+ variable_b
+ (variable_c - variable_d)
- variable_e
- variable_f)
# No recomendado
income = (variable_a +
variable_b +
(variable_c - variable_d) -
variable_e -
variable_f)
Codificación de ficheros
Los ficheros se codifican por defecto en ASCII para Python 2 y UTF-8 para Python 3, por lo que será
necesario definir la codificación que usemos cuando queramos usar otro tipo.
Esto resulta muy importante, ya que si queremos almacenar una cadena que contiene caracteres
no UTF-8 como ó y ñ, deberemos especificar el tipo de encoding de acuerdo a la PEP263. El
siguiente código puede dar problemas.
# SyntaxError: Non-UTF-8 code starting with '\xf3' in file script.py on line 1, but no encoding
declared; see http://python.org/dev/peps/pep-0263/ for details
Sin embargo, con un pequeño cambio, podemos cambiar la forma en la que se codifica el texto.
Por otro lado, si tienes intención de desarrollar código para la librería estándar de Python, o
contribuir en un proyecto con alcance global, debes saber lo siguiente:
• Las únicas excepciones son los test para código no ASCII y los nombres de autores.
A la hora de escribir código, todo tiene nombres: variables, clases, funciones, paquetes, módulos,
etc. Es por lo tanto muy importante seguir unas directrices determinadas para que nuestro código
sea lo más legible posible. No se nombra igual a una clase que a una función, y tampoco suele ser
recomendable usar nombres como a o x ya que aporta poca información. A continuación lo vemos
en detalle.
Eligiendo Nombres
Antes de nada debemos debemos pensar el nombre que le vamos dar a nuestra variable clase o
función. Es importante tener en cuenta lo siguiente:
• Evitar usar palabras reservadas. Si es necesario usar una palabra reservada como class,
usar class_ como alternativa.
• Usar _variable para especificar uso interno. Por ejemplo from m import * no importaría lo que
empieza con _.
• Se puede usar __variable para invocar el name mangling y hacer privadas determinadas
variables o métodos.
• Para métodos mágicos usar siempre __init__, pero no son nombres que debemos crear sino
reutilizar los que Python nos ofrece.
Supongamos que ya sabemos como vamos a nombrar a nuestra clase, función o variable.
Pongamos que queremos llamar a nuestra función “mi función de prueba”. Dado que no podemos
utilizar espacios para nombrar variables, hay diferentes alternativas:
• mi_funcion_de_prueba
• MiFuncionDePrueba
• MIFUNCIONDEPRUEBA
• MI_FUNCION_DE_PRUEBA
• mifunciondeprueba
Algunas de estas alternativas son conocidas como Camel Case o snake_case en el mundo de la
programación. Pues bien, Python define cómo nombrar a cada tipo de la siguiente manera:
• Clases: Uso de CamelCase, usando mayúscula y sin barra baja: MiClase, ClaseDePrueba.
• Métodos: Al igual que las funciones, usar snake case: metodo, mi_metodo.
• Paquetes: En minúsculas pero sin separar por barra bajas: packete, mipaquete
# mi_script.py
CONSTANTE_GLOBAL = 10
class MiClase():
mi_objeto = MiClase()
print(mi_objeto.mi_primer_metodo(5, 5))
# Correcto
import os
import sys
# Incorrecto
Sin embargo cuando se importen varios elementos de una misma librería, si sería correcto
importarlos en la misma línea.
# Correcto
Con respecto a su organización, debiendo haber una línea de separación entre cada grupo:
Por último, deben evitarse el from <módulo> import *. El uso de * importa todo lo presente en
el <módulo>, por lo que no queda claro que se está usando y que no.
# Incorrecto
# Correcto
El uso de comas al final de la línea suele ser opcional, salvo cuando se quiera crear tuplas de un
sólo elemento como se muestra a continuación.
# Correcto
tupla = (1,)
print(tupla[0])
# Salida: 1
Sin embargo aunque su uso sea opcional en el resto de casos, en ciertas ocasiones puede estar
justificado si por ejemplo tenemos una lista de elementos que puede cambiar con el tiempo. En
este caso el uso de , al final puede ser de ayuda al sistema de control de versiones que utilicemos
(como Git).
# Correcto
FICHEROS = [
'fichero1.txt',
'fichero2.txt',
# Incorrecto
Comentarios
Los comentarios son muy importantes para realizar anotaciones a futuros lectores de nuestro
código, y aunque resulta difícil definir cómo se se debe comentar el código, hay ciertas directrices
que debemos seguir:
• Cualquier comentario que contradiga el código es peor que ningún comentario. Por ello es
muy importante que si actualizamos el código, no olvidarnos de actualizar los comentarios
para evitar crear inconsistencias.
• Los comentarios deben ser frases completas, con la primera letra en mayúsculas.
• Aunque cada uno es libre de escribir sus comentarios en el idioma que considere oportuno,
se recomienda hacerlo en Inglés.
• Evitar comentarios poco descriptivos que no aporten nada más allá de lo que ya se ve a
simple vista.
# Incorrecto
# Correcto
Estoy seguro de que alguna vez has visto métodos, atributos o funciones que tienen algún tipo de
guión o barra baja _ en su nombre. La verdad que al principio puede resultar un poco confuso, ya
algunos pueden tenerlo al principio otros pueden tener incluso doble barra baja y otros pueden
tenerlo al principio y al final. Todo un lío. Veamos las posibilidades:
Es importante notar que el uso de _ puede significar dos cosas. Por un lado puede significar
una mera convención que no alterará el comportamiento de nuestro código. Por otro lado, en
algunos casos su uso si modifica el comportamiento y su uso es relevante. Lo vemos a
continuación.
Al inicio: _nombre
El uso de _ antes del nombre de una variable, como podría ser _mi_variable no modifica el
comportamiento del código. Su uso es una mera convención que ha sido acordada por los usuarios
de Python, y que indica que esa variable no debería ser accedida desde fuera de la clase, pero
puede serlo.
class Clase:
def __init__(self):
self._variable = 10
mi_clase = Clase()
mi_clase._variable # 10
Su uso si que puede modificar el comportamiento del código usado con funciones. Una función es
definida con _ no es importada por defecto si usamos from x import *.
Al Final: nombre_
Para entender el uso de la barra baja al final de una variable o función, es importante saber que
Python tiene un determinado conjunto de palabras reservadas o keywords. Estas palabras no
pueden ser usadas, porque de serlo Python se confundiría y no sabría como interpretarlas. Si por el
motivo que sea, queremos llamar a una variable con el mismo nombre que una palabra reservada,
podemos hacer algo así como class_. El siguiente código muestra que pasa si intentamos usar una
palabra reservada para llamar a una variable.
Podemos usar _ para solucionarlo. Nótese que a diferencia de otras formas de usar _ en este
caso no modifica comportamiento, por lo que su uso es arbitrario.
class_ = 5
def_ = 10
A diferencia del uso de una sola barra baja, el uso de la doble __ en un atributo o método hace que
Python lo oculte al exterior. Existe un concepto muy interesante y particular de Python
llamado name mangling, del que te recomendamos leer más.
Por lo tanto, en el siguiente ejemplo __nombre no será accesible desde el exterior de la clase. Por
supuesto si que podría accederse desde la propia clase.
class Clase:
def __init__(self):
self.__variable = 10
mi_clase = Clase()
Por último, el uso de __ al principio y al final de un nombre tiene especial relevancia cuando
es aplicado a métodos. De hecho, a lo largo de este post ya has visto el uso de __init__ varias veces.
Se trata de una forma que usa Python para designar a los conocidos como métodos mágicos como
pueden ser también el __call__ o el __le__. Por norma general, es mejor no definir métodos propios
con estos nombres, y limitarse a utilizar los que Python ya ofrece.
class Clase:
def __init__(self):
print("Init")
Su uso suele ser común cuando queremos descartar una variable que por ejemplo pueda devolver
una función. Veamos un ejemplo de una función que devuelve dos parámetros, la suma y la resta
de dos variables.
def sumayresta(a, b):
Tal vez queramos sólo el valor de la suma y no nos interese el valor de la resta. Para ello podríamos
hacer lo siguiente.
suma, _ = sumayresta(5, 5)
# 10
Es una forma de decir “no me interesa esta variable”, pero como no se puede dejar el hueco vacío,
se usa _ para rellenarlo.
Argparse en Python
Los Command Line Interface o interfaces de línea de comandos (comúnmente abreviado como CLI)
son una herramienta que ofrecen gran cantidad de programas para interactuar con ellos. Si alguna
vez has usado el comando ls o mkdir de Linux, ya has usado un CLI.
$ ls -ta
$ mkdir -p foo
• Por un lado tenemos el comando ls o mkdir, que determina el código o librería que se va a
usar.
• Después tenemos las opciones como -ta o -p, que modifican el comportamiento del
comando que precede. Por ejemplo, la opción -t ordena los ficheros por orden de
modificación.
• Por ultimo tenemos los argumentos que se le pasan al comando. Pueden no existir como en
el caso de ls, o tener uno como en el caso de mkdir, que indica el nombre del fichero a crear.
Los CLI son una herramienta perfecta para exponer tu código a que pueda ser usado por otras
personas, de manera sencilla y encapsulando el contenido que está en el interior. De ahí su nombre
de interfaz, que no es gráfico, sino de línea de comandos.
Vamos a ver un ejemplo más relacionado con Python. Imagina que nos han encargado hacer un
software para resolver un problema determinado, y que ya tenemos todo el código listo y
funcionando. Por las características del proyecto, dicho software no tendrá un interfaz gráfico de
usuario, pero si que será necesario recibir diferentes parámetros para cambiar el comportamiento
del programa. Es decir, tenemos unas variables que serán recibidas desde fuera.
Supongamos que tenemos el siguiente código, donde tenemos dos parámetros a y b que se suman,
restan o multiplican en función del valor de operacion.
# calculadora.py
operacion = "suma"
# Parámetros de la operación
a=4
b=7
if operacion == "suma":
print(a+b)
print(a-b)
print(a*b)
Lo que queremos por tanto es que los parámetros a, b y operacion sean recibidos desde fuera de
nuestro programa, a través de la línea de comandos.
Imaginémonos que la persona que va a usar este código ni si quiera sabe Python, pero quiere usar
nuestro software como una calculadora. En este caso necesitaremos abstraer al usuario del
código, y darle una ventana al exterior desde la que pueda simplemente decir el valor de a, b y
la operacion, y obtener el resultado.
Nuestro objetivo es por tanto buscar una manera en la que un usuario pueda pasar por terminal los
valores de a, b y operacion para que sean usados por el código. Una forma sería la siguiente.
La primera forma que tenemos de que un script reciba argumentos desde el terminal es usando el
módulo sys.
# calculadora.py
import sys
print(sys.argv)
La siguiente llamada captura todo lo que se pasa después de python. Como puedes ver, el primer
elemento argv[0] nos devuelve el nombre del script. Por otro lado, se capturan todos y cada uno de
los argumentos que se pasan a la derecha del mismo.
Con esta lista ya tendríamos todo lo que necesitamos para usar nuestra calculadora.py, pero existe
una manera mucho mejor de tratar los argumentos, y es usando la librería argparse. Veamos como
funciona.
La librería argparse fue introducida en Python en la PEP389 y permite crear CLI para todo tipo de
proyectos, ofreciendo un amplio abanico de posibilidades útiles hasta en los proyectos más
complejos.
Introducción a argparse
Vamos a comenzar con una primera aproximación de lo que sería el uso de argparse, creando un
interfaz para nuestra calculadora mostrada anteriormente:
• Importamos la librería.
• Creamos un ArgumentParser.
• Añadimos argumentos.
# script.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("a")
parser.add_argument("b")
parser.add_argument("operacion")
args = parser.parse_args()
variables = vars(args)
print(variables)
Si llamamos al código anterior en el terminal, podemos ver como ya tenemos todo lo necesario
para nuestra calculadora.
Con esta primera aproximación ya tendríamos todas las variables que necesitamos en
el diccionario variables, pero aunque pueda parecer que ya hemos resuelto nuestro problema,
tenemos los siguientes problemas:
• Estamos usando parámetros posicionales, lo que significa que estamos obligados a pasar
primero a, después b y por último c. Si pasamos los argumentos en otro orden, romperemos
el programa.
• No especificamos los tipos de las variables, por defecto todas son string cuando en
realidad a y b deberían ser integer. Esto es un problema ya que 7+7 con cadenas es 77.
• El parámetro operacion debería tomar sólo valores discretos, ya que únicamente queremos
suma/resta/multiplicación. Deberíamos forzar un error si se pasa una operación no
soportada.
• Por defecto, todos los parámetros son obligatorios, pero esto puede no ser siempre el caso.
Imagina que por defecto queremos sumar, en el caso de que no se especifique la operacion.
• Por último, no estamos ofreciendo ninguna documentación, por lo que la persona que tenga
nuestro software no sabrá como usarlo.
En vista a lo anterior, podemos realizar las siguientes modificaciones en el código para hacerlo más
correcto. Fijémonos en los cambios.
• El uso de -a y --numero_a permite indicar el nombre del parámetro que estamos pasando,
por lo que ya no será necesario hacerlo en un orden fijo. Se utiliza un guión para la
abreviación del argumento y dos para el nombre completo.
Poniéndolo todo junto en nuestro programa calculadora.py, tendríamos algo como lo siguiente.
# calculadora.py
import argparse
parser.add_argument('-o', '--operacion',
type=str,
default='suma', required=False,
args = parser.parse_args()
if args.operacion == 'suma':
print(args.numero_a + args.numero_b)
print(args.numero_a - args.numero_b)
print(args.numero_a * args.numero_b)
Como vemos a continuación, hay diferentes formas de realizar la llamada a nuestro script y son
todas equivalentes. La forma más sencilla es usando las abreviaciones como se muestra a
continuación.
21
21
Una vez vistas como se realizarían las llamadas, veamos otras características. Por ejemplo, para
acceder a la documentación usamos --help.
Calculadora, suma/resta/multiplica a y b
optional arguments:
Parámetro a
Parámetro b
Obtendremos un error si usamos un tipo incorrecto. Por ejemplo a no puede ser una cadena.
calculadora.py: error: argument -o/--operacion: invalid choice: 'Hola' (choose from 'suma', 'resta',
'multiplicacion')
Por último, el parámetro operacion es opcional, tomando el valor de suma por defecto.
10
Como podemos observar el uso de type, help, choice, default y required nos ofrecen
funcionalidades extra que hacen que nuestra CLI sea más completa. Si bien es cierto que son los
más usados, Python ofrece otra muchas funcionalidades que vemos a continuación.
Usando abreviaciones
Es común soportar el uso de argumentos con - y -- como hemos visto anteriormente, siendo su uso
heredado de gnu. Normalmente se usa - para una abreviación, y -- para el nombre completo, siendo
el segundo el usado para nombrar a la variable que almacenará el argumento.
Por otro lado, Python permite usar abreviaciones del argumento automáticamente. Es decir, si
definimos --operacion, el argumento --oper funcionará correctamente.
# abreviaciones.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--operacion')
args = parser.parse_args()
print(vars(args))
{'operacion': 'suma'}
{'operacion': 'suma'}
{'operacion': 'suma'}
Anteriormente hemos visto como por defecto los argumentos se pasan usando -, pero es posible
también cambiar el prefijo usando prefix_chars al crear el ArgumentParser.
# cambio_prefijo.py
import argparse
parser = argparse.ArgumentParser(prefix_chars='+')
parser.add_argument('+a')
parser.add_argument('+b')
args = parser.parse_args()
print(vars(args))
$ python cambio_prefijo.py +h
optional arguments:
+a A
+b B
Cuando trabajamos con un número muy elevado de argumentos, puede resultar interesante
moverlos a un fichero, lo que nos permitirá simplificar la llamada y hacerla más legible. ¿Te
imaginas pasar 20 argumentos por el terminal?. A continuación vemos un ejemplo donde los
argumentos se pasan dentro de un fichero, por lo que nuestro script sólo recibe el nombre del
fichero.
# argumentos_fichero.py
import argparse
parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('-a')
parser.add_argument('-b')
parser.add_argument('-c')
parser.add_argument('-d')
parser.add_argument('-e')
parser.add_argument('-f')
args = parser.parse_args()
print(vars(args))
-a=3
-b=10
-c=1
-d=0
-e=11
-f=9
Podemos llamar a nuestro script de la siguiente forma, simplificando la llamada a un solo
argumento, lo que en ciertas ocasiones puede resultar mucho más elegante.
{'a': '3', 'b': '10', 'c': '1', 'd': '0', 'e': '11', 'f': '9'}
Por último, es importante notar que el uso del fichero es una opción añadida, por lo que sigue
siendo posible utilizar los argumentos como hemos visto anteriormente, o incluso combinarlos,
siendo el último el que tiene prioridad.
{'a': '200', 'b': '10', 'c': '1', 'd': '0', 'e': '11', 'f': '9'}
Argumentos excluyentes
# excluyentes.py
import argparse
parser = argparse.ArgumentParser()
grupo = parser.add_mutually_exclusive_group()
grupo.add_argument('-f', '--foo')
grupo.add_argument('-b', '--bar')
args = parser.parse_args()
print(vars(args))
La siguiente llamada por tanto no es posible, ya que estamos utilizando ambos argumentos.
$ python excluyentes.py -f 3 -b 10
Las acciones que argparse nos ofrece por defecto son las siguientes:
• store_true: Almacena el booleano True en la variable. Muy útil para definir flags que no
reciben un valor concreto.
• append: Añade el argumento a una lista. Útil cuando un argumento es pasado múltiples
veces.
• help: Muestra la ayuda del programa y finaliza sin hacer nada más.
Veamos unos ejemplos con cada tipo de acción. Como ya hemos visto anteriormente, el uso
de store no tiene ningún misterio ya que es el comportamiento que tenemos por defecto.
# store.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='store')
args = parser.parse_args()
print(vars(args))
$ python store.py -a 3
{'a': '3'}
Por otro lado, store_const permite almacenar una constante determinada por el valor de const.
# store_const.py
import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()
print(vars(args))
La llamada al script se hace de la siguiente manera, pero nótese que el argumento no acepta
parámetros a continuación. Resulta lógico, puesto que ya estamos diciendo que queremos
almacenar const.
$ python store_const.py -a
{'a': '99'}
$ python store_const.py -a 1
También podemos usar store_true para almacenar True en la variable o store_false para
almacenar False.
# store_true_false.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='store_true')
parser.add_argument('-b', action='store_false')
args = parser.parse_args()
print(vars(args))
$ python store_true_false.py -a -b
# append.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='append')
args = parser.parse_args()
print(vars(args))
Pasamos el argumento -a tres veces con distintos valores y podemos ver como son almacenados
en una lista.
$ python append.py -a 1 -a 2 -a 3
# append_const.py
import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()
print(vars(args))
Podemos llamarlo de la siguiente manera. Es importante notar que dado que el argumento no
acepta valores, podemos usar un solo -.
$ python append_const.py -a -a -a
Por otro lado, el uso de count cuenta el número de veces que un argumento es pasado.
# count.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='count')
args = parser.parse_args()
print(vars(args))
Si pasamos múltiples veces el mismo argumento, la variable incrementará en uno cada vez.
$ python count.py -a -a -a
{'a': 3}
{'a': 3}
Por último, podemos usar help y version para mostrar la ayuda del programa y su versión
respectivamente. Si bien es cierto que por defecto la ayuda se muestra con -h, esta funcionalidad
nos permite modificarlo, usando por ejemplo nombre en español.
# help_version.py
import argparse
parser = argparse.ArgumentParser()
parser.version = '1.0'
parser.add_argument('--ayuda', action='help')
parser.add_argument('--version', action='version')
args = parser.parse_args()
print(vars(args))
optional arguments:
--ayuda
1.0
Hasta aquí hemos visto las acciones que Python nos ofrece por defecto, que deberían cubrir
prácticamente todos los casos de uso que se nos puedan ocurrir. Sin embargo, si necesitáramos
una funcionalidad que no existiera, siempre podríamos crear una acción personalizada creando
una clase que herede de argparse.Action. Una vez creada e implementada dicha clase, bastaría con
pasarla como action.
Hasta ahora hemos visto que los valores que pasamos a los argumentos han sido o uno o ninguno,
pero es también posible que un argumento consuma múltiples valores. Los valores consumidos
pueden ser modificados con nargs y son:
Veamos unos ejemplos que es como sin duda se entiende mejor. Empecemos definiendo
un número concreto de valores a consumir.
# argumentos_variables1.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs=5)
args = parser.parse_args()
print(vars(args))
Dado que el número de argumentos es fijo, la primera llamada es válida mientras que la segunda
no.
$ python argumentos_variables1.py -a 1 2 3 4 5
$ python argumentos_variables1.py -a 1 2
usage: argumentos_variables1.py [-h] [-a A A A A A]
# argumentos_variables2.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='?')
args = parser.parse_args()
print(vars(args))
El script puede ser llamado con o sin ningún valor, almacenando None si no se pasa ningún valor, y
por supuesto asumiendo que no se a definido un valor default.
$ python argumentos_variables2.py -a
{'a': None}
$ python argumentos_variables2.py -a 1
{'a': '1'}
# argumentos_variables3.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='?')
args = parser.parse_args()
print(vars(args))
Todas las siguiente llamadas son válidas, ya que no se especifica un número concreto de
argumentos. Nótese que también puede no pasarse ningún argumento.
$ python argumentos_variables3.py -a
{'a': None}
$ python argumentos_variables3.py -a 1
{'a': ['1']}
$ python argumentos_variables3.py -a 1 2 3
Relacionado con el anterior, también podemos pasar un número arbitrario de valores con al menos
uno.
# argumentos_variables4.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='+')
args = parser.parse_args()
print(vars(args))
Es decir, podemos pasar un número arbitrario de parámetros pero deberemos pasar al menos uno,
por lo que la primera llamada falla.
$ python argumentos_variables4.py -a
$ python argumentos_variables4.py -a 1 2
Por último si usamos argparse.REMAINDER se tomarán todos los valores restantes hasta el final del
comando. Dado que se toman todos los valores hasta el final, es importante declarar este
argumento al final de todo, de lo contrario podríamos tenemos comportamiento inesperados.
# argumentos_variables5.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a')
parser.add_argument('-b', nargs=argparse.REMAINDER)
args = parser.parse_args()
print(vars(args))
$ python argumentos_variables5.py -a 10 -b 1 2 3 4 5
Hasta ahora hemos visto como el nombre del argumento y el de la variable en la que se almacena
es el mismo, es decir, el argumento --operacion se almacena el la variable operacion por defecto.
Imaginemos que queremos que sea otro. Pues bien, es posible mediante el uso de dest.
# uso_dest.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', dest='otro_nombre')
args = parser.parse_args()
print(vars(args))
Como podemos ver, el argumento ya no es almacenado en la variable a, sino que se usará como
variable otro_nombre.
{'otro_nombre': 'PythonMola'}
Conclusiones
Llegados a este punto, ya hemos visto todas las funcionalidades que argparse nos ofrece. Recuerda
que si tienes dudas puedes consultar la documentación oficial de Python, que aunque sea más
complicada de leer, es la que debe ser usada como referencia.
• Hemos visto qué son los Command Line Interface y su utilidad a la hora de ofrecer un punto
de entrada a nuestro código, algo ideal cuando queremos compartirlo con otras personas
para que lo usen.
• Hemos visto cómo se pueden crear CLI sencillos mediante el uso de la librería sys. Se trata
de una opción perfectamente válida pero cuya funcionalidad es muy limitada.
• Y por último, hemos visto como crear CLI con la librería nativa de Python argparse, el
estándar de facto usado, explorando desde las funcionalidades más básicas a las más
avanzadas.
Código Buggy Python: Los 10 Errores más Comunes que Cometen los Desarrolladores Python
authors are vetted experts in their fields and write on topics in which they have demonstrated
experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
ByMartin Chikilian
Martin is a full-stack engineer and has worked as a professional Python developer since 2007.
Previously At
Acerca de Python
La sintaxis simple y fácil de aprender de Python, puede enviar a los desarrolladores de Python en la
dirección incorrecta - especialmente aquellos que están conociendo la lengua – perdiendo en el
camino algunas de sus sutilezas y subestimando el poder del lenguaje diverso de Python.
Con esto en mente, este artículo presenta una lista “top 10” de errores sutiles, y difíciles de ver,
que pueden tomar desprevenidos incluso a algunos de los desarrolladores de Python más
avanzados.
(Nota: Este artículo está dirigido a un público más avanzado que el de Errores Comunes de
Programadores Python, que se orienta más hacia aquellos que son nuevos en la lengua.)
Error común # 1: Un Mal Uso de Expresiones como Valores Predeterminados para los Argumentos
de la Función
Un error común es pensar que el argumento opcional se establecerá en la expresión por defecto
específica, cada vez que se llama la función sin necesidad de suministrar un valor para el
argumento opcional. En el código anterior, por ejemplo, se podría esperar que llamar a foo() varias
veces (es decir, sin especificar un argumento bar) siempre daría de regreso baz, ya que la hipótesis
sería que cada vez que foo() se llama (sin un argumento bar especificado) bar está ajustado a [] (es
decir, una nueva lista vacía).
>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]
¿Eh? ¿Por qué se siguió añadiendo el valor predeterminado de baz a una lista existente cada
vez que foo() era llamado, en lugar de crear una nueva lista en cada oportunidad? La respuesta más
avanzada de programación Python es que, el valor por defecto para un argumento de función se
evalúa sólo una vez, en el momento en que se define la función. Por lo tanto, el argumento bar se
inicializa con su valor por defecto (es decir, una lista vacía) sólo cuando foo() se ha definido por
primera vez, pero luego los llamados a foo() (es decir, sin un argumento bar especificado) seguirán
utilizando la misma lista a la que bar fue inicializado originalmente.
... bar = []
... bar.append("baz")
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]
... x=1
...
... pass
...
... pass
...
111
Tiene sentido.
>>> B.x = 2
121
>>> A.x = 3
323
¿Qué es esto? Sólo cambiamos A.x ¿Por qué C.x cambió también?
En Python, las variables de clase se manejan internamente como diccionarios y siguen lo que se
refiere a menudo como Method Resolution Order (MRO). Así que en el código anterior, ya que el
atributo x no se encuentra en la clase C, se buscará en sus clases base (únicamente A en el ejemplo
anterior, aunque Python apoya herencia múltiple). En otras palabras, C no tiene su propia
propiedad x, independiente de A. Por lo tanto, las referencias a C.x son de hecho referencias a A.x.
Esto causa un problema Python, a menos que se maneje adecuadamente. Más información
sobre atributos de clase en Python.
>>> try:
... int(l[2])
... pass
...
>>> try:
... int(l[2])
... pass
...
>>>
La resolución de ámbito de Python se basa en lo que se conoce como la regla LEGB, que es la
abreviatura de Local, Enclosing, Global, Built-in. Parece bastante sencillo, ¿verdad? Bueno, en
realidad, hay algunas sutilezas en la forma en que esto funciona en Python, lo que nos lleva al
problema común, más avanzado, de programación Python, a continuación.
Considera lo siguiente:
>>> x = 10
... x += 1
... print x
...
>>> foo()
¿Cuál es el problema?
El error anterior se debe a que, cuando se hace una asignación a una variable en un ámbito, esa
variable es considerada, automáticamente por Python, como local en ese ámbito y sigue cualquier
variable de nombre similar, en cualquier ámbito exterior.
Es particularmente común que esto confunda a los desarrolladores cuando usan listas. Considera
el siguiente ejemplo:
...
>>> foo1()
>>> lst
[1, 2, 3, 5]
...
>>> foo2()
¿Eh? ¿Por qué foo2 falló, mientras que foo1 funcionó muy bien?
La respuesta es la misma que en el problema del ejemplo anterior, pero es sin duda más
sutil. foo1 No está haciendo una asignación a lst, mientras que foo2 sí lo está. Recordando que lst
+= [5] es en realidad la abreviatura de lst = lst + [5], vemos que estamos tratando de asignar un valor
a lst (por lo tanto, Python presume que está en el ámbito local). Sin embargo, el valor que estamos
tratando de asignar a lst se basa en el mismo lst (de nuevo, ahora presume estar en el ámbito local),
que aún no ha sido definido. Boom.
... if odd(numbers[i]):
... del numbers[i] # BAD: Deleting item from a list while iterating over it
...
La eliminación de un elemento de una lista o matriz, mientras que se da iteración sobre ella, es un
problema de Python que es bien conocido por cualquier desarrollador de software con experiencia.
Sin embargo, aunque el ejemplo anterior puede ser bastante obvio, incluso los desarrolladores
avanzados pueden involuntariamente, ser tomados por sorpresa por éste código, el cual es mucho
más complejo.
>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all
>>> numbers
[0, 2, 4, 6, 8]
Error común # 6: La Confusión de Cómo Python Enlaza las Variables en los Cierres
...
¡Sorpresa!
Esto sucede debido al comportamiento enlace tardío de Python, que dice que los valores de las
variables utilizadas en los cierres, se buscan en el momento en el que se llama a la función interna.
Así que en el código anterior, cuando cualquiera de las funciones devueltas es llamada, el valor
de i se busca en el ámbito que lo rodea en el momento en que se llama (y en ese momento, el
círculo se ha completado, por lo que a i ya se le ha asignado su valor final de 4).
...
...
¡Voilà! Estamos tomando ventaja de los argumentos por defecto para generar funciones anónimas,
con el fin de lograr el comportamiento deseado. Algunos llamarían a esto, elegante. Algunos lo
llamarían sutil. Algunos lo odian. Pero si eres un desarrollador de Python, es importante entender
esto.
Digamos que tienes dos archivos, a.py y b.py, cada uno de los cuales importa al otro, de la siguiente
manera:
en a.py:
import b
def f():
return b.x
print f()
Y en b.py:
import a
x=1
def g():
print a.f()
>>> import a
1
Funcionó muy bien. Tal vez te sorprendió. Después de todo, tenemos una importación circular aquí
que presumiblemente debería ser un problema, ¿no?
La respuesta es que la mera presencia de una importación circular no es como tal un problema en
Python. Si un módulo ya se ha importado, Python es lo suficientemente inteligente como para no
intentar volverlo a importar. Sin embargo, dependiendo del punto en el que cada módulo está
intentando acceder a las funciones o variables definidas en el otro, podrías tener problemas.
Así que volviendo a nuestro ejemplo, cuando importamos a.py, no tenía ningún problema al
importar b.py, ya que b.py no requiere nada de b.py para definirse en el momento de su
importación. La única referencia en b.py a a, es el llamado a a.f(). Pero ese llamado está en g() y
nada en a.py o b.py invoca g(). Así que, la vida es bella.
Pero ¿qué ocurre si se intenta importar b.py (claro, sin haber previamente importado a.py):
>>> import b
import a
print f()
return b.x
Uh oh. ¡Eso no es bueno! El problema aquí es que, en el proceso de importación b.py, intenta
importar a.py, lo que como resultado llama a f(), que a su vez intenta acceder a b.x. Pero b.x aún no
ha sido definida. De ahí la excepción AttributeError.
Al menos una solución a esto es bastante trivial. Simplemente modifica b.py para
importar a.py dentro de g():
x=1
def g():
print a.f()
Cuando se importa, todo está bien:
>>> import b
>>> b.g()
1 # Printed a first time since module 'a' calls 'print f()' at the end
Una de las ventajas de Python es la gran cantidad de módulos de biblioteca que trae desde el
principio. Pero como resultado, si no estás evitando esto conscientemente, no es tan difícil
encontrarse con un choque de nombres, entre el nombre de uno de tus módulos y un módulo con el
mismo nombre en la biblioteca estándar que se incluye en Python (por ejemplo, es posible que
tengas un módulo denominado email.py en tu código, lo que estaría en conflicto con el módulo de
biblioteca estándar del mismo nombre).
Esto puede conducir a problemas muy agresivos, como la importación de otra biblioteca que a su
vez intenta importar la versión de bibliotecas estándar Python de un módulo, pero como ya tienes
un módulo con el mismo nombre, el otro paquete importa erróneamente tu versión, en lugar de la
que se encuentra dentro de la biblioteca estándar Python, y es aquí es donde se producen los
errores más graves.
Por lo tanto, se debe tener cuidado para evitar el uso de los mismos nombres que los de los
módulos de biblioteca estándar Python. Es mucho más fácil para ti cambiar el nombre de un
módulo dentro de tu paquete que presentar una propuesta de mejora de Python (PEP) para solicitar
un cambio de nombre upstream y conseguir que lo aprueben.
Error común # 9: El No Poder Hacer Frente a las Diferencias entre Python 2 y Python 3
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)
bad()
$ python foo.py 1
key error
$ python foo.py 2
value error
$ python3 foo.py 1
key error
bad()
print(e)
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
exception = e
print('key error')
except ValueError as e:
exception = e
print('value error')
print(exception)
good()
$ python3 foo.py 1
key error
$ python3 foo.py 2
value error
¡Yupi!
(Por cierto, nuestra Guía de contratación Python (Python Hiring Guide) analiza una serie de
diferencias importantes, que se deben tener en cuenta al migrar el código de Python 2 a Python 3.)
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
import mod
mybar = mod.Bar()
¿Por qué? Debido a que, como se informó aquí, al apagarse intérprete, las variables globales del
módulo se ajustan a None. Como resultado, en el ejemplo anterior, en el punto que __del__ se
invoca, el nombre foo ya se ha ajustado a None.
Una solución a este problema algo más avanzado que la programación Python, sería
utilizar atexit.register() en su lugar. De esta manera, cuando el programa se haya ejecutado (al salir
normalmente, quiero decir), tus gestores registrados son echados antes de que el intérprete se
apague.
Con éste conocimiento, una solución para el anterior código mod.py podría ser algo como esto:
import foo
import atexit
def cleanup(handle):
foo.cleanup(handle)
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
Esta aplicación ofrece una manera limpia y confiable de llamar a cualquier funcionalidad de
limpieza necesaria para después de la terminación normal del programa. Obviamente, le toca a
foo.cleanup decidir qué hacer con el objeto unido al nombre self.myhandle, pero se entiende la
idea.
Para Terminar
Python es un lenguaje potente y flexible con muchos mecanismos y paradigmas que pueden
mejorar considerablemente la productividad. Sin embargo, al igual que con cualquier herramienta
de software o lenguaje, el tener una limitada comprensión o apreciación de sus capacidades a
veces puede ser más un impedimento que una ventaja, dejándonos en el estado proverbial de
“saber lo suficiente como para ser peligroso”.
Familiarizándose con los matices clave de Python, tales como (pero de ninguna manera se limita a)
los problemas de programación moderadamente avanzados planteados en este artículo, ayudará a
optimizar el uso de la lengua, evitando algunos de sus errores más comunes.
Deberías revisar nuestra Guía a Entrevistas Python (Insider’s Guide to Python Interviewing), para
obtener sugerencias sobre preguntas de entrevistas que pueden ayudar a identificar a los expertos
en Python.
Esperamos que te sean útiles los consejos en este artículo y apreciamos tus comentarios.
Código Pythonico
Si queremos escribir código Python que luzca profesional, debemos prestar atención a que nuestro
estilo sea “Pythónico”. Es decir, que aproveche las herramientas que el lenguaje provee para que
nuestros algoritmos sean más elegantes y simples.
En los tres ejemplos que se muestran vemos cómo el primero, si bien es sintácticamente correcto,
se parece a un algoritmo que podría haber sido escrito con algún otro lenguaje. Sin embargo, las
opciones 2 y 3 son propias de Python.
En la opción 2 se generan listas por comprensión donde los elementos son datos booleanos
correspondientes a cada carácter del string “s” e indican si ese carácter cumple o no con la
condición pedida (isalnum() verifica si es alfanumérico, isalpha() verifica si es
alfabético, isdigit() verifica si es un dígito, islower() verifica si es una letra minúscula
y isupper() verifica si es una letra mayúscula). Luego se imprime el resultado de la operación de
pertenencia usando el operador in.
La opción 3 también comienza de manera similar a la opción 2 pero aprovecha la función any(), que
devuelve True si alguno de los elementos de la secuencia que se le pasa como argumento es True.
Click aquí para una versión accesible de la infografía (apta para lectores electrónicos)
•
•
Compartir esto en →
Artículos relacionados
© Patricia E. Miguel.
Testing en Python
Dentro de la ingeniería software y la programación en general, el testing es una de las partes más
importantes que nos encontraremos en casi cualquier proyecto. De hecho es común dedicar más
tiempo a probar que el código funciona correctamente que a escribirlo. A continuación veremos las
formas más comunes de testear el código en Python, desde lo más básico a conceptos algo más
avanzados.
Aunque sea la primera vez que leas acerca de testing en Python, estoy seguro de que ya has
ejecutado tests sobre tu código sin darte cuenta. De acuerdo a su forma de ejecución, los podemos
clasificar en:
• Tests manuales: Son tests ejecutados manualmente por una persona, probando diferentes
combinaciones y viendo que el comportamiento del código es el esperado. Sin duda los has
realizado alguna vez.
• Tests automáticos: Se trata de código que testea que otro código se comporta
correctamente. La ejecución es automática, y permite ejecutar gran cantidad de
verificaciones en muy poco tiempo. Es la forma más común, pero no siempre es posible
automatizar todo.
Imaginemos que hemos escrito una función que calcula la media de los valores que se pasan en
una lista como entrada.
def calcula_media(*args):
return(sum(*args)/len(*args))
A nadie se le ocurriría publicar nuestra función calcula_media sin haber hecho alguna verificación
anteriormente. Podemos por ejemplo probar con los siguientes datos y ver si la función hace lo que
se espera de ella. Al hacer esto ya estaríamos probando manualmente nuestro código.
print(calcula_media([3, 7, 5]))
# 5.0
print(calcula_media([30, 0]))
# 15.0
Con bases de código pequeñas y donde sólo trabajemos nosotros, tal vez sea suficiente, pero a
medida que el proyecto crece puede no ser suficiente. ¿Qué pasa si alguien modifica nuestra
función y se olvida de testear que funciona correctamente? Nuestra función habría dejado de
funcionar y nadie se habría enterado.
Es aquí donde los test automáticos nos pueden ayudar. Python nos ofrece herramientas que nos
permiten escribir tests que son ejecutados automáticamente, y que si fallan darán un error,
alertando al programador de que ha “roto” algo. Podemos hacer esto con assert, donde
identificamos dos partes claramente:
• Por un lado tenemos la llamada a la función que queremos testear, que devuelve un
resultado.
• Por otro lado tenemos el resultado esperado, que comparamos con el resultado devuelto por
la función. Si no es igual, se lanza un error.
Nótese que los valores de 5 y 15 los hemos calculado manualmente, y corresponden con la media
de 3,7,5 y 30,0 respectivamente. Si por cualquier motivo alguien rompe nuestra
función calcula_media(), cuando los tests se ejecuten lanzaran una excepción.
AssertionError
En proyectos grandes, es común que antes de permitirnos hacer merge de nuestro código
en master, se nos obligue a ejecutar un conjunto de tests automatizados. Si todos pasan, se
asumirá que nuestro código funciona y que no hemos “roto” nada, por lo que tendremos el visto
bueno.
Visto esto, tal vez pueda parecer que los test automatizados son lo mejor, sin embargo no siempre
se pueden automatizar los tests. Si por ejemplo estamos trabajando con interfaces de usuario, es
posible que no podamos automatizarlos, ya que se sigue necesitando a un humano que verifique
los cambios visualmente.
Aunque el uso de assert() puede ser suficiente para nuestros tests, a veces se nos queda corto y
necesitamos librerías como unittest, que ofrecen alguna que otra funcionalidad que nos hará la
vida más fácil. Veamos un ejemplo. Recordemos nuestra función calcula_media, que es la que
queremos testear.
# funciones.py
def calcula_media(*args):
return(sum(*args)/len(*args))
Podemos usar unittest para crear varios tests que verifiquen que nuestra función funciona
correctamente. Aunque la estructura de un conjunto de tests se puede complicar más, la
estructura será siempre muy similar a la siguiente:
# tests.py
import unittest
class TestCalculaMedia(unittest.TestCase):
def test_1(self):
self.assertEqual(resultado, 10)
def test_2(self):
self.assertEqual(resultado, 4)
if __name__ == '__main__':
unittest.main()
Si ejecutamos el código anterior, obtendremos el siguiente resultado. Esta es una de las ventajas
de unittest, ya que nos muestra información sobre los tests ejecutados, el tiempo que ha tardado y
los resultados.
OK
Por otro lado, usando -v podemos obtener más información sobre cada test ejecutado con su
resultado individualmente. Si tenemos gran cantidad de tests suele ser recomendable usarla, ya
que será más fácil localizar los tests que han fallado.
----------------------------------------------------------------------
OK
Por último, si tenemos varios ficheros de test, podemos usar discover, para decirle a Python que
busque en la carpeta todos los tests y los ejecute.
Anteriormente hemos visto el uso de .assertEqual(a, b) que simplemente verifica que dos
valores a y b son iguales, y de lo contrario da error. Sin embargo unittest nos ofrece un amplio
abanico de opciones. Nótese que existen algunas variaciones usando “not”, como assertNotIn():
• .assertEqual(a, b): Verifica la igualdad de ambos valores.
• .assertIs(a, b): Verifica que ambas variables son la misma (ver operador is).
import unittest
class TestEjemplos(unittest.TestCase):
def test_in(self):
def test_is(self):
a = [1, 2, 3]
b=a
self.assertIs(a, b)
def test_excepcion(self):
with self.assertRaises(ZeroDivisionError):
x = 0/0
OK
Otra de las ventajas de usar unittest, es que nos ofrece la posibilidad de definir funciones comunes
que son ejecutadas antes y después de cada test. Estos métodos son setUp() y tearDown().
import unittest
class TestEjemplos(unittest.TestCase):
def setUp(self):
print("Entra setUp")
def tearDown(self):
print("Entra tearDown")
def test_1(self):
print("Test: test_1")
def test_2(self):
print("Test: test_2")
Siendo el resultado el siguiente. Podemos ver que hace una especie de sandwich con cada test,
metiéndolo entre setUp y tearDown. Nótese que si ambas funciones realizan siempre lo mismo, tal
vez se pueda usar un TestSuite con una función común para todos los tests, pero se trata de un
concepto algo más avanzado que dejaremos para otro artículo.
Test: test_1
Entra tearDown
ok
Entra tearDown
ok
----------------------------------------------------------------------
OK
Hasta ahora hemos visto las herramientas que necesitamos para escribir nuestros tests, pero es
también muy importante seguir una serie de buenas practicas a la hora de escribir nuestro código.
Uno de los principios más importantes a seguir es el Principio de Responsabilidad Única o SRP, que
nos dice que el código (bien sea una clase o una función) debe tener una única responsabilidad. Si
hace demasiadas cosas, será más complicado de modificar y mantener, y además será más
complicado de testear.
Por lo tanto es tan importante escribir buenos tests que sean completos y tengan en cuenta todas
las posibles casuísticas como escribir código que pueda ser testeado de manera fácil.
Ahora que ya dominas Python veamos unos ejemplos prácticos en diferentes sectores:
Estos ejemplos dar ideas de lo que se puede hacer con Python, dejando al lector unos ejercicios
para que explore más allá.
• � github.com/ellibrodepython/50-ejemplos-python
• 📙📙 Programar tareas
• 📙📙 Logging y verbosity
• 📙📙 Benchmark de funciones
• 📙📙 Simulaciones simpy
• 📙📙 Baraja de Poker
• 📙📙 Código C en Python
• 📙📙 Martingala y apuestas
• 📙📙 Calcular impuestos
• 📙📙 Simular hipoteca
• 📙📙 Comprimir información
• 📙📙 Problema de la mochila