{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC-2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Actualizados al 2026-1.</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [I/O de archivos](#I/O-de-archivos)\n",
    "2. [*Context Manager*](#Context-Manager)\n",
    "3. [(Bonus) Cómo emular archivos para I/O](#(Bonus)-Cómo-emular-archivos-para-I/O)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## I/O de archivos\n",
    "\n",
    "Hasta ahora, en IIC1103, han operado con la lectura y escritura de archivos de texto; sin embargo, los sistemas operativos representan los archivos como secuencias de *bytes*, no como texto. Dado que leer *bytes* y convertirlos a texto es una operación muy común en archivos, los lenguajes de programación proveen maneras de manejar los *bytes* para transformarlos a una representación en *string* usando métodos de codificación (*encoding*) y decodificación (*decoding*). \n",
    "\n",
    "La función `open` nos permite, además de abrir archivos, ingresar como argumentos el set de caracteres que se usará para codificar los *bytes* y la estrategia que se debe seguir cuando aparezcan *bytes* inconsistentes con el formato:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "��Qu�� pasa con las tildes? ��Por qu�� no aparecen los signos de interrogaci��n del comienzo? :(\n"
     ]
    }
   ],
   "source": [
    "# Creamos un archivo con un texto de base.\n",
    "contenido = \"¿Qué pasa con las tildes? \" \\\n",
    "    \"¿Por qué no aparecen los signos de interrogación del comienzo? :(\"\n",
    "\n",
    "# Guardamos el contenido en un archivo con encoding UTF-8\n",
    "file = open(\"data/IO_archivo_ejemplo\", \"w\", encoding='utf-8')\n",
    "file.write(contenido)\n",
    "\n",
    "# Pero leemos el mismo archivo con encoding ASCII\n",
    "file = open('data/IO_archivo_ejemplo', \"r\", encoding='ascii', errors='replace')\n",
    "print(file.read())\n",
    "file.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En este caso, se añadió el argumento `errors=\"replace\"` para indicar a la función `open` cómo debe manejar algún error del archivo cuando es incapaz de entender algún caracter. Por defecto, debería ocurrir un error, pero usando `\"replace\"` hacemos que no se caiga el programa y solo reemplace el caracter que no puede leer por el símbolo `�`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora, veamos cómo cambia esto, si es que elegimos otro *encoding* para leer el contenido del archivo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "¿Qué pasa con las tildes? ¿Por qué no aparecen los signos de interrogación del comienzo? :(\n"
     ]
    }
   ],
   "source": [
    "file = open('data/IO_archivo_ejemplo', \"r\", encoding='utf-8')\n",
    "print(file.read())\n",
    "file.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vemos que ahora sí se muestran bien las tildes y los signos de interrogación, pues al leer el archivo lo estamos haciendo con el mismo *encoding* con el que fue escrito. Ahora, escribiremos en el mismo archivo, un texto distinto, y luego leeremos el archivo para ver qué ocurre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sorry pero ahora yo soy lo que habrá dentro del archivo\n"
     ]
    }
   ],
   "source": [
    "contenido = \"sorry pero ahora yo soy lo que habrá dentro del archivo\"\n",
    "\n",
    "file = open(\"data/IO_archivo_ejemplo\", \"w\", encoding=\"utf-8\")\n",
    "file.write(contenido)\n",
    "file.close()\n",
    "\n",
    "file = open('data/IO_archivo_ejemplo', \"r\", encoding='utf-8')\n",
    "print(file.read())\n",
    "file.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como puedes haber notado, el archivo se sobrescribió, y el nuevo texto en lugar de ser agregado al archivo, reemplazó lo que había. Para agregar un nuevo texto al final de un archivo ya existente, debemos cambiar el modo de apertura del archivo cambiando la `w` de *write* por una `a`, de *append*. De esta forma, al escribir en el archivo, se va a hacer al final del archivo en lugar de reemplazar el contenido anterior."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sorry pero ahora yo soy lo que habrá dentro del archivo\n",
      "yo me agregaré al final\n"
     ]
    }
   ],
   "source": [
    "contenido = \"\\nyo me agregaré al final\"\n",
    "\n",
    "# Abrimos el archivo en modo append\n",
    "file = open(\"data/IO_archivo_ejemplo\", \"a\", encoding=\"utf-8\")\n",
    "file.write(contenido)\n",
    "file.close()\n",
    "\n",
    "# Abrimos el archivo en el modo de lectura (read)\n",
    "file = open('data/IO_archivo_ejemplo', \"r\", encoding='utf-8')\n",
    "print(file.read())\n",
    "file.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Además de leer archivos de texto, podemos abrir archivos y leer sus *bytes* en lugar de texto. Para abrir un archivo como binario, simplemente debemos agregar una `b` por el lado derecho del modo de apertura. Por ejemplo, `wb` (*write bytes*) o `rb` (*read bytes*). El archivo se comportará igual que un archivo de texto, sólo que sin la codificación automática de *byte* a texto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'abcde12'\n"
     ]
    }
   ],
   "source": [
    "contenido = b\"abcde12\"\n",
    "\n",
    "file = open(\"data/IO_archivo_ejemplo_2\", \"wb\")\n",
    "file.write(contenido)\n",
    "file.close()\n",
    "\n",
    "file = open('data/IO_archivo_ejemplo_2', \"rb\")\n",
    "print(file.read())\n",
    "file.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podemos concatenar *bytes* simplemente con el operador `+`. En el siguiente ejemplo, construimos un contenido dinámico para ser escrito en un archivo de *bytes*. Después leemos una cantidad fija de *bytes* desde el mismo archivo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'linea_\\x00 abcde12 linea_\\x01 abcde12 linea_\\x02 a'\n"
     ]
    }
   ],
   "source": [
    "num_lineas = 100\n",
    "\n",
    "file = open(\"data/IO_archivo_ejemplo_3\", \"wb\")\n",
    "for i in range(num_lineas):\n",
    "    # La función bytes() necesita una lista de números que se puedan recorrer uno a uno, \n",
    "    # por eso usamos [i] en lugar de solo i. \n",
    "    contenido = b\"linea_\" + bytes([i]) + b\" abcde12 \"\n",
    "    file.write(contenido)\n",
    "file.close()\n",
    "\n",
    "file = open('data/IO_archivo_ejemplo_3', \"rb\")\n",
    "# El número dentro de la función read nos dice el número de bytes que\n",
    "# se van a leer del archivo\n",
    "print(file.read(41))\n",
    "file.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos a utilizar este modo de abrir archivos como _bytes_ más adelante en el curso "
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## *Context Manager*\n",
    "\n",
    "Dado que siempre necesitamos cerrar un archivo después de usarlo, debemos considerar la posibilidad de que ocurran excepciones mientras el archivo está abierto. \n",
    "\n",
    "Una forma de hacerlo es utilizar un bloque del tipo `try/finally` para cerrar el archivo. Este tipo de bloque primero ejecutará todo el contenido dentro del `try`. Luego, aunque haya un error en el código, ejecutará lo que esté dentro del bloque `finally`. \n",
    "\n",
    "\n",
    "Sin embargo, esto genera bastante código extra y requiere entender bien qué hace el bloque `try/finally` (lo cual vamos a aprender con detalle más adelante). En Python existe una forma de hacer lo mismo con menos código, a través de un *context manager*, que se encarga de ejecutar, por debajo, las sentencias `try` y `finally` sin la necesidad de llamarlas directamente. Sólo necesitamos llamar al archivo que queremos abrir usando la sentencia `with`. Veamos un ejemplo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"data/IO_archivo_ejemplo_3\", \"r\") as file:\n",
    "    contenido = file.read()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El código anterior sería equivalente a hacer lo siguiente:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "file = open(\"data/IO_archivo_ejemplo_3\", \"r\")\n",
    "try:\n",
    "    contenido = file.read()\n",
    "finally:\n",
    "    file.close()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora, si ejecutamos `dir` en un objeto de tipo archivo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines']\n"
     ]
    }
   ],
   "source": [
    "file = open(\"data/IO_archivo_ejemplo_3\", \"w\")\n",
    "print(dir(file))\n",
    "file.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vemos que existen dos métodos llamados `__enter__` y `__exit__`. Estos dos métodos transforman el archivo en un *context manager*. El método `__exit__` asegura que el archivo será cerrado incluso si aparece una excepción mientras esté abierto. El método `__enter__` inicializa el archivo o realiza cualquier acción necesaria para ajustar el contexto del objeto.\n",
    "\n",
    "Para asegurarnos que un archivo usará los métodos `__enter__` y `__exit__`, simplemente debemos llamar a la apertura del archivo con el método `with`.\n",
    "\n",
    "También podemos crear nuestros propios *context managers* a partir de cualquier clase. Simplemente agregamos los métodos `__enter__` y `__exit__` y podemos llamar a nuestra clase a través del método `with`. Del siguiente ejemplo, se puede ver cómo el método `__exit__` se ejecuta una vez que nos salimos del *scope* de la sentencia `with`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. Inicizalizando context manager\n",
      "2. Abriendo context manager y definiendo qué elemento será el `as XXXX`\n",
      "3. Primera línea dentro del context manager\n",
      "\t['g', 'l', 'p', 'h', 'w', 'd', 'l', 'p', 'a', 'o', 'u', 'm', 'm', 'r', 'y', 't', 'w', 'p', 'e', 'f']\n",
      "4. Última línea del context manager\n",
      "5. Cerrando context manager\n",
      "\t['G', 'L', 'P', 'H', 'W', 'D', 'L', 'P', 'A', 'O', 'U', 'M', 'M', 'R', 'Y', 'T', 'W', 'P', 'E', 'F']\n"
     ]
    }
   ],
   "source": [
    "import string\n",
    "import random\n",
    "\n",
    "\n",
    "class StringUpper:\n",
    "    \n",
    "    def __init__(self):\n",
    "        print(\"1. Inicizalizando context manager\")\n",
    "        self.data = []\n",
    "\n",
    "    def __enter__(self):\n",
    "        print(\"2. Abriendo context manager y definiendo qué elemento será el `as XXXX`\")\n",
    "        return self.data\n",
    "\n",
    "    def __exit__(self, exc_type, exc_value, traceback):\n",
    "        print(\"5. Cerrando context manager\")\n",
    "        i = 0\n",
    "        for char in self.data:\n",
    "            self.data[i] = char.upper()\n",
    "            i+= 1\n",
    "        \n",
    "\n",
    "\n",
    "with StringUpper() as s_upper:\n",
    "    # s_upper será una lista según lo definido en \"__enter__\"\n",
    "    print(\"3. Primera línea dentro del context manager\")\n",
    "    \n",
    "    for i in range(20):\n",
    "        # Aquí seleccionamos, en forma aleatoria, un ascii en minúsculas\n",
    "        # y las agregamos a la lista\n",
    "        s_upper.append(random.choice(string.ascii_lowercase))\n",
    "        \n",
    "    print(\"\\t\" + str(s_upper))\n",
    "    print(\"4. Última línea del context manager\")\n",
    "\n",
    "print(\"\\t\" + str(s_upper))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El código anterior simplemente corresponde a una clase que implementar los métodos `__enter__` y `__exit__`. Con esto, podemos instanciar la clase a través de un *context manager*. En este ejemplo en particular, el *context manager* se encarga de transformar todos los caracteres ASCII de la lista a mayúsculas cuando este es cerrado."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## (Bonus) Cómo emular archivos para I/O\n",
    "\n",
    "Muchas veces tenemos que interactuar con algunos módulos de *software* que sólo leen y escriben sus datos desde y hacia archivos. Si queremos comunicar nuestro código que genera -por ejemplo, *strings*- para evitar tener que escribir nuestros datos en un archivo para que el otro programa los lea, podemos *emular* el tener un archivo usando los módulos de Python `StringIO` o `BytesIO`. El siguiente ejemplo muestra cómo usar estos módulos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'informacin como texto y ms'\n"
     ]
    }
   ],
   "source": [
    "from io import StringIO, BytesIO\n",
    "\n",
    "\n",
    "# Aquí simulamos tener un archivo que contiene el string dado\n",
    "file_in = StringIO(\"información como texto y más\")\n",
    "\n",
    "# Aquí simulamos un archivo de Bytes para escribir la información\n",
    "file_out = BytesIO()\n",
    "\n",
    "char = file_in.read(1)\n",
    "while char:\n",
    "    file_out.write(char.encode(\"ascii\", \"ignore\"))\n",
    "    char = file_in.read(1)\n",
    "\n",
    "buffer_ = file_out.getvalue()\n",
    "print(buffer_)"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "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.11.14"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
