{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3ae804e1",
   "metadata": {},
   "source": [
    "# 05 · Python orientado a objetos\n",
    "\n",
    "Las clases permiten agrupar los datos con el comportamiento que les corresponde. El modelo de objetos de Python\n",
    "es flexible: casi todo, incluidos los números y las funciones, es un objeto, y tus propias clases pueden\n",
    "comportarse como los tipos integrados. Este módulo va desde los cuatro pilares clásicos hasta las\n",
    "características que el código profesional usa a diario.\n",
    "\n",
    "**Aprenderás**\n",
    "1. Los cuatro pilares: encapsulación, herencia, polimorfismo y abstracción\n",
    "2. El modelo de datos: métodos especiales (\"dunder\") que hacen que los objetos se puedan imprimir, comparar, recorrer y llamar\n",
    "3. Propiedades, métodos de clase y métodos estáticos\n",
    "4. `dataclasses`: clases para datos casi sin código repetitivo\n",
    "5. Clases base abstractas frente a `Protocol` (tipado estructural)\n",
    "6. Composición frente a herencia, `super()` y el orden de resolución de métodos\n",
    "7. `__slots__`: cuando importa la memoria por objeto"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbcafbb9",
   "metadata": {},
   "source": [
    "## 1. Los cuatro pilares\n",
    "La **encapsulación** mantiene juntos los datos y las reglas que los protegen. Python no tiene atributos\n",
    "realmente privados; un guion bajo inicial es la convención para \"interno, no tocar\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "68351a8a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.775103Z",
     "iopub.status.busy": "2026-10-18T10:10:19.774599Z",
     "iopub.status.idle": "2026-10-18T10:10:19.790198Z",
     "shell.execute_reply": "2026-10-18T10:10:19.788271Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "120\n",
      "no se puede asignar directamente: property 'balance' of 'BankAccount' object has no setter\n"
     ]
    }
   ],
   "source": [
    "class BankAccount:\n",
    "    def __init__(self, owner: str, balance: float = 0.0):\n",
    "        self.owner = owner\n",
    "        self._balance = balance                     # interno por convención\n",
    "\n",
    "    def deposit(self, amount: float) -> None:\n",
    "        if amount <= 0:\n",
    "            raise ValueError(\"el depósito debe ser positivo\")\n",
    "        self._balance += amount\n",
    "\n",
    "    def withdraw(self, amount: float) -> None:\n",
    "        if amount > self._balance:\n",
    "            raise ValueError(\"fondos insuficientes\")\n",
    "        self._balance -= amount\n",
    "\n",
    "    @property\n",
    "    def balance(self) -> float:                      # solo lectura desde fuera\n",
    "        return self._balance\n",
    "\n",
    "acct = BankAccount(\"Alice\", 100)\n",
    "acct.deposit(50); acct.withdraw(30)\n",
    "print(acct.balance)\n",
    "try:\n",
    "    acct.balance = 1_000_000\n",
    "except AttributeError as e:\n",
    "    print(\"no se puede asignar directamente:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41ea3fb3",
   "metadata": {},
   "source": [
    "La **herencia** reutiliza y amplía una clase base; el **polimorfismo** significa que el código puede llamar al\n",
    "mismo método sobre tipos distintos y obtener el comportamiento correcto para cada uno."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "5cc55c69",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.795421Z",
     "iopub.status.busy": "2026-10-18T10:10:19.794835Z",
     "iopub.status.idle": "2026-10-18T10:10:19.808019Z",
     "shell.execute_reply": "2026-10-18T10:10:19.806026Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rex: woof\n",
      "Tom: meow\n",
      "Fido: woof\n",
      "True True\n"
     ]
    }
   ],
   "source": [
    "class Animal:\n",
    "    def __init__(self, name: str):\n",
    "        self.name = name\n",
    "    def speak(self) -> str:\n",
    "        raise NotImplementedError\n",
    "\n",
    "class Dog(Animal):\n",
    "    def speak(self) -> str:\n",
    "        return f\"{self.name}: woof\"\n",
    "\n",
    "class Cat(Animal):\n",
    "    def speak(self) -> str:\n",
    "        return f\"{self.name}: meow\"\n",
    "\n",
    "for pet in [Dog(\"Rex\"), Cat(\"Tom\"), Dog(\"Fido\")]:\n",
    "    print(pet.speak())                    # una llamada, el comportamiento correcto para cada tipo\n",
    "print(isinstance(Dog(\"x\"), Animal), issubclass(Cat, Animal))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5159c1c0",
   "metadata": {},
   "source": [
    "La **abstracción** define una interfaz y oculta la implementación. Una clase base abstracta no se puede\n",
    "instanciar y obliga a las subclases a implementar sus métodos abstractos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "7132fe73",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.812847Z",
     "iopub.status.busy": "2026-10-18T10:10:19.812302Z",
     "iopub.status.idle": "2026-10-18T10:10:19.826395Z",
     "shell.execute_reply": "2026-10-18T10:10:19.824486Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Circle con área 3.14', 'Square con área 4.00']\n",
      "abstracta: Can't instantiate abstract class Shape without an implementation for abstract method 'area'\n"
     ]
    }
   ],
   "source": [
    "from abc import ABC, abstractmethod\n",
    "import math\n",
    "\n",
    "class Shape(ABC):\n",
    "    @abstractmethod\n",
    "    def area(self) -> float: ...\n",
    "\n",
    "    def describe(self) -> str:            # método concreto construido sobre el abstracto\n",
    "        return f\"{type(self).__name__} con área {self.area():.2f}\"\n",
    "\n",
    "class Circle(Shape):\n",
    "    def __init__(self, r): self.r = r\n",
    "    def area(self): return math.pi * self.r ** 2\n",
    "\n",
    "class Square(Shape):\n",
    "    def __init__(self, side): self.side = side\n",
    "    def area(self): return self.side ** 2\n",
    "\n",
    "print([s.describe() for s in (Circle(1), Square(2))])\n",
    "try:\n",
    "    Shape()\n",
    "except TypeError as e:\n",
    "    print(\"abstracta:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c033906",
   "metadata": {},
   "source": [
    "## 2. El modelo de datos: métodos especiales\n",
    "Python llama a métodos con nombres especiales para las operaciones integradas: `len(x)` llama a\n",
    "`x.__len__()`, `a + b` llama a `a.__add__(b)` y `for item in x` llama a `x.__iter__()`. Implementarlos hace\n",
    "que tus clases se sientan nativas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ecf6ed36",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.831040Z",
     "iopub.status.busy": "2026-10-18T10:10:19.830494Z",
     "iopub.status.idle": "2026-10-18T10:10:19.848851Z",
     "shell.execute_reply": "2026-10-18T10:10:19.846768Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector(4, 5) (9, 12) 5.0 True [Vector(1, 1), Vector(3, 4)] 3 4 False\n",
      "{Vector(3, 4)}\n"
     ]
    }
   ],
   "source": [
    "from functools import total_ordering\n",
    "\n",
    "@total_ordering                          # deduce <=, >, >= a partir de __eq__ y __lt__\n",
    "class Vector:\n",
    "    def __init__(self, x: float, y: float):\n",
    "        self.x, self.y = x, y\n",
    "    def __repr__(self):  return f\"Vector({self.x}, {self.y})\"         # para desarrolladores\n",
    "    def __str__(self):   return f\"({self.x}, {self.y})\"                # para usuarios\n",
    "    def __add__(self, o): return Vector(self.x + o.x, self.y + o.y)\n",
    "    def __mul__(self, k): return Vector(self.x * k, self.y * k)\n",
    "    __rmul__ = __mul__                                                  # para que 3 * v también funcione\n",
    "    def __abs__(self):   return math.hypot(self.x, self.y)\n",
    "    def __eq__(self, o): return (self.x, self.y) == (o.x, o.y)\n",
    "    def __lt__(self, o): return abs(self) < abs(o)\n",
    "    def __hash__(self):  return hash((self.x, self.y))\n",
    "    def __iter__(self):  yield from (self.x, self.y)                   # permite el desempaquetado\n",
    "    def __bool__(self):  return bool(abs(self))\n",
    "\n",
    "v, w = Vector(3, 4), Vector(1, 1)\n",
    "x, y = v\n",
    "print(repr(v + w), str(3 * v), abs(v), v > w, sorted([v, w]), x, y, bool(Vector(0, 0)))\n",
    "print({v, Vector(3, 4)})                 # los objetos iguales se funden en un conjunto gracias a __hash__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "307a6d9d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.853662Z",
     "iopub.status.busy": "2026-10-18T10:10:19.853278Z",
     "iopub.status.idle": "2026-10-18T10:10:19.862848Z",
     "shell.execute_reply": "2026-10-18T10:10:19.861302Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4 verse ['verse', 'chorus'] True ['INTRO', 'VERSE', 'CHORUS', 'OUTRO']\n",
      "42 [2, 4, 6]\n"
     ]
    }
   ],
   "source": [
    "class Playlist:\n",
    "    '''Un contenedor: admite len(), índices, rebanadas, 'in' e iteración.'''\n",
    "    def __init__(self, songs): self._songs = list(songs)\n",
    "    def __len__(self): return len(self._songs)\n",
    "    def __getitem__(self, i): return self._songs[i]\n",
    "    def __contains__(self, song): return song in self._songs\n",
    "\n",
    "class Multiplier:\n",
    "    '''Un objeto invocable: sus instancias se usan como funciones.'''\n",
    "    def __init__(self, k): self.k = k\n",
    "    def __call__(self, x): return x * self.k\n",
    "\n",
    "p = Playlist([\"intro\", \"verse\", \"chorus\", \"outro\"])\n",
    "print(len(p), p[1], p[1:3], \"chorus\" in p, [s.upper() for s in p])\n",
    "double = Multiplier(2)\n",
    "print(double(21), list(map(double, [1, 2, 3])))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a14523d0",
   "metadata": {},
   "source": [
    "## 3. Propiedades, métodos de clase y métodos estáticos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3584c7f1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.867882Z",
     "iopub.status.busy": "2026-10-18T10:10:19.867433Z",
     "iopub.status.idle": "2026-10-18T10:10:19.879813Z",
     "shell.execute_reply": "2026-10-18T10:10:19.878104Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100.0 212.0 True\n",
      "por debajo del cero absoluto\n"
     ]
    }
   ],
   "source": [
    "class Temperature:\n",
    "    def __init__(self, celsius: float):\n",
    "        self.celsius = celsius                 # pasa por el setter de abajo\n",
    "\n",
    "    @property\n",
    "    def celsius(self) -> float:\n",
    "        return self._c\n",
    "\n",
    "    @celsius.setter\n",
    "    def celsius(self, value: float) -> None:   # validación en cada asignación\n",
    "        if value < -273.15:\n",
    "            raise ValueError(\"por debajo del cero absoluto\")\n",
    "        self._c = value\n",
    "\n",
    "    @property\n",
    "    def fahrenheit(self) -> float:             # atributo calculado\n",
    "        return self._c * 9 / 5 + 32\n",
    "\n",
    "    @classmethod\n",
    "    def from_fahrenheit(cls, f: float) -> \"Temperature\":   # constructor alternativo\n",
    "        return cls((f - 32) * 5 / 9)\n",
    "\n",
    "    @staticmethod\n",
    "    def is_freezing(celsius: float) -> bool:   # función auxiliar relacionada, no necesita ni instancia ni clase\n",
    "        return celsius <= 0\n",
    "\n",
    "t = Temperature.from_fahrenheit(212)\n",
    "print(t.celsius, t.fahrenheit, Temperature.is_freezing(-3))\n",
    "try:\n",
    "    t.celsius = -300\n",
    "except ValueError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cbd9b15",
   "metadata": {},
   "source": [
    "## 4. Dataclasses\n",
    "La mayoría de las clases sobre todo guardan datos. `@dataclass` escribe por ti `__init__`, `__repr__` y\n",
    "`__eq__` y, si quieres, orden, inmutabilidad (`frozen=True`) y slots para ahorrar memoria."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b4ce6c93",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.884234Z",
     "iopub.status.busy": "2026-10-18T10:10:19.883652Z",
     "iopub.status.idle": "2026-10-18T10:10:19.897944Z",
     "shell.execute_reply": "2026-10-18T10:10:19.896035Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Version(major=1, minor=9, patch=3), Version(major=2, minor=0, patch=5), Version(major=2, minor=1, patch=0)]\n",
      "Experiment(name='baseline', learning_rate=0.001, tags=['cpu']) {'name': 'baseline', 'learning_rate': 0.001, 'tags': ['cpu']} Experiment(name='baseline', learning_rate=0.01, tags=['cpu'])\n"
     ]
    }
   ],
   "source": [
    "from dataclasses import dataclass, field, asdict, replace\n",
    "\n",
    "@dataclass(frozen=True, order=True)\n",
    "class Version:\n",
    "    major: int\n",
    "    minor: int = 0\n",
    "    patch: int = 0\n",
    "    def __str__(self): return f\"{self.major}.{self.minor}.{self.patch}\"\n",
    "\n",
    "@dataclass\n",
    "class Experiment:\n",
    "    name: str\n",
    "    learning_rate: float = 1e-3\n",
    "    tags: list[str] = field(default_factory=list)      # nunca un valor mutable por defecto directamente\n",
    "    def __post_init__(self):\n",
    "        if self.learning_rate <= 0:\n",
    "            raise ValueError(\"learning_rate debe ser positivo\")\n",
    "\n",
    "print(sorted([Version(2, 1), Version(1, 9, 3), Version(2, 0, 5)]))\n",
    "e = Experiment(\"baseline\", tags=[\"cpu\"])\n",
    "print(e, asdict(e), replace(e, learning_rate=0.01))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "025dd0a4",
   "metadata": {},
   "source": [
    "## 5. Clases base abstractas frente a Protocols\n",
    "Una ABC exige que las clases **hereden** de ella (tipado nominal). Un `Protocol` solo exige que **tengan los\n",
    "métodos adecuados** (tipado estructural, el \"duck typing\" que entienden los comprobadores de tipos). Los\n",
    "protocolos permiten describir lo que necesitas de objetos que no controlas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "d5b53722",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.904235Z",
     "iopub.status.busy": "2026-10-18T10:10:19.903829Z",
     "iopub.status.idle": "2026-10-18T10:10:19.913588Z",
     "shell.execute_reply": "2026-10-18T10:10:19.911726Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "13.141592653589793 True\n"
     ]
    }
   ],
   "source": [
    "from typing import Protocol, runtime_checkable\n",
    "\n",
    "@runtime_checkable\n",
    "class SupportsArea(Protocol):\n",
    "    def area(self) -> float: ...\n",
    "\n",
    "class Rectangle:                      # NO hereda de nada\n",
    "    def __init__(self, w, h): self.w, self.h = w, h\n",
    "    def area(self): return self.w * self.h\n",
    "\n",
    "def total_area(items: list[SupportsArea]) -> float:\n",
    "    return sum(i.area() for i in items)\n",
    "\n",
    "shapes = [Rectangle(2, 3), Circle(1), Square(2)]\n",
    "print(total_area(shapes), all(isinstance(s, SupportsArea) for s in shapes))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6da172e4",
   "metadata": {},
   "source": [
    "## 6. Composición frente a herencia, y `super()`\n",
    "La herencia modela \"es un\"; la composición modela \"tiene un\". Las jerarquías de herencia profundas se vuelven\n",
    "difíciles de cambiar, así que es preferible darle a un objeto las piezas que necesita."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "f0780e53",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.918596Z",
     "iopub.status.busy": "2026-10-18T10:10:19.918184Z",
     "iopub.status.idle": "2026-10-18T10:10:19.927051Z",
     "shell.execute_reply": "2026-10-18T10:10:19.925918Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "motor (150 CV) arrancado; calculando ruta a Madrid\n"
     ]
    }
   ],
   "source": [
    "class Engine:\n",
    "    def __init__(self, hp): self.hp = hp\n",
    "    def start(self): return f\"motor ({self.hp} CV) arrancado\"\n",
    "\n",
    "class GPS:\n",
    "    def route(self, to): return f\"calculando ruta a {to}\"\n",
    "\n",
    "class Car:                                  # un coche TIENE un motor y un GPS\n",
    "    def __init__(self, engine: Engine, gps: GPS | None = None):\n",
    "        self.engine, self.gps = engine, gps\n",
    "    def drive(self, to):\n",
    "        steps = [self.engine.start()]\n",
    "        if self.gps:\n",
    "            steps.append(self.gps.route(to))\n",
    "        return \"; \".join(steps)\n",
    "\n",
    "print(Car(Engine(150), GPS()).drive(\"Madrid\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "8483e882",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.933635Z",
     "iopub.status.busy": "2026-10-18T10:10:19.933235Z",
     "iopub.status.idle": "2026-10-18T10:10:19.943043Z",
     "shell.execute_reply": "2026-10-18T10:10:19.941238Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Service', 'Logging', 'Caching', 'Base', 'object']\n",
      "['Base', 'Caching', 'Logging', 'Service']\n"
     ]
    }
   ],
   "source": [
    "class Base:\n",
    "    def setup(self): return [\"Base\"]\n",
    "class Logging(Base):\n",
    "    def setup(self): return super().setup() + [\"Logging\"]\n",
    "class Caching(Base):\n",
    "    def setup(self): return super().setup() + [\"Caching\"]\n",
    "class Service(Logging, Caching):           # herencia múltiple\n",
    "    def setup(self): return super().setup() + [\"Service\"]\n",
    "\n",
    "print([c.__name__ for c in Service.__mro__])   # orden de resolución de métodos\n",
    "print(Service().setup())                        # super() sigue el MRO: cada clase se ejecuta una vez"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6bc968ce",
   "metadata": {},
   "source": [
    "## 7. `__slots__`: memoria por objeto\n",
    "Cada objeto normal lleva un `__dict__` para sus atributos. Con `__slots__` los atributos se guardan en huecos\n",
    "fijos, lo que ahorra memoria cuando creas millones de objetos pequeños."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "01068092",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-18T10:10:19.947496Z",
     "iopub.status.busy": "2026-10-18T10:10:19.947164Z",
     "iopub.status.idle": "2026-10-18T10:10:21.448171Z",
     "shell.execute_reply": "2026-10-18T10:10:21.446208Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "por objeto: normal 120 bytes, con __slots__ 88 bytes\n"
     ]
    }
   ],
   "source": [
    "import tracemalloc\n",
    "\n",
    "class PointDict:\n",
    "    def __init__(self, x, y): self.x, self.y = x, y\n",
    "\n",
    "class PointSlots:\n",
    "    __slots__ = (\"x\", \"y\")\n",
    "    def __init__(self, x, y): self.x, self.y = x, y\n",
    "\n",
    "def measure(cls, n=200_000):\n",
    "    tracemalloc.start()\n",
    "    objs = [cls(i, i) for i in range(n)]\n",
    "    current, _ = tracemalloc.get_traced_memory()\n",
    "    tracemalloc.stop()\n",
    "    return current / n\n",
    "\n",
    "print(f\"por objeto: normal {measure(PointDict):.0f} bytes, con __slots__ {measure(PointSlots):.0f} bytes\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65e88508",
   "metadata": {},
   "source": [
    "## Resumen\n",
    "| Herramienta | Úsala cuando |\n",
    "|---|---|\n",
    "| clase normal | comportamiento más estado con reglas (una cuenta, una conexión) |\n",
    "| `@dataclass` | sobre todo datos (registros, configuración, resultados) |\n",
    "| `@property` | validación o atributos calculados con sintaxis de atributo |\n",
    "| `@classmethod` | constructores alternativos (`from_json`, `from_fahrenheit`) |\n",
    "| ABC | una familia de clases propias que debe implementar una interfaz |\n",
    "| `Protocol` | describir lo que necesitas de cualquier objeto, incluidos los de terceros |\n",
    "| composición | combinar capacidades sin herencia profunda |\n",
    "| `__slots__` | millones de objetos pequeños |\n",
    "\n",
    "## Ejercicios\n",
    "1. Haz que `Vector` admita la resta y el producto escalar (`v @ w` llama a `__matmul__`).\n",
    "2. Escribe una dataclass inmutable `Money(amount, currency)` cuyo `+` lance un error si las monedas son distintas.\n",
    "3. Sustituye la jerarquía de `Animal` por un `Protocol` y comprueba que una clase que no hereda de nada sigue funcionando."
   ]
  }
 ],
 "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.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
