{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cb6217e7",
   "metadata": {},
   "source": [
    "[← Anterior](03_estructuras_de_datos.ipynb) · [Índice](README.md) · [Siguiente →](05_entrada_salida.ipynb)\n",
    "\n",
    "# 04 · Funciones\n",
    "\n",
    "**Audiencia.** Personas que ya han visto algo de programación y quieren repasar Python de forma práctica.  \n",
    "**Prerrequisitos.** Tipos, estructuras de datos y control del flujo.  \n",
    "**Duración orientativa.** 60–90 minutos, incluida la práctica.\n",
    "\n",
    "## Objetivos de aprendizaje\n",
    "\n",
    "- Definir funciones pequeñas con entradas y salidas claras.\n",
    "- Manejar parámetros posicionales, nombrados, variables y predeterminados.\n",
    "- Comprender alcance, anotaciones, funciones de orden superior y cierres.\n",
    "- Reconocer decoradores, generadores y recursión.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "baa05e15",
   "metadata": {},
   "source": [
    "## Itinerario\n",
    "\n",
    "1. Definición y retorno\n",
    "2. Parámetros\n",
    "3. Alcance\n",
    "4. Funciones como valores\n",
    "5. Decoradores\n",
    "6. Generadores\n",
    "7. Recursión\n",
    "8. Práctica\n",
    "\n",
    "> **Cómo trabajar:** ejecuta las celdas en orden, predice el resultado antes de verlo y modifica los ejemplos. Cada bloque termina con un ejercicio, un punto de partida y una solución posible.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3cd2f673",
   "metadata": {},
   "source": [
    "## 1. Definir, llamar y devolver\n",
    "\n",
    "Una función agrupa una responsabilidad. `return` entrega un resultado y termina la ejecución; sin `return` explícito, devuelve `None`. Una función puede devolver varios valores empaquetados en una tupla.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2d40476d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.039543Z",
     "iopub.status.busy": "2026-08-02T22:29:48.039440Z",
     "iopub.status.idle": "2026-08-02T22:29:48.042890Z",
     "shell.execute_reply": "2026-08-02T22:29:48.042634Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10.0\n",
      "3 2\n"
     ]
    }
   ],
   "source": [
    "def area_rectangulo(ancho, alto):\n",
    "    return ancho * alto\n",
    "\n",
    "area = area_rectangulo(4, 2.5)\n",
    "print(area)\n",
    "\n",
    "def cociente_y_resto(dividendo, divisor):\n",
    "    return dividendo // divisor, dividendo % divisor\n",
    "\n",
    "cociente, resto = cociente_y_resto(17, 5)\n",
    "print(cociente, resto)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f0c6cf2",
   "metadata": {},
   "source": [
    "## 2. Parámetros y argumentos\n",
    "\n",
    "Los parámetros aparecen en la definición; los argumentos son los valores usados al llamar. Los argumentos con nombre mejoran la lectura. Los valores predeterminados deben ir tras los obligatorios.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1d909d7e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.044497Z",
     "iopub.status.busy": "2026-08-02T22:29:48.044376Z",
     "iopub.status.idle": "2026-08-02T22:29:48.046871Z",
     "shell.execute_reply": "2026-08-02T22:29:48.046640Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hola, Irene\n",
      "Bienvenida, Irene. Puntuación: 9\n"
     ]
    }
   ],
   "source": [
    "def presentar(nombre, saludo=\"Hola\", puntuacion=None):\n",
    "    texto = f\"{saludo}, {nombre}\"\n",
    "    if puntuacion is not None:\n",
    "        texto += f\". Puntuación: {puntuacion}\"\n",
    "    return texto\n",
    "\n",
    "print(presentar(\"Irene\"))\n",
    "print(presentar(nombre=\"Irene\", saludo=\"Bienvenida\", puntuacion=9))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41155548",
   "metadata": {},
   "source": [
    "**Trampa importante:** un valor predeterminado mutable se crea una sola vez. Usa `None` y crea la colección dentro.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ff4a6be5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.048430Z",
     "iopub.status.busy": "2026-08-02T22:29:48.048315Z",
     "iopub.status.idle": "2026-08-02T22:29:48.050508Z",
     "shell.execute_reply": "2026-08-02T22:29:48.050266Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['inicio']\n",
      "['fin']\n"
     ]
    }
   ],
   "source": [
    "def agregar_evento(evento, historial=None):\n",
    "    if historial is None:\n",
    "        historial = []\n",
    "    historial.append(evento)\n",
    "    return historial\n",
    "\n",
    "print(agregar_evento(\"inicio\"))\n",
    "print(agregar_evento(\"fin\"))  # lista independiente\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "299d3681",
   "metadata": {},
   "source": [
    "`/` marca parámetros solo posicionales y `*` marca los que deben escribirse por nombre. `*args` reúne argumentos posicionales extra en una tupla; `**kwargs`, argumentos nombrados extra en un diccionario.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "abb61ac0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.051865Z",
     "iopub.status.busy": "2026-08-02T22:29:48.051762Z",
     "iopub.status.idle": "2026-08-02T22:29:48.054077Z",
     "shell.execute_reply": "2026-08-02T22:29:48.053866Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "68.0\n",
      "{'etiqueta': 'serie', 'cantidad': 3, 'suma': 16, 'metadatos': {'unidad': 'kg'}}\n"
     ]
    }
   ],
   "source": [
    "def convertir(valor, /, *, factor=1.0, desplazamiento=0.0):\n",
    "    return valor * factor + desplazamiento\n",
    "\n",
    "def resumen(*valores, etiqueta=\"datos\", **metadatos):\n",
    "    return {\n",
    "        \"etiqueta\": etiqueta,\n",
    "        \"cantidad\": len(valores),\n",
    "        \"suma\": sum(valores),\n",
    "        \"metadatos\": metadatos,\n",
    "    }\n",
    "\n",
    "print(convertir(20, factor=1.8, desplazamiento=32))\n",
    "print(resumen(3, 5, 8, etiqueta=\"serie\", unidad=\"kg\"))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81a1c650",
   "metadata": {},
   "source": [
    "## 3. Documentación, tipos y alcance\n",
    "\n",
    "Una *docstring* explica el contrato. Las anotaciones de tipo ayudan a lectores y herramientas, pero Python no las valida automáticamente. El alcance sigue la regla LEGB: local, función envolvente, global y built-ins.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "356ad154",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.055519Z",
     "iopub.status.busy": "2026-08-02T22:29:48.055407Z",
     "iopub.status.idle": "2026-08-02T22:29:48.057583Z",
     "shell.execute_reply": "2026-08-02T22:29:48.057363Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "108.90 €\n",
      "{'precio': <class 'float'>, 'descuento': <class 'float'>, 'return': <class 'float'>}\n"
     ]
    }
   ],
   "source": [
    "TASA_IVA = 0.21\n",
    "\n",
    "def precio_final(precio: float, descuento: float = 0.0) -> float:\n",
    "    '''Calcula el precio tras descuento e IVA; los porcentajes van de 0 a 1.'''\n",
    "    base = precio * (1 - descuento)\n",
    "    return base * (1 + TASA_IVA)\n",
    "\n",
    "print(f\"{precio_final(100, 0.10):.2f} €\")\n",
    "print(precio_final.__annotations__)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22038ff8",
   "metadata": {},
   "source": [
    "Evita modificar globales desde funciones. Un cierre puede conservar estado de la función envolvente; `nonlocal` permite actualizarlo de manera explícita.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "2cdb857d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.058941Z",
     "iopub.status.busy": "2026-08-02T22:29:48.058849Z",
     "iopub.status.idle": "2026-08-02T22:29:48.060920Z",
     "shell.execute_reply": "2026-08-02T22:29:48.060677Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "11 16 17\n"
     ]
    }
   ],
   "source": [
    "def crear_contador(inicio=0):\n",
    "    valor = inicio\n",
    "\n",
    "    def incrementar(paso=1):\n",
    "        nonlocal valor\n",
    "        valor += paso\n",
    "        return valor\n",
    "\n",
    "    return incrementar\n",
    "\n",
    "contador = crear_contador(10)\n",
    "print(contador(), contador(5), contador())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b201170f",
   "metadata": {},
   "source": [
    "## 4. Funciones como valores\n",
    "\n",
    "Las funciones se pueden guardar, pasar y devolver. Esto permite separar una transformación de los datos sobre los que se aplica. Las `lambda` son funciones anónimas breves; para lógica compleja, usa `def`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "31fb3083",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.062257Z",
     "iopub.status.busy": "2026-08-02T22:29:48.062164Z",
     "iopub.status.idle": "2026-08-02T22:29:48.064352Z",
     "shell.execute_reply": "2026-08-02T22:29:48.064138Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2, 4, 6]\n",
      "[('Leo', 10), ('Ana', 8), ('Eva', 7)]\n"
     ]
    }
   ],
   "source": [
    "def aplicar(operacion, valores):\n",
    "    return [operacion(valor) for valor in valores]\n",
    "\n",
    "def doble(numero):\n",
    "    return numero * 2\n",
    "\n",
    "print(aplicar(doble, [1, 2, 3]))\n",
    "print(sorted([(\"Ana\", 8), (\"Leo\", 10), (\"Eva\", 7)], key=lambda item: item[1], reverse=True))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c052e78a",
   "metadata": {},
   "source": [
    "## 5. Decoradores\n",
    "\n",
    "Un decorador recibe una función y devuelve otra. Se usa para añadir comportamiento transversal —registro, autorización, caché— sin cambiar el cuerpo original. `functools.wraps` conserva nombre y documentación.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "78fdeb9a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.065735Z",
     "iopub.status.busy": "2026-08-02T22:29:48.065646Z",
     "iopub.status.idle": "2026-08-02T22:29:48.069048Z",
     "shell.execute_reply": "2026-08-02T22:29:48.068812Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Llamando a sumar\n",
      "Resultado: 11\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "11"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from functools import wraps\n",
    "\n",
    "def registrar_llamada(funcion):\n",
    "    @wraps(funcion)\n",
    "    def envoltorio(*args, **kwargs):\n",
    "        print(f\"Llamando a {funcion.__name__}\")\n",
    "        resultado = funcion(*args, **kwargs)\n",
    "        print(f\"Resultado: {resultado}\")\n",
    "        return resultado\n",
    "    return envoltorio\n",
    "\n",
    "@registrar_llamada\n",
    "def sumar(a, b):\n",
    "    return a + b\n",
    "\n",
    "sumar(4, 7)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c8edcb2",
   "metadata": {},
   "source": [
    "## 6. Generadores\n",
    "\n",
    "Una función con `yield` produce valores de uno en uno y conserva su estado. Es útil para secuencias grandes o potencialmente infinitas porque no necesita almacenarlas completas.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b2aaa4d6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.070350Z",
     "iopub.status.busy": "2026-08-02T22:29:48.070275Z",
     "iopub.status.idle": "2026-08-02T22:29:48.071921Z",
     "shell.execute_reply": "2026-08-02T22:29:48.071712Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4\n",
      "[3, 2, 1]\n"
     ]
    }
   ],
   "source": [
    "def cuenta_atras(desde):\n",
    "    actual = desde\n",
    "    while actual > 0:\n",
    "        yield actual\n",
    "        actual -= 1\n",
    "\n",
    "generador = cuenta_atras(4)\n",
    "print(next(generador))\n",
    "print(list(generador))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9c6bf23",
   "metadata": {},
   "source": [
    "## 7. Recursión\n",
    "\n",
    "Una función recursiva se llama a sí misma y necesita un caso base. En Python, una versión iterativa suele ser más eficiente y evita el límite de recursión, pero la recursión expresa bien árboles y problemas divisibles.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "305e249a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.073106Z",
     "iopub.status.busy": "2026-08-02T22:29:48.073037Z",
     "iopub.status.idle": "2026-08-02T22:29:48.074899Z",
     "shell.execute_reply": "2026-08-02T22:29:48.074685Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "720\n"
     ]
    }
   ],
   "source": [
    "def factorial(n):\n",
    "    if n < 0:\n",
    "        raise ValueError(\"n debe ser no negativo\")\n",
    "    if n in (0, 1):\n",
    "        return 1\n",
    "    return n * factorial(n - 1)\n",
    "\n",
    "print(factorial(6))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a537b59c",
   "metadata": {},
   "source": [
    "## Errores habituales\n",
    "\n",
    "- Hacer que una función imprima cuando debería devolver un valor reutilizable.\n",
    "- Usar listas o diccionarios como valores predeterminados.\n",
    "- Crear funciones enormes con múltiples responsabilidades.\n",
    "- Confiar en anotaciones como si validaran datos en ejecución.\n",
    "\n",
    "**Extensión opcional:** estudia `functools.partial`, `functools.lru_cache` y protocolos de tipado.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5062059f",
   "metadata": {},
   "source": [
    "## Práctica · Canalización de datos\n",
    "\n",
    "Crea `procesar(numeros, transformacion, filtro=None)` que:\n",
    "\n",
    "1. conserve todos los números si no se proporciona filtro;\n",
    "2. filtre cuando reciba una función predicado;\n",
    "3. aplique la transformación a los valores restantes;\n",
    "4. devuelva una lista, sin imprimir.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "e7ba2e9a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.076175Z",
     "iopub.status.busy": "2026-08-02T22:29:48.076091Z",
     "iopub.status.idle": "2026-08-02T22:29:48.077646Z",
     "shell.execute_reply": "2026-08-02T22:29:48.077368Z"
    },
    "tags": [
     "ejercicio"
    ]
   },
   "outputs": [],
   "source": [
    "def procesar(numeros, transformacion, filtro=None):\n",
    "    # TODO: filtra opcionalmente y transforma.\n",
    "    pass\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "85719d04",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:48.078868Z",
     "iopub.status.busy": "2026-08-02T22:29:48.078791Z",
     "iopub.status.idle": "2026-08-02T22:29:48.081128Z",
     "shell.execute_reply": "2026-08-02T22:29:48.080882Z"
    },
    "tags": [
     "solucion"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 4, 16, 36]\n"
     ]
    }
   ],
   "source": [
    "def procesar(numeros, transformacion, filtro=None):\n",
    "    seleccionados = numeros if filtro is None else (n for n in numeros if filtro(n))\n",
    "    return [transformacion(n) for n in seleccionados]\n",
    "\n",
    "resultado = procesar(range(8), lambda n: n ** 2, lambda n: n % 2 == 0)\n",
    "print(resultado)\n",
    "assert resultado == [0, 4, 16, 36]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6453abd",
   "metadata": {},
   "source": [
    "            ## Comprobación rápida\n",
    "\n",
    "            - [ ] Mis funciones reciben datos y devuelven resultados claros.\n",
    "- [ ] Sé evitar parámetros mutables compartidos.\n",
    "- [ ] Puedo explicar para qué sirven cierres, decoradores y generadores.\n",
    "\n",
    "            [← Anterior](03_estructuras_de_datos.ipynb) · [Volver al índice](README.md) · [Siguiente →](05_entrada_salida.ipynb)\n"
   ]
  }
 ],
 "metadata": {
  "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.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
