{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "view-in-github", "colab_type": "text" }, "source": [ "\"Open" ] }, { "cell_type": "markdown", "id": "8ca433f0", "metadata": { "id": "8ca433f0" }, "source": [ "# Encapsulamiento en la POO\n", "[Wikipedia](https://es.wikipedia.org/wiki/Encapsulamiento_(inform%C3%A1tica)) \n", "En programación orientada a objetos, se denomina encapsulamiento al ocultamiento del estado interno de una clase al exterior. Encapsular consiste en hacer que los atributos y/o métodos internos a una clase no se puedan acceder ni modificar desde fuera de la propia clase.\n", "\n", "Cada objeto está aislado del exterior. El aislamiento protege a los datos asociados de un objeto contra su modificación por quien no tenga derecho a acceder a ellos, eliminando efectos secundarios e interacciones. Se evita que el usuario pueda cambiar su estado de maneras imprevistas e incontroladas.\n", "\n", "En JAVA las variables se indican si son públicas (Public) o privadas (Private). En Python por defecto todas los atributos y métodos de la clase son públicos, esto es, son accesibles desde fuera de la clase simplemente invocándolos. \n", "\n", "**Ejemplo 1** \n", "Con un atributo de clase." ] }, { "cell_type": "code", "execution_count": 1, "id": "f37de43c", "metadata": { "id": "f37de43c", "outputId": "2ac0d353-83f2-46ff-9e56-a060dddaa559", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3.1415" ] }, "metadata": {}, "execution_count": 1 } ], "source": [ "class Numero:\n", " pi = 3.1415 # Atributo de clase\n", "\n", "Numero.pi # podemos acceder sin problemas al atributo de clase desde fuera de la clase" ] }, { "cell_type": "markdown", "id": "5d145451", "metadata": { "id": "5d145451" }, "source": [ "**Ejemplo 2** \n", "Con atributos de instancia." ] }, { "cell_type": "code", "execution_count": 2, "id": "6f871669", "metadata": { "id": "6f871669", "outputId": "6f9dbf3d-7c83-47fe-f34d-94de7951f7ad", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Alberto\n", "Alberto José\n" ] } ], "source": [ "class Socio:\n", " def __init__(self, nombre):\n", " self.nombre = nombre\n", "\n", "alberto = Socio('Alberto') # creamos una instancia de la clase Socio\n", "print(alberto.nombre) # imprimimos el atributo de instancia\n", "\n", "alberto.nombre = 'Alberto José' # podemos cambiar el atributo desde fuera de la clase sin problemas\n", "print(alberto.nombre) # podemos acceder el atributo desde fuera de la clase sin problemas" ] }, { "cell_type": "markdown", "id": "7ed2e0a7", "metadata": { "id": "7ed2e0a7" }, "source": [ "**Ejemplo 3** \n", "Podemos acceder a los métodos de una clase desde fuera de ésta." ] }, { "cell_type": "code", "execution_count": 3, "id": "b8f575a1", "metadata": { "id": "b8f575a1", "outputId": "fb508585-6f9e-4253-cf8b-65e072a4855c", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Alberto\n", "Alberto José\n" ] } ], "source": [ "class Socio:\n", " def __init__(self, nombre):\n", " self.nombre = nombre\n", "\n", " def set_nombre(self, nombre):\n", " self.nombre = nombre\n", "\n", " def get_nombre(self):\n", " return self.nombre\n", "\n", "alberto = Socio('Alberto')\n", "print(alberto.get_nombre()) # accedemos, sin problemas, a un método desde fuera de la clase\n", "\n", "alberto.set_nombre('Alberto José') # accedemos, sin problemas, a un método desde fuera de la clase\n", "print(alberto.get_nombre())" ] }, { "cell_type": "code", "execution_count": 4, "id": "006835be", "metadata": { "id": "006835be", "outputId": "9051ec84-bd7e-4a5b-b17d-94eec4218fa6", "colab": { "base_uri": "https://localhost:8080/", "height": 35 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'Alberto José'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 4 } ], "source": [ "# También podemos acceder a un atributo desde fuera de la clase, no está protegido, no es privado\n", "alberto.nombre # accedemos también al atributo nombre desde fuera de la clase" ] }, { "cell_type": "markdown", "id": "ef589cec", "metadata": { "id": "ef589cec" }, "source": [ "El código anterior se asemeja al estilo JAVA con los setter y los getter. \n", "\n", "Son **públicos** tanto los métodos *set_nombre* y *get_nombre*, como el atributo *nombre*." ] }, { "cell_type": "markdown", "id": "bba770a9", "metadata": { "id": "bba770a9" }, "source": [ "# Modificadores de acceso ```_``` ```__```" ] }, { "cell_type": "markdown", "id": "12e91550", "metadata": { "id": "12e91550" }, "source": [ "## _privado\n", "\n", "Elementos privados usando como prefijo una barra baja _\n", "\n", "Por convenio, si el programador pone una barra baja precediendo el nombre de los atributos o métodos está indicando a otros desarrolladores, o a sí mismo, que esos elementos no deben ser accesibles desde fuera de la clase, no deben ser expuestos ni modificados externamente. Pero esto es solo una sugerencia que el intérprete de Python no considera como obligación y no impide el acceso desde fuera de la clase. \n", "\n", "Veamos un ejemplo donde pese a la barra baja como prefijo se puede acceder perfectamente desde fuera de la clase a los atributos y a los métodos." ] }, { "cell_type": "code", "execution_count": 5, "id": "e74c80a5", "metadata": { "id": "e74c80a5", "outputId": "666b2e6d-058b-43b5-9ef9-d56895ac06a1", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1234\n", "Alberto José\n" ] } ], "source": [ "class Socio:\n", " def __init__(self, nombre, password):\n", " self.nombre = nombre\n", " self._password = password\n", "\n", " def _set_nombre(self, nombre):\n", " self.nombre = nombre\n", "\n", " def get_nombre(self):\n", " return self.nombre\n", "\n", "alberto = Socio('Alberto', '1234')\n", "print(alberto._password) # nada nos impide acceder desde fuera de la clase a un atributo con prefijo _\n", " # incluso podemos acceder desde fuera a un atributo tan sensible como password\n", "alberto._set_nombre('Alberto José') # nada nos impide acceder desde fuera de la clase a un método con prefijo _\n", "print(alberto.get_nombre())" ] }, { "cell_type": "markdown", "id": "b85113f1", "metadata": { "id": "b85113f1" }, "source": [ "## __protegido\n", "\n", "Elementos protegidos usando como prefijo dos barras bajas __\n", "\n", "Protegeremos los métodos y atributos cuando deseemos evitar que un usuario modifique el estado interno de la instancia. Protegiéndolos nos asegurarnos que el atributo no pueda ser modificado ni accedido desde fuera de la clase.\n", "\n", "**Nota** \n", "Como en Python no existen atributos y métodos privados propiamente dichos, muchos desarrolladores de Python denominan como Privados a los elementos que llevan las dos barras bajas como prefijo. Muchos programadores llaman privados a los elementos que van con dos barras bajas como prefijo, porque esto evita que estos métodos y atributos sean accedidos o modificados externamente." ] }, { "cell_type": "code", "execution_count": 6, "id": "5c0d1d7b", "metadata": { "id": "5c0d1d7b", "outputId": "1bcef2dd-6c49-4c0f-ac58-91e7d3693c42", "colab": { "base_uri": "https://localhost:8080/", "height": 35 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'Buenos días'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 6 } ], "source": [ "class Saludo:\n", " dias = \"Buenos días\" # accesible desde el exterior\n", " __tardes = \"Buenas tardes\" # no accesible\n", "\n", " def __cafe(self): # no accesible desde el exterior\n", " print(\"Estoy tomando un café\")\n", "\n", " def desayuno(self): # accesible desde el exterior\n", " self.__cafe() # el método si es accesible desde el interior\n", "\n", "hola = Saludo()\n", "\n", "hola.dias # Buenos días\n", "#hola.__tardes # ERROR El atributo no es accesible" ] }, { "cell_type": "code", "execution_count": 7, "id": "07b73a42", "metadata": { "id": "07b73a42", "outputId": "d8ba7e03-04f2-4581-ac18-d6ce5f56cbbb", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Estoy tomando un café\n" ] } ], "source": [ "#hola.__cafe() # ERROR El método no es accesible\n", "hola.desayuno() # Estoy tomando un café" ] }, { "cell_type": "markdown", "id": "14eea6cf", "metadata": { "id": "14eea6cf" }, "source": [ "Podemos ver los métodos y atributos accesibles de la instancia *hola* perteneciente a la clase *Saludo*, usando ```dir```." ] }, { "cell_type": "code", "execution_count": 8, "id": "74e87b47", "metadata": { "id": "74e87b47", "outputId": "a214d3ba-35c3-4a9b-fc49-268a976e57b2", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['_Saludo__cafe',\n", " '_Saludo__tardes',\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", " 'desayuno',\n", " 'dias']" ] }, "metadata": {}, "execution_count": 8 } ], "source": [ "dir(hola)" ] }, { "cell_type": "markdown", "id": "2b4dd08a", "metadata": { "id": "2b4dd08a" }, "source": [ "Vemos muchos métodos especiales y además:\n", "* vemos el método *desayuno*\n", "* vemos el atributo de clase *dias*\n", "* no podemos encontrar el método *\\__cafe* → → → pero aparece otro método llamado _Saludo__cafe\n", "* no podemos encontrar el atributo *\\__tardes* → → → pero aparece otro atributo llamado _Saludo__tardes\n", "\n", "Si podemos acceder a estos métodos y atributos que inicialmente parecen ocultos, lo que sucede es que el intérprete de Python los ha cambiado de nombre para ocultarlos y evitar su uso. Podemos llamarlos de la siguiene manera aunque **no se recomienda su uso**." ] }, { "cell_type": "code", "execution_count": 9, "id": "4d227cc4", "metadata": { "id": "4d227cc4", "outputId": "91872b4b-4f52-4a63-d88d-61b2faef898b", "colab": { "base_uri": "https://localhost:8080/", "height": 35 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'Buenas tardes'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 9 } ], "source": [ "hola._Saludo__tardes" ] }, { "cell_type": "code", "execution_count": 10, "id": "fac977a2", "metadata": { "id": "fac977a2", "outputId": "6a0b6952-24d2-46cc-cd2b-7a9a406520a7", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Estoy tomando un café\n" ] } ], "source": [ "hola._Saludo__cafe()" ] }, { "cell_type": "markdown", "id": "fda6f98a", "metadata": { "id": "fda6f98a" }, "source": [ "## Proteger el acceso\n", "Proteger el acceso usando la doble barra baja como prefijo tanto para métodos como para atributos.\n", "\n", "Originariamente la doble barra baja se estableció para que el intérprete de Python renombra el elemento (fuera método o atributo) para evitar colisiones con las subclases. Aunque la idea original era evitar colisiones, pronto los desarrolladores comprendieron que el uso de la doble barra baja servía para prevenir accesos no autorizados." ] }, { "cell_type": "code", "execution_count": 11, "id": "b5e86d9b", "metadata": { "id": "b5e86d9b" }, "outputs": [], "source": [ "class Caperucita:\n", " def __colorSecreto(self):\n", " print('Mi color favorito es el ROJO')\n", " def public(self):\n", " self.__colorSecreto()\n", "class Pitufo(Caperucita):\n", " def __colorSecreto(self): # la subclase tiene el mismo nombre de método que la clase\n", " print('Mi color favorito es el AZUL')\n", " def public(self): # la subclase tiene el mismo nombre de método que la clase\n", " self.__colorSecreto()\n", "\n", "marianela=Caperucita() # pocos saben que el verdadero nombre de Caperucita Roja era Marianela\n", "#marianela.__colorSecreto() # ERROR no se puede acceder a un método protegido con dos barras bajas\n", " # AttributeError: 'Caperucita' object has no attribute '__colorSecreto'" ] }, { "cell_type": "code", "execution_count": 12, "id": "20d8de61", "metadata": { "id": "20d8de61", "outputId": "43a75c67-fafa-4941-a15a-04bd9c1170cb", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Mi color favorito es el ROJO\n" ] } ], "source": [ "marianela.public() # podemos acceder perfectamente al método public de la superclase" ] }, { "cell_type": "code", "execution_count": 13, "id": "fe5fb9be", "metadata": { "id": "fe5fb9be", "outputId": "a1fd8a8b-0d38-476b-8b31-b56f09ad25a7", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Mi color favorito es el AZUL\n" ] } ], "source": [ "pitufina = Pitufo() # creamos una instancia de la subclase\n", "pitufina.public() # la instancia de la subclase llama a su método public, no hay colisión" ] }, { "cell_type": "code", "execution_count": 14, "id": "f17fa5c0", "metadata": { "id": "f17fa5c0", "outputId": "7a387526-e789-4994-abf6-7e9fd566c680", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['_Caperucita__colorSecreto',\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", " 'public']" ] }, "metadata": {}, "execution_count": 14 } ], "source": [ "dir(marianela) # se renombra, ahora el método se llama _Caperucita__colorSecreto" ] }, { "cell_type": "markdown", "id": "0d044213", "metadata": { "id": "0d044213" }, "source": [ "Al poner la doble barra baja en el método \\__colorSecreto la instancia Marianela no puede acceder a él, obteniendo el error *AtributeError*, dando a entender que este elemento no existe. Lo que ha sucedido es que tiene otro nombre. Al listar los atributos y métodos de la clase Caperucita vemos que fue renombrado, ahora se llama _Caperucita__colorSecreto." ] }, { "cell_type": "markdown", "id": "5d3a14b0", "metadata": { "id": "5d3a14b0" }, "source": [ "Vamos a listar los métodos y atributos de pitufina que es una instancia de la clase hija. \n", "Podemos observar dos métodos colorSecreto uno de la clase padre y otro de la clase hija. \n", "* _Caperucita__colorSecreto\n", "* _Pitufo__colorSecreto" ] }, { "cell_type": "code", "execution_count": 15, "id": "323baa5e", "metadata": { "id": "323baa5e", "outputId": "b0aceb9e-574c-44af-92fa-2e6b75e9c4e4", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['_Caperucita__colorSecreto',\n", " '_Pitufo__colorSecreto',\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", " 'public']" ] }, "metadata": {}, "execution_count": 15 } ], "source": [ "dir(pitufina)" ] }, { "cell_type": "markdown", "id": "daa200f3", "metadata": { "id": "daa200f3" }, "source": [ "Traslademos estas ideas a la clase Socio." ] }, { "cell_type": "code", "execution_count": 16, "id": "84ec8734", "metadata": { "id": "84ec8734" }, "outputs": [], "source": [ "import hashlib\n", "\n", "class Socio:\n", " def __init__(self, nombre, password):\n", " '''Construcctor. La password se\n", " encriptará antes de ser almacenada.'''\n", " self.nombre = nombre\n", " self.password = self.__encripta_pw(password)\n", "\n", " def __encripta_pw(self, password): # método privado (protegido)\n", " '''Encripta la password con el\n", " nombre de usuario y retorna el sha.'''\n", " hash_string = (self.nombre + password)\n", " hash_string = hash_string.encode(\"utf8\")\n", " return hashlib.sha256(hash_string).hexdigest()\n", "\n", " def check_password(self, password):\n", " '''Retorna True si la password es válida para\n", " el usuario, en caso contrario retorna False.'''\n", " encriptada = self.__encripta_pw(password)\n", " return encriptada == self.password\n", "\n", " def change_password(self, password):\n", " '''Permite cambiar la password\n", " de un usuario, alterando el hash.'''\n", " self.password = self.__encripta_pw(password)" ] }, { "cell_type": "code", "execution_count": 17, "id": "35ce2301", "metadata": { "id": "35ce2301", "outputId": "05292b71-b34b-43a1-855d-223aa8cc8529", "colab": { "base_uri": "https://localhost:8080/", "height": 35 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'5773a02052b08a9af8885dc6b46e6c60b52d9cbcf1cb0111671637a2f3f95345'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 17 } ], "source": [ "pedro = Socio(\"Pedro\", \"1234\")\n", "pedro.password # muestra el hash almacenado, pero no la password original" ] }, { "cell_type": "code", "execution_count": 18, "id": "712c3909", "metadata": { "id": "712c3909", "outputId": "15145cde-7516-43fd-c209-45cd12a19825", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "True" ] }, "metadata": {}, "execution_count": 18 } ], "source": [ "pedro.check_password(\"1234\") # comprueba que la clave es correcta" ] }, { "cell_type": "code", "execution_count": 19, "id": "8a3fd6ac", "metadata": { "id": "8a3fd6ac", "outputId": "8cb239d2-7ad6-4260-cd43-c6f9402d898e", "colab": { "base_uri": "https://localhost:8080/", "height": 35 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'11f6d55713fd13945e3551d8040cf297a47b75198887cd9c47d71e330bf5cd34'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 19 } ], "source": [ "pedro.change_password('abcde') # cambiamos la password\n", "pedro.password # con la password nueva cambia el hash" ] }, { "cell_type": "code", "execution_count": 20, "id": "b14a23b6", "metadata": { "id": "b14a23b6", "outputId": "01e3fd4c-4df0-439c-eeaa-f89d885c4649", "colab": { "base_uri": "https://localhost:8080/", "height": 35 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'5773a02052b08a9af8885dc6b46e6c60b52d9cbcf1cb0111671637a2f3f95345'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 20 } ], "source": [ "pedro.change_password('1234') # volvemos a la password antigua\n", "pedro.password # comprobamos que el hash es el antiguo" ] } ], "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_0080.ipynb", "provenance": [], "include_colab_link": true } }, "nbformat": 4, "nbformat_minor": 5 }