Está en la página 1de 179

Nombre: Pol Requena Martos

Fecha: 01/11/2022
Curso: Udemy - Curso Maestro de Python
Profesor: Héctor Costa Guzmán
Trabajando con números
Utilizando el intérprete como una calculadora
3+2

3-2

3*2

Podemos utilizar comentarios # para explicar lo que hace nuestro código

# División
3/2

1.5

# Módulo
3%2

# Potencia
3**2

Podemos distinguir 2 tipos de números


Enteros: Que no tienen una parte decimal y van desde menos infinito a más infinito.
Flotantes o decimales: Números que tienen una parte decimal escrita con un punto.

323239829389.238273283

323239829389.2383

También podemos realizar operaciones más complejas


Python interpretará automáticamente las prioridades de los operadores.

3-2+4*10

41

Las variables
Concepto fundamental de la programación, en el cuál se define un identificador y se le asigna un valor.

Luego podemos utilizarlas como si se tratase de un valor literal, incluso operarlas entre otras variables y volver a asignarles un valor en
cualquier momento.

# Asignación de un valor a una variable


n = 3
n

n+3

n*2
n*2

n*n

m=10

n+m

13

n*m+10

40

n=10
m=15
n+m

25

n=m

15

15

n=m+10

25

n=n+25

50

Reutilización
Al crear una estructura de cálculos con variables, podemos fácilmente adaptar sus valores para hacer distintas comprobaciones.

nota_1 = 2
nota_2 = 5
nota_media = (nota_1 + nota_2) / 2
nota_media

3.5

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Textos (cadenas de caracteres)
'Hola Mundo'

'Hola Mundo'

"Hola Mundo"

'Hola Mundo'

'Este texto incluye unas " " '

'Este texto incluye unas " " '

"Esta 'palabra' se encuentra escrita entre comillas simples"

"Esta 'palabra' se encuentra escrita entre comillas simples"

"Esta \"palabra\" se encuentra escrita entre comillas dobles"

'Esta "palabra" se encuentra escrita entre comillas dobles'

'Esta \'palabra\' se encuentra escrita entre comillas dobles'

"Esta 'palabra' se encuentra escrita entre comillas dobles"

La función print()
Es una instrucción que nos permite mostrar correctamente el valor de una cadena (u otros valores/variables) por pantalla.

"Una cadena"
'otra cadena'
'otra cadena más'

'otra cadena más'

print("Una cadena")
print('otra cadena')
print('otra cadena más')

Una cadena
otra cadena
otra cadena más

Acepta carácteres especiales como las tabulaciones /t o los saltos de línea /n

print("Un texto\tuna tabulación")

Un texto una tabulación

print("Un texto\nuna nueva línea")

Un texto
una nueva línea

Para evitar los carácteres especiales, debemos indicar que una cadena es cruda (raw)

print("C:\nombre\directorio")

C:
ombre\directorio

print(r"C:\nombre\directorio") # r => raw (cruda)

C:\nombre\directorio

Podemos utilizar """ (triple comillas) para cadenas multilínea

print("""Una línea
otra línea
otra línea\tuna tabulación""")

Una línea
otra línea
otra línea una tabulación

También es posible asignar cadenas a variables


La forma correcta de mostrarlas es con la instrucción print().

c = "Esto es una cadena\ncon dos líneas"

'Esto es una cadena\ncon dos líneas'

print(c)

Esto es una cadena


con dos líneas

Operaciones
Una de las operaciones de las cadenas es la concatenación (o suma de cadenas)

c + c

'Esto es una cadena\ncon dos líneasEsto es una cadena\ncon dos líneas'

print(c+c)

Esto es una cadena


con dos líneasEsto es una cadena
con dos líneas

s = "Una cadena" " compuesta de dos cadenas"


print(s)

Una cadena compuesta de dos cadenas

c1 = "Una cadena"
c2 = "otra cadena"
print("Una cadena " + c2)

Una cadena otra cadena

También es posible utilizar la multiplicación de cadenas

diez_espacios = " " * 10


print(diez_espacios + "un texto a diez espacios")

un texto a diez espacios

Índices en las cadenas


Los índices nos permiten posicionarnos en un carácter específico de una cadena.

Representan un número [índice], que empezando por el 0 indica el carácter de la primera posición, y así sucesivamente.

palabra = "Python"

palabra[0] # carácter en la posición 0

'P'

palabra[3]

'h'

El índice negativo -1, hace referencia al carácter de la última posición, el -2 al penúltimo y así sucesivamente

palabra[-1]

'n'

palabra[-0]

'P'

palabra[-2]

'o'

palabra[-6]

'P'
palabra[5]

'n'

Slicing en las cadenas


El slicing es una capacidad de las cadenas que devuelve un subconjunto o subcadena utilizando dos índices [inicio:fin]:

El primer índice indica donde empieza la subcadena (se incluye el carácter).


El segundo índice indica donde acaba la subcadena (se excluye el carácter).

palabra = "Python"

palabra[0:2]

'Py'

palabra[2:]

'thon'

palabra[:2]

'Py'

Si en el slicing no se indica un índice se toma por defecto el principio y el final (incluídos)

palabra[:]

'Python'

palabra[:2] + palabra[2:]

'Python'

palabra[-2:]

'on'

Si un índice se encuentra fuera del rango de la cadena, dará error

palabra[99]

---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-47-b31ddef6ab27> in <module>()
----> 1 palabra[99]

IndexError: string index out of range

Pero con slicing ésto no pasa y simplemente se ignora el espacio hueco

palabra[:99]

'Python'

palabra[99:]

''

Inmutabilidad
Una propiedad de las cadenas es que no se pueden modificar. Si intentamos reasignar un carácter, no nos dejará:

palabra[0] = "N"

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-51-c87a9e773639> in <module>()
----> 1 palabra[0] = "N"

TypeError: 'str' object does not support item assignment

Sin embargo, utilizando slicing y concatenación podemos generar nuevas cadenas fácilmente:

palabra = "N" + palabra[1:]


palabra = "N" + palabra[1:]
palabra

'Nython'

Funciones
Un ejemplo de función útil que soportan las cadenas es len(), que nos permite saber su longitud (el número de caracteres que
contienen).

len(palabra)

Hay más funciones, pero las iremos descubriendo a lo largo del curso.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Las listas
Tipo compuesto de dato que puede almacenar distintos valores (llamados ítems) ordenados entre [ ] y separados con comas.

numeros = [1,2,3,4]

datos = [4,"Una cadena",-15,3.14,"Otra cadena"]

Índices y slicing
Funcionan de una forma muy similar a las cadenas de caracteres.

datos[0]

datos[-1]

'Otra cadena'

datos[-2:]

[3.14, 'Otra cadena']

Suma de listas
Da como resultado una nueva lista que incluye todos los ítems.

numeros + [5,6,7,8]

[1, 2, 3, 4, 5, 6, 7, 8]

Son modificables
A diferencia de las cadenas, en las listas sí podemos modificar sus ítems utilizando índices:

pares = [0,2,4,5,8,10]

pares[3]= 6

pares

[0, 2, 4, 6, 8, 10]

Integran funcionalidades internas, como el método .append() para añadir un ítem al final de la lista

pares.append(12)

pares

[0, 2, 4, 6, 8, 10, 12]

pares.append(7*2)

pares

[0, 2, 4, 6, 8, 10, 12, 14]

Y una peculiaridad, es que también aceptan asignación con slicing para modificar varios ítems en conjunto

letras = ['a','b','c','d','e','f']

letras[:3]

['a', 'b', 'c']

letras[:3] = ['A','B','C']

letras
letras

['A', 'B', 'C', 'd', 'e', 'f']

Asignar una lista vacía equivale a borrar los ítems de la lista o sublista

letras[:3] = []

letras

['d', 'e', 'f']

letras = []

letras

[]

La función len() también funciona con las listas del mismo modo que en las cadenas:

len(letras)

len(pares)

Listas dentro de listas (anidadas)


Podemos manipular fácilmente este tipo de estructuras utilizando múltiples índices, como si nos refieréramos a las filas y columnas de
una tabla.

a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
r = [a,b,c]

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

r[0] # Primera sublista

[1, 2, 3]

r[-1] # Última sublista

[7, 8, 9]

r[0][0] # Primera sublista, y de ella, primer ítem

r[1][1] # Segunda sublista, y de ella, segundo ítem

r[2][2] # Tercera sublista, y de ella, tercer ítem

r[-1][-1] # Última sublista, y de ella, último ítem

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Leyendo valores por teclado
Se consigue utilizando la instrucción input() que lee y devuelve una cadena:

valor = input()

algo

valor

'algo'

valor = input()

100

# Aunque leemos un número, en realidad es una cadena de texto


valor

'100'

# Podemos mostrar un mensaje antes de leer el valor


valor = input("Introduce un valor: ")

Introduce un valor: 100

# Una cadena y un número no se pueden operar


valor + 100

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-5071d551e583> in <module>()
----> 1 valor + 100

TypeError: Can't convert 'int' object to str implicitly

valor = input("Introduce un número entero: ")

Introduce un número entero: 500

Cast con int(), de cadena a entero


# La función int() de entero, devuelve un número entero a partir de una cadena
valor = int(valor)

valor

500

valor + 1000 # Ahora ya es operable

1500

valor = input("Introduce un número entero: ")

Introduce un número entero: 10.50

Cast con float(), de cadena a flotante


# La función float() de flotante, devuelve un número flotante a partir de una cadena
valor = float(valor)

10 + valor

20.5

valor

10.5

valor = float( input("Introduce un número decimal o entero: ") )

Introduce un número decimal o entero: 3.14

valor

3.14

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ejemplo de cabecera
Intenta deducir qué hace este programa (no hace falta que le des muchas vueltas).

n = 0
while n < 10: # while = mientras
if (n % 2) == 0: # if = si (de condicion)
print(n,'es un número par')
else: # else = sino (def condicion)
print(n,'es un número impar')
n = n + 1

0 es un número par
1 es un número impar
2 es un número par
3 es un número impar
4 es un número par
5 es un número impar
6 es un número par
7 es un número impar
8 es un número par
9 es un número impar
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Tema 01: Introducción informal (Soluciones)
Nota: Estos ejercicios son optativos para hacer al final de la unidad y están pensados para apoyar tu aprendizaje.

1) Identifica el tipo de dato (int, float, string o list) de los siguientes valores literales

"Hola Mundo" # string


[1, 10, 100] # list
-25 # int
1.167 # float
["Hola", "Mundo"] # list
' ' # string

2) Determina mentalmente (sin programar) el resultado que aparecerá por pantalla a partir de las siguientes variables:

a = 10
b = -5
c = "Hola "
d = [1, 2, 3]
print(a * 5) # 50
print(a - b) # 15
print(c + "Mundo") # Hola Mundo
print(c * 2) # Hola Hola
print(c[-1]) # 3
print(c[1:]) # [2, 3]
print(d + d) # [1, 2, 3, 1, 2, 3]

3) El siguiente código pretende realizar una media entre 3 números, pero no funciona correctamente. ¿Eres capaz de identificar
el problema y solucionarlo?

numero_1 = 9
numero_2 = 3
numero_3 = 6

media = (numero_1 + numero_2 + numero_3) / 3 # Hay que realizar primero la suma de los 3 números antes de dividir
print("La nota media es", media)

La nota media es 6.0

4) A partir del ejercicio anterior, vamos a suponer que cada número es una nota, y lo que queremos es obtener la nota final. El
problema es que cada nota tiene un valor porcentual:

La primera nota vale un 15% del total


La segunda nota vale un 35% del total
La tercera nota vale un 50% del total

Desarrolla un programa para calcular perfectamente la nota final.

nota_1 = 10
nota_2 = 7
nota_3 = 4

# Completa el ejercicio aquí


media = numero_1 * 0.15 + numero_2 * 0.35 + numero_3 * 0.50 # Podemos multiplicar en tanto por 1 cada nota y sumarla
print("La nota media es", media)

La nota media es 5.3999999999999995

5) La siguiente matriz (o lista con listas anidadas) debe cumplir una condición, y es que en cada fila, el cuarto elemento
siempre debe ser el resultado de sumar los tres primeros. ¿Eres capaz de modificar las sumas incorrectas utilizando la técnica
del slicing?

Ayuda: La función llamada sum(lista) devuelve una suma de todos los elementos de la lista ¡Pruébalo!

matriz = [
[1, 1, 1, 3],
[2, 2, 2, 7],
[3, 3, 3, 9],
[4, 4, 4, 13]
]

# Completa el ejercicio aquí


matriz[1][-1] = sum(matriz[1][:-1])
matriz[3][-1] = sum(matriz[3][:-1])

print(matriz)
[[1, 1, 1, 3], [2, 2, 2, 6], [3, 3, 3, 9], [4, 4, 4, 12]]

6) Al realizar una consulta en un registro hemos obtenido una cadena de texto corrupta al revés. Al parecer contiene el nombre
de un alumno y la nota de un exámen. ¿Cómo podríamos formatear la cadena y conseguir una estructura como la siguiente?:

Nombre Apellido ha sacado un Nota de nota.

Ayuda: Para voltear una cadena rápidamente utilizando slicing podemos utilizar un tercer índice -1: cadena[::-1]

cadena = "zeréP nauJ,01"

# Completa el ejercicio aquí


cadena_volteada = cadena[::-1]
print(cadena_volteada[3:], "ha sacado un", cadena_volteada[:2], "de nota.")

Juan Pérez ha sacado un 10 de nota.


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
El tipo lógico
Representa la mínima expresión racional:

Verdadero (True)
Falso (False)

1+1 == 3

False

1+1 == 2

True

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Los operadores relacionales
Sirven para comparar dos valores, dependiendo del resultado de la comparación pueden devolver:

Verdadero (True), si es cierta


Falso (False), si no es cierta

# Igual que
3 == 2

False

# Distinto de
3 != 2

True

# Mayor que
3 > 2

True

# Menor que
3 < 2

False

# Mayor o igual que


3 >= 4

False

# Menor o igual que


3 <= 4

True

También podemos comparar variables

a = 10
b = 5
a > b

True

b != a

True

a == b*2

True

Y otros tipos, como cadenas, listas, el resultado de algunas funciones o los propios tipos lógicos

"Hola" == "Hola"

True

"Hola" != "Hola"

False

c = "Hola"
c[0] == "H"

True

c[-1] == "a"

True

l1 = [0,1,2]
l2 = [2,3,4]
l1 == l2
False

len(l1) == len(l2)

True

l1[-1] == l2[0]

True

True == True

True

False == True

False

False != True

True

True > False

True

False > True

False

La representación aritmética de True y False equivale a 1 y 0 respectivamente

True * 3

False / 5

0.0

True * False

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Operadores lógicos
Encontramos 3 básicos:

Not
And
Or

Not - Negación lógica


not True

False

not True == False

True

And - Conjunción lógica (agrupa uniendo)


True and True

True

True and False

False

False and True

False

False and False

False

a = 45
a > 10 and a < 20

False

c = "Hola Mundo"
len(c) >= 20 and c[0] == "H"

False

Or - Disyunción lógica (agrupa separando)


True or True

True

True or False

True

False or True

True

False or False

False

c = "OTRA COSA"
c == "EXIT" or c == "FIN" or c == "SALIR"

False

c = "Lector"
c[0] == "H" or c[0] == "h"
False

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Expresiones anidadas
Se pueden solucionar empleando las reglas de precedencia:

1. Primero los paréntesis porque tienen prioridad


2. Segundo, las expresiones aritméticas por sus propias reglas
3. Tercero, las expresiones relacionales
4. Cuarto, las expresiones lógicas

a = 10
b = 5
a * b - 2**b >= 20 and not (a % b) != 0

False

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Operadores de asignación
Actúan directamente sobre la variable actual modificando su valor.

a = 0

a += 5 # suma en asignación
a

10

a -= 10 # resta en asignación
a

a = 5
a *= 2 # producto en asignación
a

10

a /= 2 # división en asignación
a

5.0

a %= 2 # módulo en asignación

1.0

a **= 10 # potencia en asignación

1.0

a = 5
a **= 5
a

3125

Repasando nuestro ejemplo de cabecera


¿Qué expresiones eres capaz de identificar?

n = 0 # Asignación de 0 en n
while n < 10: # Expresión relacional n < 10, que devuelve True
if (n % 2) == 0: # Expresión aritmética y expresión relacional
print(n,'es un número par')
else:
print(n,'es un número impar')
n += 1 # expresión aritmética n = n + 1 equivalente a operación en asignación n+=1
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Tema 02: Operadores y expresiones (Soluciones)
Nota: Estos ejercicios son optativos para hacer al final de la unidad y están pensados para apoyar tu aprendizaje.

1) Realiza un programa que lea 2 números por teclado y determine los siguientes aspectos (es suficiene con mostrar True o
False):

Si los dos números son iguales


Si los dos números son diferentes
Si el primero es mayor que el segundo
Si el segundo es mayor o igual que el primero

# Completa el ejercicio aquí


n1 = float( input("Introduce el primer número: ") )
n2 = float( input("Introduce el segundo número: ") )

print("¿Son iguales? ", n1 == n2)


print("¿Son diferentes?", n1 != n2)
print("¿El primero es mayor que el segundo?", n1 > n2)
print("¿El segundo es mayor o igual que el primero?", n1 <= n2)

Introduce el primer número: 10


Introduce el segundo número: 10
¿Son iguales? True
¿Son diferentes? False
¿El primero es mayor que el segundo? False
¿El segundo es mayor o igual que el primero? True

2) Utilizando operadores lógicos, determina si una cadena de texto introducida por el usuario tiene una longitud mayor o igual
que 3 y a su vez es menor que 10 (es suficiene con mostrar True o False):

# Completa el ejercicio aquí


cadena = input("Escribe una cadena: ")
print("¿La longitud de la cadena es mayor o igual que 3 y menor que 10?", len(cadena) >= 3 and len(cadena) < 10

Escribe una cadena: hola amigos que tal


¿La longitud de la cadena es mayor o igual que 3 y menor que 10? False

3) Realiza un programa que cumpla el siguiente algoritmo utilizando siempre que sea posible operadores en asignación:

Guarda en una variable numero_magico el valor 12345679 (sin el 8)


Lee por pantalla otro numero_usuario, especifica que sea entre 1 y 9 (asegúrate que sea un número entero)
Multiplica el numero_usuario por 9 en sí mismo
Multiplica el numero_magico por el numero_usuario en sí mismo
Finalmente muestra el valor final del numero_magico por pantalla

# Completa el ejercicio aquí


numero_magico = 12345679
numero_usuario = int(input("Introduce un número del 1 al 9: "))
numero_usuario *= 9
numero_magico *= numero_usuario
print("El número mágico es:", numero_magico)

Introduce un número del 1 al 9: 5


El número mágico es: 555555555
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Condiciones
Sentencia If (Si)
Permite dividir el flujo de un programa en diferentes caminos. El if se ejecuta siempre que la expresión que comprueba devuelva True

if True: # equivale a if not False


print("Se cumple la condición")
print("También se muestre este print")

Se cumple la condición
También se muestre este print

Podemos encadenas diferentes If

a = 5
if a == 2:
print("a vale 2")
if a == 5:
print("a vale 5")

a vale 5

O también anidar If dentro de If

a = 5
b = 10
if a == 5:
print("a vale",a)
if b == 10:
print("y b vale",b)

a vale 5
y b vale 10

Como condición podemos evaluar múltiples expresiones, siempre que éstas devuelvan True o False

if a==5 and b == 10:


print("a vale 5 y b vale 10")

a vale 5 y b vale 10

Sentencia Else (Sino)


Se encadena a un If para comprobar el caso contrario (en el que no se cumple la condición).

n = 11
if n % 2 == 0:
print(n,"es un número par")
else:
print(n,"es un número impar")

11 es un número impar

Sentencia Elif (Sino Si)


Se encadena a un if u otro elif para comprobar múltiples condiciones, siempre que las anteriores no se ejecuten.

comando = "OTRA COSA"


if comando == "ENTRAR":
print("Bienvenido al sistema")
elif comando == "SALUDAR":
print("Hola, espero que te lo estés pasando bien aprendiendo Python")
elif comando == "SALIR":
print("Saliendo del sistema...")
else:
print("Este comando no se reconoce")

Este comando no se reconoce

nota = float(input("Introduce una nota: "))


if nota >= 9:
print("Sobresaliente")
elif nota >= 7:
print("Notable")
elif nota >= 6:
print("Bien")
elif nota >= 5:
print("Suficiente")
else:
print("Insuficiente")

Introduce una nota: 10


Sobresaliente

Es posible simular el funcionamiento de elif con if utilizando expresiones condicionales

nota = float(input("Introduce una nota: "))


if nota >= 9:
print("Sobresaliente")
if nota >= 7 and nota < 9:
print("Notable")
if nota >= 6 and nota < 7:
print("Bien")
if nota >= 5 and nota < 6:
print("Suficiente")
if nota < 5:
print("Insuficiente")

Introduce una nota: 8


Notable

Instrucción Pass
Sirve para finalizar un bloque, se puede utilizar en un bloque vacío.

if True:
pass
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Condiciones
Sentencia If (Si)
Permite dividir el flujo de un programa en diferentes caminos. El if se ejecuta siempre que la expresión que comprueba devuelva True

if True: # equivale a if not False


print("Se cumple la condición")
print("También se muestre este print")

Se cumple la condición
También se muestre este print

Podemos encadenas diferentes If

a = 5
if a == 2:
print("a vale 2")
if a == 5:
print("a vale 5")

a vale 5

O también anidar If dentro de If

a = 5
b = 10
if a == 5:
print("a vale",a)
if b == 10:
print("y b vale",b)

a vale 5
y b vale 10

Como condición podemos evaluar múltiples expresiones, siempre que éstas devuelvan True o False

if a==5 and b == 10:


print("a vale 5 y b vale 10")

a vale 5 y b vale 10

Sentencia Else (Sino)


Se encadena a un If para comprobar el caso contrario (en el que no se cumple la condición).

n = 11
if n % 2 == 0:
print(n,"es un número par")
else:
print(n,"es un número impar")

11 es un número impar

Sentencia Elif (Sino Si)


Se encadena a un if u otro elif para comprobar múltiples condiciones, siempre que las anteriores no se ejecuten.

comando = "OTRA COSA"


if comando == "ENTRAR":
print("Bienvenido al sistema")
elif comando == "SALUDAR":
print("Hola, espero que te lo estés pasando bien aprendiendo Python")
elif comando == "SALIR":
print("Saliendo del sistema...")
else:
print("Este comando no se reconoce")

Este comando no se reconoce

nota = float(input("Introduce una nota: "))


if nota >= 9:
print("Sobresaliente")
elif nota >= 7:
print("Notable")
elif nota >= 6:
print("Bien")
elif nota >= 5:
print("Suficiente")
else:
print("Insuficiente")

Introduce una nota: 10


Sobresaliente

Es posible simular el funcionamiento de elif con if utilizando expresiones condicionales

nota = float(input("Introduce una nota: "))


if nota >= 9:
print("Sobresaliente")
if nota >= 7 and nota < 9:
print("Notable")
if nota >= 6 and nota < 7:
print("Bien")
if nota >= 5 and nota < 6:
print("Suficiente")
if nota < 5:
print("Insuficiente")

Introduce una nota: 8


Notable

Instrucción Pass
Sirve para finalizar un bloque, se puede utilizar en un bloque vacío.

if True:
pass
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Iteraciones
Iterar significa realizar una acción varias veces. Cada vez que se repite se denomina iteración.

Sentencia While (Mientras)


Se basa en repetir un bloque a partir de evaluar una condición lógica, siempre que ésta sea True.

Queda en las manos del programador decidir el momento en que la condición cambie a False para hacer que el While finalice.

c = 0
while c <= 5:
c+=1
print("c vale",c)

c vale 1
c vale 2
c vale 3
c vale 4
c vale 5
c vale 6

Sentencia Else en bucle While


Se encadena al While para ejecutar un bloque de código una vez la condición ya no devuelve True (normalmente al final).

c = 0
while c <= 5:
c+=1
print("c vale",c)
else:
print("Se ha completado toda la iteración y c vale",c)

c vale 1
c vale 2
c vale 3
c vale 4
c vale 5
c vale 6
Se ha completado toda la iteración y c vale 6

Instrucción Break
Sirve para "romper" la ejecución del While en cualquier momento. No se ejecutará el Else, ya que éste sólo se llama al finalizar la
iteración.

c = 0
while c <= 5:
c+=1
if (c==4):
print("Rompemos el bucle cuando c vale",c)
break
print("c vale",c)
else:
print("Se ha completado toda la iteración y c vale",c)

c vale 1
c vale 2
c vale 3
Rompemos el bucle cuando c vale 4

Instrucción Continue
Sirve para "saltarse" la iteración actual sin romper el bucle.

c = 0
while c <= 5:
c+=1
if c==3 or c==4:
# print("Continuamos con la siguiente iteración",c)
continue
print("c vale",c)
else:
print("Se ha completado toda la iteración y c vale",c)
c vale 1
c vale 2
c vale 5
c vale 6
Se ha completado toda la iteración y c vale 6

Creando un menú interactivo


print("Bienvenido al menú interactivo")
while(True):
print("""¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir""")
opcion = input()
if opcion == '1':
print("Hola, espero que te lo estés pasando bien")
elif opcion == '2':
n1 = float(input("Introduce el primer número: "))
n2 = float(input("Introduce el segundo número: "))
print("El resultado de la suma es: ",n1+n2)
elif opcion =='3':
print("¡Hasta luego! Ha sido un placer ayudarte")
break
else:
print("Comando desconocido, vuelve a intentarlo")

Bienvenido al menú interactivo


¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir
1
Hola, espero que te lo estés pasando bien
¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir
2
Introduce el primer número: 10
Introduce el segundo número: 5
El resultado de la suma es: 15.0
¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir
kdjsk
Comando desconocido, vuelve a intentarlo
¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir
3
¡Hasta luego! Ha sido un placer ayudarte
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Recorriendo los elementos de una lista utilizando While
numeros = [1,2,3,4,5,6,7,8,9,10]
indice = 0
while indice < len(numeros):
print(numeros[indice])
indice+=1

1
2
3
4
5
6
7
8
9
10

Sentencia For (Para) con listas


for numero in numeros: # Para [variable] en [lista]
print(numero)

1
2
3
4
5
6
7
8
9
10

Modificar ítems de la lista al vuelo


Para asignar un nuevo valor a los elementos de una lista mientras la recorremos, podríamos intentar asignar al número el nuevo valor:

for numero in numeros:


numero *= 10

numeros

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Sin embargo, ésto no funciona. La forma correcta de hacerlo es haciendo referencia al índice de la lista en lugar
de la variable:

indice = 0
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for numero in numeros:
numeros[indice] *= 10
indice+=1
numeros

[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

Podemos utilizar la función enumerate() para conseguir el índice y el valor en cada iteración fácilmente:

numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


for indice,numero in enumerate(numeros):
numeros[indice] *= 10
numeros

[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

For con cadenas


cadena = "Hola amigos"
for caracter in cadena:
print(caracter)
H
o
l
a

a
m
i
g
o
s

Pero debemos recordar que las cadenas son inmutables:

for i,c in enumerate(cadena):


cadena[i] = "*"

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-9-8ba888c46579> in <module>()
1 for i,c in enumerate(cadena):
----> 2 cadena[i] = "*"

TypeError: 'str' object does not support item assignment

Sin embargo siempre podemos generar una nueva cadena:

cadena2 = ""
for caracter in cadena:
cadena2 += caracter * 2

cadena

'Hola amigos'

cadena2

'HHoollaa aammiiggooss'

La función range()
Sirve para generar una lista de números que podemos recorrer fácilmente, pero no ocupa memoria porque se interpreta sobre la
marcha:

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

0
1
2
3
4
5
6
7
8
9

range(10)

range(0, 10)

for i in [0,1,2,3,4,5,6,7,9]:
print(i)

0
1
2
3
4
5
6
7
9

Si queremos conseguir la lista literal podemos transformar el range a una lista:

list(range(10))

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ejemplo de cabecera
n = 0
while n < 10:
if (n % 2) == 0:
print(n,'es un número par')
else:
print(n,'es un número impar')
n = n + 1
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Tema 03: Control de flujo (Soluciones)
Nota: Estos ejercicios son optativos para hacer al final de la unidad y están pensados para apoyar tu aprendizaje.

1) Realiza un programa que lea dos números por teclado y permita elegir entre 3 opciones en un menú:

Mostrar una suma de los dos números


Mostrar una resta de los dos números (el primero menos el segundo)
Mostrar una multiplicación de los dos números
En caso de no introducir una opción válida, el programa informará de que no es correcta.

# Completa el ejercicio aquí


n1 = float(input("Introduce un número: ") )
n2 = float(input("Introduce otro número: ") )
opcion = 0

print("¿Qué quieres hacer? \n1) Sumar los dos números\n2) Restar los dos números\n3) Multiplicar los dos números"
opcion = int(input("Introduce un número: ") )

if opcion == 1:
print("La suma de",n1,"+",n2,"es",n1+n2)
elif opcion == 2:
print("La resta de",n1,"-",n2,"es",n1-n2)
elif opcion == 3:
print("El producto de",n1,"*",n2,"es",n1*n2)
else:
print("Opción incorrecta")

Introduce un número: 5
Introduce otro número: 5
¿Qué quieres hacer?
1) Sumar los dos números
2) Restar los dos números
3) Multiplicar los dos números
Introduce un número: 3
El producto de 5.0 * 5.0 es 25.0

2) Realiza un programa que lea un número impar por teclado. Si el usuario no introduce un número impar, debe repetise el
proceso hasta que lo introduzca correctamente.

# Completa el ejercicio aquí


numero = 0
while numero % 2 == 0: # Mientras sea par repetimos el proceso
numero = int(input("Introduce un número impar: ") )

Introduce un número impar: 4


Introduce un número impar: 2
Introduce un número impar: 3

3) Realiza un programa que sume todos los números pares desde el 0 hasta el 100:

Sugerencia: Puedes utilizar la funciones sum() y range() para hacerlo más fácil. El tercer parámetro en la función range(inicio, fin, salto)
indica un salto de números, pruébalo.

# Completa el ejercicio aquí


# range(0, 101, 2)

suma = sum( range(0, 101, 2) )


print(suma)

# Segunda forma con bucles


num = 0
suma = 0

while num <= 100:


if num % 2 == 0:
suma += num
num += 1

print(suma)

2550
2550

4) Realiza un programa que pida al usuario cuantos números quiere introducir. Luego lee todos los números y realiza una
media aritmética:

# Completa el ejercicio aquí


suma = 0
numeros = int(input("¿Cuántos números quieres introducir? ") )
for x in range(numeros):
suma += float(input("Introduce un número: ") )
print("Se han introducido",numeros,"números que en total han sumado",suma,"y la media es",suma/numeros)

¿Cuántos numeros quieres introducir? 4


Introduce un número: 3
Introduce un número: 2
Introduce un número: 4
Introduce un número: 6
Se han introducido 4 números que en total han sumado 15.0 y la media es 3.75

5) Realiza un programa que pida al usuario un número entero del 0 al 9, y que mientras el número no sea correcto se repita el
proceso. Luego debe comprobar si el número se encuentra en la lista de números y notificarlo:

Consejo: La sintaxis "valor in lista" permite comprobar fácilmente si un valor se encuentra en una lista (devuelve True o False)

# Completa el ejercicio aquí


numeros = [1, 3, 6, 9]

while True:
numero = int(input("Escribe un número del 0 al 9: "))
if numero >= 0 and numero <= 9:
break
if numero in numeros:
print("El número",numero,"se encuentra en la lista",numeros)
else:
print("El número",numero,"no se encuentra en la lista",numeros)

Escribe un número del 0 al 9: 3


El número 3 se encuentra en la lista [1, 3, 6, 9]

6) Utilizando la función range() y la conversión a listas genera las siguientes listas dinámicamente:

Todos los números del 0 al 10 [0, 1, 2, ..., 10]


Todos los números del -10 al 0 [-10, -9, -8, ..., 0]
Todos los números pares del 0 al 20 [0, 2, 4, ..., 20]
Todos los números impares entre -20 y 0 [-19, -17, -15, ..., -1]
Todos los números múltiples de 5 del 0 al 50 [0, 5, 10, ..., 50]

Pista: Utiliza el tercer parámetro de la función range(inicio, fin, salto).

# Completa el ejercicio aquí


print( list( range( 0, 11 ) ) )
print( list( range( -10, 1 ) ) )
print( list( range( 0, 21, 2 ) ) )
print( list( range( -19, 0, 2 ) ) )
print( list( range( 0, 51, 5 ) ) )

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[-19, -17, -15, -13, -11, -9, -7, -5, -3, -1]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]

7) Dadas dos listas, debes generar una tercera con todos los elementos que se repitan en ellas, pero no debe repetise ningún
elemento en la nueva lista:

# Completa el ejercicio aquí


lista_1 = ["h",'o','l','a',' ', 'm','u','n','d','o']
lista_2 = ["h",'o','l','a',' ', 'l','u','n','a']

lista_3 = []

for letra in lista_1:


if letra in lista_2 and letra not in lista_3:
lista_3.append(letra)

print(lista_3)

['h', 'o', 'l', 'a', ' ', 'u', 'n']


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Trabajando con números
Utilizando el intérprete como una calculadora
3+2

3-2

3*2

Podemos utilizar comentarios # para explicar lo que hace nuestro código

# División
3/2

1.5

# Módulo
3%2

# Potencia
3**2

Podemos distinguir 2 tipos de números


Enteros: Que no tienen una parte decimal y van desde menos infinito a más infinito.
Flotantes o decimales: Números que tienen una parte decimal escrita con un punto.

323239829389.238273283

323239829389.2383

También podemos realizar operaciones más complejas


Python interpretará automáticamente las prioridades de los operadores.

3-2+4*10

41

Las variables
Concepto fundamental de la programación, en el cuál se define un identificador y se le asigna un valor.

Luego podemos utilizarlas como si se tratase de un valor literal, incluso operarlas entre otras variables y volver a asignarles un valor en
cualquier momento.

# Asignación de un valor a una variable


n = 3
n

n+3

n*2
n*2

n*n

m=10

n+m

13

n*m+10

40

n=10
m=15
n+m

25

n=m

15

15

n=m+10

25

n=n+25

50

Reutilización
Al crear una estructura de cálculos con variables, podemos fácilmente adaptar sus valores para hacer distintas comprobaciones.

nota_1 = 2
nota_2 = 5
nota_media = (nota_1 + nota_2) / 2
nota_media

3.5

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Textos (cadenas de caracteres)
'Hola Mundo'

'Hola Mundo'

"Hola Mundo"

'Hola Mundo'

'Este texto incluye unas " " '

'Este texto incluye unas " " '

"Esta 'palabra' se encuentra escrita entre comillas simples"

"Esta 'palabra' se encuentra escrita entre comillas simples"

"Esta \"palabra\" se encuentra escrita entre comillas dobles"

'Esta "palabra" se encuentra escrita entre comillas dobles'

'Esta \'palabra\' se encuentra escrita entre comillas dobles'

"Esta 'palabra' se encuentra escrita entre comillas dobles"

La función print()
Es una instrucción que nos permite mostrar correctamente el valor de una cadena (u otros valores/variables) por pantalla.

"Una cadena"
'otra cadena'
'otra cadena más'

'otra cadena más'

print("Una cadena")
print('otra cadena')
print('otra cadena más')

Una cadena
otra cadena
otra cadena más

Acepta carácteres especiales como las tabulaciones /t o los saltos de línea /n

print("Un texto\tuna tabulación")

Un texto una tabulación

print("Un texto\nuna nueva línea")

Un texto
una nueva línea

Para evitar los carácteres especiales, debemos indicar que una cadena es cruda (raw)

print("C:\nombre\directorio")

C:
ombre\directorio

print(r"C:\nombre\directorio") # r => raw (cruda)

C:\nombre\directorio

Podemos utilizar """ (triple comillas) para cadenas multilínea

print("""Una línea
otra línea
otra línea\tuna tabulación""")

Una línea
otra línea
otra línea una tabulación

También es posible asignar cadenas a variables


La forma correcta de mostrarlas es con la instrucción print().

c = "Esto es una cadena\ncon dos líneas"

'Esto es una cadena\ncon dos líneas'

print(c)

Esto es una cadena


con dos líneas

Operaciones
Una de las operaciones de las cadenas es la concatenación (o suma de cadenas)

c + c

'Esto es una cadena\ncon dos líneasEsto es una cadena\ncon dos líneas'

print(c+c)

Esto es una cadena


con dos líneasEsto es una cadena
con dos líneas

s = "Una cadena" " compuesta de dos cadenas"


print(s)

Una cadena compuesta de dos cadenas

c1 = "Una cadena"
c2 = "otra cadena"
print("Una cadena " + c2)

Una cadena otra cadena

También es posible utilizar la multiplicación de cadenas

diez_espacios = " " * 10


print(diez_espacios + "un texto a diez espacios")

un texto a diez espacios

Índices en las cadenas


Los índices nos permiten posicionarnos en un carácter específico de una cadena.

Representan un número [índice], que empezando por el 0 indica el carácter de la primera posición, y así sucesivamente.

palabra = "Python"

palabra[0] # carácter en la posición 0

'P'

palabra[3]

'h'

El índice negativo -1, hace referencia al carácter de la última posición, el -2 al penúltimo y así sucesivamente

palabra[-1]

'n'

palabra[-0]

'P'

palabra[-2]

'o'

palabra[-6]

'P'
palabra[5]

'n'

Slicing en las cadenas


El slicing es una capacidad de las cadenas que devuelve un subconjunto o subcadena utilizando dos índices [inicio:fin]:

El primer índice indica donde empieza la subcadena (se incluye el carácter).


El segundo índice indica donde acaba la subcadena (se excluye el carácter).

palabra = "Python"

palabra[0:2]

'Py'

palabra[2:]

'thon'

palabra[:2]

'Py'

Si en el slicing no se indica un índice se toma por defecto el principio y el final (incluídos)

palabra[:]

'Python'

palabra[:2] + palabra[2:]

'Python'

palabra[-2:]

'on'

Si un índice se encuentra fuera del rango de la cadena, dará error

palabra[99]

---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-47-b31ddef6ab27> in <module>()
----> 1 palabra[99]

IndexError: string index out of range

Pero con slicing ésto no pasa y simplemente se ignora el espacio hueco

palabra[:99]

'Python'

palabra[99:]

''

Inmutabilidad
Una propiedad de las cadenas es que no se pueden modificar. Si intentamos reasignar un carácter, no nos dejará:

palabra[0] = "N"

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-51-c87a9e773639> in <module>()
----> 1 palabra[0] = "N"

TypeError: 'str' object does not support item assignment

Sin embargo, utilizando slicing y concatenación podemos generar nuevas cadenas fácilmente:

palabra = "N" + palabra[1:]


palabra = "N" + palabra[1:]
palabra

'Nython'

Funciones
Un ejemplo de función útil que soportan las cadenas es len(), que nos permite saber su longitud (el número de caracteres que
contienen).

len(palabra)

Hay más funciones, pero las iremos descubriendo a lo largo del curso.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Las listas
Tipo compuesto de dato que puede almacenar distintos valores (llamados ítems) ordenados entre [ ] y separados con comas.

numeros = [1,2,3,4]

datos = [4,"Una cadena",-15,3.14,"Otra cadena"]

Índices y slicing
Funcionan de una forma muy similar a las cadenas de caracteres.

datos[0]

datos[-1]

'Otra cadena'

datos[-2:]

[3.14, 'Otra cadena']

Suma de listas
Da como resultado una nueva lista que incluye todos los ítems.

numeros + [5,6,7,8]

[1, 2, 3, 4, 5, 6, 7, 8]

Son modificables
A diferencia de las cadenas, en las listas sí podemos modificar sus ítems utilizando índices:

pares = [0,2,4,5,8,10]

pares[3]= 6

pares

[0, 2, 4, 6, 8, 10]

Integran funcionalidades internas, como el método .append() para añadir un ítem al final de la lista

pares.append(12)

pares

[0, 2, 4, 6, 8, 10, 12]

pares.append(7*2)

pares

[0, 2, 4, 6, 8, 10, 12, 14]

Y una peculiaridad, es que también aceptan asignación con slicing para modificar varios ítems en conjunto

letras = ['a','b','c','d','e','f']

letras[:3]

['a', 'b', 'c']

letras[:3] = ['A','B','C']

letras
letras

['A', 'B', 'C', 'd', 'e', 'f']

Asignar una lista vacía equivale a borrar los ítems de la lista o sublista

letras[:3] = []

letras

['d', 'e', 'f']

letras = []

letras

[]

La función len() también funciona con las listas del mismo modo que en las cadenas:

len(letras)

len(pares)

Listas dentro de listas (anidadas)


Podemos manipular fácilmente este tipo de estructuras utilizando múltiples índices, como si nos refieréramos a las filas y columnas de
una tabla.

a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
r = [a,b,c]

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

r[0] # Primera sublista

[1, 2, 3]

r[-1] # Última sublista

[7, 8, 9]

r[0][0] # Primera sublista, y de ella, primer ítem

r[1][1] # Segunda sublista, y de ella, segundo ítem

r[2][2] # Tercera sublista, y de ella, tercer ítem

r[-1][-1] # Última sublista, y de ella, último ítem

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Leyendo valores por teclado
Se consigue utilizando la instrucción input() que lee y devuelve una cadena:

valor = input()

algo

valor

'algo'

valor = input()

100

# Aunque leemos un número, en realidad es una cadena de texto


valor

'100'

# Podemos mostrar un mensaje antes de leer el valor


valor = input("Introduce un valor: ")

Introduce un valor: 100

# Una cadena y un número no se pueden operar


valor + 100

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-5071d551e583> in <module>()
----> 1 valor + 100

TypeError: Can't convert 'int' object to str implicitly

valor = input("Introduce un número entero: ")

Introduce un número entero: 500

Cast con int(), de cadena a entero


# La función int() de entero, devuelve un número entero a partir de una cadena
valor = int(valor)

valor

500

valor + 1000 # Ahora ya es operable

1500

valor = input("Introduce un número entero: ")

Introduce un número entero: 10.50

Cast con float(), de cadena a flotante


# La función float() de flotante, devuelve un número flotante a partir de una cadena
valor = float(valor)

10 + valor

20.5

valor

10.5

valor = float( input("Introduce un número decimal o entero: ") )

Introduce un número decimal o entero: 3.14

valor

3.14

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ejemplo de cabecera
Intenta deducir qué hace este programa (no hace falta que le des muchas vueltas).

n = 0
while n < 10: # while = mientras
if (n % 2) == 0: # if = si (de condicion)
print(n,'es un número par')
else: # else = sino (def condicion)
print(n,'es un número impar')
n = n + 1

0 es un número par
1 es un número impar
2 es un número par
3 es un número impar
4 es un número par
5 es un número impar
6 es un número par
7 es un número impar
8 es un número par
9 es un número impar
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Tema 01: Introducción informal (Soluciones)
Nota: Estos ejercicios son optativos para hacer al final de la unidad y están pensados para apoyar tu aprendizaje.

1) Identifica el tipo de dato (int, float, string o list) de los siguientes valores literales

"Hola Mundo" # string


[1, 10, 100] # list
-25 # int
1.167 # float
["Hola", "Mundo"] # list
' ' # string

2) Determina mentalmente (sin programar) el resultado que aparecerá por pantalla a partir de las siguientes variables:

a = 10
b = -5
c = "Hola "
d = [1, 2, 3]
print(a * 5) # 50
print(a - b) # 15
print(c + "Mundo") # Hola Mundo
print(c * 2) # Hola Hola
print(c[-1]) # 3
print(c[1:]) # [2, 3]
print(d + d) # [1, 2, 3, 1, 2, 3]

3) El siguiente código pretende realizar una media entre 3 números, pero no funciona correctamente. ¿Eres capaz de identificar
el problema y solucionarlo?

numero_1 = 9
numero_2 = 3
numero_3 = 6

media = (numero_1 + numero_2 + numero_3) / 3 # Hay que realizar primero la suma de los 3 números antes de dividir
print("La nota media es", media)

La nota media es 6.0

4) A partir del ejercicio anterior, vamos a suponer que cada número es una nota, y lo que queremos es obtener la nota final. El
problema es que cada nota tiene un valor porcentual:

La primera nota vale un 15% del total


La segunda nota vale un 35% del total
La tercera nota vale un 50% del total

Desarrolla un programa para calcular perfectamente la nota final.

nota_1 = 10
nota_2 = 7
nota_3 = 4

# Completa el ejercicio aquí


media = numero_1 * 0.15 + numero_2 * 0.35 + numero_3 * 0.50 # Podemos multiplicar en tanto por 1 cada nota y sumarla
print("La nota media es", media)

La nota media es 5.3999999999999995

5) La siguiente matriz (o lista con listas anidadas) debe cumplir una condición, y es que en cada fila, el cuarto elemento
siempre debe ser el resultado de sumar los tres primeros. ¿Eres capaz de modificar las sumas incorrectas utilizando la técnica
del slicing?

Ayuda: La función llamada sum(lista) devuelve una suma de todos los elementos de la lista ¡Pruébalo!

matriz = [
[1, 1, 1, 3],
[2, 2, 2, 7],
[3, 3, 3, 9],
[4, 4, 4, 13]
]

# Completa el ejercicio aquí


matriz[1][-1] = sum(matriz[1][:-1])
matriz[3][-1] = sum(matriz[3][:-1])

print(matriz)
[[1, 1, 1, 3], [2, 2, 2, 6], [3, 3, 3, 9], [4, 4, 4, 12]]

6) Al realizar una consulta en un registro hemos obtenido una cadena de texto corrupta al revés. Al parecer contiene el nombre
de un alumno y la nota de un exámen. ¿Cómo podríamos formatear la cadena y conseguir una estructura como la siguiente?:

Nombre Apellido ha sacado un Nota de nota.

Ayuda: Para voltear una cadena rápidamente utilizando slicing podemos utilizar un tercer índice -1: cadena[::-1]

cadena = "zeréP nauJ,01"

# Completa el ejercicio aquí


cadena_volteada = cadena[::-1]
print(cadena_volteada[3:], "ha sacado un", cadena_volteada[:2], "de nota.")

Juan Pérez ha sacado un 10 de nota.


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
El tipo lógico
Representa la mínima expresión racional:

Verdadero (True)
Falso (False)

1+1 == 3

False

1+1 == 2

True

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Los operadores relacionales
Sirven para comparar dos valores, dependiendo del resultado de la comparación pueden devolver:

Verdadero (True), si es cierta


Falso (False), si no es cierta

# Igual que
3 == 2

False

# Distinto de
3 != 2

True

# Mayor que
3 > 2

True

# Menor que
3 < 2

False

# Mayor o igual que


3 >= 4

False

# Menor o igual que


3 <= 4

True

También podemos comparar variables

a = 10
b = 5
a > b

True

b != a

True

a == b*2

True

Y otros tipos, como cadenas, listas, el resultado de algunas funciones o los propios tipos lógicos

"Hola" == "Hola"

True

"Hola" != "Hola"

False

c = "Hola"
c[0] == "H"

True

c[-1] == "a"

True

l1 = [0,1,2]
l2 = [2,3,4]
l1 == l2
False

len(l1) == len(l2)

True

l1[-1] == l2[0]

True

True == True

True

False == True

False

False != True

True

True > False

True

False > True

False

La representación aritmética de True y False equivale a 1 y 0 respectivamente

True * 3

False / 5

0.0

True * False

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Operadores lógicos
Encontramos 3 básicos:

Not
And
Or

Not - Negación lógica


not True

False

not True == False

True

And - Conjunción lógica (agrupa uniendo)


True and True

True

True and False

False

False and True

False

False and False

False

a = 45
a > 10 and a < 20

False

c = "Hola Mundo"
len(c) >= 20 and c[0] == "H"

False

Or - Disyunción lógica (agrupa separando)


True or True

True

True or False

True

False or True

True

False or False

False

c = "OTRA COSA"
c == "EXIT" or c == "FIN" or c == "SALIR"

False

c = "Lector"
c[0] == "H" or c[0] == "h"
False

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Expresiones anidadas
Se pueden solucionar empleando las reglas de precedencia:

1. Primero los paréntesis porque tienen prioridad


2. Segundo, las expresiones aritméticas por sus propias reglas
3. Tercero, las expresiones relacionales
4. Cuarto, las expresiones lógicas

a = 10
b = 5
a * b - 2**b >= 20 and not (a % b) != 0

False

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Operadores de asignación
Actúan directamente sobre la variable actual modificando su valor.

a = 0

a += 5 # suma en asignación
a

10

a -= 10 # resta en asignación
a

a = 5
a *= 2 # producto en asignación
a

10

a /= 2 # división en asignación
a

5.0

a %= 2 # módulo en asignación

1.0

a **= 10 # potencia en asignación

1.0

a = 5
a **= 5
a

3125

Repasando nuestro ejemplo de cabecera


¿Qué expresiones eres capaz de identificar?

n = 0 # Asignación de 0 en n
while n < 10: # Expresión relacional n < 10, que devuelve True
if (n % 2) == 0: # Expresión aritmética y expresión relacional
print(n,'es un número par')
else:
print(n,'es un número impar')
n += 1 # expresión aritmética n = n + 1 equivalente a operación en asignación n+=1
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Tema 02: Operadores y expresiones (Soluciones)
Nota: Estos ejercicios son optativos para hacer al final de la unidad y están pensados para apoyar tu aprendizaje.

1) Realiza un programa que lea 2 números por teclado y determine los siguientes aspectos (es suficiene con mostrar True o
False):

Si los dos números son iguales


Si los dos números son diferentes
Si el primero es mayor que el segundo
Si el segundo es mayor o igual que el primero

# Completa el ejercicio aquí


n1 = float( input("Introduce el primer número: ") )
n2 = float( input("Introduce el segundo número: ") )

print("¿Son iguales? ", n1 == n2)


print("¿Son diferentes?", n1 != n2)
print("¿El primero es mayor que el segundo?", n1 > n2)
print("¿El segundo es mayor o igual que el primero?", n1 <= n2)

Introduce el primer número: 10


Introduce el segundo número: 10
¿Son iguales? True
¿Son diferentes? False
¿El primero es mayor que el segundo? False
¿El segundo es mayor o igual que el primero? True

2) Utilizando operadores lógicos, determina si una cadena de texto introducida por el usuario tiene una longitud mayor o igual
que 3 y a su vez es menor que 10 (es suficiene con mostrar True o False):

# Completa el ejercicio aquí


cadena = input("Escribe una cadena: ")
print("¿La longitud de la cadena es mayor o igual que 3 y menor que 10?", len(cadena) >= 3 and len(cadena) < 10

Escribe una cadena: hola amigos que tal


¿La longitud de la cadena es mayor o igual que 3 y menor que 10? False

3) Realiza un programa que cumpla el siguiente algoritmo utilizando siempre que sea posible operadores en asignación:

Guarda en una variable numero_magico el valor 12345679 (sin el 8)


Lee por pantalla otro numero_usuario, especifica que sea entre 1 y 9 (asegúrate que sea un número entero)
Multiplica el numero_usuario por 9 en sí mismo
Multiplica el numero_magico por el numero_usuario en sí mismo
Finalmente muestra el valor final del numero_magico por pantalla

# Completa el ejercicio aquí


numero_magico = 12345679
numero_usuario = int(input("Introduce un número del 1 al 9: "))
numero_usuario *= 9
numero_magico *= numero_usuario
print("El número mágico es:", numero_magico)

Introduce un número del 1 al 9: 5


El número mágico es: 555555555
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Condiciones
Sentencia If (Si)
Permite dividir el flujo de un programa en diferentes caminos. El if se ejecuta siempre que la expresión que comprueba devuelva True

if True: # equivale a if not False


print("Se cumple la condición")
print("También se muestre este print")

Se cumple la condición
También se muestre este print

Podemos encadenas diferentes If

a = 5
if a == 2:
print("a vale 2")
if a == 5:
print("a vale 5")

a vale 5

O también anidar If dentro de If

a = 5
b = 10
if a == 5:
print("a vale",a)
if b == 10:
print("y b vale",b)

a vale 5
y b vale 10

Como condición podemos evaluar múltiples expresiones, siempre que éstas devuelvan True o False

if a==5 and b == 10:


print("a vale 5 y b vale 10")

a vale 5 y b vale 10

Sentencia Else (Sino)


Se encadena a un If para comprobar el caso contrario (en el que no se cumple la condición).

n = 11
if n % 2 == 0:
print(n,"es un número par")
else:
print(n,"es un número impar")

11 es un número impar

Sentencia Elif (Sino Si)


Se encadena a un if u otro elif para comprobar múltiples condiciones, siempre que las anteriores no se ejecuten.

comando = "OTRA COSA"


if comando == "ENTRAR":
print("Bienvenido al sistema")
elif comando == "SALUDAR":
print("Hola, espero que te lo estés pasando bien aprendiendo Python")
elif comando == "SALIR":
print("Saliendo del sistema...")
else:
print("Este comando no se reconoce")

Este comando no se reconoce

nota = float(input("Introduce una nota: "))


if nota >= 9:
print("Sobresaliente")
elif nota >= 7:
print("Notable")
elif nota >= 6:
print("Bien")
elif nota >= 5:
print("Suficiente")
else:
print("Insuficiente")

Introduce una nota: 10


Sobresaliente

Es posible simular el funcionamiento de elif con if utilizando expresiones condicionales

nota = float(input("Introduce una nota: "))


if nota >= 9:
print("Sobresaliente")
if nota >= 7 and nota < 9:
print("Notable")
if nota >= 6 and nota < 7:
print("Bien")
if nota >= 5 and nota < 6:
print("Suficiente")
if nota < 5:
print("Insuficiente")

Introduce una nota: 8


Notable

Instrucción Pass
Sirve para finalizar un bloque, se puede utilizar en un bloque vacío.

if True:
pass
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Condiciones
Sentencia If (Si)
Permite dividir el flujo de un programa en diferentes caminos. El if se ejecuta siempre que la expresión que comprueba devuelva True

if True: # equivale a if not False


print("Se cumple la condición")
print("También se muestre este print")

Se cumple la condición
También se muestre este print

Podemos encadenas diferentes If

a = 5
if a == 2:
print("a vale 2")
if a == 5:
print("a vale 5")

a vale 5

O también anidar If dentro de If

a = 5
b = 10
if a == 5:
print("a vale",a)
if b == 10:
print("y b vale",b)

a vale 5
y b vale 10

Como condición podemos evaluar múltiples expresiones, siempre que éstas devuelvan True o False

if a==5 and b == 10:


print("a vale 5 y b vale 10")

a vale 5 y b vale 10

Sentencia Else (Sino)


Se encadena a un If para comprobar el caso contrario (en el que no se cumple la condición).

n = 11
if n % 2 == 0:
print(n,"es un número par")
else:
print(n,"es un número impar")

11 es un número impar

Sentencia Elif (Sino Si)


Se encadena a un if u otro elif para comprobar múltiples condiciones, siempre que las anteriores no se ejecuten.

comando = "OTRA COSA"


if comando == "ENTRAR":
print("Bienvenido al sistema")
elif comando == "SALUDAR":
print("Hola, espero que te lo estés pasando bien aprendiendo Python")
elif comando == "SALIR":
print("Saliendo del sistema...")
else:
print("Este comando no se reconoce")

Este comando no se reconoce

nota = float(input("Introduce una nota: "))


if nota >= 9:
print("Sobresaliente")
elif nota >= 7:
print("Notable")
elif nota >= 6:
print("Bien")
elif nota >= 5:
print("Suficiente")
else:
print("Insuficiente")

Introduce una nota: 10


Sobresaliente

Es posible simular el funcionamiento de elif con if utilizando expresiones condicionales

nota = float(input("Introduce una nota: "))


if nota >= 9:
print("Sobresaliente")
if nota >= 7 and nota < 9:
print("Notable")
if nota >= 6 and nota < 7:
print("Bien")
if nota >= 5 and nota < 6:
print("Suficiente")
if nota < 5:
print("Insuficiente")

Introduce una nota: 8


Notable

Instrucción Pass
Sirve para finalizar un bloque, se puede utilizar en un bloque vacío.

if True:
pass
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Iteraciones
Iterar significa realizar una acción varias veces. Cada vez que se repite se denomina iteración.

Sentencia While (Mientras)


Se basa en repetir un bloque a partir de evaluar una condición lógica, siempre que ésta sea True.

Queda en las manos del programador decidir el momento en que la condición cambie a False para hacer que el While finalice.

c = 0
while c <= 5:
c+=1
print("c vale",c)

c vale 1
c vale 2
c vale 3
c vale 4
c vale 5
c vale 6

Sentencia Else en bucle While


Se encadena al While para ejecutar un bloque de código una vez la condición ya no devuelve True (normalmente al final).

c = 0
while c <= 5:
c+=1
print("c vale",c)
else:
print("Se ha completado toda la iteración y c vale",c)

c vale 1
c vale 2
c vale 3
c vale 4
c vale 5
c vale 6
Se ha completado toda la iteración y c vale 6

Instrucción Break
Sirve para "romper" la ejecución del While en cualquier momento. No se ejecutará el Else, ya que éste sólo se llama al finalizar la
iteración.

c = 0
while c <= 5:
c+=1
if (c==4):
print("Rompemos el bucle cuando c vale",c)
break
print("c vale",c)
else:
print("Se ha completado toda la iteración y c vale",c)

c vale 1
c vale 2
c vale 3
Rompemos el bucle cuando c vale 4

Instrucción Continue
Sirve para "saltarse" la iteración actual sin romper el bucle.

c = 0
while c <= 5:
c+=1
if c==3 or c==4:
# print("Continuamos con la siguiente iteración",c)
continue
print("c vale",c)
else:
print("Se ha completado toda la iteración y c vale",c)
c vale 1
c vale 2
c vale 5
c vale 6
Se ha completado toda la iteración y c vale 6

Creando un menú interactivo


print("Bienvenido al menú interactivo")
while(True):
print("""¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir""")
opcion = input()
if opcion == '1':
print("Hola, espero que te lo estés pasando bien")
elif opcion == '2':
n1 = float(input("Introduce el primer número: "))
n2 = float(input("Introduce el segundo número: "))
print("El resultado de la suma es: ",n1+n2)
elif opcion =='3':
print("¡Hasta luego! Ha sido un placer ayudarte")
break
else:
print("Comando desconocido, vuelve a intentarlo")

Bienvenido al menú interactivo


¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir
1
Hola, espero que te lo estés pasando bien
¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir
2
Introduce el primer número: 10
Introduce el segundo número: 5
El resultado de la suma es: 15.0
¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir
kdjsk
Comando desconocido, vuelve a intentarlo
¿Qué quieres hacer? Escribe una opción
1) Saludar
2) Sumar dos números
3) Salir
3
¡Hasta luego! Ha sido un placer ayudarte
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Recorriendo los elementos de una lista utilizando While
numeros = [1,2,3,4,5,6,7,8,9,10]
indice = 0
while indice < len(numeros):
print(numeros[indice])
indice+=1

1
2
3
4
5
6
7
8
9
10

Sentencia For (Para) con listas


for numero in numeros: # Para [variable] en [lista]
print(numero)

1
2
3
4
5
6
7
8
9
10

Modificar ítems de la lista al vuelo


Para asignar un nuevo valor a los elementos de una lista mientras la recorremos, podríamos intentar asignar al número el nuevo valor:

for numero in numeros:


numero *= 10

numeros

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Sin embargo, ésto no funciona. La forma correcta de hacerlo es haciendo referencia al índice de la lista en lugar
de la variable:

indice = 0
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for numero in numeros:
numeros[indice] *= 10
indice+=1
numeros

[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

Podemos utilizar la función enumerate() para conseguir el índice y el valor en cada iteración fácilmente:

numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


for indice,numero in enumerate(numeros):
numeros[indice] *= 10
numeros

[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

For con cadenas


cadena = "Hola amigos"
for caracter in cadena:
print(caracter)
H
o
l
a

a
m
i
g
o
s

Pero debemos recordar que las cadenas son inmutables:

for i,c in enumerate(cadena):


cadena[i] = "*"

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-9-8ba888c46579> in <module>()
1 for i,c in enumerate(cadena):
----> 2 cadena[i] = "*"

TypeError: 'str' object does not support item assignment

Sin embargo siempre podemos generar una nueva cadena:

cadena2 = ""
for caracter in cadena:
cadena2 += caracter * 2

cadena

'Hola amigos'

cadena2

'HHoollaa aammiiggooss'

La función range()
Sirve para generar una lista de números que podemos recorrer fácilmente, pero no ocupa memoria porque se interpreta sobre la
marcha:

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

0
1
2
3
4
5
6
7
8
9

range(10)

range(0, 10)

for i in [0,1,2,3,4,5,6,7,9]:
print(i)

0
1
2
3
4
5
6
7
9

Si queremos conseguir la lista literal podemos transformar el range a una lista:

list(range(10))

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ejemplo de cabecera
n = 0
while n < 10:
if (n % 2) == 0:
print(n,'es un número par')
else:
print(n,'es un número impar')
n = n + 1
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Tema 03: Control de flujo (Soluciones)
Nota: Estos ejercicios son optativos para hacer al final de la unidad y están pensados para apoyar tu aprendizaje.

1) Realiza un programa que lea dos números por teclado y permita elegir entre 3 opciones en un menú:

Mostrar una suma de los dos números


Mostrar una resta de los dos números (el primero menos el segundo)
Mostrar una multiplicación de los dos números
En caso de no introducir una opción válida, el programa informará de que no es correcta.

# Completa el ejercicio aquí


n1 = float(input("Introduce un número: ") )
n2 = float(input("Introduce otro número: ") )
opcion = 0

print("¿Qué quieres hacer? \n1) Sumar los dos números\n2) Restar los dos números\n3) Multiplicar los dos números"
opcion = int(input("Introduce un número: ") )

if opcion == 1:
print("La suma de",n1,"+",n2,"es",n1+n2)
elif opcion == 2:
print("La resta de",n1,"-",n2,"es",n1-n2)
elif opcion == 3:
print("El producto de",n1,"*",n2,"es",n1*n2)
else:
print("Opción incorrecta")

Introduce un número: 5
Introduce otro número: 5
¿Qué quieres hacer?
1) Sumar los dos números
2) Restar los dos números
3) Multiplicar los dos números
Introduce un número: 3
El producto de 5.0 * 5.0 es 25.0

2) Realiza un programa que lea un número impar por teclado. Si el usuario no introduce un número impar, debe repetise el
proceso hasta que lo introduzca correctamente.

# Completa el ejercicio aquí


numero = 0
while numero % 2 == 0: # Mientras sea par repetimos el proceso
numero = int(input("Introduce un número impar: ") )

Introduce un número impar: 4


Introduce un número impar: 2
Introduce un número impar: 3

3) Realiza un programa que sume todos los números pares desde el 0 hasta el 100:

Sugerencia: Puedes utilizar la funciones sum() y range() para hacerlo más fácil. El tercer parámetro en la función range(inicio, fin, salto)
indica un salto de números, pruébalo.

# Completa el ejercicio aquí


# range(0, 101, 2)

suma = sum( range(0, 101, 2) )


print(suma)

# Segunda forma con bucles


num = 0
suma = 0

while num <= 100:


if num % 2 == 0:
suma += num
num += 1

print(suma)

2550
2550

4) Realiza un programa que pida al usuario cuantos números quiere introducir. Luego lee todos los números y realiza una
media aritmética:

# Completa el ejercicio aquí


suma = 0
numeros = int(input("¿Cuántos números quieres introducir? ") )
for x in range(numeros):
suma += float(input("Introduce un número: ") )
print("Se han introducido",numeros,"números que en total han sumado",suma,"y la media es",suma/numeros)

¿Cuántos numeros quieres introducir? 4


Introduce un número: 3
Introduce un número: 2
Introduce un número: 4
Introduce un número: 6
Se han introducido 4 números que en total han sumado 15.0 y la media es 3.75

5) Realiza un programa que pida al usuario un número entero del 0 al 9, y que mientras el número no sea correcto se repita el
proceso. Luego debe comprobar si el número se encuentra en la lista de números y notificarlo:

Consejo: La sintaxis "valor in lista" permite comprobar fácilmente si un valor se encuentra en una lista (devuelve True o False)

# Completa el ejercicio aquí


numeros = [1, 3, 6, 9]

while True:
numero = int(input("Escribe un número del 0 al 9: "))
if numero >= 0 and numero <= 9:
break
if numero in numeros:
print("El número",numero,"se encuentra en la lista",numeros)
else:
print("El número",numero,"no se encuentra en la lista",numeros)

Escribe un número del 0 al 9: 3


El número 3 se encuentra en la lista [1, 3, 6, 9]

6) Utilizando la función range() y la conversión a listas genera las siguientes listas dinámicamente:

Todos los números del 0 al 10 [0, 1, 2, ..., 10]


Todos los números del -10 al 0 [-10, -9, -8, ..., 0]
Todos los números pares del 0 al 20 [0, 2, 4, ..., 20]
Todos los números impares entre -20 y 0 [-19, -17, -15, ..., -1]
Todos los números múltiples de 5 del 0 al 50 [0, 5, 10, ..., 50]

Pista: Utiliza el tercer parámetro de la función range(inicio, fin, salto).

# Completa el ejercicio aquí


print( list( range( 0, 11 ) ) )
print( list( range( -10, 1 ) ) )
print( list( range( 0, 21, 2 ) ) )
print( list( range( -19, 0, 2 ) ) )
print( list( range( 0, 51, 5 ) ) )

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[-19, -17, -15, -13, -11, -9, -7, -5, -3, -1]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]

7) Dadas dos listas, debes generar una tercera con todos los elementos que se repitan en ellas, pero no debe repetise ningún
elemento en la nueva lista:

# Completa el ejercicio aquí


lista_1 = ["h",'o','l','a',' ', 'm','u','n','d','o']
lista_2 = ["h",'o','l','a',' ', 'l','u','n','a']

lista_3 = []

for letra in lista_1:


if letra in lista_2 and letra not in lista_3:
lista_3.append(letra)

print(lista_3)

['h', 'o', 'l', 'a', ' ', 'u', 'n']


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ejemplo de implementación con Programación Estructurada
clientes= [
{'Nombre': 'Hector', 'Apellidos':'Costa Guzman', 'dni':'11111111A'},
{'Nombre': 'Juan', 'Apellidos':'González Márquez', 'dni':'22222222B'}
]

clientes

[{'Apellidos': 'Costa Guzman', 'Nombre': 'Hector', 'dni': '11111111A'},


{'Apellidos': 'González Márquez', 'Nombre': 'Juan', 'dni': '22222222B'}]

def mostrar_cliente(clientes, dni):


for c in clientes:
if (dni == c['dni']):
print('{} {}'.format(c['Nombre'],c['Apellidos']))
return

print('Cliente no encontrado')

mostrar_cliente(clientes, '11111111A')

Hector Costa Guzman

mostrar_cliente(clientes, '11111111Z')

Cliente no encontrado

def borrar_cliente(clientes, dni):


for i,c in enumerate(clientes):
if (dni == c['dni']):
del( clientes[i] )
print(str(c),"> BORRADO")
return

print('Cliente no encontrado')

borrar_cliente(clientes, '22222222V')

Cliente no encontrado

borrar_cliente(clientes, '22222222B')

{'Apellidos': 'González Márquez', 'Nombre': 'Juan', 'dni': '22222222B'} > BORRADO

clientes

[{'Apellidos': 'Costa Guzman', 'Nombre': 'Hector', 'dni': '11111111A'}]

Espacio en blanco a propósito


Ejemplo de implementación con Programación Orientada a Objetos
No hace falta entender el código, lo aprenderemos en esta unidad.

class Cliente:

def __init__(self, dni, nombre, apellidos):


self.dni = dni
self.nombre = nombre
self.apellidos = apellidos

def __str__(self):
return '{} {}'.format(self.nombre,self.apellidos)

class Empresa:

def __init__(self, clientes=[]):


self.clientes = clientes

def mostrar_cliente(self, dni=None):


for c in self.clientes:
if c.dni == dni:
print(c)
return
print("Cliente no encontrado")

def borrar_cliente(self, dni=None):


for i,c in enumerate(self.clientes):
if c.dni == dni:
del(self.clientes[i])
print(str(c),"> BORRADO")
return
print("Cliente no encontrado")

hector = Cliente(nombre="Hector", apellidos="Costa Guzman", dni="11111111A")

hector

<__main__.Cliente at 0x4b97150>

juan = Cliente("22222222B", "Juan", "Gonzalez Marquez")

empresa = Empresa(clientes=[hector, juan])

empresa.clientes

[<__main__.Cliente at 0x4b97150>, <__main__.Cliente at 0x4b97890>]

empresa.mostrar_cliente("11111111A")

Hector Costa Guzman

empresa.borrar_cliente("22222222B")

Juan Gonzalez Marquez > BORRADO

empresa.clientes
[<__main__.Cliente at 0x4b97150>]

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
La clase es como un molde para crear objetos
Definición de clase
class Galleta:
pass

Instanciación
una_galleta = Galleta()
otra_galleta = Galleta()

Función type()
Sirve para determinar la clase de un objeto.

type(una_galleta)

__main__.Galleta

type(10)

int

type(3.14)

float

type("Hola")

str

type([])

list

type({})

dict

type(True)

bool

def hola():
pass

type(hola)

function

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Atributos y métodos de clase
Atributos: Hacen referencia a las variables internas de la clase.
Métodos: Hacen referencia a las funciones internas de la clase.

class Galleta:
pass

una_galleta = Galleta()

Definición de atributos dinámicos en los objetos

una_galleta.sabor = "Salado"

una_galleta.color = "Marrón"

print("El sabor de esta galleta es",una_galleta.sabor)

El sabor de esta galleta es Salado

Definición de atributos en la clase

class Galleta:
chocolate = False

g = Galleta()
g.chocolate

False

g.chocolate = True
g.chocolate

True

Método init()
Se llama automáticamente al crear una instancia de clase.

class Galleta():
chocolate = False
def __init__(self):
print("Se acaba de crear una galleta.")
g = Galleta()

Se acaba de crear una galleta.

Métodos y la palabra self


Self sirve para hacer referencia a los métodos y atributos base de una clase dentro de sus propios métodos.

class Galleta():
chocolate = False

def __init__(self):
print("Se acaba de crear una galleta.")

def chocolatear(self):
self.chocolate = True

def tiene_chocolate(self):
if (self.chocolate):
print("Soy una galleta chocolateada :-D")
else:
print("Soy una galleta sin chocolate :-(")

g = Galleta()
g.tiene_chocolate()
g.chocolatear()
g.tiene_chocolate()

Se acaba de crear una galleta.


Soy una galleta sin chocolate :-(
Soy una galleta chocolateada :-D

Parámetros en el init (argumentos al instanciar)


class Galleta():
class Galleta():
chocolate = False

def __init__(self, sabor, forma):


self.sabor = sabor
self.forma = forma
print("Se acaba de crear una galleta {} y {}".format(sabor,forma))

def chocolatear(self):
self.chocolate = True

def tiene_chocolate(self):
if (self.chocolate):
print("Soy una galleta chocolateada :-D")
else:
print("Soy una galleta sin chocolate :-(")

g = Galleta("salada","cuadrada")

Se acaba de crear una galleta salada y cuadrada

Parámetros con valores por defecto en el init()


g = Galleta()

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-20-bfd19da23fef> in <module>()
----> 1 g = Galleta()

TypeError: __init__() missing 2 required positional arguments: 'sabor' and 'color'

class Galleta():
chocolate = False

def __init__(self, sabor=None, forma=None):


self.sabor = sabor
self.forma = forma
if sabor is not None and forma is not None:
print("Se acaba de crear una galleta {} y {}".format(sabor,forma))

def chocolatear(self):
self.chocolate = True

def tiene_chocolate(self):
if (self.chocolate):
print("Soy una galleta chocolateada :-D")
else:
print("Soy una galleta sin chocolate :-(")

g = Galleta("salada","cuadrada")

Se acaba de crear una galleta salada y cuadrada


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Métodos especiales de clase
Constructor y destructor
class Pelicula:
# Constructor de clase (al crear la instancia)
def __init__(self,titulo,duracion,lanzamiento):
self.titulo = titulo
self.duracion = duracion
self.lanzamiento = lanzamiento
print("Se ha creado la película",self.titulo)

# Destructor de clase (al borrar la instancia)


def __del__(self):
print("Se está borrando la película", self.titulo)

p = Pelicula("El Padrino",175,1972)

Se ha creado la película El Padrino

Al reinstanciar la misma variable se crea de nuevo y se borra la anterior

p = Pelicula("El Padrino",175,1972)

Se ha creado la película El Padrino


Se está borrando la película El Padrino

String
Para devolver una cadena por defecto al convertir un objeto a una cadena con str(objeto):

<__main__.Pelicula at 0x20bb8318fd0>

str(10)

'10'

str(p)

'<__main__.Pelicula object at 0x0000020BB8318FD0>'

class Pelicula:
# Constructor de clase
def __init__(self,titulo,duracion,lanzamiento):
self.titulo = titulo
self.duracion = duracion
self.lanzamiento = lanzamiento
print("Se ha creado la película",self.titulo)

# Destructor de clase
def __del__(self):
print("Se está borrando la película", self.titulo)

# Redefinimos el método string


def __str__(self):
return "{} lanzada en {} con una duración de {} minutos".format(self.titulo,self.lanzamiento,self.duracion

p = Pelicula("El Padrino",175,1972)

Se ha creado la película El Padrino

str(p)

'El Padrino lanzada en 1972 con una duración de 175 minutos'

Length
Para devolver un número que simula la longitud del objeto len(objeto):

len(p)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-16-503cac95e140> in <module>()
----> 1 len(p)

TypeError: object of type 'Pelicula' has no len()

class Pelicula:
# Constructor de clase
def __init__(self,titulo,duracion,lanzamiento):
self.titulo = titulo
self.duracion = duracion
self.lanzamiento = lanzamiento
print("Se ha creado la película",self.titulo)

# Destructor de clase
def __del__(self):
print("Se está borrando la película", self.titulo)

# Redefinimos el método string


def __str__(self):
return "{} lanzada en {} con una duración de {} minutos".format(self.titulo,self.lanzamiento,self.duracion

# Redefinimos el método length


def __len__(self):
return self.duracion

p = Pelicula("El Padrino",175,1972)
len(p)

Se ha creado la película El Padrino


Se está borrando la película El Padrino
175

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
class Pelicula:

# Constructor de clase
def __init__(self, titulo, duracion, lanzamiento):
self.titulo = titulo
self.duracion = duracion
self.lanzamiento = lanzamiento
print('Se ha creado la película:',self.titulo)

def __str__(self):
return '{} ({})'.format(self.titulo, self.lanzamiento)

Creando un catálogo de películas


class Catalogo:

peliculas = [] # Esta lista contendrá objetos de la clase Pelicula

def __init__(self,peliculas=[]):
self.peliculas = peliculas

def agregar(self,p): # p será un objeto Pelicula


self.peliculas.append(p)

def mostrar(self):
for p in self.peliculas:
print(p) # Print toma por defecto str(p)

p = Pelicula("El Padrino",175,1972)
c = Catalogo([p])

Se ha creado la película: El Padrino

c.mostrar()

El Padrino (1972)

c.agregar(Pelicula("El Padrino: Parte 2",202,1974)) # Añadimos una película directamente

Se ha creado la película: El Padrino: Parte 2

c.mostrar()

El Padrino (1972)
El Padrino: Parte 2 (1974)
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Encapsulación
Consiste en denegar el acceso a los atributos y métodos internos de la clase desde el exterior.

En Python no existe, pero se puede simular precediendo atributos y métodos con dos barras bajas __:

class Ejemplo:
__atributo_privado = "Soy un atributo inalcanzable desde fuera"

def __metodo_privado(self):
print("Soy un método inalcanzable desde fuera")

e = Ejemplo()

e.__atributo_privado

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-73328dd71c23> in <module>()
----> 1 e.__atributo_privado

AttributeError: 'Ejemplo' object has no attribute '__atributo_privado'

e.__metodo_privado()

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-164c67db4a9b> in <module>()
----> 1 e.__metodo_privado()

AttributeError: 'Ejemplo' object has no attribute '__metodo_privado'

Cómo acceder
Internamente la clase sí puede acceder a sus atributos y métodos encapsulados, el truco consiste en crear sus equivalentes "publicos":

class Ejemplo:
__atributo_privado = "Soy un atributo inalcanzable desde fuera"

def __metodo_privado(self):
print("Soy un método inalcanzable desde fuera")

def atributo_publico(self):
return self.__atributo_privado

def metodo_publico(self):
return self.__metodo_privado()

e = Ejemplo()

e.atributo_publico()

'Soy un atributo inalcanzable desde fuera'

e.metodo_publico()

Soy un método inalcanzable desde fuera


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Estructura para los productos de una tienda

Ejemplo sin herencia


class Producto:
def __init__(self,referencia,tipo,nombre,pvp,descripcion,productor=None,distribuidor=None,isbn=None,autor=None
self.referencia = referencia
self.tipo = tipo
self.nombre = nombre
self.pvp = pvp
self.descripcion = descripcion
self.productor = productor
self.distribuidor = distribuidor
self.isbn = isbn
self.autor = autor

adorno = Producto('000A','ADORNO','Vaso Adornado',15,'Vaso de porcelana con dibujos')

adorno

<__main__.Producto at 0x5243810>

adorno.tipo

'ADORNO'

Creando una jerarquía de productos con clases


Superclase Producto

class Producto:
def __init__(self,referencia,nombre,pvp,descripcion):
self.referencia = referencia
self.nombre = nombre
self.pvp = pvp
self.descripcion = descripcion

def __str__(self):
return """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion)

Subclase Adorno
class Adorno(Producto):
pass

a = Adorno(2034,"Vaso adornado",15,"Vaso de porcelana adornado con árboles")


print(a)

Subclase Alimento

class Alimento(Producto):
productor = ""
distribuidor = ""

def __str__(self):
return """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}
PRODUCTOR\t{}
DISTRIBUIDOR\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion,self.productor,self.distribuidor

al = Alimento(2035,"Botella de Aceite de Oliva Extra",5,"250 ML")


al.productor = "La Aceitera"
al.distribuidor = "Distribuciones SA"

print(al)
REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva Extra
PVP 5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA

Subclase Libro
class Libro(Producto):
isbn = ""
autor = ""

def __str__(self):
return """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}
ISBN\t\t{}
AUTOR\t\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion,self.isbn,self.autor)

li = Libro(2036,"Cocina Mediterránea",9,"Recetas sanas y buenas")


li.isbn = "0-123456-78-9"
li.autor = "Doña Juana"

print(li)

REFERENCIA 2036
NOMBRE Cocina Mediterránea
PVP 9
DESCRIPCIÓN Recetas sanas y buenas
ISBN 0-123456-78-9
AUTOR Doña Juana
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Trabajando con clases heredadas en conjunto
class Producto:
def __init__(self,referencia,nombre,pvp,descripcion):
self.referencia = referencia
self.nombre = nombre
self.pvp = pvp
self.descripcion = descripcion

def __str__(self):
return """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion)

class Adorno(Producto):
pass

class Alimento(Producto):
productor = ""
distribuidor = ""

def __str__(self):
return """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}
PRODUCTOR\t{}
DISTRIBUIDOR\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion,self.productor,self.distribuidor

class Libro(Producto):
isbn = ""
autor = ""

def __str__(self):
return """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}
ISBN\t\t{}
AUTOR\t\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion,self.isbn,self.autor)

Creamos algunos objetos

ad = Adorno(2034,"Vaso adornado",15,"Vaso de porcelana adornado con árboles")

al = Alimento(2035,"Botella de Aceite de Oliva Extra",5,"250 ML")


al.productor = "La Aceitera"
al.distribuidor = "Distribuciones SA"

li = Libro(2036,"Cocina Mediterránea",9,"Recetas sanas y buenas")


li.isbn = "0-123456-78-9"
li.autor = "Doña Juana"

Lista de productos
productos = [ad, al]

productos.append(li)

productos

[<__main__.Adorno at 0x14c58660940>,
<__main__.Alimento at 0x14c586608d0>,
<__main__.Libro at 0x14c58660978>]

Lectura secuencial de productos con un for .. in


for p in productos:
print(p,"\n")
REFERENCIA 2034
NOMBRE Vaso adornado
PVP 15
DESCRIPCIÓN Vaso de porcelana adornado con árboles

REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva Extra
PVP 5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA

REFERENCIA 2036
NOMBRE Cocina Mediterránea
PVP 9
DESCRIPCIÓN Recetas sanas y buenas
ISBN 0-123456-78-9
AUTOR Doña Juana

Podemos acceder a los atributos si son compartidos entre todos los objetos

for p in productos:
print(p.referencia, p.nombre)

2034 Vaso adornado


2035 Botella de Aceite de Oliva Extra
2036 Cocina Mediterránea

Pero si un objeto no tiene el atributo deseado, dará error:

for p in productos:
print(p.autor)

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-36e9baf5c1cc> in <module>()
1 for p in productos:
----> 2 print(p.autor)

AttributeError: 'Adorno' object has no attribute 'autor'

Tendremos que tratar cada subclase de forma distinta, gracias a la función isistance():

for p in productos:
if( isinstance(p, Adorno) ):
print(p.referencia,p.nombre)
elif( isinstance(p, Alimento) ):
print(p.referencia,p.nombre,p.productor)
elif( isinstance(p, Libro) ):
print(p.referencia,p.nombre,p.isbn)

2034 Vaso adornado


2035 Botella de Aceite de Oliva Extra La Aceitera
2036 Cocina Mediterránea 0-123456-78-9

Funciones que reciben objetos de distintas clases


Los obetos se envían por referencia a las funciones
Así que debemos tener en cuenta que cualquier cambio realizado dentro afectará al propio objeto.

def rebajar_producto(p, rebaja):


"""Rebaja un producto en porcentaje de su precio"""
p.pvp = p.pvp - (p.pvp/100 * rebaja)

rebajar_producto(al, 10)
print(al_rebajado)

REFERENCIA 2038
NOMBRE Botella de Aceite de Oliva Extra
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA

print(al)

REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva Extra
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA

Una copia de un objeto también hace referencia al objeto copiado (como un acceso directo)
Una copia de un objeto también hace referencia al objeto copiado (como un acceso directo)
copia_al = al

copia_al.referencia = 2038

print(copia_al)

REFERENCIA 2038
NOMBRE Botella de Aceite de Oliva Extra
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA

print(al)

REFERENCIA 2038
NOMBRE Botella de Aceite de Oliva Extra
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA

Esto también sucede con los tipos compuestos:

l = [1,2,3]

l2 = l[:]

l2.append(4)

[1, 2, 3, 4]

Para crear una copia 100% nueva debemos utilizar el módulo copy:
import copy

copia_ad = copy.copy(ad)

print(copia_ad)

REFERENCIA 2034
NOMBRE Vaso adornado
PVP 15
DESCRIPCIÓN Vaso de porcelana adornado con árboles

copia_ad.pvp = 25

print(copia_ad)

REFERENCIA 2034
NOMBRE Vaso adornado
PVP 25
DESCRIPCIÓN Vaso de porcelana adornado con árboles

print(ad)

REFERENCIA 2034
NOMBRE Vaso adornado
PVP 15
DESCRIPCIÓN Vaso de porcelana adornado con árboles

Polimorfismo
Se refiere a una propiedad de la herencia por la que objetos de distintas subclases pueden responder a una misma acción.

def rebajar_producto(p, rebaja):


p.pvp = p.pvp - (p.pvp/100 * rebaja)

El método rebajar_producto() es capaz de tomar objetos de distintas subclases y manipular el atributo pvp.

La acción de manipular el pvp funcionará siempre que los objetos tengan ése atributo, pero en el caso de no ser así, daría error.

La polimorfia es implícita en Python en todos los objetos, ya que todos son hijos de una superclase común llamada Object.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Herencia múltiple
Posibilidad de que una subclase herede de múltiples superclases.

El problema aparece cuando las superclases tienen atributos o métodos comunes.

En estos casos, Python dará prioridad a las clases más a la izquierda en el momento de la declaración de la subclase.

class A:
def __init__(self):
print("Soy de clase A")
def a(self):
print("Este método lo heredo de A")

class B:
def __init__(self):
print("Soy de clase B")
def b(self):
print("Este método lo heredo de B")

class C(B,A):
def c(self):
print("Este método es de C")

c = C()

Soy de clase B

c.a()

Este método lo heredo de A

c.b()

Este método lo heredo de B

c.c()

Este método es de C
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Métodos de las cadenas
upper(): Devuelve la cadena con todos sus caracteres a mayúscula
"Hola Mundo".upper()

'HOLA MUNDO'

lower(): Devuelve la cadena con todos sus caracteres a minúscula

"Hola Mundo".lower()

'hola mundo'

capitalize(): Devuelve la cadena con su primer carácter en mayúscula

"hola mundo".capitalize()

'Hola mundo'

title(): Devuelve la cadena con el primer carácter de cada palabra en mayúscula


"hola mundo".title()

'Hola Mundo'

count(): Devuelve una cuenta de las veces que aparece una subcadena en la cadena
"Hola mundo".count('mundo')

find(): Devuelve el índice en el que aparece la subcadena (-1 si no aparece)


"Hola mundo".find('mundo')

"Hola mundo".find('mundoz')

-1

rfind(): Devuelve el índice en el que aparece la subcadena, empezando por el final


"Hola mundo mundo mundo".rfind('mundo')

17

isdigit(): Devuelve True si la cadena es todo números (False en caso contrario)


c = "100"

c.isdigit()

True

isalnum(): Devuelve True si la cadena es todo números o carácteres alfabéticos

c2 = "ABC10034po"

c2.isalnum()

True

isalpha(): Devuelve True si la cadena es todo carácteres alfabéticos


c2.isalpha()
False

"Holamundo".isalpha()

True

islower(): Devuelve True si la cadena es todo minúsculas


"Hola mundo".islower()

False

isupper(): Devuelve True si la cadena es todo mayúsculas


"Hola mundo".isupper()

False

istitle(): Devuelve True si la primera letra de cada palabra es mayúscula


"Hola Mundo".istitle()

True

isspace(): Devuelve True si la cadena es todo espacios


" - ".isspace()

False

startswith(): Devuelve True si la cadena empieza con una subcadena

"Hola mundo".startswith("Mola")

False

endswith(): Devuelve True si la cadena acaba con una subcadena


"Hola mundo".endswith('mundo')

True

split(): Separa la cadena en subcadenas a partir de sus espacios y devuelve una lista
"Hola mundo mundo".split()[0]

'Hola'

Podemos indicar el carácter a partir del que se separa:

"Hola,mundo,mundo,otra,palabra".split(',')

['Hola', 'mundo', 'mundo', 'otra', 'palabra']

join(): Une todos los caracteres de una cadena utilizando un caracter de unión
",".join("Hola mundo")

'H,o,l,a, ,m,u,n,d,o'

" ".join("Hola")

'H o l a'

strip(): Borra todos los espacios por delante y detrás de una cadena y la devuelve
" Hola mundo ".strip()

'Hola mundo'

Podemos indicar el carácter a borrar:


Podemos indicar el carácter a borrar:

"-----Hola mundo---".strip('-')

'Hola mundo'

replace(): Reemplaza una subcadena de una cadena por otra y la devuelve


"Hola mundo".replace('o','0')

'H0la mund0'

Podemos indicar un límite de veces a reemplazar:

"Hola mundo mundo mundo mundo mundo".replace(' mundo','',4)

'Hola mundo'

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Métodos de las listas
append(): Añade un ítem al final de la lista
lista = [1,2,3,4,5]
lista.append(6)

clear(): Vacía todos los ítems de una lista


lista

[1, 2, 3, 4, 5, 6]

lista.clear()

lista

[]

extend(): Une una lista a otra


l1 = [1,2,3]
l2 = [4,5,6]

l1.extend(l2)

count(): Cuenta el número de veces que aparece un ítem

l1

[1, 2, 3, 4, 5, 6]

["Hola", "mundo", "mundo"].count("Hola")

index(): Devuelve el índice en el que aparece un ítem (error si no aparece)

["Hola", "mundo", "mundo"].index("mundo")

["Hola", "mundo", "mundo"].index("mundoz")

---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-1-3c3755903d17> in <module>()
----> 1 ["Hola", "mundo", "mundo"].index("mundoz")

ValueError: 'mundoz' is not in list

insert(indice, valor): Agrega un ítem a la lista en un índice específico

Primera posición (0)

l = [1,2,3]
l.insert(0,0)

print(l)

[0, 1, 2, 3]

Penúltima posición (-1)

l = [5,10,15,25]
l.insert(-1,20)

print(l)

[5, 10, 15, 20, 25]

Última posición en una lista (podemos utilizar len)


n = len(l)
l.insert(n,30)

print(l)

[5, 10, 15, 20, 25, 30]

Una posición fuera de rango (999)

l.insert(20,999)

print(l)

[5, 10, 15, 20, 25, 30, 999]

pop(): Extrae un ítem de la lista y lo borra


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

l.pop()

50

print(l)

[10, 20, 30, 40]

Podemos indicarle un índice con el elemento a sacar (0 es el primer ítem)

l.pop(0)

10

print(l)

[20, 30, 40]

remove(): Borra un ítem de la lista directamente a partir del índice


l.remove(30)

print(l)

[20, 40]

l = [20,30,30,30,40]

l.remove(30)

print(l)

[20, 30, 30, 40]

reverse(): Le da la vuelta a la lista actual


l.reverse()

print(l)

[40, 30, 30, 20]

Las cadenas no tienen el método .reverse() pero podemos simularlo haciendo unas conversiones...

"Hola mundo".reverse()

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-58-eb5308f434bf> in <module>()
----> 1 "Hola mundo".reverse()

AttributeError: 'str' object has no attribute 'reverse'

lista = list("Hola mundo")

lista

['H', 'o', 'l', 'a', ' ', 'm', 'u', 'n', 'd', 'o']

lista.reverse()
lista.reverse()

lista

['o', 'd', 'n', 'u', 'm', ' ', 'a', 'l', 'o', 'H']

cadena = "".join(lista)

cadena

'odnum aloH'

sort(): Ordena automáticamente los ítems de una lista por su valor de menor a mayor
lista = [5,-10,35,0,-65,100]

lista.sort()

lista

[-65, -10, 0, 5, 35, 100]

Podemos utilizar el argumento reverse=True para indicar que la ordene del revés

lista.sort(reverse=True)

lista

[100, 35, 5, 0, -10, -65]

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Métodos de los conjuntos
c = set()

add(): Añade un ítem a un conjunto, si ya existe no lo añade


c.add(1)

c.add(2)

c.add(3)

{1, 2, 3}

discard(): Borra un ítem de un conjunto

c.discard(1)

{2, 3}

c.add(1)

c2 = c

c2.add(4)

{1, 2, 3, 4}

copy(): Crea una copia de un conjunto


Recordad que los tipos compuestos no se pueden copiar, son como accesos directos por referencia

c2 = c.copy()

c2

{1, 2, 3, 4}

{1, 2, 3, 4}

c2.discard(4)

c2

{1, 2, 3}

{1, 2, 3, 4}

clear(): Borra todos los ítems de un conjunto


c2.clear()

c2

set()

Comparación de conjuntos
c1 = {1,2,3}
c2 = {3,4,5}
c3 = {-1,99}
c4 = {1,2,3,4,5}

isdisjoint(): Comprueba si el conjunto es disjunto de otro conjunto


Si no hay ningún elemento en común entre ellos

c1.isdisjoint(c2)

False

issubset(): Comprueba si el conjunto es subconjunto de otro conjunto


Si sus ítems se encuentran todos dentro de otro

c3.issubset(c4)

False

issuperset(): Comprueba si el conjunto es contenedor de otro subconjunto


Si contiene todos los ítems de otro

c3.issuperset(c1)

False

Métodos avanzados
Se utilizan para realizar uniones, diferencias y otras operaciones avanzadas entre conjuntos.

Suelen tener dos formas, la normal que devuelve el resultado, y otra que hace lo mismo pero actualiza el propio resultado.

c1 = {1,2,3}
c2 = {3,4,5}
c3 = {-1,99}
c4 = {1,2,3,4,5}

union(): Une un conjunto a otro y devuelve el resultado en un nuevo conjunto


c1.union(c2) == c4

True

c1.union(c2)

{1, 2, 3, 4, 5}

c1

{1, 2, 3}

c2

{3, 4, 5}

update(): Une un conjunto a otro en el propio conjunto

c1.update(c2)

c1

{1, 2, 3, 4, 5}

difference(): Encuentra los elementos no comunes entre dos conjuntos

c1 = {1,2,3}
c2 = {3,4,5}
c3 = {-1,99}
c4 = {1,2,3,4,5}

c1.difference(c2)
c1.difference(c2)

{1, 2}

difference_update(): Guarda en el conjunto los elementos no comunes entre dos conjuntos


c1.difference_update(c2)

c1

{1, 2}

intersection(): Devuelve un conjunto con los elementos comunes en dos conjuntos


c1 = {1,2,3}
c2 = {3,4,5}
c3 = {-1,99}
c4 = {1,2,3,4,5}

c1.intersection(c2)

{3}

intersection_update(): Guarda en el conjunto los elementos comunes entre dos conjuntos


c1.intersection_update(c2)
c1

{3}

symmetric_difference(): Devuelve los elementos simétricamente diferentes entre dos conjuntos


Todos los elementos que no concuerdan entre los dos conjuntos

c1 = {1,2,3}
c2 = {3,4,5}
c3 = {-1,99}
c4 = {1,2,3,4,5}

c1.symmetric_difference(c2)

{1, 2, 4, 5}

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Métodos de los diccionarios
colores = { "amarillo":"yellow", "azul":"blue", "verde":"green" }

colores['amarillo']

'yellow'

get(): Busca un elemento a partir de su clave y si no lo encuentra devuelve un valor por defecto
colores.get('negro','no se encuentra')

'no se encuentra'

'amarillo' in colores

True

keys(): Genera una lista en clave de los registros del diccionario

colores.keys()

dict_keys(['amarillo', 'azul', 'verde'])

values(): Genera una lista en valor de los registros del diccionario

colores.values()

dict_values(['yellow', 'blue', 'green'])

items(): Genera una lista en clave-valor de los registros del diccionario


colores.items()

dict_items([('amarillo', 'yellow'), ('azul', 'blue'), ('verde', 'green')])

for c in colores:
print(colores[c])

yellow
blue
green

for c,v in colores.items():


print(c,v) # clave, valor

amarillo yellow
azul blue
verde green

pop(): Extrae un registro de un diccionario a partir de su clave y lo borra, acepta valor por
defecto

colores.pop("amarillo","no se ha encontrado")

'yellow'

colores

{'azul': 'blue', 'verde': 'green'}

colores.pop("negro","no se ha encontrado")

'no se ha encontrado'

colores

{'azul': 'blue', 'verde': 'green'}

clear(): Borra todos los registros de un diccionario


colores.clear()
colores

{}

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
El módulo collections
Contador
Es una subclase de diccionario utilizada para realizar cuentas.

from collections import Counter

l = [1,2,3,4,1,2,3,1,2,1]

Counter(l)

Counter({1: 4, 2: 3, 3: 2, 4: 1})

p = "palabra"

Counter(p)

Counter({'a': 3, 'b': 1, 'l': 1, 'p': 1, 'r': 1})

animales = "gato perro canario perro canario perro"

Counter(animales)

Counter({' ': 5,
'a': 5,
'c': 2,
'e': 3,
'g': 1,
'i': 2,
'n': 2,
'o': 6,
'p': 3,
'r': 8,
't': 1})

animales.split()

['gato', 'perro', 'canario', 'perro', 'canario', 'perro']

Counter(animales.split())

Counter({'canario': 2, 'gato': 1, 'perro': 3})

c = Counter(animales.split())

c.most_common(1)

[('perro', 3)]

c.most_common(2)

[('perro', 3), ('canario', 2)]

c.most_common()

[('perro', 3), ('canario', 2), ('gato', 1)]

l = [10,20,30,40,10,20,30,10,20,10]

c = Counter(l)

c.items()

dict_items([(40, 1), (10, 4), (20, 3), (30, 2)])

c.keys()

dict_keys([40, 10, 20, 30])

c.values()

dict_values([1, 4, 3, 2])

sum(c.values())
10

list(c)

[40, 10, 20, 30]

dict(c)

{10: 4, 20: 3, 30: 2, 40: 1}

d = dict(c)

d.most_common(1)

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-30-4fdd6ba29964> in <module>()
----> 1 d.most_common(1)

AttributeError: 'dict' object has no attribute 'most_common'

set(c)

{10, 20, 30, 40}

Diccionarios con valor por defecto


Se utilizan para crear diccionarios con un valor por defecto aunque el registro no haya sido definido anteriormente.

d = {}

d['algo']

---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-38-c4e2998bb821> in <module>()
----> 1 d['algo']

KeyError: 'algo'

from collections import defaultdict

d = defaultdict(float)

d['algo']

0.0

defaultdict(float, {'algo': 0.0})

d = defaultdict(str)

d['algo']

''

defaultdict(str, {'algo': ''})

d = defaultdict(object)

d['algo']

<object at 0x1ad7f3201f0>

defaultdict(object, {'algo': <object at 0x1ad7f3201f0>})

d = defaultdict(int)

d['algo'] = 10.5

d['algo']
d['algo']

10.5

d['algomas']

defaultdict(int, {'algo': 10.5, 'algomas': 0})

n = {}

n['uno'] = 'one'

n['dos'] = 'two'

n['tres'] = 'three'

{'dos': 'two', 'tres': 'three', 'uno': 'one'}

Diccionarios ordenados
Otra subclase de diccionario que conserva el orden en que añadimos los registros.

from collections import OrderedDict

n = OrderedDict()

n['uno'] = 'one'
n['dos'] = 'two'
n['tres'] = 'three'

OrderedDict([('uno', 'one'), ('dos', 'two'), ('tres', 'three')])

n1 = {}
n1['uno'] = 'one'
n1['dos'] = 'two'

n2 = {}
n2['dos'] = 'two'
n2['uno'] = 'one'

n1 == n2

True

n1 = OrderedDict()
n1['uno'] = 'one'
n1['dos'] = 'two'

n2 = OrderedDict()
n2['dos'] = 'two'
n2['uno'] = 'one'

n1 == n2

False

Tuplas con nombres


Subclase de las tuplas utilizada para crear pequeñas estructuras inmutables, parecidas a una clase y sus objetos pero mucho más
simples.

t = (20,40,60)

t[0]

20

t[-1]
60

from collections import namedtuple

Persona = namedtuple('Persona','nombre apellido edad')

p = Persona(nombre="Hector",apellido="Costa",edad=27)

p.nombre

'Hector'

p.apellido

'Costa'

p.edad

27

Persona(nombre='Hector', apellido='Costa', edad=27)

p[0]

'Hector'

p[1]

'Costa'

p[-1]

27

p.nombre = "Hola"

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-91-5cd7aba08457> in <module>()
----> 1 p.nombre = "Hola"

AttributeError: can't set attribute


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
El módulo datetime
import datetime

El objeto datetime
dt = datetime.datetime.now() # Ahora

dt

datetime.datetime(2016, 6, 18, 21, 29, 28, 607208)

dt.year # año

2016

dt.month # mes

dt.day # día

18

dt.hour # hora

21

dt.minute # minutos

29

dt.second # segundos

28

dt.microsecond # microsegundos

607208

dt.tzinfo # zona horaria, nula por defecto

print("{}:{}:{}".format(dt.hour, dt.minute, dt.second))

21:29:28

print("{}/{}/{}".format(dt.day, dt.month, dt.year))

18/6/2016

Crear un datetime manualmente (year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
Notad que sólo son obligatorios el año, el mes y el día

dt = datetime.datetime(2000,1,1)

dt

datetime.datetime(2000, 1, 1, 0, 0)

dt.year = 3000 # Error en asignación

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-18-f655491f2afa> in <module>()
----> 1 dt.year = 3000

AttributeError: attribute 'year' of 'datetime.date' objects is not writable

dt = dt.replace(year=3000) # Asignación correcta con .replace()

dt

datetime.datetime(3000, 1, 1, 0, 0)
Formateos
Formato automático ISO (Organización Internacional de Normalización)
dt = datetime.datetime.now()

dt.isoformat()

'2016-06-18T21:37:10.303386'

Formateo munual (inglés por defecto)


https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior

dt.strftime("%A %d %B %Y %I:%M")

'Saturday 18 June 2016 09:37'

Códigos de idiomas
https://msdn.microsoft.com/es-es/es/library/cdax410z.aspx

import locale

locale.setlocale(locale.LC_ALL, 'es-ES') # Establece idioma en "es-ES" (español de España)

'es-ES'

dt.strftime("%A %d %B %Y %I:%M")

'sábado 18 junio 2016 09:37'

dt.strftime("%A %d de %B del %Y - %H:%M") # %I 12h - %H 24h

'sábado 18 de junio del 2016 - 21:37'

Sumando y restando tiempo con timedelta


dt = datetime.datetime.now()

dt

datetime.datetime(2016, 6, 18, 21, 47, 13, 504534)

t = datetime.timedelta(days=14, hours=4, seconds=1000)

dentro_de_dos_semanas = dt + t

dentro_de_dos_semanas

datetime.datetime(2016, 7, 3, 2, 3, 53, 504534)

dentro_de_dos_semanas.strftime("%A %d de %B del %Y - %H:%M")

'domingo 03 de julio del 2016 - 02:03'

hace_dos_semanas = dt - t

hace_dos_semanas.strftime("%A %d de %B del %Y - %H:%M")

'sábado 04 de junio del 2016 - 17:30'

Extra: Zonas horarias con pytz


pip3 install pytz

import pytz

pytz.all_timezones

['Africa/Abidjan',
'Africa/Accra',
'Africa/Addis_Ababa',
'Africa/Algiers',
'Africa/Asmara',
'Africa/Asmera',
'Africa/Bamako',
'Africa/Bangui',
'Africa/Banjul',
'Africa/Bissau',
'Africa/Blantyre',
'Africa/Brazzaville',
'Africa/Bujumbura',
'Africa/Cairo',
'Africa/Casablanca',
'Africa/Ceuta',
'Africa/Conakry',
'Africa/Dakar',
'Africa/Dar_es_Salaam',
'Africa/Djibouti',
'Africa/Douala',
'Africa/El_Aaiun',
'Africa/Freetown',
'Africa/Gaborone',
'Africa/Harare',
'Africa/Johannesburg',
'Africa/Juba',
'Africa/Kampala',
'Africa/Khartoum',
'Africa/Kigali',
'Africa/Kinshasa',
'Africa/Lagos',
'Africa/Libreville',
'Africa/Lome',
'Africa/Luanda',
'Africa/Lubumbashi',
'Africa/Lusaka',
'Africa/Malabo',
'Africa/Maputo',
'Africa/Maseru',
'Africa/Mbabane',
'Africa/Mogadishu',
'Africa/Monrovia',
'Africa/Nairobi',
'Africa/Ndjamena',
'Africa/Niamey',
'Africa/Nouakchott',
'Africa/Ouagadougou',
'Africa/Porto-Novo',
'Africa/Sao_Tome',
'Africa/Timbuktu',
'Africa/Tripoli',
'Africa/Tunis',
'Africa/Windhoek',
'America/Adak',
'America/Anchorage',
'America/Anguilla',
'America/Antigua',
'America/Araguaina',
'America/Argentina/Buenos_Aires',
'America/Argentina/Catamarca',
'America/Argentina/ComodRivadavia',
'America/Argentina/Cordoba',
'America/Argentina/Jujuy',
'America/Argentina/La_Rioja',
'America/Argentina/Mendoza',
'America/Argentina/Rio_Gallegos',
'America/Argentina/Salta',
'America/Argentina/San_Juan',
'America/Argentina/San_Luis',
'America/Argentina/Tucuman',
'America/Argentina/Ushuaia',
'America/Aruba',
'America/Asuncion',
'America/Atikokan',
'America/Atka',
'America/Bahia',
'America/Bahia_Banderas',
'America/Barbados',
'America/Belem',
'America/Belize',
'America/Blanc-Sablon',
'America/Boa_Vista',
'America/Bogota',
'America/Boise',
'America/Buenos_Aires',
'America/Cambridge_Bay',
'America/Campo_Grande',
'America/Cancun',
'America/Caracas',
'America/Catamarca',
'America/Cayenne',
'America/Cayman',
'America/Chicago',
'America/Chihuahua',
'America/Coral_Harbour',
'America/Cordoba',
'America/Costa_Rica',
'America/Creston',
'America/Cuiaba',
'America/Curacao',
'America/Danmarkshavn',
'America/Dawson',
'America/Dawson_Creek',
'America/Denver',
'America/Detroit',
'America/Dominica',
'America/Edmonton',
'America/Eirunepe',
'America/El_Salvador',
'America/Ensenada',
'America/Fort_Nelson',
'America/Fort_Wayne',
'America/Fortaleza',
'America/Glace_Bay',
'America/Godthab',
'America/Goose_Bay',
'America/Grand_Turk',
'America/Grenada',
'America/Guadeloupe',
'America/Guatemala',
'America/Guayaquil',
'America/Guyana',
'America/Halifax',
'America/Havana',
'America/Hermosillo',
'America/Indiana/Indianapolis',
'America/Indiana/Knox',
'America/Indiana/Marengo',
'America/Indiana/Petersburg',
'America/Indiana/Tell_City',
'America/Indiana/Vevay',
'America/Indiana/Vincennes',
'America/Indiana/Winamac',
'America/Indianapolis',
'America/Inuvik',
'America/Iqaluit',
'America/Jamaica',
'America/Jujuy',
'America/Juneau',
'America/Kentucky/Louisville',
'America/Kentucky/Monticello',
'America/Knox_IN',
'America/Kralendijk',
'America/La_Paz',
'America/Lima',
'America/Los_Angeles',
'America/Louisville',
'America/Lower_Princes',
'America/Maceio',
'America/Managua',
'America/Manaus',
'America/Marigot',
'America/Martinique',
'America/Matamoros',
'America/Mazatlan',
'America/Mendoza',
'America/Menominee',
'America/Merida',
'America/Metlakatla',
'America/Mexico_City',
'America/Miquelon',
'America/Moncton',
'America/Monterrey',
'America/Montevideo',
'America/Montreal',
'America/Montserrat',
'America/Nassau',
'America/New_York',
'America/Nipigon',
'America/Nome',
'America/Noronha',
'America/North_Dakota/Beulah',
'America/North_Dakota/Center',
'America/North_Dakota/New_Salem',
'America/Ojinaga',
'America/Panama',
'America/Pangnirtung',
'America/Paramaribo',
'America/Phoenix',
'America/Port-au-Prince',
'America/Port_of_Spain',
'America/Porto_Acre',
'America/Porto_Velho',
'America/Puerto_Rico',
'America/Rainy_River',
'America/Rankin_Inlet',
'America/Recife',
'America/Regina',
'America/Resolute',
'America/Rio_Branco',
'America/Rosario',
'America/Santa_Isabel',
'America/Santarem',
'America/Santiago',
'America/Santo_Domingo',
'America/Sao_Paulo',
'America/Scoresbysund',
'America/Shiprock',
'America/Sitka',
'America/St_Barthelemy',
'America/St_Johns',
'America/St_Kitts',
'America/St_Lucia',
'America/St_Thomas',
'America/St_Vincent',
'America/Swift_Current',
'America/Tegucigalpa',
'America/Thule',
'America/Thunder_Bay',
'America/Tijuana',
'America/Toronto',
'America/Tortola',
'America/Vancouver',
'America/Virgin',
'America/Whitehorse',
'America/Winnipeg',
'America/Yakutat',
'America/Yellowknife',
'Antarctica/Casey',
'Antarctica/Davis',
'Antarctica/DumontDUrville',
'Antarctica/Macquarie',
'Antarctica/Mawson',
'Antarctica/McMurdo',
'Antarctica/Palmer',
'Antarctica/Rothera',
'Antarctica/South_Pole',
'Antarctica/Syowa',
'Antarctica/Troll',
'Antarctica/Vostok',
'Arctic/Longyearbyen',
'Asia/Aden',
'Asia/Almaty',
'Asia/Amman',
'Asia/Anadyr',
'Asia/Aqtau',
'Asia/Aqtobe',
'Asia/Ashgabat',
'Asia/Ashkhabad',
'Asia/Baghdad',
'Asia/Bahrain',
'Asia/Baku',
'Asia/Bangkok',
'Asia/Barnaul',
'Asia/Beirut',
'Asia/Bishkek',
'Asia/Brunei',
'Asia/Calcutta',
'Asia/Chita',
'Asia/Choibalsan',
'Asia/Chongqing',
'Asia/Chungking',
'Asia/Colombo',
'Asia/Dacca',
'Asia/Damascus',
'Asia/Dhaka',
'Asia/Dili',
'Asia/Dubai',
'Asia/Dushanbe',
'Asia/Gaza',
'Asia/Harbin',
'Asia/Hebron',
'Asia/Ho_Chi_Minh',
'Asia/Hong_Kong',
'Asia/Hovd',
'Asia/Irkutsk',
'Asia/Istanbul',
'Asia/Jakarta',
'Asia/Jayapura',
'Asia/Jerusalem',
'Asia/Kabul',
'Asia/Kamchatka',
'Asia/Karachi',
'Asia/Kashgar',
'Asia/Kathmandu',
'Asia/Katmandu',
'Asia/Khandyga',
'Asia/Kolkata',
'Asia/Krasnoyarsk',
'Asia/Kuala_Lumpur',
'Asia/Kuching',
'Asia/Kuwait',
'Asia/Macao',
'Asia/Macau',
'Asia/Magadan',
'Asia/Makassar',
'Asia/Manila',
'Asia/Muscat',
'Asia/Nicosia',
'Asia/Novokuznetsk',
'Asia/Novosibirsk',
'Asia/Omsk',
'Asia/Oral',
'Asia/Phnom_Penh',
'Asia/Pontianak',
'Asia/Pyongyang',
'Asia/Qatar',
'Asia/Qyzylorda',
'Asia/Rangoon',
'Asia/Riyadh',
'Asia/Saigon',
'Asia/Sakhalin',
'Asia/Samarkand',
'Asia/Seoul',
'Asia/Shanghai',
'Asia/Singapore',
'Asia/Srednekolymsk',
'Asia/Taipei',
'Asia/Tashkent',
'Asia/Tbilisi',
'Asia/Tehran',
'Asia/Tel_Aviv',
'Asia/Thimbu',
'Asia/Thimphu',
'Asia/Tokyo',
'Asia/Tomsk',
'Asia/Ujung_Pandang',
'Asia/Ulaanbaatar',
'Asia/Ulan_Bator',
'Asia/Urumqi',
'Asia/Ust-Nera',
'Asia/Vientiane',
'Asia/Vladivostok',
'Asia/Yakutsk',
'Asia/Yekaterinburg',
'Asia/Yerevan',
'Atlantic/Azores',
'Atlantic/Bermuda',
'Atlantic/Canary',
'Atlantic/Cape_Verde',
'Atlantic/Faeroe',
'Atlantic/Faroe',
'Atlantic/Jan_Mayen',
'Atlantic/Madeira',
'Atlantic/Reykjavik',
'Atlantic/South_Georgia',
'Atlantic/St_Helena',
'Atlantic/Stanley',
'Australia/ACT',
'Australia/Adelaide',
'Australia/Brisbane',
'Australia/Broken_Hill',
'Australia/Canberra',
'Australia/Currie',
'Australia/Darwin',
'Australia/Eucla',
'Australia/Hobart',
'Australia/LHI',
'Australia/Lindeman',
'Australia/Lord_Howe',
'Australia/Melbourne',
'Australia/NSW',
'Australia/North',
'Australia/Perth',
'Australia/Queensland',
'Australia/South',
'Australia/Sydney',
'Australia/Tasmania',
'Australia/Victoria',
'Australia/West',
'Australia/Yancowinna',
'Brazil/Acre',
'Brazil/DeNoronha',
'Brazil/East',
'Brazil/West',
'CET',
'CST6CDT',
'Canada/Atlantic',
'Canada/Central',
'Canada/East-Saskatchewan',
'Canada/Eastern',
'Canada/Mountain',
'Canada/Newfoundland',
'Canada/Pacific',
'Canada/Saskatchewan',
'Canada/Yukon',
'Chile/Continental',
'Chile/EasterIsland',
'Cuba',
'EET',
'EST',
'EST5EDT',
'Egypt',
'Eire',
'Etc/GMT',
'Etc/GMT+0',
'Etc/GMT+1',
'Etc/GMT+10',
'Etc/GMT+11',
'Etc/GMT+12',
'Etc/GMT+2',
'Etc/GMT+3',
'Etc/GMT+4',
'Etc/GMT+5',
'Etc/GMT+6',
'Etc/GMT+7',
'Etc/GMT+8',
'Etc/GMT+9',
'Etc/GMT-0',
'Etc/GMT-1',
'Etc/GMT-10',
'Etc/GMT-11',
'Etc/GMT-12',
'Etc/GMT-13',
'Etc/GMT-14',
'Etc/GMT-2',
'Etc/GMT-3',
'Etc/GMT-4',
'Etc/GMT-5',
'Etc/GMT-6',
'Etc/GMT-7',
'Etc/GMT-8',
'Etc/GMT-9',
'Etc/GMT0',
'Etc/Greenwich',
'Etc/UCT',
'Etc/UTC',
'Etc/Universal',
'Etc/Zulu',
'Europe/Amsterdam',
'Europe/Andorra',
'Europe/Astrakhan',
'Europe/Athens',
'Europe/Belfast',
'Europe/Belgrade',
'Europe/Berlin',
'Europe/Bratislava',
'Europe/Brussels',
'Europe/Bucharest',
'Europe/Budapest',
'Europe/Busingen',
'Europe/Chisinau',
'Europe/Copenhagen',
'Europe/Dublin',
'Europe/Gibraltar',
'Europe/Guernsey',
'Europe/Helsinki',
'Europe/Isle_of_Man',
'Europe/Istanbul',
'Europe/Jersey',
'Europe/Kaliningrad',
'Europe/Kiev',
'Europe/Kirov',
'Europe/Lisbon',
'Europe/Ljubljana',
'Europe/London',
'Europe/Luxembourg',
'Europe/Madrid',
'Europe/Malta',
'Europe/Mariehamn',
'Europe/Minsk',
'Europe/Monaco',
'Europe/Moscow',
'Europe/Nicosia',
'Europe/Oslo',
'Europe/Paris',
'Europe/Podgorica',
'Europe/Prague',
'Europe/Riga',
'Europe/Rome',
'Europe/Samara',
'Europe/San_Marino',
'Europe/Sarajevo',
'Europe/Simferopol',
'Europe/Skopje',
'Europe/Sofia',
'Europe/Stockholm',
'Europe/Tallinn',
'Europe/Tirane',
'Europe/Tiraspol',
'Europe/Ulyanovsk',
'Europe/Uzhgorod',
'Europe/Vaduz',
'Europe/Vatican',
'Europe/Vienna',
'Europe/Vilnius',
'Europe/Volgograd',
'Europe/Warsaw',
'Europe/Zagreb',
'Europe/Zaporozhye',
'Europe/Zurich',
'GB',
'GB-Eire',
'GMT',
'GMT+0',
'GMT-0',
'GMT0',
'Greenwich',
'HST',
'Hongkong',
'Iceland',
'Indian/Antananarivo',
'Indian/Chagos',
'Indian/Christmas',
'Indian/Cocos',
'Indian/Comoro',
'Indian/Kerguelen',
'Indian/Mahe',
'Indian/Maldives',
'Indian/Mauritius',
'Indian/Mayotte',
'Indian/Reunion',
'Iran',
'Israel',
'Jamaica',
'Japan',
'Kwajalein',
'Libya',
'MET',
'MST',
'MST7MDT',
'Mexico/BajaNorte',
'Mexico/BajaSur',
'Mexico/General',
'NZ',
'NZ-CHAT',
'Navajo',
'PRC',
'PST8PDT',
'Pacific/Apia',
'Pacific/Auckland',
'Pacific/Bougainville',
'Pacific/Chatham',
'Pacific/Chuuk',
'Pacific/Easter',
'Pacific/Efate',
'Pacific/Enderbury',
'Pacific/Fakaofo',
'Pacific/Fiji',
'Pacific/Funafuti',
'Pacific/Galapagos',
'Pacific/Gambier',
'Pacific/Guadalcanal',
'Pacific/Guam',
'Pacific/Honolulu',
'Pacific/Johnston',
'Pacific/Kiritimati',
'Pacific/Kosrae',
'Pacific/Kwajalein',
'Pacific/Majuro',
'Pacific/Marquesas',
'Pacific/Midway',
'Pacific/Nauru',
'Pacific/Niue',
'Pacific/Norfolk',
'Pacific/Noumea',
'Pacific/Pago_Pago',
'Pacific/Palau',
'Pacific/Pitcairn',
'Pacific/Pohnpei',
'Pacific/Ponape',
'Pacific/Port_Moresby',
'Pacific/Rarotonga',
'Pacific/Saipan',
'Pacific/Samoa',
'Pacific/Tahiti',
'Pacific/Tarawa',
'Pacific/Tongatapu',
'Pacific/Truk',
'Pacific/Wake',
'Pacific/Wallis',
'Pacific/Yap',
'Poland',
'Portugal',
'ROC',
'ROK',
'Singapore',
'Turkey',
'UCT',
'US/Alaska',
'US/Aleutian',
'US/Arizona',
'US/Central',
'US/East-Indiana',
'US/Eastern',
'US/Hawaii',
'US/Indiana-Starke',
'US/Michigan',
'US/Mountain',
'US/Pacific',
'US/Pacific-New',
'US/Samoa',
'UTC',
'Universal',
'W-SU',
'WET',
'Zulu']

dt = datetime.datetime.now(pytz.timezone('Asia/Tokyo'))

dt.strftime("%A %d de %B del %Y - %H:%M") # %I 12h - %H 24h

'domingo 19 de junio del 2016 - 04:52'

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
El módulo math
import math

Redondeos
pi = 3.14159

round(pi) # Redondeo integrado

math.floor(pi) # Redondeo a la baja - Suelo

math.floor(3.99)

math.ceil(pi) # Redondeo al alta - Techo

math.ceil(3.01)

Valor absoluto

abs(-10)

10

Sumatorio

# Sumatorio integrado
n = [0.9999999, 1, 2, 3]
sum(n)

6.999999900000001

# Sumatorio mejorado para números reales


math.fsum(n)

6.9999999

Truncamiento

math.trunc(123.45) # Truncar, cortar la parte decimal

123

Potencias y raíces

math.pow(2, 3) # Potencia con flotante

8.0

2 ** 3 # Potencia directa

math.sqrt(9) # Raíz cuadrada (square root)

3.0

Constantes
math.pi # Constante pi
3.141592653589793

math.e # Constante e

2.718281828459045

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
El módulo random
import random

random.random() # Flotante aleatorio >= 0 y < 1.0

0.12539542779843138

random.random() # Flotante aleatorio >= 0 y < 1.0

0.3332807222427663

random.uniform(1,10) # Flotante aleatorio >= 1 y <10.0

6.272300429556777

random.randrange(10) # Entero aleatorio de 0 a 9, 10 excluído

random.randrange(0,101) # Entero aleatorio de 0 a 100

14

random.randrange(0,101,2) # Entero aleatorio de 0 a 100 cada 2 números, múltiples de 2

68

random.randrange(0,101,5) # Entero aleatorio de 0 a 100 cada 5 números, múltiples de 5

25

c = 'Hola mundo'
random.choice(c) # letra aleatoria

'o'

l = [1,2,3,4,5]
random.choice(l) # elemento aleatorio

random.shuffle(l) # barajar una lista, queda guardado

[3, 4, 2, 5, 1]

random.sample(l, 2) # muestra aleatoria de 2 elementos de la lista

[3, 4]

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Tema 11: Módulos (Soluciones)
Nota: Estos ejercicios son optativos para hacer al final de la unidad y están pensados para apoyar tu aprendizaje.

1) Crea el siguiente módulo:

El módulo se denominará operaciones y contendrá 4 funciones para realizar una suma, una resta, un producto y una division
entres dos números. Todas ellas devolverán el resultado.
En las funciones del módulo deberá de haber tratamiento e invocación manual de errores para evitar que se quede bloqueada una
funcionalidad, eso incluye:
TypeError: En caso de que se envíen valores a las funciones que no sean números. Además deberá aparecer un mensaje que
informe "Error: Tipo de dato no válido".
ZeroDivisionError: En caso de realizar una división por cero. Además deberá aparecer un mensaje que informe "Error: No es
posible dividir entre cero".

Una vez creado el módulo, crea un script calculos.py en el mismo directorio en el que deberás importar el módulo y realizar las
siguientes instrucciones. Observa si el comportamiento es el esperado:

from operaciones import *

a, b, c, d = (10, 5, 0, "Hola")

print( "{} + {} = {}".format(a, b, suma(a, b) ) )


print( "{} - {} = {}".format(b, d, resta(b, d) ) )
print( "{} * {} = {}".format(b, b, producto(b, b) ) )
print( "{} / {} = {}".format(a, c, division(a, c) ) )

operaciones.py
def suma(a,b):
try:
r = a + b
except TypeError:
print("Error: Tipo de dato no válido")
else:
return r

def resta(a,b):
try:
r = a - b
except TypeError:
print("Error: Tipo de dato no válido")
else:
return r

def producto(a,b):
try:
r = a * b
except TypeError:
print("Error: Tipo de dato no válido")
else:
return r

def division(a,b):
try:
r = a / b
except TypeError:
print("Error: Tipo de dato no válido")
except ZeroDivisionError:
print("Error: No es posible dividir entre cero")
else:
return r

calculos.py
from operaciones import *

a, b, c, d = (10, 5, 0, "Hola")
print( "{} + {} = {}".format(a, b, suma(a, b) ) )
print( "{} - {} = {}".format(b, d, resta(b, d) ) )
print( "{} * {} = {}".format(b, b, producto(b, b) ) )
print( "{} / {} = {}".format(a, c, division(a, c) ) )

10 + 5 = 15
Error: Tipo de dato no válido
5 - Hola = None
5 * 5 = 25
Error: No es posible dividir entre cero
10 / 0 = None

2) ¿Eres capaz de desarrollar un reloj de horas, minutos y segundos utilizando el módulo datetime con la hora actual? Hazlo en
un script llamado reloj.py y ejecútalo en la terminal.

Ayudas: El módulo time integra una función llamada sleep(segundos) que puede pausar la ejecución de un programa durante un
tiempo. Así mismo el módulo os integra la función system('cls') y system('clear') para limpiar la pantalla de la terminal en sistemas
Windows y Linux/Unix respecticamente.

reloj.py
import datetime
import time
import os

while(True):
os.system('cls') # Limpiamos la pantalla
dt = datetime.datetime.now()
print( "{}:{}:{}".format( dt.hour, dt.minute, dt.second ) )
time.sleep(1) # Esperar 1 segundo

3) Crea un script llamado generador.py que cumpla las siguientes necesidades:

Debe incluir una función llamada leer_numero(). Esta función tomará 3 valores: ini, fin y mensaje. El objetivo es leer por pantalla
un número >= que ini y <= que fin. Además a la hora de hacer la lectura se mostrará en el input el mensaje enviado a la función.
Finalmente se devolverá el valor. Esta función tiene que devolver un número, y tiene que repetirse hasta que el usuario no lo
escriba bien (lo que incluye cualquier valor que no sea un número del ini al fin).
Una vez la tengas creada, deberás crear una nueva función llamada generador, no recibirá ningún parámetro. Dentro leerás dos
números con la función leer_numero():
El primer numero será llamado numeros, deberá ser entre 1 y 20, ambos incluidos, y se mostrará el mensaje ¿Cuantos
números quieres generar? [1-20]:
El segundo número será llamado modo y requerirá un número entre 1 y 3, ambos incluidos. El mensaje que mostrará será:
¿Cómo quieres redondear los números? [1]Al alza [2]A la baja [3]Normal: .
Una vez sepas los números a generar y cómo redondearlos, tendrás que realizar lo siguiente:
Generarás una lista de números aleatorios decimales entre 0 y 100 con tantos números como el usuario haya indicado.
A cada uno de esos 100 números deberás redondearlos en función de lo que el usuario ha especificado en el modo.
Para cada número muestra durante el redondeo, el número normal y después del redondeo.
Finalmente devolverás la lista de números redondeados.

El objetivo de este ejercicio es practicar la reutilización de código y los módulos random y math.

Nota: Recuerda que el redondeo tradicional round() no requiere ningún módulo.

generador.py
import random
import math

# Completa el código aquí

def leer_numero(ini,fin,mensaje):
while True:
try:
valor = int( input(mensaje) )
except:
pass
else:
if valor >= ini and valor <= fin:
break
return valor

def generador():
numeros = leer_numero(1,20,"¿Cuantos números quieres generar? [1-20]: ")
modo = leer_numero(1,3,"¿Cómo quieres redondear los números? [1]Al alza [2]A la baja [3]Normal: ")

lista = []
for i in range(numeros):
numero = random.uniform(0,101)
if modo == 1:
print("{} => {}".format(numero, math.ceil(numero)) )
numero = math.ceil(numero)
elif modo == 2:
print("{} => {}".format(numero, math.floor(numero)) )
numero = math.floor(numero)
elif modo == 3:
print("{} => {}".format(numero, round(numero)) )
numero = round(numero)
lista.append(numero)

return lista

generador()

¿Cuantos números quieres generar? [1-20]: 10


¿Cómo quieres redondear los números? [1]Al alza [2]A la baja [3]Normal: 3
51.66610298393511 => 52
14.973082044487331 => 15
94.3282398833624 => 94
38.66307742306034 => 39
62.6740877833471 => 63
31.652519261323828 => 32
71.73756569490445 => 72
68.9959674989237 => 69
83.22516964165754 => 83
0.14433853347224324 => 0
[52, 15, 94, 39, 63, 32, 72, 69, 83, 0]

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ficheros de texto
Crear fichero y escribir texto
texto = "Una línea con texto\nOtra línea con texto"

fichero = open('fichero.txt','w') # fichero.txt ruta donde lo crearemos, w indica modo de escritura, write (puntero

fichero.write(texto) # escribimos el texto

40

fichero.close() # cerramos el fichero

Lectura de un fichero de texto


fichero = open('fichero.txt','r') # modo lectura read, por defecto ya es r, no es necesario

texto = fichero.read() # lectura completa

fichero.close()

print(texto)

Una línea con texto


Otra línea con texto
Otra línea más abajo del todo

fichero = open('fichero.txt','r')
texto = fichero.readlines() # leer creando una lista de líneas
fichero.close()
print(texto)

['Una línea con texto\n', 'Otra línea con texto\n', 'Otra línea más abajo del todo']

print(texto[-1]) # Última línea

Otra línea más abajo del todo

Extensión de un fichero de texto


fichero = open('fichero.txt','a') # modo a, append, añadir - extender (puntero al final)

fichero.write('\nOtra línea más abajo del todo')

fichero.close()

Lectura de un fichero no existente


fichero = open('fichero_inventado.txt','r')

---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-2-c2865d5b1523> in <module>()
----> 1 fichero = open('fichero_inventado.txt','r')

FileNotFoundError: [Errno 2] No such file or directory: 'fichero_inventado.txt'

fichero = open('fichero_inventado.txt','a+') # Extensión con escritura simultánea, crea fichero si no existe

Lectura línea a línea


fichero = open('fichero.txt','r')

fichero.readline() # Línea a línea

'Una línea con texto\n'

fichero.readline()

'Otra línea con texto'


fichero.readline()

''

fichero.close()

Lectura línea a línea secuencial


with open("fichero.txt", "r") as fichero:
for linea in fichero:
print(linea)

Una línea con texto

Otra línea con texto

Otra línea más abajo del todo

Manejando el puntero
fichero = open('fichero.txt','r')
fichero.seek(0) # Puntero al principio
fichero.read(10) # Leemos 10 carácteres

'Una línea '

fichero.read(10) # Leemos 10 carácteres más, a partir del 10 donde está el puntero

'con texto\n'

fichero.seek(0)
fichero.seek( len(fichero.readline()) ) # Leemos la primera línea y situamos el puntero al principio de la segunda

20

fichero.read() # Leemos todo lo que queda del puntero hasta el final

'\nOtra línea con texto\nOtra línea más abajo del todo'

Lectura y escritura a la vez


fichero2 = open('fichero2.txt','w')

texto = "Línea 1\nLínea 2\nLínea 3\nLínea 4"

fichero2.write(texto)

31

fichero2.close()

fichero2 = open('fichero2.txt','r+') # + escritura simultánea, puntero al principio por defecto

fichero2.write('asdfgh')

fichero2.close()

Modificar una línea específica


fichero2 = open('fichero2.txt','r+') # modo lectura con escritura, puntero al principio por defecto

texto = fichero2.readlines() # leemos todas las líneas

texto[2] = "Esta es la línea 3 modificada\n" # indice menos 1

texto

['asdfgh1\n', 'Línea 2\n', 'Esta es la línea 3 modificada\n', 'Línea 4']

fichero2.seek(0) # Ponemos el puntero al principio


fichero2.writelines(texto)
fichero2.close()
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
El módulo pickle

Guardar estructura en fichero binario


import pickle

lista = [1,2,3,4,5] # Podemos guardar lo que queramos, listas, diccionarios, tuplas...

fichero = open('lista.pckl','wb') # Escritura en modo binario, vacía el fichero si existe

pickle.dump(lista, fichero) # Escribe la estructura en el fichero

fichero.close()

Recuperar estructura de fichero binario


fichero = open('lista.pckl','rb') # Lectura en modo binario

lista_fichero = pickle.load(fichero)

print(lista_fichero)

[1, 2, 3, 4, 5]

Lógica para trabajar con objetos


1. Crear una colección
2. Introducir los objetos en la colección
3. Guardar la colección haciendo un dump
4. Recuperar la colección haciendo un load
5. Seguir trabajando con nuestros objetos

class Persona:

def __init__(self,nombre):
self.nombre = nombre

def __str__(self):
return self.nombre

nombres = ["Héctor","Mario","Marta"]
personas = []

for n in nombres:
p = Persona(n)
personas.append(p)

import pickle
f = open('personas.pckl','wb')
pickle.dump(personas, f)
f.close()

f = open('personas.pckl','rb')
personas = pickle.load(f)
f.close()
for p in personas:
print(p)

Héctor
Mario
Marta
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Práctica
Catálogo de películas con ficheros y pickle
from io import open
import pickle

class Pelicula:

# Constructor de clase
def __init__(self, titulo, duracion, lanzamiento):
self.titulo = titulo
self.duracion = duracion
self.lanzamiento = lanzamiento
print('Se ha creado la película:',self.titulo)

def __str__(self):
return '{} ({})'.format(self.titulo, self.lanzamiento)

class Catalogo:

peliculas = []

# Constructor de clase
def __init__(self):
self.cargar()

def agregar(self,p):
self.peliculas.append(p)
self.guardar()

def mostrar(self):
if len(self.peliculas) == 0:
print("El catálogo está vacío")
return
for p in self.peliculas:
print(p)

def cargar(self):
fichero = open('catalogo.pckl', 'ab+')
fichero.seek(0)
try:
self.peliculas = pickle.load(fichero)
except:
print("El fichero está vacío")
finally:
fichero.close()
del(fichero)
print("Se han cargado {} películas".format( len(self.peliculas) ))

def guardar(self):
fichero = open('catalogo.pckl', 'wb')
pickle.dump(self.peliculas, fichero)
fichero.close()
del(fichero)

# Destructor de clase
def __del__(self):
self.guardar() # guardado automático
print("Se ha guardado el fichero")

Creando un objeto catálogo


c = Catalogo()

El fichero está vacío


Se han cargado 0 películas

c.mostrar()

El catálogo está vacío

c.agregar( Pelicula("El Padrino", 175, 1972) )

Se ha creado la película: El Padrino

c.agregar( Pelicula("El Padrino: Parte 2", 202, 1974) )

Se ha creado la película: El Padrino: Parte 2

c.mostrar()
El Padrino (1972)
El Padrino: Parte 2 (1974)

del(c)

Recuperando el catálogo al crearlo de nuevo


c = Catalogo()

Se han cargado 2 películas

c.mostrar()

El Padrino (1972)
El Padrino: Parte 2 (1974)

del(c)

Se ha guardado el fichero

c = Catalogo()

Se han cargado 2 películas

c.agregar( Pelicula("Prueba", 100, 2005) )

Se ha creado la película: Prueba

c.mostrar()

El Padrino (1972)
El Padrino: Parte 2 (1974)
Prueba (2005)

del(c)

Se ha guardado el fichero

c = Catalogo()

Se han cargado 3 películas

c.mostrar()

El Padrino (1972)
El Padrino: Parte 2 (1974)
Prueba (2005)

Conclusiones
Trabajamos en memoria, no en el fichero
Nosotros decidimos cuando escribir los datos:
1. Al manipular un registro
2. Al finalizar el programa
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ficheros CSV
Valores separados por comas (comma-separated values)
Documentación: https://docs.python.org/3/library/csv.html

import csv

contactos = [
("Manuel", "Desarrollador Web", "manuel@ejemplo.com"),
("Lorena", "Gestora de proyectos", "lorena@ejemplo.com"),
("Javier", "Analista de datos", "javier@ejemplo.com"),
("Marta", "Experta en Python", "marta@ejemplo.com")
]

with open("contactos.csv", "w", newline="\n") as csvfile:


writer = csv.writer(csvfile, delimiter=",")
for contacto in contactos:
writer.writerow(contacto)

with open("contactos.csv", newline="\n") as csvfile:


reader = csv.reader(csvfile, delimiter=",")
for nombre, empleo, email in reader:
print(nombre, empleo, email)

Manuel Desarrollador Web manuel@ejemplo.com


Lorena Gestora de proyectos lorena@ejemplo.com
Javier Analista de datos javier@ejemplo.com
Marta Experta en Python marta@ejemplo.com

contactos = [
("Manuel", "Desarrollador Web", "manuel@ejemplo.com"),
("Lorena", "Gestora de proyectos", "lorena@ejemplo.com"),
("Javier", "Analista de datos", "javier@ejemplo.com"),
("Marta", "Experta en Python", "marta@ejemplo.com")
]

with open("contactos.csv", "w", newline="\n") as csvfile:


campos = ["nombre", "empleo", "email"]
writer = csv.DictWriter(csvfile, fieldnames=campos)
writer.writeheader()
for nombre, empleo, email in contactos:
writer.writerow({
"nombre": nombre, "empleo": empleo, "email": email
})

with open("contactos.csv", newline="\n") as csvfile:


reader = csv.DictReader(csvfile)
for contacto in reader:
print(contacto["nombre"], contacto["empleo"], contacto["email"])

Manuel Desarrollador Web manuel@ejemplo.com


Lorena Gestora de proyectos lorena@ejemplo.com
Javier Analista de datos javier@ejemplo.com
Marta Experta en Python marta@ejemplo.com
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ficheros JSON
Notación de objeto de JavaScript (javascript object notation)
Documentación: https://docs.python.org/3/library/json.html

import json

contactos = [
("Manuel", "Desarrollador Web", "manuel@ejemplo.com"),
("Lorena", "Gestora de proyectos", "lorena@ejemplo.com"),
("Javier", "Analista de datos", "javier@ejemplo.com"),
("Marta", "Experta en Python", "marta@ejemplo.com")
]

datos = []

for nombre, empleo, email in contactos:


datos.append({"nombre":nombre, "empleo":empleo, "email":email})

datos

[{'nombre': 'Manuel',
'empleo': 'Desarrollador Web',
'email': 'manuel@ejemplo.com'},
{'nombre': 'Lorena',
'empleo': 'Gestora de proyectos',
'email': 'lorena@ejemplo.com'},
{'nombre': 'Javier',
'empleo': 'Analista de datos',
'email': 'javier@ejemplo.com'},
{'nombre': 'Marta',
'empleo': 'Experta en Python',
'email': 'marta@ejemplo.com'}]

with open("contactos.json", "w") as jsonfile:


json.dump(datos, jsonfile)

datos = None

with open("contactos.json") as jsonfile:


datos = json.load(jsonfile)
for contacto in datos:
print(contacto["nombre"], contacto["empleo"], contacto["email"])

Manuel Desarrollador Web manuel@ejemplo.com


Lorena Gestora de proyectos lorena@ejemplo.com
Javier Analista de datos javier@ejemplo.com
Marta Experta en Python marta@ejemplo.com
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Tema 12: Manejo de ficheros (Soluciones)
Estos ejercicios son optativos para hacer al final de la unidad y están pensados para apoyar tu aprendizaje.

NOTA IMPORTANTE
Todos los ejercicios de ficheros deberás realizarlos en scripts creados en el mismo directorio donde vayas a crear los ficheros de datos
tal como se te indicará en los ejercicios.

1) En este ejercicio deberás crear un script llamado personas.py que lea los datos de un fichero de texto, que transforme cada
fila en un diccionario y lo añada a una lista llamada personas. Luego rocorre las personas de la lista y paracada una muestra
de forma amigable todos sus campos.

El fichero de texto se denominará personas.txt y tendrá el siguiente contenido en texto plano (créalo previamente):

1;Carlos;Pérez;05/01/1989
2;Manuel;Heredia;26/12/1973
3;Rosa;Campos;12/06/1961
4;David;García;25/07/2006

Los campos del diccionario serán por orden: id, nombre, apellido y nacimiento.

Aviso importante: Si quieres leer un fichero que no se ha escrito directamente con Python, entonces es posible que encuentres errores
de codificación al mostrar algunos caracteres. Asegúrate de indicar la codificación del fichero manualmente durante la apertura como
argumento en el open, por ejemplo con UTF-8:

open(..., encoding="utf8")

personas.py
from io import open

fichero = open('personas.txt','r', encoding="utf8")


lineas = fichero.readlines()
fichero.close()

personas = []
for linea in lineas:
campos = linea.replace("\n", "").split(";") # borramos los saltos de línea y separamos
persona = {"id":campos[0], "nombre":campos[1], "apellido":campos[2], "nacimiento":campos[3]}
personas.append(persona)

for p in personas:
print("(id={}) {} {} => {} ".format( p['id'], p['nombre'], p['apellido'], p['nacimiento']) )

(id=1) Cárlos Peralta => 05/01/1989


(id=2) Manuel Heredia => 26/12/1973
(id=3) Rosa Campos => 12/06/1961
(id=4) David García => 25/07/2006

2) En este ejercicio deberás crear un script llamado contador.py que realice varias tareas sobre un fichero llamado contador.txt
que almacenará un contador de visitas (será un número):

Nuestro script trabajará sobre un fichero contador.txt. Comprobaremos si el fichero no existe o está vacío, en ese caso lo
crearemos con el número 0. Si existe simplemente leeremos el valor del contador.
Luego a partir de un argumento:
Si se envía el argumento inc, se incrementará el contador en uno y se mostrará por pantalla.
Si se envía el argumento dec, se decrementará el contador en uno y se mostrará por pantalla.
Si no se envía ningún argumento (o algo que no sea inc o dec), se mostrará el valor del contador por pantalla.
Finalmente guardará de nuevo el valor del contador de nuevo en el fichero.
Utiliza excepciones si crees que es necesario, puedes mostrar el mensaje: Error: Fichero corrupto.

contador.py
from io import open
import sys

fichero = open("contador.txt", "a+")


fichero.seek(0)
contenido = fichero.readline()

if len(contenido) == 0:
contenido = "0"
fichero.write(contenido)

fichero.close()

# Vamos a intentar transformar el texto a un número


try:
contador = int(contenido)

# En función de lo que el usuario quiera...


if len(sys.argv) == 2:
if sys.argv[1] == "inc":
contador += 1
elif sys.argv[1] == "dec":
contador -= 1

print(contador)

# Finalmente volvemos a escribir los cambios en el fichero


fichero = open("contador.txt", "w")
fichero.write( str(contador) )
fichero.close()

except:
print("Error: Fichero corrupto.")

-1

3) Utilizando como base el ejercicio de los personajes que hicimos, en este ejercicio tendrás que crear un gestor de personajes
(gestor.py) para añadir y borrar la información de los siguientes personajes:

Vida Ataque Defensa Alcance

Caballero 4 2 4 2

Guerrero 2 4 2 4

Arquero 2 4 1 8

Deberás hacer uso del módulo pickle y todos los cambios que realices se irán guardando en un fichero binario
personajes.pckl, por lo que aunque cerremos el programa los datos persistirán.

Crea dos clases, una Personaje y otra Gestor.


La clase Personaje deberá permitir crear un personaje con el nombre (que será la clase), y sus propiedades de vida, ataque,
defensa y alcance (que deben ser números enteros positivos mayor que cero obligatoriamente).**
Por otro lado la clase Gestor deberá incorporar todos los métodos necesarios para añadir personajes, mostrarlos y borrarlos (a
partir de su nombre por ejemplo) (tendrás que pensar una forma de hacerlo), además de los métodos esenciales para guardar los
cambios en el fichero personajes.pckl que se deberían ejecutar automáticamente.
En caso de que el personaje ya exista en el Gestor entonces no se creará.

Una vez lo tengas listo realiza las siguientes prueba en tu código:

Crea los tres personajes de la tabla anterior y añádelos al Gestor.


Muestra los personajes del Gestor.
Borra al Arquero.
Muestra de nuevo el Gestor.

Sugerencias: El ejemplo con pickle que realizamos puede servirte como base.

gestor.py
from io import open
import pickle

class Personaje:

def __init__(self, nombre, vida, ataque, defensa, alcance):


self.nombre = nombre
self.vida = vida
self.ataque = ataque
self.defensa = defensa
self.alcance = alcance

def __str__(self):
return "{} => {} vida, {} ataque, {} defensa, {} alcance".format(self.nombre, self.vida, self.ataque, self

class Gestor:

personajes = []

# Constructor de clase
def __init__(self):
self.cargar()

def agregar(self, p):


for pTemp in self.personajes:
if pTemp.nombre == p.nombre:
return
self.personajes.append(p)
self.guardar()

def borrar(self, nombre):


for pTemp in self.personajes:
if pTemp.nombre == nombre:
self.personajes.remove(pTemp)
self.guardar()
print("\nPersonaje {} borrado".format(nombre))
return

def mostrar(self):
if len(self.personajes) == 0:
print("El gestor está vacío")
return
for p in self.personajes:
print(p)

def cargar(self):
fichero = open('personajes.pckl', 'ab+')
fichero.seek(0)
try:
self.personajes = pickle.load(fichero)
except:
# print("El fichero está vacío")
pass
finally:
fichero.close()
print("Se han cargado {} personajes".format( len(self.personajes) ))

def guardar(self):
fichero = open('personajes.pckl', 'wb')
pickle.dump(self.personajes, fichero)
fichero.close()

G = Gestor()
G.agregar( Personaje("Caballero",4,2,4,2) )
G.agregar( Personaje("Guerrero",2,4,2,4) )
G.agregar( Personaje("Arquero",2,4,1,8) )
G.mostrar()
#G.mostrar()
#G.borrar("Arquero")
#G.borrar("Caballera")
#G.mostrar()

Se han cargado 0 personajes


Caballero => 4 vida, 2 ataque, 4 defensa, 2 alcance
Guerrero => 2 vida, 4 ataque, 2 defensa, 4 alcance
Arquero => 2 vida, 4 ataque, 1 defensa, 8 alcance
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Nota: Estos ejemplos están indicados para hacerse en scripts de código Python, no en Jupyter

Conexión a la base de datos, creación y desconexión


# Importamos el módulo
import sqlite3

# Nos conectamos a la base de datos ejemplo.db (la crea si no existe)


conexion = sqlite3.connect('ejemplo.db')

# Cerramos la conexión, si no la cerramos se mantendrá en uso y no podremos gestionar el fichero


conexion.close()

Creación de una tabla utilizando sintaxis SQL


Antes de ejecutar una consulta (query) en código SQL, tenemos que crear un cursor.

Una vez creada la tabla, si intentamos volver a crearla dará error indicándonos que esta ya existe.

import sqlite3

conexion = sqlite3.connect('ejemplo.db')

# Creamos el cursor
cursor = conexion.cursor()

# Ahora crearemos una tabla de usuarios para almacenar nombres, edades y emails
cursor.execute("CREATE TABLE usuarios (nombre VARCHAR(100), edad INTEGER, email VARCHAR(100))")

# Guardamos los cambios haciendo un commit


conexion.commit()

conexion.close()

---------------------------------------------------------------------------
OperationalError Traceback (most recent call last)
<ipython-input-14-279f8cd3e83f> in <module>()
7
8 # Ahora crearemos una tabla de usuarios para almacenar nombres, edades y emails
----> 9 cursor.execute("CREATE TABLE usuarios (nombre text, edad int, email text)")
10
11 # Guardamos los cambios haciendo un commit

OperationalError: table usuarios already exists

Insertando un registro
import sqlite3

conexion = sqlite3.connect('ejemplo.db')
cursor = conexion.cursor()

# Insertamos un registro en la tabla de usuarios


cursor.execute("INSERT INTO usuarios VALUES ('Hector', 27, 'hector@ejemplo.com')")

# Guardamos los cambios haciendo un commit


conexion.commit()

conexion.close()

Recuperando el primer registro con .fetchone()


import sqlite3

conexion = sqlite3.connect('ejemplo.db')
cursor = conexion.cursor()

# Recuperamos los registros de la tabla de usuarios


cursor.execute("SELECT * FROM usuarios")

# Mostrar el cursos a ver que hay ?


print(cursor)

# Recorremos el primer registro con el método fetchone, devuelve una tupla


usuario = cursor.fetchone()
print(usuario)

conexion.close()
('Hector', 27, 'hector@ejemplo.com')

Insertando varios registros con .executemany()


import sqlite3

conexion = sqlite3.connect('ejemplo.db')
cursor = conexion.cursor()

# Creamos una lista con varios usuarios


usuarios = [('Mario', 51, 'mario@ejemplo.com'),
('Mercedes', 38, 'mercedes@ejemplo.com'),
('Juan', 19, 'juan@ejemplo.com'),
]

# Ahora utilizamos el método executemany() para insertar varios


cursor.executemany("INSERT INTO usuarios VALUES (?,?,?)", usuarios)

# Guardamos los cambios haciendo un commit


conexion.commit()

conexion.close()

Recuperando varios registros con .fetchall()


import sqlite3

conexion = sqlite3.connect('ejemplo.db')
cursor = conexion.cursor()

# Recuperamos los registros de la tabla de usuarios


cursor.execute("SELECT * FROM usuarios")

# Recorremos todos los registros con fetchall, y los volvamos en una lista de usuarios
usuarios = cursor.fetchall()

# Ahora podemos recorrer todos los usuarios


for usuario in usuarios:
print(usuario)

conexion.close()

('Hector', 27, 'hector@ejemplo.com')


('Mario', 51, 'mario@ejemplo.com')
('Mercedes', 38, 'mercedes@ejemplo.com')
('Juan', 19, 'juan@ejemplo.com')

Utilizando DB Browser
En esta práctica vamos a analizar el contenido de nuestra base de datos utilizando un programa externo.

http://sqlitebrowser.org/
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Nota: Estos ejemplos están indicados para hacerse en scripts de código Python, no en Jupyter

Claves primarias
Una clave primaria es un campo especial de una tabla que actúa como identificador único de los registros, en otras palabras, no se
puede repetir un registro con la misma clave primaria. Por ejemplo dos usuarios con el mismo DNI:

import sqlite3

conexion = sqlite3.connect('usuarios.db')

cursor = conexion.cursor()

# Creamos un campo dni como clave primaria


cursor.execute('''CREATE TABLE usuarios (
dni VARCHAR(9) PRIMARY KEY,
nombre VARCHAR(100),
edad INTEGER,
email VARCHAR(100))''')

usuarios = [('11111111A', 'Hector', 27, 'hector@ejemplo.com'),


('22222222B', 'Mario', 51, 'mario@ejemplo.com'),
('33333333C', 'Mercedes', 38, 'mercedes@ejemplo.com'),
('44444444D', 'Juan', 19, 'juan@ejemplo.com')]

cursor.executemany("INSERT INTO usuarios VALUES (?,?,?,?)", usuarios)

conexion.commit()
conexion.close()

Si ahora intentamos introducir un registro con un DNI duplicado, saltará un error:

import sqlite3

conexion = sqlite3.connect('usuarios.db')

cursor = conexion.cursor()

# Añadimos un usuario con el mismo DNI


cursor.execute("INSERT INTO usuarios VALUES ('11111111A', 'Fernando', 31, 'fernando@ejemplo.com')")

conexion.commit()
conexion.close()

---------------------------------------------------------------------------
IntegrityError Traceback (most recent call last)
<ipython-input-88-1f8a69b706db> in <module>()
6
7 # Añadimos un usuario con el mismo DNI
----> 8 cursor.execute("INSERT INTO usuarios VALUES ('11111111A', 'Fernando', 31, 'fernando@ejemplo.com')")
9
10 conexion.commit()

IntegrityError: UNIQUE constraint failed: usuarios.dni

Campos autoincrementales
No siempre contaremos con claves primarias en nuestras tablas (como el DNI), sin embargo siempre necesitaremos uno para identificar
cada registro y poder consultarlo, modificarlo o borrarlo.

Para estas situaciones lo más útil es utilizar campos autoincrementales, campos especiales que asignan automáticamente un número
(de uno en uno) al crear un nuevo registro. Es muy útil para identificar de forma única cada registro ya que nunca se repiten.

En SQLite, si indicamos que un campo numérico entero es una clave primaria, automáticamente se tomará como un campo auto
incremental. Podemos hacerlo fácilmente así:

import sqlite3

conexion = sqlite3.connect('productos.db')

cursor = conexion.cursor()

# Las cláusulas not null indican que no puede ser campos vacíos
cursor.execute('''CREATE TABLE productos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre VARCHAR(100) NOT NULL,
marca VARCHAR(50) NOT NULL,
precio FLOAT NOT NULL)''')
conexion.close()

¡Problema al insertar registros con campos autoincrementales!


Al utilizar un nuevo campo autoincremental, la sintaxis sencilla para insertar registros ya no funciona, pues en primer lugar se espera un
identificador único, por lo que recibimos un error indicándonos se esperan 4 columnas en lugar de 3:

import sqlite3

conexion = sqlite3.connect('productos.db')

cursor = conexion.cursor()

productos = [('Teclado', 'Logitech', 19.95),


('Pantalla 19"' 'LG', 89.95),]

cursor.executemany("INSERT INTO productos VALUES (?,?,?)", productos)

conexion.commit()
conexion.close()

---------------------------------------------------------------------------
OperationalError Traceback (most recent call last)
<ipython-input-96-7b99f15c4bb5> in <module>()
8 ('Pantalla 19"' 'LG', 89.95),]
9
---> 10 cursor.executemany("INSERT INTO productos VALUES (?,?,?)", productos)
11
12 conexion.commit()

OperationalError: table productos has 4 columns but 3 values were supplied

Para arreglarlo cambiaremos la notación durante la inserción, especificando el valor null para el auto incremento:

import sqlite3

conexion = sqlite3.connect('productos.db')

cursor = conexion.cursor()

productos = [('Teclado', 'Logitech', 19.95),


('Pantalla 19"','LG', 89.95),
('Altavoces 2.1','LG', 24.95),]

cursor.executemany("INSERT INTO productos VALUES (null,?,?,?)", productos)

conexion.commit()
conexion.close()

Ahora podemos consultar nuestros productos fácilmente con su identificador único:

import sqlite3

conexion = sqlite3.connect('productos.db')
cursor = conexion.cursor()

# Recuperamos los registros de la tabla de usuarios


cursor.execute("SELECT * FROM productos")

# Recorremos todos los registros con fetchall, y los volvamos en una lista de usuarios
productos = cursor.fetchall()

# Ahora podemos recorrer todos los productos


for producto in productos:
print(producto)

conexion.close()

(1, 'Teclado', 'Logitech', 19.95)


(2, 'Pantalla 19"', 'LG', 89.95)
(3, 'Altavoces 2.1', 'LG', 24.95)

Claves únicas
El problema con las claves primarias es que sólo podemos tener un campo con esta propiedad, y si da la casualidad que utilizamos un
campo autoincremental, ya no podemos asignarla a otro campo.

Para estos casos existen las claves únicas, que nos permiten añadir otros campos únicos no repetibles.

Podemos adaptar el ejemplo de los usuarios con un campo autoincremental que haga de clave primaria, y a su vez marcar el DNI como
un campo único:
import sqlite3

conexion = sqlite3.connect('usuarios_autoincremental.db')

cursor = conexion.cursor()

# Creamos un campo dni como clave primaria


cursor.execute('''CREATE TABLE usuarios (
id INTEGER PRIMARY KEY,
dni VARCHAR(9) UNIQUE,
nombre VARCHAR(100),
edad INTEGER(3),
email VARCHAR(100))''')

usuarios = [('11111111A', 'Hector', 27, 'hector@ejemplo.com'),


('22222222B', 'Mario', 51, 'mario@ejemplo.com'),
('33333333C', 'Mercedes', 38, 'mercedes@ejemplo.com'),
('44444444D', 'Juan', 19, 'juan@ejemplo.com')]

cursor.executemany("INSERT INTO usuarios VALUES (null, ?,?,?,?)", usuarios)

conexion.commit()
conexion.close()

Si intentamos añadir un usuario con la misma clave da error de integridad:

import sqlite3

conexion = sqlite3.connect('usuarios_autoincremental.db')

cursor = conexion.cursor()

# Añadimos un usuario con el mismo DNI


cursor.execute("INSERT INTO usuarios VALUES (null, '11111111A', 'Fernando', 31, 'fernando@ejemplo.com')")

conexion.commit()
conexion.close()

---------------------------------------------------------------------------
IntegrityError Traceback (most recent call last)
<ipython-input-100-fdd36d467cfc> in <module>()
6
7 # Añadimos un usuario con el mismo DNI
----> 8 cursor.execute("INSERT INTO usuarios VALUES (null, '11111111A', 'Fernando', 31, 'fernando@ejemplo.com')
")
9
10 conexion.commit()

IntegrityError: UNIQUE constraint failed: usuarios.dni

Con la ventaja de contar con un identificador automático para cada registro:

import sqlite3

conexion = sqlite3.connect('usuarios_autoincremental.db')
cursor = conexion.cursor()

# Recuperamos los registros de la tabla de usuarios


cursor.execute("SELECT * FROM usuarios")

# Recorremos todos los registros con fetchall, y los volvamos en una lista de usuarios
usuarios = cursor.fetchall()

# Ahora podemos recorrer todos los usuarios


for usuario in usuarios:
print(usuario)

conexion.close()

(1, '11111111A', 'Hector', 27, 'hector@ejemplo.com')


(2, '22222222B', 'Mario', 51, 'mario@ejemplo.com')
(3, '33333333C', 'Mercedes', 38, 'mercedes@ejemplo.com')
(4, '44444444D', 'Juan', 19, 'juan@ejemplo.com')

Importancia de los identificadores únicos


Es muy importante contar siempre con identificadores únicos para cada registro, ya que sin ellos nos sería prácticamente imposible
editar y borrar campos de forma fácil.

En la próxima lección haremos uso de las consultas UPDATE y DELETE junto a la cláusula WHERE para modificar y borrar registros
gracias a los identificadores únicos, y otras propiedades.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Nota: Estos ejemplos están indicados para hacerse en scripts de código Python, no en Jupyter

Consultando registros específicos


Una vez contamos con algún campo que nos sirva de identificador único, podemos realizar consultas específicas utilizando la cláusula
WHERE:

import sqlite3

conexion = sqlite3.connect('usuarios_autoincremental.db')
cursor = conexion.cursor()

# Recuperamos un registro de la tabla de usuarios


cursor.execute("SELECT * FROM usuarios WHERE id=1")

usuario = cursor.fetchone()
print(usuario)

conexion.close()

(1, '11111111A', 'Hector', 27, 'hector@ejemplo.com')

También podemos buscar sólo algunos campos específicos utilizando el DNI:

import sqlite3

conexion = sqlite3.connect('usuarios_autoincremental.db')
cursor = conexion.cursor()

# Recuperamos un registro de la tabla de usuarios


cursor.execute("SELECT nombre, edad, email FROM usuarios WHERE dni='22222222B'")

usuario = cursor.fetchone()
print(usuario)

conexion.close()

('Mario', 51, 'mario@ejemplo.com')

Modificando registros específicos


De forma similar al SELECT podemos utilizar la cláusula:

UPDATE tabla
SET columna1 = valor1, columna2 = valor2...., columnaN = valorN
WHERE [condicion]

import sqlite3

conexion = sqlite3.connect('usuarios_autoincremental.db')
cursor = conexion.cursor()

# Actualizamos un registro
cursor.execute("UPDATE usuarios SET nombre='Hector Costa' WHERE dni='11111111A'")

# Ahora lo consultamos de nuevo


cursor.execute("SELECT * FROM usuarios WHERE dni='11111111A'")
usuario = cursor.fetchone()
print(usuario)

conexion.commit()
conexion.close()

(1, '11111111A', 'Hector Costa', 27, 'hector@ejemplo.com')

Importantísimo: No olvidar la cláusula WHERE o podéis acabar actualizando todos los registros

Borrando registros específicos


Finalmente, para borrar un registro a partir de su id o campo único:

DELETE FROM tabla WHERE [condicion]

import sqlite3

conexion = sqlite3.connect('usuarios_autoincremental.db')
cursor = conexion.cursor()

# Creamos un registro de prueba


cursor.execute("INSERT INTO usuarios VALUES (null, '55555555E', 'Fernando', 31, 'fernando@ejemplo.com')")

# Consultamos los usuarios


for usuario in cursor.execute("SELECT * FROM usuarios"):
print(usuario)

# Ahora lo borramos
cursor.execute("DELETE FROM usuarios WHERE dni='55555555E'")

print() # Espacio en blanco

# Consultamos de nuevo los usuarios


for usuario in cursor.execute("SELECT * FROM usuarios"):
print(usuario)

conexion.commit()
conexion.close()

(1, '11111111A', 'Hector Costa', 27, 'hector@ejemplo.com')


(2, '22222222B', 'Mario', 51, 'mario@ejemplo.com')
(3, '33333333C', 'Mercedes', 38, 'mercedes@ejemplo.com')
(4, '44444444D', 'Juan', 19, 'juan@ejemplo.com')
(5, '55555555E', 'Fernando', 31, 'fernando@ejemplo.com')

(1, '11111111A', 'Hector Costa', 27, 'hector@ejemplo.com')


(2, '22222222B', 'Mario', 51, 'mario@ejemplo.com')
(3, '33333333C', 'Mercedes', 38, 'mercedes@ejemplo.com')
(4, '44444444D', 'Juan', 19, 'juan@ejemplo.com')

No te olvides el WHERE
En SQL es posible realizar actualizaciones y borrados en masa, pero las dos últimas son un poco peligrosas. Sin embargo realizarlas es
tan sencillo como olvidarnos la cláusula WHERE en el UPDATE o el DELETE.

Canción: No te olvides el WHERE en el DELETE FROM: https://www.youtube.com/watch?v=i_cVJgIz_Cs

import sqlite3

conexion = sqlite3.connect('usuarios_autoincremental.db')
cursor = conexion.cursor()

# Borramos sin el WHERE


cursor.execute("DELETE FROM usuarios")

# Consultamos de nuevo los usuarios


usuarios = cursor.execute("SELECT * FROM usuarios").fetchall()
print(usuarios)

conexion.commit()
conexion.close()

[]

Mucho más por aprender


SQL es un lenguaje muy extenso con muchísimas posibilidades. Ya que esta unidad no deja de ser una introducción, te animo a seguir
aprendiendo por tu cuenta conceptos tan importantes como:

Consultas avanzadas: or, and, like, join


Funciones simples: count, group by, distinct
Funciones avanzadas: sum, avg, min, max
Manejo de fechas: date, year, month, day
Relaciones y claves foráneas
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Operadores encadenados
Una de las pecualiaridades más interesantes de Python, y que otros lenguajes no ofrecen, es la capacidad de encadenar múltiples
expresiones.

Normalmente para encadenar expresiones las unimos utilizando el operador lógico and:

1 < 2 and 2 < 3

True

La condición para poder encadenar con operadores es encontrar un punto en común entre ambas expresiones:

1 < 2 < 3

True

La cual también se puede expresar de la siguiente forma:

3 > 2 > 1

True

RECORDATORIO: Al utilizar operadores encadenados, estos se basan en comprobar el resultado de cada mínima expresión y
relacionarlos con AND. No confundir con la comparación del resultado de cada expresión con el siguiente:

Delimitando un valor numérico


Veamos en ejemplo mucho más útil, donde queremos comprobar si un número se encuentra entre 0 y 100 (ambos incluidos):

numero = 35
if numero >= 0 and numero <= 100:
print("El número {} se encuentra entre 0 y 100".format(numero) )
else:
print("El número {} no se encuentra entre 0 y 100".format(numero) )

El número 35 se encuentra entre 0 y 100

Utilizando operadores encadenados podemos simplificar la sintaxis readaptando la lógica:

numero = 35
if 0 <= numero <= 100:
print("El número {} se encuentra entre 0 y 100".format(numero) )
else:
print("El número {} no se encuentra entre 0 y 100".format(numero) )

El número 35 se encuentra entre 0 y 100


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Comprensión de listas
El poder de Python va mucho más allá de lo que a primera vista podemos imaginar.

La comprensión de listas, del inglés list comprehensions , es una funcionalidad que nos permite crear listas avanzadas en una misma
línea de código. Esto se ve mucho mejor en la práctica, así que a lo largo de esta lección vamos a trabajar distintos ejemplos.

Ejemplo 1) Crear una lista con las letras de una palabra

# Método tradicional
lista = []
for letra in 'casa':
lista.append(letra)
print(lista)

['c', 'a', 's', 'a']

# Con comprensión de listas


lista = [letra for letra in 'casa']
print(lista)

['c', 'a', 's', 'a']

Como vemos, gracias a la comprensión de listas podemos indicar directamente cada elemento que va a formar la lista, en este caso la
letra, a la vez que definimos el for:

lista = [letra for letra in 'casa'] # La lista está formada por cada letra que recorremos en el
for

Ejemplo 2) Crear una lista con las potencias de 2 de los primeros 10 números

# Método tradicional
lista = []
for numero in range(0,11):
lista.append(numero**2)
print(lista)

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# Con comprensión de listas


lista = [numero**2 for numero in range(0,11)]
print(lista)

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

De este ejemplo podemos aprender que es posible modificar al vuelo los elementos que van a formar la lista.

Ejemplo 3) Crear una lista con los todos los múltiples de 2 entre 0 y 10

# Método tradicional
lista = []
for numero in range(0,11):
if numero % 2 == 0:
lista.append(numero)
print(lista)

[0, 2, 4, 6, 8, 10]

# Con comprensión de listas


lista = [numero for numero in range(0,11) if numero % 2 == 0 ]
print(lista)

[0, 2, 4, 6, 8, 10]

En este caso podemos observar que incluso podemos marcar una condición justo al final para añadir o no el elemento en la lista.

[numero for numero in range(0,11) if numero % 2 == 0 ] # Añadir los números del 0 al 10 cuando su
módulo de 2 sea 0

Ejemplo 4) Crear una lista de pares a partir de otra lista creada con las potencias de 2 de los primeros 10
números

# Método tradicional
lista = []
for numero in range(0,11):
lista.append(numero**2)

pares = []
for numero in lista:
if numero % 2 == 0:
pares.append(numero)
print(pares)

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

# Con comprensión de listas


lista = [numero for numero in [numero**2 for numero in range(0,11)] if numero % 2 == 0 ]
print(lista)

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

Crear listas a partir de listas anidadas nos permite llevar la comprensión de listas al siguiente nivel y además no hay un límite.

Por ahora te animo a hacer tus propias pruebas y practicar, ya que de ahora en adelante haremos uso de la comprensión de listas en
ejemplos y otras funcionalidades avanzadas.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Ámbitos y funciones decoradoras
NOTA: Antes de realizar esta lección debes reiniciar Jupyter Notebook para vaciar la memoria.

Introducción
No cabe duda de que Python es un lenguaje flexible, y cuando trabajamos con funciones no es una excepción.

En Python, dentro de una función podemos definir otras funciones. Con la peculiaridad de que el ámbito de estas funciones se encuentre
únicamente dentro de la función padre. Vamos a trabajar los ámbitos un poco más en profundidad:

def hola():

def bienvenido():
return "Hola!"

return bienvenido

Si intentamos llamar a la función bienvenido...

bienvenido()

---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-3-f083d151b813> in <module>()
----> 1 bienvenido()

NameError: name 'bienvenido' is not defined

Como vemos nos da un error de que no existe. En cambio si intentamos ejecutar la función hola():

hola()

<function __main__.hola.<locals>.bienvenido>

Se devuelve la función bienvenido, y podemos apreciar dentro de su definición que existe un espacio llamado locals, el cual hace
referencia al ámbito local que abarca la función.

Ámbito local y global


Si utilizamos una función reservada locals() obtendremos un diccionario con todas las definiciones dentro del espacio local del bloque
en el que estamos:

def hola():

def bienvenido():
return "Hola!"

print( locals() ) # Mostramos el ámbito local

hola()

{'bienvenido': <function hola.<locals>.bienvenido at 0x000001F867E88C80>}

Como vemos se nos muestra un diccionario, aquí encontraremos la función bienvenido().

Podríamos añadir algo más:

lista = [1,2,3]

def hola():

numero = 50

def bienvenido():
return "Hola!"

print( locals() ) # Mostramos el ámbito local

hola()

{'bienvenido': <function hola.<locals>.bienvenido at 0x000001F867E88950>, 'numero': 50}

Como podemos observar, ahora además de la función tenemos una clave con el número y el valor 50. Sin embargo no encontramos la
lista, pues esta se encuentra fuera del ámbito local. De hecho se encuentra en el ámbito global, el cual podemos mostrar con la función
reservada globals():
# Antes de ejecutar este bloque reinicia el Notebook para vaciar la memoria.
lista = [1,2,3]

def hola():

numero = 50

def bienvenido():
return "Hola!"

print( globals() ) # Mostramos el ámbito global

hola()

{'__name__': '__main__', '_oh': {}, '__doc__': 'Automatically created module for IPython interactive environmen
t', 'lista': [1, 2, 3], '__builtin__': <module 'builtins' (built-in)>, 'In': ['', '# Antes de ejecutar este blo
que reinicia el Notebook para vaciar la memoria.\nlista = [1,2,3]\n\ndef hola():\n \n numero = 50\n \n
def bienvenido():\n return "Hola!"\n \n print( globals() ) # Mostramos el ámbito global\n\nhola()
'], '_ih': ['', '# Antes de ejecutar este bloque reinicia el Notebook para vaciar la memoria.\nlista = [1,2,3]\
n\ndef hola():\n \n numero = 50\n \n def bienvenido():\n return "Hola!"\n \n print( gl
obals() ) # Mostramos el ámbito global\n\nhola()'], '__loader__': None, '__builtins__': <module 'builtins' (bu
ilt-in)>, '_dh': ['C:\\CursoPython\\Fase 4 - Temas avanzados\\Tema 15 - Funcionalidades avanzadas\\Apuntes'], '
get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0
x00000243D11F5E80>>, 'hola': <function hola at 0x00000243D1B58C80>, '_sh': <module 'IPython.core.shadowns' from
'C:\\Users\\Hector\\Anaconda3\\lib\\site-packages\\IPython\\core\\shadowns.py'>, '_': '', '_ii': '', 'Out': {},
'__package__': None, '___': '', '_iii': '', '_i': '', '__spec__': None, 'exit': <IPython.core.autocall.ZMQExitA
utocall object at 0x00000243D1A92B70>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x00000243D1A92
B70>, '_i1': '# Antes de ejecutar este bloque reinicia el Notebook para vaciar la memoria.\nlista = [1,2,3]\n\n
def hola():\n \n numero = 50\n \n def bienvenido():\n return "Hola!"\n \n print( globa
ls() ) # Mostramos el ámbito global\n\nhola()', '__': ''}

Tampoco es necesario que nos paremos a analizar el contenido, pero como podemos observar, desde el ámbito global tenemos acceso
a muchas más definiciones porque engloba a su vez todas las de sus bloques padres.

Si mostramos únicamente las claves del diccionario globals(), quizá sería más entendible:

globals().keys()

dict_keys(['_i4', '__name__', '_oh', '__doc__', 'lista', '__builtin__', 'In', '_i5', '_ih', '__loader__', '__bu
iltins__', '_dh', 'get_ipython', 'hola', '_sh', '_', '_ii', 'Out', '__package__', '___', '_iii', '_i', '__spec_
_', 'exit', '_i3', 'quit', '_i1', '_i2', '__'])

Ahora si buscamos bien encontraremos la clave lista, la cual hace referencia a la variable declarada fuera de la función. Incluso
podríamos acceder a ella como si fuera un diccionario normal:

globals()['lista'] # Desde la función globals

[1, 2, 3]

lista # Forma tradicional

[1, 2, 3]

Funciones como variables


Volviendo a nuestra función hola(), ahora sabemos que si la ejecutamos, en realidad estamos accediendo a su función local
bienvenido(), pero eso no significa que la ejecutamos, sólo estamos haciendo referencia a ella.

Esa es la razón de que se devuelva su definición y no el resultado de su ejecución:

def hola():

def bienvenido():
return "Hola!"

return bienvenido

hola()

<function __main__.hola.<locals>.bienvenido>

Por muy raro que parezca, podríamos ejecutarla llamando una segunda vez al paréntesis. La primera para hola() y la segunda para
bienvenido():

hola()()

'Hola!'

Como es realmente extraño, normalmente lo que hacemos es asignar la función a una variable y ejecutarla como si fuera una nueva
función:

bienvenido = hola()
bienvenido = hola()
bienvenido()

'Hola!'

A diferencia de las colecciones y los objetos, donde las copias se utilizaban como accesos directos, las copias de las funciones son
independientes y aunque borrásemos la original, la nueva copia seguiría existiendo:

del(hola)

bienvenido()

'Hola!'

Funciones como argumentos


Si ya era extraño ejecutar funciones anidadas, todavía es más extraño el concepto de enviar una función como argumento de otra
función, sin embargo gracias a la flexibilidad de Python es posible hacerlo:

def hola():
return "Hola!"

def test(funcion):
print( funcion() )

test(hola)

Hola Mundo

Quizá en este momento no se ocurren muchas utilidades para esta funcionalidad, pero creedme que es realmente útil cuando queremos
extender funciones ya existentes sin modificarlas. De ahí que este proceso se conozca como un decorador, y de ahí pasamos
directamente a las funciones decoradoras.

Funciones decoradoras
Una función decoradora es una función que envuelve la ejecución de otra función y permite extender su comportamiento. Están
pensadas para reutilazarlas gracias a una sintaxis de ejecución mucho más simple.

Imaginaros estas dos funciones sencillas:

def hola():
print("Hola!")

def adios():
print("Adiós!")

Y queremos queremos crear un decorador para monitorizar cuando se ejecutan las dos funciones, avisando antes y después.

Para crear una función decoradora tenemos que recibir la función a ejecutar, y envolver su ejecución con el código a extender:

def monitorizar(funcion):

def decorar():
print("\t* Se está apunto de ejecutar la función:", funcion.__name__)

funcion()

print("\t* Se ha finalizado de ejecutar la función:", funcion.__name__)

return decorar

Ahora para realizar la monitorización deberíamos llamar al monitor ejecutando la función enviada y devuelta:

monitorizar(hola)()

* Se está apunto de ejecutar la función: hola


Hola!
* Se ha finalizado de ejecutar la función: hola

Sin embargo esto no es muy cómodo, y ahí es cuando aparece la sintaxis que nos permite configurar una función decoradora en una
función normal:

@monitorizar
def hola():
print("Hola!")

@monitorizar
def adios():
print("Adiós!")
Una vez configurada la función decoradora, al utilizar las funciones se ejecutarán automáticamente dentro de la función decoradora:

hola()
print()
adios()

* Se está apunto de ejecutar la función: hola


Hola!
* Se ha finalizado de ejecutar la función: hola

* Se está apunto de ejecutar la función: adios


Adiós!
* Se ha finalizado de ejecutar la función: adios

Pasando argumentos al decorador


def monitorizar_args(funcion):

def decorar(*args,**kwargs):
print("\t* Se está apunto de ejecutar la función:", funcion.__name__)
funcion(*args,**kwargs)
print("\t* Se ha finalizado de ejecutar la función:", funcion.__name__)

return decorar

@monitorizar_args
def hola(nombre):
print("Hola {}!".format(nombre))

@monitorizar_args
def adios(nombre):
print("Adiós {}!".format(nombre))

hola("Héctor")
print()
adios("Héctor")

* Se está apunto de ejecutar la función: hola


Hola Héctor!
* Se ha finalizado de ejecutar la función: hola

* Se está apunto de ejecutar la función: adios


Adiós Héctor!
* Se ha finalizado de ejecutar la función: adios

Perfecto! Ahora ya sabes qué son las funciones decoradoras y cómo utilizar el símbolo @ para automatizar su ejecución. Estas
funciones se utilizan mucho cuando trabajamos con Frameworks Web como Django, así que seguro te harán servicio si tienes pensado
aprender a utilizarlo.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Funciones generadoras
Por regla general, cuando queremos crear una lista de algún tipo, lo que hacemos es crear la lista vacía, y luego con un bucle varios
elementos e ir añadiendolos a la lista si cumplen una condición:

[numero for numero in [0,1,2,3,4,5,6,7,8,9,10] if numero % 2 == 0 ]

[0, 2, 4, 6, 8, 10]

También vimos cómo era posible utilizar la función range() para generar dinámicamente la lista en la memoria, es decir, no teníamos
que crearla en el propio código, sino que se interpretaba sobre la marcha:

[numero for numero in range(0,11) if numero % 2 == 0 ]

[0, 2, 4, 6, 8, 10]

La verdad es que range() es una especie de función generadora. Por regla general las funciones devolvuelven un valor con return, pero
la preculiaridad de los generadores es que van cediendo valores sobre la marcha, en tiempo de ejecución.

La función generadora range(0,11), empieza cediendo el 0, luego se procesa el for comprobando si es par y lo añade a la lista, en la
siguiente iteración se cede el 1, se procesa el for se comprueba si es par, en la siguiente se cede el 2, etc.

Con esto se logra ocupar el mínimo de espacio en la memoria y podemos generar listas de millones de elementos sin necesidad de
almacenarlos previamente.

Veamos a ver cómo crear una función generadora de pares:

def pares(n):
for numero in range(n+1):
if numero % 2 == 0:
yield numero

pares(10)

<generator object pares at 0x000002945F38BFC0>

Como vemos, en lugar de utilizar el return, la función generadora utiliza el yield, que significa ceder. Tomando un número busca todos
los pares desde 0 hasta el número+1 sirviéndonos de un range().

Sin embargo, fijaros que al imprimir el resultado, éste nos devuelve un objeto de tipo generador.

De la misma forma que recorremos un range() podemos utilizar el bucle for para recorrer todos los elementos que devuelve el
generador:

for numero in pares(10):


print(numero)

0
2
4
6
8
10

Utilizando comprensión de listas también podemos crear una lista al vuelo:

[numero for numero in pares(10)]

[0, 2, 4, 6, 8, 10]

Sin embargo el gran potencial de los generadores no es simplemente crear listas, de hecho como ya hemos visto, el propio resultado no
es una lista en sí mismo, sino una secuencia iterable con un montón de características únicas.

Iteradores
Por tanto las funciones generadoras devuelven un objeto que suporta un protocolo de iteración. ¿Qué nos permite hacer? Pues
evidentemente controlar el proceso de generación. Teniendo en cuenta que cada vez que la función generadora cede un elemento,
queda suspendida y se retoma el control hasta que se le pide generar el siguiente valor.

Así que vamos a tomar nuestro ejemplo de pares desde otra perspectiva, como si fuera un iterador manual, así veremos exactamente a
lo que me refiero:

pares = pares(3)
pares = pares(3)

Bien, ahora tenemos un iterador de pares con todos los números pares entre el 0 y el 3. Vamos a conseguir el primer número par:

next(pares)

Como vemos la función integrada next() nos permite acceder al siguiente elemento de la secuencia. Pero no sólo eso, si volvemos a
ejecutarla...

next(pares)

Ahora devuelve el segundo! ¿No os recuerdo esto al puntero de los ficheros? Cuando leíamos una línea, el puntero pasaba a la
siguiente y así sucesivamente. Pues aquí igual.

¿Y qué pasaría si intentamos acceder al siguiente, aún sabiendo que entre el 0 y el 3 sólo tenemos los pares 0 y 2?

next(pares)

---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-34-68378216ba43> in <module>()
----> 1 next(pares)

StopIteration:

Pues que nos da un error porque se ha acabado la secuencia, así que tomad nota y capturad la excepción si váis a utilizarlas sin saber
exactamente cuantos elementos os devolverá el generador.

Así que la pregunta que nos queda es ¿sólo es posible iterar secuencias generadas al vuelo? Vamos a probar con una lista:

lista = [1,2,3,4,5]
next(lista)

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-38-28c22b67c419> in <module>()
1 lista = [1,2,3,4,5]
----> 2 next(lista)
3
4 cadena = "Hola"
5 next(cadena)

TypeError: 'list' object is not an iterator

¿Quizá con una cadena?

cadena = "Hola"
next(cadena)

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-39-44ca9ed1903b> in <module>()
1 cadena = "Hola"
----> 2 next(cadena)

TypeError: 'str' object is not an iterator

Pues no, no podemos iterar ninguna colección como si fuera una secuencia. Sin embargo, hay una función muy interesante que nos
permite covertir las cadenas y algunas colecciones a iteradores, la función iter():

lista = [1,2,3,4,5]
lista_iterable = iter(lista)
print( next(lista_iterable) )
print( next(lista_iterable) )
print( next(lista_iterable) )
print( next(lista_iterable) )
print( next(lista_iterable) )

1
2
3
4
5

cadena = "Hola"
cadena_iterable = iter(cadena)
print( next(cadena_iterable) )
print( next(cadena_iterable) )
print( next(cadena_iterable) )
print( next(cadena_iterable) )

H
o
l
a

Muy bien, ahora ya sabemos qué son las funciones generadores, cómo utilizarlas, y también como como convertir algunos objetos a
iteradores. Os sugiero probar por vuestra cuenta más colecciones a ver si encontráis alguna más que se pueda iterar.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Funciones Lambda
Si empiezo diciendo que las funciones o expresiones lambda sirven para crear funciones anónimas, posiblemente me diréis ¿qué me
estás contando?, así que vamos a tomarlo con calma, pues estamos ante unas de las funcionalidades más potentes de Python a la vez
que más confusas para los principiantes.

Una función anónima, como su nombre indica es una función sin nombre. ¿Es posible ejecutar una función sin referenciar un nombre?
Pues sí, en Python podemos ejecutar una función sin definirla con def. De hecho son similares pero con una diferencia fundamental:

El contenido de una función lambda debe ser una única expresión en lugar de un bloque de acciones.

Y es que más allá del sentido de función que tenemos, con su nombre y sus acciones internas, una función en su sentido más trivial
significa realizar algo sobre algo. Por tanto podríamos decir que, mientras las funciones anónimas lambda sirven para realizar funciones
simples, las funciones definidas con def sirven para manejar tareas más extensas.

Si deconstruimos una función sencilla, podemos llegar a una función lambda. Por ejemplo tomad la siguiente función para doblar un
valor:

def doblar(num):
resultado = num*2
return resultado

doblar(2)

Vamos a simplificar el código lo máximo posible:

def doblar(num):
return num*2

Todavía más, podemos escribirlo todo en una sola línea:

def doblar(num): return num*2

Esta notación simple es la que una función lambda intenta replicar, fijaros, vamos a convertir la función en una función anónima:

lambda num: num*2

<function __main__.<lambda>>

¡Hualá! Aquí tenemos una función anónima con una entrada que recibe num, y una salida que devuelve num * 2.

Lo único que necesitamos hacer para utilizarla es guardarla en una variable y utilizarla tal como haríamos con una función normal:

doblar = lambda num: num*2

doblar(2)

Gracias a la flexibilidad de Python podemos implementar infinitas funciones simples.

Por ejemplo comprobar si un número es impar:

impar = lambda num: num%2 != 0


impar(5)

True

Darle la vuelta a una cadena utilizando slicing:

revertir = lambda cadena: cadena[::-1]


revertir("Hola")

'aloH'

Incluso podemos enviar varios valores, por ejemplo para sumar dos números:

sumar = lambda x,y: x+y


sumar(5,2)

7
Como véis podemos realizar cualquier cosa que se nos ocurra, siempre que lo podamos definir en una sola expresión.

Por ahora lo dejamos aquí, pero en las próximas lecciones veremos como utilizar la función lambda en conjunto con otras funciones
como filter() y map(), que es cuando sale a relucir su verdadero potencial.

Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Función filter()
Tal como su nombre indica filter significa filtrar, y es una de mis funciones favoritas, ya que a partir de una lista o iterador, y una función
condicional, es capaz de devolver una nueva colección con los elementos filtrados que cumplan la condición.

Por ejemplo, supongamos que tenemos una lista varios números y queremos filtrarla, quedándonos únicamente con los múltiples de 5...

def multiple(numero): # Primero declaramos una función condicional


if numero % 5 == 0: # Comprobamos si un numero es múltiple de cinco
return True # Sólo devolvemos True si lo es

numeros = [2, 5, 10, 23, 50, 33]

filter(multiple, numeros)

<filter at 0x257ac84abe0>

Si ejecutamos el filtro obtenemos un objeto de tipo filtro, pero podemos transformarlo en una lista fácilmente haciendo un cast:

list( filter(multiple, numeros) )

[5, 10, 50]

Por tanto cuando utilizamos la función filter() tenemos que enviar una función condicional, pero como recordaréis, no es necesario
definirla, podemos utlizar una función anónima lambda:

list( filter(lambda numero: numero%5 == 0, numeros) )

[5, 10, 50]

Así, en una sola línea hemos definido y ejecutado el filtro utilizando una función condicional anónima y una lista de numeros.

Filtrando objetos
Sin embargo, para mí, más allá de filtrar listas con valores simples, el verdadero potencial de filter() sale a relucir cuando necesitamos
filtrar varios objetos de una lista.

Por ejemplo, dada una lista con varias personas, nos gustaría filtrar únicamente las que son menores de edad:

class Persona:

def __init__(self, nombre, edad):


self.nombre = nombre
self.edad = edad

def __str__(self):
return "{} de {} años".format(self.nombre, self.edad)

personas = [
Persona("Juan", 35),
Persona("Marta", 16),
Persona("Manuel", 78),
Persona("Eduardo", 12)
]

Para hacerlo nos vamos a servir de una función lambda, comprobando el campo edad para cada persona:

menores = filter(lambda persona: persona.edad < 18, personas)

for menor in menores:


print(menor)

Marta de 16 años
Eduardo de 12 años

Sé que es un ejemplo sencillo, pero estoy seguro que os puede servir como base para realizar filtrados en muchos de vuestros
proyectos.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Función map()
Esta función trabaja de una forma muy similar a filter(), con la diferencia que en lugar de aplicar una condición a un elemento de una
lista o secuencia, aplica una función sobre todos los elementos y como resultado se devuelve un iterable de tipo map:

def doblar(numero):
return numero*2

numeros = [2, 5, 10, 23, 50, 33]

map(doblar, numeros)

<map at 0x212eb6e0748>

Fácilmente podemos transformar este iterable en una lista:

list(map(doblar, numeros))

[4, 10, 20, 46, 100, 66]

Y podemos simplificarlo con una función lambda para substituir la llamada de una función definida:

list( map(lambda x: x*2, numeros) )

[4, 10, 20, 46, 100, 66]

La función map() se utiliza mucho junto a expresiones lambda ya que permite ahorrarnos el esfuerzo de crear bucles for.

Además se puede utilizar sobre más de un iterable con la condición que tengan la misma longitud.

Por ejemplo si queremos multiplicar los números de dos listas:

a = [1, 2, 3, 4, 5]
b = [6, 7, 8, 9, 10]

list( map(lambda x,y : x*y, a,b) )

[7, 9, 11, 13, 15]

E incluso podemos extender la funcionalidad a tres listas o más:

c = [11, 12, 13, 14, 15]

list( map(lambda x,y,z : x*y*z, a,b,c) )

[66, 168, 312, 504, 750]

Mapeando objetos
Evidentemente, siempre que la utilicemos correctamente podemos mapear una serie de objetos sin ningún problema:

class Persona:

def __init__(self, nombre, edad):


self.nombre = nombre
self.edad = edad

def __str__(self):
return "{} de {} años".format(self.nombre, self.edad)

personas = [
Persona("Juan", 35),
Persona("Marta", 16),
Persona("Manuel", 78),
Persona("Eduardo", 12)
]

Por ejemplo una función para incrementar un año de edad a todas las personas de la lista:

def incrementar(p):
p.edad += 1
return p

personas = map(incrementar, personas)


for persona in personas:
print(persona)

Juan de 36 años
Marta de 17 años
Manuel de 79 años
Eduardo de 13 años

Claro que en este caso tenemos que utilizar una función definida porque no necesitamos actuar sobre la instancia, a no ser que nos
tomemos la molestia de rehacer todo el objeto:

personas = [
Persona("Juan", 35),
Persona("Marta", 16),
Persona("Manuel", 78),
Persona("Eduardo", 12)
]

personas = map(lambda p: Persona(p.nombre, p.edad+1), personas)

for persona in personas:


print(persona)

Juan de 36 años
Marta de 17 años
Manuel de 79 años
Eduardo de 13 años

Y con esto acabamos esta interesante funcionalidad.


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Expresiones regulares
Una de las tareas más utilizadas en la programación es la búsqueda de subcadenas o patrones dentro de otras cadenas de texto.

Las expresiones regulares, también conocidas como 'regex' o 'regexp', son patrones de búsqueda definidos con una sintaxis formal.
Siempre que sigamos sus reglas, podremos realizar búsquedas simples y avanzadas, que utilizadas en conjunto con otras
funcionalidades, las vuelven una de las opciones más útiles e importantes de cualquier lenguaje.

Sin embargo antes de utilizarlas hay que estar seguros de lo que hacemos, de ahí aquella famosa frase de Jamie Zawinski,
programador y hacker:

Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.

Que viene a decir:

Hay gente que, cuando se enfrenta a un problema, piensa "Ya sé, usaré expresiones regulares". Ahora tienen dos
problemas.

Métodos básicos
re.search: buscar un patrón en otra cadena

import re

texto = "En esta cadena se encuentra una palabra mágica"

re.search('mágica', texto)

<_sre.SRE_Match object; span=(40, 46), match='mágica'>

Como vemos, al realizar la búsqueda lo que nos encontramos es un objeto de tipo Match (encontrado), en lugar un simple True o False.

En cambio, si no se encontrase la palabra, no se devolvería nada (None):

re.search('hola', texto)

Por tanto, podemos utilizar la propia funcionalidad junto a un condicional sin ningún problema:

palabra = "mágica"

encontrado = re.search(palabra, texto)

if encontrado:
print("Se ha encontrado la palabra:", palabra)
else:
print("No se ha encontrado la palabra:", palabra)

Se ha encontrado la palabra: mágica

Sin embargo, volviendo al objeto devuelto de tipo Match, éste nos ofrece algunas opciones interesantes.

print( encontrado.start() ) # Posición donde empieza la coincidencia


print( encontrado.end() ) # Posición donde termina la coincidencia
print( encontrado.span() ) # Tupla con posiciones donde empieza y termina la coincidencia
print( encontrado.string ) # Cadena sobre la que se ha realizado la búsqueda

40
46
(40, 46)
En esta cadena se encuentra una palabra mágica

Como vemos, en este objeto se esconde mucha más información de la que parece a simple vista, luego seguiremos hablando de ellos.

re.match: buscar un patrón al principio de otra cadena


texto = "Hola mundo"
re.match('Hola', texto)

<_sre.SRE_Match object; span=(0, 4), match='Hola'>

texto = "Hola mundo"


re.match('Mola', texto) # no devuelve nada

re.split: dividir una cadena a partir de un patrón


texto = "Vamos a dividir esta cadena"
re.split(' ', texto)

['Vamos', 'a', 'dividir', 'esta', 'cadena']

re.sub: sustituye todas las coincidencias en una cadena


texto = "Hola amigo"
re.sub('amigo', 'amiga', texto)

'Hola amiga'

re.findall: buscar todas las coincidencias en una cadena


texto = "hola adios hola hola"
re.findall('hola', texto)

['hola', 'hola', 'hola']

Aquí se nos devuelve una lista, pero podríamos aplicar la función len() para saber el número:

len(re.findall('hola', texto))

Patrones con múltiples alternativas


Si queremos comprobar varias posibilidades, podemos utilizar una tubería | a modo de OR. Generalmente pondremos el listado de
alternativas entre paréntesis ():

texto = "hola adios hello bye"


re.findall('hola|hello', texto)

['hola', 'hello']

Patrones con sintaxis repetida


Otra posibilidad que se nos ofrece es la de buscar patrones con letras repetidas, y aquí es donde se empieza a poner interesante. Como
podemos o no saber de antemano el número de repeticiones hay varias formas de definirlos.

texto = "hla hola hoola hooola hooooola"

Antes de continuar, y para aligerar todo el proceso, vamos a crear una función capaz de ejecutar varios patrones en una lista sobre un
texto:

def buscar(patrones, texto):


for patron in patrones:
print( re.findall(patron, texto) )

patrones = ['hla', 'hola', 'hoola']


buscar(patrones, texto)

['hla']
['hola']
['hoola']

Con meta-carácter *
Lo utilizaremos para definir ninguna o más repeticiones de la letra a la izquierda del meta-carácter:

patrones = ['ho','ho*','ho*la','hu*la'] # 'ho', 'ho[0..N]', 'ho[0..N]la', 'hu[0..N]la'


buscar(patrones, texto)

['ho', 'ho', 'ho', 'ho']


['h', 'ho', 'hoo', 'hooo', 'hooooo']
['hla', 'hola', 'hoola', 'hooola', 'hooooola']
['hla']

Con meta-carácter +
Lo utilizaremos para definir una o más repeticiones de la letra a la izquierda del meta-carácter:

patrones = ['ho*', 'ho+'] # 'ho[0..N], 'ho[1..N]'


buscar(patrones, texto)
['h', 'ho', 'hoo', 'hooo', 'hooooo']
['ho', 'hoo', 'hooo', 'hooooo']

Con meta-carácter ?
Lo utilizaremos para definir una o ninguna repetición de la letra a la izquierda del meta-carácter:

patrones = ['ho*', 'ho+', 'ho?', 'ho?la'] # 'ho[0..N], 'ho[1..N]', 'ho[0..1]', 'ho[0..1]la'


buscar(patrones, texto)

['h', 'ho', 'hoo', 'hooo', 'hooooo']


['ho', 'hoo', 'hooo', 'hooooo']
['h', 'ho', 'ho', 'ho', 'ho']
['hla', 'hola']

Con número de repeticiones explícito {n}


Lo utilizaremos para definir 'n' repeticiones exactas de la letra a la izquierda del meta-carácter:

patrones = ['ho{0}la', 'ho{1}la', 'ho{2}la'] # 'ho[0]la', 'ho[1]la', 'ho[2]la'


buscar(patrones, texto)

['hla']
['hola']
['hoola']

Con número de repeticiones en un rango {n, m}


Lo utilizaremos para definir un número de repeticiones variable entre 'n' y 'm' de la letra a la izquierda del meta-carácter:

patrones = ['ho{0,1}la', 'ho{1,2}la', 'ho{2,9}la'] # 'ho[0..1]la', 'ho[1..2]la', 'ho[2..9]la'


buscar(patrones, texto)

['hla', 'hola']
['hola', 'hoola']
['hoola', 'hooola', 'hooooola']

Trabajando con conjuntos de caracteres [ ]


Cuando nos interese crear un patrón con distintos carácteres, podemos definir conjuntos entre paréntesis:

texto = "hala hela hila hola hula"

patrones = ['h[ou]la', 'h[aio]la', 'h[aeiou]la']


buscar(patrones, texto)

['hola', 'hula']
['hala', 'hila', 'hola']
['hala', 'hela', 'hila', 'hola', 'hula']

Evidentemente los podemos utilizar con repeticiones:

texto = "haala heeela hiiiila hoooooola"

patrones = ['h[ae]la', 'h[ae]*la', 'h[io]{3,9}la']


buscar(patrones, texto)

[]
['haala', 'heeela']
['hiiiila', 'hoooooola']

Exclusión en grupos [^ ]
Cuando utilizamos grupos podemos utilizar el operador de exclusión ^ para indicar una búsqueda contraria:

texto = "hala hela hila hola hula"

patrones = ['h[o]la', 'h[^o]la']


buscar(patrones, texto)

['hola']
['hala', 'hela', 'hila', 'hula']

Si excluimos una expresión regular con un grupo excluido, en realidad tenemos lo mismo:

Rangos [ - ]
Otra característica que hace ultra potentes los grupos, es la capacidad de definir rangos. Ejemplos de rangos:

[A-Z]: Cualquier carácter alfabético en mayúscula (no especial ni número)


[a-z]: Cualquier carácter alfabético en minúscula (no especial ni número)
[A-Za-z]: Cualquier carácter alfabético en minúscula o mayúscula (no especial ni número)
[A-z]: Cualquier carácter alfabético en minúscula o mayúscula (no especial ni número)
[0-9]: Cualquier carácter numérico (no especial ni alfabético)
[a-zA-Z0-9]: Cualquier carácter alfanumérico (no especial)

Tened en cuenta que cualquier rango puede ser excluido para conseguir el patrón contrario.

texto = "hola h0la Hola mola m0la M0la"

patrones = ['h[a-z]la', 'h[0-9]la', '[A-z]{4}', '[A-Z][A-z0-9]{3}']


buscar(patrones, texto)

['hola']
['h0la']
['hola', 'Hola', 'mola']
['Hola', 'M0la']

Códigos escapados \
Si cada vez que quisiéramos definir un patrón variable tuviéramos que crear rangos, al final tendríamos expresiones regulares gigantes.
Por suerte su sintaxis también acepta una serie de caracteres escapados que tienen un significo único. Algunos de los más importantes
son:

Código Significado

\d numérico

\D no numérico

\s espacio en blanco

\S no espacio en blanco

\w alfanumérico

\W no alfanumérico

El problema que encontraremos en Python a la hora de definir código escapado, es que las cadenas no tienen en cuenta el \ a no ser
que especifiquemos que son cadenas en crudo (raw), por lo que tendremos que precedir las expresiones regulares con una 'r'.

texto = "Este curso de Python se publicó en el año 2016"

patrones = [r'\d+', r'\D+', r'\s', r'\S+', r'\w+', r'\W+']


buscar(patrones, texto)

['2016']
['Este curso de Python se publicó en el año ']
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
['Este', 'curso', 'de', 'Python', 'se', 'publicó', 'en', 'el', 'año', '2016']
['Este', 'curso', 'de', 'Python', 'se', 'publicó', 'en', 'el', 'año', '2016']
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']

Por mi parte lo vamos a dejar aquí, pero el mundo de las expresiones regulares es gigantesco y daría para un curso entero. Os animo a
seguir aprendiendo leyendo documentación y buscando ejemplos.

Aquí os dejo algunos enlaces que quizá os pueden servir, en inglés:

Documentación
Hay docenas y docenas de códigos especiales, si queréis echar un vistazo a todos ellos podéis consultar la documentación oficial:

https://docs.python.org/3.5/library/re.html#regular-expression-syntax

Un resumen por parte de Google Eduactión:

https://developers.google.com/edu/python/regular-expressions

Otro resumen muy interesante sobre el tema:

https://www.tutorialspoint.com/python/python_reg_expressions.htm

Un par de documentos muy trabajados con ejemplos básicos y avanzados:

http://www.python-course.eu/python3_re.php
http://www.python-course.eu/python3_re_advanced.php
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Docstrings
En Python todos los objetos cuentan con una variable especial llamada doc gracias a la que podemos describir para qué sirven los y
cómo se usan los objetos. Estas variables reciben el nombre de docstrings, cadenas de documentación.

Docstrings en funciones
Python implementa un sistema muy sencillo para establecer el valor de las docstrings, únicamente tenemos que crear un comentario en
la primera línea después de la declaración.

def hola(arg):
"""Este es el docstring de la función"""
print("Hola", arg, "!")

hola("Héctor")

Hola Héctor !

Para consultar la documentación es tan sencillo como utilizar la función reservada help y pasarle el objeto:

help(hola)

Help on function hola in module __main__:

hola(arg)
Este es el docstring de la función

Docstrings en clases y métodos


De la misma forma podemos establecer la documentación de la clase después de la definición, y de los métodos, como si fueran
funciones:

class Clase:
""" Este es el docstring de la clase"""

def __init__(self):
"""Este es el docstring del inicializador de clase"""

def metodo(self):
"""Este es el docstring del metodo de clase"""

o = Clase()

help(o)

Help on Clase in module __main__ object:

class Clase(builtins.object)
| Este es el docstring de la clase
|
| Methods defined here:
|
| __init__(self)
| Este es el docstring del inicializador de clase
|
| metodo(self)
| Este es el docstring del metodo de clase
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)

Docstrings en scripts y módulos


Cuando tenemos un script o módulo, la primera línea del mismo hará referencia al docstring del módulo, en él deberíamos explicar el
funcionamiento del mismo:

mi_modulo.py

"""Este es el docstring del módulo"""


"""Este es el docstring del módulo"""

def despedir():
"""Este es el docstring de la función despedir"""
print("Adiós! Me estoy despidiendo desde la función despedir() del módulo prueba")

def saludar():
"""Este es el docstring de la función saludar"""
print("Hola! Te estoy saludando desde la función saludar() del módulo prueba")

import mi_modulo # Este módulo lo he creado en el directorio

help(mi_modulo)

Help on module mi_modulo:

NAME
mi_modulo - Este es el docstring del módulo

FUNCTIONS
despedir()
Este es el docstring de la función despedir

saludar()
Este es el docstring de la función saludar

FILE
c:\cursopython\fase 4 - temas avanzados\tema 16 - docomentación y pruebas\mi_modulo.py

help(mi_modulo.despedir)

Help on function despedir in module mi_modulo:

despedir()
Este es el docstring de la función despedir

Como dato curioso, también podemos listar las variables y funciones del módulo con la función dir:

dir(mi_modulo)

['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'despedir',
'saludar']

Como vemos muchas de ellas son especiales, seguro que muchas os suenan, os invito a comprobar sus valores:

mi_modulo.__name__

'mi_modulo'

mi_modulo.__package__

''

mi_modulo.__doc__

'Este es el docstring del módulo'

Docstrings en paquetes
En el caso de los paquetes el docstring debemos establecerlo en la primera línea del inicializador init:

__init__.py
""" Este es el docstring de mi_paquete """

import mi_paquete

help(mi_paquete)
Help on package mi_paquete:

NAME
mi_paquete - Este es el docstring de mi_paquete

PACKAGE CONTENTS
adios (package)
hola (package)

FILE
c:\cursopython\fase 4 - temas avanzados\tema 16 - docomentación y pruebas\mi_paquete\__init__.py

Creando buena documentación


Podéis aprender a crear buena documentación tomando como referencia contenido de las librerías internas de Python:

help(print)

Help on built-in function print in module builtins:

print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

Prints the values to a stream, or to sys.stdout by default.


Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.

help(len)

Help on built-in function len in module builtins:

len(obj, /)
Return the number of items in a container.

help(str)

Help on class str in module builtins:

class str(object)
| str(object='') -> str
| str(bytes_or_buffer[, encoding[, errors]]) -> str
|
| Create a new string object from the given object. If encoding or
| errors is specified, then the object must expose a data buffer
| that will be decoded using the given encoding and error handler.
| Otherwise, returns the result of object.__str__() (if defined)
| or repr(object).
| encoding defaults to sys.getdefaultencoding().
| errors defaults to 'strict'.
|
| Methods defined here:
|
| __add__(self, value, /)
| Return self+value.
|
| __contains__(self, key, /)
| Return key in self.
|
| __eq__(self, value, /)
| Return self==value.
|
| __format__(...)
| S.__format__(format_spec) -> str
|
| Return a formatted version of S as described by format_spec.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __getitem__(self, key, /)
| Return self[key].
|
| __getnewargs__(...)
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __iter__(self, /)
| Implement iter(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __len__(self, /)
| Return len(self).
|
| __lt__(self, value, /)
| Return self<value.
|
| __mod__(self, value, /)
| Return self%value.
|
| __mul__(self, value, /)
| Return self*value.n
|
| __ne__(self, value, /)
| Return self!=value.
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __repr__(self, /)
| Return repr(self).
|
| __rmod__(self, value, /)
| Return value%self.
|
| __rmul__(self, value, /)
| Return self*value.
|
| __sizeof__(...)
| S.__sizeof__() -> size of S in memory, in bytes
|
| __str__(self, /)
| Return str(self).
|
| capitalize(...)
| S.capitalize() -> str
|
| Return a capitalized version of S, i.e. make the first character
| have upper case and the rest lower case.
|
| casefold(...)
| S.casefold() -> str
|
| Return a version of S suitable for caseless comparisons.
|
| center(...)
| S.center(width[, fillchar]) -> str
|
| Return S centered in a string of length width. Padding is
| done using the specified fill character (default is a space)
|
| count(...)
| S.count(sub[, start[, end]]) -> int
|
| Return the number of non-overlapping occurrences of substring sub in
| string S[start:end]. Optional arguments start and end are
| interpreted as in slice notation.
|
| encode(...)
| S.encode(encoding='utf-8', errors='strict') -> bytes
|
| Encode S using the codec registered for encoding. Default encoding
| is 'utf-8'. errors may be given to set a different error
| handling scheme. Default is 'strict' meaning that encoding errors raise
| a UnicodeEncodeError. Other possible values are 'ignore', 'replace' and
| 'xmlcharrefreplace' as well as any other name registered with
| codecs.register_error that can handle UnicodeEncodeErrors.
|
| endswith(...)
| S.endswith(suffix[, start[, end]]) -> bool
|
| Return True if S ends with the specified suffix, False otherwise.
| With optional start, test S beginning at that position.
| With optional end, stop comparing S at that position.
| suffix can also be a tuple of strings to try.
|
| expandtabs(...)
| S.expandtabs(tabsize=8) -> str
|
| Return a copy of S where all tab characters are expanded using spaces.
| If tabsize is not given, a tab size of 8 characters is assumed.
|
| find(...)
| S.find(sub[, start[, end]]) -> int
|
| Return the lowest index in S where substring sub is found,
| such that sub is contained within S[start:end]. Optional
| arguments start and end are interpreted as in slice notation.
|
| Return -1 on failure.
|
| format(...)
| S.format(*args, **kwargs) -> str
|
| Return a formatted version of S, using substitutions from args and kwargs.
| The substitutions are identified by braces ('{' and '}').
|
| format_map(...)
| S.format_map(mapping) -> str
|
| Return a formatted version of S, using substitutions from mapping.
| The substitutions are identified by braces ('{' and '}').
|
| index(...)
| S.index(sub[, start[, end]]) -> int
|
| Like S.find() but raise ValueError when the substring is not found.
|
| isalnum(...)
| S.isalnum() -> bool
|
| Return True if all characters in S are alphanumeric
| and there is at least one character in S, False otherwise.
|
| isalpha(...)
| S.isalpha() -> bool
|
| Return True if all characters in S are alphabetic
| and there is at least one character in S, False otherwise.
|
| isdecimal(...)
| S.isdecimal() -> bool
|
| Return True if there are only decimal characters in S,
| False otherwise.
|
| isdigit(...)
| S.isdigit() -> bool
|
| Return True if all characters in S are digits
| and there is at least one character in S, False otherwise.
|
| isidentifier(...)
| S.isidentifier() -> bool
|
| Return True if S is a valid identifier according
| to the language definition.
|
| Use keyword.iskeyword() to test for reserved identifiers
| such as "def" and "class".
|
| islower(...)
| S.islower() -> bool
|
| Return True if all cased characters in S are lowercase and there is
| at least one cased character in S, False otherwise.
|
| isnumeric(...)
| S.isnumeric() -> bool
|
| Return True if there are only numeric characters in S,
| False otherwise.
|
| isprintable(...)
| S.isprintable() -> bool
|
| Return True if all characters in S are considered
| printable in repr() or S is empty, False otherwise.
|
| isspace(...)
| S.isspace() -> bool
|
| Return True if all characters in S are whitespace
| and there is at least one character in S, False otherwise.
|
| istitle(...)
| S.istitle() -> bool
|
| Return True if S is a titlecased string and there is at least one
| character in S, i.e. upper- and titlecase characters may only
| follow uncased characters and lowercase characters only cased ones.
| Return False otherwise.
|
| isupper(...)
| S.isupper() -> bool
|
| Return True if all cased characters in S are uppercase and there is
| at least one cased character in S, False otherwise.
|
| join(...)
| S.join(iterable) -> str
|
| Return a string which is the concatenation of the strings in the
| iterable. The separator between elements is S.
|
| ljust(...)
| S.ljust(width[, fillchar]) -> str
|
| Return S left-justified in a Unicode string of length width. Padding is
| done using the specified fill character (default is a space).
|
| lower(...)
| S.lower() -> str
|
| Return a copy of the string S converted to lowercase.
|
| lstrip(...)
| S.lstrip([chars]) -> str
|
| Return a copy of the string S with leading whitespace removed.
| If chars is given and not None, remove characters in chars instead.
|
| partition(...)
| S.partition(sep) -> (head, sep, tail)
|
| Search for the separator sep in S, and return the part before it,
| the separator itself, and the part after it. If the separator is not
| found, return S and two empty strings.
|
| replace(...)
| S.replace(old, new[, count]) -> str
|
| Return a copy of S with all occurrences of substring
| old replaced by new. If the optional argument count is
| given, only the first count occurrences are replaced.
|
| rfind(...)
| S.rfind(sub[, start[, end]]) -> int
|
| Return the highest index in S where substring sub is found,
| such that sub is contained within S[start:end]. Optional
| arguments start and end are interpreted as in slice notation.
|
| Return -1 on failure.
|
| rindex(...)
| S.rindex(sub[, start[, end]]) -> int
|
| Like S.rfind() but raise ValueError when the substring is not found.
|
| rjust(...)
| S.rjust(width[, fillchar]) -> str
|
| Return S right-justified in a string of length width. Padding is
| done using the specified fill character (default is a space).
|
| rpartition(...)
| S.rpartition(sep) -> (head, sep, tail)
|
| Search for the separator sep in S, starting at the end of S, and return
| the part before it, the separator itself, and the part after it. If the
| separator is not found, return two empty strings and S.
|
| rsplit(...)
| S.rsplit(sep=None, maxsplit=-1) -> list of strings
|
| Return a list of the words in S, using sep as the
| delimiter string, starting at the end of the string and
| working to the front. If maxsplit is given, at most maxsplit
| splits are done. If sep is not specified, any whitespace string
| is a separator.
|
| rstrip(...)
| S.rstrip([chars]) -> str
|
| Return a copy of the string S with trailing whitespace removed.
| If chars is given and not None, remove characters in chars instead.
|
| split(...)
| S.split(sep=None, maxsplit=-1) -> list of strings
|
| Return a list of the words in S, using sep as the
| delimiter string. If maxsplit is given, at most maxsplit
| splits are done. If sep is not specified or is None, any
| whitespace string is a separator and empty strings are
| removed from the result.
|
| splitlines(...)
| S.splitlines([keepends]) -> list of strings
|
| Return a list of the lines in S, breaking at line boundaries.
| Line breaks are not included in the resulting list unless keepends
| is given and true.
|
| startswith(...)
| S.startswith(prefix[, start[, end]]) -> bool
|
| Return True if S starts with the specified prefix, False otherwise.
| With optional start, test S beginning at that position.
| With optional end, stop comparing S at that position.
| prefix can also be a tuple of strings to try.
|
| strip(...)
| S.strip([chars]) -> str
|
| Return a copy of the string S with leading and trailing
| whitespace removed.
| If chars is given and not None, remove characters in chars instead.
|
| swapcase(...)
| S.swapcase() -> str
|
| Return a copy of S with uppercase characters converted to lowercase
| and vice versa.
|
| title(...)
| S.title() -> str
|
| Return a titlecased version of S, i.e. words start with title case
| characters, all remaining cased characters have lower case.
|
| translate(...)
| S.translate(table) -> str
|
| Return a copy of the string S in which each character has been mapped
| through the given translation table. The table must implement
| lookup/indexing via __getitem__, for instance a dictionary or list,
| mapping Unicode ordinals to Unicode ordinals, strings, or None. If
| this operation raises LookupError, the character is left untouched.
| Characters mapped to None are deleted.
|
| upper(...)
| S.upper() -> str
|
| Return a copy of S converted to uppercase.
|
| zfill(...)
| S.zfill(width) -> str
|
| Pad a numeric string S with zeros on the left, to fill a field
| of the specified width. The string S is never truncated.
|
| ----------------------------------------------------------------------
| Static methods defined here:
|
| maketrans(x, y=None, z=None, /)
| Return a translation table usable for str.translate().
|
| If there is only one argument, it must be a dictionary mapping Unicode
| ordinals (integers) or characters to Unicode ordinals, strings or None.
| Character keys will be then converted to ordinals.
| If there are two arguments, they must be strings of equal length, and
| in the resulting dictionary, each character in x will be mapped to the
| character at the same position in y. If there is a third argument, it
| must be a string, whose characters will be mapped to None in the result.

import datetime

help(datetime)

Help on module datetime:

NAME
datetime - Fast implementation of the datetime type.

CLASSES
builtins.object
date
datetime
time
timedelta
tzinfo
timezone

class date(builtins.object)
| date(year, month, day) --> date object
|
| Methods defined here:
|
| __add__(self, value, /)
| Return self+value.
|
| __eq__(self, value, /)
| Return self==value.
|
| __format__(...)
| Formats self with strftime.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __lt__(self, value, /)
| Return self<value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __radd__(self, value, /)
| Return value+self.
|
| __reduce__(...)
| __reduce__() -> (cls, state)
|
| __repr__(self, /)
| Return repr(self).
|
| __rsub__(self, value, /)
| Return value-self.
|
| __str__(self, /)
| Return str(self).
|
| __sub__(self, value, /)
| Return self-value.
|
| ctime(...)
| Return ctime() style string.
|
| fromordinal(...) from builtins.type
| int -> date corresponding to a proleptic Gregorian ordinal.
|
| fromtimestamp(...) from builtins.type
| timestamp -> local date from a POSIX timestamp (like time.time()).
|
| isocalendar(...)
| Return a 3-tuple containing ISO year, week number, and weekday.
|
| isoformat(...)
| Return string in ISO 8601 format, YYYY-MM-DD.
|
| isoweekday(...)
| Return the day of the week represented by the date.
| Monday == 1 ... Sunday == 7
|
| replace(...)
| Return date with new specified fields.
|
| strftime(...)
| format -> strftime() style string.
|
| timetuple(...)
| Return time tuple, compatible with time.localtime().
|
| today(...) from builtins.type
| Current date or datetime: same as self.__class__.fromtimestamp(time.time()).
|
| toordinal(...)
| Return proleptic Gregorian ordinal. January 1 of year 1 is day 1.
|
| weekday(...)
| Return the day of the week represented by the date.
| Monday == 0 ... Sunday == 6
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| day
|
| month
|
| year
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| max = datetime.date(9999, 12, 31)
|
| min = datetime.date(1, 1, 1)
|
| resolution = datetime.timedelta(1)

class datetime(date)
| datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
|
| The year, month and day arguments are required. tzinfo may be None, or an
| instance of a tzinfo subclass. The remaining arguments may be ints.
|
| Method resolution order:
| datetime
| date
| builtins.object
|
| Methods defined here:
|
| __add__(self, value, /)
| Return self+value.
|
| __eq__(self, value, /)
| Return self==value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __lt__(self, value, /)
| Return self<value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __radd__(self, value, /)
| Return value+self.
|
| __reduce__(...)
| __reduce__() -> (cls, state)
|
| __repr__(self, /)
| Return repr(self).
|
| __rsub__(self, value, /)
| Return value-self.
|
| __str__(self, /)
| Return str(self).
|
| __sub__(self, value, /)
| Return self-value.
|
| astimezone(...)
| tz -> convert to local time in new timezone tz
|
| combine(...) from builtins.type
| date, time -> datetime with same date and time fields
|
| ctime(...)
| Return ctime() style string.
|
| date(...)
| Return date object with same year, month and day.
|
| dst(...)
| Return self.tzinfo.dst(self).
|
| fromtimestamp(...) from builtins.type
| timestamp[, tz] -> tz's local time from POSIX timestamp.
|
| isoformat(...)
| [sep] -> string in ISO 8601 format, YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM].
|
| sep is used to separate the year from the time, and defaults to 'T'.
|
| now(tz=None) from builtins.type
| Returns new datetime object representing current time local to tz.
|
| tz
| Timezone object.
|
| If no tz is specified, uses local timezone.
|
| replace(...)
| Return datetime with new specified fields.
|
| strptime(...) from builtins.type
| string, format -> new datetime parsed from a string (like time.strptime()).
|
| time(...)
| Return time object with same time but with tzinfo=None.
|
| timestamp(...)
| Return POSIX timestamp as float.
|
| timetuple(...)
| Return time tuple, compatible with time.localtime().
|
| timetz(...)
| Return time object with same time and tzinfo.
|
| tzname(...)
| Return self.tzinfo.tzname(self).
|
| utcfromtimestamp(...) from builtins.type
| Construct a naive UTC datetime from a POSIX timestamp.
|
| utcnow(...) from builtins.type
| Return a new datetime representing UTC day and time.
|
| utcoffset(...)
| Return self.tzinfo.utcoffset(self).
|
| utctimetuple(...)
| Return UTC time tuple, compatible with time.localtime().
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| hour
|
| microsecond
|
| minute
|
| second
|
| tzinfo
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| max = datetime.datetime(9999, 12, 31, 23, 59, 59, 999999)
|
| min = datetime.datetime(1, 1, 1, 0, 0)
|
| resolution = datetime.timedelta(0, 0, 1)
|
| ----------------------------------------------------------------------
| Methods inherited from date:
|
| __format__(...)
| Formats self with strftime.
|
| fromordinal(...) from builtins.type
| int -> date corresponding to a proleptic Gregorian ordinal.
|
| isocalendar(...)
| Return a 3-tuple containing ISO year, week number, and weekday.
|
| isoweekday(...)
| Return the day of the week represented by the date.
| Monday == 1 ... Sunday == 7
|
| strftime(...)
| format -> strftime() style string.
|
| today(...) from builtins.type
| Current date or datetime: same as self.__class__.fromtimestamp(time.time()).
|
| toordinal(...)
| Return proleptic Gregorian ordinal. January 1 of year 1 is day 1.
|
| weekday(...)
| Return the day of the week represented by the date.
| Monday == 0 ... Sunday == 6
|
| ----------------------------------------------------------------------
| Data descriptors inherited from date:
|
| day
|
| month
|
| year

class time(builtins.object)
| time([hour[, minute[, second[, microsecond[, tzinfo]]]]]) --> a time object
|
| All arguments are optional. tzinfo may be None, or an instance of
| a tzinfo subclass. The remaining arguments may be ints.
|
| Methods defined here:
|
| __eq__(self, value, /)
| Return self==value.
|
| __format__(...)
| Formats self with strftime.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __lt__(self, value, /)
| Return self<value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __reduce__(...)
| __reduce__() -> (cls, state)
|
| __repr__(self, /)
| Return repr(self).
|
| __str__(self, /)
| Return str(self).
|
| dst(...)
| Return self.tzinfo.dst(self).
|
| isoformat(...)
| Return string in ISO 8601 format, HH:MM:SS[.mmmmmm][+HH:MM].
|
| replace(...)
| Return time with new specified fields.
|
| strftime(...)
| format -> strftime() style string.
|
| tzname(...)
| Return self.tzinfo.tzname(self).
|
| utcoffset(...)
| Return self.tzinfo.utcoffset(self).
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| hour
|
| microsecond
|
| minute
|
| second
|
| tzinfo
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| max = datetime.time(23, 59, 59, 999999)
|
| min = datetime.time(0, 0)
|
| resolution = datetime.timedelta(0, 0, 1)

class timedelta(builtins.object)
| Difference between two datetime values.
|
| Methods defined here:
|
| __abs__(self, /)
| abs(self)
|
| __add__(self, value, /)
| Return self+value.
|
| __bool__(self, /)
| self != 0
|
| __divmod__(self, value, /)
| Return divmod(self, value).
|
| __eq__(self, value, /)
| Return self==value.
|
| __floordiv__(self, value, /)
| Return self//value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __lt__(self, value, /)
| Return self<value.
|
| __mod__(self, value, /)
| Return self%value.
|
| __mul__(self, value, /)
| Return self*value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __neg__(self, /)
| -self
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __pos__(self, /)
| +self
|
| __radd__(self, value, /)
| Return value+self.
|
| __rdivmod__(self, value, /)
| Return divmod(value, self).
|
| __reduce__(...)
| __reduce__() -> (cls, state)
|
| __repr__(self, /)
| Return repr(self).
|
| __rfloordiv__(self, value, /)
| Return value//self.
|
| __rmod__(self, value, /)
| Return value%self.
|
| __rmul__(self, value, /)
| Return value*self.
|
| __rsub__(self, value, /)
| Return value-self.
|
| __rtruediv__(self, value, /)
| Return value/self.
|
| __str__(self, /)
| Return str(self).
|
| __sub__(self, value, /)
| Return self-value.
|
| __truediv__(self, value, /)
| Return self/value.
|
| total_seconds(...)
| Total seconds in the duration.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| days
| Number of days.
|
| microseconds
| Number of microseconds (>= 0 and less than 1 second).
|
| seconds
| Number of seconds (>= 0 and less than 1 day).
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| max = datetime.timedelta(999999999, 86399, 999999)
|
| min = datetime.timedelta(-999999999)
|
| resolution = datetime.timedelta(0, 0, 1)

class timezone(tzinfo)
| Fixed offset from UTC implementation of tzinfo.
|
| Method resolution order:
| timezone
| tzinfo
| builtins.object
|
| Methods defined here:
|
| __eq__(self, value, /)
| Return self==value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getinitargs__(...)
| pickle support
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __lt__(self, value, /)
| Return self<value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __repr__(self, /)
| Return repr(self).
|
| __str__(self, /)
| Return str(self).
|
| dst(...)
| Return None.
|
| fromutc(...)
| datetime in UTC -> datetime in local time.
|
| tzname(...)
| If name is specified when timezone is created, returns the name. Otherwise returns offset as 'UTC(
+|-)HH:MM'.
|
| utcoffset(...)
| Return fixed offset.
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| max = datetime.timezone(datetime.timedelta(0, 86340))
|
| min = datetime.timezone(datetime.timedelta(-1, 60))
|
| utc = datetime.timezone.utc
|
| ----------------------------------------------------------------------
| Methods inherited from tzinfo:
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __reduce__(...)
| -> (cls, state)

class tzinfo(builtins.object)
| Abstract base class for time zone info objects.
|
| Methods defined here:
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __reduce__(...)
| -> (cls, state)
|
| dst(...)
| datetime -> DST offset in minutes east of UTC.
|
| fromutc(...)
| datetime in UTC -> datetime in local time.
|
| tzname(...)
| datetime -> string name of time zone.
|
| utcoffset(...)
| datetime -> timedelta showing offset from UTC, negative values indicating West of UTC

DATA
MAXYEAR = 9999
MINYEAR = 1
datetime_CAPI = <capsule object "datetime.datetime_CAPI">

FILE
c:\program files\anaconda3\lib\datetime.py

Recordad, una buena documentación siempre dará respuesta a las dos preguntas básicas: ¿Para qué sirve? y ¿Cómo se utiliza?
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Pydoc
En la lección anterior aprendimos que utilizando la función help podemos mostrar información formateada por la consola. Pues en
realidad esta función hace uso del módulo pydoc para generar la documentación a partir de sus docstrings.

Desde la terminal no podemos utilizar la función help, pero sí existe la posibilidad de utilizar pydoc de forma similar.

Por ejemplo podemos consultar la documentación de scripts, módulos y clases utilizando la sintaxis:

pydoc nombre.py
También podemos utilizar la misma sintaxis para los paquetes:

pydoc nombre_paquete

Documentación en HTML
Una función interesante de Pydoc es que podemos generar la documentación de nuestro código utilizando la siguiente sintaxis:

pydoc -w nombre.py
Si tenemos un paquete podemos generar toda la documentación de forma recursiva de la siguiente forma estando en el directorio del
paquete:

pydoc -w .\
Esto generará toda la documentación del paquete, subpaquete, clases, métodos y funciones. Está bien para publicarla en Internet, pero
también podemos consultarla directamente en local lanzando un servidor temporal, desde el directorio del paquete:

pydoc -p 8000
Aunque esto mostrará la documentación general de Python en ese momento, a parte de nuestro módulo.

Existen otras alternativas más bonitas para generar documentación, como los módulos Epydoc o Sphinx, pero al ser externos a Python
no los trataremos en este curso.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Doctest
Si por un lado las docstrings nos permitían describir documentación, los doctest nos permiten combinar pruebas en la propia
documentación.

Este concepto de integrar las pruebas en la documentación nos ayuda a mantener las pruebas actualizadas, y además nos sirve como
ejemplo de utilización del código, ayudándonos a explicar su propósito.

Para utilizar doctests hay que inidicar una línea dentro de la documentación de la siguiente forma:

>>>
De esta Python entenderá que debe ejecutar el contenido dentro del comentario como si fuera código normal, y lo hará hasta que
encuentre una línea en blanco (o llegue al final de la documentación).

La mejor forma de ver a doctest en acción.

Definiendo pruebas
Por regla general cada prueba va ligada a una funcionalidad, pueden ser funciones, clases o sus métodos. Por ejemplo, dada una
función suma...

def suma(a, b):


"""Esta función recibe dos parámetros y devuelve la suma de ambos"""
return a+b

Para realizar una prueba dentro de la función, vamos a ejecutar un código de prueba de la propia suma:

def suma(a, b):


"""Esta función recibe dos parámetros y devuelve la suma de ambos

>>> suma(5,10)
"""
return a+b

Bien, ya tenemos la prueba, pero ahora nos falta indicarle a doctest cuál es el resultado que debería devolver la prueba, y eso lo
indicaremos en la siguiente línea:

>>>

def suma(a, b):


"""Esta función recibe dos parámetros y devuelve la suma de ambos

>>> suma(5,10)
15
"""
return a+b

¡Muy bien! Ahora tenemos que ejecutar la pruebas y ver si funciona o no, pero antes tenemos que adaptar el código.

Ejecutando pruebas en un módulo


Para ejecutar pruebas tendremos que utilizar la terminal, así vamos a guardar la función en un script test.py como si fuera un módulo
con funciones.

Ahora justo al final indicaremos que se ejecten las pruebas doctest de las funciones del módulo escribiendo el siguiente código abajo del
todo:

import doctest
doctest.testmod() # Notar que mod significa módulo
Esto sería suficiente, pero con el objetivo de evitar que este código se ejecute al importarlo desde otro lugar, se suele indicar de la
siguiente forma:

if __name__ == "__main__":
import doctest
doctest.testmod()
Así únicamente se lanzarán las pruebas al ejecutar directamente el módulo, y ya podremos ejecutar el módulo desde la terminal:

python test.py
Como resultado veremos que no se muestra nada. Eso no significa que no se ejecute nuestra prueba, sino que esta ha funcionado
correctamente y no hay fallos.
Si queremos podemos mostrar todo el registro de ejecución pasando un argumento -v a python justo al final:

python test.py -v
Y entonces veremos el siguiente resultado:

Trying:
suma(5,10)
Expecting:
15
ok
1 items had no tests:
__main__
1 items passed all tests:
1 tests in __main__.suma
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
En el que se prueba el código suma(5,10), se espera 15 y el resultado es ok; un resumen y finalmente Test passed.

Creando varias pruebas


Evidentemente podemos definir múltiples pruebas. Probemos también alguna que sabemos que es incorrecta:

def suma(a, b):


"""Esta función recibe dos parámetros y devuelve la suma de ambos

>>> suma(5,10)
15

>>> suma(0,0)
1

>>> suma(-5,7)
2
"""
return a+b

Ahora, si ejecutamos el script de forma normal...

python test.py
A diferencia que antes sí que nos muestra algo, indicándonos que uno de los tests a fallado:

**********************************************************************
File "test.py", line 7, in __main__.suma
Failed example:
suma(0,0)
Expected:
1
Got:
0
**********************************************************************
1 items had failures:
1 of 3 in __main__.suma
***Test Failed*** 1 failures.
La cuestión ahora sería revisar el test si es incorrecto, o adaptar la función para que devuelve el resultado esperado en el test.
Evidentemente en este caso hemos hecho un test incorrecto a propósito así que simplemente lo borraríamos.

Pruebas para guiarnos en el desarrollo


Una de las ventajas de usar tests es que podemos utilizarlos para detectar posibles fallas. En el siguiente ejemplo vamos a guiarnos de
los tests para implementar correctamente una función.

def palindromo(palabra):
"""Comprueba si una palabra es un palíndrimo, si lo es devuelve True y si no False
Los palíndromos son palabras o frases que se leen igual de izquierda a derecha que de derecha a izquierda.

>>> palindromo("radar")
True

>>> palindromo("somos")
True

>>> palindromo("holah")
False
"""
if palabra == palabra[::-1]:
return True
else:
return False

Bien, ¿pero que ocurre si hacemos el siguiente test?

>>> palindromo("Ana")
True
Pues que nos falla:

**********************************************************************
File "test.py", line 11, in __main__.palindromo
Failed example:
palindromo("Ana")
Expected:
True
Got:
False
**********************************************************************
1 items had failures:
1 of 3 in __main__.palindromo
***Test Failed*** 1 failures.
Claro, es que Ana empieza por mayúscula, pero hay que recordar que un palíndrimo lo es si se pronuncia igual, por tanto las
mayúsculas no debes afectar. En este caso por tanto tendríamos que readaptar nuestro código para prevenir el error:

def palindromo(palabra):
"""Comprueba si una palabra es un palíndrimo, si lo es devuelve True y si no False
Los palíndromos son palabras o frases que se leen igual de izquierda a derecha que de derecha a izquierda.

>>> palindromo("radar")
True

>>> palindromo("somos")
True

>>> palindromo("holah")
False

>>> palindromo("Ana")
True
"""
if palabra.lower() == palabra[::-1].lower():
return True
else:
return False

Y en el test de la frase... ¿"Atar a la rata"? Esto también es un palíndromo:

>>> palindromo("Atar a la rata")


True

**********************************************************************
Failed example:
palindromo("Atar a la rata")
Expected:
True
Got:
False
**********************************************************************
1 items had failures:
1 of 4 in __main__.palindromo
***Test Failed*** 1 failures.
¡Nos falla de nuevo! El problema en este caso son los espacios, ya que estos no se leen pero si existen en la cadena, la frase no
concuerda.

def palindromo(palabra):
"""Comprueba si una palabra es un palíndrimo, si lo es devuelve True y si no False
Los palíndromos son palabras o frases que se leen igual de izquierda a derecha que de derecha a izquierda.

>>> palindromo("radar")
True

>>> palindromo("somos")
True

>>> palindromo("holah")
False

>>> palindromo("Atar a la rata")


True
"""
if palabra.lower().replace(" ", "") == palabra[::-1].lower().replace(" ", ""):
return True
else:
return False

Todavía habría algunos casos como los acentos que nos darían fallos, pero ya véis por donde va la lógica. Se trata de guiarnos a partir
de los tests para crear la función correctamente.

De hecho en el mundo de la programación hay una práctica conocida como TDD, Test Driven Development o Desarrollo guiado por
pruebas que trata de escribir las pruebas primero y luego refactorizar para ir puliendo la funcionalidad.

Es una práctica algo avanzada y no la veremos en este curso, pero vale la pena comentarla.

Tests avanzados
Hasta ahora hemos hecho unos tests muy simples, pero los doctests son muy flexibles. Algunas de sus funcionalidades interesantes
son la posibilidad de ejecutar bloques de código o la captura de excepciones.

Para crear un test que incluya un bloque de código, debemos utilizar las sentencias anidadas para simular tabulaciones:

...

def doblar(lista):
"""Dobla el valor de los elementos de una lista
>>> l = [1, 2, 3, 4, 5]
>>> doblar(l)
[2, 4, 6, 8, 10]
"""
return [n*2 for n in lista]

En este caso hemos creado la lista del test manualmente, pero podríamos generarla con un bucle utilizando sentencias anidadas:

def doblar(lista):
"""Dobla el valor de los elementos de una lista
>>> l = [1, 2, 3, 4, 5]
>>> doblar(l)
[2, 4, 6, 8, 10]

>>> l = []
>>> for i in range(10):
... l.append(i)
>>> doblar(l)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
"""
return [n*2 for n in lista]

Si ejecutamos el script monitorizando todo:

python test.py -v
Podemos observar la ejecución del test avanzado:

Trying:
l = [1, 2, 3, 4, 5]
Expecting nothing
ok
Trying:
doblar(l)
Expecting:
[2, 4, 6, 8, 10]
ok
Trying:
l = []
Expecting nothing
ok
Trying:
for i in range(10):
l.append(i)
Expecting nothing
ok
Trying:
doblar(l)
Expecting:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
ok
1 items had no tests:
__main__
1 items passed all tests:
5 tests in __main__.doblar
5 tests in 2 items.
5 passed and 0 failed.
Test passed.

Por último vamos a volver a nuestra función suma para tratar excepciones dentro de los tests.

def suma(a, b):


"""Esta función recibe dos parámetros y devuelve la suma de ambos

Pueden ser números:

>>> suma(5,10)
15

>>> suma(-5,7)
2

Cadenas de texto:

>>> suma('aa','bb')
'aabb'

O listas:

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> suma(a,b)
[1, 2, 3, 4, 5, 6]
"""
return a+b

Ahora sabemos que no podemos sumar tipos distintos, ¿cómo podemos tenerlo en cuenta en un test?

Pues por ahora podemos suponer un resultado y comprobar el resultado cuando falle:

>>> suma(10,"hola")
"10hola"

def suma(a, b):


"""Esta función recibe dos parámetros y devuelve la suma de ambos

Pueden ser números:

>>> suma(5,10)
15

>>> suma(-5,7)
2

Cadenas de texto:

>>> suma('aa','bb')
'aabb'

O listas:

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> suma(a,b)
[1, 2, 3, 4, 5, 6]

Sin embargo no podemos sumar elementos de tipos diferentes:

>>> suma(10,"hola")
"10hola"
"""
return a+b

Si ejecutamos el script monitorizando todo:

python test.py -v
Podemos observar el fallo:

Trying:
suma(5,10)
Expecting:
15
ok
Trying:
suma(-5,7)
Expecting:
2
ok
Trying:
suma('aa','bb')
Expecting:
'aabb'
ok
Trying:
a = [1, 2, 3]
Expecting nothing
ok
Trying:
b = [4, 5, 6]
Expecting nothing
ok
Trying:
suma(a,b)
Expecting:
[1, 2, 3, 4, 5, 6]
ok
Trying:
suma(10,"hola")
Expecting:
"10hola"
**********************************************************************
File "test.py", line 26, in __main__.suma
Failed example:
suma(10,"hola")
Exception raised:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\doctest.py", line 1321, in __run
compileflags, 1), test.globs)
File "<doctest __main__.suma[6]>", line 1, in <module>
suma(10,"hola")
File "test.py", line 29, in suma
return a+b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
1 items had no tests:
__main__
**********************************************************************
1 items had failures:
1 of 7 in __main__.suma
7 tests in 2 items.
6 passed and 1 failed.
***Test Failed*** 1 failures.
Concretamente debemos fijarnos en la primera línea y última de la excepción:

Traceback (most recent call last):


...
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Y precisamente esto es lo que tenemos que indicar en el test:

def suma(a, b):


"""Esta función recibe dos parámetros y devuelve la suma de ambos

Pueden ser números:

>>> suma(5,10)
15

>>> suma(-5,7)
2

Cadenas de texto:

>>> suma('aa','bb')
'aabb'

O listas:

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> suma(a,b)
[1, 2, 3, 4, 5, 6]

Sin embargo no podemos sumar elementos de tipos diferentes:

>>> suma(10,"hola")
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'int' and 'str'
"""
return a+b

Y con esto acabamos esta lección en la que hemos visto como crear tests sencillos en la documentación con doctest. En la siguiente
veremos un tipo distinto de tests más avanzados, los tests unitarios o Unittest.
Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Unittest
El módulo unittest, a veces referido como PyUnit, forma parte de una serie de frameworks conocidos como xUnit. Estas librerías se
encuentran en la mayoría de lenguajes y son casi un estándard a la hora de programar pruebas unitarias.

A diferencia de doctest, unittest ofrece la posibilidad de crear las pruebas en el propio código implementando una clase llamada
unittest.TestCase en la que se incluirá un kit o batería de pruebas.

Cada una de las pruebas puede devolver tres respuestas en función del resultado:

OK: Para indicar que la prueba se ha pasado éxitosamente.


FAIL: Para indicar que la prueba no ha pasado éxitosamente lanzaremos una excepción AssertionError (sentencia verdadero-falso)
ERROR: Para indicar que la prueba no ha pasado éxitosamente, pero el resultado en lugar de ser una aserción es otro error.

Vamos a crear una prueba unitaria muy sencilla para ver su funcionamiento en un script tests.py:

import unittest

class Pruebas(unittest.TestCase):
def test(self):
pass

if __name__ == "__main__":
unittest.main()

En este sencillo ejemplo podemos observar como heredamos de la clase unittest.TestCase para crear una batería de pruebas.

Cada método dentro de esta clase será una prueba, que en nuestro ejemplo no lanza ninguna excepción ni error, porlo que significa que
los tests pasarán correctamente, y finalmente ejecutamos el método main() para ejecutar todas las baterías:

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

OK
Como vemos se ha 1 realizado 1 test y el resultado a sido OK.

Si en lugar de pasar, invocamos una execepción AssertError...

import unittest

class Pruebas(unittest.TestCase):
def test(self):
raise AssertionError()

if __name__ == "__main__":
unittest.main()
Entonces el test fallaría:

F
======================================================================
FAIL: test (__main__.Pruebas)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Hector\Desktop\test.py", line 5, in test
raise AssertionError()
AssertionError

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

FAILED (failures=1)

En el supuesto caso que dentro del test diera un error no asertivo, entonces tendríamos un Error:

import unittest

class Pruebas(unittest.TestCase):
def test(self):
1/0

if __name__ == "__main__":
unittest.main()
Entonces el test fallaría:
E
======================================================================
ERROR: test (__main__.Pruebas)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Hector\Desktop\test.py", line 5, in test
1/0
ZeroDivisionError: division by zero

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

FAILED (errors=1)

Excepciones Asertivas
Con lo que sabemos podríamos crear tests complejos sirviéndonos de condiciones y excepciones AssertionError, pero la clase
TestCase nos provee de un montón de alternativas.

Vamos a hacer un repaso de las más comunes, recordad que siempre devolverán True o False dependiendo de si pasan o no el test:

Si os interesa profundizar os dejo el enlace oficial: https://docs.python.org/3/library/unittest.html

Vamos a hacer algunos ejemplos para practicar.

Funciones propias
import unittest

def doblar(a): return a*2


def sumar(a,b): return a+b
def es_par(a): return 1 if a%2 == 0 else 0

class PruebasFunciones(unittest.TestCase):

def test_doblar(self):
self.assertEqual(doblar(10), 20)
self.assertEqual(doblar('Ab'), 'AbAb')

def test_sumar(self):
self.assertEqual(sumar(-15, 15), 0)
self.assertEqual(sumar('Ab' ,'cd'), 'Abcd')

def test_es_par(self):
self.assertEqual(es_par(11), False)
self.assertEqual(es_par(68), True)

if __name__ == '__main__':
unittest.main()
Resultado:

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Métodos de cadenas
import unittest

class PruebasMetodosCadenas(unittest.TestCase):

def test_upper(self):
self.assertEqual('hola'.upper(), 'HOLA')

def test_isupper(self):
self.assertTrue('HOLA'.isupper())
self.assertFalse('Hola'.isupper())

def test_split(self):
s = 'Hola mundo'
self.assertEqual(s.split(), ['Hola', 'mundo'])
if __name__ == '__main__':
unittest.main()
Resultado:

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Preparación y limpieza
Lo último importante a comentar es que la clase TestCase incorpora dos métodos extras.

El primero es setUp() y sirve para preparar el contexto de las pruebas, por ejemplo para escribir unos valores de prueba en un fichero
conectarse a un servidor o a una base de datos.

Luego tendríamos tearDown() para hacer lo propio con la limpieza, borrar el fichero, desconectarse del servidor o borrar las entradas de
prueba de la base de datos.

Este proceso de preparar el contexto se conoce como test fixture o accesorios de prueba.

Sólo por poner un ejemplo supongamos que necesitamos contar con una lista de elementos para realizar una serie de pruebas:

import unittest

def doblar(a): return a*2

class PruebaTestFixture(unittest.TestCase):

def setUp(self):
print("Preparando el contexto")
self.numeros = [1, 2, 3, 4, 5]

def test(self):
print("Realizando una prueba")
r = [doblar(n) for n in self.numeros]
self.assertEqual(r, [2, 4, 6, 8, 10])

def tearDown(self):
print("Destruyendo el contexto")
del(self.numeros)

if __name__ == '__main__':
unittest.main()
Resultado de la prueba:

Preparando el contexto
.
Realizando una prueba
Destruyendo el contexto
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Y con esto finalizamos el tema.

Ahora ya sabemos cómo documentar nuestro código docstrings, generar la documentación con pydoc, introducir pruebas en las
docstrings combinando doctest, y crear pruebas avanzadas con el módulo unittest.

Estamos a un paso de finalizar con el curso, sólo nos falta ver cómo distribuir nuestros módulos y programas.

¡Nos vemos en la próxima unidad!


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Setuptools
En Python todo el tema de empaquetar puede ser un poco lioso, ya que encontramos varios módulos desintados a ello. Nosotros vamos
a centrarnos en setuptools, ya que es la forma más utilizada, nos proporciona todo lo necesario para distribuir nuestros propios
módulos e incluso nos permite publicar paquetes en el respositorio público PyPI (Python Package Index) de forma directa desde la
propia terminal.

Si lo recordáis, en la lección de módulos ya os enseñé como crear un distribuible con setuptools, a lo largo de esta lección vamos a
repasar y aprender varios conceptos nuevos.

Paquete básico
Antes de comenzar es importante repasar la estructura de un paquete en Python,ya que para distribuir nuestro código es indispensable
estructurarlo dentro de un paquete:

| setup.py # Fichero que contiene toda la información de instalación


+ prueba/ # Directorio del paquete al mismo nivel que setup.py
| __init__.py # Fichero que indica que el directorio es un paquete
| modulo.py # Módulo o script que contiene definiciones

Por lo tanto vamos a empaquetar el paquete de nombre prueba, que contiene código en el fichero modulo.py.

Vamos a aprender un poco más sobre el fichero de instalación.

setup.py
El fichero de configuración incluye toda la información necesaria para realizar la instalación de nuestro paquete. Algunos campos
incluyen sólo metadatos como el nombre, la versión, la descripción o el autor. Pero otros sirven para extender la instalación.

Como sería un caos que cada desarrollador pusiera los campos que quisiera, hay una serie de parámetros comunes y avanzados, pero
como son muchos lo más común es utilizar una plantilla base como la siguiente que pasa la configuración a la función setup:

from setuptools import setup

setup(name="Prueba", # Nombre
version="0.1", # Versión de desarrollo
description="Paquete de prueba", # Descripción del funcionamiento
author="Hector Costa", # Nombre del autor
author_email='me@hcosta.info', # Email del autor
license="GPL", # Licencia: MIT, GPL, GPL 2.0...
url="http://ejemplo.com", # Página oficial (si la hay)
packages=['prueba'],
)
¿Hasta aquí fácil no? Son simples metadatos para definir el paquete, con la excepción de packages, en el que tenemos que indicar
todos los paquetes que formarán parte del paquete distribuido en forma de lista.

Aunque en este caso únicamente tendríamos al paquete prueba, imaginaros que tenemos docenas de subpaquetes y tubiéramos que
añadirlos uno a uno... Pues para estos casos podemos importar una función que se encargará de buscar automáticamente los
subpaquetes, se trata de find_packages y la podemos encontrar dentro de setuptools:

from setuptools import setup, find_packages

setup(...
packages=find_packages()
)

Dependencias
Ahora imaginaros que en vuestro paquete algún código utiliza funciones de un módulo externo o paquete que hay que instalar
manualmente. Esto se conoce como dependencias del paquete, y por suerte podemos indicar a un parámetro que descargue todos los
paquetes en la versión que nosotros indiquemos, se trata de install_requires.

Por ejemplo imaginad que dentro de nuestro paquete necesitamos utilizar el módulo Pillow para manejar imágenes. Por regla general
podemos instalarlo desde la terminal con el comando:

pip install pillow


Pero si queremos que el paquete lo instale automáticamente sólo tenemos que indicarlo de esta forma:

setup(...,
install_requires=["pillow"],
)
Y así iríamos poniendo todas las dependencias en la lista.

Lo bueno que tiene es que podemos indicar la versión exacta que queremos instalar, por ejemplo. Si mi programa utilizase la versión
1.1.0 de Pillow tendría que poner:

setup(...,
install_requires=["pillow==1.1.0"],
)
En cambio si fuera compatible con cualquier versión a partir de la 1.1.5 podría poner:

setup(...,
install_requires=["pillow>=1.1.5"],
)
Si no indicamos una versión, se instalará automáticamente la más actual.

Utilizando un fichero de dependencias


De forma similar a antes, quizá llega el momento donde tenemos muchísimas dependencias y es un engorro tener que cambiar
directamente el fichero setup.py. Para solucionarlo podemos utilizar una técnica que se basa en crear un fichero de texto y escribir las
dependencias, una por línea.

Luego podemos abrir el fichero y añadir las dependencias automáticamente en forma de lista. Generalmente a este fichero se le llama
requirements.txt y debe estar en el mismo directorio que setup.py:

requirements.txt

pillow==1.1.0
django>=1.10.0,<=1.10.3
pygame

Luego en las dependencias indicaríamos lo siguiente:

setup(...,
install_requires=[i.strip() for i in open("requirements.txt").readlines()],
)

Suite Test
Otra cosa interesante que podemos hacer es adjuntar una suite de tests unitarios para nuestro paquete, ya sabéis, los que aprendimos
en la unidad anterior.

Para incluirlos tendremos indicar un parámetro en el instalador llamado test_suite, al que le pasaremos el nombre del directorio que los
contiene, por lo general llamado tests:

| setup.py
| requeriments.txt
+ prueba/
| __init__.py
| modulo.py
+ tests/
| test_pillow.py
| test_django.py
| test_pygame.py

En el setup.py:

setup(...,
test_suite="tests"
)
Luego para ejecutarlos podemos utilizar el comando:

python setup.py test

PyPI y PIP
Por último hablemos un poco más del Python Package Index.
Como ya sabéis se trata de un repositorio público con miles y miles de paquetes creados por la enorme comunidad de Python. De hecho
yo mismo creé hace años un pequeño módulo para el framework django, os dejo el enlace por si os pica la curiosidad:
https://pypi.python.org/pypi/django-easyregistration

Sea como sea, la forma de instalar cómodamente los paquetes de PyPI es con la herramienta PIP (un acrónimo recursivo de Pip Installs
Packages), utilizando el comando pip install nombre_paquete.

Además podemos listar los paquetes instalados con pip list, borrar alguno con pip uninstall nombre_paquete o incluso instalar todas
las dependencias de un fichero requisites.txt utilizando pip install requisites.txt.

Si queréis saber más sobre pip, simplemente escribid pip en la terminal.

Clasificadores
Por lo tanto tenemos un repositorio inmenso, así que ¿cómo podemos añadir información para categorizar nuestro paquete en PyPI?
Pues utilizando un parámetro llamado classifiers de la siguiente forma:

setup(...,
classifiers=[
"Development Status :: 3 - Alpha",
"Topic :: Utilities",
"License :: OSI Approved :: GNU General Public License (GPL)",
],
)
Hay un montón de clasificadores, desde el estado del proyecto, el tema, las licencias, etc. Una lista completa de los clasificadores
disponibles podemos encontrarla en la propia web de PyPI: https://pypi.python.org/pypi?%3Aaction=list_classifiers

Probando el paquete
Una vez tenemos toda la información configurada, podemos probar nuestro paquete fácilmente realizando una instalación en modo
desarrollo. Para ello utilizaríamos el siguiente comando:

python setup.py develop

Este modo es muy práctico, ya que nos permite utilizar nuestro módulo en cualquier lugar y hacer modificacione sin necesidad de
reinstalarlo constamente. Eso es posible porque se utiliza desde el propio directorio.

Una vez hayamos hecho las probaturas y estemos satisfechos, podemos desinstalar el paquete de desarrollo:

python setup.py develop --uninstall

Para instalar el paquete definitivo utilizaríamos:

python setup.py install

Pero tenemos que tener en cuenta que una vez hecho esto, el paquete se instala en una copia interna y ya no podremos modificarlo sin
antes desinstalarlo, algo que tendremos que hacer con PIP, buscando el nombre del paquete con pip list y haciendo un pip uninstall
nombre_paquete.

Distribuyendo el paquete
Ya tenemos el paquete, hemos creado el instalador, lo hemos probado y estamos preparados para distribuirlo. Hay dos formas:

Localmente: Generando un fichero comprimido que podemos compartir con nuestros conocidos.
Públicamente: En el repositorio PyPI para que todo el mundo pueda utilizarlo.

Evidentemente si distribuimos localmente no tenemos que tener mucho cuidado, y además podemos hacer pruebas. Pero si decidimos
hacerlo públicamente tendremos que intentar que el paquete tenga un mínimo de calidad.

Localmente
Distribuir el paquete localmente es muy fáci. Simplemente tenemos que utilizar el comando:

python setup.py sdist

Esto generará un directorio dist/ en la carpeta del paquete. Dentro encontraremos un fichero zip o tar.gz dependiendo de nuestro
sistema operativo.
Este fichero ya podremos compartirlo con quien queramos, y para instalarlo sólo tendremos que utilizar la herramienta pip:

pip install nombre_del_fichero.zip # La extensión depende del sistema operativo

Luego para desinstalarlo de la misma forma pero utilizando el nombre del paquete:

pip uninstall nombre_paquete

Públicamente
Aunque no voy a hacer la demostración porque ahora mismo no dispongo de un paquete para publicar en el repositorio de PyPI, sí que
os voy a enseñar los pasos a seguir para hacerlo. Lo bueno de registrar un paquete en PyPI es que podemos instalarlo desde cualquier
lugar a través de internet utilizando la herramienta PIP.

Dicho ésto, si algún día creáis un paquete de calidad y queréis compartirlo con la comunidad, lo primero es registrar una cuenta en PyPI:
https://pypi.python.org/pypi?%3Aaction=register_form

A continuación desde el directorio de nuestro paquete tenemos que ejecutar el comando:

python setup.py register

Así iniciaremos una petición para registrar nuestro paquete en el repositorio. Luego tendremos que seguir los pasos e identificarnos
cuando lo pida con nuestro usuario y contraseña (que hemos creado antes).

Una vez hecho esto ya hemos creado nuestro paquete, pero todavía no hemos publicado una versión, así que vamos a hacerlo
utilizando el comando:

python setup.py sdist upload

¡Y ya está! Ahora podremos instalar nuestro paquete desde en cualquier lugar con PIP:

pip install nombre_paquete


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js
Pyinstaller
Ya hemos visto como distribuir nuestros paquetes... ¿Pero y si creamos una aplicación y queremos generar un ejecutable para utilizarla?
Bueno, en este caso puede ser bastante complicado dependiendo de las dependencias que utilice el programa.

Por suerte hay un módulo que nos ayudará mucho a generar ejecutables porque automatiza el proceso, ese es pyinstaller.

Lo que hace es generar un .EXE en Windows, un .DMG en MAC o el ejecutable que utilice el sistema operativo. Dentro del ejecutable se
incluye el propio intérprete de Python, y por esa razón podremos utilizarlo en cualquier ordenador sin necesidad de instalar Python
previamente.

Instalación
La instalación es muy fácil:

pip install pyinstaller

No hay más.

Primer ejecutable
Comencemos con algo simple, tenemos un script hola.py:

print("Hola mundo!")
Y queremos crear un ejecutable a partir de él, pues haríamos lo siguiente:

pyinstaller hola.py

Una vez acabe el proceso se nos habrán creado varias carpetas. La que nos interesa es dist, y dentro encontraremos una carpeta con
el nombre programa y en esta un montón de ficheros y el ejecutable, en mi caso como estoy en Windows es hola.exe.

Como es un programa para terminal, para ejecutarlo tengo que abrir la terminal en ese directorio y ejecutar el programa manualmente:

C:\Users\Hector\Desktop\hola\dist\hola>hola.exe
Hola mundo!

Ejecutable con interfaz


Ahora vamos a hacer otro a partir de un simple programa con Tkinter, la librería de componentes integrada en Python que ya
conocemos. Nos debería funcionar sin problemas:

from tkinter import *


root = Tk()
Label(text='Hola mundo').pack(pady=10)
root.mainloop()
Suponiendo que lo hemos puesto en el mismo script:

pyinstaller hola.py

En esta ocasión si ejecutamos el programa con doble clic nos funcionará bien, el problema es que se muestra la terminal de fondo.

Para que desaparezca tenemos que indicar que es una aplicación en ventana, y eso lo hacemos de la siguiente forma al crear el
ejecutable:

pyinstaller --windowed hola.py

Ejecutable en un fichero
Ya véis que por defecto Pyinstaller crea un directorio con un montón de ficheros. Podemos utilizar un comando para generar un solo
fichero ejecutable que lo contenga todo, pero este ocupara bastante más:

pyinstaller --windowed --onefile hola.py

Cambiar el icono
También podemos cambiar el icono por defecto del ejecutable. Para ello necesitamos una imagen en formato .ico.

pyinstaller --windowed --onefile --icon=./hola.ico hola.py

Si no tenéis uno, podéis utilizar este para probar: http://www.iconarchive.com/download/i3532/artua/star-wars/Darth-Vader.ico

Si por algo no os cambia el icono, probad cambiando el nombre del ejecutable. A veces el caché de Windows puede ignorar estas
cosas.

Limitaciones
El gran problema con Pyinstaller como os decía al principio son las dependencias.

Si nuestro programa utiliza únicamente módulos de la librería estándard no tendremos ningún problema, pero si queremos utilizar
módulos externos es posible que no funcione... A no ser que sea alguno de los soportados como PyQT, django, pandas, matpotlib...
pero requieren una configuraciones extra.

Si queréis saber más os dejo este enlace con los paquetes soportados: https://github.com/pyinstaller/pyinstaller/wiki/Supported-
Packages

Personalmente nunca he hecho nada avanzado, pero si queréis hacerlo y no lo conseguís, os podéis poner en contacto conmigo y
estaré encantado de ayudaros.

Y con esto llegamos al final... por ahora ;-)


Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js

También podría gustarte