1. Input - Output (I/O)
2. Lambdas
3. Programación orientada a objetos
Sabemos cómo imprimir datos por consola:
x = 4
print(f"2+2 = {x}")
print("Damn")
print("LaTeX no soportado por defecto: " + "x^2 = 2$")
print("123", "456", "789", sep="-")
print("Hola", end=" ")
print("mundo!")
Veamos el proceso inverso: la introducción de datos por consola.
Podemos pedir al usuario que introduzca datos usando input("mensaje")
username = input("Dime tu nombre: ")
print(username)
Si el prompt es largo, es recomendable escribirlo por separado de antemano:
prompt = "Buenos días! Este es el servicio de atención al usuario.\n"
prompt += "Por favor, escribe tu nombre: "
nombre = input(prompt)
print(f"Hola, {nombre}.")
Por defecto, input() devuelve strings.
edad = input("¿Cuántos años tienes? ")
if edad >= 18: # TypeError: '>=' not supported between instances of 'str' and 'int'
print("Adulto")
else:
print("Menor")
Si queremos usar el resultado como otro tipo de dato, podemos convertirlo explícitamente.
edad = input("¿Cuántos años tienes? ")
edad = int(edad)
print(edad >= 18) # True o False
precio = input("¿Cuánto cuesta una barra de pan?")
precio = float(precio)
if precio > 1.85:
print("Caro, si me preguntan")
La idea de pedir input por consola no es solo una curiosidad!
Se utiliza en la práctica, por
ejemplo para construir menús
menu = "Introduce una opción:\n"
menu += "\t a. Buscar en la base de datos.\n"
menu += "\t b. Añadir entrada a la base de datos.\n"
menu += "\t c. Salir.\n"
msg = ""
while (msg != "c"):
msg = input(menu).lower()
if msg == "a": buscar()
elif msg == "b": añadir()
elif msg == "c": print("¡Adiós!")
else: print("Opción incorrecta.")
Si durante la ejecución ocurre un error, el programa finalizará abruptamente.
El manejo de excepciones nos permite indicar al programa cómo afrontar un error de manera controlada para que pueda continuar ejecutándose.
Los bloques de manejo de excepciones utilizan 4 keywords:
Ni except, ni else ni finally son obligatorios.
try:
# Código que puede producir un error
pass
except:
# Se ejecuta si se produce un error
# El resto de secuencias de try no se ejecutan
pass
else:
# Se ejecuta después de todo el código de try si no se produce un error
pass
finally:
# Se ejecuta en último lugar, independientemente de si se ha producido un error o no
pass
Nota: el orden es importante. try-else-except no es válido. Quitar los pass y preguntar por qué el código daría un error? else es útil para lo que dependa de que el bloque de try tenga éxito else y finally son anecdóticos Puede haber else sin except, finally sin ... pero todos dependen de try
def dividir(a, b):
try:
res = a / b
except ZeroDivisionError as e:
print("Error. b no puede ser 0:", e)
else:
print(res)
finally:
print("Terminado!")
dividir(3, 0)
dividir(3, 1)
def dividir(a, b):
try:
res = a / b
print(res)
except:
print("Error. b no puede ser 0")
dividir(3, 0)
dividir(3, 1)
except permite recoger varios tipos de errores.
También podemos generar nuestras propias excepciones.
Volveremos a esto (necesitamos el concepto de clase).
Función básica: open(path, modo)
# Ruta relativa (i.e. desde el mismo directorio que el script)
archivo = open("archivo.txt", "r")
# Ruta absoluta (i.e. desde el directorio raíz)
archivo2 = open("C:\\Users\\Xiana\\Desktop\\archivo.txt", "r")
arch_bin = open("archivo_binario.txt", "rb")
Hay diferentes formas de leer el contenido:
archivo = open("archivo.txt", "r")
contenido = archivo.read() # Todo el contenido como una única cadena
print(contenido)
archivo.close()
archivo2 = open("archivo.txt", "r")
lineas = archivo2.readlines()
for linea in lineas:
print(linea.rstrip())
# Sin rstrip(), se introducirían 2 saltos de línea
archivo2.close()
archivo3 = open("archivo.txt", "r")
for linea in archivo3:
print(linea.rstrip())
archivo3.close()
archivo4 = open("archivo.txt", "r")
lineas = archivo4.read().splitlines()
for linea in archivo4:
print(linea)
# No hace falta rstrip()
archivo4.close()
archivo5 = open("archivo.txt", "r")
linea = archivo5.readline()
while linea:
print(linea.rstrip())
linea = archivo5.readline()
archivo5.close()
Importante: cerrar los ficheros!
Es preferible abrir archivos usando with:
# El archivo se cierra automáticamente,
# no es necesario incluir archivo.close()
with open("archivo.txt", "r") as archivo:
for linea in archivo:
print(linea.rstrip())
La sintaxis de with es equivalente al siguiente bloque de código:
try:
archivo = open("archivo.txt", "r")
try:
datos = archivo.read()
print(datos)
finally:
archivo.close()
TODO: with para input y output a la vez
Nota: no es necesario hacer close en el bloque externo porque el archivo no se ha llegado a abrir
(de
hecho, daría un error)
Por supuesto, pueden seguir occuriendo varios errores al abrir archivos $\dots$
try:
with open("archivo.txt", "r") as archivo: # archivo = open("archivo.txt", "r")
for linea in archivo:
print(linea.rstrip())
except FileNotFoundError:
print("El archivo no existe")
except IOError as e:
print(f"Error al acceder al archivo: {e}")
except:
print("Error desconocido")
La función open("archivo.txt", modo) tiene varios modos para abrir ficheros:
r es la opción por defecto: open("fichero.txt", "r") y open("fichero.txt") hacen lo mismo.
A cada uno de estos modos se le puede añadir b, para manejar los archivos en binario, o t, para manejarlos como archivos de texto (por defecto).
Si queremos leer y escribir a la vez, podemos añadir +.
Modo | Uso | Posición del cursor | ¿Crea el archivo si no existe? | ¿Sobreescribe el contenido? |
---|---|---|---|---|
r | Lectura | Inicio | No | No |
r+ | Lectura y escritura | Inicio | No | No |
w | Escritura | Inicio | Sí | Sí |
w+ | Lectura y escritura | Inicio | Sí | Sí |
a | Escritura (append) | Final | Sí | No |
a+ | Lectura y escritura (append) | Final | Sí | No |
Unlike lambda forms in other languages, where they add functionality, Python lambdas are only a shorthand notation if you're too lazy to define a function.
Documentación de Python
# Algunos ejemplos de sintaxis
lambda x: x + 1
lambda: True
# Podemos darle un nombre y ejecutarla luego
cuadrado = lambda x: x * x
print(cuadrado(3)) # 9
Función normal de división
def dividir(x, y):
if y != 0:
return x / y
else:
return "undefined"
$\Longrightarrow$
Lambda de división
lambda x, y: x / y if y != 0 else "undefined"
Función de formato de nombre
def nombre_completo(nombre, apellido):
return f"{nombre.title()} {apellidos.title()}"
$\Longrightarrow$
Lambda de formato de nombre
lambda nombre, apellido: f"{nombre.title()} {apellidos.title()}"
Función + map
def doblar(x):
return 2 * x
lista = [1, 2, 3, 4]
dobles = map(dobles, lista)
print(dobles) # [2, 4, 6, 8]
$\Longrightarrow$
Lambda + map (más conciso)
lista = [1, 2, 3, 4]
dobles = list(map(lambda x: 2 * x, lista))
print(dobles) # [2, 4, 6, 8]
Función + filter
def es_par(x):
return x % 2 == 0
lista = range(2, 7)
res = list(filter(es_par, lista))
print(res) # [2, 4, 6]
$\Longrightarrow$
Lambda + filter (más conciso)
lista = range(2, 7)
res = list(filter(lambda x: x % 2 == 0, lista))
print(res) # [2, 4, 6]
lista = list('vwerfa')
ordenada = sorted(lista)
print(ordenada)
# >>> ['a', 'e', 'f', 'r', 'v', 'w']
# Ordenar números con una función especial (key)
nums = [15, 11, 4, -5, 0, -1, 9]
def dist_10(n):
return abs(10-n)
print(sorted(nums, key=dist_10))
# >>> [11, 9, 15, 4, 0, -1, -5]
$\Longrightarrow$
# Ordenamos los números igual, pero ahora la key es un lambda
nums = [15, 11, 4, -5, 0, -1, 9]
print(sorted(nums, key=lambda n: abs(10-n), reverse=True))
# >>> [-5, -1, 0, 4, 15, 11, 9]
datos = range(5, 50, 5)
mayores_20 = filter(lambda x: x > 20, datos)
print(list(mayores_20)) # [25, 30, 35, 40, 45]
datos = [(4, 20), (1, 30), (2, 10)]
datos_ordenados = sorted(data, key=lambda x: x[1])
print(datos_ordenados) # [(2, 10), (3, 20), (1, 30)]
Lorem codium
Lorem codium
Quizás mejor dejar esto como ejercicios... Also: TODO ver reduce (hay que importarla)
Veamos cómo implementar el ejemplo de la imagen
class Person:
def __init__(self):
pass
class Person:
def __init__(self, name, age, gender, occupation):
self.name = name
self.age = age
self.gender = gender
self.occupation = occupation
# Ya podemos crear objetos de la clase Persona
marta = Person("Marta", 20, "F", "estudiante")
artai = Person("Artai", 23, "NB", "dibujante")
class Person:
def __init__(self, name, age, gender, occupation):
self.name = name
self.age = age
self.gender = gender
self.occupation = occupation
def walk(self):
print(f"{self.name} está caminando")
def eat(self, comida):
print(f"{self.name} está comiendo {comida}")
def sleep(self):
print("No hace nada...")
def work():
print("Qué currante")
marta = Person("Marta", 20, "F", "estudiante")
artai = Person("Artai", 23, "NB", "dibujante")
class Person:
def __init__(self, name, age, gender, occupation):
self.name = name
self.age = age
self.gender = gender
self.occupation = occupation
self.health = 100 # Salud inicial
def walk(self):
print(f"{self.name} está caminando")
self.health += 5 # Caminar es bueno para la salud
def eat(self, comida):
print(f"{self.name} está comiendo {comida}")
self.health += 10 # Recuperamos fuerzas
def sleep(self):
print("No hace nada...")
self.health += 20 # A mimir zzzzz
def work():
print("Qué currante")
self.health -= 10 # El trabajo es duro
marta = Person("Marta", 20, "F", "estudiante")
artai = Person("Artai", 23, "NB", "dibujante")
Ahora podemos llamar a los métodos de cada objeto creados con la sintaxis objeto.metodo(args...)
Con esto, ejecutamos una acción sobre el objeto, posiblemente alterando su estado.
Importante: Cuando llamamos a un método de un objeto, no hace falta pasar self
marta = Person("Marta", 20, "F", "estudiante")
artai = Person("Artai", 23, "NB", "dibujante")
marta.walk() # Marta está caminando
marta.eat("pizza") # Marta está comiendo pizza
# Podemos también acceder e imprimir los valores del objeto
print(marta.name) # Marta
print(marta.health) # 115
marta.work() # Qué currante
print(marta.health) # 105
class Doctor(Person):
def __init__(self, name, age, gender, specialty):
# Buena práctica: El constructor original de Person cubre campos comúnes
super().__init__(name, age, "M", "doctor")
# Ahora añadimos el resto del estado inicial
self.specialty = specialty
Los objetos de subclase pueden acceder y llamar a los métodos de su clase base.
Por ejemplo, un nuevo Doctor podría llamar a walk()
doc = Doctor("House", 45, "M", "médico de cabecera")
doc.walk() # House está caminando
Una subclase puede reimplementar un método que ya está en la superclase. Esto se conoce como overriding (o sobreescritura)
Por ejemplo, si queremos que un Doctor camine de forma diferente a una Persona normal, podemos hacerlo así:
class Doctor(Person):
def walk(self):
print(f"{self.name} está caminando... pero con bata")
Más avanzado: Esto lleva al concepto de polimorfismo. Las clases que sobreescriben métodos se pueden comportar como instancias de su superclase, hasta que llaman a uno de los métodos sobreescritos. Entonces, se ejecuta la versión sobreescrita en vez de la original $\dots$
class UXAError(Exception):
def __init__(self, message = "Tu matrícula es incorrecta"):
super().__init__(message)
Uso de la excepción personalizada en código:
def matricularse_usc(str_matricula):
if str_matricula == "No quiero estudiar":
raise UXAError()
return str_matricula + " aceptada"
try:
resultado = matricularse_usc("No quiero estudiar")
except UXAErrror as error:
print(f"Error al matricularse en la USC: {error}")
Podemos implementar el método __str__(self) para modificar como se imprime un objeto de una clase
class Person:
def __str__(self):
return f"{self.name} es un {self.occupation} de {self.age}"
f"años y género {self.gender}"
# Ahora podemos imprimir más fácilmente un objeto Person
marta = Person("Marta", 20, "F", "estudiante")
print(marta) # Marta es un estudiante de 20 años y género F
Hay muchos más métodos mágicos de este estilo, por ejemplo __em__ para reimplementar la igualdad entre dos objetos de la misma clase, __add__ para la suma, $\dots$
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)
La clase object es la clase base de todas las demás, aunque no se especifique explícitamente. Los tipos y contenedores primitivos (int, str, list, dict, $\dots$) también son subclases de object
Más avanzado: Gracias a que todos los objetos sean subclases de object, funcionan los métodos mágicos! Todos estos métodos están definidos en object, y no hacemos más que overridearlos. El lenguaje interpreta cada uno de ellos de forma especial, y los asigna a los operadores. Por ejemplo v1 + v2 es lo mismo que v1.__add__(v2)