"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.
