functools — Herramientas para manipular funciones¶
Propósito: | Funciones que operan en otras funciones. |
---|
El módulo functools
proporciona herramientas para adaptar o ampliar
funciones y otros objetos invocables, sin re escribirlos completamente.
Decoradores¶
La herramienta principal proporcionada por el módulo functools
es la clase
partial
, que se puede usar para «envolver» un objeto invocable con
argumentos por defecto. El objeto resultante es en sí mismo invocable y puede
ser tratado como si fuera la función original. Accepta todos los mismos
argumentos que el original, y se puede invocar con argumentos posicionales
extra o también nombrados. Se puede usar una partial
en lugar de
lambda
para proporcionar argumentos predeterminados a una función, mientras
deja algunos argumentos no especificados.
Objetos parciales¶
Este ejemplo muestra dos objetos partial
simples para la function
myfunc()
. La salida de show_details()
incluye los atributos
func
, args
, y keywords
del objeto parcial.
import functools
def myfunc(a, b=2):
"Docstring for myfunc()."
print(' called myfunc with:', (a, b))
def show_details(name, f, is_partial=False):
"Show details of a callable object."
print('{}:'.format(name))
print(' object:', f)
if not is_partial:
print(' __name__:', f.__name__)
if is_partial:
print(' func:', f.func)
print(' args:', f.args)
print(' keywords:', f.keywords)
return
show_details('myfunc', myfunc)
myfunc('a', 3)
print()
# Set a different default value for 'b', but require
# the caller to provide 'a'.
p1 = functools.partial(myfunc, b=4)
show_details('partial with named default', p1, True)
p1('passing a')
p1('override b', b=5)
print()
# Set default values for both 'a' and 'b'.
p2 = functools.partial(myfunc, 'default a', b=99)
show_details('partial with defaults', p2, True)
p2()
p2(b='override b')
print()
print('Insufficient arguments:')
p1()
Al final del ejemplo, el primer partial
creado es invocado sin pasar un
valor para a
, causando una excepción.
$ python3 functools_partial.py
myfunc:
object: <function myfunc at 0x1007a6a60>
__name__: myfunc
called myfunc with: ('a', 3)
partial with named default:
object: functools.partial(<function myfunc at 0x1007a6a60>,
b=4)
func: <function myfunc at 0x1007a6a60>
args: ()
keywords: {'b': 4}
called myfunc with: ('passing a', 4)
called myfunc with: ('override b', 5)
partial with defaults:
object: functools.partial(<function myfunc at 0x1007a6a60>,
'default a', b=99)
func: <function myfunc at 0x1007a6a60>
args: ('default a',)
keywords: {'b': 99}
called myfunc with: ('default a', 99)
called myfunc with: ('default a', 'override b')
Insufficient arguments:
Traceback (most recent call last):
File "functools_partial.py", line 51, in <module>
p1()
TypeError: myfunc() missing 1 required positional argument: 'a'
Aduiriendo propiedades de función¶
El objeto partial
por defecto no tiene los atributos __name__
o
__doc__
y sin esos atributos, las funciones decoradas son más difíciles
de depurar. Usando update_wrapper()
, copia o agrega atributos de la
función original al objeto partial
.
import functools
def myfunc(a, b=2):
"Docstring for myfunc()."
print(' called myfunc with:', (a, b))
def show_details(name, f):
"Show details of a callable object."
print('{}:'.format(name))
print(' object:', f)
print(' __name__:', end=' ')
try:
print(f.__name__)
except AttributeError:
print('(no __name__)')
print(' __doc__', repr(f.__doc__))
print()
show_details('myfunc', myfunc)
p1 = functools.partial(myfunc, b=4)
show_details('raw wrapper', p1)
print('Updating wrapper:')
print(' assign:', functools.WRAPPER_ASSIGNMENTS)
print(' update:', functools.WRAPPER_UPDATES)
print()
functools.update_wrapper(p1, myfunc)
show_details('updated wrapper', p1)
Los atributos agregados al wrapper están definidos en WRAPPER_ASSIGNMENTS
,
mientras que WRAPPER_UPDATES
lista los valores a modificar.
$ python3 functools_update_wrapper.py
myfunc:
object: <function myfunc at 0x1018a6a60>
__name__: myfunc
__doc__ 'Docstring for myfunc().'
raw wrapper:
object: functools.partial(<function myfunc at 0x1018a6a60>,
b=4)
__name__: (no __name__)
__doc__ 'partial(func, *args, **keywords) - new function with
partial application\n of the given arguments and keywords.\n'
Updating wrapper:
assign: ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
update: ('__dict__',)
updated wrapper:
object: functools.partial(<function myfunc at 0x1018a6a60>,
b=4)
__name__: myfunc
__doc__ 'Docstring for myfunc().'
Otros invocables¶
Los parciales funcionan con cualquier objeto invocable, no solo con funciones independientes.
import functools
class MyClass:
"Demonstration class for functools"
def __call__(self, e, f=6):
"Docstring for MyClass.__call__"
print(' called object with:', (self, e, f))
def show_details(name, f):
"Show details of a callable object."
print('{}:'.format(name))
print(' object:', f)
print(' __name__:', end=' ')
try:
print(f.__name__)
except AttributeError:
print('(no __name__)')
print(' __doc__', repr(f.__doc__))
return
o = MyClass()
show_details('instance', o)
o('e goes here')
print()
p = functools.partial(o, e='default for e', f=8)
functools.update_wrapper(p, o)
show_details('instance wrapper', p)
p()
Este ejemplo crea parciales de una instancia de una clase con un método
__call__()
.
$ python3 functools_callable.py
instance:
object: <__main__.MyClass object at 0x1011b1cf8>
__name__: (no __name__)
__doc__ 'Demonstration class for functools'
called object with: (<__main__.MyClass object at 0x1011b1cf8>,
'e goes here', 6)
instance wrapper:
object: functools.partial(<__main__.MyClass object at
0x1011b1cf8>, f=8, e='default for e')
__name__: (no __name__)
__doc__ 'Demonstration class for functools'
called object with: (<__main__.MyClass object at 0x1011b1cf8>,
'default for e', 8)
Métodos y Funciones¶
Mientras que partial()
devuelve un invocable listo para ser usado
directamente, partialmethod()
devuelve un invocable listo para ser
utilizado como un método sin consolidar de un objeto. En el siguiente ejemplo,
la misma función independiente se agrega como un atributo de MyClass
dos
veces, una vez usando partialmethod()
como method1()
y otra vez usando
partial()
como method2()
.
import functools
def standalone(self, a=1, b=2):
"Standalone function"
print(' called standalone with:', (self, a, b))
if self is not None:
print(' self.attr =', self.attr)
class MyClass:
"Demonstration class for functools"
def __init__(self):
self.attr = 'instance attribute'
method1 = functools.partialmethod(standalone)
method2 = functools.partial(standalone)
o = MyClass()
print('standalone')
standalone(None)
print()
print('method1 as partialmethod')
o.method1()
print()
print('method2 as partial')
try:
o.method2()
except TypeError as err:
print('ERROR: {}'.format(err))
method1()
se puede invocar desde una instancia de MyClass
, y la
instancia se pasa como el primer argumento al igual que con los métodos
definidos normalmente. method2()
no está configurado como un método
vinculado, por lo que el argumento self
debe pasarse explícitamente, o la
invocación resultará en un TypeError
.
$ python3 functools_partialmethod.py
standalone
called standalone with: (None, 1, 2)
method1 as partialmethod
called standalone with: (<__main__.MyClass object at
0x1007b1d30>, 1, 2)
self.attr = instance attribute
method2 as partial
ERROR: standalone() missing 1 required positional argument:
'self'
Adquiriendo propiedades de funciones para decoradores¶
Actualizar las propiedades de un invocable envuelto es especialmente útil cuando se usa en un decorador, ya que la función transformada termina con propiedades de la función «desnuda» original.
import functools
def show_details(name, f):
"Show details of a callable object."
print('{}:'.format(name))
print(' object:', f)
print(' __name__:', end=' ')
try:
print(f.__name__)
except AttributeError:
print('(no __name__)')
print(' __doc__', repr(f.__doc__))
print()
def simple_decorator(f):
@functools.wraps(f)
def decorated(a='decorated defaults', b=1):
print(' decorated:', (a, b))
print(' ', end=' ')
return f(a, b=b)
return decorated
def myfunc(a, b=2):
"myfunc() is not complicated"
print(' myfunc:', (a, b))
return
# The raw function
show_details('myfunc', myfunc)
myfunc('unwrapped, default b')
myfunc('unwrapped, passing b', 3)
print()
# Wrap explicitly
wrapped_myfunc = simple_decorator(myfunc)
show_details('wrapped_myfunc', wrapped_myfunc)
wrapped_myfunc()
wrapped_myfunc('args to wrapped', 4)
print()
# Wrap with decorator syntax
@simple_decorator
def decorated_myfunc(a, b):
myfunc(a, b)
return
show_details('decorated_myfunc', decorated_myfunc)
decorated_myfunc()
decorated_myfunc('args to decorated', 4)
functools
proporciona un decorador, wraps()
, que aplica
update_wrapper()
a la función decorada.
$ python3 functools_wraps.py
myfunc:
object: <function myfunc at 0x101241b70>
__name__: myfunc
__doc__ 'myfunc() is not complicated'
myfunc: ('unwrapped, default b', 2)
myfunc: ('unwrapped, passing b', 3)
wrapped_myfunc:
object: <function myfunc at 0x1012e62f0>
__name__: myfunc
__doc__ 'myfunc() is not complicated'
decorated: ('decorated defaults', 1)
myfunc: ('decorated defaults', 1)
decorated: ('args to wrapped', 4)
myfunc: ('args to wrapped', 4)
decorated_myfunc:
object: <function decorated_myfunc at 0x1012e6400>
__name__: decorated_myfunc
__doc__ None
decorated: ('decorated defaults', 1)
myfunc: ('decorated defaults', 1)
decorated: ('args to decorated', 4)
myfunc: ('args to decorated', 4)
Comparación¶
En Python 2, las clases podían definir un método __cmp __()
que devuelve
-1
, 0
, o 1
en función de que si el objeto es menos que, igual o
mayor que el elemento que se compara. Python 2.1 introdujo la interfaz de
métodos de comparación rica (__lt__()
, __le__()
, __eq__()
,
__ne__()
, __gt__()
, y __ge__()
), que realizan una única operación
de comparación y regresan un valor booleano. Python 3 desaprobó __cmp__()
en favor de estos nuevos métodos y functools
proporcionan herramientas para
hacer más fácil el escribir clases que cumplan con los nuevos requerimientos de
comparación requisitos en Python 3.
Comparación rica¶
La interfaz de comparación rica está diseñada para permitir clases con
comparaciones complejas, implementar cada prueba de la manera más eficiente
posible. Sin embargo, para clases donde la comparación es relativamente
simple, hay no tiene sentido crear manualmente cada uno de los métodos de
comparación rica. El decorador de clases total_ordering()
toma una clase
que proporciona algunos de los métodos, y agrega el resto de ellos.
import functools
import inspect
from pprint import pprint
@functools.total_ordering
class MyObject:
def __init__(self, val):
self.val = val
def __eq__(self, other):
print(' testing __eq__({}, {})'.format(
self.val, other.val))
return self.val == other.val
def __gt__(self, other):
print(' testing __gt__({}, {})'.format(
self.val, other.val))
return self.val > other.val
print('Methods:\n')
pprint(inspect.getmembers(MyObject, inspect.isfunction))
a = MyObject(1)
b = MyObject(2)
print('\nComparisons:')
for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']:
print('\n{:<6}:'.format(expr))
result = eval(expr)
print(' result of {}: {}'.format(expr, result))
La clase debe proporcionar la implementación de __eq__()
y otro método de
comparación rica. El decorador agrega implementaciones del resto de los
métodos que funcionan usandor las comparaciones proporcionadas. Si no se puede
hacer una comparación, el método debería devolver NotImplemented
, entonces
la comparación puede ser probada usando los operadores de comparación inversa
en el otro objeto, antes de fallar por completo.
$ python3 functools_total_ordering.py
Methods:
[('__eq__', <function MyObject.__eq__ at 0x10139a488>),
('__ge__', <function _ge_from_gt at 0x1012e2510>),
('__gt__', <function MyObject.__gt__ at 0x10139a510>),
('__init__', <function MyObject.__init__ at 0x10139a400>),
('__le__', <function _le_from_gt at 0x1012e2598>),
('__lt__', <function _lt_from_gt at 0x1012e2488>)]
Comparisons:
a < b :
testing __gt__(1, 2)
testing __eq__(1, 2)
result of a < b: True
a <= b:
testing __gt__(1, 2)
result of a <= b: True
a == b:
testing __eq__(1, 2)
result of a == b: False
a >= b:
testing __gt__(1, 2)
testing __eq__(1, 2)
result of a >= b: False
a > b :
testing __gt__(1, 2)
result of a > b: False
Orden de colación¶
Como las funciones de comparación antiguas están en desuso en Python 3, el
argumento cmp
para funciones como sort()
ya no es soportado. Los
programas antiguos que usan funciones de comparación pueden usar
cmp_to_key()
para convertirlos a una función que devuelve una llave de
colación, que se utiliza para determinar la posición en la secuencia final.
import functools
class MyObject:
def __init__(self, val):
self.val = val
def __str__(self):
return 'MyObject({})'.format(self.val)
def compare_obj(a, b):
"""Old-style comparison function.
"""
print('comparing {} and {}'.format(a, b))
if a.val < b.val:
return -1
elif a.val > b.val:
return 1
return 0
# Make a key function using cmp_to_key()
get_key = functools.cmp_to_key(compare_obj)
def get_key_wrapper(o):
"Wrapper function for get_key to allow for print statements."
new_key = get_key(o)
print('key_wrapper({}) -> {!r}'.format(o, new_key))
return new_key
objs = [MyObject(x) for x in range(5, 0, -1)]
for o in sorted(objs, key=get_key_wrapper):
print(o)
Normalmente cmp_to_key()
se usaría directamente, pero en este ejemplo, se
introduce una función de envoltura adicional para imprimir más información
cuando se está llamando a la función clave.
El resultado muestra que sorted()
comienza llamando get_key_wrapper()
para cada elemento en la secuencia para producir una llave. Las claves
devueltas por cmp_to_key()
son instancias de una clase definida en
functools
que implementa la interfaz de comparación rica utilizando la
función de comparación al estilo antiguo. Después de que todas las llaves se
crean, la secuencia se ordena comparando las llaves.
$ python3 functools_cmp_to_key.py
key_wrapper(MyObject(5)) -> <functools.KeyWrapper object at
0x1011c5530>
key_wrapper(MyObject(4)) -> <functools.KeyWrapper object at
0x1011c5510>
key_wrapper(MyObject(3)) -> <functools.KeyWrapper object at
0x1011c54f0>
key_wrapper(MyObject(2)) -> <functools.KeyWrapper object at
0x1011c5390>
key_wrapper(MyObject(1)) -> <functools.KeyWrapper object at
0x1011c5710>
comparing MyObject(4) and MyObject(5)
comparing MyObject(3) and MyObject(4)
comparing MyObject(2) and MyObject(3)
comparing MyObject(1) and MyObject(2)
MyObject(1)
MyObject(2)
MyObject(3)
MyObject(4)
MyObject(5)
Almacenamiento en caché¶
El decorador lru_cache()
envuelve una función en un caché de memoria
utilizada menos recientemente. Los argumentos a la función se utilizan para
construir una llave hash, que luego se asigna al resultado. Llamadas
posteriores con los mismos argumentos obtendrán el valor de la memoria caché en
lugar de llamar a la función. El decorador también agrega métodos a la función
para examinar el estado de la memoria caché (cache_info()
) y vaciar el
caché (cache_clear()
).
import functools
@functools.lru_cache()
def expensive(a, b):
print('expensive({}, {})'.format(a, b))
return a * b
MAX = 2
print('First set of calls:')
for i in range(MAX):
for j in range(MAX):
expensive(i, j)
print(expensive.cache_info())
print('\nSecond set of calls:')
for i in range(MAX + 1):
for j in range(MAX + 1):
expensive(i, j)
print(expensive.cache_info())
print('\nClearing cache:')
expensive.cache_clear()
print(expensive.cache_info())
print('\nThird set of calls:')
for i in range(MAX):
for j in range(MAX):
expensive(i, j)
print(expensive.cache_info())
Este ejemplo realiza varias llamadas a expensive()
en un conjunto de bucles
anidados. La segunda vez que esas llamadas se realizan con los mismos valores,
los resultados aparecen en el caché. Cuando el caché se borra y los bucles se
ejecutan de nuevo los valores deben ser re calculados.
$ python3 functools_lru_cache.py
First set of calls:
expensive(0, 0)
expensive(0, 1)
expensive(1, 0)
expensive(1, 1)
CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)
Second set of calls:
expensive(0, 2)
expensive(1, 2)
expensive(2, 0)
expensive(2, 1)
expensive(2, 2)
CacheInfo(hits=4, misses=9, maxsize=128, currsize=9)
Clearing cache:
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
Third set of calls:
expensive(0, 0)
expensive(0, 1)
expensive(1, 0)
expensive(1, 1)
CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)
Para evitar que la memoria caché crezca sin límites en un proceso de larga
ejecución, se le da un tamaño máximo. El valor predeterminado es de 128
entradas, pero eso se puede cambiar para cada caché usando el argumento
maxsize
.
import functools
@functools.lru_cache(maxsize=2)
def expensive(a, b):
print('called expensive({}, {})'.format(a, b))
return a * b
def make_call(a, b):
print('({}, {})'.format(a, b), end=' ')
pre_hits = expensive.cache_info().hits
expensive(a, b)
post_hits = expensive.cache_info().hits
if post_hits > pre_hits:
print('cache hit')
print('Establish the cache')
make_call(1, 2)
make_call(2, 3)
print('\nUse cached items')
make_call(1, 2)
make_call(2, 3)
print('\nCompute a new value, triggering cache expiration')
make_call(3, 4)
print('\nCache still contains one old item')
make_call(2, 3)
print('\nOldest item needs to be recomputed')
make_call(1, 2)
En este ejemplo, el tamaño de la memoria caché está configurado en 2 entradas.
Cuando se utiliza el tercer set de argumentos únicos (3, 4
), el elemento
más antiguo en la memoria caché es desechado y reemplazado con el nuevo
resultado.
$ python3 functools_lru_cache_expire.py
Establish the cache
(1, 2) called expensive(1, 2)
(2, 3) called expensive(2, 3)
Use cached items
(1, 2) cache hit
(2, 3) cache hit
Compute a new value, triggering cache expiration
(3, 4) called expensive(3, 4)
Cache still contains one old item
(2, 3) cache hit
Oldest item needs to be recomputed
(1, 2) called expensive(1, 2)
Las claves para la memoria caché gestionadas por lru_cache()
deben ser
hashable, por lo que todos los argumentos a la función envuelta con la búsqueda
de caché deben ser hashable
import functools
@functools.lru_cache(maxsize=2)
def expensive(a, b):
print('called expensive({}, {})'.format(a, b))
return a * b
def make_call(a, b):
print('({}, {})'.format(a, b), end=' ')
pre_hits = expensive.cache_info().hits
expensive(a, b)
post_hits = expensive.cache_info().hits
if post_hits > pre_hits:
print('cache hit')
make_call(1, 2)
try:
make_call([1], 2)
except TypeError as err:
print('ERROR: {}'.format(err))
try:
make_call(1, {'2': 'two'})
except TypeError as err:
print('ERROR: {}'.format(err))
Si cualquier objeto que no puede ser hash se pasa a la función, se plantea un
TypeError
.
$ python3 functools_lru_cache_arguments.py
(1, 2) called expensive(1, 2)
([1], 2) ERROR: unhashable type: 'list'
(1, {'2': 'two'}) ERROR: unhashable type: 'dict'
Reduciendo un conjunto de datos¶
La función reduce()
toma un invocable y una secuencia de datos como entrada
y produce un solo valor como salida basado en invocar el invocable con los
valores de la secuencia y la acumulación de el resultado resultante.
import functools
def do_reduce(a, b):
print('do_reduce({}, {})'.format(a, b))
return a + b
data = range(1, 5)
print(data)
result = functools.reduce(do_reduce, data)
print('result: {}'.format(result))
Este ejemplo suma los números en la secuencia de entrada.
$ python3 functools_reduce.py
range(1, 5)
do_reduce(1, 2)
do_reduce(3, 3)
do_reduce(6, 4)
result: 10
El argumento opcional initializer
se coloca al frente de la secuencia y es
procesado junto con los otros elementos. Esto puede usarse para actualizar un
valor previamente calculado con nuevas entradas.
import functools
def do_reduce(a, b):
print('do_reduce({}, {})'.format(a, b))
return a + b
data = range(1, 5)
print(data)
result = functools.reduce(do_reduce, data, 99)
print('result: {}'.format(result))
En este ejemplo, se utiliza una suma previa de 99
para inicializar el valor
calculado por reduce()
.
$ python3 functools_reduce_initializer.py
range(1, 5)
do_reduce(99, 1)
do_reduce(100, 2)
do_reduce(102, 3)
do_reduce(105, 4)
result: 109
Las secuencias con un solo elemento se reducen automáticamente a ese valor cuando no hay inicializador presente. Las listas vacías generan un error, a menos que se proporcione un inicializador.
import functools
def do_reduce(a, b):
print('do_reduce({}, {})'.format(a, b))
return a + b
print('Single item in sequence:',
functools.reduce(do_reduce, [1]))
print('Single item in sequence with initializer:',
functools.reduce(do_reduce, [1], 99))
print('Empty sequence with initializer:',
functools.reduce(do_reduce, [], 99))
try:
print('Empty sequence:', functools.reduce(do_reduce, []))
except TypeError as err:
print('ERROR: {}'.format(err))
Porque el argumento de inicialización sirve como pre determinado, pero también
es combinado con los nuevos valores si la secuencia de entrada no está vacía,
es importante considerar cuidadosamente usarlo. Cuando no tiene sentido
combinar el valor predeterminado con nuevos valores, es mejor atrapar el
TypeError
en lugar de pasar un inicializador.
$ python3 functools_reduce_short_sequences.py
Single item in sequence: 1
do_reduce(99, 1)
Single item in sequence with initializer: 100
Empty sequence with initializer: 99
ERROR: reduce() of empty sequence with no initial value
Funciones Genéricas¶
En un lenguaje de tipado dinámico como Python, es común necesitar realizar una
operación ligeramente diferente en función del tipo de argumento, especialmente
cuando se trata de la diferencia entre una lista de elementosy un solo
elemento. Es lo suficientemente simple verificar el tipo de argumento
directamente, pero en casos en que la diferencia de comportamiento pueda ser
aislada en funciones separadas functools
proporciona el decorador
singledispatch()
para registrar un conjunto de funciones genéricas para
la conmutación automática en función del tipo del primer argumento a una
función.
import functools
@functools.singledispatch
def myfunc(arg):
print('default myfunc({!r})'.format(arg))
@myfunc.register(int)
def myfunc_int(arg):
print('myfunc_int({})'.format(arg))
@myfunc.register(list)
def myfunc_list(arg):
print('myfunc_list()')
for item in arg:
print(' {}'.format(item))
myfunc('string argument')
myfunc(1)
myfunc(2.3)
myfunc(['a', 'b', 'c'])
El atributo register()
de la nueva función sirve como otro decorador para
registrar implementaciones alternativas. La primera función envuelta con
singledispatch()
es la implementación predeterminada si no se encuentra
otra función específica de tipo, como con el caso float
en este ejemplo.
$ python3 functools_singledispatch.py
default myfunc('string argument')
myfunc_int(1)
default myfunc(2.3)
myfunc_list()
a
b
c
Cuando no se encuentra una coincidencia exacta para el tipo, el orden de herencia es evaluado y se usa el tipo de coincidencia más cercano.
import functools
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B):
pass
class E(C, D):
pass
@functools.singledispatch
def myfunc(arg):
print('default myfunc({})'.format(arg.__class__.__name__))
@myfunc.register(A)
def myfunc_A(arg):
print('myfunc_A({})'.format(arg.__class__.__name__))
@myfunc.register(B)
def myfunc_B(arg):
print('myfunc_B({})'.format(arg.__class__.__name__))
@myfunc.register(C)
def myfunc_C(arg):
print('myfunc_C({})'.format(arg.__class__.__name__))
myfunc(A())
myfunc(B())
myfunc(C())
myfunc(D())
myfunc(E())
En este ejemplo, las clases D
y E
no coinciden exactamente con ninguna
función genérica registrada, y la función seleccionada depende de la jerarquía
de clases.
$ python3 functools_singledispatch_mro.py
myfunc_A(A)
myfunc_B(B)
myfunc_C(C)
myfunc_B(D)
myfunc_C(E)
Ver también
- Documentación de la biblioteca estándar para functools
- Métodos de comparación rica – Descripción de los métodos de comparación rica de la Guía de Referencia Python.
- @memoize aislado – Artículo sobre el crear decoradores memoizantes que trabajan bien con pruebas de unidad, por Ned Batchelder.
- PEP 443 – «Funciones Genéricas de despacho simple».
inspect
– Interfaz de introspección para objetos vivos.