{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## 8. Clases (crear, herencia y métodos mágicos)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "La forma más sencilla de definir una clase sería:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class ClassName:\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Cuando se entra en la definición de una clase, se crea un nuevo namespace, y se accede a él desde el scope local, por lo que toda asignación o definición que se haga, quedará será accesible sólo desde ese scope local.\n", "\n", "Cuando se define una clase decimos que se crea un objeto clase, que se queda asociado en el namespace al nombre que le hemos dado a la clase, en nuestro caso `ClassName`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Un objeto clase soporta dos tipos de operaciones:\n", "\n", "- referencia a atributos, mediante el operador `.`\n", "- instanciación, usando los paréntesis, de la misma forma que se usan en una función" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "42\n" ] } ], "source": [ "class MyClass:\n", " \"\"\"A simple example class\"\"\"\n", " i = 12345\n", "\n", " def f(self):\n", " return 'hello world'\n", " def g(self, value):\n", " self.i = value\n", "\n", "x = MyClass()\n", "x.g(42)\n", "print(x.i)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "En este ejemplo, `MyClass.i` sería una referencia válida a un atributo de clase, y `MyClass.f` sería una referencia válida a un método.\n", "\n", "Para crear una nueva instancia de la clase, simplemente usamos la notación de la función:\n", "\n", "```python\n", "x = MyClass()\n", "```\n", "\n", "Esto crea una nueva instancia de la clase, y guarda este objeto en la variable local x." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "42\n", "27\n" ] } ], "source": [ "class MyClass:\n", " i = 123\n", "x = MyClass() # nueva instancia de MyClass\n", "y = MyClass()\n", "x.i = 42\n", "MyClass.i = 27\n", "print(x.i)\n", "print(y.i)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Atributos y métodos, la declaración, el self, los métodos estáticos, de clase, etc." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Cuando instanciamos una clase podemos hacer que la nueva instancia tenga un estado inicial concreto.\n", "\n", "Para ello debemos definir un método `__init__()`." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4435586128\n", "4435586240\n", "{}\n", "{}\n" ] }, { "ename": "AttributeError", "evalue": "'MyClass' object has no attribute 'data2'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'MyClass' object has no attribute 'data2'" ] } ], "source": [ "class MyClass:\n", " def __init__(self):\n", " self.data = []\n", "\n", " def add(self, x):\n", " self.data.append(x)\n", " def f(self):\n", " print(self.data2)\n", "\n", "x = MyClass()\n", "y = MyClass()\n", "print(id(x))\n", "print(id(y))\n", "x.add(1)\n", "y.add(2)\n", "x.data2=dict()\n", "x.f = \n", "\n", "print(x.data2)\n", "print(y.data2)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "- Siempre que se instancia una clase, se llama al método `__init__`. \n", "- El primer argumento que recibe este método, así como en todos los métodos de la clase es `self`. \n", "- El argumento `self` hace referencia siempre a la instancia que está ejecutando el método en cada instante." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "0\n" ] } ], "source": [ "class MyClass:\n", "\n", " def __init__(self):\n", " self.data = []\n", "\n", " def size(self):\n", " return len(self.data)\n", "\n", "\n", "x = MyClass() # al llamar a __init__, self hace referencia a la intancia recien creada que se asociará al nombre x\n", "x.data.append(\"a\")\n", "y = MyClass() # y tendrá una nueva lista vacía en data\n", "print(x.size()) # muestra 1\n", "print(y.size()) # muestra 0" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Este método `__init__` también acepta argumentos, que pueden ser pasados cuando se crea una nueva instancia de la clase, como si se trataran de los argumentos de una función.\n", "\n", "A un objeto instancia se le pueden agregar nuevos atributos o métodos de forma dinámica, simplemente referenciando un nuevo nombre con el operador `.`. No hace falta que estén declarados o inicializados en el `__init__`." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "42\n" ] } ], "source": [ "x = MyClass()\n", "x.value = 42\n", "print(x.value) # muestra 42" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Además de estos métodos de instancia, que reciben el argumento `self` para hacer referencia a la instancia actual, se pueden declarar métodos estáticos y métodos de clase.\n", "\n", "Los métodos estáticos se declaran con la notación `@staticmethod` sobre la definición, y no reciben el argumento `self`.\n", "\n", "Los métodos de clase se declarar con la notación `@classmethod` sobre la definición, y en vez de `self`, reciben `cls` que hace referencia al objeto clase actual" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "scrolled": true, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Soy estático!\n", "Soy un nuevo atributo de clase!\n" ] } ], "source": [ "class MyClass:\n", "\n", " def __init__(self, x):\n", " self.data = []\n", " self.x = x\n", "\n", " @staticmethod\n", " def some_static_stuff():\n", " print(\"Soy estático!\")\n", "\n", " @classmethod\n", " def some_class_stuff(cls):\n", " cls.class_attribute = \"Soy un nuevo atributo de clase!\"\n", "MyClass(19)\n", "MyClass.some_static_stuff()\n", "MyClass.some_class_stuff()\n", "print(MyClass.class_attribute)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Herencia y herencia múltiple, sistema de herencia de Python." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Python soporta la herencia entre clases, y para declarar una clase que hereda de otra lo podemos hacer de la siguiente forma:" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "```python\n", "class BaseClaseName:\n", " pass\n", "\n", "\n", "class DerivedClassName(BaseClaseName):\n", " pass\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Cuando una clase derivada o hija se construye, esta recuerda el namespace de la clase padre, por lo que tiene acceso a los mismos nombres que esta clase define.\n", "\n", "Si se definen atributos o métodos con los mismo nombres, estas nuevas definiciones sobreescriben su asociación en el namespace, por lo que podemos decir que podemos cambiar la implementación de los métodos de la clase base." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Python soporta también la herencia múltiple. Una herencia múltiple puede ser definidia de la siguiente forma:" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "```python\n", "class Base1:\n", " pass\n", "\n", "\n", "class Base2:\n", " pass\n", "\n", "\n", "class Base3:\n", " pass\n", "\n", "\n", "class DerivedClassName(Base1, Base2, Base3):\n", " pass\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Cuando una clase que tiene herencia múltiple intenta buscar un nombre en su namespace, realiza la búsqueda en profundidad y de izquierda a derecha, es decir, en el ejemplo, primero busca el nombre en Base1, luego en la ascendencia de Base1, luego en Base2, luego en la ascendencia de Base2 y por último en Base3 y su ascendencia." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "En un método cualquiera, podemos acceder a la implementación del padre usando la función `super()`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Encapsulación, métodos y atributos \"privados\", con _ o __ al principio." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "En el sentido tradicional, todos los métodos y atributos de un objeto en Python se consideran públicos, es decir, pueden ser accedidos desde cualquier punto, mientras se tenga una referencia al objeto." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Sin embargo, se usa una convección para definir métodos y atributos privados, que serían todos aquellos que empiezan con al menos un `_` en el nombre. Todos estos atributos o métodos deberán ser tratados como parte no pública del API de la clase, de tal forma que la implementación de este método se podría cambiar sin que un usuario de esta clase lo notara." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Todo identificador que empiece por `__`, dos `_`, será traducido internamente por `__classname__`, de tal forma que si tenemos un método `__spam`, sería traducido internamente por `__classname__spam`. Esto impide que un método que empiece por `__` pueda ser sobreescrito por una clase hija. También es útil si queremos cambiar los argumentos que recibe un método en una clase hija, sin romper la clase padre." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "init de Mapping\n", "update de Mapping\n", "update de MappingSubclass\n" ] } ], "source": [ "class Mapping:\n", " def __init__(self, iterable):\n", " print(\"init de Mapping\")\n", " self.items_list = []\n", " self.__update(iterable)\n", "\n", " def update(self, iterable):\n", " print(\"update de Mapping\")\n", " for item in iterable:\n", " self.items_list.append(item)\n", "\n", " __update = update # copia privada del método update() original\n", "\n", "class MappingSubclass(Mapping):\n", "\n", " def update(self, keys, values):\n", " print(\"update de MappingSubclass\")\n", " # proporciona un nuevo método update, con parámetros distintos\n", " # pero no rompe la implementación de __init__()\n", " for item in zip(keys, values):\n", " self.items_list.append(item)\n", " __update = update\n", " \n", "sm = MappingSubclass(\"abc\")\n", "sm.update(list(range(5)), \"abcde\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Esto es una convención que se usa, y que se el propio interprete respeta en cierta medida, pero cabe resaltar que el hecho de que se declare un método como privado no impide que sea accedido desde fuera de la clase." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Métodos mágicos" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "El método `__init__` visto anteriormente se le considera también un método mágico. Los métodos mágicos de las clases permiten definir el comportamiento de esta en diferentes circunstancias, como la inicialización, la comparación con otros objetos o como obtener una representación." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Construcción e inicialización\n", "\n", "**`__new__(cls, [...)`**\n", "\n", "Este es el primer método que se llama al crear una instancia de una clase, antes de que la instancia en si exista. Recibe la clase y los mismos argumentos que se le pasan a `__init__`.\n", "\n", "**`__init__(self, [...)`**\n", "\n", "Es el inicializador de la instancia. Se llama cuando ya existe la instancia, y tiene como argumentos los que se le pasan al crear la instancia. Este junto a `__new__` formarían el constructor como lo entendemos en otros lenguajes de programación.\n", "\n", "`__del__(self)`\n", "\n", "Este método se llama cuando el colector de basura destruye el objeto. No se llama cuando se hace uso de `del`. No se recomienda su uso ya que no se puede garantizar que sea llamado cuando el objeto existe y se cierra el interprete.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Comparaciones\n", "\n", "`__eq__(self, other)`\n", "\n", "Permite realizar una comparación igualdad entra el objeto y `other`.\n", "\n", "\n", "`__ne__(self, other)`\n", "\n", "Permite realizar una comparación no igualdad entra el objeto y `other`.\n", "\n", "`__lt__(self, other)`\n", "\n", "Permite realizar una comparación menor que entra el objeto y `other`.\n", "\n", "`__gt__(self, other)`\n", "\n", "Permite realizar una comparación mayor que entra el objeto y `other`.\n", "\n", "`__le__(self, other)`\n", "\n", "\n", "Permite realizar una comparación menor o igual que entra el objeto y `other`.\n", "\n", "`__ge__(self, other)`\n", "\n", "Permite realizar una comparación mayor o igual que entra el objeto y `other`.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Operadores unarios\n", "\n", "`__pos__(self)`\n", "\n", "Realiza y devuelve el resultado de la operación `+obj`.\n", "\n", "`__neg__(self)`\n", "\n", "Realiza y devuelve el resultado de la operación `-obj`.\n", "\n", "`__abs__(self)`\n", "\n", "Realiza y devuelve el resultado de la operación `abs(obj)`.\n", "\n", "`__invert__(self)`\n", "\n", "Realiza y devuelve el resultado de la operación `~obj`.\n", "\n", "`__round__(self, n)`\n", "\n", "Realiza y devuelve el resultado de la operación `round(obj)`.\n", "\n", "`__floor__(self)`\n", "\n", "Realiza y devuelve el resultado de la operación `math.floor(obj)`.\n", "\n", "`__ceil__(self)`\n", "\n", "Realiza y devuelve el resultado de la operación `math.ceil(obj)`.\n", "\n", "`__trunc__(self)`\n", "\n", "Realiza y devuelve el resultado de la operación `math.trunc(obj)`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Operadores aritméticos normales\n", "\n", "`__add__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj + other`.\n", "\n", "`__sub__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj - other`.\n", "\n", "`__mul__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj * other`.\n", "\n", "`__floordiv__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj // other`.\n", "\n", "`__div__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj / other`.\n", "\n", "`__mod__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj % other`.\n", "\n", "`__divmod__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `divmod(obj, other)`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "`__pow__`\n", "\n", "Realiza y deveulve el resultado de hacer `obj ** other`.\n", "\n", "`__lshift__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj << other`.\n", "\n", "`__rshift__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj >> other`.\n", "\n", "`__and__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj & other`.\n", "\n", "`__or__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj | other`.\n", "\n", "`__xor__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj ^ other`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Operadores aritméticos reflejados\n", "\n", "`__radd__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other + obj`.\n", "\n", "`__rsub__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other - obj`.\n", "\n", "`__rmul__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other * obj`.\n", "\n", "`__rfloordiv__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other // obj`.\n", "\n", "`__rdiv__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other / obj`.\n", "\n", "`__rmod__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other % obj`.\n", "\n", "`__rdivmod__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `divmod(other, obj)`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "`__rpow__`\n", "\n", "Realiza y deveulve el resultado de hacer `other ** obj`.\n", "\n", "`__rlshift__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other << obj`.\n", "\n", "`__rrshift__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other >> obj`.\n", "\n", "`__rand__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other & obj`.\n", "\n", "`__ror__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other | obj`.\n", "\n", "`__rxor__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `other ^ obj`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Operadores con asignación\n", "\n", "`__iadd__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj += other`.\n", "\n", "`__isub__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj -= other`.\n", "\n", "`__imul__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj *= other`.\n", "\n", "`__ifloordiv__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj //= other`.\n", "\n", "`__idiv__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj /= other`.\n", "\n", "`__imod__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj %= other`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "`__ipow__`\n", "\n", "Realiza y deveulve el resultado de hacer `obj **= other`.\n", "\n", "`__ilshift__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj <<= other`.\n", "\n", "`__irshift__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj >>= other`.\n", "\n", "`__iand__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj &= other`.\n", "\n", "`__ior__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj |= other`.\n", "\n", "`__ixor__(self, other)`\n", "\n", "Realiza y deveulve el resultado de hacer `obj ^= other`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Representación de Clases\n", "\n", "**`__str__(self)`**\n", "\n", "Se llama para obtener una representación en texto de la clase cuando se llama a `str(obj)`.\n", "\n", "`__bytes__(self)`\n", "\n", "Se llama para obtener una representación como secuencia de bytes de la clase.\n", "\n", "`__repr__(self)`\n", "\n", "Se llama para obtener una representación en texto de la clase cuando se llama a `repr(obj)`.\n", "\n", "`__format__(self, formatstr)`\n", "\n", "Se llama cuando se hace llama a `obj.format()`.\n", "\n", "`__hash__(self)`\n", "\n", "Se llama cuando se hace llama a `hash(obj)`.\n", "\n", "`__bool__(self)`\n", "\n", "Se llama cuando se hace llama a `bool(obj)`.\n", "\n", "`__dir__(self)`\n", "\n", "Se llama cuando se hace llama a `dir(obj)`.\n", "\n", "`__sizeof__(self)`\n", "\n", "Se llama cuando se hace llama a `sizeof(obj)`.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Acceso a atributos\n", "\n", "**`__getattr__(self, name)`**\n", "\n", "Define el comportamiento que realzia la clase cuando se accede a un atributo que no existe.\n", "\n", "**`__setattr__(self, name, value)`**\n", "\n", "Permite definir el comportamiento para la asignación de un valor en un atributo, tanto si existe como si no.\n", "\n", "`__delattr__(self, name)`\n", "\n", "Permite definir el comportamiento para cuando se intenta borrar un atributo, usando `del`.\n", "\n", "**`__getattribute__(self, name)`**\n", "\n", "Permite definir el comportamiento para el acceso a un atributo, tanto si existe como si no." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Secuencias\n", "\n", "`__len__(self)`\n", "\n", "Devuelve el resultado de llamar a la función `len(ojb)`.\n", "\n", "`__getitem__(self, key)`\n", "\n", "Devuelve el resultado de llamar a `obj[key]`.\n", "\n", "`__setitem__(self, key, value)`\n", "\n", "Devuelve el resultado de llamar a `obj[key] = value`.\n", "\n", "`__delitem__(self, key)`\n", "\n", "Devuelve el resultado de llamar a `del obj[key]`.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**`__iter__(self)`**\n", "\n", "Devuelve el resultado de llamar a `iter(obj)`.\n", "\n", "`__reversed__(self)`\n", "\n", "Devuelve el resultado de llamar a `reversed(obj)`.\n", "\n", "`__contains__(self, item)`\n", "\n", "Devuelve el resultado de llamar a `item in obj` o `item not in obj`.\n", "\n", "`__missing__(self, key)`\n", "\n", "Se utiliza en las subclases de `dic`, y se llama cuando se hace acceso a un elemento con clave `key` que no extiste en el diccionario." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Objetos llamables\n", "\n", "**`__call__(self, [args...])`**\n", "\n", "Se ejecuta cuando se llama a una instancia como si fuera una función.\n", "\n", "#### Contextos\n", "\n", "`__enter__(self)`\n", "\n", "Se llama cuando se entra en un contexto con la instrucción `with`.\n", "\n", "`__exit__(self, exception_type, exception_value, traceback)`\n", "\n", "Se llama cuando se sale de un contexto.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Decoradores, introducción, para que sirven y cómo crear decoradores" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Un decorador en Python no es más que un nombre que se le da a un patrón de diseño. Los decoradores alteran de forma dinámica la funcionalidad de una función, método o clase, sin tener que usar subclases o cambiar el código fuente de la función que está siendo decorada." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "De forma sencilla, un decorador no es más que un objeto invocable, una función o un objeto que implementa el método `__call__`, que recibe como otro objeto invocable.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def dec(func):\n", " def wrapper(*args, **kwargs):\n", " print(\"Antes de ejecutar la función\")\n", " result = func(*args, **kwargs)\n", " print(\"Después de ejecutar la función\")\n", " return result\n", " return wrapper\n", "\n", "\n", "def dummy_function(value):\n", " return value\n", "\n", "\n", "decorated_dummy_function = dec(dummy_function)\n", "print(decorated_dummy_function(42))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Python incluye azúcar sintáctica para simplificar el uso de los decoradores, de forma que para decorar una función, basta con usar `@`. En el ejemplo anterior:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def dec(func):\n", " def wrapper(*args, **kwargs):\n", " print(\"Antes de ejecutar la función\")\n", " result = func(*args, **kwargs)\n", " print(\"Después de ejecutar la función\")\n", " return result\n", " return wrapper\n", "\n", "\n", "@dec\n", "def dummy_function(value):\n", " return value\n", "\n", "\n", "print(dummy_function(42))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Un decorador también puede recibir parámetros, para lo que necesitamos añadir una nueva función de envoltura." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def dec(param=False):\n", " def _dec(func):\n", " def wrapper(*args, **kwargs):\n", " if param:\n", " print(\"Se ha pasado True\")\n", " else:\n", " print(\"Se ha pasado False\")\n", " return func(*args, **kwargs)\n", " return wrapper\n", " return _dec\n", "\n", "\n", "@dec(True)\n", "def dummy_function(value):\n", " return value\n", "\n", "\n", "print(dummy_function(42))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Un decorado también puede ser declarado como una clase, solo necesita implementar el método mágico `__call__`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "class Dec:\n", " \"\"\"Decorador sin argumentos\"\"\"\n", " \n", " def __init__(self, function):\n", " print(\"Método __init__ del decorador.\")\n", " self.function = function\n", "\n", " def __call__(self, *args, **kwargs):\n", " print(\"Método __call__ del decorador.\")\n", " return self.function(*args, **kwargs)\n", "\n", "@Dec\n", "def dummy_function(value):\n", " return value\n", "\n", "\n", "print(dummy_function(42))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Además de decorar funciones, también se pueden decorar métodos de clases. Para ello, sólo hay que tener en cuenta que Python pasa de forma automática el argumento `self` a todos los métodos de clases." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def dec(func):\n", " \"\"\"Decorador reutilizable para funciones y métodos.\"\"\"\n", " def wrapper(self=None, *args, **kwargs):\n", " print(\"Antes de ejecutar el método\")\n", " result = func(self, *args, **kwargs)\n", " print(\"Después de ejecutar el método\")\n", " return result\n", " return wrapper\n", "\n", "\n", "class DummyClass:\n", "\n", " @dec\n", " def dummy(self, value):\n", " return value\n", "\n", " \n", "@dec\n", "def dummy_function(value):\n", " return value\n", "\n", "obj = DummyClass()\n", "print(obj.dummy(42))\n", "\n", "print(dummy_function(42))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Y no solo los métodos, también se pueden crear decoradores que se pueden aplicar a una clase, permitiendo cambiar así la definición entera de la clase y de todos sus métodos." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def dec(func):\n", " def wrapper(*args, **kwargs):\n", " print(\"Antes de ejecutar el método\")\n", " result = func(*args, **kwargs)\n", " print(\"Después de ejecutar el método\")\n", " return result\n", " return wrapper\n", "\n", "def dec_class(cls):\n", " \"\"\"El decorador de clase recibe como primer argumento el objeto clase.\"\"\"\n", "\n", " class NewCls:\n", " \"\"\"Creamos una nueva clase que reemplazará a la original.\"\"\"\n", " \n", " def __init__(self, *args, **kwargs):\n", " self.original_instance = cls(*args, **kwargs)\n", " \n", " def __getattribute__(self, name):\n", " \"\"\"Este método se llama siempre que se accede a un método de un objeto NewCls. Esté método \n", " primero intenta acceder a los atributos de NewCls, si falla, entonces accede a los de \n", " self.original_instance, y si el atributo es un metodo, entonces se aplica el decorador.\n", " \"\"\"\n", " try: \n", " result = super().__getattribute__(name)\n", " except AttributeError: \n", " pass\n", " # El else se ejecuta cuando no se lanza ninguna excepción\n", " else:\n", " return result\n", " result = self.original_instance.__getattribute__(name)\n", " if type(result) == type(self.__init__):\n", " return dec(result)\n", " else:\n", " return result\n", " return NewCls\n", "\n", "\n", "@dec_class\n", "class MyClass:\n", " \n", " def dummy1(self, value):\n", " return value\n", " \n", " def dummy2(self):\n", " print(\"dummy\")\n", "\n", "\n", "obj = MyClass()\n", "print(obj.dummy1(42))\n", "obj.dummy2()" ] } ], "metadata": { "celltoolbar": "Slideshow", "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.7.1" } }, "nbformat": 4, "nbformat_minor": 2 }