{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "iwFmF2sLRkJr"
   },
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC-2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Actualizados el 2026-1.</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ptYFt7BTRxHt",
    "tags": []
   },
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Motivación](#Motivación)\n",
    "3. [Anotaciones](#Anotaciones)\n",
    "2. [Módulo `typing`](#Módulo-typing)\n",
    "4. [Listas y `collections`](#Listas-y-collections)\n",
    "5. [Clases y autorreferencia](#Clases-y-autorreferencia)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Motivación\n",
    "\n",
    "A medida que nuestros programas crecen en complejidad y tamaño, resulta cada vez más difícil recordar que tipos de datos usan las distintas variables y funciones a lo largo de los módulos que hemos creado. Esto es peor cuando debemos hacer mantención a un código que no hemos trabajado por meses, o de otra persona. Una alternativa muy recomendada es el uso de comentarios para documentar las particularidades de un código. Esto ayuda enormemente la comprensión posterior de un código.\n",
    "\n",
    "Cuando programamos, para mantener el orden en el código, muchas veces decidimos utilizar un único tipo de dato para una variable determinada. Por ejemplo, una variable llamada `nombre` puede ser de cualquier tipo, pero la utilizamos solo como un `string`. Lo mismo ocurre con argumentos, atributos y retornos de funciones. Esta información es muy útil al momento de documentar, pero también ayuda a que las *IDE*s a que sugieran código y alerten posibles errores. Por esto Python nos ofrece una manera especial de incorporarla al código, las anotaciones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Anotaciones\n",
    "\n",
    "Python nos permite declarar el tipo esperado de una variable, argumento o atributo por medio de anotaciones. Para esto, después del nombre, debemos escribir `:` y el tipo. Por ejemplo, podemos declarar que la intención de una variable es almacenar solamente números enteros de la siguiente forma:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "variable_numerica: int = 42"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo mismo se puede aplicar para una variable que es un string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "nombre: str = \"Julián\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para los casos de funciones o métodos, podemos anotar el tipo de datos de los argumentos. Para esto, se hace del mismo modo que una variable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def direccion(calle: str, numero: int):\n",
    "    return f'{calle.strip().title()} {numero}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Además, podemos anotar el tipo de dato esperado del retorno. Para esto, después de la declaración de los argumentos debemos escribir `->` y el tipo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def direccion(calle: str, numero: int) -> str:\n",
    "    return f'{calle.strip().title()} {numero}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "De este modo, podemos mantener un cierto grado de documentación funcional en nuestro código. Ya que las *IDE*s pueden analizar las anotaciones, dar sugerencias de atributos y métodos mientras se programa, y levantar alertas si se escribe algo que no cumpla con los tipos ya declarados:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alameda 10.0'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "variable_numerica = \"la respuesta\"  # Alerta IDE: Expected type 'int', got 'srt' instead\n",
    "\n",
    "direccion(\"Alameda\", 10.0)  # Alerta IDE: Expected type 'int', got 'float' instead"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finalmente, podemos utilizar nuestras propias clases como tipo de dato para las anotaciones. En el siguiente ejemplo se anotó que `variable` será una instancia del tipo `Computador`: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Computador:\n",
    "    def __init__(self) -> None:\n",
    "        self.energia: int = 100\n",
    "\n",
    "    def reducir_energia(self) -> None:\n",
    "        self.energia -= 1\n",
    "\n",
    "\n",
    "variable: Computador = Computador()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cabe destacar que las anotaciones y alertas de las *IDE*s no impiden que un código se ejecute."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Módulo `typing`\n",
    "\n",
    "Para extender y flexibilizar las anotaciones, el módulo [`typing`](https://docs.python.org/3/library/typing.html) de Python nos provee de tipos adicionales. Algunos de los más útiles son:\n",
    "\n",
    "- `Any`: \n",
    "Nos permite declarar que el tipo de una variable no está restringida: \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Any\n",
    "\n",
    "variable: Any = None\n",
    "variable = 1\n",
    "variable = 2.0\n",
    "variable = \"3\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `Union`: Nos permite declarar que una variable puede ser de más de un tipo distinto. Por ejemplo, si va a ser un número, independiente de si es entero o real, podemos usar: `Union[int, float]`. Desde python 3.10 también podemos usar el operador `|`, siendo el ejemplo anterior equivalente a `int | float`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Union\n",
    "\n",
    "\n",
    "def dividir_numeros(numerador: Union[int, float], denominador: Union[int, float]) -> float:\n",
    "    return numerador / denominador"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `Callable`: Nos permite declarar que una variable contiene la referencia a una función. Si lo deseamos, también podemos especificar los tipos de los argumentos y del retorno: `Callable[[TipoArgumento, ...], TipoRetorno]`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.5"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from typing import Callable\n",
    "\n",
    "\n",
    "funcion_dividir: Callable[[Union[int, float], Union[int, float]], float] = dividir_numeros\n",
    "funcion_dividir(1.0, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `Optional`: Nos permite declarar que una variable es de algún tipo o `None`. Escribir `Optional[X]` es equivalente a `Union[X, None]` y `X | None`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Optional\n",
    "\n",
    "\n",
    "variable: Optional[int] = None\n",
    "variable = 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Listas y `collections`\n",
    "\n",
    "Python ofrece estructuras de datos cuyo objetivo es hacer de contenedores para otros elementos, algunos *built-in* y otros por medio del módulo [`collections`](https://docs.python.org/3/library/collections.html). De estas estructuras, actualmente conocen las listas que son del tipo `list`.\n",
    "\n",
    "Para poder definir el tipo de elementos contenidos en una lista, el módulo [`typing`](https://docs.python.org/3/library/typing.html) nos ofrece `List`. Por ejemplo, podemos declarar que una variable corresponde a una lista que solo contiene números enteros usando `List[int]`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "\n",
    "\n",
    "lista: List[int] = [1, 2, 3, 4, 5, 6]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De este mismo modo, el módulo [`typing`](https://docs.python.org/3/library/typing.html) ofrece tipos para el resto de las estructuras de datos *built-in* y del módulo [`collections`](https://docs.python.org/3/library/collections.html). Les recomendamos revisar la [documentación de Python](https://docs.python.org/3/library/typing.html) si desean utilizarlas.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Clases y autorreferencia\n",
    "\n",
    "Un problema es que, con lo visto anteriormente, no podremos usar un tipo personalizado en la definición de su propia clase. Esto es porque al momento de definir la clase su tipo aún no existe. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'Persona' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[12], line 4\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Optional\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28;43;01mclass\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;21;43;01mPersona\u001b[39;49;00m\u001b[43m:\u001b[49m\n\u001b[1;32m      5\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;28;43;01mdef\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;21;43m__init__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mPersona\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m>\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m:\u001b[49m\n\u001b[1;32m      6\u001b[0m \u001b[43m        \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbff\u001b[49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mOptional\u001b[49m\u001b[43m[\u001b[49m\u001b[43mPersona\u001b[49m\u001b[43m]\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\n",
      "Cell \u001b[0;32mIn[12], line 5\u001b[0m, in \u001b[0;36mPersona\u001b[0;34m()\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01mPersona\u001b[39;00m:\n\u001b[0;32m----> 5\u001b[0m     \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m: \u001b[43mPersona\u001b[49m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m      6\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbff: Optional[Persona] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m      8\u001b[0m     \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21masignar_bff\u001b[39m(\u001b[38;5;28mself\u001b[39m: Persona, otro: Persona) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m:\n",
      "\u001b[0;31mNameError\u001b[0m: name 'Persona' is not defined"
     ]
    }
   ],
   "source": [
    "from typing import Optional\n",
    "\n",
    "\n",
    "class Persona:\n",
    "    def __init__(self: Persona) -> None:\n",
    "        self.bff: Optional[Persona] = None\n",
    "\n",
    "    def asignar_bff(self: Persona, otro: Persona) -> None:\n",
    "        self.bff = otro"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una solución, hasta ahora, es postergar la evaluación de las anotaciones, de tal modo que al momento de ser evaluadas todos los tipos ya existan. Para hacerlo solo es necesario importar annotations del módulo `__future__`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import annotations  # Esto habilita la evaluación postergada de las anotaciones\n",
    "from typing import Optional\n",
    "\n",
    "\n",
    "class NuevaPersona:\n",
    "    def __init__(self: NuevaPersona) -> None:\n",
    "        self.bff: Optional[NuevaPersona] = None\n",
    "\n",
    "    def asignar_bff(self: NuevaPersona, otro: NuevaPersona) -> None:\n",
    "        self.bff = otro\n"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "5-terminal.ipynb",
   "provenance": []
  },
  "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.11.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
