Computing">
[Go to site: main page, start]

0% encontró este documento útil (0 votos)
9 vistas250 páginas

Qué Es Python, Libro de Python

Python es un lenguaje de programación versátil y fácil de aprender, creado en los años 1990 por Guido van Rossum, que se utiliza en diversas industrias y aplicaciones, desde desarrollo web hasta machine learning. Su comunidad es amplia, con numerosos recursos y librerías disponibles, lo que contribuye a su creciente popularidad. Además, Python se caracteriza por su sintaxis sencilla, tipado dinámico y multiplataforma, lo que facilita su uso y aprendizaje.

Cargado por

labigonzalez15
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
9 vistas250 páginas

Qué Es Python, Libro de Python

Python es un lenguaje de programación versátil y fácil de aprender, creado en los años 1990 por Guido van Rossum, que se utiliza en diversas industrias y aplicaciones, desde desarrollo web hasta machine learning. Su comunidad es amplia, con numerosos recursos y librerías disponibles, lo que contribuye a su creciente popularidad. Además, Python se caracteriza por su sintaxis sencilla, tipado dinámico y multiplataforma, lo que facilita su uso y aprendizaje.

Cargado por

labigonzalez15
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd

¿Qué es Python?

Introducción

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.

 Es un lenguaje multiplataforma, por lo que el mismo código es compatible en


cualquier plataforma (Windows, macOS, Linux) sin hacer nada.

 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.

 La NASA usa Python[6] en gran cantidad de programas científicos.

 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.

 Machine Learning: En los último años ha crecido el número de implementaciones en


Python de librerías de aprendizaje automático
como Keras, TensorFlow, PyTorch o sklearn.

 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:

 Es un lenguaje interpretado, no compilado.

 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.

 Es multiplataforma, ya que un código escrito en macOS funciona en Windows o Linux y


vice versa.
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.

Algunas cosas curiosidad que en otros lenguajes no pasan. La función acepta un


parámetro entrada pero no se especifica su tipo. La x almacena primero una cadena, luego un
float y luego un integer. La función funcion() es llamada con un int, pero su valor se divide entre
2 y el resultado es convertido automáticamente en un float.

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'>

The Zen of Python

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:

1. Bello es mejor que feo.

2. Explícito es mejor que implícito.

3. Simple es mejor que complejo.

4. Complejo es mejor que complicado.

5. Plano es mejor que anidado.

6. Espaciado es mejor que denso.

7. La legibilidad es importante.

8. Los casos especiales no son lo suficientemente especiales como para romper las reglas.
9. Sin embargo la practicidad gana a la pureza.

10. Los errores nunca deben pasar silenciosamente.

11. A menos que se silencien explícitamente.

12. Frente a la ambigüedad, evitar la tentación de adivinar.

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)

15. Ahora es mejor que nunca.

16. A pesar de que nunca es muchas veces mejor que ahora mismo.

17. Si la implementación es difícil de explicar, es una mala idea.

18. Si la implementación es fácil de explicar, puede que sea una buena idea.

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.

Primeros pasos en Python

A continuación te ayudamos a dar tus primeros pasos en Python. ¡Allá vamos!

Hola mundo en Python

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.

Definiendo variables en Python

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.

Sumando variables en Python

Vamos a sumar dos variables e imprimir su valor. Lo primero vamos a declararlas, con
nombres a y b. Declarar una variable significa “crearla”.

# Declaramos las variables a, b

# y asignamos dos valores

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)

# NameError: name 'z' is not defined

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 ".

mi_cadena = "Hola Mundo"

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.

# Definimos una variable x con una cadena

x = "El valor de (a+b)*c es"

# Podemos realizar múltiples asignaciones

a, b, c = 4, 3, 2

# Realizamos unas operaciones con a,b,c

d = (a + b) * c

# Definimos una variable booleana

imprimir = True

# Si imprimir, print()

if imprimir:

print(x, d)

# Salida: El valor de (a+b)*c es 14

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

'''

Indentación y bloques 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.

# Otros lenguajes como C

# requieren de ; al final de cada línea

x = 10;

Sin embargo en Python no es necesario, basta con un salto de línea.

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)

Se puede hacer lo mismo para llamadas a funciones

def funcion(a, b, c):

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

O también podemos asignar varios valores separados por coma.

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.

Por otro lado existen ciertas normas a la hora de nombrar variables:

 El nombre no puede empezar por un número

 No se permite el uso de guiones -

 Tampoco se permite el uso de espacios.

Se muestran unos ejemplos de nombres de variables válidos y no válidos.

# 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([Link])

# ['False', 'None', 'True', 'and', 'as', 'assert',

# 'async', 'await', 'break', 'class', 'continue',

# 'def', 'del', 'elif', 'else', 'except', 'finally',

# 'for', 'from', 'global', 'if', 'import', 'in', 'is',

# 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise',

# 'return', 'try', 'while', 'with', 'yield']

De hecho con el siguiente comando puedes ver todas las palabras clave que no puedes usar.

import keyword

print([Link])

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)

No te preocupes si no lo has entendido. Es un concepto un poco complicado de pillar al


principio, pero lo veremos más adelante. Te recomendamos leer los siguientes posts para
entender mejor las funciones y el alcance de las variables:

 Funciones en Python y sus argumentos

 Paso por valor y por referencia

 Variable global en Python

Uso de la función print()

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.

print("Esto es el contenido a imprimir")

También es posible imprimir el contenido de una variable.

x = 10

print(x)

Y separando por comas , los valores, es posible imprimir el texto y el contenido de variables.
x = 10

y = 20

print("Los valores x, y son:", x, y)

# Salida: Los valores x, y son: 10 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

Los siguientes ejemplos no son permitidos.

# No válido

2variable = 10

var-iable = 10

var iable = 10

Asignar múltiples valores

Se pueden asignar múltiples variables en la misma línea.

x, y, z = 10, 20, 30

Imprimir variables

Una variable puede ser impresa por pantalla usando print()

x = 10
y = "Nombre"

print(x)

print(y)

Palabras reservadas en Python

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

# SyntaxError: invalid syntax

Análogamente, no podemos llamar a una variable is ya que se trata del operador de identidad.

is = 4

# SyntaxError: invalid syntax

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)

# ['l', 'e', 't', 'r', 'a', 's']

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")

# TypeError: list() takes 0 positional arguments but 1 was given


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([Link])

# ['False', 'None', 'True', 'and', 'as', 'assert',

# 'async', 'await', 'break', 'class', 'continue',

# 'def', 'del', 'elif', 'else', 'except', 'finally',

# 'for', 'from', 'global', 'if', 'import', 'in', 'is',

# 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise',

# 'return', 'try', 'while', 'with', 'yield']

Vistas ya las palabras reservadas de Python, a continuación explicaremos para que sirve cada
una de ellas y las pondremos en contexto.

Condicionales: if, elif, else

El uso del if y los condicionales en general son la base de cualquier lenguaje de programación.
Son usados para alterar la línea de ejecución del programa en función de determinadas
condiciones.

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":

print("Estoy de acuerdo, Python es el mejor")

elif lenguaje == "Java":

print("No me gusta, Java no mola")

else:

print("Ningún otro lenguaje supera a Python")

# Salida: Estoy de acuerdo, Python es el mejor

Bucles: while, for, break, continue

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.

El while ejecuta su sección de código mientras se cumpla una determinada condición.


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

Valores: False, True, None

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

También podemos asignar nosotros True a una variable.

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

Operadores lógicos: and, or, not

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.

print(True and False) # False

print(True or False) # True

print(not True) # False

Funciones: def, return, lambda, pass, yield

Todas ellas relacionadas con las funciones. El uso de def nos permite crear una función.

def funcion_suma(a, b):

print("La suma es", a + b)

funcion_suma(3, 5)

# Salida: La suma es 8

Si queremos que la función devuelva uno o varios valores, podemos usar return.
def funcion_suma(a, b):

return a + b

suma = funcion_suma(3, 5)

print("La suma es", suma)

# Salida: La suma es 8

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.

print("La suma es", (lambda a, b: a + b)(3, 5))

# 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.

def funcion_suma(a, b):

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):

print("Creando objeto de MiClase")

objeto = MiClase()

# Salida: Creando objeto de MiClase

Excepciones: assert, try, except, finally, raise

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:

print("El valor es", valor)


# Salida: El valor es 10

Variables: global, nonlocal

El uso de global permite realizar lo siguiente, y de no usarlo tendríamos un UnboundLocalError.


Aunque puede resultar muy útil, mucho cuidado con las variables globales.

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

Al usar nonlocal, el valor de x se modifica en el ámbito de funcion_a desde funcion_b.

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.

Módulos: from, import

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.

from collections import namedtuple

Pertenencia e Identidad: in, is

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.

lista = ["a", "b", "c"]

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

Eliminar variables: del

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)

# Salida: NameError: name 'a' is not defined

Context Managers: with, as

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.

with open('[Link]', 'r') as file:

print([Link]())

Concurrencia: async, await

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

async def proceso(id_proceso):

print("Empieza proceso:", id_proceso)

await [Link](10)

print("Acaba proceso:", id_proceso)

async def main():

await [Link](proceso(1), proceso(2), proceso(3))

[Link](main())

# Salida:

# Empieza proceso: 1

# Empieza proceso: 2

# Empieza proceso: 3

# Acaba proceso: 1
# Acaba proceso: 2

# Acaba proceso: 3

Alcance variables en Python

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():

mensaje = "¡Hola, mundo!" # Variable local

print(mensaje)

saludo()

print(mensaje) # Esto generará un error porque 'mensaje' es local a la función.

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():

mensaje = "Hola desde la función exterior" # Variable en el alcance 'enclosing'

def funcion_interior():

print(mensaje) # La función interior accede a la variable de la exterior

funcion_interior()

funcion_exterior()

Salida: Hola desde la función exterior

En este caso, mensaje no es ni local para funcion_interior ni global, sino que pertenece al
alcance de funcion_exterior. Este es un ejemplo de una variable con alcance enclosing.

Global: Variables definidas en el nivel superior del script(/ejecutar-script-python) o programa,


fuera de cualquier función. Son accesibles en todo el archivo, pero pueden ser modificadas
dentro de una función solo si se usa la palabra clave global.
mensaje = "Hola desde el alcance global" # Variable global

def imprimir_mensaje():

print(mensaje) # Accediendo a la variable global

imprimir_mensaje()

print(mensaje) # También accesible fuera de las funciones

Salida: Hola desde el alcance global Hola desde el alcance global

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

raiz_cuadrada = sqrt(numero) # Usamos la función built-in para calcular la raíz cuadrada

print(f"La raíz cuadrada de {numero} es {raiz_cuadrada}")

Salida: La raíz cuadrada de 16 es 4.0

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.

Duck Typing en Python

El duck typing o tipado de pato es un concepto relacionado con la programación que aplica a
ciertos lenguajes orientados a objetos, y que tiene origen en la siguiente frase:

If it walks like a duck and it quacks like a duck, then it must be a duck

Lo que se podría traducir al español como. Si camina como un pato y habla como un pato,
entonces tiene que ser un pato.

¿Y qué relación tienen los patos con la programación? Pues bien, se trata de un símil en el que
los patos son objetos y hablar/andar métodos. Es decir, que si un determinado objeto tiene
los métodos que nos interesan, nos basta, siendo su tipo irrelevante.

Dicho de otra manera, no mires si es un pato. Fíjate si habla como un pato, camina como un
pato, etc. Si cumple con todas estas características, ¿no podríamos acaso decir que se trata de
un pato?
Don’t check whether it is-a duck: check whether it quacks-like-a duck, walks-like-a duck, etc,
etc, depending on exactly what subset of duck-like behavior you need to play your language-
games with. ([Link], Jul. 26, 2000) — Alex Martelli

El concepto de duck typing se fundamenta en el razonamiento inductivo, donde una serie de


premisas apoyan la conclusión, pero no la garantizan. Si vemos a un animal que parece un pato,
habla como tal y anda como tal, sería razonable pensar que se trata de un pato, pero sin un
test de ADN nunca estaríamos al cien por cien seguros.

Una vez entendido el origen del concepto, veamos lo que realmente significa esto en Python.
En pocas palabras, a Python le dan igual los tipos de los objetos, lo único que le importan son
los métodos.

Definamos una clase Pato con un método hablar().

class Pato:

def hablar(self):

print("¡Cua!, Cua!")

Y llamamos al método de la siguiente forma.

p = Pato()

[Link]()

# ¡Cua!, Cua!

Hasta aquí nada nuevo, pero vamos a definir una función llama_hablar(), que llama al
método hablar() del objeto que se le pase.

def llama_hablar(x):

[Link]()

Como puedes observar, en Python no es necesario especificar los tipos, simplemente decimos
que el parámetro de entrada tiene el nombre x, pero no especificamos su tipo.

Cuando Python entra en la función y evalúa [Link](), le da igual el tipo al que


pertenezca x siempre y cuando tenga el método hablar(). Esto es el duck typing en todo su
esplendor.

p = Pato()

llama_hablar(p)

# ¡Cua!, Cua!

¿Y qué pasa si usamos otros objetos que no son de la clase Pato? Pues bien, como hemos
dicho, a la función llama_hablar() le da igual el tipo. Lo único que el importa es que el objeto
tenga el método hablar().

Definamos tres clases de animales distintas que implementan el método hablar(). Nótese que
no existe herencia entre ellas, son clases totalmente independientes. De haberla estaríamos
hablando de polimorfismo.
class Perro:

def hablar(self):

print("¡Guau, Guau!")

class Gato:

def hablar(self):

print("¡Miau, Miau!")

class Vaca:

def hablar(self):

print("¡Muuu, Muuu!")

Y como es de esperar la función llama_hablar() funciona correctamente con todos los objetos.

llama_hablar(Perro())

llama_hablar(Gato())

llama_hablar(Vaca())

# ¡Guau, Guau!

# ¡Miau, Miau!

# ¡Muuu, Muuu!

Otra forma de verlo, es iterando una lista con diferentes animales, donde animal va tomando
los valores de cada objeto animal. Todo un ejemplo del duck typing y del tipado dinámico en
Python.

lista = [Perro(), Gato(), Vaca()]

for animal in lista:

[Link]()

# ¡Guau, Guau!

# ¡Miau, Miau!

# ¡Muuu, Muuu!

Ejemplos Duck Typing

Ejemplo len()
Podemos ver el duck typing en todo su esplendor con la función len(). Dicha función lo único
que realiza por debajo es llamar al método mágico __len__(). Definamos dos clases:

 Foo implementa el método __len__().

 Bar no lo implementa.

class Foo():

def __len__(self):

return 99

class Bar():

pass

Como ya hemos explicado, a la función len() no le importa el tipo del objeto que se le pase,
siempre y cuando tenga el método __len__() implementado. Por ello, en el segundo caso falla.

print(len(Foo())) # 99

print(len(Bar())) # Error

Ejemplo multiplicar

Por otro lado, cuando hacemos una multiplicación utilizando el operador aritmético * el
resultado depende de los tipos que estemos usando.

No es lo mismo multiplicar dos enteros que un entero y cadena.

print(3*3) # 9

print(3*"3") # 333

Una vez más, podemos ver el duck typing en Python. Simplemente se busca que los objetos a
la izquierda y derecha del * tengan implementado el __rmul__ o __mul__.

Conclusiones

 Python es un lenguaje que soporta el duck typing, lo que hace que el tipo de los
objetos no sea tan relevante, siendo más importante lo que pueden hacer (sus
métodos).

 Otros lenguajes como Java no soporta el duck typing, pero se puede conseguir un
comportamiento similar cuando los objetos comparten un interfaz (si existe herencia
entre ellos). Este concepto relacionado es el polimorfismo.

 El duck typing está en todos lados, desde la función len() hasta el uso del operador *.

Unpacking en Python

El unpacking en Python nos permite asignar una lista a múltiples variables en una única línea
de código.
a, b, c = [1, 2, 3]

print(a) # 1

print(b) # 2

print(c) # 3

También es posible hacerlo con otros iterables como las tuplas.

a, b, c = (1, 2, 3)

print(a) # 1

print(b) # 2

print(c) # 3

Y como es de esperar, el número de variables debe coincidir con la longitud. Obtenemos not
enough values to unpack si proporcionamos menos.

a, b, c = (1, 2)

# ValueError: not enough values to unpack (expected 3, got 2)

Y too many values to unpack si proporcionamos de más.

a, b = (1, 2, 3, 4)

# ValueError: too many values to unpack (expected 2)

Se pueden dar casos curiosos como el siguiente, ya que en realidad funciona con
cualquier iterable.

a, b, c = "123"

print(a) # 1

print(b) # 2

print(c) # 3

De hecho funciona también con diccionarios, siendo la key lo usado por defecto.

a, b, c = {'uno': 1, 'dos':2, 'tres': 3}

print(a) # uno

print(b) # dos

print(c) # tres

Aunque también podemos usar los values.

a, b, c = {'uno': 1, 'dos':2, 'tres': 3}.values()

print(a) # 1

print(b) # 2

print(c) # 3
Dado que range devuelve un iterador, también lo podemos usar.

a, b, c = range(3)

print(a) # 0

print(b) # 1

print(c) # 2

Operador de Unpacking

Relacionado con el unpacking existe el operador *, que nos permite realizar asignaciones
cuando el número de elementos es distinto. Tanto de esta manera.

*a, b = (1, 2, 3)

print(a) # [1, 2]

print(b) # 3

Como de esta otra.

a, *b = (1, 2, 3)

print(a) # 1

print(b) # [2, 3]

Podemos usarlo para unir listas. Aunque es importante notar que esto se puede hacer de otras
formas como usando + o .extend().

a = [1, 2]

b = [3, 4]

c = [*a, *b]

print(c)

# [1, 2, 3, 4]

También tenemos el operador definido para diccionarios, usando **.

a = {"uno": 1, "dos": 2}

b = {"tres": 3, "cuatro": 4}

c = {**a, **b}

print(c)

# {'uno': 1, 'dos': 2, 'tres': 3, 'cuatro': 4}

Ten cuidado si tienes keys duplicados, ya que el segundo sobrescribirá al primero.


a = {"uno": 1, "dos": 2}

b = {"uno": 0, "dos": 0}

c = {**a, **b}

print(c)

# {'uno': 0, 'dos': 0}

Y por último también podemos hacer cosas interesantes con bucles for.

for primero, *resto in [(1, 2, 3), (4, 5, 6, 7)]:

print("Primero:", primero)

print("Resto:", resto)

# Primero: 1

# Resto: [2, 3]

# Primero: 4

# Resto: [5, 6, 7]

Unpacking para Swapping

La principal aplicación práctica del unpacking es para intercambiar o hacer swap de variables
en una única línea de código. Aunque pueda parecer algo sencillo, no todos los lenguajes
permiten esto.

a, b = (1, 2)

print(a, b)

#12

a, b = b, a

print(a, b)

#21

Unpacking en Funciones

Por último, aunque lo vemos más en detalle en args y kwargs, el unpacking nos permite pasar
un número de argumentos variables a una función.

def funcion(a, *args, **kwargs):

print(f"args={a} args={args} kwargs={kwargs}")


funcion(1)

# args=1 args=() kwargs={}

funcion(1, 2)

# args=1 args=(2,) kwargs={}

funcion(1, 2, 3, cuatro=4, cinco=5)

# args=1 args=(2, 3) kwargs={'cuatro': 4, 'cinco': 5}

Estructuras de Control en Python

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?

# Cocinamos para la primera persona

poner_agua_hervir()

echar_arroz_hervir()

cortar_pollo()

freir_pollo()

mezclar_pollo_arroz()

# Volvemos a cocinar para la segunda

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.

# Repite lo que hay dentro 100 veces

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

 📙 Iterar con zip

 📙 Iterar con enumerate

 📙 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.

 El bloque de código que se ejecutará si se cumple la condición anterior.

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

if a > 5 and a < 15:

print("Mayor que 5 y menos que 15")


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.

if a > 5: print("Es > 5")

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 ;.

if a > 5: print("Es > 5"); print("Dentro del if")

Uso de else y elif

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().

Para saber más: El operador ternario fue propuesto en la PEP 308.

x=5

print("Es 5" if x == 5 else "No es 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.

# [código si se cumple] if [condición] else [código si no se cumple]

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

c = a/b if b!=0 else -1

print(c)

#2

Ejemplos if

# Verifica si un número es par o impar

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.

for i in range(0, 5):

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.

#for <variable> in <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.

from collections import Iterable

lista = [1, 2, 3, 4]

cadena = "Python"

numero = 10

print(isinstance(lista, Iterable)) #True

print(isinstance(cadena, Iterable)) #True

print(isinstance(numero, Iterable)) #False

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)

print(it) #<list_iterator object at 0x106243828>

print(type(it)) #<class 'list_iterator'>

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

Para saber mas: Existen otros iteradores para diferentes clases:

 str_iterator para cadenas

 list_iterator para sets.

 tuple_iterator para tuplas.

 set_iterator para sets.

 dict_keyiterator para diccionarios.

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))

#print(next(it)) # Error! StopIteration

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.

lista = [[56, 34, 1],

[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.

print(sum(i for i in range(10)))

# Salida: 45

Range en Python

Uso del range

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.

for i in (0, 1, 2, 3, 4, 5):

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.

#range(inicio, fin, salto)

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(list(range(5, 20, 2)))

Y mezclándolo con el for, podemos hacer lo siguiente.

for i in range(5, 20, 2):

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.
for i in range (5, 0, -1):

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:

 La condición que se tiene que cumplir para que se ejecute el código.

 El bloque de código que se ejecutará mientras la condición se cumpla.

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.

# No ejecutes esto, en serio

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

while x > 0: x-=1; print(x)

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.

x = ["Uno", "Dos", "Tres"]

while x:

[Link](0)

print(x)

#['Dos', 'Tres']

#['Tres']

#[]

Else y while

Algo no muy corriente en otros lenguajes de programación pero si en Python, es el uso de la


cláusula else al final del while. Podemos ver el ejemplo anterior mezclado con el else. La
sección de código que se encuentra dentro del else, se ejecutará cuando el bucle termine, pero
solo si lo hace “por razones naturales”. Es decir, si el bucle termina porque la condición se deja
de cumplir, y no porque se ha hecho uso del break.

x=5

while x > 0:

x -=1

print(x) #4,3,2,1,0

else:

print("El bucle ha finalizado")

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

print("Fin del bucle")

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:

print(' ' * z + '*' * x + ' ' * z)

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

while i < len(text):

print(text[:i + 1])

i += 1

#P

# Py

# Pyt

# Pyth

# Pytho
# Python

Sucesión de Fibonacci en Python. En matemáticas, la sucesión de fibonacci es una sucesión


infinita de números naturales, donde el siguiente es calculado sumando los dos anteriores.

a, b = 0, 1

while b < 25:

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")

# ... hasta 100

elif condicion == 100:

print("3")

else:

print("x")

El tiempo de ejecución será distinto si la condicion es 1 o es 70 por ejemplo:

 Si es 1: Se evalúa el primer if, y como se cumple la condición se ejecuta y se sale.

 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.

Emulando switch en 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.

def opera1(operador, a, b):

if operador == 'suma':

return a + b

elif operador == 'resta':

return a - b

elif operador == 'multiplica':

return a * b

elif operador == 'divide':

return a / b

else:

return None

En el siguiente:

def opera2(operador, a, b):

return {

'suma': lambda: a + b,

'resta': lambda: a - b,

'multiplica': lambda: a * b,

'divide': lambda: a / b

}.get(operador, lambda: None)

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

Tiempo Ejecución switch vs if

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.

La primera forma es usando if y elif.

def usa_if(decimal):

if decimal == '0':

return "000"

elif decimal == '1':

return "001"

elif decimal == '2':

return "010"

elif decimal == '3':

return "011"

elif decimal == '4':

return "100"

elif decimal == '5':

return "101"

elif decimal == '6':

return "110"

elif decimal == '7':

return "111"

else:

return "NA"

La segunda forma es usando diccionarios simulando un switch.

tabla_switch = {

'0': '000',
'1': '001',

'2': '010',

'3': '011',

'4': '100',

'5': '101',

'6': '110',

'7': '111',

def usa_switch(decimal):

return tabla_switch.get(decimal, "NA")

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):

def funcion_medida(*args, **kwargs):

inicio = [Link]()

c = funcion(*args, **kwargs)

print(f"Entrada: {args[1]}. Tiempo: {[Link]() - inicio}")

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

def repite_funcion(funcion, entrada):

return [funcion(entrada) for i in range(10000000)]

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:

# Entrada: 0. Tiempo: 2.167248249053955

# Entrada: 1. Tiempo: 2.4989960193634033

# Entrada: 2. Tiempo: 2.898904800415039

# Entrada: 3. Tiempo: 3.0172407627105713

# Entrada: 4. Tiempo: 3.7285051345825195

# Entrada: 5. Tiempo: 3.828972101211548

# Entrada: 6. Tiempo: 4.256570100784302

# Entrada: 7. Tiempo: 4.421134948730469

# Usando switch:

# Entrada: 0. Tiempo: 2.640446186065674

# Entrada: 1. Tiempo: 2.765054941177368

# Entrada: 2. Tiempo: 2.6275320053100586

# Entrada: 3. Tiempo: 2.561228036880493

# Entrada: 4. Tiempo: 2.5796279907226562

# Entrada: 5. Tiempo: 2.5939972400665283

# Entrada: 6. Tiempo: 2.5642037391662598

# Entrada: 7. Tiempo: 2.54691219329834

Por lo tanto, podemos concluir lo siguiente:

 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.

Match en Python
Python ofrece algo similar al switch de otros lenguajes de programación desde la versión 3.10.

Se trata del match. Cada case define un camino posible. El _ es la opción por defecto, que se
ejecuta si la entrada no coincide con ningún caso.

hora = 8

match hora:

case 8:

print("Desayuno")

case 14:

print("Comida")

case 21:

print("Cena")

case _:

print("No toca comer")

# Desayuno

El match nos permite realizar lo mismo que con múltiples if/elif como hemos visto
anteriormente. Ambos códigos son equivalentes.

if hora == 8:

print("Desayuno")

elif hora == 14:

print("Comida")

elif hora == 21:

print("Cena")

else:

print("No toca comer")

También podemos tener en nuestros case múltiples condiciones, donde | es interpretado como
un or.

mes = 4

match mes:

case 12 | 1 | 2: print("Invierno")

case 3 | 4 | 5: print("Primavera")

case 6 | 7 | 8: print("Verano")

case 9 | 10 | 11: print("Otoño")


case _: print("Error")

# Primavera

Aunque no acaba ahí la cosa. Podemos hacer matching de prácticamente cualquier cosa como
una tupla.

coordenada = (0, 1)

match coordenada:

case (0, 0):

print("Coordenada en origen")

case (x, 0):

print(f"Coordenada en eje x: {x}")

case (0, y):

print(f"Coordenada en eje y: {y}")

case (x, y):

print(f"Coordenada en: {x}, {y}")

case _:

print("Error")

# Coordenada en eje y: 1

Sentencia break Python

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.

Break con bucles for

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'

for letra in cadena:

if letra == 'h':

print("Se encontró la h")

break

print(letra)

# Salida:

#P

#y

#t

# Se encontró la h

Break con bucles while

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

print("Fin del bucle")


#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.

Break y bucles anidados

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.

for i in range(0, 4):

for j in range(0, 4):

break

#Nunca se realiza más de una iteración

# El break no afecta a este for

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'

for letra in cadena:

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

Iterar con zip en Python

La función zip() de Python viene incluida por defecto en el namespace, lo que significa que
puede ser usada sin tener que importarse.

De acuerdo con la documentación oficial:

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))

# [(1, 'Uno'), (2, 'Dos')]

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)

for numero, texto in zip(a, b):

print("Número", numero, "Letra", texto)

# Número 1 Letra Uno

# Número 2 Letra Dos

zip() con n argumentos

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]

espanol = ["Uno", "Dos"]

ingles = ["One", "Two"]

frances = ["Un", "Deux"]

c = zip(numeros, espanol, ingles, frances)

for n, e, i, f in zip(numeros, espanol, ingles, frances):

print(n, e, i, f)

# 1 Uno One Un

# 2 Dos Two Deux


zip() con diferentes longitudes

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]

espanol = ["Uno", "Dos"]

for n, e in zip(numeros, espanol):

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.

zip() con un argumento

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))

# [(1,), (2,), (3,), (4,), (5,)]

zip() con diccionarios

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.

esp = {'1': 'Uno', '2': 'Dos', '3': 'Tres'}

eng = {'1': 'One', '2': 'Two', '3': 'Three'}

for a, b in zip(esp, eng):

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.

esp = {'1': 'Uno', '2': 'Dos', '3': 'Tres'}

eng = {'1': 'One', '2': 'Two', '3': 'Three'}

for (k1, v1), (k2, v2) in zip([Link](), [Link]()):

print(k1, v1, v2)

# 1 Uno One

# 2 Dos Two

# 3 Tres Three

Nótese que en este caso ambas key k1 y k2 son iguales.

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]

b = ["One", "Two", "Three"]

c = zip(a, b)

print(list(c))

# [(1, 'One'), (2, 'Two'), (3, 'Three')]

¿Es posible obtener a y b desde c? Sí, tan sencillo como:

c = [(1, 'One'), (2, 'Two'), (3, 'Three')]

a, b = zip(*c)

print(a) # (1, 2, 3)

print(b) # ('One', 'Two', 'Three')

Nótese el uso de *c, lo que es conocido como unpacking en Python.


Enumerate en Python

El uso del for en Python nos permite iterar colecciones, recorriendo todos los elementos de la
misma.

lista = ["A", "B", "C"]

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.

lista = ["A", "B", "C"]

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.

lista = ["A", "B", "C"]

for indice, l in enumerate(lista):

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.

lista = ["A", "B", "C"]

en = list(enumerate(lista))

print(en)

# Salida;

# [(0, 'A'), (1, 'B'), (2, 'C')]

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

cuadrados = [i**2 for i in range(5)]

#[0, 1, 4, 9, 16]

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):

[Link](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.

# lista = [expresión for elemento in iterable]

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.

unos = [1 for i in range(5)]

#[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

cuadrados = [eleva_al_2(i) for i in range(5)]

#[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.

lista = [10, 20, 30, 40 , 50]

nueva_lista = [i/10 for i in lista]

#[1.0, 2.0, 3.0, 4.0, 5.0]

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.

# lista = [expresión for elemento in iterable if condición]

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.

frase = "El perro de san roque no tiene rabo"

erres = [i for i in frase if i == 'r']


#['r', 'r', 'r', 'r']

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á.

frase = "El perro de san roque no tiene rabo"

mi_set = {i for i in frase if i == "r"}

#{'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.

lista1 = ['nombre', 'edad', 'región']

lista2 = ['Pelayo', 30, 'Asturias']

mi_dict = {i:j for i,j in zip(lista1, lista2)}

#{'nombre': 'Pelayo', 'edad': 30, 'región': 'Asturias'}

Como se puede ver, usando : asignamos un valor a una llave. Hemos usado también zip(), que
nos permite iterar dos listas paralelamente. Por lo tanto, en este ejemplo estamos convirtiendo
dos listas a un diccionario.

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

while i < len(lista):

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]

for elemento in lista:

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.

# for elemento in [clase_iterable]:

# ...

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.

from collections import Iterable

cadena = "Hola"

numero = 3

print("cadena", isinstance(cadena, Iterable))

print("numero", isinstance(numero, Iterable))

# 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)

# Error TypeError: 'int' object is not iterable

Python nos ofrece también diferentes métodos que pueden ser usados sobre clases iterables
como los que se muestran a continuación:

 list() convierte a lista una clase iterable

 sum() para sumar

 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:

#['H', 'o', 'l', 'a']

#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.

mi_dict = {'a':1, 'b':2, 'c':3}

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.

libro = ['página1', 'página2', 'página3', 'página4']

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.

Creando tu clase iterable

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

# TypeError: 'MiClase' object is not iterable

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):

[Link] = items

def __iter__(self):

return iter([Link])

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.

miobjeto = MiClase([5, 4, 3])

for item in miobjeto:

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.

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, -[Link] a
[Link]. Con 64 bits, el rango es de -[Link].854.775.808 hasta
[Link].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))

#305493636349960468205197939321361769978940274057232666389361390928129162652
4720457701857235108015228256875152693590467155317853427804283969735133114200
9178896307244205337728522220355888195318837008165086679301794879136633899370
5251636497892270212003524508209121908744820211960149463721109340307985507678
2836518362040933993739599827677011489868164062500000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000

#<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([Link](x), type(x))

print([Link](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.

Declarar variable bool

Se puede declarar una variable booleana de la siguiente manera.

x = True

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(1 > 0) #True

print(1 <= 0) #False

print(9 == 9) #True

Función bool

También es posible convertir un determinado valor a bool usando la 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

Los condicionales if evalúan una condición que es un valor bool.

a=1

b=2

if b > a:

print("b es mayor que 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")

Bool como subclase de int

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

print(type(f)) #<class 'float'>

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)

print(a, type(a)) #1.0 <class 'float'>

print(b, type(b)) #1.0 <class 'float'>

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

Precisión del float

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í.

print(0.1 + 0.1 + 0.1 - 0.3)

# 5.551115123125783e-17

Afortunadamente, salvo aplicaciones muy concretas esto no resulta un problema. Al fin y al


cabo, ¿a quién le importa lo que haya en la posición decimal 15?

¿Qué son los números complejos?

En pocas palabras, los números complejos son aquellos que tienen dos partes:

 Una parte real, como por ejemplo 3 o 7.5.

 Y otra imaginaria, como 5j o -7j.

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

Veamos unos ejemplos:

a = 5 + 5j

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.

 Los números imaginarios son números reales acompañados de la constante i (a veces


la j es usada indistintamente), como 4i o 3.7i.

 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.

¿Para qué sirven los números complejos?

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.

Números complejos en Python

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)

print(type(c)) #<class 'complex'>

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([Link]) #3.0

print([Link]) #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)

Operaciones con números complejos

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([Link]()) #(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.

 Calcular la forma polar, es decir módulo y ángulo.

import cmath

print([Link](complex(5, 0))) # 0.0

print([Link](complex(5, 5))) # (7.0710678118654755, 0.7853981633974483)

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).

s = "Esto es una cadena"

print(s) #Esto es una cadena


print(type(s)) #<class 'str'>

También es valido declarar las cadenas con comillas simples simples '.

s = 'Esto es otra cadena'

print(s) #Esto es otra cadena

print(type(s)) #<class 'str'>

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.

#s = "Esto es una comilla doble " de ejemplo" # Error!

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.

s = "Esto es una comilla doble \" de ejemplo"

print(s) #Esto es una comilla doble " de ejemplo

También podemos incluir un salto de línea dentro de una cadena, lo que significa que lo que
esté después del salto, se imprimirá en una nueva línea.

s = "Primer linea\nSegunda linea"

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

s = "El número es: " + str(x)

print(s) #El número es: 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

s = "El número es: %d" % x

print(s) #El número es: 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.

s = "Los números son %d y %d." % (5, 10)

print(s) #Los números son 5 y 10.

Una forma un poco más moderna de realizar lo mismo, es haciendo uso de format().

s = "Los números son {} y {}".format(5, 10)

print(s) #Los números son 5 y 10

Es posible también darle nombre a cada elemento, y format() se encargará de reemplazar todo.

s = "Los números son {a} y {b}".format(a=5, b=10)

print(s) #Los números son 5 y 10

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

s = f"Los números son {a} y {b}"

print(s) #Los números son 5 y 10

Puedes incluso hacer operaciones dentro de la creación del string.

a = 5; b = 10
s = f"a + b = {a+b}"

print(s) #a + b = 15

Puedes incluso llamar a una función dentro.

def funcion():

return 20

s = f"El resultado de la función es {funcion()}"

print(s) #El resultado de la funcion es 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"

print(s1 + " " + s2) #Parte 1 Parte 2

Se puede multiplicar un string por un int. Su resultado es replicarlo tantas veces como el valor
del entero.

s = "Hola "

print(s*3) #Hola Hola Hola

Podemos ver si una cadena esta contenida en otra con in.

print("mola" in "Python mola") #True

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

print(type(x)) #<class 'str'>

También se pueden indexar las cadenas, como si de una lista se tratase.

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])

Si no se indica ningún valor a la derecha de los : se llega hasta el final.

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

Algunos de los métodos de la clase string.

capitalize()

El método capitalize() se aplica sobre una cadena y la devuelve con su primera letra en
mayúscula.

s = "mi cadena"

print([Link]()) #Mi cadena

lower()

El método lower() convierte todos los caracteres alfabéticos en minúscula.

s = "MI CADENA"

print([Link]()) #mi cadena

swapcase()

El método swapcase() convierte los caracteres alfabéticos con mayúsculas en minúsculas y


viceversa.

s = "mI cAdEnA"
print([Link]()) #Mi CaDeNa

upper()

El método upper() convierte todos los caracteres alfabéticos en mayúsculas.

s = "mi cadena"

print([Link]())

count(<sub>[, <start>[, <end>]])

El método count() permite contar las veces que otra cadena se encuentra dentro de la primera.
Permite también dos parámetros opcionales que indican donde empezar y acabar de buscar.

s = "el bello cuello "

print([Link]("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@[Link]"

print([Link]())

isalpha()

El método isalpha() devuelve True si todos los caracteres son alfabéticos, False de lo contrario.

s = "abcdefg"

print([Link]())

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.

s = " abc "

print([Link]()) #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([Link](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.

s = " y ".join(["1", "2", "3"])


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"

print([Link](",")) #['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.

Crear listas Python

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]

También se puede crear usando list y pasando un objeto iterable.

lista = list("1234")

Una lista sea crea con [] separando sus elementos con comas ,. Una gran ventaja es que
pueden almacenar tipos de datos distintos.

lista = [1, "Hola", 3.67, [1, 2, 3]]

Algunas propiedades de las listas:

 Son ordenadas, mantienen el orden en el que han sido definidas

 Pueden ser formadas por tipos arbitrarios

 Pueden ser indexadas con [i].

 Se pueden anidar, es decir, meter una dentro de la otra.

 Son mutables, ya que sus elementos pueden ser modificados.

 Son dinámicas, ya que se pueden añadir o eliminar elementos.

Acceder y modificar listas

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.

a = [90, "Python", 3.87]

print(a[0]) #90
print(a[1]) #Python

print(a[2]) #3.87

Se puede también acceder al último elemento usando el índice [-1].

a = [90, "Python", 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

print(a) #[90, 'Python', 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.

x = [1, 2, 3, ['p', 'q', [5, 6, 7]]]

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]

Y de la misma manera podemos modificar múltiples valores de la lista a la vez usando :.

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.

lista = [5, 9, 10]

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.

lista = [5, 9, 10]

for index, l in enumerate(lista):

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.

lista1 = [5, 9, 10]

lista2 = ["Jazz", "Rock", "Djent"]

for l1, l2 in zip(lista1, lista2):

print(l1, l2)

#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.

lista1 = [5, 9, 10]

for i in range(0, len(lista)):

print(lista1[i])

#5

#9

#10

Métodos listas

append(<obj>)

El método append() añade un elemento al final de la lista.

l = [1, 2]

[Link](3)

print(l) #[1, 2, 3]

extend(<iterable>)

El método extend() permite añadir una lista a la lista inicial.

l = [1, 2]

[Link]([3, 4])

print(l) #[1, 2, 3, 4]

insert(<index>, <obj>)

El método insert() añade un elemento en una posición o índice determinado.

l = [1, 3]

[Link](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]

[Link](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]

[Link]()

print(l) #[1, 2]

reverse()

El método reverse() inverte el órden de la lista.

l = [1, 2, 3]

[Link]()

print(l) #[3, 2, 1]

sort()

El método sort() ordena los elementos de menos a mayor por defecto.

l = [3, 1, 2]

[Link]()

print(l) #[1, 2, 3]

Y también permite ordenar de mayor a menor si se pasa como parámetro reverse=True.

l = [3, 1, 2]

[Link](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.

l = ["Periphery", "Intervals", "Monuments"]

print([Link]("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([Link](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.
Crear set Python

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.

 Sus elementos deben ser inmutables.

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}

print(type(s)) #<class 'set'>

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}

print(type(s)) #<class 'set'>

Operaciones con sets

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[0] = 3 #Error! TypeError

Los elementos de un set deben ser inmutables, por lo que un elemento de un set no puede ser
ni un diccionario ni una lista. Si lo intentamos tendremos un TypeError

lista = ["Perú", "Argentina"]

#s = set(["México", "España", lista]) #Error! TypeError

Los sets se pueden iterar de la misma forma que las listas.

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])

print(s1 | s2) #{1, 2, 3, 4, 5}

Métodos sets

[Link](<elem>)

El método add() permite añadir un elemento al set.

l = set([1, 2])

[Link](3)

print(l) #{1, 2, 3}

[Link](<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])

[Link](2)

print(s) #{1}

[Link](<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])

[Link](3)

print(s) #{1, 2}

[Link]()
El método pop() elimina un elemento aleatorio del set.

s = set([1, 2])

[Link]()

print(s) #{2}

[Link]()

El método clear() elimina todos los elementos de set.

s = set([1, 2])

[Link]()

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([Link](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([Link](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:

 [Link](s2[, s3 ...])

 [Link](s2[, s3 ...])

 [Link](s2[, s3 ...])

 s1.symmetric_difference(s2)

 [Link](s2)

 [Link](s2)

 [Link](s2)

 [Link](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.

Crear tupla Python

Las tuplas en Python o tuples son muy similares a las listas, pero con dos diferencias.
Son inmutables, lo que significa que no pueden ser modificadas una vez declaradas, y en vez
de inicializarse con corchetes se hace con (). Dependiendo de lo que queramos hacer, las tuplas
pueden ser más rápidas.

tupla = (1, 2, 3)

print(tupla) #(1, 2, 3)

También pueden declararse sin (), separando por , todos sus elementos.

tupla = 1, 2, 3

print(type(tupla)) #<class 'tuple'>

print(tupla) #(1, 2, 3)

Operaciones con tuplas

Como hemos comentado, las tuplas son tipos inmutables, lo que significa que una vez
asignado su valor, no puede ser modificado. Si se intenta, tendremos un TypeError.

tupla = (1, 2, 3)

#tupla[0] = 5 # Error! TypeError

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

Y se puede también asignar el valor de una tupla con n elementos a n variables.

l = (1, 2, 3)

x, y, z = l

print(x, y, z) #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([Link](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([Link](5)) #4

En el caso de no encontrarse, se devuelve un ValueError.

l = [7, 7, 7, 3, 5]

#print([Link](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([Link](7, 2)) #2

Diccionario
Los diccionarios en Python son una estructura de datos que permite almacenar su contenido
en forma de llave y valor.

Crear diccionario Python

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)

#{'Nombre': 'Sara', 'Edad': 27, 'Documento': 1003882}

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)

#{'Nombre': 'Sara', 'Edad': '27', 'Documento': '1003882'}

También es posible usar el constructor dict() para crear un diccionario.

d3 = dict(Nombre='Sara',

Edad=27,

Documento=1003882)

print(d3)

#{'Nombre': 'Sara', 'Edad': 27, 'Documento': 1003882}

Algunas propiedades de los diccionario en Python son las siguientes:

 Son dinámicos, pueden crecer o decrecer, se pueden añadir o eliminar elementos.

 Son indexados, los elementos del diccionario son accesibles a través del key.

 Y son anidados, un diccionario puede contener a otro diccionario en su campo value.


Acceder y modificar elementos

Se puede acceder a sus elementos con [] o también con la función get()

print(d1['Nombre']) #Sara

print([Link]('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)

#{'Nombre': Laura', 'Edad': 27, 'Documento': 1003882}

Si el key al que accedemos no existe, se añade automáticamente.

d1['Direccion'] = "Calle 123"

print(d1)

#{'Nombre': 'Laura', 'Edad': 27, 'Documento': 1003882, 'Direccion': 'Calle 123'}

Iterar diccionario

Los diccionarios se pueden iterar de manera muy similar a las listas u otras estructuras de
datos. Para imprimir los key.

# Imprime los key del diccionario

for x in d1:

print(x)

#Nombre

#Edad

#Documento

#Direccion

Se puede imprimir también solo el value.

# Impre los value del diccionario

for x in d1:

print(d1[x])

#Laura

#27

#1003882

#Calle 123

O si queremos imprimir el key y el value a la vez.


# Imprime los key y value del diccionario

for x, y in [Link]():

print(x, y)

#Nombre Laura

#Edad 27

#Documento 1003882

#Direccion Calle 123

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.

anidado1 = {"a": 1, "b": 2}

anidado2 = {"a": 1, "b": 2}

d={

"anidado1" : anidado1,

"anidado2" : anidado2

print(d)

#{'anidado1': {'a': 1, 'b': 2}, 'anidado2': {'a': 1, 'b': 2}}

Métodos diccionarios Python

clear()

El método clear() elimina todo el contenido del diccionario.

d = {'a': 1, 'b': 2}

[Link]()

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([Link]('a')) #1

print([Link]('z', 'No encontrado')) #No encontrado

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 = [Link]()

print(it) #dict_items([('a', 1), ('b', 2)])

print(list(it)) #[('a', 1), ('b', 2)]

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 = [Link]()

print(k) #dict_keys(['a', 'b'])

print(list(k)) #['a', 'b']

values()

El método values() devuelve una lista con todos los values o valores del diccionario.

d = {'a': 1, 'b': 2}

print(list([Link]())) #[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}

[Link]('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}

[Link]('c', -1)

print(d) #{'a': 1, 'b': 2}

popitem()

El método popitem() elimina de manera aleatoria un elemento del diccionario.

d = {'a': 1, 'b': 2}
[Link]()

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}

d2 = {'a': 0, 'd': 400}

[Link](d2)

print(d1)

#{'a': 0, 'b': 2, 'd': 400}

Frozenset

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.

Crear frozenset Python

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])

print(fs) #frozenset({1, 2, 3})

print(type(fs)) #<class 'frozenset'>

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])

#[Link](4) #Error! AttributeError

#[Link]() #Error! AttributeError

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}
#s3 = {s1, s2} # Error! TypeError

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}

print(s3) #{frozenset({3, 4}), frozenset({1, 2})}

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])

#d = {s1: "Texto1", s2: "Texto2"} # Error! TypeError

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])

d = {s1: "Texto1", s2: "Texto2"}

print(d) #{frozenset({1, 2}): 'Texto1', frozenset({3, 4}): 'Texto2'}

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 implícita: Es realizada automáticamente por Python. Sucede cuando


realizamos ciertas operaciones con dos tipos distintos.

 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.

El ejemplo más sencillo donde podemos ver este comportamiento es el siguiente:


a = 1 # <class 'int'>

b = 2.3 # <class 'float'>

a=a+b

print(a) # 3.3

print(type(a)) # <class 'float'>

 a es un int

 b es un float

Pero si sumamos a y b y almacenamos el resultado en a, podemos ver como internamente


Python ha convertido el int en float para poder realizar la operación, y la variable resultante
es float

Sin embargo hay otros casos donde Python no es tan listo y no es capaz de realizar la
conversión. Si intentamos sumar un int a un string, tendremos un error TypeError.

a=1

b = "2.3"

c=a+b

# TypeError: unsupported operand type(s) for +: 'int' and 'str'

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:

 float(), str(), int(), list(), set()

 Y algunas otras como hex(), oct() y bin()

Convertir float a int

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
Convertir float a string

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

print(type(a)) # <class 'float'>

a = str(a)

print(type(a)) # <class 'str'>

Convertir string a float

Podemos convertir un string a float usando float(). Es importante notar que se usa el . como
separador.

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))

# Salida: ValueError: could not convert string to float: '35,5'

Y por último, resulta obvio pensar que el siguiente código dará un error también.

a = "Python"

print(float(a))

# Salida: ValueError: could not convert string to float: 'Python'

Convertir string a int

Al igual que la conversión a float del caso anterior, podemos convertir


de string a int usando int().

a = "3"

print(type(a)) # <class 'str'>

a = int(a)

print(type(a)) # <class 'int'>

Cuidado ya que no es posible convertir a int cualquier valor.

a = "Python"

a = int(a)
# ValueError: invalid literal for int() with base 10: 'Python'

Convertir int a string

La conversión de int a string se puede realizar con str().

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

print(type(a)) # <class 'int'>

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)

print(type(a)) # <class 'set'>

print(type(b)) # <class 'list'>

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)

print(type(a)) # <class 'list'>

print(type(b)) # <class 'set'>

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.

Y recuerda, la función type() es tu amiga.

Si quieres saber más, tal vez te interese el concepto de duck typing.

Identidad, tipo y valor


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

Y una función es también un objeto.

# 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.

Veamos un ejemplo con un entero:

 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.

 Por otro lado el tipo entero, <class 'int'>.

 Por último tenemos su valor, 10.

x = 10

print("Identidad:", id(x))

print("Tipo:", type(x))

print("Value:", x)

# Identidad: 4474035136

# Tipo: <class 'int'>

# 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:

 Mutables: Si permiten ser modificados una vez creados.

 Inmutables: Si no permiten ser modificados una vez creados.

Son mutables los siguientes tipos:

 Listas

 Bytearray

 Memoryview

 Diccionarios

 Sets

 Y clases definidas por el usuario

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.

Una lista l puede ser modificada una vez creada.

# La asignación se puede realizar

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.

# La asignación no se puede realizar

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).

Esto no resulta tan evidente si usamos un entero. Veamos un ejemplo.

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

print(id(x)) # 4525173568 (Ha cambiado)

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

[Link](3)

print(id(x)) # 4382432768 (No cambia)

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]

# La lista es parte de la tupla

t = (1, 2, 3, l)

print(t) # (1, 2, 3, [4, 5, 6])

# Podemos modificar la lista

l[0] = 0

# Que también modifica la tupla

print(t) # (1, 2, 3, [0, 5, 6])

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.

Mutabilidad y paso por valor/referencia

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.

Tenemos una función que modifica dos variables.

def funcion(a, b):

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

Modificar una tupla con slicing

Como hemos visto anteriormente, realizar la siguiente asignación no es posible.

T = (1, 2, 3, 4, 5)

T[2] = 0

# TypeError: 'tuple' object does not support item assignment

Si por ejemplo queremos modificar el índice T[2], podemos hacer uso del slicing.
T = (1, 2, 3, 4, 5)

T = T[:2] + (0,) + T[3:]

print(T) #(1, 2, 0, 4, 5)

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

Cualquier función tendrá un nombre, unos argumentos de entrada, un código a ejecutar y


unos parámetros de salida. Al igual que las funciones matemáticas, en programación nos
permiten realizar diferentes operaciones con la entrada, para entregar una determinada salida
que dependerá del código que escribamos dentro. Por lo tanto, es totalmente análogo al
clásico y=f(x) de las matemáticas.

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.

Pasando argumentos de entrada

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.

Argumentos por posición

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.

def resta(a, b):

return a-b

resta(5, 3) # 2

Al tratarse de parámetros posicionales, se interpretará que el primer número es la a y el


segundo la b. El número de parámetros es fijo, por lo que si intentamos llamar a la función con
solo uno, dará error.

#resta(1) # Error! TypeError

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.

#TypeError: resta() takes 2 positional arguments but 3 were given

#resta(5,4,3) # Error

Argumentos por nombre

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

Al indicar en la llamada a la función el nombre de la variable y el valor, el orden ya no importa,


y se podría llamar de la siguiente forma.

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.

#resta() got an unexpected keyword argument 'c'

#resta(b=5, c=3) # Error!

Argumentos por defecto

Tal vez queramos tener una función con algún parámetro opcional, que pueda ser usado o no
dependiendo de diferentes circunstancias. Para ello, lo que podemos hacer es asignar un
valor por defecto a la función. En el siguiente caso c valdría cero salvo que se indique lo
contrario.

def suma(a, b, c=0):

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.

def suma(a=3, b=5, c=0):

return a+b+c

suma() # 8

Las siguientes llamadas a la función también son válidas

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

Argumentos de longitud variable

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

suma(6, 4, 10, 20, 4, 6, 7) # 57

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;

for key, value in [Link]():

print(key, value)

suma += value

return suma
suma(a=5, b=20, c=23) # 48

De igual manera, podemos pasar un diccionario como parámetro de entrada.

def suma(**kwargs):

suma = 0

for key, value in [Link]():

print(key, value)

suma += value

return suma

di = {'a': 10, 'b':20}

suma(**di) # 30

Sentencia return

El uso de la sentencia return permite realizar dos cosas:

 Salir de la función y transferir la ejecución de vuelta a donde se realizó la llamada.

 Devolver uno o varios parámetros, fruto de la ejecución de la función.

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")

mi_funcion() # Entra en mi_funcion

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

return suma, media

suma, media = suma_y_media(9, 6, 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.

def mi_funcion_suma(a, b):

"""

Descripción de la función. Como debe ser usada,

que parámetros acepta y que devuelve

"""

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)

Otra forma de acceder a la documentación es la siguiente.

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'

Paso por valor y referencia

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

Iniciamos la x a 10 y se la pasamos a funcion(). Dentro de la función hacemos que la variable


valga 0. Dado que Python trata a los int como pasados por valor, dentro de la función se crea
una copia local de x, por lo que la variable original no es modificada.

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.

x = [10, 20, 30]

def funcion(entrada):

[Link](40)
funcion(x)

print(x) # [10, 20, 30, 40]

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.

x = [10, 20, 30]

def funcion(entrada):

entrada = []

funcion(x)

print(x)

# [10, 20, 30]

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.

x = [10, 20, 30]

print(id(x)) # 4422423560

def funcion(entrada):

[Link](40)

print(id(entrada)) # 4422423560

funcion(x)

Args y Kwargs en Python


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.

def suma(a, b, c):

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)

#TypeError: suma() takes 3 positional arguments but 4 were given

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

for arg in args:

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

for key, value in [Link]():

print(key, "=", value)


s += value

return s

suma(a=3, b=10, c=3)

#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.

Mezclando *args y **kwargs

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:

 Primero argumentos normales.

 Después los *args.

 Y por último los **kwargs.

Veamos un ejemplo.

def funcion(a, b, *args, **kwargs):

print("a =", a)

print("b =", b)

for arg in args:

print("args =", arg)

for key, value in [Link]():

print(key, "=", value)

funcion(10, 20, 1, 2, 3, 4, x="Hola", y="Que", z="Tal")

#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.

def funcion(a, b, *args, **kwargs):

print("a =", a)

print("b =", b)

for arg in args:

print("args =", arg)

for key, value in [Link]():

print(key, "=", value)

args = [1, 2, 3, 4]

kwargs = {'x':"Hola", 'y':"Que", 'z':"Tal"}

funcion(10, 20, *args, **kwargs)

#Salida

#a = 10

#b = 20

#args = 1

#args = 2

#args = 3

#args = 4

#x = Hola

#y = Que
#z = Tal

Anotaciones en Funciones

Function Annotations en Python

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.

def suma(a: int, b: int) -> 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.

Motivación y Necesidad de las Anotaciones

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.

Ejemplos de Function Annotations


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__.

def suma(a: 'parametro 1', b: 'parametro 2') -> 'retorno':

return a + b

print(suma.__annotations__)

# Salida:

# {'a': 'parametro 1',

# 'b': 'parametro 2',

# '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 [].

def filtrar_pares(salida: 'list' = []) -> 'list':

return [i for i in salida if i%2 == 0]

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

def funcion(a: ClaseA) -> ClaseA:

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.

pi: float = 3.14

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.

# Ojo: No sería correcto, pero Python no da error

pi: int = 3.14

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.

Uso de mypy y Static Type Checking

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

def suma(a: int, b: int) -> int:

if isinstance(a, suma.__annotations__['a']) and isinstance(b, suma.__annotations__['b']):

return a + b

else:

raise Exception("Error de tipos")

print(suma(7, 3))

# Salida: 10

print(suma(7.0, 3.0))

# Salida: Exception: Error de tipos

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.

$ pip instal mypy

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

def suma(a: int, b: int) -> int:

return a + b

print(suma(7, 3))

$ mypy suma_correcta.py

Success: no issues found in 1 source file

Sin embargo si cambiamos los tipos de los parámetros de entrada, obtendremos un error.

# suma_incorrecta.py

def suma(a: int, b: int) -> int:

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"

Found 2 errors in 1 file (checked 1 source file)

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.

def suma(a, b):

return a+b
Se podría expresar en forma de una función lambda de la siguiente manera.

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

Una función lambda puede ser la entrada a una función normal.

def mi_funcion(lambda_func):

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.

def mi_otra_funcion(a, b):

return a + b

(lambda a, b: mi_otra_funcion(a, b))(2, 4)

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.

(lambda a, b, c=3: a + b + c)(1, 2) # 6

También se pueden pasar los parámetros indicando su nombre.

(lambda a, b, c: a + b + c)(a=1, b=2, c=3) # 6

Al igual que en las funciones se puede tener un número variable de argumentos haciendo uso
de *, lo conocido como tuple unpacking.

(lambda *args: sum(args))(1, 2, 3) # 6

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.

(lambda **kwargs: sum([Link]()))(a=1, b=2, c=3) # 6


Por último, es posible devolver más de un valor.

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.

Cualquier función recursiva tiene dos secciones de código claramente divididas:

 Por un lado tenemos la sección en la que la función se llama a sí misma.

 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.

Veamos unos ejemplos con el factorial y la serie de fibonacci.

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.

Utilizando un enfoque tradicional no recursivo, podríamos calcular el factorial de la siguiente


manera.

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:

return fibonacci_recursivo(n-1) + fibonacci_recursivo(n-2)

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):

def nueva_funcion(a, b):

print("Se va a llamar")

c = funcion(a, b)

print("Se ha llamado")

return c

return nueva_funcion

@mi_decorador

def suma(a, b):

print("Entra en funcion suma")

return a + b

suma(5,8)

# Se va a llamar

# Entra en funcion suma

# 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.

Veamos otro ejemplo usando el decorador sobre otra función.

@mi_decorador
def resta(a ,b):

print("Entra en funcion resta")

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:

 di_hola() llama a la función.

 di_hola hace referencia a la función, no la llama.

def di_hola():

print("Hola")

f1 = di_hola() # Llama a la función

f2 = di_hola # Asigna a f2 la función

print(f1) # None, di_hola no devuelve nada

print(f2) # <function di_hola at 0x1077bf158>

#f1() # Error! No es válido

f2() # Llama a f2, que es di_hola()

del f2 # Borra el objeto que es la función

#f2() # Error! Ya no existe


di_hola() # Ok. Sigue existiendo

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

def resta(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):

def envoltorio_func(a, b):

print("Decorador antes de llamar a la función")

c = func(a, b)

print("Decorador después de llamar a la función")

return c

return envoltorio_func
def suma(a, b):

print("Dentro de suma")

return a + b

# Nueva funcion decorada

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 @.

Decoradores con parámetros

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):

def nueva_funcion(a, b):

print(arg)

c = funcion(a, b)

print(arg)

return c

return nueva_funcion

return decorador_real

@mi_decorador("Imprimer esto antes y después")

def suma(a, b):

print("Entra en funcion suma")

return a + b

suma(5,8)

# Imprimer esto antes y después


# Entra en funcion suma

# Imprimer esto antes y después

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):

def decorador_funcion(*args, **kwargs):

with open(fichero_log, 'a') as opened_file:

output = func(*args, **kwargs)

opened_file.write(f"{output}\n")

return decorador_funcion

return decorador_log

@log('[Link]')

def suma(a, b):

return a + b

@log('[Link]')

def resta(a, b):

return a - b

@log('[Link]')

def multiplicadivide(a, b, c):

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.

Ejemplo: Uso autorizado

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):

def funcion_decorada(*args, **kwargs):

if not autenticado:

print("Error. El usuario no se ha autenticado")

else:

return f(*args, **kwargs)

return funcion_decorada

@requiere_autenticación

def di_hola():

print("Hola")

di_hola()

Prueba a cambiar la variable de True a False.

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

Y ahora vamos a intentar llamar a las dos “funciones”.

print(funcion())

print(generador())

# Salida: 5

# Salida: <generator object generador at 0x103e7f0a0>

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.

Iterando los Generadores

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.

Volviendo al ejemplo anterior, vamos a ver como podemos usar el next().

a = generador()

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

# Salida: Error! StopIteration:

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.

 La tercera llamada, realiza lo mismo.

 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.

lista = [2, 4, 6, 8, 10]

al_cuadrado = [x**2 for x in lista]

print(al_cuadrado)

# [4, 16, 36, 64, 100]

Y su equivalente con generadores sería lo siguiente.

al_cuadrado_generador = (x**2 for x in lista)

print(al_cuadrado_generador)

# Salida: <generator object <genexpr> at 0x103e803b8>

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):

[Link](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

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:

 Si ejecutamos la función y el resultado no ha sido calculado con anterioridad, se calcula


y se almacena por si fuera útil en el futuro. Esto se conoce como cache miss.

 Si ejecutamos la función y el caché tiene almacenado el resultado para esa operación,


en vez de calcular otra vez la salida la podemos reutilizar, lo que se conoce como cache
hit. Dado que estamos reutilizando un valor ya calculado, generalmente el tiempo de
respuesta será menor.

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:
 El caching es especialmente útil cuando trabajamos con funciones muy intensivas en
cálculo, lo que hace que reutilizar el valor del caché reduzca notablemente el tiempo
de respuesta.

 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.

 El uso de un caché puede mejorar el tiempo de respuesta, pero frecuentemente se


paga en un incremento del uso de memoria. También es necesario decidir el número
de valores a almacenar.

A continuación veremos como implementar caching en Python, pudiendo hacerlo


con diccionarios o utilizando la librería functools. Para ejemplificarlo, veremos como
implementar un caché en nuestro código de números primos visto anteriormente, empleando
ambas formas.

def es_primo(num):

for n in range(2, num):

if num % n == 0:

return False

return True

Caching con Diccionarios

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={}):

if num not in _cache:

_cache[num] = True

for n in range(2, num):

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 = [Link]()

es_primo_concache(25565479)

print([Link]() - tic)

tic = [Link]()

es_primo_concache(25565479)

print([Link]() - tic)

# 3.5551438331604004

# 4.0531158447265625e-06

Caching con functools y lru_cache

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é.

from functools import lru_cache

@lru_cache(maxsize=32)

def es_primo_concache(num):

for n in range(2, 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 = [Link]()

es_primo_concache(25565479)

print([Link]() - tic)

tic = [Link]()
es_primo_concache(25565479)

print([Link]() - 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()

Programación Funcional en Python

En pocas palabras, la programación funcional es un paradigma de programación distinto al


tradicional estructurado u orientado a objetos al que solemos estar acostumbrados. Se basa
principalmente en el uso de funciones, siendo las mismas practicamente la única herramienta
que el lenguaje nos ofrece. Por ello, en lenguajes puramente funcionales como Haskell no
existen bucles for o while.

¿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

La función map toma dos entradas:

 Una lista o iterable que será modificado en una nueva.

 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

lista_pordos = map(por_dos, lista)

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]

lista_pordos = map(lambda x: 2*x, lista)

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.

lista = [7, 4, 16, 3, 8]

pares = filter(lambda x: x % 2 == 0, lista)

print(list(pares))

# [4, 16, 8]
Nótese que el siguiente código sería equivalente:

lista = [7, 4, 16, 3, 8]

def es_par(x):

return x % 2 == 0

pares = filter(es_par, lista)

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.

from functools import reduce

lista = [1, 2, 3, 4, 5]

suma = reduce(lambda acc, val: acc + val, lista)

print(suma) # 15

Lo que podría reescribirse usando la función add:

from operator import add

from functools import reduce

lista = [1, 2, 3, 4, 5]

suma = reduce(add, lista)

print(suma) # 15

O también los podemos multiplicar, modificando la función lambda.

from functools import reduce

lista = [1, 2, 3, 4, 5]

multiplicacion = reduce(lambda acc, val: acc * val, lista)


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 acumulador es el valor devuelto en la iteración anterior, que va acumulando un


resultado hasta que llegamos al final.

 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.

from functools import reduce

personas = [

{'Nombre': 'Alicia', 'Edad': 22},

{'Nombre': 'Bob', 'Edad': 29},

{'Nombre': 'Charlie', 'Edad': 33}

suma_edad = reduce(lambda total, p: total + p['Edad'], personas, 0)

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.

Características 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:

 Los lenguajes de programación puramente funcionales carecen de bucles for y while.

 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.

Ejemplos Programación Funcional

Dada una lista de personas almacenadas en diccionarios:

 Filtrar de acuerdo al sexo

 Y calcular la media

from functools import reduce

personas = [

{'Nombre': 'Alicia', 'Edad': 22, 'Sexo': 'F'},

{'Nombre': 'Bob', 'Edad': 25, 'Sexo': 'M'},

{'Nombre': 'Charlie', 'Edad': 33, 'Sexo': 'M'},

{'Nombre': 'Diana', 'Edad': 15, 'Sexo': 'F'},

{'Nombre': 'Esteban', 'Edad': 30, 'Sexo': 'M'},

{'Nombre': 'Federico', 'Edad': 44, 'Sexo': 'M'},

hombres = list(filter(lambda x: x['Sexo'] == 'M', personas))

suma_edades = reduce(lambda suma, p: suma + p['Edad'], hombres, 0)

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.

media_edades = reduce(lambda suma, p: suma + p['Edad'], filter(lambda x: x['Sexo'] == 'M',


personas), 0) / len(list(filter(lambda x: x['Sexo'] == 'M', personas)))

print(media_edades) # 33.0

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
Operador Ejemplo Equivalente

= 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 = 3.5

%= x%=2 x=x%2 = 1

//= x//=2 x=x//2 = 3

**= x**=2 x=x**2 = 49

&= x&=2 x=x&2 = 2

|= x|=2 x=x|2 = 7

^= x^=2 x=x^2 = 5

>>= x>>=2 x=x>>2 = 1

&lt&lt= x&lt&lt=2 x=x&lt&lt2 = 28

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.

Operador Nombre Ejemplo

+ Suma x + y = 13

- Resta x-y=7

* Multiplicación x * y = 30

/ División x/y = 3.333

% Módulo x%y = 1

** Exponente x ** y = 1000

// Cociente 3

x = 10; y = 3

print("Operadores aritméticos")

print("x+y =", x+y) #13

print("x-y =", x-y) #7

print("x*y =", x*y) #30

print("x/y =", x/y) #3.3333333333333335

print("x%y =", x%y) #1


print("x**y =", x**y) #1000

print("x//y =", x//y) #3

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

print([1, 3] + [6, 7]) # [1, 3, 6, 7]

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 *

El operador * multiplica los números presentes a la izquierda y derecha del 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 **

El operador ** realiza el exponente del número a la izquierda elevado al número de la derecha.

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

print([Link](10, 3)) #1000.0

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.

D = 10 # Número que queremos dividir

d = 3 # Número entre el que queremos dividir

print(3 * (10//3) + 10%3) # 10

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

 * / // Multiplicación, División, Cociente, Módulo

 + - Suma, Resta

print(10*(5+3)) # Con paréntesis se realiza primero la suma

# 80

print(10*5+3) # Sin paréntesis se realiza primero la multiplicación

# 53

print(3*3+2/5+5%4) # Primero se multiplica y divide, después se suma

#10.4

print(-2**4) # Primero se hace la potencia, después se aplica el signo

#-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

Operador Nombre Ejemplo

== Igual x == y = False

!= Distinto x != y = True

> Mayor x > y = False

&lt Menor x &lt y = True

>= Mayor o igual x >= y = False

&lt= Menor o igual x &lt y = True

x=2; y=3

print("Operadores Relacionales")
print("x==y =", x==y) # False

print("x!=y =", x!=y) # True

print("x>y =", x>y) # False

print("x<y =", x<y) # True

print("x>=y =", x>=y) # False

print("x<=y =", x<=y) # True

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

print([1, 2, 3] == [1, 2, 3]) # True

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

print([1, 2, 3] != [1, 2, 3]) # False

Operador >

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.

print([1, 2] > [10, 10]) # False

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("abc" < "abd") # True

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

print([3,4] >= [3,5]) # False

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.

Operador Nombre Ejemplo

and Devuelve True si ambos elementos son True True and True = True

or Devuelve True si al menos un elemento es True True or False = True

not Devuelve el contrario, True si es Falso y viceversa not True = False

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).

print(True and True) # True

print(True and False) # False

print(False and True) # False

print(False and False) # False

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.

print(True or True) # True

print(True or False) # True

print(False or True) # True

print(False or False) # False

Es importante notar que varios operadores pueden ser usados conjuntamente, y salvo que
existan paréntesis que indiquen una cierta prioridad, el primer operador que se evaluará será
el and. En el ejemplo que se muestra a continuación, podemos ver que tenemos dos
operadores and. Para calcular el resultado final, tenemos que empezar por el and calculando el
resultado en grupos de dos y arrastrando el resultado hacia el siguiente grupo. El resultado del
primer grupo sería True ya que estamos evaluando True and True. Después, nos guardamos
ese True y vamos a por el siguiente y último elemento, que es False. Por lo tanto hacemos True
and False por lo que el resultado final es False
print(True and True and False)

# |-----------|

# True and False

# |------------------|

# 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.

print(False and True or True or False)

# False and True = False

# Fase or True = True

# True or False = 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á.

print(not True) # False

print(not False) # True

print(not not not not True) # True

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

print(False and False or True)

# 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

print(True or False and False)

# True

Como ya hemos indicado anteriormente, se puede usar 0 y 1 para


representar False y True respectivamente. Veamos a usar esa representación para abreviar.
Podemos simplificar la siguiente expresión, ya que el not es el operador que primer se aplica.
Por lo tanto nos quedaría 0 and 0 or 1 and 1 or 1 and 0. Y como ya sabemos, después se aplica
el and, por lo que nos quedaría (0 and 0) or (1 and 1) or (1 and 0). Y ya nos quedaría sólo
aplicar el or de la expresión resultante 0 or 1 or 1, por lo que el resultando final es 1 o True.
Date cuenta que la expresión se podría simplificar, y una vez en un or nos encontramos que un
lado es True, podríamos dejar de calcular el resto de la expresión ya que el resultado ya
sería True

print(0 and not 1 or 1 and not 0 or 1 and 0)

# 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

& And bit a bit

~ Not bit a bit

^ Xor bit a bit

>> Desplazamiento a la derecha

&lt&lt Desplazamiento a la izquierda

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

# 7-> En realidad vale 7*1000 = 7000

# 2-> En relidad vale 2*100 = 200

# 6-> En reaidad vale 6*10 = 60

# 4 En realidad vale 4*1 = 4

# +---------

# Sumando todo: 7264

Entonces por ejemplo el número en binario 11011 es en realidad el número 27 en decimal. Es


posible convertir entre binario y decimal y viceversa. Para números pequeños se puede hacer
mentalmente muy rápido, pero para números más grandes, os recomendamos hacer uso de
alguna función en Python, como la función bin()

# Sistema binario

# 11011

# 1-> En realidad vale 1*16 = 16

# 1-> En realidad vale 1*8 = 8

# 0-> En realidad vale 1*4 = 0

# 1-> En realidad vale 1*2 = 2

# 1-> En realidad vle 1*1 = 1

# +---------

# 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
Ahora que ya sabemos como es la representación binaria, estamos ya en condiciones de
continuar con los operadores a nivel de bit, que realizan operaciones sobre los bits de estos
números binarios que acabamos de introducir.

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

print(bin(a & b))

#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

is Devuelve True si hacen referencia a el mismo objeto

is not Devuelve False si no hacen referencia a el mismo objeto

Operador is

El operador is comprueba si dos variables hacen referencia a el mismo objeto. En el siguiente


ejemplo podemos ver como al aplicarse sobre a y b el resultado es True.

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.

# Python crea dos objetos diferentes, uno

# para cada lista. Las listas son mutables.

a = [1, 2, 3]

b = [1, 2, 3]

print(a is not b) # True

# Python reutiliza el objeto que almacena 5

# por lo que ambas variables apuntan a el mismo

a=5

b=5
print(a is not b) # False

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 Nombre Ejemplo

in True si el elemento esta contenido xxx

not in False si el elemento no esta contenido xxx

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

#print(3 in 3) # Error! TypeError

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

print([1, 2] in [4, [1, 2], 7])

# 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

print(3 not in [1, 2, 4, 5])

# 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]

# Función que implementa "is" y "is not"

def estaContenido(a, lista):

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;

Ejemplos Operador Walrus

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 = []

entrada = input("Escribe algo: ")

while entrada != "terminar":

[Link](entrada)

entrada = input("Escribe algo: ")

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 = []

while (entrada := input("Escribe algo: ")) != "terminar":

[Link](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]

if (n := len(a)) > 10:

print(f"La lista tiene {n} elementos (>10)")

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.

resultado = [(x, y, x/y) for x in datos if (y := f(x)) > 0]

De manera similar, podemos reutilizar el resultado de una expresión evitando tener que volver
a computarla.

lista = [[y := f(x), x/y] for x in range(5)]

Puedes encontrar otros ejemplos en los siguientes enlaces:

 En la documentación oficial de Python

 En la propia sección de ejemplos de la PEP572.

 Y en este pull request.

Críticas Al Operador Walrus

El operador walrus ha recibido muchas críticas entre los desarrolladores de Python,


principalmente porque aunque sea cierto que su uso puede ahorrarnos alguna línea de código,
a veces empeora la legibilidad. No siempre menos código implica una mejora, sobre todo si se
hace más complicado de leer.

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:

[(j := i) for i in range(5)]

# SyntaxError: assignment expression within a comprehension cannot be used in a class body

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.
Programación Orientada a Objetos

Antes de nada, empecemos con una introducción básica a la Programación Orientada


a Objetos POO o OOP en inglés. Se trata de un paradigma de programación introducido en los
años 1970s, pero que no se hizo popular hasta años más tarde.

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.

La programación orientada a objetos está basada en 6 principios o pilares básicos:

 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.

# Creando una clase vacía

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.

# Creamos un objeto de la clase perro

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 instancia: Pertenecen a la instancia de la clase o al objeto. Son atributos


particulares de cada instancia, en nuestro caso de cada perro.

 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:

# El método __init__ es llamado al crear el objeto

def __init__(self, nombre, raza):

print(f"Creando perro {nombre}, {raza}")

# Atributos de instancia

[Link] = nombre

[Link] = 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.

mi_perro = Perro("Toby", "Bulldog")

print(type(mi_perro))

# Creando perro Toby, Bulldog

# <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'

# El método __init__ es llamado al crear el objeto

def __init__(self, nombre, raza):

print(f"Creando perro {nombre}, {raza}")


# Atributos de instancia

[Link] = nombre

[Link] = raza

Dado que es un atributo de clase, no es necesario crear un objeto para acceder al atributos.
Podemos hacer lo siguiente.

print([Link])

# mamífero

Se puede acceder también al atributo de clase desde el objeto.

mi_perro = Perro("Toby", "Bulldog")

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'

# El método __init__ es llamado al crear el objeto

def __init__(self, nombre, raza):

print(f"Creando perro {nombre}, {raza}")

# Atributos de instancia

[Link] = nombre

[Link] = raza
def ladra(self):

print("Guau")

def camina(self, pasos):

print(f"Caminando {pasos} pasos")

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 = Perro("Toby", "Bulldog")

mi_perro.ladra()

mi_perro.camina(10)

# Creando perro Toby, Bulldog

# Guau

# Caminando 10 pasos

Si te ha parecido útil este post, tal vez te interese:

 Uso del decorador property

 Métodos estáticos y de clase

Métodos en Python: instancia, clase y estáticos

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:

 Lo métodos de instancia “normales” que ya hemos visto como metodo()

 Métodos de clase usando el decorador @classmethod

 Y métodos estáticos usando el decorador @staticmethod

En la siguiente clase tenemos un ejemplo donde definimos los tres tipos de métodos.

class Clase:

def metodo(self):

return 'Método normal', self

@classmethod
def metododeclase(cls):

return 'Método de clase', cls

@staticmethod

def metodoestatico():

return "Método estático"

Veamos su comportamiento en detalle uno por uno.

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:

def metodo(self, arg1, arg2):

return 'Método normal', self

Y como ya sabemos, una vez creado un objeto pueden ser llamados.

mi_clase = Clase()

mi_clase.metodo("a", "b")

# ('Método normal', <__main__.Clase at 0x10b9daa90>)

En vista a esto, los métodos de instancia:

 Pueden acceder y modificar los atributos del objeto.

 Pueden acceder a otros métodos.

 Dado que desde el objeto self se puede acceder a la clase con ` [Link]`,
también pueden modificar el estado de la clase

Métodos de clase (classmethod)

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):

return 'Método de clase', cls


Se pueden llamar sobre la clase.

[Link]()

# ('Método de clase', __main__.Clase)

Pero también se pueden llamar sobre el objeto.

mi_clase.metododeclase()

# ('Método de clase', __main__.Clase)

Por lo tanto, los métodos de clase:

 No pueden acceder a los atributos de la instancia.

 Pero si pueden modificar los atributos de la clase.

Métodos estáticos (staticmethod)

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():

return "Método estático"

mi_clase = Clase()

[Link]()

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

Para entender la herencia, es fundamental entender la programación orientada a objetos, por


lo que te recomendamos empezar por ahí antes.
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

# Creamos una clase hija que hereda de la padre

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.

Extendiendo y modificando métodos

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:

 Tenemos la especie ya que todos los animales pertenecen a una.


 Y la edad, ya que todo ser vivo nace, crece, se reproduce y muere.

Y los métodos o funcionalidades:

 Tendremos el método hablar, que cada animal implementará de una forma. Los perros
ladran, las abejas zumban y los caballos relinchan.

 Un método moverse. Unos animales lo harán caminando, otros volando.

 Y por último un método descríbeme que será común.

Definimos la clase padre, con una serie de atributos comunes para todos los animales como
hemos indicado.

class Animal:

def __init__(self, especie, edad):

[Link] = especie

[Link] = edad

# Método genérico pero con implementación particular

def hablar(self):

# Método vacío

pass

# Método genérico pero con implementación particular

def moverse(self):

# Método vacío

pass

# Método genérico con la misma implementación

def describeme(self):

print("Soy un Animal del tipo", type(self).__name__)

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.

# Perro hereda de Animal

class Perro(Animal):

pass
mi_perro = Perro('mamífero', 10)

mi_perro.describeme()

# Soy un Animal del tipo Perro

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):

print("Caminando con 4 patas")

class Vaca(Animal):

def hablar(self):

print("Muuu!")

def moverse(self):

print("Caminando con 4 patas")

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:

 Heredados directamente de la clase padre: describeme()

 Heredados de la clase padre pero modificados: hablar() y moverse()

 Creados en la clase hija por lo tanto no existentes en la clase padre: picar()

mi_perro = Perro('mamífero', 10)

mi_vaca = Vaca('mamífero', 23)

mi_abeja = Abeja('insecto', 1)

mi_perro.hablar()

mi_vaca.hablar()

# Guau!

# Muuu!

mi_vaca.describeme()

mi_abeja.describeme()

# Soy un Animal del tipo Vaca

# Soy un Animal del tipo Abeja

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):

[Link] = especie

[Link] = edad

def hablar(self):

pass

def moverse(self):
pass

def describeme(self):

print("Soy un Animal del tipo", type(self).__name__)

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):

def __init__(self, especie, edad, dueño):

# Alternativa 1

# [Link] = especie

# [Link] = edad

# [Link]ño = dueño

# Alternativa 2

super().__init__(especie, edad)

[Link]ño = dueño

mi_perro = Perro('mamífero', 7, 'Luis')

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

class Clase3(Clase1, Clase2):

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

class Clase3(Clase1, Clase2):

pass

print(Clase3.__mro__)

# (<class '__main__.Clase3'>, <class '__main__.Clase1'>, <class '__main__.Clase2'>, <class


'object'>)

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

class Clase4(Clase1, Clase3, Clase2):

pass

print(Clase4.__mro__)

# (<class '__main__.Clase4'>, <class '__main__.Clase1'>, <class '__main__.Clase3'>, <class


'__main__.Clase2'>, <class 'object'>)

Junto con la herencia,


la cohesión, abstracción, polimorfismo, acoplamiento y encapsulamiento son otros de los
conceptos claves para entender la programación orientada a objetos.

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

Como si de un atributo normal se tratase, podemos acceder a el con el objeto . y nombre.

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 ().
# mi_clase.mi_atributo() # Error! Es un atributo, no un método

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:

def __init__(self, mi_atributo):

self.mi_atributo = mi_atributo

mi_clase = Clase("valor_atributo")

mi_clase.mi_atributo

# 'valor_atributo'

Bien, la explicación no es sencilla, pero está relacionada con el concepto de encapsulación de


la programación orientada a objetos. Este concepto nos indica que en determinadas ocasiones
es importante ocultar el estado interno de los objetos al exterior, para evitar que sean
modificados de manera incorrecta. Para la gente que venga del mundo de Java, esto no será
nada nuevo, y está muy relacionado con los métodos set()y get() que veremos a continuación.

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:

def __init__(self, mi_atributo):

self.__mi_atributo = mi_atributo

@property

def mi_atributo(self):
# El acceso se realiza a través de este "método" y

# podría contener código extra y no un simple retorno

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:

def __init__(self, mi_atributo):

self.__mi_atributo = mi_atributo

@property

def mi_atributo(self):

return self.__mi_atributo

@mi_atributo.setter

def mi_atributo(self, valor):

if valor != "":

print("Modificando el valor")

self.__mi_atributo = valor

else:

print("Error está vacío")

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 = ""

# Error está vacío

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”.

Interfaces y Abstract Base Class (ABC)

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.

Aunque lo veremos más adelante, ya podemos adelantar que Python no posee


la keyword interface como otros lenguajes de programación. A pesar de esto, existen dos
formas de definir interfaces en Python:
 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")

Análogamente creamos MandoLG.

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.

Al heredar de la clase Mando, no se obliga a MandoSamsung o MandoLG a implementar todos


los métodos. Es decir, ambas clases podrían no tener código para todos los métodos, y esto es
algo que puede causar problemas.

El razonamiento es el siguiente. Si Mando es un interfaz que como tal no implementa ningún


método (tan sólo define los métodos), ¿no sería acaso importante asegurarse de que las clases
que usan dicho interfaz implementan los métodos?

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 [Link].

from abc import 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.
from abc import ABCMeta

class Mando(metaclass=ABCMeta):

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.

Veamos como queda nuestro interfaz formal Mando.

from abc import abstractmethod

from abc import ABCMeta

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")

Y como de costumbre podemos crear un objeto y llamar a sus métodos.

mando_samsung = MandoSamsung()

mando_samsung.bajar_volumen()

# Samsung->Bajar

Siguiendo con el ejemplo podemos definir la clase MandoLG.

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")

Y creamos un objeto de MandoLG.

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.

from abc import ABCMeta

class FloatABC(metaclass=ABCMeta):

pass

[Link](float)

Y esto implica que el comportamiento de issubclass se ve modificado.

print(issubclass(float, FloatABC))

# True

Análogamente podemos realizar lo mismo con una clase definida por nosotros.

@[Link]
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.

from abc import ABC, abstractmethod

class Clase(metaclass=ABCMeta):

@abstractmethod

def metodo_abstracto(self):

pass

Sin embargo, también es posible combinar el decorador @abstractmethod con los


decoradores @classmethod y @staticmethod que ya vimos anteriormente. Nótese
que @abstractmethod debe ser usado siempre justo antes del método.

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

Análogamente podemos definir un @staticmethod. Se trata de un método que no permite


realizar modificaciones ni de la clase ni del objeto, ya que no lo recibe como parámetro.

class Clase(ABC):

@staticmethod

@abstractmethod

def metodo_abstracto_estatico():

pass

Y por último también podemos combinarlo con el decorador property.


class Clase(ABC):

@property

@abstractmethod

def metodo_abstracto_propiedad(self):

pass

Abstract Base Classes y colecciones

Python nos ofrece un conjunto de Abstract Base Classes que podemos usar para crear nuestras
propias clases, denominado [Link]. 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 [Link], 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.

from collections import abc

class MiSet([Link]):

def __init__(self, iterable):

[Link] = []

for value in iterable:

if value not in [Link]:

[Link](value)

def __iter__(self):

return iter([Link])

def __contains__(self, value):

return value in [Link]

def __len__(self):

return len([Link])

def __str__(self):

return "".join(str(i) for i in [Link])

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)

print(s1 | s2)

# efg

# abcdefghij

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)

# ZeroDivisionError: division by zero

Ese “error” que decimos que ha ocurrido es lanzado por Python (raise en Inglés) ya que la
división entre cero es una operación que matemáticamente no está definida.

Se trata de la excepción ZeroDivisionError. En el siguiente enlace, tenemos un listado de todas


las excepciones con las que nos podemos encontrar.

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

# A través de esta comprobación prevenimos que se divida entre cero.

if b!=0:

print(a/b)

else:

print("No se puede dividir!")

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("Información de la excepción")

O podemos lanzar otra de tipo NameError.

raise NameError("Información de la excepción")

Se puede ver como el string que hemos pasado se imprime a continuación de la excepción. Se
puede llamar también sin ningún parámetro como se hace a continuación.

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.

 O también podemos lanzar nosotros una excepción manualmente, usando raise.

 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.
Uso de try y except

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:

print("No se ha podido realizar la división")

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.

También se puede capturar diferentes excepciones como se ve en el siguiente ejemplo.

try:

#c = 5/0 # Si comentas esto entra en TypeError

d = 2 + "Hola" # Si comentas esto entra en ZeroDivisionError

except ZeroDivisionError:

print("No se puede dividir entre cero!")

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:

#c = 5/0 # Si comentas esto entra en TypeError

d = 2 + "Hola" # Si comentas esto entra en ZeroDivisionError

except (ZeroDivisionError, TypeError):

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:

#c = 5/0 # Si comentas esto entra en TypeError

d = 2 + "Hola" # Si comentas esto entra en ZeroDivisionError

except Exception:

print("Ha habido una excepción")

No obstante hay una forma de saber que excepción ha sido la que ha ocurrido.

try:

d = 2 + "Hola" # Si comentas esto entra en ZeroDivisionError

except Exception as ex:

print("Ha habido una excepción", type(ex))

# Ha habido una excepción <class 'TypeError'>

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:

# Forzamos una excepción al dividir entre 0

x = 2/0

except:

print("Entra en except, ha ocurrido una excepción")

else:

print("Entra en else, no ha ocurrido ninguna excepción")

#Entra en except, ha ocurrido una excepción

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:
# La división puede realizarse sin problema

x = 2/2

except:

print("Entra en except, ha ocurrido una excepción")

else:

print("Entra en else, no ha ocurrido ninguna excepción")

#Entra en else, no ha ocurrido ninguna excepción

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:

# Se entra ya que ha habido una excepción

print("Entra en except, ha ocurrido una excepción")

finally:

# También entra porque finally es ejecutado siempre

print("Entra en finally, se ejecuta el bloque finally")

#Entra en except, ha ocurrido una excepción

#Entra en finally, se ejecuta el bloque 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.
# Se intenta abrir un fichero y se captura una posible excepción

try:

with open('[Link]') as file:

read_data = [Link]()

except:

# Se entra aquí si no pudo ser abierto

print('No se pudo abrir')

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.

# Se intenta abrir un fichero y se captura una posible excepción

try:

with open('[Link]') as file:

read_data = [Link]()

# Capturamos una excepción concreta

except OSError:

print('OSError. No se pudo abrir')

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:

print("Entra en except, ha ocurrido una excepción")

else:

print("Entra en el else, no ha ocurrido ninguna excepción")

finally:

print("Entra en finally, se ejecuta el bloque 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:

print("Hay un error de sintaxis")

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().

assert False, "El assert falló"

Aunque mucho cuidado, ya que la expresión anterior no es equivalente a la siguiente, siendo la


misma errónea. Esto se debe a que en realidad se está evaluando bool((False, "El assert falló")),
lo que resulta ser siempre True. De hecho el siguiente código no lanzaría una excepción,
cuando realmente se esperaría que lo hiciera.

# INCORRECTO

assert(False, "El assert falló")

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)

En el mundo de la programación es muy importante probar o testear el software, para


asegurarse de que está libre de errores. Gracias al uso de assert() podemos realizar estas
comprobaciones de manera automática.
assert(calcula_media([5, 10, 7.5]) == 7.5)

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.

# Funcion suma de variables enteras

def suma(a, b):

assert(type(a) == int)

assert(type(b) == int)

return a+b

# Error, ya que las variables no son int

suma(3.0, 5.0)

# Ok, los argumentos son int

suma(3, 5)

assert() con clases

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))

# Error, mi_objeto no pertenece a MiOtraClase

assert(isinstance(mi_objeto, MiOtraClase))

# Error, mi_otro_objeto no pertenece a MiClase

assert(isinstance(mi_otro_objeto, MiClase))

Deshabilitar assert

A modo de curiosidad, es posible ejecutar un script de Python deshabilitando el assert.


Supongamos que tenemos el siguiente código.

# [Link]

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 [Link]

Definiendo Excepciones

Antes de ver como se define una excepción, te recomendamos otros de nuestros posts que te
ayudarán a entenderlo mejor:

 Programación Orientada a Objetos

 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.

# Creamos una excepción personalizada

class MiExcepcionPersonalizada(Exception):

pass
Y ya podríamos lanzarla con raise cuando quisiéramos.

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):

def __init__(self, parametro1, parametro2):

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.

# Lanzamos con raise la excepción que hemos creado

try:

raise MiExcepcionPersonalizada("ValorPar1", "ValorPar2")

except MiExcepcionPersonalizada as ex:

p1, p2 = [Link]

print(type(ex))

print("parametro1 =", p1)

print("parametro2 =", p2)

#<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.

# Se define una excepción

class MiExcepcion(Exception):

pass

# Se lanza
try:

raise MiExcepcion({"mensaje":"Mi Mensaje", "informacion":"Mi Informacion"})

# Se captura

except MiExcepcion as e:

detalles = [Link][0]

print(detalles)

print(detalles["mensaje"])

print(detalles["informacion"])

#{'mensaje': 'Este es el mensaje', 'informacion': 'Esto es la 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):

def __init__(self, mensaje, informacion):

[Link] = mensaje

[Link] = informacion

try:

raise MiExcepcion("Mi Mensaje", "Mi Informacion")

except MiExcepcion as e:

print([Link])

print([Link])

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.

El ejemplo más típico es el siguiente. Abrimos un fichero, escribimos contenido en él, y lo


cerramos.

# Haciendo uso de los context managers

with open('[Link]', 'w') as fichero:

[Link]('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.

# Sin usar los context managers

fichero = open('[Link]', 'w')

try:

[Link]('Hola!')

finally:

[Link]()

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.

Implementación con clases

Si quieres definir tu propio gestor de contexto, existen dos formas de hacerlo:

 Con una clase: Implementando los métodos dunder __enter__ y __exit__ en tu clase.

 Con decoradores: Usando el decorador @contextmanager.


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__")

def __exit__(self, exc_type, exc_value, traceback):

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'>

 exc_value: Valor de la excepción en el caso de que fuera proporcionado.

 traceback: Objecto traceback con más información acerca de la excepción.

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:

def __init__(self, nombre_fichero):

self.nombre_fichero = nombre_fichero

def __enter__(self):

[Link] = open(self.nombre_fichero, 'w')

return [Link]

def __exit__(self, exc_type, exc_val, exc_tb):

if [Link]:

[Link]()

Vayamos parte por parte:

 En el __init__ guardamos el nombre del fichero que queremos crear, nada nuevo.

 En el __enter__ creamos un fichero, lo almacenamos en nuestra clase, y devolvemos la


referencia que será usada dentro del with.

 Por último en el __exit__ cerramos el fichero si estaba abierto.

Una vez definida la clase, ya estamos en condiciones de usarla como hemos visto
anteriormente.

with MiClaseFichero("[Link]") as fichero:

[Link]("Hola!")

Por supuesto se trata de un ejemplo didáctico, si quieres leer un fichero simplemente usa las
funciones que Python proporciona por defecto.

Implementación con decoradores


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.

from contextlib import contextmanager

@contextmanager

def gestor_fichero(nombre_fichero):

try:

fichero = open(nombre_fichero, 'w')

yield fichero

finally:

[Link]()

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.

with gestor_fichero("[Link]") as fichero:

[Link]("Hola!")

Anidando diferentes with

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):

[Link] = -1

[Link] = [0]

def __enter__(self):

[Link] += 1

[Link](0)
return self

def __exit__(self, exc_type, exc_val, exc_tb):

#[Link][[Link]] = 0

[Link]()

[Link] -= 1

def print(self, text):

[Link][[Link]] += 1

numer = [str(i) for i in [Link][:[Link]+1]]

print(f"{' '*[Link]}{'.'.join(numer)} {text}")

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.

with Indice() as indice:

[Link]('Apartado')

with indice:

[Link]('Apartado')

[Link]('Apartado')

[Link]('Apartado')

[Link]('Apartado')

with indice:

[Link]('Apartado')

[Link]('Apartado')

with indice:

[Link]('Apartado')

[Link]('Apartado')

[Link]('Apartado')

[Link]('Apartado')

# 1 Apartado
# 1.1 Apartado

# 1.2 Apartado

# 1.3 Apartado

# 1.4 Apartado

# 1.4.1 Apartado

# 1.4.2 Apartado

# [Link] Apartado

# [Link] Apartado

# 2 Apartado

# 3 Apartado

Escribir archivos en Python

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)

 ‘x’: Si ya existe el fichero se devuelve un error.

Por lo tanto con la siguiente línea estamos creando un fichero con el


nombre datos_guardados.txt.

# Abre un nuevo fichero

fichero = open("datos_guardados.txt", 'w')

Si por lo contrario queremos añadir el contenido al ya existente en un fichero de antes,


podemos hacerlo en el modo append como hemos indicado.

# Abre un nuevo y añade el contenido al final

fichero = open("datos_guardados.txt", 'a')

Método write()

Ya hemos visto como crear el fichero. Veamos ahora como podemos añadir contenido.
Empecemos escribiendo un texto.
fichero = open("datos_guardados.txt", 'w')

[Link]("Contenido a escribir")

[Link]()

Por lo tanto si ahora abrimos el fichero datos_guardados.txt, veremos como efectivamente


contiene una línea con Contenido a escribir. ¿A que es fácil?

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 = open("datos_guardados.txt", 'w')

# Tenemos unos datos que queremos guardar

lista = ["Manzana", "Pera", "Plátano"]

# Guardamos la lista en el fichero

for linea in lista:

[Link](linea + "\n")

# Cerramos el fichero

[Link]()

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 = open("datos_guardados.txt", 'w')

lista = ["Manzana", "Pera", "Plátano"]

[Link](lista)

[Link]()
# 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')

lista = ["Manzana\n", "Pera\n", "Plátano\n"]

[Link](lista)

[Link]()

# Se guarda

# Manzana

# Pera

# Plátano

Uso del with

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.

lista = ["Manzana\n", "Pera\n", "Plátano\n"]

with open("datos_guardados.txt", 'w') as fichero:

[Link](lista)

Ejemplos escribir ficheros en Python

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.

# Escribe un mensaje en un fichero

def escribe_fichero(mensaje):

with open('fichero_comunicacion.txt', 'w') as fichero:

[Link](mensaje)

# Leer el mensaje del fichero

def lee_fichero():

mensaje = ""

with open('fichero_comunicacion.txt', 'r') as fichero:

mensaje = [Link]()

# Borra el contenido del fichero para dejarlo vacío

f = open('fichero_comunicacion.txt', 'w')

[Link]()

return mensaje

escribe_fichero("Esto es un mensaje")

print(lee_fichero())

Python PEP8: Escribiendo Código Fácil de Leer

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:
 Líneas demasiado largas.

 Nombres de variables poco explicativos.

 Código mal comentado.

 Uso incorrecto de espacios y líneas en blanco.

 Código mal identado.

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.

Formatear Código Python PEP8: Linters y Autoformatters

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 linters como flake8 o pycodestyle.

 Y los autoformatters como black y autopep8.

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:

$ pip install autopep8

Y si lo usamos sobre un [Link] intentará corregir los problemas.

$ autopep8 [Link] -v -i

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.

# [Link]

def MiFuncionSuma(A, B, C, imprime = True):

resultado=A+B+C

if imprime != False:

print(resultado)

return resultado

a =4

variable_b = 5

var_c = 10
MiFuncionSuma(a, variable_b, var_c)

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.

 Todo lo relativo al uso de espacios o líneas en blanco, puede ser corregido


automáticamente por autopep8.

 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.

El código anterior incumple las siguientes reglas:

 E251: Uso incorrecto de espacios en imprime = True, debería ser imprime=True.

 E225: Los operadores como el + deben usar espacios, A + B + C.

 E712: Usar if imprime en vez de if imprime != False.

 E305: Después de la declaración de una función debemos dejar dos espacios en


blanco.

 E221: No debemos usar tantos espacios al usar el operador = creando variables.

 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.

# [Link]

def mi_funcion_suma(a, b, c, imprime=True):

resultado = a + b + a

if imprime:

print(resultado)

return resultado

a=4

variable_b = 5

var_c = 10

mi_funcion_suma(a, variable_b, var_c)


Visto ya un ejemplo concreto, a continuación veremos las normas más importantes
introducidas en la PEP8.

Organización del código

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.

# 1 espacio entre métodos

# 2 espacios entre clases y funciones

class ClaseA:

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

for valor in valores:

suma_valores += valor

media = suma_valores / len(valores)

# 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

return media, mediana

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.

Se nos recomienda usar espacio con operadores de asignación.

# Correcto

x=5

# Incorrecto

x=5

Y también con operadores relacionales.

# 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

def mi_funcion(parameto_por_defecto = 5):

print(parameto_por_defecto)

Por otro lado se recomienda no dejar espacios dentro del paréntesis.

def duplica(a):

return a * 2

# Correcto

duplica(2)

# Incorrecto

duplica( 2 )

Y tampoco entre corchetes.

# 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

if x>0 and x%2==0:

print('...')

# Incorrecto

if x% 2 == 0:

print('...')

No usar espacio antes de , en llamadas a funciones o métodos.

# 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

diccionario ['key'] = lista [indice]

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

Identación del código

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:

 Usa siempre cuatro espacios.

 Usa tabuladores si trabajas sobre código ajeno que ya use tabuladores.


 Bajo ningún concepto mezcles uso de espacios y tabuladores.

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

def mi_funcion(primer_parametro, segundo_parametro,

tercer_parametro, cuarto_parametro,

quinto_parametro):

print("Python")

# Incorrecto

def mi_funcion(primer_parametro, segundo_parametro, tercer_parametro, cuarto_parametro,


quinto_parametro):

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

def mi_funcion(primer_parametro, segundo_parametro,

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

with open('/esta/ruta/es/muy/pero/que/muy/larga/y/no/entra/en/una/sola/linea/') as
fichero_1, \

open('/esta/ruta/es/muy/pero/que/muy/larga/y/no/entra/en/una/sola/linea/', 'w') as
fichero_2:

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)

La siguiente opción no es recomendada pero la PEP8 tampoco la prohíbe.

# 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.

print("La acentuación del Español")

# SyntaxError: Non-UTF-8 code starting with '\xf3' in file [Link] on line 1, but no encoding
declared; see [Link] for details

Sin embargo, con un pequeño cambio, podemos cambiar la forma en la que se codifica el texto.

# -*- coding: latin-1 -*-


print("La acentuación del Español")

# La acentuación del Español

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:

 Todos los identificadores (como variables) deben usar ASCII.

 También deberán usar Inglés en la medida de lo posible, salvo abreviaciones.

 Las únicas excepciones son los test para código no ASCII y los nombres de autores.

Convenciones al Nombrar Elementos: CamelCase y snake_case

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.

 Evitar usar l O y I, ya que pueden ser confundidas.

 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.

Estilos: Camel Case y snake_case

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:
 Funciones: Letras en minúscula separadas por barra
baja: funcion, mi_funcion_de_prueba.

 Variables: Al igual que las funciones: variable, mi_variable.

 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.

 Constantes: Nombrarlas usando mayúsculas y separadas por barra


bajas: UNA_CONSTANTE, OTRA_CONSTANTE.

 Módulos: Igual que las funciones: [Link], mi_modulo.py.

 Paquetes: En minúsculas pero sin separar por barra bajas: packete, mipaquete

En el siguiente fragmento podemos ver su uso.

# mi_script.py

CONSTANTE_GLOBAL = 10

class MiClase():

def mi_primer_metodo(self, variable_a, variable_b):

return (variable_a + variable_b) / CONSTANTE_GLOBAL

mi_objeto = MiClase()

print(mi_objeto.mi_primer_metodo(5, 5))

Importando Paquetes: Orden y Organización

Los import deben separarse en diferentes líneas.

# Correcto

import os

import sys

# Incorrecto

import os, sys

Sin embargo cuando se importen varios elementos de una misma librería, si sería correcto
importarlos en la misma línea.

# Correcto

from subprocess import Popen, PIPE


Con respecto a su ubicación, deberán seguir la siguiente:

 Deben ir al principio del fichero.

 Después de comentarios del módulo y docstrings.

 Antes de los global y las constantes.

Con respecto a su organización, debiendo haber una línea de separación entre cada grupo:

 Primero las librerías estándar.

 Segundo las librerías externas.

 Tercero las librerías locales.

Con respecto a su tipo:

 Se recomienda usar imports absolutos.

 Aunque también se permiten los relativos.

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

from collections import *

Si por ejemplo usamos únicamente deque y defaultdict, indicarlo.

# Correcto

from collections import deque, defaultdict

Comas al Final de Línea

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 = [

'[Link]',

'[Link]',
]

# Incorrecto

FICHEROS = ['[Link]', '[Link]',]

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.

 Si el comentario es corto, no hace falta usar el punto y final.

 Si el código es comentado en Inglés, usar Strunk/White.

 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.

 En lo relativo a los comentarios docstrings, usar la PEP257 como referencia.

A modo de ejemplo, como hemos explicado es conveniente evitar comentarios redundantes.

# Incorrecto

x=x+1 # Suma 1 a la variable x

# Correcto

x=x+1 # Compensa el offset producido por la medida

Nombrando Variables en Python

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:

 Barra baja al principio _nombre

 Barra baja al final nombre_

 Doble barra baja al principio __nombre


 Doble barra baja al principio y final __nombre__

 Barra baja sin contenido _

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()

# No se debería hacer, pero se puede

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.

#class = 5 # Error! class es una palabra reservada

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

Doble al Principio: __nombre


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()

#mi_clase.__variable # Error! No es accesible

Doble al Principio y Final: nombre

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")

Descartando Variables con Barra Baja

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):

return (a+b), (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

Introducción al Command Line Interface


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.

El comando ls permite listar directorios y ficheros.

$ ls -ta

El comando mkdir permite la creación de directorios, de ahí su nombre de make dir.

$ mkdir -p foo

En ambos comandos podemos identificar tres partes:

 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.

Command Line Interfaces en Python

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.

# [Link]

# Tipo de operacion: suma/resta/multiplicación

operacion = "suma"

# Parámetros de la operación

a=4

b=7
if operacion == "suma":

print(a+b)

elif operacion == "resta":

print(a-b)

elif operacion == "multiplicacion":

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.

$ python [Link] 5 2 suma

Otra opción es usando nombres para los argumentos.

$ python [Link] --a=5 --b=2 --operacion=suma

Este será por tanto el problema que resolveremos a lo largo de este artículo, utilizando
diferentes maneras y explorando las opciones que Python nos ofrece.

Creando un CLI sin argparse

La primera forma que tenemos de que un script reciba argumentos desde el terminal es
usando el módulo sys.

# [Link]

import sys

print([Link])

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.

$ python [Link] 5 2 suma

['[Link]', '5', '2', 'suma']

Con esta lista ya tendríamos todo lo que necesitamos para usar nuestra [Link], pero
existe una manera mucho mejor de tratar los argumentos, y es usando la librería argparse.
Veamos como funciona.
Creando un CLI con argparse

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.

 Y parseamos los argumentos.

# [Link]

import argparse

parser = [Link]()

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.

$ python [Link] 7 7 suma

{'a': '7', 'b': '7', 'operacion': 'suma'}

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.

 Añadimos type a cada argumento.

 Añadimos choices a la operación, para que de un error si seleccionamos una no


soportada.

 Ponemos la operación como parámetro opcional required=False y asignamos un valir


por defecto default en el caso de que no se proporcione.

 Añadimos documentación con description y help para que un usuario ajeno a nuestro
código sepa utilizarlo.

Poniéndolo todo junto en nuestro programa [Link], tendríamos algo como lo siguiente.

# [Link]

import argparse

parser = [Link](description='Calculadora, suma/resta/multiplica a y b')

parser.add_argument('-a', '--numero_a', type=int, help='Parámetro a')

parser.add_argument('-b', '--numero_b', type=int, help='Parámetro b')

parser.add_argument('-o', '--operacion',

type=str,

choices=['suma', 'resta', 'multiplicacion'],

default='suma', required=False,

help='Operación a realizar con a y b')

args = parser.parse_args()
if [Link] == 'suma':

print(args.numero_a + args.numero_b)

elif [Link] == 'resta':

print(args.numero_a - args.numero_b)

elif [Link] == 'multiplicacion':

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.

$ python [Link] -a 1 -b 4 -o suma

Pero es también posible usar el nombre completo.

$ python [Link] --numero_a 7 --numero_b 3 --operacion multiplicacion

21

O también podemos usar el signo igual = para realizar la asignación.

$ python [Link] -a=7 -b=3 --operacion=multiplicacion

21

Podemos alterar el orden de los parámetros ya que no son posicionales.

$ python [Link] -b=3 --operacion=multiplicacion -a=7

21

Una vez vistas como se realizarían las llamadas, veamos otras características. Por ejemplo, para
acceder a la documentación usamos --help.

$ python [Link] --help

usage: [Link] [-h] [-a NUMERO_A] [-b NUMERO_B] [-o {suma,resta,multiplicacion}]

Calculadora, suma/resta/multiplica a y b

optional arguments:

-h, --help show this help message and exit

-a NUMERO_A, --numero_a NUMERO_A

Parámetro a
-b NUMERO_B, --numero_b NUMERO_B

Parámetro b

-o {suma,resta,multiplicacion}, --operacion {suma,resta,multiplicacion}

Operación a realizar con a y b

Obtendremos un error si usamos un tipo incorrecto. Por ejemplo a no puede ser una cadena.

$ python [Link] -a=Hola -b=Hola --operacion=multiplicacion

usage: [Link] [-h] [-a NUMERO_A] [-b NUMERO_B] [-o {suma,resta,multiplicacion}]

[Link]: error: argument -a/--numero_a: invalid int value: 'Hola'

Por otro lado operacion sólo puede tomar los valores suma/resta/multiplicación, por lo que un
valor diferente dará error.

python [Link] -a=7 -b=3 -o=Hola

usage: [Link] [-h] [-a NUMERO_A] [-b NUMERO_B] [-o {suma,resta,multiplicacion}]

[Link]: 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.

$ python [Link] -a=7 -b=3

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.

# [Link]

import argparse

parser = [Link]()

parser.add_argument('-o', '--operacion')
args = parser.parse_args()

print(vars(args))

Las siguiente llamadas son todas válidas y producen el mismo resultado.

$ python [Link] -o suma

{'operacion': 'suma'}

$ python [Link] --operacion suma

{'operacion': 'suma'}

$ python [Link] --opera suma

{'operacion': 'suma'}

$ python [Link] --op suma

{'operacion': 'suma'}

Cambiar el prefijo de los argumentos

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 = [Link](prefix_chars='+')

parser.add_argument('+a')

parser.add_argument('+b')

args = parser.parse_args()

print(vars(args))

Ahora si queremos consultar la ayuda, deberemos usar el nuevo prefijo.

$ python cambio_prefijo.py +h

usage: cambio_prefijo.py [+h] [+a A] [+b B]


optional arguments:

+h, ++help show this help message and exit

+a A

+b B

Y podemos pasar parámetros de la siguiente manera.

$ python cambio_prefijo.py +a 10 +b 7

{'a': '10', 'b': '7'}

Pasando argumentos desde un archivo

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 = [Link](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))

Asumiendo que tenemos el siguiente contenido almacenado en [Link].

-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.

$ python argumentos_fichero.py @[Link]

{'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.

$ python argumentos_fichero.py @[Link] -a 200

{'a': '200', 'b': '10', 'c': '1', 'd': '0', 'e': '11', 'f': '9'}

Argumentos excluyentes

Otra utilidad muy importante es la definición de grupos mutually exclusive o mutuamente


excluyentes. Su uso nos permite hacer que dos argumentos no puedan ser utilizados a la vez,
es decir, que sean excluyentes. Para ello creamos un grupo
con add_mutually_exclusive_group y los argumentos que añadamos al mismo no podrán ser
usados simultáneamente.

# [Link]

import argparse

parser = [Link]()

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 [Link] -f 3 -b 10

usage: [Link] [-h] [-f FOO | -b BAR]

[Link]: error: argument -b/--bar: not allowed with argument -f/--foo

Usando acciones para los argumentos


Otra característica muy útil que nos ofrece argparse, es el uso de acciones personalizadas para
cada argumento utilizando action. Esto nos permite modificar como el argumento que
introducimos es parseado y almacenado en la variable, pudiendo usar acciones por defecto o
definirlas nosotros mismos.

Las acciones que argparse nos ofrece por defecto son las siguientes:

 store: Es el comportamiento por defecto que hemos visto anteriormente. Simplemente


almacena el valor que se pasa con el argumento en una variable.

 store_const: Almacena una constante en la variable, cuyo valor debemos especificar


en const.

 store_true: Almacena el booleano True en la variable. Muy útil para definir flags que
no reciben un valor concreto.

 store_false: Análogo al anterior pero almacena False.

 append: Añade el argumento a una lista. Útil cuando un argumento es pasado


múltiples veces.

 append_const: Similar a append pero almacena en la lista la constante especificada


en const.

 count: Cuenta el número de veces que un determinado argumento es pasado.

 help: Muestra la ayuda del programa y finaliza sin hacer nada más.

 version: Muestra la versión del programa y finaliza la ejecución.

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.

# [Link]

import argparse

parser = [Link]()

parser.add_argument('-a', action='store')

args = parser.parse_args()

print(vars(args))

Y podemos llamar a nuestro script como ya sabemos.

$ python [Link] -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 = [Link]()
parser.add_argument('-a', action='store_const', const="99")

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

usage: store_const.py [-h] [-a]

store_const.py: error: unrecognized arguments: 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 = [Link]()

parser.add_argument('-a', action='store_true')

parser.add_argument('-b', action='store_false')

args = parser.parse_args()

print(vars(args))

Y podemos llamar al script de la siguiente manera.

$ python store_true_false.py -a -b

{'a': True, 'b': False}

$ python store_true_false.py -ab

{'a': True, 'b': False}

Por otro lado, podemos usar append para pasar el mismo argumento varias veces, siendo cada
valor almacenado en la misma lista.

# [Link]

import argparse

parser = [Link]()
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 [Link] -a 1 -a 2 -a 3

{'a': ['1', '2', '3']}

Análogamente podemos usar append_const, que almacena el valor de const en la lista.

# append_const.py

import argparse

parser = [Link]()

parser.add_argument('-a', action='append_const', const=0)

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

{'a': [0, 0, 0]}

$ python append_const.py -aaa

{'a': [0, 0, 0]}

Por otro lado, el uso de count cuenta el número de veces que un argumento es pasado.

# [Link]

import argparse

parser = [Link]()

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 [Link] -a -a -a

{'a': 3}
$ python [Link] -aaa

{'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 = [Link]()

[Link] = '1.0'

parser.add_argument('--ayuda', action='help')

parser.add_argument('--version', action='version')

args = parser.parse_args()

print(vars(args))

La siguiente llamada muestra la ayuda.

$ python help_version.py --ayuda

usage: help_version.py [-h] [--ayuda] [--version]

optional arguments:

-h, --help show this help message and exit

--ayuda

--version show program's version number and exit

Y de la siguiente manera podemos mostrar la versión, previa definición en el


campo version del parser.

$ python help_version.py --version

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 [Link]. Una vez creada e implementada dicha clase, bastaría
con pasarla como action.

Argumentos con múltiples valores


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:

 n: Se consumen n valores por el argumento, y se almacenen en una lista.

 ?: Se consume un sólo valor, que puede ser opcional.

 *: Para un número arbitrario de valores, y son almacenados en una lista.

 +: Similar a * pero requiere al menos un valor.

 [Link]: Consume todos los valores hasta el final.

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 = [Link]()

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

{'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_variables1.py: error: argument -a: expected 5 arguments

Para el caso de un sólo valor opcional tenemos el siguiente ejemplo.

# argumentos_variables2.py

import argparse

parser = [Link]()

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'}

También podemos pasar un número arbitrario de valores.

# argumentos_variables3.py

import argparse

parser = [Link]()

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

{'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 = [Link]()

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

usage: argumentos_variables4.py [-h] [-a A [A ...]]

argumentos_variables4.py: error: argument -a: expected at least one argument

$ python argumentos_variables4.py -a 1 2

{'a': ['1', '2']}

Por último si usamos [Link] 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 = [Link]()

parser.add_argument('-a')

parser.add_argument('-b', nargs=[Link])

args = parser.parse_args()

print(vars(args))

$ python argumentos_variables5.py -a 10 -b 1 2 3 4 5

{'a': '10', 'b': ['1', '2', '3', '4', '5']}

Cambiar el nombre de la variable

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 = [Link]()

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.

$ python uso_dest.py -a PythonMola

{'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.

Recapitulemos lo que hemos aprendido en este capítulo:

 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.

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.

Tests Manuales y Tests Automatizados

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.

assert(calcula_media([3, 7, 5]) == 5.0)

assert(calcula_media([30, 0]) == 15.0)

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.

Traceback (most recent call last):

File "[Link]", line 7, in <module>

assert((calcula_media([30, 0]) == 15.0))

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.

Tests Unitarios en Python con unittest

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.

# [Link]

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:

 Creamos una clase Test<NombreDeLoQueSePrueba> que hereda de [Link].

 Definimos varios tests como métodos de la clase, usando test_<NombreDelTest> para


nombrarlos.

 En cada test ejecutamos las comprobaciones necesarias, usando assertEqual en vez


de assert, pero su comportamiento es totalmente análogo.

# [Link]

from funciones import calcula_media

import unittest

class TestCalculaMedia([Link]):

def test_1(self):

resultado = calcula_media([10, 10, 10])

[Link](resultado, 10)

def test_2(self):

resultado = calcula_media([5, 3, 4])

[Link](resultado, 4)

if __name__ == '__main__':

[Link]()

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.

Ran 2 tests in 0.006s

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.

$ python -m unittest -v tests

test_1 ([Link]) ... ok

test_2 ([Link]) ... ok

----------------------------------------------------------------------

Ran 2 tests in 0.000s

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.

$ python -m unittest discover -v

Otras comprobaciones en unittest

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.

 .assertTrue(x): Verifica que el valor es True.

 .assertFalse(x): Verifica que el valor es False.

 .assertIs(a, b): Verifica que ambas variables son la misma (ver operador is).

 .assertIsNone(x): Verifica que el valor es None.

 .assertIn(a, b): Verifica que a pertenece al iterable b (ver operador in).

 .assertIsInstance(a, b): Verifica que a es una instancia de b

 .assertRaises(x): Verifica que se lanza una excepción.

import unittest

class TestEjemplos([Link]):

def test_in(self):

# Ok ya que 1 esta contenido en [1, 2, 3]

[Link](1, [1, 2, 3])


def test_is(self):

a = [1, 2, 3]

b=a

# Ok ya que son el mismo objeto

[Link](a, b)

def test_excepcion(self):

# Dividir 0/0 da error, pero es lo esperado por el test

with [Link](ZeroDivisionError):

x = 0/0

$ python -m unittest -v tests

test_excepcion ([Link]) ... ok

test_in ([Link]) ... ok

test_is ([Link]) ... ok

----------------------------------------------------------------------

Ran 3 tests in 0.000s

OK

Usando setUp y tearDown

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([Link]):

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.

$ python -m unittest -v tests

test_1 ([Link]) ... Entra setUp

Test: test_1

Entra tearDown

ok

test_2 ([Link]) ... Entra setUp

Test: test_2

Entra tearDown

ok

----------------------------------------------------------------------

Ran 2 tests in 0.000s

OK

Evitando Side Effects

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.

Top 50 Ejemplos y Ejercicios


Ahora que ya dominas Python veamos unos ejemplos prácticos en diferentes sectores:

 🤖 Inteligencia artificial, redes neuronales, genética y ADN.

 📊 Dashboards y representación gráfica.

 📈 Análisis financiero, apuestas, estadística y matemáticas.

 🔐 Encriptado, firma digital, computación cuántica, factorización de números primos y


fuerza bruta.

 🎮 Desarrollo de juegos, webs, APIs e interfaces de usuario.

 📐 Implementación de algoritmos sencillos.

Estos ejemplos dar ideas de lo que se puede hacer con Python, dejando al lector unos
ejercicios para que explore más allá.

Aquí puedes encontrar los códigos completos:

 🔗 [Link]/ellibrodepython/50-ejemplos-python

 📙 Firma digital con cryptography

 📙 Encriptar mensaje con cryptography

 📙 Determina si un número es primo

 📙 Factoriza un número en primos

 📙 Computación cuántica con qiskit

 📙 Fuerza bruta con itertools

 📙 Acortador de enlaces con flask

 📙 Construye una API con flask

 📙 Trabaja con ficheros con os

 📙 Bases de datos con sqlite

 📙 Pattern matching con re

 📙 Analítica de futbol con seaborn

 📙 Programar tareas

 📙 Gráficas con matplotlib

 📙 Redes neuronales con tensorflow

 📙 Logging y verbosity

 📙 Cálculo simbólico con sympy

 📙 Crear juego con pygame

 📙 Scrapping web con beautifulsoup

 📙 Unir pdfs pdf


 📙 Excels con pyexcel

 📙 Benchmark de funciones

 📙 Rotar imagen con scikit-image

 📙 Simulaciones simpy

 📙 Coordenadas y distancia con geopy

 📙 Genética y ADN con biopython

 📙 Moléculas con rdkit

 📙 Polinomios con numpy

 📙 Simular apuestas con numpy

 📙 Monte Carlo con numpy

 📙 Análisis financiero con yfinance

 📙 Programación asincrona con aiohttp

 📙 Baraja de Poker

 📙 Barajar cartas con shuffle

 📙 Ordenar con bubble sort

 📙 Convertir binario a decimal

 📙 Código C en Python

 📙 Martingala y apuestas

 📙 Temporizador con time

 📙 Calcular impuestos

 📙 Plot interactivo con bokeh

 📙 Simular hipoteca

 📙 El Quijote con wordcloud

 📙 Programas ejecutables con pyinstaller

 📙 Calcular interés compuesto

 📙 Busca el número que falta

 📙 Comprimir información

 📙 Interfaz de usuario con pyqt

 📙 Dashboard con streamlit

 📙 Problema de la mochila

También podría gustarte