{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Clase 5: Programación Orientada a Objetos\n", "\n", "**MDS7202: Laboratorio de Programación Científica para Ciencia de Datos**\n", "\n", "## Objetivos de la Clase\n", "\n", "El objetivo de esta clase es comprender los fundamentos de la programación orientada a objetos (POO).\n", "En particular, veremos:\n", "\n", "- Programación orientada a objetos: Clases, atributos y métodos.\n", "- Principios básicos de POO:\n", " - Abstraccion y Encapsulación.\n", " - Herencia.\n", " - Anulación de Métodos.\n", " - Métodos Mágicos.\n", "- Manejo de Excepciones.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Paradigmas de Programación \n", "\n", "Una forma usual de clasificar lenguajes de programación es por medio de su **paradigma principal**.\n", "\n", "> Según la RAE, un paradigma es un *ejemplo* o una *teoría o conjunto de teorías cuyo núcleo central se acepta sin cuestionar y que suministra la base y modelo para resolver problemas y avanzar en el conocimiento. *.\n", "\n", "Se puede interpretar a un paradigma de programación, como un conjunto de patrones o un modelo base o estilo sobre los cuales se resuelven problemas computacionales y que tienen directa relación con la sintaxis del lenguaje.\n", "\n", "Los paradigmas de programación principales son:\n", "\n", "* Imperativo\n", " - Orientado a objetos\n", "* Declarativo\n", " - Funcional\n", " - Optimización matemática\n", "\n", "Pero existen muchos mas.\n", "\n", "En esta sección, estudiaremos las características de Python, que permiten implementar técnicas del paradigma orientado a objetos. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Pregunta ❓**: Python es un lenguaje de programación multi-paradigma. ¿Qué paradigmas de programación soporta Python? " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "> **Ejercicio ✏️**: Busca información, define con tus propias palabras y da ejemplos los paradigmas de programación _imperativa_, _declarativa_, _funcional_ y _orientada a objetos_." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## Programación Orientada a Objetos\n", "\n", "El paradigma de **programación orientado a objetos** (POO / OOP) permite estructurar programas, de manera tal que es posible **asociar acciones (métodos) y propiedades (atributos) a entidades llamadas objetos**. Las relaciones entre objetos encargados de procesar tareas de diversa índole permiten estructurar ordenadamente el programa y obtener de manera mas sencilla los resultados buscados. \n", "\n", "\n", "Por ejemplo, un *objeto* puede representar a un auto. En este caso, sus *atributos* serán la cantidad de bencina en el estanque, velocidad actual, velocidad máxima y mucho otros más. Por otro lado, sus comportamientos/*métodos* serán cargar bencina, acelerar, frenar, etc... e incluso podrá tener relaciones entre objetos \"persona\" al ejecutar un método conducir.\n", "\n", "\n", "\n", "![Ejemplo clase auto](./resources/clase_auto.png)\n", "
Ejemplo clase auto y objetos que la instancian
\n" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Clases en Python\n", "\n", "\n", "Una **clase** provee de la estructura sobre la cual se definirá un objeto. Esta en su formulación base no se encuentra provista de la información que se materializa con el objeto.\n", "\n", "En Python, las clases se definen siguiendo la sintaxis `class : ...`:\n" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "class Estudiante:\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La clase anterior es la más básica de todas, solo declara la clase y nada más.\n", "\n", "> **Nota 🗒️**: Según la especificación de estilos PEP8, las clases deben ser declaradas usando nombres en **Upper CamelCase**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Instanciación\n", "\n", "\n", "Para instanciar un objeto (es, decir, crear un objeto a partir de alguna clase) invocamos la clase y la asignamos a alguna variable. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:27:54.414026Z", "start_time": "2021-03-29T19:27:54.409027Z" }, "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "<__main__.Estudiante at 0x7fc6efda2160>" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "juan = Estudiante()\n", "juan" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Atributos, Métodos y Constructor\n", "\n", "Como se mencionó al inicio, la idea de la POO es que un objeto represente algún concepto de la realidad, es decir, que posea un estado dado por un conjunto de características (llamados **atributos**) y funcionalidades (llamadas **métodos**).\n", "\n", "Sin embargo, no hemos declarado nada de esto hasta el momento. A continuación se mostrará como incluirlos.\n", "\n", "\n", "> **Nota 🗒️**: La dinámica de scopes sigue la misma lógica que en funciones: **todas las asignaciones a variables locales dentro de la clase, quedan su scope local asociado (o *namespace*).** \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "#### Constructor\n", "\n", "Para inicializar atributos al momento de instanciar (crear) los objetos se utiliza el **método constructor ```__init__()```**. Es una función especial (método) que recibe como argumentos al objeto que se está creando (`self`) más los otros argumentos iniciales que creamos necesarios.\n", "\n", "> **Ejemplo 📖**\n", "\n", "Definimos la clase ```Estudiante```, el cuál inicialmente debe contar con los atributos ```nombre``` y ```edad```." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:34:12.466917Z", "start_time": "2021-03-29T19:34:12.447917Z" } }, "outputs": [], "source": [ "class Estudiante:\n", " '''Clase Estudiante del Curso MDS7202'''\n", "\n", " def __init__(self, nombre, edad):\n", " self.nombre = nombre\n", " self.edad = edad\n", " self.horas_de_estudio = 0\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En este ejemplo, la clase ```Estudiante``` entrega la base para que cada una de sus instancias tenga un dato asociado a ```nombre``` y ```edad```.\n", "Para crear un objeto Estudiante, debemos instanciar la clase." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:33:49.375119Z", "start_time": "2021-03-29T19:33:49.351121Z" } }, "outputs": [ { "data": { "text/plain": [ "<__main__.Estudiante at 0x7fc704201dc0>" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "agustin = Estudiante(nombre='Agustín', edad=27)\n", "agustin" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Accedemos a sus atributos a través de la notación **{`objeto`}.{`atributo`}**" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Agustín'" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "agustin.nombre" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "27" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "agustin.edad" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "agustin.horas_de_estudio" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Pregunta ❓**: Entonces, ¿quién es y cuál es la idea de self?" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'Estudiante' object has no attribute 'self'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[22], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43magustin\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mself\u001b[49m\n", "\u001b[0;31mAttributeError\u001b[0m: 'Estudiante' object has no attribute 'self'" ] } ], "source": [ "agustin.self" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<__main__.Estudiante at 0x7fc704201dc0>" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "agustin" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos asignar el valor de algún atributo usando **{`objeto`}.{`atributo`}** = **`valor`**" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "agustin.horas_de_estudio = 100 # pasó de curso :)" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "100" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "agustin.horas_de_estudio" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "--- \n", "\n", "> **Ejercicios ✏️** \n", "\n", "1. ¿Cuáles de estos comandos son correctos y por qué? \n", "\n", " a. ```estudiante_1 = Estudiante('Sara', 29)``` \n", " \n", " b. ```estudiante_1 = Estudiante(name = 'Alberto', age = 25)```\n", " \n", " c. ```estudiante_1 = Estudiante(name = 'Alberto', age = '25')``` \n", " \n", " d. ```estudiante_1 = Estudiante()``` " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2. Ejecute el siguiente código:\n", "\n", "```python\n", "estudiante = Estudiante()\n", "estudiante.sueño = \"70%\"\n", "print(estudiante.sueño)\n", "```\n", "Donde ```Estudiante``` se definió unas celdas más arriba. Si observa con detención, se creó el atributo ```sueño``` \"directamente\" ¿Qué diferencia / ventaja / desventaja posee este método de creación de atributos versus el uso del constructor ```__init__()__```?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "3. Declare la clase ```Auto``` sin generar un constructor ```__init__()__``` y sin embargo, añada una variable `motor = 1.2` al momento de declarar la clase.\n", "\n", "```python\n", "class Auto:\n", " motor = 1.2\n", " \n", "```\n", "Luego, instancie un par de autos y modifique motor usando la notación **{`objeto`}.{`atributo`}** = **`valor`**. ¿Qué sucede con la variable motor? (*hint*: este tipo de variables se denomina *variable de clase*)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "4. Defina el objeto ```cajero_de_metro```, el cuál es un objeto de la clase ```CajeroAutomatico``` y sobreescriba el atributo ```cajero_de_metro.dinero``` por -1, luego imprima en pantalla el valor de tal atributo. Clase por usar:\n", "\n", "```python\n", "class CajeroAutomatico:\n", " def __init__(self):\n", " self.dinero = 10000000\n", "```\n", "\n", "¿Es buena idea usar esta forma de acceder a los atributos? Basarse en la siguiente nota para responder:\n", "\n", "\n", "> **Nota 📝**: Por lo general se busca bloquear el acceso a atributos internos de un objeto, con el fin de garantizar integridad y control sobre los datos que se operan. Esto se denota como **encapsulación**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "#### Métodos\n", "\n", "Ahora es el turno de las funcionalidades de los objetos: los métodos. En términos prácticos, los métodos serán funciones que cada objeto que instancie la clase tendrá acceso a dichas funcionalidades.\n", "\n", "\n", "Noten que ya estudiamos el primer método!: el constructor ```__init__()```. Al igual que este, todos los métodos se definen usando un bloque ```def``` y siempre su primer argumento debe ser `self` (que recordando lo que vimos antes, permite acceder a los atributos de cada objeto instanciado.\n", "\n", "\n", "> **Ejemplo 📖**\n" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "class Estudiante:\n", " '''Clase Estudiante del Curso MDS7202'''\n", "\n", " def __init__(self, name, age):\n", " self.nombre = name\n", " self.edad = age\n", " self.horas_de_estudio = 0\n", " \n", " def estudiar_una_hora(self):\n", " self.horas_de_estudio = self.horas_de_estudio + 1\n", " \n", " def describir_estudiante(self):\n", " print(f'{self.nombre} ha estudiado {self.horas_de_estudio} horas.')" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Juanita ha estudiado 0 horas.\n" ] } ], "source": [ "estudiante = Estudiante('Juanita', 24)\n", "\n", "estudiante.describir_estudiante()" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Juanita ha estudiado 6 horas.\n" ] } ], "source": [ "estudiante.estudiar_una_hora()\n", "\n", "estudiante.describir_estudiante()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Duck typing\n", "\n", "Python permite realizar **Duck typing** con sus objetos, lo cuál es parte de las definiciones del tipado dinámico. \n", "\n", "Este término proviene del dicho:\n", "\n", " \"If it walks like a duck and it quacks like a duck, then it must be a duck.\"\n", " Si camina como pato y grazna como pato, entonces debe ser un pato. \n", "\n", "\n", "En términos simples, significa que es más importante los métodos o atributos que un objeto implementa a que su tipo en si (cosa contraria a otros lenguajes como Java en donde siempre se verifica el tipo).\n", "\n", "Veamos el siguiente ejemplo:" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "class Pato:\n", " def nadar(self):\n", " print(\"🦆🦆 estoy nadando como pato 😀 🦆🦆\")\n", " \n", " def volar(self):\n", " print(\"🦆🦆 estoy volando!!! 🦆🦆\")" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "class Ballena:\n", " def nadar(self):\n", " print(\"🐋🐳 estoy nadando como ballena 🐳🐋\")\n", " " ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "🦆🦆 estoy nadando como pato 😀 🦆🦆\n" ] } ], "source": [ "animal_1 = Pato()\n", "animal_1.nadar()" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "🐋🐳 estoy nadando como ballena 🐳🐋\n" ] } ], "source": [ "animal_2 = Ballena()\n", "animal_2.nadar()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "¿Qué ver aquí?\n", "\n", "Notar que en ningún momento comprobé si animal_1 o animal_2 eran de algún tipo (clase) específicos, simplemente invoqué `nadar`." ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2021-03-29T17:22:31.322706Z", "start_time": "2021-03-29T17:22:31.307706Z" } }, "source": [ "### Nota interesante: Todo en Python son objetos\n", "\n", "Todos los tipos de datos básicos y funciones que hemos visto hasta ahora son objetos y, la mayoría de estos objetos tienen algún método asociado:" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:34:53.292227Z", "start_time": "2021-03-29T19:34:53.276229Z" } }, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = []\n", "a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pueden acceder a los métodos y atributos de cada objeto a través de la función dir" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir(a)" ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:34:58.866417Z", "start_time": "2021-03-29T19:34:58.849385Z" } }, "outputs": [], "source": [ "# Esto es en verdad un método de la clase list\n", "a.append(1)" ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:35:02.363094Z", "start_time": "2021-03-29T19:35:02.354095Z" } }, "outputs": [ { "data": { "text/plain": [ "[1]" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Principios Básicos de la POO\n", "\n", "POO no solo consiste en basar la programación en objetos. Estos deben también poder cumplir con los siguientes principios, los cuales veremos a continuación \n", "\n", "- Abstracción\n", "- Encapsulación\n", "- Polimorfismo\n", "- Herencia\n", "\n", "Todos estos los veremos a continuación.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Abstracción y Encapsulación \n", "\n", "\n", "- En primer lugar, **encapsulación** es el empaquetamiento de los datos del objeto para **esconderlos o restringir su acceso**. La idea de estos es evitar que estos cambien de manera accidental o sean accedidos por clases que no estaban autorizadas a ello.\n", "\n", "\n", "- La **abstracción** es el mecanismo por el cual, para modelar objetos reales, definimos qué atributos y métodos tendrán sin detallar la implementación de estos. Básicamente, es pensar qué debería tener un objeto y que funcionalidades debería ejecutar sin pensar en la implementación como tal.\n", "\n", "\n", "En la mayoría de los lenguajes orientados a objetos, la encapsulación de datos se logra por medio de métodos que permiten el acceso a los datos, llamados **métodos _getter_** (*retornan* el valor de los atributos) y **métodos _setter_** que permitir ciertos tipos de modificaciones y bloquea otras.\n", "\n", "\n", "> **Ejemplo 📖**\n", "\n", "Definimos la clase ```Cafetera``` esta posee un método *setter* y un método *getter* para el atributo ```numero_id```. " ] }, { "cell_type": "code", "execution_count": 77, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:40:22.064853Z", "start_time": "2021-03-29T19:40:22.048855Z" }, "code_folding": [] }, "outputs": [], "source": [ "class Cafetera:\n", " \"\"\"Clase Cafetera que prepara ricos cafecitos.\"\"\"\n", "\n", " tipos_cafe_admitidos = [\"expreso\", \"capuchino\", \"cortado\", \"doble\"]\n", " def __init__(self):\n", " \n", " self.pedido = None\n", "\n", " # getter\n", " def get_pedido(self):\n", " return self.pedido\n", "\n", " # setter\n", " def set_pedido(self, nuevo_pedido):\n", " \n", " if nuevo_pedido in self.tipos_cafe_admitidos:\n", " self.pedido = nuevo_pedido\n", " \n", " else:\n", " raise ValueError(\n", " 'Error ❌: el tipo_cafe debe ser uno de: \"expreso\" '\n", " f'\"capuchino\", \"cortado\" o \"doble\". Entregado: {nuevo_pedido}'\n", " )\n", "\n", " def trabajar(self):\n", " if self.pedido is not None:\n", " print(f\"☕☕ Preparando un café {self.pedido} ☕☕\")\n", " print('Cafe está listo :)')\n", "\n", " else:\n", " print(\"No tengo pedidos pendientes... 😴😴😴\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Ejemplo 📖**" ] }, { "cell_type": "code", "execution_count": 78, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:40:23.031854Z", "start_time": "2021-03-29T19:40:23.026856Z" }, "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "<__main__.Cafetera at 0x7fc704786460>" ] }, "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cafetera_1 = Cafetera()\n", "cafetera_1" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "☕☕ Preparando un café capuchino ☕☕\n", "Cafe está listo :)\n" ] } ], "source": [ "cafetera_1.set_pedido('capuchino')\n", "cafetera_1.trabajar()" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "Error ❌: el tipo_cafe debe ser uno de: \"expreso\" \"capuchino\", \"cortado\" o \"doble\". Entregado: moka", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[80], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mcafetera_1\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset_pedido\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mmoka\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n", "Cell \u001b[0;32mIn[77], line 20\u001b[0m, in \u001b[0;36mCafetera.set_pedido\u001b[0;34m(self, nuevo_pedido)\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpedido \u001b[38;5;241m=\u001b[39m nuevo_pedido\n\u001b[1;32m 19\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m---> 20\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[1;32m 21\u001b[0m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mError ❌: el tipo_cafe debe ser uno de: \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mexpreso\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m \u001b[39m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m 22\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcapuchino\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m, \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcortado\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m o \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdoble\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m. Entregado: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mnuevo_pedido\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m 23\u001b[0m )\n", "\u001b[0;31mValueError\u001b[0m: Error ❌: el tipo_cafe debe ser uno de: \"expreso\" \"capuchino\", \"cortado\" o \"doble\". Entregado: moka" ] } ], "source": [ "cafetera_1.set_pedido('moka')" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [], "source": [ "cafetera_1.pedido = 'malicia'" ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "☕☕ Preparando un café malicia ☕☕\n", "Cafe está listo :)\n" ] } ], "source": [ "cafetera_1.trabajar()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Nota 🗒️**: Si bien, estos patrones getter/setters son comunes en lenguajes como `Java` o `C#`, `Python` desincentiva la creación de setters y getters y le da el favor al acceso directo de los atributos. \n", "\n", "Veremos en unos momentos más la manera *pythonica* de implementar getters y setters a través de *propiedades*." ] }, { "cell_type": "code", "execution_count": 83, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:40:54.929595Z", "start_time": "2021-03-29T19:40:54.921595Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The Zen of Python, by Tim Peters\n", "\n", "Beautiful is better than ugly.\n", "Explicit is better than implicit.\n", "Simple is better than complex.\n", "Complex is better than complicated.\n", "Flat is better than nested.\n", "Sparse is better than dense.\n", "Readability counts.\n", "Special cases aren't special enough to break the rules.\n", "Although practicality beats purity.\n", "Errors should never pass silently.\n", "Unless explicitly silenced.\n", "In the face of ambiguity, refuse the temptation to guess.\n", "There should be one-- and preferably only one --obvious way to do it.\n", "Although that way may not be obvious at first unless you're Dutch.\n", "Now is better than never.\n", "Although never is often better than *right* now.\n", "If the implementation is hard to explain, it's a bad idea.\n", "If the implementation is easy to explain, it may be a good idea.\n", "Namespaces are one honking great idea -- let's do more of those!\n" ] } ], "source": [ "import this" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ">**There should be one-- and preferably only one --obvious way to do it.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Atributos Públicos, Protegidos y Privados\n", "\n", "Como se puede evidenciar en el ejercicio anterior, no se puede evitar directamente acceder y modificar atributos, aún cuando se definen funciones setter y getter. \n", "\n", "En algunos lenguajes de programación es posible agregar grados de restricción a los datos por medio de atributos privados y protegidos. \n", "\n", "- Los atributos **privados** por convención solo deben ser accedidos por los objetos de la misma clase.\n", "\n", "\n", "- Los atributos **protegidos** pueden ser accedidos por otros objetos de la clase y del módulo, pero no de otros objetos fuera de estos.\n", "\n", "\n", "- Finalmente, cualquier atributo que pueden ser accedidos por cualquier segmento de código (por lo tanto \"no encapsulado\") son llamados **públicos**.\n", "\n", "\n", "\n", "Importante: **En Python NO EXISTEN KEYWORDS PARA LIMITAR EL ACCESO A LOS ATRIBUTOS**\n", " \n", " \n", "Existe la convención de que un atributo es privado o protegido al anteponer un `_` antes del nombre del atributo. Ejemplo: " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:44:04.936225Z", "start_time": "2021-03-29T19:44:04.920225Z" } }, "outputs": [], "source": [ "class Cafetera:\n", " \"\"\"Clase Cafetera que prepara ricos cafecitos.\"\"\"\n", "\n", " tipos_cafe_admitidos = [\"expreso\", \"capuchino\", \"cortado\", \"doble\"]\n", "\n", " def __init__(self):\n", " self._pedido = None\n", "\n", " # getter\n", " def get_pedido(self):\n", " return self._pedido\n", "\n", " # setter\n", " def set_pedido(self, nuevo_pedido):\n", " \n", " if nuevo_pedido in self.tipos_cafe_admitidos:\n", " self._pedido = nuevo_pedido\n", " \n", " else:\n", " print(\n", " '❌ Error ❌: el tipo_cafe debe ser uno de: \"expreso\" '\n", " f'\"capuchino\", \"cortado\" o \"doble\". Entregado: {nuevo_pedido}'\n", " )\n", "\n", " def trabajar(self):\n", " if self._pedido is not None:\n", " print(f\"☕☕ Preparando un café {self._pedido} ☕☕\")\n", "\n", " else:\n", " print(\"No tengo pedidos pendientes... 😴😴😴\")\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Si bien en varios lugares recomiendan la convención de usar `__atributo` (lo cual es llamado [mangled names](https://en.wikipedia.org/wiki/Name_mangling#Python)) para ocultar un atributo, esto **NO LO HACE PRIVADO**. Solo cambia su nombre para evitar conflictos con subclases, lo cual veremos más adelante." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Luego, instanciamos una cafetera:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:44:05.945224Z", "start_time": "2021-03-29T19:44:05.930240Z" } }, "outputs": [], "source": [ "cafetera_1 = Cafetera()\n", "cafetera_1.set_pedido('doble')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Noten que aún podemos acceder a este atributo:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:44:35.569690Z", "start_time": "2021-03-29T19:44:35.553653Z" } }, "outputs": [], "source": [ "cafetera_1._pedido" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Y incluso cambiarlo por algo inválido!!!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cafetera_1._pedido = 'con queso'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cafetera_1.trabajar()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "### Propiedades\n", "\n", "Abusar de métodos *getter-setter*, puede llevar a complejizar demasiado el código (recordar que el código se lee más de lo que se escribe) y además viola el principio de una sola forma de hacer las cosas. \n", "La solución que propone python al problema de encapsulación es rediseñar la clase por medio de **propiedades**, mecanismos basados en decoradores que permiten implementar getters y setters de forma _pythonica_ (conservando la simpleza y legibilidad). \n", "\n" ] }, { "cell_type": "code", "execution_count": 91, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:50:09.936724Z", "start_time": "2021-03-29T19:50:09.898725Z" } }, "outputs": [], "source": [ "class Cafetera:\n", " \"\"\"Clase Cafetera que prepara ricos cafecitos.\"\"\"\n", "\n", " tipos_cafe_admitidos = [\"expreso\", \"capuchino\", \"cortado\", \"doble\"]\n", "\n", " def __init__(self):\n", " self._pedido = None\n", " \n", " @property\n", " def pedido(self):\n", " return self._pedido\n", " \n", "\n", " @pedido.setter\n", " def pedido(self, nuevo_pedido):\n", " \n", " if nuevo_pedido in self.tipos_cafe_admitidos:\n", " self._pedido = nuevo_pedido\n", " \n", " else:\n", " raise Exception(\n", " 'Error ❌: el tipo_cafe debe ser uno de: \"expreso\" '\n", " f'\"capuchino\", \"cortado\" o \"doble\". Entregado: {nuevo_pedido}'\n", " )\n", "\n", " def trabajar(self):\n", " if self._pedido is not None:\n", " print(f\"☕☕ Preparando un café {self._pedido} ☕☕\")\n", "\n", " else:\n", " print(\"No tengo pedidos pendientes... 😴😴😴\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En el siguiente ejemplo se aprecia como el *setter* es llamado cuando se ejecuta una asignación directa sobre el atributo (lo cual genera solo una manera de definir tal asignación sobre atributo) a la vez que no se pierde la lógica de la asignación." ] }, { "cell_type": "code", "execution_count": 90, "metadata": { "ExecuteTime": { "start_time": "2021-03-29T19:50:11.133Z" } }, "outputs": [ { "data": { "text/plain": [ "'Pedido asignado: capuchino'" ] }, "execution_count": 90, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cafetera_2 = Cafetera()\n", "cafetera_2.pedido = 'capuchino'\n", "\n", "cafetera_2.pedido" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cafetera_2.trabajar()" ] }, { "cell_type": "code", "execution_count": 86, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:49:07.354908Z", "start_time": "2021-03-29T19:49:07.337910Z" } }, "outputs": [ { "ename": "Exception", "evalue": "Error ❌: el tipo_cafe debe ser uno de: \"expreso\" \"capuchino\", \"cortado\" o \"doble\". Entregado: pan con queso", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mException\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[86], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m cafetera_2\u001b[38;5;241m.\u001b[39mpedido \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mpan con queso\u001b[39m\u001b[38;5;124m'\u001b[39m\n", "Cell \u001b[0;32mIn[84], line 21\u001b[0m, in \u001b[0;36mCafetera.pedido\u001b[0;34m(self, nuevo_pedido)\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_pedido \u001b[38;5;241m=\u001b[39m nuevo_pedido\n\u001b[1;32m 20\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m---> 21\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\n\u001b[1;32m 22\u001b[0m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mError ❌: el tipo_cafe debe ser uno de: \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mexpreso\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m \u001b[39m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m 23\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcapuchino\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m, \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcortado\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m o \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdoble\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m. Entregado: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mnuevo_pedido\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m 24\u001b[0m )\n", "\u001b[0;31mException\u001b[0m: Error ❌: el tipo_cafe debe ser uno de: \"expreso\" \"capuchino\", \"cortado\" o \"doble\". Entregado: pan con queso" ] } ], "source": [ "cafetera_2.pedido = 'pan con queso'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Definir propiedades es la manera *pythonica* de encapsular atributos **accesibles por el usuario**. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Pregunta ❓**: ¿Podemos seguir accediendo a `_pedido`?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Herencia \n", "\n", "La herencia es una herramienta que permite obtener nuevas clases a partir de otras. Al hacer esto, se obtiene una jerarquía de clases, donde las clases de niveles más bajos adquieren atributos y métodos pre establecidos por las clases de jerarquías más altas. \n", "\n", "El beneficio directo de utilizar herencia, es poder de reciclar y modificar el comportamiento de una clase base. Más aún, una clase derivada puede añadir nuevas propiedades atributos y métodos, extendiendo la funcionalidad inicial. \n", "\n", "\n", "La sintaxis asociada al proceso de herencia se resume a continuación.\n", "\n", "```python\n", "class SubClase(ClaseBase):\n", " hacer_cosas()\n", " ...\n", " \n", "```\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Ejemplo 📖**: Instrumentos y piano\n", "\n", "Se define la clase ```Instrumento``` de esta clase base, se deriva la subclase ```Piano```. " ] }, { "cell_type": "code", "execution_count": 106, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:56:26.888564Z", "start_time": "2021-03-29T19:56:26.880565Z" } }, "outputs": [], "source": [ "class Instrumento:\n", " '''Clase ejemplo representando un instrumento abstracto.'''\n", "\n", " def __init__(self, name):\n", " self.name = name\n", "\n", " def nombre(self):\n", " print('Este instrumento es un ' + self.name)\n", "\n", " def tocar(self):\n", " print('🎵🎵🎵')\n", "\n", "\n", "class Piano(Instrumento):\n", " '''Sub clase sencilla, representa un instrumento musical.'''\n", " \n", " def __init__(self):\n", " super().__init__(name = \"Piano\")\n", "\n", " \n", " def solo(self):\n", " print('🎹 🎵🎵🎵 🎹')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Aquí podemos observar como la clase `Piano` poseen los métodos `.nombre()` y `.tocar()` aún cuando este no se define de manera explicita.\n" ] }, { "cell_type": "code", "execution_count": 93, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:56:16.183484Z", "start_time": "2021-03-29T19:56:16.160484Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Este instrumento es un Flauta\n" ] } ], "source": [ "ins = Instrumento('Flauta')\n", "ins.nombre()" ] }, { "cell_type": "code", "execution_count": 107, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Este instrumento es un Piano\n" ] } ], "source": [ "piano = Piano()\n", "piano.nombre()" ] }, { "cell_type": "code", "execution_count": 102, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "🎵🎵🎵\n" ] } ], "source": [ "ins.tocar()" ] }, { "cell_type": "code", "execution_count": 103, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "🎵🎵🎵\n" ] } ], "source": [ "piano.tocar()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La verdadera funcionalidad de la herencia es implementar nuevos funcionamientos y atributos adicionales a la clase que se está extendiendo. En las siguientes celdas podemos apreciar esto:" ] }, { "cell_type": "code", "execution_count": 111, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:54:02.443252Z", "start_time": "2021-03-29T19:54:01.670356Z" } }, "outputs": [ { "ename": "AttributeError", "evalue": "'Instrumento' object has no attribute 'solo'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[111], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mins\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msolo\u001b[49m()\n", "\u001b[0;31mAttributeError\u001b[0m: 'Instrumento' object has no attribute 'solo'" ] } ], "source": [ "ins.solo()" ] }, { "cell_type": "code", "execution_count": 109, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:54:06.508264Z", "start_time": "2021-03-29T19:54:06.493252Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "🎹 🎵🎵🎵 🎹\n" ] } ], "source": [ "# El solo está definido solo para el Piano\n", "piano.solo()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Pregunta ❓**: Puedo heredar atributos y funcionalidades de más de una clase" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "\n", "### Polimorfismo\n", "\n", "El polimorfismo se refiere al principio por el que es posible enviar mensajes sintácticamente iguales a objetos de clases distintas. \n", "El único requisito que deben cumplir los objetos que se utilizan de manera polimórfica es saber responder al mensaje que se les envía. \n", "\n", "Esto es ampliamente utilizado en herencia, como pudimos ver en el ejemplo anterior. \n", "Anulación de métodos también es un buen ejemplo de esto.\n", "\n", "\n", "#### Anulación de métodos\n", "\n", "La anulación de métodos en herencia, consiste en modificar o redefinir los métodos de una clase base a una clase derivada. El nombre de esta operación proviene *method overriding*. \n", "\n", "> **Ejemplo 📖** \n", "\n", "Se define la clase ```Ciudadano```. Como clase derivada se define ```Medico```." ] }, { "cell_type": "code", "execution_count": 112, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:59:54.370825Z", "start_time": "2021-03-29T19:59:54.354826Z" }, "tags": [] }, "outputs": [], "source": [ "class Ciudadano:\n", " \n", " def __init__(self, name):\n", " self.name = name\n", "\n", " def saludar(self, otro_nombre):\n", " print(f'Hola {otro_nombre}!, un gusto 😀')\n", "\n", "\n", "class Medico(Ciudadano):\n", "\n", " def saludar(self, otro_nombre):\n", " print(f'Bienvenido a mi consulta {otro_nombre}!')\n", " print(f'Soy el doctor {self.name}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Al ejecutar, comprobamos que el método ```.saludo()``` es ignorado por la subclase Medico." ] }, { "cell_type": "code", "execution_count": 113, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hola María!, un gusto 😀\n" ] } ], "source": [ "ciudadano = Ciudadano('Juanito')\n", "ciudadano.saludar(\"María\")" ] }, { "cell_type": "code", "execution_count": 114, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T19:59:59.545867Z", "start_time": "2021-03-29T19:59:59.533868Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bienvenido a mi consulta María!\n", "Soy el doctor House\n" ] } ], "source": [ "doc = Medico('House')\n", "doc.saludar(\"María\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "### Sobrecarga de Operadores y Métodos Mágicos\n", "\n", "Los métodos mágicos, corresponden a funciones especiales con nombres fijos, por lo general denotados por doble guión bajo. Hasta el momento se ha estudiado el método ```__init__()``` que sobrecarga la inicialización de clases." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ClaseBasica:\n", " pass\n", "\n", "siguiente_objeto = ClaseBasica() # invocar ClaseBasica() en verdad ejecuta el método definido en init." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ClaseInitSobrecargado:\n", " def __init__(self):\n", " print('Me estoy instanciado...')\n", " \n", " \n", "ClaseInitSobrecargado()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Se aprecia que no es necesario especificar un método ```__init__()``` para que la clase tenga un constructor predeterminado. Por otra parte, el concepto de **sobrecarga de operadores**, este se aprecia en las siguientes operaciones ya utilizadas:" ] }, { "cell_type": "code", "execution_count": 115, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "18\n", "[1, 2, 3, 4, 5]\n", "nueve nueve\n" ] } ], "source": [ "print(9 + 9)\n", "print([1, 2] + [3, 4, 5])\n", "print('nueve ' + 'nueve')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "El operador sobrecargado es ```+``` pues está habilitado para trabajar de manera *polimorfica*, es decir, en distintas clases o tipos de datos. Los métodos mágicos juegan un papel fundamental en la sobrecarga de operadores. \n", "\n", "> **Ejemplo 📖**: Sobrecargando el mágico asociado al operador ```+```: ```__add__()```. " ] }, { "cell_type": "code", "execution_count": 116, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T20:01:28.094253Z", "start_time": "2021-03-29T20:01:28.085248Z" } }, "outputs": [], "source": [ "class Curso:\n", " '''Clase modificada para sobrecargar el operado +.'''\n", " def __init__(self, nombre):\n", " self._nombre = nombre\n", " self._estudiantes = {}\n", "\n", " def __add__(self, other):\n", " self._estudiantes[other.nombre] = other\n", " return self._estudiantes\n", "\n", "\n", "mds7202 = Curso(\"MDS7202\")" ] }, { "cell_type": "code", "execution_count": 117, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'María': <__main__.Estudiante at 0x7fc7040fec40>}" ] }, "execution_count": 117, "metadata": {}, "output_type": "execute_result" } ], "source": [ "maria = Estudiante(\"María\", 23)\n", "\n", "mds7202 + maria" ] }, { "cell_type": "code", "execution_count": 118, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'María': <__main__.Estudiante at 0x7fc7040fec40>}" ] }, "execution_count": 118, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mds7202._estudiantes" ] }, { "cell_type": "code", "execution_count": 119, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'María': <__main__.Estudiante at 0x7fc7040fec40>,\n", " 'Juan': <__main__.Estudiante at 0x7fc6ef583eb0>}" ] }, "execution_count": 119, "metadata": {}, "output_type": "execute_result" } ], "source": [ "juan = Estudiante(\"Juan\", 24)\n", "mds7202 + juan" ] }, { "cell_type": "code", "execution_count": 120, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'María': <__main__.Estudiante at 0x7fc7040fec40>,\n", " 'Juan': <__main__.Estudiante at 0x7fc6ef583eb0>,\n", " 'Carla': <__main__.Estudiante at 0x7fc6ef7117c0>}" ] }, "execution_count": 120, "metadata": {}, "output_type": "execute_result" } ], "source": [ "carla = Estudiante(\"Carla\", 25)\n", "mds7202 + carla" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "> **Ejercicios ✏️**\n", "\n", "1. Nombre los métodos mágicos asociados a los operadores ```-```, ```*```, ```//```, ```/```, ```%```, ```+=```,```*=```, ```<```, ```==``` y ```>=```.\n", "\n", "2. Defina la clase ```Temp```. Esta clase modela unidades de medida de temperatura (°C,°F y K) y permite sumarlas obteniendo el resultado en kelvins. Para esto, implemente:\n", " 1. Un atributo protegido ```_temp_conv```, consistente en un diccionario con los valores de conversión (ej. ```{'C': 274.15}```).\n", " 2. Un constructor que inicializa los atributos ```.val``` (float) y ```.unit``` (str). A estos atributos se les debe asociar una propiedad y setter correspondiente, donde se comprueba que los valores ```val``` sean superiores a 0 K y que las unidades ```.unit``` solo puedan ser 'C','F' o 'K'. \n", " 3. Un método ```.to_kelvin()``` que utiliza los atributos ```__temp_conv```, ```unit``` y ```val``` y retorna el valor correspondiente en kelvin.\n", " 4. Una sobrecarga al operador suma, de manera tal, que se puedan sumar objetos ```Temp``` con unidades de medición arbitrarias y se retorne un objeto ```Temp``` con el resultado en kelvins.\n", " 5. Una sobrecarga al operador ```__str__``` (método que conversión a tipo de dato string) que retorne el resultado del método ```.to_kelvin()``` concatenado con ' K'.\n", " 6. Una sobrecarga al operador ```__repr__``` (método de representación de objetos) que retorne un string de la forma ```'Temp('+ str(self.val) + ',' + self.unit + ')'```.\n", " 7. Defina objetos t1 y t2 de la clase ```Temp``` luego defina un objeto t3 como la suma de t1 y t2. Ejecute ```print(t3)```, ```str(t3)``` y luego ejecute ```t3```, relacione los *outputs* con el funcionamiento de ```__str__``` y ```__repr__```." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Excepciones ❌\n", "\n", "\n", "Las excepciones son errores que se producen al ejecutar algún segmento del código. \n", "Al momento de ocurrir, termina la ejecución del programa.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejemplo**: Recorramos una lista hasta un indice que no existe:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T20:15:59.804233Z", "start_time": "2021-03-29T20:15:59.788234Z" } }, "outputs": [], "source": [ "lista = [1000, 2000, 3000, 4000, 5000]\n", "\n", "for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:\n", " print(lista[i])\n", " \n", "print('El programa continua')\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "1 + 'y' " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'y'/10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manejo de Excepciones\n", "\n", "Para que el programa no se detenga, podemos manjear las excepciones a través de la estructura `try-except`.\n", "\n", "Su uso es sencillo: todo el código que se desea ejecutar se encapsula en un bloque `try`. Luego, en el caso que ocurra algun error o excepción, le programa pasara al bloque `except`, el cuál tendrá código dedicado a manejar dicha excepción.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T20:15:37.941407Z", "start_time": "2021-03-29T20:15:37.930407Z" } }, "outputs": [], "source": [ "lista = [1, 2, 3, 4, 5]\n", "\n", "try:\n", " for i in range(10):\n", " print(lista[i])\n", "except:\n", " print(f'Error!, indice {i} fuera de la lista. Omitiendo el elemento...\\n')\n", "\n", "print('El programa continua 😀')" ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2021-03-29T16:01:20.679052Z", "start_time": "2021-03-29T16:01:20.665010Z" } }, "source": [ "Podemos acceder incluso al error usando `except Exception as e:`" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T20:16:16.240143Z", "start_time": "2021-03-29T20:16:16.219143Z" } }, "outputs": [], "source": [ "lista = [1, 2, 3, 4, 5]\n", "\n", "try:\n", " for i in range(10):\n", " print(lista[i])\n", "except Exception as e:\n", " print(f'Error! Descripción del error: {e}')\n", "\n", "print('El programa continua 😀')" ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2021-03-29T16:01:40.156002Z", "start_time": "2021-03-29T16:01:40.145011Z" } }, "source": [ "### Tipos de Excepciones en Python\n", "\n", "\n", "Existen varios tipos de excepciones en Python, cada una especificada para un caso en particular de error.\n", "\n", "\n", "> **Ejercicio ✏️** Para entender que tipos de excepciones pueden existir en Python:\n", "\n", "1. Nombre al menos 6 tipos de excepciones en Python. (*hint:https://docs.python.org/3/library/exceptions.html#bltin-exceptions* )\n", "2. Genere un código que produzca exepciones del tipo: ```NameError```,```ZeroDivisionError``` y ```TypeError```.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T20:17:00.880474Z", "start_time": "2021-03-29T20:17:00.839476Z" } }, "outputs": [], "source": [ "f" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T20:16:55.570952Z", "start_time": "2021-03-29T20:16:55.553923Z" } }, "outputs": [], "source": [ "1/0" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2021-03-29T20:17:23.294111Z", "start_time": "2021-03-29T20:17:23.277112Z" } }, "outputs": [], "source": [ "lista['hola']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Manejar excepciones específicas\n", "\n", "Existe la posibilidad de generar código que gestione independientemente cada tipo de excepcion por separado (lo que es bastante útil cuando tenemos distintos tipos de errores que manejar). Esto se logra a través de la siguiente sintaxis:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Pregunta ❓**: ¿Cómo se manejarán las excepciones en el siguiente caso?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lista = [1, 2, 3, 4, 5, 0]\n", "\n", "try:\n", " for i in range(10):\n", " print(1 / lista[i])\n", " \n", "except IndexError as e:\n", " print(f'Error!, indice {i} fuera de la lista. Omitiendo el elemento...\\n')\n", "\n", "except ZeroDivisionError as e:\n", " print(f\"Error {e}!, se intentó dividir {1}/{i}. Omitiendo caso...\\n\")\n", " \n", "\n", "print('El programa continua 😀')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Agrupar excepts\n", "\n", "Se puede ocupar un mismo bloque `except` para tratar varias excepciones a la vez." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lista = [1, 2, 3, 4, 5, 0]\n", "\n", "try:\n", " for i in range(10):\n", " print(1 / lista[i])\n", " \n", "except (ZeroDivisionError, IndexError) as e:\n", " print(f\"Error: {e}. Omitiendo caso...\")\n", "\n", "print('El programa continua 😀')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Pregunta ❓**: Por qué es mejor especificar el error en vez de capturar todas las excepciones con `except Exception as e`. Usa el siguiente ejemplo para entenderlo." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lista = [1, 2, 3, 4, \"5\", 0]\n", "\n", "try:\n", " for i in range(10):\n", " print(1 / lista[i])\n", " \n", "except (ZeroDivisionError, IndexError) as e:\n", " print(f\"Error: {e}. Omitiendo caso...\")\n", "\n", "print('El programa continua 😀')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" }, "nbTranslate": { "displayLangs": [ "es", "en" ], "hotkey": "alt-t", "langInMainMenu": true, "sourceLang": "en", "targetLang": "es", "useGoogleTranslate": true }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "269.867px" }, "toc_section_display": true, "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }