{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](img/logocurso.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbpresent": {
     "id": "d9e822f9-0a52-43f2-90ef-e8fd6860aacc"
    }
   },
   "source": [
    "![](img/sesion4.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Clases y objetos. La programación orientada a objetos\n",
    "\n",
    "La **programación orientada a objetos** (POO o OOP) surge como un paradigma nuevo en los lenguajes de programación que, aunque es muy anterior, hacia la década de los 90 comienza a imponerse sobre la **programación procedimental**.\n",
    "\n",
    "Esquemáticamente podemos resumir la evolución de los lenguajes de programación en las siguientes fases:\n",
    "* **Fase 1**: Lenguajes de poco nivel, con tipos de datos simples y fijos. La estructura de datos tenía que ser implementada por el usuario sobre esos tipos de datos.\n",
    "* **Fase 2**: Incorporación de mecanismos para que el programador defina sus propios tipos de datos.\n",
    "* **Fase 3**: Programación orientada a objetos.\n",
    "\n",
    "<div class=\"alert alert-info\">Debemos implementar un sistema que trabaje con los clientes de un banco. Para cada cliente debemos manejar la siguiente información:\n",
    "<ul>\n",
    "<li><b>Datos identificativos:</b><ul><li>Nombre</li><li>DNI</li><li>Dirección</li></ul>\n",
    "</li>\n",
    "    <li><b>Movimientos en cuenta:</b> Cada apunte con su fecha y cuantía.</li>\n",
    "</ul><br>\n",
    "Pretendemos además poder realizar los siguientes cálculos sobre cada cliente:\n",
    "<ul>\n",
    "<li>Calcular su saldo.</li>\n",
    "<li>Calcular el saldo medio.</li>\n",
    "<li>Añadir un movimiento.</li>\n",
    "<li>Realizar propuesta de productos en función del saldo y saldo medio.</li>\n",
    "</ul>\n",
    "</div>\n",
    "\n",
    "En un tratamiento procedimental de este problema (correspondientes a las fases 1 o 2 antes comentadas) el esquema de trabajo pasaría por establecer la estructura de datos para esta información y luego las funciones que actuarán sobre esos datos para producir los resultados que buscamos.\n",
    "<table >\n",
    "<tr>\n",
    "    <td><b>Fase 1</b></td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td><b>Fase 2</b></td>\n",
    "</tr>\n",
    "<tr>\n",
    "    <td>array nombre[n]</td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td>Dato estructurado</td>\n",
    "</tr>\n",
    "<tr>\n",
    "    <td>array DNI[n]</td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td></td>\n",
    "</tr>\n",
    "<tr>\n",
    "    <td>array direccion[n]</td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td></td>\n",
    "</tr>\n",
    "<tr>\n",
    "    <td>array movimientosfecha[n,m]</td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td></td>\n",
    "</tr>\n",
    "<tr>\n",
    "    <td>array movimientoscuantia[n,m]</td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td></td>\n",
    "</tr>\n",
    "<tr style=\"background: rgba(255, 128, 0, 0.3);\"><td></td><td></td><td></td></tr>\n",
    "<tr>\n",
    "    <td>función calculasaldo[n]</td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td>función calculasaldo[n]</td>\n",
    "</tr>\n",
    "<tr>\n",
    "    <td>función calculasaldomedio[n]</td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td>función calculasaldomedio[n]</td>\n",
    "</tr>\n",
    "<tr>\n",
    "    <td>función informa[n]</td><td style=\"background: rgba(255, 128, 0, 0.3);\"></td><td>función informe[n]</td>\n",
    "</tr>\n",
    "\n",
    "</table>\n",
    "En el tratamiento procedimental la estructura de datos y las funciones que los manejan están separados.\n",
    "\n",
    "<div class=\"alert alert-info\">La programación orientada a objetos se desarrolla en dos pasos involucrando al mismo tiempo dos elementos esenciales: \n",
    "<ul>\n",
    "    <li><b>Definición (Clase):</b> Creamos el tipo de dato adecuado a cada problema junto con las funciones que operan sobre esos datos de forma integrada definiendo lo que se denomina una <b>clase</b>. La clase sería un nuevo tipo de datos definido por el usuario que además incorpora las instrucciones y operaciones específicas que son necesarias para manejar esos datos según nuestras necesidades. Por tanto una clase integra datos y funciones que en la nomenclatura del POO se denominan:\n",
    "<ul>\n",
    "    <li>A los datos que forman la clase se les denomina <b>atributos</b>.</li>\n",
    "    <li>A las funciones de la clase para manipular sus datos se les denomina <b>métodos</b></li>\n",
    "</ul>\n",
    "</li>\n",
    "    <li><b>Instanciación (Objetos):</b> Cada dato concreto del tipo definido por una clase es lo que se denomina <b>objeto de la clase</b> y en su definición daremos unos valores determinados para sus datos/atributos. Sobre esos datos podremos utilizar las funciones/métodos de la clase.\n",
    "</ul>\n",
    "\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La sintaxis básica para definir un clase es la siguiente:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "class nombreclase:\n",
    "\n",
    "\n",
    "        variables de clase                        Se introducen variables/atributos comunes a\n",
    "                                                  todos los objetos de la clase\n",
    "    ...\n",
    "    \n",
    "                                                  Se definen las funciones/métodos de la clase.\n",
    "                                                  Todas se aplican al propio objeto que se denomina   \n",
    "                                                  self y siempre debe \n",
    "                                                  aparecer como primer argumento de todas ellas.\n",
    "                                                  \n",
    "    __init__(self,arg1,arg2,...):                 La primera función/método es __init__ que se aplica \n",
    "                                                  automáticamente al \n",
    "        variables de objeto                       crear un nuevo objeto para iniciar sus variables y  \n",
    "                                                  parámetros.\n",
    "        \n",
    "    def function1(self,arg1,arg2,...):            Se definen el resto de funciones/métodos de la \n",
    "                                                  clase.\n",
    "        ....\n",
    "        return\n",
    "        \n",
    "    def function2(self,arg1,arg2,...):\n",
    "        ....\n",
    "        return\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La sintaxis para crear un objeto de una clase es:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**nombreobjeto=nombreclase(arg1,arg2,...)**          Al definir el objeto pasamos los valores iniciales que tomarán sus atributos.\n",
    "\n",
    "**nombreorjevo.var1**                         La notación para acceder a los atributos es esta.\n",
    "\n",
    "**nombreobjeto.funcion1(arg1,arg2,...)**             La notación para utilizar una función/método de una clase sobre los datos de\n",
    "                                                 objeto es similar. Ahora, entre los argumentos ya no es necesario pasar el\n",
    "                                                 argumento self que se incluyó en la definición."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Una clase navideña\n",
    "# Definición de la clase\n",
    "# Estructura de datos:Una variable de clase: maximoregalos (fija el número máximo de regalos por \n",
    "#                                                              persona)\n",
    "#                       Dos datos/atributos: destinatario (texto con un nombre)\n",
    "#                                            regalos (lista con cadenas de texto para los regalos)\n",
    "#\n",
    "# Funciones para manipular los datos:        addregalo (para añadir un regalo más a la lista)\n",
    "#                                            tarjetaregalo (para generar una tarjeta para el \n",
    "#                                                              regalo)\n",
    "#\n",
    "class regalo:\n",
    "    maximoregalos=3\n",
    "    def __init__(self,persona,listareg):\n",
    "        self.destinatario=persona\n",
    "        self.regalos=listareg\n",
    "        print(\"Tomada nota encargo de regalos para \"+self.destinatario)\n",
    "    def addregalo(self,regalo):\n",
    "        if len(self.regalos)<self.maximoregalos:\n",
    "            self.regalos.append(regalo)\n",
    "        else:\n",
    "            print(\"Ya tiene demasiados regalos\")\n",
    "    def tarjetaregalo(self):\n",
    "        return \"Querido/a \"+self.destinatario+\" esperamos que disfrutes de estos regalos: \"+\", \".join(self.regalos)+\".\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Definimos el objeto regalos1 de la clase regalosreyes. Al definirlo pasamos como argumentos los valores para iniciar\n",
    "# sus variables\n",
    "regalos1=regalo(\"Papá\",[\"Colonia\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Podemos mostrar los valores o atributos de este objeto con la notación .\n",
    "print(regalos1.regalos)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Podemos utilizar las funciones/métodos de la clase para trabajar sobre los datos almacenados en el objeto\n",
    "print(regalos1.tarjetaregalo())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Los métodos pueden cambiar los valores de los atributos.\n",
    "regalos1.addregalo(\"Bufanda\")\n",
    "print(regalos1.tarjetaregalo())\n",
    "regalos1.addregalo(\"After save\")\n",
    "print(regalos1.tarjetaregalo())\n",
    "regalos1.addregalo(\"Iphone 13\")\n",
    "print(\"-------------------------------------\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Podemos definir otro objeto de la clase regalosreyes para otra persona\n",
    "regalos2=regalo(\"Mamá\",[\"Bolso Purificación García de 1500 euros\"])\n",
    "print(regalos2.tarjetaregalo())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "regaloprimo=regalo(\"Luisito\",[\"Xbox\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "regaloprimo.tarjetaregalo()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "regalos1.maximoregalos=4\n",
    "regalos1.addregalo(\"IPhone 13\")\n",
    "print(regalos1.tarjetaregalo())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejercicio 1\n",
    "\n",
    "Implementar una clase mascota que contenga los siguientes datos:\n",
    "* Nombre de la mascota.\n",
    "* Lista de dueños.\n",
    "\n",
    "Que disponga además de un método para añadir nuevos dueños a la mascota. Definir dos objetos de esa clase e incorporar diferentes dueños a cada uno."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejercicio 2\n",
    "Implementar la clase <b>clientecuenta</b> para clientes de banco que se describe en el ejemplo introductorio.\n",
    "<div class=\"alert alert-info\">\n",
    "Podemos manejar los siguientes elementos adicionales:\n",
    "<ul>\n",
    "    <li>El paquete <b>datetime</b> permite manipular y trabajar con fechas. Para cargarlo: <b>import datetime</b></li>\n",
    "    <li><b>fecha=datetime.date(año,mes,dia)</b> crea una variable que contiene una fecha concreta.</li>\n",
    "    <li><b>(fecha1-fecha2).days</b> proporciona el número de días entre dos fechas</li>\n",
    "    <li><b>sorted(lista,key=función de orden)</b> ordena <b>lista</b> según el elemento que señale la función key</li>\n",
    "</ul>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Herencia\n",
    "\n",
    "Una de las características fundamentales de la programación de objetos es la implementación de la **herencia de clases**. Gracias a esta característica podemos definir subclases que utilicen todos los atributos y métodos de una clase añadiendo nuevas funcionalidades. La sintáxis básica para definir una subclase de la clase **nombreclase** es:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "class nombresubclase(nombreclase):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tomada nota encargo de regalos para Laurita\n",
      "Recordar mandar la carta de Reyes para Laurita\n",
      "Estimados Reyes Magos os ruego que la noche del 5 tengáis a bien entregar para Laurita la siguiente lista de regalos: Nancy.\n"
     ]
    }
   ],
   "source": [
    "# Definimos una subclase regalosreyes que añade la funcionalidad de generar la correspondiente carta a los reyes)\n",
    "# Función adicional:   cartareyes()\n",
    "# Véase cómo el método __init__ de la subclase recurre al método __init__ de la clase regalo para inicializar las variables.\n",
    "\n",
    "class regaloreyes(regalo):\n",
    "    def __init__(self,persona,listareg):\n",
    "        regalo.__init__(self,persona,listareg)\n",
    "        print(\"Recordar mandar la carta de Reyes para \"+self.destinatario)\n",
    "    def cartareyes(self):\n",
    "        return \"Estimados Reyes Magos os ruego que la noche del 5 tengáis a bien entregar para \"+self.destinatario+\" la siguiente lista de regalos: \"+\", \".join(self.regalos)+\".\"\n",
    "\n",
    "regalospeque=regaloreyes(\"Laurita\",[\"Nancy\"])\n",
    "\n",
    "print(regalospeque.cartareyes())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ahora podremos utilizar la nueva funcionalidad que proporciona la subclase\n",
    "regalospeque.addregalo(\"Tableta\")\n",
    "print(regalospeque.cartareyes())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(regalospeque.tarjetaregalo())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejercicio 3\n",
    "Implementar una subclase de la clase **mascota** del ejercicio 1 que se denomine **perro** y que incorpore dos nuevos datos, fecha de vacunación y raza y un nuevo método que nos indique los días pasados desde la fecha de vacunación. Definir objetos de esta subclase y trabajar con ellos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class mascota:\n",
    "    def __init__(self,nombre,duenos):\n",
    "        self.nombre=nombre\n",
    "        self.duenos=duenos\n",
    "    \n",
    "    def nuevodueno(self,nuevo):\n",
    "        self.duenos.append(nuevo)\n",
    "\n",
    "perro1=mascota(\"Tarzan\",[\"Papi\",\"Mami\"])\n",
    "perro1.nuevodueno(\"Pepe\")\n",
    "#print(perro1.duenos)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejercicio 4\n",
    "Implementar una subclase de la clase creada en el ejercicio 1 que se denomine que **clientejunior** que añada a la funcion addmovimiento la funcionalidad de establecer un límite máximo a la cuantía de los movimientos que puede realizar el cliente cuando su edad es inferior al límite de edad que se establezca y que incorpore el método mensajealertas que muestre un mensaje indicado si el menor intentó realizar un movimiento no permitido y otro si el menor tiene más de una cierta cantidad de dinero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import reduce\n",
    "import datetime\n",
    "\n",
    "class clientecuenta:\n",
    "    def __init__(self,nombre,dni,direccion):\n",
    "        self.nombre=nombre\n",
    "        self.dni=dni\n",
    "        self.direccion=direccion\n",
    "        self.movimientos=[]\n",
    "    \n",
    "    def addmovimiento(self,movimiento):\n",
    "        self.movimientos.append(movimiento)\n",
    "        \n",
    "    def saldo(self):\n",
    "        return reduce((lambda x,y:x+y),[mov[\"dinero\"] for mov in self.movimientos])\n",
    "    \n",
    "    def saldomedio(self):\n",
    "        totdias=(self.movimientos[-1][\"fecha\"]-self.movimientos[0][\"fecha\"]).days\n",
    "        saldop=0\n",
    "        saldomedio=0\n",
    "        for i in range(len(self.movimientos)-1):\n",
    "            saldop+=self.movimientos[i][\"dinero\"]\n",
    "            dias=(self.movimientos[i+1][\"fecha\"]-self.movimientos[i][\"fecha\"]).days\n",
    "            saldomedio+=(dias/totdias)*saldop\n",
    "        return saldomedio\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Sobrecarga de operadores\n",
    "\n",
    "Los operadores habituales +, -, &#42;, /, &#42;&#42;, ^, etc. tienen un significado prestablecido en Python pero cuando definimos una clase podemos añadir un nuevo significado (sobrecargando el operador) específico  válido para los objetos de esa clase. De esa manera podemos reutilizar en un nuevo contexto símbolos que son familiares y fácilmente comprensibles.\n",
    "\n",
    "Disponemos para ello de los siguientes métodos especiales:\n",
    "<div class=\"alert alert-info\">\n",
    "<ul>\n",
    "    <li><b>__add__(self, objeto)</b>: permite redefinir una suma self+objeto para objetos de la clase.</li>\n",
    "    <li><b>__mul__(self,objeto)</b>: para redefinir el producto.</li>\n",
    "    <li><b>__sub__(self,objeto)</b>: para redefinir la diferencia.</li>\n",
    "    <li><b>__truediv__(self,objeto)</b>: para redefinir la división.</li>\n",
    "    <li><b>__xor__(self,objeto)</b>: para redefinir el símbolo ^.</li>\n",
    "    <li><b>__repr(self)__</b>: establece la forma en la que se representará el objeto.</li>\n",
    "</ul>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.5"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "3/2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "class fraccion:\n",
    "    \n",
    "# Cada fracción tendrá dos datos, el numerados y el denominador\n",
    "\n",
    "    def __init__(self,n,d):\n",
    "        self.n=n\n",
    "        self.d=d\n",
    "        \n",
    "# Sobrecargamos el operador producto,*, mediante __mul__(self,fra) en el que se indica cómo se deben multiplicar dos objetos de\n",
    "# la clase fraccion. Luego hacemos de forma similar para redefinir la suma.\n",
    "    \n",
    "    def __mul__(self,fra):\n",
    "        return fraccion(self.n*fra.n,self.d*fra.d)\n",
    "    \n",
    "    def __add__(self,fra):\n",
    "        return fraccion(self.n*fra.d+self.d*fra.n,self.d*fra.d)\n",
    "    \n",
    "    def __sub__(self,fra):\n",
    "        return fraccion(self.n*fra.d-self.d*fra.n,self.d*fra.d)\n",
    "    \n",
    "# __repr__ indica qué cadena de texto mostrar para presentarnos el objeto.\n",
    "    \n",
    "    def __repr__(self):\n",
    "        if self.d==0:\n",
    "            return \"Error: División por cero.\"\n",
    "        else:\n",
    "            return str(self.n)+\"/\"+str(self.d)\n",
    "    \n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "f1=fraccion(3,2)\n",
    "f2=fraccion(4,3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "27//8"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f1*f1*f1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f1-f2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fraccion(3,0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejercicio 5\n",
    "Incorporar al ejemplo anterior la definición para diferencia y división de fracciones. Modificar __repr__ para que muestre un aviso en caso de división por cero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejercicio 6\n",
    "\n",
    "Implementar una clase para vectoresa tridimensionales que redefina + para ser la suma de vectores, * para ser el producto escalar y ^ para ser el producto vectorial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "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.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
