{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "< [Ciclo For](PythonIntroCh6.ipynb) | [Contenido](PythonIntro.ipynb) | [Modulos](PythonIntroCh8.ipynb) >" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 7. Clases\n", "## 7.1 Introducción\n", "Una cosa que conocerás acerca de la programación, es que los programadores son unos perezosos, si han hecho algo antes, porque deberían hacerlo nuevamente?\n", "\n", "Eso es lo que cubren las funciones en Python. Ya has hecho que tu código realice algo especial. Ahora deseeas hacerlo nuevamente. Coloca ese código especial dentro de una función, y reutilizarlo por todo lo que vale. Puedes referirte a una función en cualquier parte del código, y la computadora siempre sabrá de que estas hablando. Práctico? eh?\n", "\n", "Por supuesto, las funciones tienen sus limitaciones. Las funciones no almacenan información como los hacen las variables - cada vez que se ejecuta una función, comienza de nuevo. Sin embargo, algunas funciones y variables estan muy relacionadas, y requieren la interacción unas con otras. Por ejemplo, imagina un palo de golf, tienes información acerca de esto (por ejemplo, variables) como la longitud de la variable, el material del mango y el material de la cabeza. También tiene funciones asociadas con este, como funciones de balanceo del palo del golf, o la función de romper el palo por pura frustración. Para estas funciones, requieres conocer las variables de la longitud de la varilla, material de la cabeza, etc.\n", "\n", "Puede funcionar facilmente con funciones normales. Los parámetros afectan el efecto de una función. Pero que pasa si una función requiere afectar a las variables? ¿Qué pasa si en cada ocasión tu palo de golf, la varilla se debilita, el agarre del mango se desgasta un poco, te frustrarás un poco más, y harás un nuevo arañazo en la cabeza del palo. Una función no puede hacer esto. Una función sólo tiene una salida, no cuatro o cinco, o quinientos. Lo que se requiere es una forma de agrupar las funciones y las variables se encontrarán relacionadas en un lugar para que puedan interactuar entre sí.\n", "\n", "Lo más probable es que tengas más de un palo de golf. Sin clases, tienes que escribir un montón de código para cada palo de golf diferente. Esto es un dolor porque todos los palos de golf comparten características comunes, sólo algunos cambian sus propiedades - como el material de su eje y peso. Lo ideal es contar con el diseño de un palo de golf. Cada ocasión que creas un nuevo palo, simplemente especifica sus atributos - la longitud de la varilla, peso, etc. \n", "\n", "¿O que pasa si requiere un palo de golf con características adicionales? Tal vez decida añadir un reloj a su palo de golf(no sé porque fue idea suya) . ¿Esto significa que tenemos que crear un palo de golf desde cero? Tendriamos que escribir el código de un palo de golf, más aún, el código del reloj, para nuestro nuevo diseño. ¿No sería mejor que tomáramos un palo de golf existentes y le añadieramos un reloj?\n", "\n", "Estos problemas se resuelven con una cosa llamada Programación Orientada a Objetos (POO). Coloca las funciones y variables juntas de manera que puedan verse y trabajarse juntas, replicarse y alterarse cuando sea necesario, y usamos una cosa llamada `class` para hacer esto.\n", "\n", "\n", "## 7.2 Creación de una `Class`\n", "¿Qué es una clase? Piensala como un plano. No es algo en sí mismo, simplemente describe como hacer algo. Puedes crear partes de los objetos desde un plano - conocido tecnicamente como una *instance*.\n", "\n", "¿Cómo hacer estas cosas llamadas 'classes'? muy fácil, con el operador `class`:\n", "\n", "```Python\n", "# Definición de una clase\n", "class class_name:\n", " [sentencia 1]\n", " [sentencia 2]\n", " [sentencia 3]\n", " [etc]\n", "```\n", "\n", "¿Tiene un poco de sentido? Está bien, aquí hay un ejemplo, que crea la definición de una clase `Shape`:\n", "\n", "```Python\n", "#Un ejemplo de clase Shape\n", "class Shape:\n", " def __init__(self,x,y):\n", " self.x = x\n", " self.y = y\n", " description = \"Esta forma no ha sido descrito aún\"\n", " author = \"Nadie ha afirmado en hacer esta forma todavía\"\n", " def area(self):\n", " return self.x * self.y\n", " def perimeter(self):\n", " return 2 * self.x + 2 * self.y\n", " def describe(self,text):\n", " self.description = text\n", " def authorName(self,text):\n", " self.author = text\n", " def scaleSize(self,scale):\n", " self.x = self.x * scale\n", " self.y = self.y * scale\n", "```\n", "\n", "Has creado la descripción de una forma (shape) - es decir, las variables- y que operaciones pueden hacerse con la clase Shape (es decir, las funciones). Es muy importante - no has realizado un forma aún, simplemente ls descripción de los mismos. La forma tiene un ancho (`x`), alto (`y`), un area y un perimetro (`area(self)` and `perimeter(self)`). Ningún código se ejecuta cuando defines una clase - sólo realizar la definición de funciones y variables.\n", "\n", "La función llamada `__init__` se ejecuta cuando creas una instancia de `Shape` - esto es, cuando creamos una forma real, a diferencia del plano, `__init__` se ejecuta. Entenderemos como funciona esto más adelante.\n", "\n", "`self` es la forma en que nos referimos a las cosas en una clase dentro de si misma. `self` es el primer parámetro en cualquier función definida dentro de una clase. Cualquier función o variables creada sobre el primer nivel de indentación (esto es, las líneas de código que inicias con TAB a la derecha de donde colocamos la clase `Shape` se colocan automáticamente dentro del self. Para acceder a estas funciones y variables en cualquier otro lugar dentro de la clase, su nombre debe ir precedido de `self` y punto.(ejemplo `self.variable_name`). Sin `self` unicamente puedes utilizar variables dentro de la función donde estan definidas, no en otras funciones de la misma `class`.\n", "\n", "## 7.3 Utilizando una clase `class`\n", "Esta muy bien que podamos hacer una clase, pero ¿Cómo la usamos? Aquí hay un ejemplo de como se crea una instancia de clase. Suponga que el código anterior ya se había ejecutado:\n", "\n", "```Python\n", "rectangle = Shape(100,45)\n", "```\n", "¿Qué se ha hecho? Se necesita un poco de explicación...\n", "La función `__init__` realmente entra en juego en este momento. Creamos una instancia de una clase pasando primero su nombre (en este caso, `Shape`) y entonces, en corchetes, los valores se pasan la función `__init__`. La función init se ejecuta (usando los parámetros que colocas entre corchetes) y entonces generas una instancia de la clase, la cual se llama en este caso `rectangle`.\n", "\n", "Piensa en nuestra instancia de clase, `rectangle`, como una colección autónoma de variables y funciones. De la misma forma que que utilizamos `self` para acceder a funciones y variables de la instancia de clase desde fuera de si misma. Añadiendo todo el código anterior, hariamos esto:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Shape:\n", " def __init__(self,x,y):\n", " self.x = x\n", " self.y = y\n", " description = \"This shape has not been described yet\"\n", " author = \"Nobody has claimed to make this shape yet\"\n", " def area(self):\n", " return self.x * self.y\n", " def perimeter(self):\n", " return 2 * self.x + 2 * self.y\n", " def describe(self,text):\n", " self.description = text\n", " def authorName(self,text):\n", " self.author = text\n", " def scaleSize(self,scale):\n", " self.x = self.x * scale\n", " self.y = self.y * scale\n", " \n", "rectangle = Shape(100,45)\n", "\n", "#finding the area of your rectangle:\n", "print(rectangle.area())\n", "\n", "#finding the perimeter of your rectangle:\n", "print(rectangle.perimeter())\n", "\n", "#describing the rectangle\n", "rectangle.describe(\"A wide rectangle, more than twice\\\n", " as wide as it is tall\")\n", "\n", "#making the rectangle 50% smaller\n", "rectangle.scaleSize(0.5)\n", "\n", "#re-printing the new area of the rectangle\n", "print(rectangle.area())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como puedes ver, donde `self` podría ser usado dentro de la instancia de clase, su nombre signado es utilizado cuando esta fuera de la clase. Hacemos esto para ver y cambiar las variables dentro de las clases, y acceder a las funciones que estan allí.\n", "\n", "No estamos limitados a una sola instancia de una clase - podemos crear tantas instancias como queramos. Se podría hacer esto:\n", "\n", "```Python\n", "longrectangle = Shape(120,10)\n", "fatrectangle = Shape(130,120)\n", "```\n", "Ambas instancias `longrectangle` y `fatrectangle` tienen sus propias funciones y variables contenidas dentro de cada una - ellos son totalmente independientes una de otra. No hay límite para el número de instancias que puedas crear.\n", "\n", "Experimento con pocas instancias diferentes en el campo de arriba." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7.4 Lingo (Jerga)\n", "La Programación Orientada a Objetos tiene una serie de jerga asociada. Es hora de aclarar todo:\n", "* Cuando describimos una clase `class`, definimos la clase *defining* (como con las funciones)\n", "* La capacidad de agrupar funciones y variables similares es llamada encapsulación *encapsulation*\n", "* La palabra `class` puede usarse cuando describes el código donde la clase es definida (como una función es definida), y también refiere una instancia de clase `class` - puede resultar confuso, asegurate en conocer de que forma estamos hablando acerca de clases\n", "* Una variable dentro de una clase es conocida como un atributo *Attribute*\n", "* Una función dentro de una clase es conocida como método *method*\n", "* Una clase se encuentra en la misma categoría de cosas como variables, listas, diccionarios, etc. es decir, son objetos *objects*\n", "* Una clase es conocida como una estructura de datos 'data structure' - esta contiene datos y métodos para procesar los datos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7.5 Herencia\n", "Echemos un vistazo a la introducción. Sabemos que las clases agrupan variable y funciones, conocidas como atributos y métodos, así que ambos los datos y el código para procesarse se encuentran en el mismo lugar. Podemos crear cualquier número de instancias (objetos) de una clase, así que no requerimos escribir código para la creación de cada objeto nuevo. Pero ¿Qué pasa con la creación de funcionalidades extras para el diseño de un palo de golf? Aquí es donde entra el acción el proceso de la herencia *inheritance*.\n", "\n", "Python realiza la herencia muy fácil. Nosotros definimos una nueva clase, basada en otra, una clase 'padre'. La nueva clase trae consigo muchas características de la clase \"padre\", además podemos añadir más cosas a ella. Cualquier atributo o método con el mismo nombre de la clase \"padre\", es utilizada en lugar de la clase. ¿Recuerdas la clase `Shape`?\n", "\n", "```Python\n", "class Shape:\n", " def __init__(self,x,y):\n", " self.x = x\n", " self.y = y\n", " description = \"This shape has not been described yet\"\n", " author = \"Nobody has claimed to make this shape yet\"\n", " def area(self):\n", " return self.x * self.y\n", " def perimeter(self):\n", " return 2 * self.x + 2 * self.y\n", " def describe(self,text):\n", " self.description = text\n", " def authorName(self,text):\n", " self.author = text\n", " def scaleSize(self,scale):\n", " self.x = self.x * scale\n", " self.y = self.y * scale\n", "```\n", "\n", "Si deseamos la definición de una nueva clase, digamos un cuadrado, basado en la clase previa Shape, haríamos lo siguiente:\n", "\n", "\n", "```Python\n", "class Square(Shape):\n", " def __init__(self,x):\n", " self.x = x\n", "\t self.y = x\n", "```\n", "Es como la definición de una clase, pero esta vez, colocamos entre parentesis, la clase padre que heredamos. Como puedes ver, describimos un cuadrado realmente rápido *quickly*, gracias a esto. Es porque heredamos todo desde la clase Shape, y cambiar unicamente lo que hay que cambiar. En este caso, redefinimos la función `__init__` de Shape, para que los valores X y Y sean los mismos.
\n", "\n", "Aprovechemos lo aprendido, creemos otra clase, ahora heredada de `Square`. Estarán dos cuadrados, uno inmediatamente a la izquierda del otro:\n", "```Python\n", "# La forma shape luce así:\n", "# _________\n", "#| | |\n", "#| | |\n", "#|____|____|\n", "\n", "class DoubleSquare(Square):\n", " def __init__(self,y):\n", " self.x = 2 * y\n", " self.y = y\n", " def perimeter(self):\n", " return 2 * self.x + 3 * self.y\n", "```\n", "Esta ocasión, hemos redefinido la función `perimeter`, ya que hay una línea en medio de las formas. Intenta crear una instancia (objeto) en el campo de abajo y juega con diferentes valores. Como la clase `class Shape` ha sido ejecutada, puedes simplemente añadir nuevas clases y agregar la definición de las instancias." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Square(Shape):\n", " def __init__(self,x):\n", " self.x = x\n", " self.y = x\n", " \n", "# The shape looks like this:\n", "# _________\n", "#| | |\n", "#| | |\n", "#|____|____|\n", "\n", "class DoubleSquare(Square):\n", " def __init__(self,y):\n", " self.x = 2 * y\n", " self.y = y\n", " def perimeter(self):\n", " return 2 * self.x + 3 * self.y\n", "testsquare = Square(5)\n", "testdouble = DoubleSquare(6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7.6 Punteros y Diccionarios de Clases\n", "\n", "Recapítulando, cuando comparamos una variable con otra, por ejemplo `variable2 = variable1`, la variable del lado izquierdo toma el valor del lado derecho. Con las instancias de clase (objetos) sucede una pequeña diferencia - el nombre del lado izquierdo se convierte en la instancia de clase del lado derecho. Así en la expresión `instance2 = instance1`, en la instancia `instance2` esta 'apuntado' a `instance1` - hay dos nombres dados a una única instancia de clase, y puedes acceder a la instancia de clase con cualquiera de los nombres.\n", "\n", "En otro lenguajes de programación, se hacen acciones parecidas utilizando punteros *pointers*, sin embargo en Python todo esto sucede tras bambalinas. La última cosa que cubriremos son los Diccionarios de clases. Considerando lo que acabamos de aprender, podemos asignar una instancia de clase a una entrada en una lista o diccionario. Esto permite que exista prácticamente cualquier cantidad de instancias de clases cuando se ejecuta nuestro programa. Echemos un vistazo al siguiente ejemplo, y veamos cómo se hace lo que estoy hablando:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Nuevamente, asumimos la definición sobre la clase Shape,\n", "# Square and DoubleSquare han sido ejecutadas.\n", "# Primero, crea un diccionario:\n", "dictionary = {}\n", "\n", "# Entonces, crea algunas instancias de clases en el diccionario:\n", "dictionary[\"DoubleSquare 1\"] = DoubleSquare(5)\n", "dictionary[\"long rectangle\"] = Shape(600,45)\n", "\n", "#Ahora puedes utilizar como una clase normal:\n", "print(dictionary[\"long rectangle\"].area())\n", "\n", "dictionary[\"DoubleSquare 1\"].authorName(\"The Gingerbread Man\")\n", "print(dictionary[\"DoubleSquare 1\"].author)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como puedes ver, sólo reemplazamos nuestros aburridos nombres sobre el lado izquierdo con una excitante, nueva, dinámica, entrada del diccionario. Muy bonito, eh?\n", "\n", "## 7.7 Conclusiones\n", "¡Y esta es la lección sobre las clases! No creeras el tiempo que me ha llevado escribir esto de forma clara, y todavía no estoy satisfecho! He reescrito y reescrito la mitad de la lección otra vez, y si aún estás confundido, probablemente la repasaré de nuevo. Quizás los he confundido con mi propia confusión de este tema, pero recuerda - lo importante no es el nombre de algo, sino lo que hace (esto no funciona en un entorno social, créanme...;))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "< [Ciclo For](PythonIntroCh6.ipynb) | [Contenido](PythonIntro.ipynb) | [Modulos](PythonIntroCh8.ipynb) >" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.9" } }, "nbformat": 4, "nbformat_minor": 2 }