{
 "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'> Modificado en 2019-1 al 2026-1 por Equipo Docente IIC2233. </font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Input/Output](#Input/Output)\n",
    "    1. [Bytes y encoding](#Bytes-y-encoding)\n",
    "    2. [El objeto `bytes`](#El-objeto-bytes)\n",
    "    3. [El objeto `bytearray`](#El-objeto-bytearray)\n",
    "    4. [*Chunks*](#Chunks)\n",
    "    5. [Transformar números](#Transformar-números)\n",
    "    6. [Print de `bytes`](#Print-de-bytes)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# *Input/Output*\n",
    "\n",
    "En este *notebook*, estudiaremos detalles del uso de archivos y de la representación de *bytes* en Python. Aprenderemos que no solamente podemos almacenar *strings* como caracteres que podemos ver en la pantalla, sino que podemos también manipular directamente la representación de más bajo nivel de los datos, los *bytes*.\n",
    "\n",
    "En específico, estudiaremos a fondo el manejo de *bytes*, arreglos de *bytes*, archivos, y *context managers*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## *Bytes* y *encoding*\n",
    "\n",
    "Internamente, un computador solo puede almacenar ceros y unos, por lo que es válido preguntarse cómo se almacenan los diferentes datos como los números o los caracteres que usamos para los *strings*.\n",
    "\n",
    "Para almacenar información, un computador lo hace en formato de *Bytes*, donde un *Byte* es una secuencia de 8 *bits*, y un *bit* corresponde a un valor 0 ó 1. Un *Byte*, por lo tanto, puede almacenar hasta $2^8=256$ combinaciones distintas de 0's y 1's, que representarán los números desde el 0 al 255 codificados en código binario.\n",
    "\n",
    "En el caso de los caracteres, lo que hacemos es **interpretar** el número almacenado en el *Byte* como un caracter. Por ejemplo, podríamos decir que el *Byte* 0 corresponde a la letra `a`, el *Byte* 1 corresponde a `b`, el *Byte* 2 corresponde a `c`, etc, hasta cubrir todos los caracteres que queremos representar. Esa asociación se conoce como **codificación** o ***encoding*** y una codificación muy común es la codificación [ASCII](https://es.wikipedia.org/wiki/ASCII), que data de 1963 y asocia números (*Bytes*) con caracteres de la siguiente manera:\n",
    "\n",
    "<img src=\"imgs/ascii.jpg\" alt=\"Codificación ASCII\" style=\"height: 400px; width:400px;\"/>\n",
    "\n",
    "La tabla ASCII muestra el valor del *Byte* en formato decimal (0 a 255), en formato hexadecimal (0x00 a 0xFF), y el caracter o significado correspondiente. Podemos ver que las letras mayúsculas del alfabeto corresponden desde el código 65 al código 90, las minúsculas van del código 97 al 122, y los dígitos van desde el código 48 al 57. Además los caracteres de puntuación, espacios, cambios de línea, tabulación, etc. también tienen un código asociado.\n",
    "\n",
    "Si bien esta es una codificación relativamente simple para estándares modernos y carece de códigos para caracteres de otros idiomas o para varios símbolos de puntuación, es un muy buen ejemplo para entender cómo funciona un encoding.\n",
    "\n",
    "Particularmente, la funciones `chr` u `ord` de Python permiten obtener el caracter correspondiente a un código decimal y el código correspondiente a un caracter, sin embargo, es fácil ver que estas funciones permiten más valores que los presentes en la tabla ASCII.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i\n",
      "i\n",
      "c\n",
      "2\n",
      "2\n",
      "3\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "print(chr(105))\n",
    "print(chr(105))\n",
    "print(chr(99))\n",
    "print(chr(50))\n",
    "print(chr(50))\n",
    "print(chr(51))\n",
    "print(chr(51))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De manera equivalente, para obtener el código correspondiente a un caracter, usamos la función `ord`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "66\n",
      "121\n",
      "116\n",
      "101\n"
     ]
    }
   ],
   "source": [
    "print(ord('B'))\n",
    "print(ord('y'))\n",
    "print(ord('t'))\n",
    "print(ord('e'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ࢹ\n",
      "™\n",
      "鼓\n",
      "225\n",
      "7491\n",
      "960\n"
     ]
    }
   ],
   "source": [
    "print(chr(2233))\n",
    "print(chr(8482))\n",
    "print(chr(40723))\n",
    "\n",
    "print(ord('á'))\n",
    "print(ord('ᵃ'))\n",
    "print(ord('π'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para representar Bytes, si bien la tabla y la función `ord` muestran el valor decimal, es común usar dos dígitos hexadecimal. Un **dígito hexadecimal** es uno de los dígitos: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, que corresponden a un valor desde el 0 al 15 respectivamente. La representación **hexadecimal** de un Byte utiliza dos dígitos hexadecimales (valores decimales de 0 a 15) para representar los 8 bits del Byte. Para identificar que una representación es hexadecimal se suele anteponer el string `0x`. La función `hex` de Python nos permiten obtener rápidamente la representación hexadecimal de un número. Por ejemplo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Decimal: 0, Hexadecimal: 0x0\n",
      "Decimal: 8, Hexadecimal: 0x8\n",
      "Decimal: 12, Hexadecimal: 0xc\n",
      "Decimal: 15, Hexadecimal: 0xf\n",
      "Decimal: 16, Hexadecimal: 0x10\n",
      "Decimal: 42, Hexadecimal: 0x2a\n",
      "Decimal: 100, Hexadecimal: 0x64\n",
      "Decimal: 255, Hexadecimal: 0xff\n"
     ]
    }
   ],
   "source": [
    "for i in [0, 8, 12, 15, 16, 42, 100, 255]:\n",
    "    print(f\"Decimal: {i}, Hexadecimal: {hex(i)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Existen [muchas representaciones modernas](https://es.wikipedia.org/wiki/Codificaci%C3%B3n_de_caracteres), pero una de las más comunes y expandidas actualmente es **Unicode**, que puede representar hasta 65536 caracteres, y será el encoding principal que usaremos a lo largo del curso. Además la mayoría de las aplicaciones modernas utilizan por defecto esta representación."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El objeto `bytes`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En Python los *bytes* se representan con el objeto de tipo `bytes`. Un objeto de tipo `bytes` es una secuencia **inmutable**, tal como los `str`. Para declarar que un objeto es un *byte* simplemente se pone al comienzo del objeto una `b`. Por ejemplo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'clich\\xe9'\n",
      "<class 'bytes'>\n"
     ]
    }
   ],
   "source": [
    "# Lo que está entre las comillas es un objeto de tipo bytes\n",
    "# La notación \\x63 indica el valor hexadecimal 63\n",
    "# Este ejemplo almacena los caracteres c, l, i, c, h, é\n",
    "caracteres = b\"\\x63\\x6c\\x69\\x63\\x68\\xe9\"\n",
    "print(caracteres)\n",
    "print(type(caracteres))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cada secuencia `\\x63` representa **1 Byte**. El Byte está descrito como un valor hexadecimal. En este caso el valor hexadecimal 63.\n",
    "\n",
    "El símbolo escape `\\x` indica que los siguientes dos caracteres después de la `x` corresponden a un *byte* usando dígitos hexadecimales. Los *bytes* que coinciden con los *bytes* de ASCII son reconocidos inmediatamente, así cuando los tratamos de imprimir aparecen correctamente (`clich`), el resto (`é`) se imprime como hexadecimal. La `b` en la impresión nos recuerda que lo que está a la derecha es un objeto de tipo `bytes`, no un `str`.\n",
    "\n",
    "Los *bytes* a secas pueden representar cualquier entidad, desde caracteres codificados de un *string* a pixeles de una imagen. Para poder interpretar correctamente los *bytes*, necesitamos conocer la forma en que fueron codificados. Por ejemplo, un patrón binario de 8 *bits* (1 *byte*) puede corresponder a un carácter en particular si lo decodificamos usando la codificación llamada `latin1`, pero puede corresponder a un carácter completamente distinto si lo decodificamos como un carácter de tipo `utf-16`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'clich\\xe9'\n",
      "cliché\n"
     ]
    }
   ],
   "source": [
    "caracteres = b\"\\x63\\x6c\\x69\\x63\\x68\\xe9\"  # Secuencia de 6 bytes\n",
    "print(caracteres)\n",
    "\n",
    "# Con bytes.decode() interpretamos los bytes utilizando la codificación latin-1 para obtener un string\n",
    "print(caracteres.decode(\"latin-1\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ab\n"
     ]
    }
   ],
   "source": [
    "# 0x61 y 0x62 son la representación en hexadecimal de los caracteres 'a' y 'b', respectivamente\n",
    "caracteres = b\"\\x61\\x62\"\n",
    "print(caracteres.decode(\"ascii\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'ab'\n"
     ]
    }
   ],
   "source": [
    "# 97 y 98 corresponden al código ASCII (en decimal) de a y b, respectivamente\n",
    "caracteres = bytes((97, 98))\n",
    "print(caracteres)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "bytes can only contain ASCII literal characters (1196113990.py, line 2)",
     "output_type": "error",
     "traceback": [
      "  \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[9]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[31m    \u001b[39m\u001b[31mcaracteres = b\"áb\"\u001b[39m\n                 ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m bytes can only contain ASCII literal characters\n"
     ]
    }
   ],
   "source": [
    "# Esto generará un error ya que sólo se pueden usar literales ASCII para la creación de bytes\n",
    "caracteres = b\"áb\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El método `decode` retorna un *string* normal (Unicode). Si, por ejemplo, hubiésemos usado otro alfabeto, habríamos obtenido otro *string*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cliché\n",
      "clichщ\n",
      "clichΘ\n",
      "汣捩\n"
     ]
    }
   ],
   "source": [
    "caracteres = b'\\x63\\x6c\\x69\\x63\\x68\\xe9'\n",
    "print(caracteres.decode(\"latin-1\"))\n",
    "print(caracteres.decode(\"iso8859-5\"))\n",
    "print(caracteres.decode(\"CP437\"))\n",
    "print(caracteres.decode(\"utf-16\"))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para codificar un *string* en un alfabeto específico, simplemente usamos el método `encode` de la clase `str`. Es necesario ingresar como argumento el conjunto de caracteres o alfabeto con que se quiere codificar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'estaci\\xc3\\xb3n'\n",
      "b'estaci\\xf3n'\n",
      "b'estaci\\xa2n'\n"
     ]
    },
    {
     "ename": "UnicodeEncodeError",
     "evalue": "'ascii' codec can't encode character '\\xf3' in position 6: ordinal not in range(128)",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mUnicodeEncodeError\u001b[39m                        Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 8\u001b[39m\n\u001b[32m      4\u001b[39m \u001b[38;5;28mprint\u001b[39m(caracteres.encode(\u001b[33m\"\u001b[39m\u001b[33mCP437\u001b[39m\u001b[33m\"\u001b[39m))\n\u001b[32m      6\u001b[39m \u001b[38;5;66;03m# No se puede codificar en ASCII el caracter \"ó\" ya que no existe dentro\u001b[39;00m\n\u001b[32m      7\u001b[39m \u001b[38;5;66;03m# de los 128 caracteres de ASCII\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[43mcaracteres\u001b[49m\u001b[43m.\u001b[49m\u001b[43mencode\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mascii\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m)\n",
      "\u001b[31mUnicodeEncodeError\u001b[39m: 'ascii' codec can't encode character '\\xf3' in position 6: ordinal not in range(128)"
     ]
    }
   ],
   "source": [
    "caracteres = \"estación\"\n",
    "print(caracteres.encode(\"UTF-8\"))  # 8-bit Unicode Transformation Format\n",
    "print(caracteres.encode(\"latin-1\"))\n",
    "print(caracteres.encode(\"CP437\"))\n",
    "\n",
    "# No se puede codificar en ASCII el caracter \"ó\" ya que no existe dentro\n",
    "# de los 128 caracteres de ASCII\n",
    "print(caracteres.encode(\"ascii\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El método `encode` nos ofrece opciones de cómo manejar el caso en que el *string* que se quiere codificar no puede ser codificado con el alfabeto requerido. Estas opciones se ingresan a través del argumento opcional `errors`, donde los valores posibles son: `strict` (el valor por defecto), `replace`, `ignore` o `xmlcharrefreplace`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'estaci?n'\n",
      "b'estacin'\n",
      "b'estaci&#243;n'\n"
     ]
    }
   ],
   "source": [
    "print(caracteres.encode(\"ascii\", errors='replace'))  # en ascii se reemplaza el caracter desconocido con \"?\"\n",
    "print(caracteres.encode(\"ascii\", errors='ignore'))\n",
    "print(caracteres.encode(\"ascii\", errors='xmlcharrefreplace'))  # se crea una entidad xml que representa el caracter Unicode"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En general, si queremos codificar un *string* y no sabemos con qué alfabeto deberíamos codificar, lo mejor es usar UTF-8, ya que los primeros 128 caracteres de UTF-8 son los mismos que en ASCII (es *backwards* compatible con ASCII). \n",
    "\n",
    "Recordar siempre que los objetos tipo *byte* son **inmutables**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El objeto `bytearray`\n",
    "\n",
    "Los *bytearrays* son **arreglos (listas) de `bytes`** que, a diferencia de los `bytes` **son mutables**. Los `bytearrays` se comportan como las listas: podemos indexar con la notación de *slicing*, y también podemos ir agregando `bytes` con el método `extend`. Para construir un `bytearray` podemos ingresar un objeto `bytes` inicial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bytearray(b'holamundo')\n",
      "bytearray(b'amun')\n",
      "bytearray(b'hola\\x15\\xa3ndo')\n",
      "bytearray(b'hola\\x15\\xa3ndoprograma')\n"
     ]
    }
   ],
   "source": [
    "ba = bytearray(b\"holamundo\")\n",
    "print(ba)\n",
    "\n",
    "# Podemos ocupar la notación de slicing\n",
    "print(ba[3:7])\n",
    "ba[4:6] = b\"\\x15\\xa3\"\n",
    "print(ba)\n",
    "\n",
    "# Podemos agregar bytes con el método extend\n",
    "ba.extend(b\"programa\")\n",
    "print(ba)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "104\n",
      "0b1101000\n",
      "01101000\n"
     ]
    }
   ],
   "source": [
    "# Aquí se imprime un byte, representado por un entero, el ascii que corresponde a la letra \"h\"\n",
    "print(ba[0])\n",
    "# La función bin genera un string con la representación binaria del byte\n",
    "print(bin(ba[0]))\n",
    "# El método zfill(r) rellena con 0's al inicio hasta completar r caracteres\n",
    "print(bin(ba[0])[2:].zfill(8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La última línea permite imprimir directamente los valores de los *bits* correspondientes al primer *byte* (representado en el literal `h` o el entero 104). El `[2:]` es para partir desde la tercera posición, ya que las primeras dos posiciones contienen los caracteres `0b`, que simplemente indica que el formato es en binario (línea anterior). Al agregar `.zfill(8)` indicamos que se usarán 8 *bits* para representar el *byte*, lo cual tiene sentido cuando hay ceros por el lado izquierdo y el *default* no los muestra (línea anterior tiene sólo 7 bits después del `0b`). \n",
    "\n",
    "Un caracter de un *byte* puede ser convertido a un entero usando la función `ord`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "97\n",
      "97\n"
     ]
    }
   ],
   "source": [
    "# Recordemos que imprimir un elemento de un bytearray nos muestra el int que le representa\n",
    "print(bytearray(b\"a\")[0])\n",
    "# La función ord hace esta conversión directamente a partir de un byte\n",
    "print(ord(b\"a\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bytearray(b'abcgDf')\n"
     ]
    }
   ],
   "source": [
    "# Aqui ocupamos ambas formas (int directamente y ord) para hacer cambios en un bytearray\n",
    "b = bytearray(b'abcdef')\n",
    "b[3] = ord(b'g')         # El caracter g tiene como código ascii el 103\n",
    "b[4] = 68                # El caracter D tiene como código ascii el 68, esto sería lo mismo que ingresar b[4] = ord(b'D')\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como mencionamos antes, los `bytearrays` se comportan como las listas. Por lo tanto, podemos hacer todas las operaciones que hemos aprendido en listas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bytearray(b'\\x02')\n"
     ]
    }
   ],
   "source": [
    "mi_bytearray = bytearray()\n",
    "\n",
    "# El método append solo funciona en bytearray con un int\n",
    "mi_bytearray.append(2)\n",
    "print(mi_bytearray)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bytearray(b'\\xff')\n",
      "bytearray(b'\\xff\\xff\\x12')\n"
     ]
    }
   ],
   "source": [
    "mi_bytearray = bytearray()\n",
    "\n",
    "# El método extend funciona con byte o bytearray\n",
    "mi_bytearray.extend(b'\\xff') #extendemos con un byte\n",
    "print(mi_bytearray)\n",
    "\n",
    "mi_bytearray.extend(bytearray(b'\\xff\\x12')) #extendemos con otro bytearray\n",
    "print(mi_bytearray)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "44\n"
     ]
    }
   ],
   "source": [
    "mi_bytearray = bytearray()\n",
    "\n",
    "mi_bytearray.append(44) # Append de un `int`\n",
    "print(mi_bytearray[0]) # Veremos el número (no el byte)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bytearray(b'\\x01\\x02\\x03')\n",
      "bytearray(b'\\x01\\x02')\n"
     ]
    }
   ],
   "source": [
    "mi_bytearray = bytearray()\n",
    "\n",
    "mi_bytearray.append(1) # Append de un `int`\n",
    "mi_bytearray.append(2) # Append de un `int`\n",
    "mi_bytearray.append(3) # Append de un `int`\n",
    "print(mi_bytearray)\n",
    "print(mi_bytearray[0: 2]) # Veremos el bytearray en esa sección"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3\n"
     ]
    }
   ],
   "source": [
    "mi_bytearray = bytearray(b'\\x01\\x02\\x03')\n",
    "\n",
    "# Veremos el `int` más grande entre cada byte del bytearray\n",
    "print(max(mi_bytearray)) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "mi_bytearray = bytearray(b'\\x01\\x02\\x03')\n",
    "\n",
    "for x in mi_bytearray:\n",
    "    print(x) # X será cada `int` del bytearray"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Chunks*\n",
    "\n",
    "Un concepto importante en el manejo de *bytes* es lo que se entiende por ***chunk***: un grupo de *bytes*. Cuando trabajamos con una cantidad importante de *bytes*, no es conveniente leerlos todos a la vez, ya que pueden ser muchos y no caber en nuestra memoria; ni tampoco uno por uno, pues requiere más operaciones de lectura de datos y puede ser muy lento. Sin embargo, podemos utilizar el punto medio: leer los *bytes* en *chunks* (grupos) de cierta cantidad de *bytes*. Por ejemplo, el siguiente *bytearray* lo leeremos en *chunks* de tamaño 4 (`TAMANO_CHUNK`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bytearray(b'Una ')\n",
      "bytearray(b'gran')\n",
      "bytearray(b' can')\n",
      "bytearray(b'tida')\n",
      "bytearray(b'd de')\n",
      "bytearray(b' byt')\n",
      "bytearray(b'es q')\n",
      "bytearray(b'ue q')\n",
      "bytearray(b'uier')\n",
      "bytearray(b'o le')\n",
      "bytearray(b'er d')\n",
      "bytearray(b'e a ')\n",
      "bytearray(b'poqu')\n",
      "bytearray(b'ito')\n"
     ]
    }
   ],
   "source": [
    "muchos_bytes = bytearray(b\"Una gran cantidad de bytes que quiero leer de a poquito\")\n",
    "TAMANO_CHUNK = 4\n",
    "\n",
    "for i in range(0, len(muchos_bytes), TAMANO_CHUNK):\n",
    "    # Aqui obtenemos nuestro chunk\n",
    "    chunk = bytearray(muchos_bytes[i:i+TAMANO_CHUNK])\n",
    "    print(chunk)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "También podemos leer los archivos por chunks, usando el modo de lectura de bytes `rb` al abrir el archivo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'IIC2'\n",
      "b'233 '\n",
      "b'Prog'\n",
      "b'rama'\n",
      "b'ci\\xc3\\xb3'\n",
      "b'n Av'\n",
      "b'anza'\n",
      "b'da'\n"
     ]
    }
   ],
   "source": [
    "from os.path import join\n",
    "\n",
    "TAMANO_CHUNK = 4\n",
    "with open(join('data', 'ejemplo_chunk.bin'), 'rb') as file:\n",
    "    while chunk := file.read(TAMANO_CHUNK):\n",
    "        print(chunk)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Al usar el `file.read()` pasándole un número como parámetro, se puede leer en bloques de esa cantidad de bytes.\n",
    "\n",
    "> (Inicio paréntesis\n",
    "> \n",
    "> Aquí hemos usado el llamado 'walrus operator' `:=` que almacena el valor de lo que hay en la derecha en lo que hay a la izquierda, de esta forma, guardamos en `chunk` lo leido, y si lo leído es vacío (por ejemplo si se leyó todo el archivo) entonces finaliza el `while` al recibir algo vacío. De esta forma lo que se hace es guardar en la variable `chunk` el valor de `file.read(TAMANO_CHUNK)`, y también se usa para revisar el valor en el `while`.\n",
    "> Por lo anterior el código se ha vuelto menos engorroso al no hacer las dos operaciones por separado, pero hay que tener cuidado de no usarlo demasiado ya que puede causar un código menos legible.\n",
    "> \n",
    "> Fin paréntesis)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transformar números\n",
    "\n",
    "El tipo de dato `int` como objeto tiene métodos adicionales que nos permiten entender la representación de este en *bytes*. Estos métodos pueden ser útiles cuando queremos trabajar con data binaria, o cuando usemos protocolos que requieran codificar y decodificar enteros (*integers* o `int`) a *bytes*. En esta conversión, será importante establecer cómo se ordenarán los bytes en memoria al representar datos de múltiples bytes como lo sería un *integer*.\n",
    "\n",
    "Dado lo anterior, es necesario entender el concepto de `byteorder`. Como un 1 *byte* posee 8 *bits*, como máximo puede representar un número entre 0 y 255 (`0x00` es `0` y `0xFF` es `255`). Por lo tanto, ¿cómo se transforma un número más grande a *bytes*?\n",
    "\n",
    "Aquí surge el concepto de *big endian* y *little endian*. \n",
    "\n",
    "* En *big endian* el *byte* más significativo (de mayor peso) quedará al inicio del *byte array*. Por ejemplo, el número 256 en *big endian* es `\\x01 \\x00`.\n",
    "\n",
    "* En *little endian* es lo opuesto, el *byte* más significativo (de mayor peso) quedará al final del *byte array*. Por ejemplo, el número 256 en *little endian* es `\\x00 \\x01`.\n",
    "\n",
    "Veamos algunos ejemplos más:\n",
    "\n",
    "| Número decimal | *Byte array* en *big endian* | *Byte array* en *little endian* |\n",
    "| -------------- | ---------------------------- | ------------------------------- |\n",
    "|     269        | `\\x01\\x0d`                   | `\\x0d\\x01`                      |\n",
    "|    68866       | `\\x01\\x0d\\x02`               | `\\x02\\x0d\\x01`                  |\n",
    "|    1157698     | `\\x11\\xAA\\x42`               | `\\x42\\xAA\\x11`                  |\n",
    "\n",
    "\n",
    "Ahora, veamos cómo podemos transformar números en _byte_ mediante código:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "* `int.to_bytes(length = 1, byteorder = 'big')`: retorna el *integer* como un arreglo de *bytes*. Debe recibir al menos dos argumentos: el largo del *byte array* y el orden. Si `byteorder` es 'big', el *byte* más significativo (de mayor peso) quedará al inicio del *byte array*. Si `byteorder` es 'little', el *byte* más significativo (de mayor peso) quedará al final del *byte array*.\n",
    "\n",
    "    En caso que `length` sea más grande que la cantidad necesaria de bytes para representar el número, esta función rellenará con los 0 necesarios para que el valor no cambie, pero pueda alcanzar el largo indicado. Esto último es análogo a cuando escribimos `001235`, donde agregamos 2 ceros que no influyen en el valor.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'A'"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(65).to_bytes(1, 'little')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x01\\x00'"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(256).to_bytes(2, 'big')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x00\\x00\\x00\\x01\\x00'"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(256).to_bytes(5, 'big')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x00\\x01'"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(256).to_bytes(2, 'little')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x00\\x01\\x00\\x00\\x00'"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(256).to_bytes(5, 'little')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x00\\x00\\x04\\x00'"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "i = 1024\n",
    "i.to_bytes(4, byteorder='big') # prueba cambiando el segundo argumento a 'little'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* `int.from_bytes(byte, byteorder = 'big')`: retorna el *integer* representado por el arreglo de *bytes*. Debe recibir al menos dos argumentos: el *byte* (cualquier objeto del tipo byte o un iterable que produzca *bytes*) y el orden. Si `byteorder` es 'big', el *byte* más significativo (de mayor peso) será el del inicio del *byte array*. Si `byteorder` es 'little', el *byte* más significativo (de mayor peso) será el del final del *byte array*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1024"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int.from_bytes(b'\\x00\\x00\\x04\\x00', byteorder='big')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "262144"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int.from_bytes(b'\\x00\\x00\\x04\\x00', byteorder='little')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "65"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int.from_bytes(b'A', byteorder='little')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "65"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int.from_bytes(b'A', byteorder='big')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podrás notar en el ejemplo anterior que cuando el largo del *byte array* es 1, no influye el `byteorder`. No obstante, cuando el *byte array* tiene un largo mayor a uno, pasar de `big` a `little` puede cambiar radicalmente el valor del *integer* a retornar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Print de `bytes`\n",
    "\n",
    "Un mensaje muy importante a transmitir cuando trabajamos con _bytes_ es:  **No confíes en los `print` de bytes**.\n",
    "\n",
    "Cuando transformamos un número en *bytes* e imprimos en consola ese valor, el computador puede reemplazar **visualmente** el *byte* a mostrar por otro carácter. Esto no implica que está mal la operación, solo es una forma que el computador tiene para representar cierto valor de byte cuando hace `print`.\n",
    "\n",
    "Veamos un ejemplo:\n",
    "\n",
    "El byte `\\x09` es `9`. Usemos el método `from_bytes` podemos confirmar eso:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int.from_bytes(b'\\x09', byteorder='big')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pero ahora transformemos ese 9 en un _byte_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\t'"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(9).to_bytes(1,'big') # Esto debería ser \\x09"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Que raro... sale que 9 es '\\t'. Esto no está mal, es solo que visualmente `'\\t'` equivale a 9. Incluso, si usamos el método `from_bytes`, podemos confirmar que es 9."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int.from_bytes(b'\\t', byteorder='big')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto mismo ocurre con el 13:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "13"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int.from_bytes(b'\\x0d', byteorder='big')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\r'"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(13).to_bytes(1,'big') # Esto debería ser \\x0d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "13"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int.from_bytes(b'\\r', byteorder='big')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por lo tanto, cuando trabajamos con _bytes_,  recomendamos no depender de cómo visualmente se ve el _byte_, sino transformar ese *byte* en el *integer* respectivo y con eso verificar que sea el número esperado."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": ".venv",
   "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.12.3"
  },
  "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
}
