{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Material creado en 2020-1 por Equipo Docente IIC2233. </font>\n",
    "<br>\n",
    "<font size='1'>Modificado en 2020-2 al 2025-2 por Equipo Docente IIC2233. </font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Introducción](#¿args-y-kwargs?)\n",
    "2. [Argumentos y argumentos por palabra clave](#Argumentos-y-argumentos-por-palabra-clave)\n",
    "3. [Cantidad variable de parámetros](#Cantidad-variable-de-parámetros)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ¿`args` y `kwargs`?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Ah? ¿`args` y qué?\n",
    "\n",
    "Y `kwargs`, sí. No hemos encontrado un consenso general de cómo pronunciar esta última palabra, pero sí sabemos que provienen de los conceptos *arguments* y *keyword arguments*. Si bien se conocen casi exclusivamente así en Python, este es un tema de bastante utilidad y que suele ser difícil de entender (y explicar). Este cuaderno busca explicar los conceptos de `args` y `kwargs` de Python y su utilidad.\n",
    "\n",
    "Como sugieren sus nombres completos, describen dos tipos de argumentos en el llamado de funciones (o métodos): argumentos (a secas) y argumentos por palabra clave. Son una forma de referirse a la manera de **especificar una cantidad variable de argumentos o parámetros en la definición de una función** en Python."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### (Abre paréntesis...\n",
    "### Argumentos v/s parámetros"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es común usar estos dos conceptos como sinónimos, pero en realidad se refieren a entidades levemente distintas. Como indican en la [documentación oficial de Python](https://docs.python.org/3/faq/programming.html#what-is-the-difference-between-arguments-and-parameters), parámetros son los **nombres** que recibe una función y se declaran en su **definición**, mientras que argumentos son los **valores** efectivos que se le entregan al momento de **llamarse** la función. Luego, una función solo tiene **un** conjunto de parámetros establecido, pero puede recibir múltiples argumentos distintos al llamarse.\n",
    "\n",
    "Por ejemplo, para la función `sumar`, sus parámetros son `x`, `y` y `z`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sumar(x, y, z):\n",
    "    return x + y + z"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pero al llamarse, los valores `3`, `4` y `valor` son argumentos para `sumar`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "12"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "valor = 5\n",
    "sumar(3, 4, valor)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### ...cierra paréntesis)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Argumentos y argumentos por palabra clave\n",
    "\n",
    "Antes que todo, hay que recordar cómo se pueden especificar argumentos al momento de llamar una función. La forma más simple de especificar parámetros es mediante una secuencia de parámetros con nombre separados por coma, y la forma más simple de especificar argumentos es listándolos en orden:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: hola, b: mundo, c: 42\n"
     ]
    }
   ],
   "source": [
    "def ejemplo(a, b, c):\n",
    "    print(f'a: {a}, b: {b}, c: {c}')\n",
    "\n",
    "ejemplo('hola', 'mundo', 42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por defecto, se usa el **orden de los argumentos** para establecer la correspondencia entre argumentos y parámetros. Pero resulta que los parámetros, al tener nombre, también permiten se le especifiquen en desorden utilizando su **nombre como palabra clave**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: hola, b: mundo, c: 42\n"
     ]
    }
   ],
   "source": [
    "ejemplo(b='mundo', c=42, a='hola')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Según esto, la [documentación de Python](https://docs.python.org/3/glossary.html) cataloga los argumentos en dos grupos:\n",
    "\n",
    "- Argumento posicional (*positional argument*): argumento que no es por palabra clave. El mejor conocido como argumento a secas, que sigue el orden de establecimiento.\n",
    "- Argumento por palabra clave (*keyword argument*): argumento precedido mediante un identificador (`name=`) en un llamado de función.\n",
    "\n",
    "La diferencia es clara, pero Python impone algunas restricciones sobre su uso en conjunto para no contar con ambigüedades. Específicamente, se pueden establecer argumentos de ambos tipos en la misma llamada, pero no pueden existir argumentos posicionales **después** de argumentos por palabra clave, y no se puede establecer por palabra clave un argumento **ya establecido** mediante posición. Las siguientes llamadas son todas válidas y equivalentes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n"
     ]
    }
   ],
   "source": [
    "ejemplo('hola', 'mundo', 42)\n",
    "ejemplo('hola', 'mundo', c=42)\n",
    "ejemplo('hola', b='mundo', c=42)\n",
    "ejemplo('hola', c=42, b='mundo')\n",
    "ejemplo(a='hola', b='mundo', c=42)\n",
    "ejemplo(c=42, a='hola', b='mundo')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pero las siguientes llamadas son inválidas y Python lanza errores:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "positional argument follows keyword argument (7135585.py, line 1)",
     "output_type": "error",
     "traceback": [
      "  \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[6]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[31m    \u001b[39m\u001b[31mejemplo(a='hola', 'mundo', 42)  # Posicional después de palabra clave\u001b[39m\n                                 ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m positional argument follows keyword argument\n"
     ]
    }
   ],
   "source": [
    "ejemplo(a='hola', 'mundo', 42)  # Posicional después de palabra clave"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "ejemplo() got multiple values for argument 'a'",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mTypeError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mejemplo\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mhola\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mmundo\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43ma\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m42\u001b[39;49m\u001b[43m)\u001b[49m  \u001b[38;5;66;03m# Palabra clave vuelve a usar parámetro usado por argumento posicional\u001b[39;00m\n",
      "\u001b[31mTypeError\u001b[39m: ejemplo() got multiple values for argument 'a'"
     ]
    }
   ],
   "source": [
    "ejemplo('hola', 'mundo', a=42)  # Palabra clave vuelve a usar parámetro usado por argumento posicional"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora, existen dos formas más de establecer argumentos en la llamada de una función que siguen las reglas anteriores, con `*` y `**`:\n",
    "\n",
    "- `func(*argumentos)`: donde `argumentos` es una **lista o tupla**, permite desempaquetar el contenido de dicha estructura y establecer esos argumentos como posicionales en la llamada. En realidad, `argumentos` puede ser cualquier objeto **iterable**, pero aprenderemos de ellos más adelante. \n",
    "- `func(**argumentos)`: donde `argumentos` es un **diccionario**, permite desempaquetar los pares llave-valor y establecer argumentos por palabra clave en la llamada.\n",
    "\n",
    "Por ejemplo, las siguientes llamadas son todas equivalentes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n"
     ]
    }
   ],
   "source": [
    "lista = ['hola', 'mundo', 42]\n",
    "tupla = ('hola', 'mundo', 42)\n",
    "diccionario = {'a': 'hola', 'b': 'mundo', 'c': 42}\n",
    "\n",
    "ejemplo(*lista)\n",
    "ejemplo(*tupla)\n",
    "ejemplo(**diccionario)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No solo eso, pero se permite el uso simultáneo de todos estos mecanismos, mientras se respeten las reglas antes mencionadas. Las siguientes son todas equivalentes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: hola, b: mundo, c: 42\n"
     ]
    }
   ],
   "source": [
    "ejemplo('hola', *['mundo', 42])\n",
    "ejemplo(*['hola', 'mundo'], 42)\n",
    "ejemplo(*['hola', 'mundo'], *[42])\n",
    "ejemplo(*['hola', 'mundo'], c=42)\n",
    "ejemplo('hola', 'mundo', **{'c': 42})\n",
    "ejemplo(*['hola', 'mundo'], **{'c': 42})\n",
    "ejemplo(*['hola'], **{'c': 42}, b='mundo')\n",
    "ejemplo(*['hola'], **{'c': 42}, **{'b': 'mundo'})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El uso de `*` y `**` en las llamadas a funciones es un paso para ejecutar llamadas de funciones con cantidad variable de argumentos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cantidad variable de parámetros\n",
    "\n",
    "Definir una cantidad variable de parámetros entrega flexibilidad a la hora de definir y llamar funciones, ya que nos permite utilizar la misma función para más de una situación. El mecanismo más simple para recibir una cantidad de argumentos variable es mediante valores por defecto de parámetros, que toman cierto valor si no son declarados:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: hola, b: Juan, c: 5\n",
      "a: hola, b: Juan, c: 42\n",
      "a: hola, b: mundo, c: 42\n",
      "a: chao, b: mundo, c: 42\n",
      "a: chao, b: tú, c: 42\n",
      "a: chao, b: tú, c: 100\n",
      "a: ho, b: mundo, c: 21\n"
     ]
    }
   ],
   "source": [
    "def ejemplo(a, b=\"mundo\", c=42):\n",
    "    print(f'a: {a}, b: {b}, c: {c}')\n",
    "\n",
    "\n",
    "ejemplo(\"hola\", \"Juan\", 5)\n",
    "ejemplo(\"hola\", \"Juan\")\n",
    "ejemplo(\"hola\")\n",
    "\n",
    "lista = [\"chao\"]\n",
    "ejemplo(*lista)\n",
    "\n",
    "lista.append(\"tú\")\n",
    "ejemplo(*lista)\n",
    "\n",
    "lista.append(100)\n",
    "ejemplo(*lista)\n",
    "\n",
    "ejemplo(**{'c': 21, 'a': 'ho'})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La única restricción de uso de valores por defecto es que en parámetros donde el orden importa, no es posible declarar argumentos sin valor por defecto después de argumentos con valor por defecto:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "parameter without a default follows parameter with a default (2460778971.py, line 1)",
     "output_type": "error",
     "traceback": [
      "  \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[31m    \u001b[39m\u001b[31mdef ejemplo_invalido(a=3, b):\u001b[39m\n                              ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m parameter without a default follows parameter with a default\n"
     ]
    }
   ],
   "source": [
    "def ejemplo_invalido(a=3, b):\n",
    "    print(a, b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si bien este mecanismo permite una cantidad variable de parámetros, sigue siendo restringida la cantidad. Para `ejemplo`, la cantidad de argumentos posibles sigue siendo entre uno y tres. No es posible entregarlo ocho argumentos, pero sabemos que es posible, ya que existen funciones como `print` que recibe una cantidad arbitraria de argumentos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hola\n",
      "hola mundo\n",
      "\n",
      "hola mundo 1 2 3 4 5\n"
     ]
    }
   ],
   "source": [
    "print('hola')\n",
    "print('hola', 'mundo')\n",
    "print()\n",
    "print('hola', 'mundo', *[1, 2, 3, 4, 5])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Resulta que es mediante el uso de `*` y `**` en la declaración de funciones que es posible establecer este comportamiento:\n",
    "\n",
    "- `*args`: permite declarar una cantidad arbitraría de argumentos **posicionales**. Al llamarse la función, se reciben esos argumentos y son contenidos en una **tupla** accesible por `args`.\n",
    "- `**kwargs`: permite declarar una cantidad arbitraría de argumentos **por palabra clave**. Al llamarse la función, se reciben esos argumentos y son contenidos en un **diccionario** accesible por `kwargs`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "func1: (1,)\n",
      "func1: (1, 2, 3, 4)\n",
      "func1: ()\n",
      "---------------------------------------------\n",
      "func2: {'nombre': 'Pedro'}\n",
      "func2: {'nombre': 'Pedro', 'apellido': 'Rojas'}\n",
      "func2: {}\n"
     ]
    }
   ],
   "source": [
    "def func1(*args):\n",
    "    print(f'func1: {args}')\n",
    "\n",
    "\n",
    "def func2(**kwargs):\n",
    "    print(f'func2: {kwargs}')\n",
    "\n",
    "\n",
    "func1(1)\n",
    "func1(1, 2, 3, 4)\n",
    "func1()\n",
    "print('-' * 45)\n",
    "func2(nombre='Pedro')\n",
    "func2(nombre='Pedro', apellido=\"Rojas\")\n",
    "func2()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es importante notar varios detalles. Primero, cada declaración es exclusiva para argumentos posicionales o por palabra clave, solo reciben de cada tipo. Es por esto, que las siguientes llamadas lanzan un error:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "func1() got an unexpected keyword argument 'nombre'",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mTypeError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[14]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mfunc1\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnombre\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mPedro\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m  \u001b[38;5;66;03m# func1 usa *, entonces solo recibe posicionales\u001b[39;00m\n",
      "\u001b[31mTypeError\u001b[39m: func1() got an unexpected keyword argument 'nombre'"
     ]
    }
   ],
   "source": [
    "func1(nombre='Pedro')  # func1 usa *, entonces solo recibe posicionales"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "func2() takes 0 positional arguments but 1 was given",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mTypeError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[15]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mfunc2\u001b[49m\u001b[43m(\u001b[49m\u001b[32;43m1\u001b[39;49m\u001b[43m)\u001b[49m  \u001b[38;5;66;03m# func2 usa **, entonces solo recibe por palabra clave\u001b[39;00m\n",
      "\u001b[31mTypeError\u001b[39m: func2() takes 0 positional arguments but 1 was given"
     ]
    }
   ],
   "source": [
    "func2(1)  # func2 usa **, entonces solo recibe por palabra clave"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pero, es posible usarlas en simultáneo, además de establecer parámetros mínimos y por defecto:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: 1, b: 3, args: (), kwargs: {})\n",
      "a: 1, b: 2, args: (), kwargs: {})\n",
      "a: 1, b: 2, args: (3,), kwargs: {})\n",
      "a: 1, b: 2, args: (3, 4), kwargs: {})\n",
      "a: 1, b: 2, args: (3, 4), kwargs: {'c': 5, 'd': 6})\n",
      "a: 1, b: 2, args: (), kwargs: {'c': 3, 'd': 4})\n",
      "a: 1, b: 2, args: (), kwargs: {'c': 3, 'd': 4})\n"
     ]
    }
   ],
   "source": [
    "def func3(a, b=3, *args, **kwargs):\n",
    "    print(f'a: {a}, b: {b}, args: {args}, kwargs: {kwargs})')\n",
    "\n",
    "\n",
    "func3(1)\n",
    "func3(1, 2)\n",
    "func3(1, 2, 3)\n",
    "func3(1, 2, 3, 4)\n",
    "func3(1, 2, 3, 4, c=5, d=6)\n",
    "func3(1, b=2, c=3, d=4)\n",
    "func3(a=1, b=2, c=3, d=4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podemos ver que de cierta forma, al declarar parámetros específicos (como `a` y `b`) en conjunto a posicionales variables (`*args`), este último se lleva aquellos argumentos posicionales que no correspondan a los primeros. Mientras que `**kwargs` se lleva aquellos argumentos por palabra clave que no correspondan a otros parámetros declarados.  \n",
    "\n",
    "Es importante notar que este comportamiento es propio del uso de `*` y `**`, y no de las palabras `args` y `kwargs`. Estas últimas suelen utilizarse por convención para llamar a estos contenedores generados por `*` y `**`, pero **pueden tener cualquier nombre de variable en Python**. Además, su uso en la declaración de una función es único y en orden. Es decir, solo puede haber una declaración con `*`, solo una declaración de `**`, y la última no puede anteceder a la primera."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "* argument may appear only once (2297518307.py, line 1)",
     "output_type": "error",
     "traceback": [
      "  \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[17]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[31m    \u001b[39m\u001b[31mdef funcion_invalida(*a, *b): # Se repite uso de *\u001b[39m\n                             ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m * argument may appear only once\n"
     ]
    }
   ],
   "source": [
    "def funcion_invalida(*a, *b): # Se repite uso de *\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "arguments cannot follow var-keyword argument (4137009285.py, line 1)",
     "output_type": "error",
     "traceback": [
      "  \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[18]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[31m    \u001b[39m\u001b[31mdef funcion_invalida(**a, **b): # Se repite uso de **\u001b[39m\n                              ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m arguments cannot follow var-keyword argument\n"
     ]
    }
   ],
   "source": [
    "def funcion_invalida(**a, **b): # Se repite uso de **\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "arguments cannot follow var-keyword argument (3162352557.py, line 1)",
     "output_type": "error",
     "traceback": [
      "  \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[19]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[31m    \u001b[39m\u001b[31mdef funcion_invalida(**a, *b): # ** antecede a *\u001b[39m\n                              ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m arguments cannot follow var-keyword argument\n"
     ]
    }
   ],
   "source": [
    "def funcion_invalida(**a, *b): # ** antecede a *\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tampoco es válido declarar parámetros después del uso de `**`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "arguments cannot follow var-keyword argument (2689853194.py, line 1)",
     "output_type": "error",
     "traceback": [
      "  \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[20]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[31m    \u001b[39m\u001b[31mdef func(**palabras_clave, a):\u001b[39m\n                               ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m arguments cannot follow var-keyword argument\n"
     ]
    }
   ],
   "source": [
    "def func(**palabras_clave, a):\n",
    "    print(palabras_clave, a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pero sí es válido declarar parámetros entre `*` y `**`, pero estos solo pueden ser poblados por argumentos **por palabra clave**, no posicionales:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "def func4(*posicionales, arg1, arg2, **otras_palabras_clave):\n",
    "    print(f'posicionales: {posicionales}')\n",
    "    print(f'arg1: {arg1}')\n",
    "    print(f'arg2: {arg2}')\n",
    "    print(f'otras_palabras_clave: {otras_palabras_clave}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "posicionales: (1, 2, 3)\n",
      "arg1: hola\n",
      "arg2: mundo\n",
      "otras_palabras_clave: {}\n"
     ]
    }
   ],
   "source": [
    "func4(1, 2, 3, arg1='hola', arg2=\"mundo\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "posicionales: (1, 2, 3)\n",
      "arg1: hola\n",
      "arg2: mundo\n",
      "otras_palabras_clave: {'arg3': '¿qué tal?'}\n"
     ]
    }
   ],
   "source": [
    "func4(1, 2, 3, arg1='hola', arg2=\"mundo\", arg3=\"¿qué tal?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "posicionales: ()\n",
      "arg1: hola\n",
      "arg2: mundo\n",
      "otras_palabras_clave: {'arg3': '¿qué tal?'}\n"
     ]
    }
   ],
   "source": [
    "func4(arg1='hola', arg2=\"mundo\", arg3=\"¿qué tal?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "func4() missing 2 required keyword-only arguments: 'arg1' and 'arg2'",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mTypeError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[25]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mfunc4\u001b[49m\u001b[43m(\u001b[49m\u001b[32;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m3\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43marg3\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m¿qué tal?\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# ¡Faltan argumentos por palabra clave!\u001b[39;00m\n",
      "\u001b[31mTypeError\u001b[39m: func4() missing 2 required keyword-only arguments: 'arg1' and 'arg2'"
     ]
    }
   ],
   "source": [
    "func4(1, 2, 3, arg3=\"¿qué tal?\") # ¡Faltan argumentos por palabra clave!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este tipo de declaración permite separar entonces los parámetros y argumentos de funciones en tipos. Sea una función cualquiera:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "def funcion_general(arg1, arg2, *args, kwarg1, kwarg2, **kwargs):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `arg1` y `arg2` son posicionales **o** por palabra clave.\n",
    "- `args` contiene una cantidad **variable** de posicionales.\n",
    "- `kwarg1` y `kwarg2` son **solo** por palabra clave.\n",
    "- `kwargs` contiene una cantidad **variable** por palabra clave.\n",
    "\n",
    "Esto alza la pregunta. ¿Es posible especificar en declaración que un argumento solo puede declararse por posición? La respuesta depende de la versión de Python. Hasta Python 3.7 no era posible, pero tras una [actualización en Python 3.8](https://www.python.org/dev/peps/pep-0570/) es posible."
   ]
  }
 ],
 "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.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
