Python: Decoradores estándar y su uso
Published: 2019-01-16 14:47:46

---
title: Python: Decoradores estándar y su uso
published: true
tags: 
canonical_url: https://yorodm.github.io/blog/decoradores-stdlib/
---

Entre las _baterías includas_ de Python, vienen varios decoradores que nos facilitan la vida. Aquí una lista de algunos y breve explicación acerca de su uso.

## Decoradores para OOP

### property

Uno de los más usados. Convierte un conjunto de métodos en un **descriptor** que hace las funciones de propiedad. Útil cuando queremos adicionar algún tipo de lógica a propiedades de la clase.

```
class C:
    def __init__ (self):
        self._x = None

    def getx(self):
        return self._x or request.get('http://mydata.com/x')

    def setx(self, value):
        self._x = value
        request.post('http://mydata.com/x', data=x)

    def delx(self):
        del self._x
        request.delete('http://mydata.com/x')

    x = property(getx, setx, delx, "x como proxy a servicio REST.")
```

### staticmethod

Define métodos estáticos<sup id="fnref:1"><a href="#fn:1">1</a></sup>. Pueden ser llamados tanto desde la clase o una de sus instancias

```
class C:

    @staticmethod
    def calculo_externo(val, val2):
        return val + val2
```

Nótese la falta del parámetro implicito inicial.

### classmethod

Convierte un método normal a un método de clase. Los métodos de clase reciben la clase como parámetro implícito en vez de una de las instancias. Uno de los casos de uso más comunes es crear formas alternativas de instanciar la clase.

```
class C:

    def __init__ (self, x, y ,z):
        self.x = x
        self.y = y
        self.z = z

    @classmethod
    def from_dict(cls, data):
        return cls(data[0], data[1], data[3])
```

### abc.abstractmethod

Métodos que tienen que ser implementados **obligatoriamente** por las subclases. El equivalente a _interfaces_ o _clases abstractas_ que tanto encuentras en otros lenguajes.

```
import abc

class Animal:

    @abc.abstractmethod
    def sonido(self):
        pass

class Perro(Animal):

    def sonido(self):
        print("Ladrido")

class Vaca(Animal):

    def sonido(self):
        print("Mugido")
```

Se puede utilizar en conjunción con _@property_ o _staticmethod_ para crear propiedades o métodos estáticos abstractos.

### functools.total\_ordering

Nos permite crear clases que soporten operadores de comparación. Solo tenemos que proveer el método _ **eq** _ y uno de los seis métodos predefinidos de orden. La documentación de la bilioteca estándar advierte que este decorador puede tener impactos en el rendimiento.

```
@total_ordering
class Student:
    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def __eq__ (self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__ (self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))
```

### dataclasses.dataclass

Nos permite omitir la declaración de _ **init** _ y _ **repr** _ en la declaración de clases en las que solo vamos a manejar datos. El uso puede ser extendido a subclases

```
import dataclasses

@dataclasses.dataclass
class Point:
    x: float
    y: float

    def euclidian_distance(self, other: Point) -> float:
        return math.sqrt(
            (other.x - self.y)**2 + (other.y - self.y)**2
        )

@dataclasses.dataclass
class ThreeDPoint(Point):
    z: float

    def euclidian_distance(self,other: Point) -> float:
        dx = (other.x - self.y)**2
        dy = (other.y - self.y)**2
        dz = (other.z - self.z)**2
        return math.sqrt(
            dx+dy+dz
        )
```

## Decoradores para funciones

### functools.lru\_cache

Caché de las últimas _n_ llamadas a una función. Los parametros tienen que poder ser utilizados como llaves de un diccionario.

```
from functools import lru_cache
@lru_cache(20)
def get_exchange(local, foreign):
    exchange_service.convert(local, foreign)
```

### asyncio.coroutine

Convierte corutinas basadas en generadores para que sean compatibles con el nuevo modelo _async/await_. Usar solamente en código que necesita ser portado

```
from asyncio import coroutine

@coroutine
def render_view(view_name, context):
    template = Template(view_name, context)
    yield template.render()

async def hanlde_get(route):
    await render_view(route.view, route.params)
```

### contextlib.contextmanager

Convierte una función en un _contextmanager_. Puede ser usado de dos formas:

1. Utilizando una sentencia _with_
2. Como un decorador.

Personalmente para hacer decoradores que sean _contextmanagers_ prefiero utilizar la clase _ContextDecorator_.

```
from contextlib import contextmanager

@contextmanager
def acquire_lock(key):
    credentials = get_credentials(key)
    lock = acquire_lock_using_credentials(credentials)
    try:
        yield lock
    finally:
        release_lock(lock)

with acquire_lock('my secret api key') as lock:
    use_critical_resource(lock)
```

## Decoradores extra

### functools.wraps

Decorador para crear decoradores. Útil para hacer que las funciones conserven propiedades como _ **name** _ y _ **doc** _

```
from functools import wraps
def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        return f(*args, **kwds)
        return wrapper

@my_decorator
def funcion():
    """
    Esta es la doc
    """
    pass
```

### functools.singledispatch

Permite crear funciones sobrecargadas a partir del tipo del primer parámetro. Hace uso de las anotaciones de tipos.

```
class Perro:
    pass

clas Vaca:
    pass

@singledispatch
def sonido(animal):
    pass

@sonido.register(Perro)
def _(animal):
    print("Ladra")

@sonido.register(Vaca)
def _(animal):
    print("Muge")
```

* * *

1. No soy muy partidario de usar métodos estáticos en Python. <sup>[return]</sup>