{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "view-in-github", "colab_type": "text" }, "source": [ "\"Open" ] }, { "cell_type": "markdown", "id": "081c90a0", "metadata": { "id": "081c90a0" }, "source": [ "# Ejemplos sin herencia [3]" ] }, { "cell_type": "markdown", "id": "a8102966", "metadata": { "id": "a8102966" }, "source": [ "## Asignaturas\n", "Disponemos de la clase Carrera, por ejemplo, ingeniería, en la que vamos a añadir en un diccionario las materias (álgebra, física, química, ...) con un código identificador de esta materia. \n", "Creamos el método agregarMateria para que dado el nombre de la materia y el código podamos añadir los elementos clave (que es el código) y valor (que es el nombre de la materia) al diccionario. Si hubiéramos usado listas se haría con append, pero al tratarse de un diccionario se hace con el nombre del diccionario (materias) así:\n", "\n", "materias[codigo] = materia\n", "\n", "por ejemplo\n", "\n", "materias[111] = \"cálculo\"\n", "\n", "Luego, tenemos la clase Materia con dos atributos en el constructor: materia y profesor. \n", "\n", "Nos gustaría poder proteger (ocultar) el acceso a algunos atributos y métodos." ] }, { "cell_type": "code", "execution_count": 1, "id": "08a7a7a8", "metadata": { "id": "08a7a7a8" }, "outputs": [], "source": [ "class Carrera:\n", " def __init__(self, nombre):\n", " self.nombre = nombre\n", " self.materias = {} # diccionario clave:valor\n", "\n", " def agregarMateria(self, materia, codigo):\n", " self.materias[codigo] = materia # como es un diccionario se agrega así.\n", "\n", "class Materia:\n", " def __init__(self, nombre, profesor):\n", " self.nombre = nombre\n", " self.profesor = profesor\n", "\n", "ing = Carrera(\"Ingeniería\")\n", "algebra = Materia(\"Álgebra\", \"Rosa López\")\n", "fisica = Materia(\"Física\", \"Roberto Ruiz\")\n", "quimica = Materia(\"Química\", \"Sonia Arce\")" ] }, { "cell_type": "code", "execution_count": 2, "id": "85d87e07", "metadata": { "id": "85d87e07" }, "outputs": [], "source": [ "ing.agregarMateria(algebra, 345)" ] }, { "cell_type": "code", "execution_count": 3, "id": "d9febeef", "metadata": { "id": "d9febeef", "outputId": "6eb4f4e1-eb26-46f9-bea4-c86b1270e88a", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{345: <__main__.Materia at 0x782829e80d90>}" ] }, "metadata": {}, "execution_count": 3 } ], "source": [ "ing.materias # vemos que se ha agregado una materia" ] }, { "cell_type": "code", "execution_count": 4, "id": "feb90471", "metadata": { "id": "feb90471", "outputId": "0d642062-fddb-416c-e6fa-f0d3e46defc3", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{345: <__main__.Materia at 0x782829e80d90>,\n", " 589: <__main__.Materia at 0x782829e801f0>}" ] }, "metadata": {}, "execution_count": 4 } ], "source": [ "ing.agregarMateria(fisica, 589)\n", "ing.materias # vemos que se han agregado dos materias" ] }, { "cell_type": "markdown", "id": "d3326594", "metadata": { "id": "d3326594" }, "source": [ "## @property\n", "Métodos getter y setter: usados para gestionar el ocultamiento. Los métodos Get y Set están traídos de la programación al estilo JAVA.\n", "\n", "En Python se usan más las **properties** (propiedades) esto supone que a partir de los atributos vamos a crear propiedades que son las que definen a un objeto. Vamos a enmascarar estos atributos dentro de propiedades. Podemos hacerlo con todos los atributos o con algunos concretos.\n", "\n", "Vamos a crear una **property**. Supongamos que definimos un nuevo atributo llamado *inicio* que recoge el año en el que se inició la materia. Luego lo que haremos es impedir que una materia se pueda iniciar antes del año 2019 que fue el año de creación de la titulación.\n", "\n", "Al añadir un decorador, cada vez que queramos saber la fecha de inicio de una materia tendremos que pasar por el método inicio que nos la proporcionará. Para comprobar que esto es así, añadimos un print en el método que indique que está actuando el método inicio.\n", "La property se articula creando un método que se llamará igual que el atributo *inicio*." ] }, { "cell_type": "code", "execution_count": 5, "id": "823aa4fa", "metadata": { "id": "823aa4fa", "outputId": "006535c7-3887-49cd-94c4-b74fae20b5d3", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "La titulación comenzó en 2019, por lo que la materia no puede ser de 2004.\n" ] } ], "source": [ "class Carrera:\n", " def __init__(self, nombre):\n", " self.nombre = nombre\n", " self.materias = {}\n", " def agregarMateria(self, materia, codigo): # nuevo atributo inicio\n", " self.materias[codigo] = materia\n", "\n", "class Materia:\n", " def __init__(self, nombre, profesor, fecha):\n", " self.nombre = nombre\n", " self.profesor = profesor\n", " self.inicio = fecha # año de inicio de una materia\n", "\n", " @property # decorador (decorator)\n", " def inicio(self): # este método lo único que hace es devolver el atributo\n", " print(\"El método inicio es el que nos proporciona el año de inicio para esta materia.\")\n", " return self._inicio # OJO el atributo lleva un prefijo de barra baja que indica\n", " # que es privado (oculto, protegido) pero esto solo es un convenio\n", " @inicio.setter\n", " def inicio(self, fecha):\n", " if fecha < 2019:\n", " self._inicio = 2019 # cambiamos la fecha para que no sea anterior al año de creación\n", " print(f\"La titulación comenzó en 2019, por lo que la materia no puede ser de {fecha}.\")\n", " else:\n", " self._inicio = fecha # dejamos la fecha que nos dan\n", "\n", "ing = Carrera(\"Ingeniería\")\n", "algebra = Materia(\"Álgebra\", \"Rosa López\", 2004)\n", "fisica = Materia(\"Física\", \"Roberto Ruiz\", 2019)\n", "quimica = Materia(\"Química\", \"Sonia Arce\", 2021)" ] }, { "cell_type": "code", "execution_count": 6, "id": "b060b4e6", "metadata": { "id": "b060b4e6", "outputId": "0279652c-50c3-4aa2-bb99-0a6cf25154dd", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "El método inicio es el que nos proporciona el año de inicio para esta materia.\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "2019" ] }, "metadata": {}, "execution_count": 6 } ], "source": [ "algebra.inicio # no imprime directamente la fecha de inicio, primero pasa por la\n", " # property e imprime la frase indicada" ] }, { "cell_type": "markdown", "id": "27675491", "metadata": { "id": "27675491" }, "source": [ "La property enmascara o envuelve al atributo dentro de una propiedad. \n", "El setter es el que ha validado la fecha, comprobando si se trataba de un año anterior o no a 2019, año de creación de la titulación, para evitar que la fecha de inicio de una materia pueda llegar a introducirse por error con un año anterior al inicio de esa carrera.\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "fb4eed06", "metadata": { "id": "fb4eed06", "outputId": "4d699090-4eb6-467f-c3bb-8e35d9383762", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "El método inicio es el que nos proporciona el año de inicio para esta materia.\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "2021" ] }, "metadata": {}, "execution_count": 7 } ], "source": [ "quimica.inicio # imprime el año 2021 que es el proporcionado,\n", " # ya que no es anterior a 2019" ] }, { "cell_type": "markdown", "id": "d69dad7d", "metadata": { "id": "d69dad7d" }, "source": [ "Esta es una forma de ocultamiento de los datos. Si solicitamos el año de inicio de una materia directamente nos dirá que esa variable no existe, dando un error." ] }, { "cell_type": "code", "execution_count": 8, "id": "d902aff3", "metadata": { "id": "d902aff3", "outputId": "366c30e6-bb4d-47d8-b805-b82680c90f52", "colab": { "base_uri": "https://localhost:8080/", "height": 35 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'Álgebra'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 8 } ], "source": [ "algebra.nombre" ] }, { "cell_type": "code", "execution_count": 9, "id": "61b19e30", "metadata": { "id": "61b19e30", "outputId": "e203cfeb-763f-4bee-9a29-df2c85862174", "colab": { "base_uri": "https://localhost:8080/", "height": 35 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'Rosa López'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 9 } ], "source": [ "algebra.profesor" ] }, { "cell_type": "code", "execution_count": 10, "id": "97eb3766", "metadata": { "id": "97eb3766" }, "outputs": [], "source": [ "#algebra.fecha # AttributeError: 'Materia' object has no attribute 'fecha'" ] }, { "cell_type": "markdown", "id": "3632f261", "metadata": { "id": "3632f261" }, "source": [ "## Ocultamiento\n", "Nos gustaría poder proteger el acceso directo al diccionario de materias evitando crear una materia nueva sin necesidad de pasar por el método agregarMateria, que hasta ahora es privado por convenio pero se puede saltar así." ] }, { "cell_type": "code", "execution_count": 11, "id": "4f973eb0", "metadata": { "id": "4f973eb0" }, "outputs": [], "source": [ "ing.materias[111] = \"cálculo\" # así conseguimos acceder directamente al diccionario sin pasar\n", " # por el método agregarMateria" ] }, { "cell_type": "code", "execution_count": 12, "id": "eb0e38d2", "metadata": { "id": "eb0e38d2", "outputId": "4437baea-4f94-4163-80f0-9f05adf4d8fc", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{111: 'cálculo'}" ] }, "metadata": {}, "execution_count": 12 } ], "source": [ "ing.materias # veamos que hemos borrado el contenido anterior del diccionario\n", " # ha quedado solo la nueva materia inclida directamente" ] }, { "cell_type": "code", "execution_count": 13, "id": "035c2999", "metadata": { "id": "035c2999" }, "outputs": [], "source": [ "ing.materias[999] = fisica # añadiendo ahora el objeto fisica" ] }, { "cell_type": "code", "execution_count": 14, "id": "c4acb057", "metadata": { "id": "c4acb057", "outputId": "a6081406-7546-4bb4-f5da-b5bf4c5dc4d4", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 14 } ], "source": [ "len(ing.materias)" ] }, { "cell_type": "code", "execution_count": 15, "id": "9f677d94", "metadata": { "id": "9f677d94", "outputId": "8f80709c-05bb-4c95-d75f-cbfc78baf45d", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{111: 'cálculo', 999: <__main__.Materia at 0x782829e816c0>}" ] }, "metadata": {}, "execution_count": 15 } ], "source": [ "ing.materias # podemos ver que es un lio tener objetos creados por dos métodos" ] }, { "cell_type": "markdown", "id": "c4d4e85b", "metadata": { "id": "c4d4e85b" }, "source": [ "Lo mejor es ocultar o proteger la posibilidad de que el usuario u otros programadores puedan acceder a los atributos y métodos de forma directa." ] }, { "cell_type": "markdown", "id": "bfd4d848", "metadata": { "id": "bfd4d848" }, "source": [ "### Prefijo con dos barras bajas ```__atributo```" ] }, { "cell_type": "code", "execution_count": 16, "id": "328278aa", "metadata": { "id": "328278aa" }, "outputs": [], "source": [ "class Carrera:\n", " def __init__(self, nombre):\n", " self.nombre = nombre\n", " self.__materias = {} # prefijo __\n", " def agregarMateria(self, materia, codigo):\n", " self.__materias[codigo] = materia # prefijo __\n", " def getMaterias(self): # nuevo método para listar todas las materias\n", " print(self.__materias.items())\n", "\n", "class Materia:\n", " def __init__(self, nombre, profesor, fecha):\n", " self.nombre = nombre\n", " self.profesor = profesor\n", " self.inicio = fecha\n", "\n", " @property\n", " def inicio(self):\n", " return self._inicio\n", "\n", " @inicio.setter\n", " def inicio(self, fecha):\n", " if fecha < 2019:\n", " self._inicio=2019\n", " else:\n", " self._inicio=fecha\n", "\n", "ing = Carrera(\"Ingeniería\")\n", "algebra = Materia(\"Álgebra\", \"Rosa López\", 2004)\n", "fisica = Materia(\"Física\", \"Roberto Ruiz\", 2019)\n", "quimica = Materia(\"Química\", \"Sonia Arce\", 2021)" ] }, { "cell_type": "markdown", "id": "5886d0f3", "metadata": { "id": "5886d0f3" }, "source": [ "Cuanto se pone un prefijo de dos barras bajas a un atributo el intérprete de Python renombra el atributo.\n", "\n", "Veamos que ahora el atributo \\__materias se llama _Carrera__materias" ] }, { "cell_type": "code", "execution_count": 17, "id": "1e607fb2", "metadata": { "id": "1e607fb2", "outputId": "435b877f-7ec5-4700-e297-3f01b654bc53", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['_Carrera__materias',\n", " '__class__',\n", " '__delattr__',\n", " '__dict__',\n", " '__dir__',\n", " '__doc__',\n", " '__eq__',\n", " '__format__',\n", " '__ge__',\n", " '__getattribute__',\n", " '__gt__',\n", " '__hash__',\n", " '__init__',\n", " '__init_subclass__',\n", " '__le__',\n", " '__lt__',\n", " '__module__',\n", " '__ne__',\n", " '__new__',\n", " '__reduce__',\n", " '__reduce_ex__',\n", " '__repr__',\n", " '__setattr__',\n", " '__sizeof__',\n", " '__str__',\n", " '__subclasshook__',\n", " '__weakref__',\n", " 'agregarMateria',\n", " 'getMaterias',\n", " 'nombre']" ] }, "metadata": {}, "execution_count": 17 } ], "source": [ "dir(ing)" ] }, { "cell_type": "code", "execution_count": 18, "id": "621db5e1", "metadata": { "id": "621db5e1" }, "outputs": [], "source": [ "ing.agregarMateria(algebra, 345)\n", "ing.agregarMateria(fisica, 589)" ] }, { "cell_type": "code", "execution_count": 19, "id": "6f94f411", "metadata": { "id": "6f94f411" }, "outputs": [], "source": [ "#ing.__materias # AttributeError: 'Carrera' object has no attribute '__materias'" ] }, { "cell_type": "markdown", "id": "5762a4f4", "metadata": { "id": "5762a4f4" }, "source": [ "Aunque podemos hacer trampa y acceder de todas formas sabiendo que el atributo ahora se llama _Carrera__materias" ] }, { "cell_type": "code", "execution_count": 20, "id": "8804c13c", "metadata": { "id": "8804c13c", "outputId": "242a999d-cfc8-4d32-eafa-bcda3fce4f5b", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{345: <__main__.Materia at 0x782829e827d0>,\n", " 589: <__main__.Materia at 0x782829e810f0>}" ] }, "metadata": {}, "execution_count": 20 } ], "source": [ "ing._Carrera__materias # vemos que está funcionando" ] }, { "cell_type": "markdown", "id": "23db679a", "metadata": { "id": "23db679a" }, "source": [ "Intentemos hacer una copia del atributo _Carrera\\__materias perteneciente al objeto ing." ] }, { "cell_type": "code", "execution_count": 21, "id": "9ba0aa14", "metadata": { "id": "9ba0aa14" }, "outputs": [], "source": [ "duplicadoMaterias = ing._Carrera__materias # vemos que funciona, no da error" ] }, { "cell_type": "markdown", "id": "b4fa0c2f", "metadata": { "id": "b4fa0c2f" }, "source": [ "El problema de los duplicados de un diccionario, o lista, es que la copia ocupa el mismo lugar en memoria. De esta forma, si se modifica o borra la copia, al original le sucederá lo mismo. Esto es bastante grave, ya que otro programador podría hacer una copia de ese diccionario, si tiene acceso a él y modificarlo, alterando completamente nuestra aplicación.\n", "\n", "Lo que se debe hacer es ocultar bien, ese método incluso de otros programadores, o de nosotros mismos que en el futuro podamos olvidar los detalles de la aplicación que estamos creando." ] }, { "cell_type": "code", "execution_count": 22, "id": "f003d7e4", "metadata": { "id": "f003d7e4", "outputId": "fcb24089-95b9-4f3a-8f5c-9ae3027b9bed", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{345: <__main__.Materia at 0x782829e827d0>,\n", " 589: <__main__.Materia at 0x782829e810f0>}" ] }, "metadata": {}, "execution_count": 22 } ], "source": [ "duplicadoMaterias # mismas direcciones de memoria que el original" ] }, { "cell_type": "markdown", "id": "19ff8cef", "metadata": { "id": "19ff8cef" }, "source": [ "Si agregamos una nueva materia (química) al diccionario de materias, podemos comprobar que también se ha añadido a la copia." ] }, { "cell_type": "code", "execution_count": 23, "id": "095aba6b", "metadata": { "id": "095aba6b", "outputId": "fc81aeed-b426-422f-d119-9ee35c18a85b", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{345: <__main__.Materia at 0x782829e827d0>,\n", " 589: <__main__.Materia at 0x782829e810f0>,\n", " 888: <__main__.Materia at 0x782829e829b0>}" ] }, "metadata": {}, "execution_count": 23 } ], "source": [ "ing._Carrera__materias[888] = quimica # añadimos la materia química al diccionario\n", "ing._Carrera__materias" ] }, { "cell_type": "code", "execution_count": 24, "id": "5734f8d6", "metadata": { "id": "5734f8d6", "outputId": "502af7e6-3ba2-4b8b-9a7d-7e46377746d9", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{345: <__main__.Materia at 0x782829e827d0>,\n", " 589: <__main__.Materia at 0x782829e810f0>,\n", " 888: <__main__.Materia at 0x782829e829b0>}" ] }, "metadata": {}, "execution_count": 24 } ], "source": [ "duplicadoMaterias # la copia tiene tb 3 materias con la misma dirección de memoria" ] }, { "cell_type": "code", "execution_count": 25, "id": "4a070ed8", "metadata": { "id": "4a070ed8", "outputId": "75dd58f8-bb79-42f1-de89-79dc56f1cf10", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{589: <__main__.Materia at 0x782829e810f0>,\n", " 888: <__main__.Materia at 0x782829e829b0>}" ] }, "metadata": {}, "execution_count": 25 } ], "source": [ "duplicadoMaterias.pop(345) # eliminamos un elemento del diccionario duplicado\n", "duplicadoMaterias" ] }, { "cell_type": "code", "execution_count": 26, "id": "4788e0d3", "metadata": { "id": "4788e0d3", "outputId": "ad3c2e31-29e8-42c6-953b-aa47641caf5a", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "{589: <__main__.Materia at 0x782829e810f0>,\n", " 888: <__main__.Materia at 0x782829e829b0>}" ] }, "metadata": {}, "execution_count": 26 } ], "source": [ "ing._Carrera__materias # también se ha eliminado la materia en el diccionario original" ] }, { "cell_type": "code", "execution_count": 27, "id": "29532c0e", "metadata": { "id": "29532c0e", "outputId": "29ef8955-578a-4e57-d6d5-08ac9365e7b3", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "dict_items([(589, <__main__.Materia object at 0x782829e810f0>), (888, <__main__.Materia object at 0x782829e829b0>)])\n" ] } ], "source": [ "ing.getMaterias() # invocamos el método que nos lista internamente las materias" ] } ], "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.8.8" }, "colab": { "name": "calisto2_0180.ipynb", "provenance": [], "include_colab_link": true } }, "nbformat": 4, "nbformat_minor": 5 }