functools — Funciones de orden superior y operaciones sobre objetos invocables

Código fuente: Lib/functools.py


El módulo functools es para funciones de orden superior: funciones que actúan o retornan otras funciones. En general, cualquier objeto invocable puede ser tratado como una función para los propósitos de este módulo.

El módulo functools define las siguientes funciones:

@functools.cached_property(func)

Transforma un método de una clase en una propiedad cuyo valor se computa una vez y luego se almacena como un atributo normal durante la vida de la instancia. Similar a property(), con la adición de caching. Útil para propiedades calculadas costosas de instancias que de otra manera son efectivamente inmutables.

Ejemplo:

class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

    @cached_property
    def variance(self):
        return statistics.variance(self._data)

Nuevo en la versión 3.8.

Nota

Este decorador requiere que el atributo __dict__ en cada instancia sea un mapeo mutable. Esto significa que no funcionará con algunos tipos, como las metaclases (ya que los atributos __dict__ en las instancias de tipo son proxies sólo de lectura para el espacio de nombres de la clase), y los que especifican __slots__ sin incluir __dict__ como uno de los slots definidos (ya que tales clases no proporcionan un atributo __dict__ en absoluto).

functools.cmp_to_key(func)

Transformar una función de comparación de estilo antiguo en una key function. Se utiliza con herramientas que aceptan funciones clave (como sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby()). Esta función se utiliza principalmente como una herramienta de transición para los programas que se están convirtiendo a partir de Python 2, que soportaba el uso de funciones de comparación.

Una función de comparación es cualquier invocable que acepta dos argumentos, los compara y retorna un número negativo para diferencia, cero para igualdad o un número positivo para más. Una función clave es un invocable que acepta un argumento y retorna otro valor para ser usado como clave de ordenación.

Ejemplo:

sorted(iterable, key=cmp_to_key(locale.strcoll))  # locale-aware sort order

Para ejemplos de clasificación y un breve tutorial de clasificación, ver HOW TO - Ordenar.

Nuevo en la versión 3.2.

@functools.lru_cache(user_function)
@functools.lru_cache(maxsize=128, typed=False)

Decorador para envolver una función con un memorizador invocable que guarda hasta el maxsize de las llamadas más recientes. Puede salvar el tiempo cuando una función costosa o de E/S es llamada periódicamente con los mismos argumentos.

Dado que se utiliza un diccionario para guardar los resultados, los argumentos posicionales y de las palabras clave de la función deben ser hashable.

Los patrones de argumento distintos pueden considerarse como llamadas distintas con entradas de caché separadas. Por ejemplo, f(a=1, b=2) y f(b=2, a=1) difieren en el orden de los argumentos de las palabras clave y pueden tener dos entradas de caché separadas.

Si se especifica user_function, debe ser una llamada. Esto permite que el decorador lru_cache se aplique directamente a una función de usuario, dejando el maxsize en su valor por defecto de 128:

@lru_cache
def count_vowels(sentence):
    sentence = sentence.casefold()
    return sum(sentence.count(vowel) for vowel in 'aeiou')

Si maxsize está configurado como None, la función LRU está desactivada y la caché puede crecer sin límites.

Si typed se establece como verdadero, los argumentos de las funciones de diferentes tipos se almacenarán en la memoria caché por separado. Por ejemplo, f(3) y f(3.0) se tratarán como llamadas distintas con resultados distintos.

Para ayudar a medir la efectividad del cache y afinar el parámetro maxsize, la función envolvente está instrumentada con una función cache_info() que retorna un named tuple mostrando hits, misses, maxsize y currsize. En un entorno multi-hilo, los aciertos y los fallos son aproximados.

El decorador también proporciona una función cache_clear() para limpiar o invalidar el caché.

La función subyacente original es accesible a través del atributo __wrapped__. Esto es útil para la introspección, para evitar el caché, o para volver a envolver la función con un caché diferente.

Una caché LRU (la menos usada recientemente) funciona mejor cuando las llamadas más recientes son los mejores pronosticadores de las próximas llamadas (por ejemplo, los artículos más populares en un servidor de noticias tienden a cambiar cada día). El límite de tamaño de la caché asegura que ésta no crezca sin estar vinculada a procesos de larga duración como los servidores web.

En general, la caché de la LRU sólo debe utilizarse cuando se desea reutilizar valores previamente calculados. Por consiguiente, no tiene sentido almacenar en la caché funciones con efectos secundarios, funciones que necesitan crear distintos objetos mutables en cada llamada, o funciones impuras como time() o random().

Ejemplo de un caché de la LRU para contenido web estático:

@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'

>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
...     pep = get_pep(n)
...     print(n, len(pep))

>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)

Ejemplo de computar eficientemente los «números de Fibonacci» <https://en.wikipedia.org/wiki/Fibonacci_number>`_ usando un cache para implementar una «programación dinámica» <https://en.wikipedia.org/wiki/Dynamic_programming>`_ técnica:

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

Nuevo en la versión 3.2.

Distinto en la versión 3.3: Añadida la opción typed option.

Distinto en la versión 3.8: Añadida la opción user_function.

@functools.total_ordering

Dada una clase que define uno o más métodos de ordenamiento de comparación ricos, este decorador de clase suministra el resto. Esto simplifica el esfuerzo de especificar todas las posibles operaciones de comparación rica:

La clase debe definir uno de __lt__(), __le__(), __gt__(), o __ge__(). Además, la clase debe suministrar un método __eq__(). Dada una clase que define uno o más métodos de ordenamiento de comparación ricos, este decorador de clase suministra el resto. Esto simplifica el esfuerzo de especificar todas las posibles operaciones de comparación rica.

Por ejemplo:

@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()))

Nota

Mientras que este decorador facilita la creación de tipos bien comportados y totalmente ordenados, does a costa de una ejecución más lenta y de trazos de pila (stack traces) más complejos para los métodos de comparación derivados. Si la evaluación comparativa del rendimiento indica que se trata de un cuello de botella para una aplicación determinada, la aplicación de los seis métodos de comparación ricos en su lugar es probable que proporcione un fácil aumento de la velocidad.

Nuevo en la versión 3.2.

Distinto en la versión 3.4: Retornando NotImplemented de la función de comparación subyacente para los tipos no reconocidos está ahora soportado.

functools.partial(func, /, *args, **keywords)

Retorna un nuevo partial object que cuando sea llamado se comportará como func llamado con los argumentos posicionales args y los argumentos de palabras clave keywords. Si se suministran más argumentos a la llamada, se añaden a args. Si se suministran más argumentos de palabras clave, se extienden y anulan las keywords. Aproximadamente equivalente a:

def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

El partial() se utiliza para la aplicación de funciones parciales que «congela» (freezes) alguna porción de los argumentos y/o palabras clave de una función dando como resultado un nuevo objeto con una firma simplificada. Por ejemplo, partial() puede usarse para crear una llamada que se comporte como la función int() donde el argumento base tiene un valor por defecto de dos:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
class functools.partialmethod(func, /, *args, **keywords)

Retorna un nuevo descriptor partialmethod que se comporta como partial excepto que está diseñado para ser usado como una definición de método en lugar de ser directamente invocable.

func debe ser un descriptor o un invocable (los objetos que son ambos, como las funciones normales, se manejan como descriptores).

Cuando func es un descriptor (como una función Python normal, classmethod(), staticmethod(), abstractmethod() u otra instancia de partialmethod), las llamadas a __get__ se delegan al descriptor subyacente, y se retorna un partial object apropiado como resultado.

Cuando func es una llamada no descriptiva, se crea dinámicamente un método de unión apropiado. Esto se comporta como una función Python normal cuando se usa como método: el argumento self se insertará como el primer argumento posicional, incluso antes de las args y keywords suministradas al constructor partialmethod.

Ejemplo:

>>> class Cell(object):
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True

Nuevo en la versión 3.4.

functools.reduce(function, iterable[, initializer])

Aplicar una función de dos argumentos acumulativos a los elementos de iterable, de izquierda a derecha, para reducir los itables a un solo valor. Por ejemplo, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calcula ((((1+2)+3)+4)+5). El argumento de la izquierda, x, es el valor acumulado y el de la derecha, y, es el valor de actualización del iterable. Si el initializer opcional está presente, se coloca antes de los ítems de la iterable en el cálculo, y sirve como predeterminado cuando la iterable está vacía. Si no se da el initializer y el iterable contiene sólo un elemento, se retorna el primer elemento.

Aproximadamente equivalente a:

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

Ver itertools.accumulate() para un iterador que produce todos los valores intermedios.

@functools.singledispatch

Transformar una función en una single-dispatch generic function.

Para definir la función genérica, decórela con el decorador @singledispatch. Ten en cuenta que el envío ocurre en el tipo del primer argumento, crea tu función en consecuencia:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

Para añadir implementaciones sobrecargadas a la función, use el atributo register() de la función genérica. Es un decorador. Para las funciones anotadas con tipos, el decorador deducirá automáticamente el tipo del primer argumento:

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

Para el código que no utiliza anotaciones de tipo, el argumento de tipo apropiado puede ser pasado explícitamente al propio decorador:

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

Para permitir el registro de lambdas y funciones preexistentes, el atributo register() puede utilizarse de forma funcional:

>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

El atributo register() retorna la función no decorada que permite al decorador apilar, decapar, así como crear pruebas de unidad para cada variante de forma independiente:

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Half of your number:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

Cuando se llama, la función genérica despacha sobre el tipo del primer argumento:

>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615

Cuando no hay una implementación registrada para un tipo específico, su orden de resolución de método se utiliza para encontrar una implementación más genérica. La función original decorada con @singledispatch se registra para el tipo de object base, lo que significa que se usa si no se encuentra una mejor implementación.

Para comprobar qué implementación elegirá la función genérica para un tipo determinado, utilice el atributo dispatch():

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)    # note: default implementation
<function fun at 0x103fe0000>

Para acceder a todas las implementaciones registradas, utilice el atributo registry de sólo lectura:

>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
          <class 'decimal.Decimal'>, <class 'list'>,
          <class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>

Nuevo en la versión 3.4.

Distinto en la versión 3.7: El atributo register() soporta el uso de anotaciones de tipo.

class functools.singledispatchmethod(func)

Transformar un método en un single-dispatch generic function.

Para definir un método genérico, decóralo con el decorador @singledispatchmethod. Tenga en cuenta que el envío se produce en el tipo del primer argumento que no sea un atributo de instancias (non-self) ni un atributo de clases (non-cls), cree su función en consecuencia:

class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg

El @singledispatchmethod apoya el anidamiento con otros decoradores como el @classmethod. Ten en cuenta que para permitir el dispatcher.register, singledispatchmethod debe ser el decorador outer most. Aquí está la clase neg con los métodos Negator limitados a la clase:

class Negator:
    @singledispatchmethod
    @classmethod
    def neg(cls, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    @classmethod
    def _(cls, arg: int):
        return -arg

    @neg.register
    @classmethod
    def _(cls, arg: bool):
        return not arg

El mismo patrón puede ser usado para otros decoradores similares: staticmethod, abstractmethod, y otros.

Nuevo en la versión 3.8.

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Actualizar una función envoltorio (wrapper) para que se parezca a la función de envoltura(wrapped). Los argumentos opcionales son tuplas para especificar qué atributos de la función original se asignan directamente a los atributos correspondientes de la función de envoltura y qué atributos de la función de envoltura se actualizan con los atributos correspondientes de la función original. Los valores por defecto de estos argumentos son las constantes de nivel de módulo WRAPPER_ASSIGNMENTS (que asigna al __module__ de la función de envoltura, __module__, __name__, __qualname__, __annotations__ y __doc__, la cadena de documentación) y WRAPPER_UPDATES (que actualiza el __dict__ de la función de envoltura(wrapped), i. e. el diccionario de instancias).

Para permitir el acceso a la función original para la introspección y otros propósitos (por ejemplo, evitando un decorador de caché como lru_cache()), esta función añade automáticamente un atributo __wrapped__ al envoltorio que se refiere a la función que se está envolviendo.

El principal uso previsto para esta función es en decorator functions que envuelven la función decorada y retornan el envoltorio. Si la función de envoltura no se actualiza, los metadatos de la función retornada reflejarán la definición de la envoltura en lugar de la definición de la función original, lo que normalmente no es de gran ayuda.

update_wrapper() puede ser usado con otros invocables que no sean funciones. Cualquier atributo nombrado en assigned o updated que falte en el objeto que se está invoca se ignora (es decir, esta función no intentará establecerlos en la función de envoltura (wrapper)). AttributeError sigue apareciendo si la propia función de envoltura no tiene ningún atributo nombrado en updated.

Nuevo en la versión 3.2: Adición automática de __wrapped__ attribute.

Nuevo en la versión 3.2: Copia del atributo __annotations__ por defecto.

Distinto en la versión 3.2: Los atributos faltantes ya no desencadenan un AtributoError.

Distinto en la versión 3.4: El atributo __wrapped__ ahora siempre se refiere a la función envuelta, incluso si esa función definió un atributo __wrapped__. (see :issue:17482)

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Esta es una función conveniente para invocar update_wrapper() como decorador de la función cuando se define una función de envoltura (wrapper). Es equivalente a partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated). Por ejemplo:

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

Sin el uso de esta fábrica de decoradores, el nombre de la función de ejemplo habría sido 'wrapper', y el docstring de la example() se habría perdido.

partial Objetos

Los objetos partial son objetos invocables creados por partial(). Tienen tres atributos de sólo lectura:

partial.func

Un objeto o función invocable. Las llamadas al objeto partial serán reenviadas a func con nuevos argumentos y palabras clave.

partial.args

Los argumentos posicionales de la izquierda que se prepararán para los argumentos posicionales proporcionados un llamado al objeto partial.

partial.keywords

Los argumentos de la palabra clave que se suministrarán cuando se llame al objeto partial.

Los objetos partial son como los objetos function que son invocables, de referencia débil y pueden tener atributos. Hay algunas diferencias importantes. Por ejemplo, los atributos __name__ y __doc__ no se crean automáticamente. Además, los objetos partial definidos en las clases se comportan como métodos estáticos y no se transforman en métodos vinculados durante la búsqueda de atributos de la instancia.