python memoization

python - ¿Cómo memorizar** kwargs?



title python (5)

No he visto una forma establecida de memorizar una función que tome argumentos de palabras clave, es decir, algo de tipo

def f(*args, **kwargs)

ya que normalmente un memoizer tiene un dict para almacenar en caché los resultados de un conjunto dado de parámetros de entrada, y kwargs es un dict y, por lo tanto, no es manejable. He intentado, siguiendo las discusiones here , usando

(args, frozenset(kwargs.items()))

como clave para el dict la memoria caché, pero esto solo funciona si los valores en kwargs son hashable. Además, como se señala en las respuestas a continuación, frozenset no es una estructura de datos ordenada. Por lo tanto, esta solución podría ser más segura:

(args, tuple(sorted(kwargs.items())))

Pero todavía no puede hacer frente a los elementos no hashable. Otro enfoque que he visto es usar una representación de string de los kwargs en la clave de caché:

(args, str(sorted(kwargs.items())))

El único inconveniente que veo con esto es la sobrecarga del hashing de una cadena potencialmente muy larga. Por lo que puedo ver los resultados deben ser correctos. ¿Alguien puede detectar algún problema con este último enfoque? Una de las respuestas a continuación señala que esto asume cierto comportamiento de las funciones __str__ o __repr__ para los valores de los argumentos de palabras clave. Esto parece un show stopper.

¿Hay alguna otra forma más establecida de lograr una memoria que pueda hacer frente a los valores de los argumentos de **kwargs y no-hashable?


¿Qué pasa con key = pickle.dumps( (args, sorted(kwargs.items()), -1 ) ? Este parecería ser un enfoque más sólido que str () o repr ().


Aquí:

from functools import wraps def memoize(fun): """A simple memoize decorator for functions supporting positional args.""" @wraps(fun) def wrapper(*args, **kwargs): key = (args, frozenset(sorted(kwargs.items()))) try: return cache[key] except KeyError: ret = cache[key] = fun(*args, **kwargs) return ret cache = {} return wrapper

Pruebas:

import unittest class TestMemoize(unittest.TestCase): def test_it(self): @memoize def foo(*args, **kwargs): "foo docstring" calls.append(None) return (args, kwargs) calls = [] # no args for x in range(2): ret = foo() expected = ((), {}) self.assertEqual(ret, expected) self.assertEqual(len(calls), 1) # with args for x in range(2): ret = foo(1) expected = ((1, ), {}) self.assertEqual(ret, expected) self.assertEqual(len(calls), 2) # with args + kwargs for x in range(2): ret = foo(1, bar=2) expected = ((1, ), {''bar'': 2}) self.assertEqual(ret, expected) self.assertEqual(len(calls), 3) self.assertEqual(foo.__doc__, "foo docstring") unittest.main()

Esto funciona siempre y cuando no pase un tipo inestable (por ejemplo, dict) como argumento. No tengo una solución para eso, pero la implementación de collections.lru_cache () podría tener. Consulte la función _make_key () aquí: http://code.activestate.com/recipes/578078/


Es similar a lo que dijo EMS, pero la mejor manera sería:

key = cPickle.dumps((*args, **kwargs))

He estado haciendo muchas investigaciones y pruebas de memorización con decoradores, y este es el mejor método que he encontrado hasta ahora.


Los dictados pueden estar en orden arbitrario, por lo que no hay garantía de que este último funcione. Use sorted(kwargs.items()) para sorted(kwargs.items()) primero por clave.


key = (args, frozenset(kwargs.items())

Este es el "mejor" que puede hacer sin hacer suposiciones sobre sus datos.

Sin embargo, parece concebible querer realizar una memorización en los diccionarios (aunque es un poco inusual), podría tener un caso especial si lo desea. Por ejemplo, podría aplicar recursivamente frozenset(---.items()) al copiar diccionarios.

Si lo hace, puede estar en una mala situación en la que tiene llaves desordenadas. Por ejemplo, " Las comparaciones de subconjuntos e igualdad no se generalizan a una función de orden completa. Por ejemplo, dos conjuntos disjuntos no son iguales y no son subconjuntos entre sí, por lo que todo lo siguiente devuelve False: ab. En consecuencia, los conjuntos hacen No implementar el método cmp () " .

>>> sorted([frozenset({1,2}), frozenset({1,3})]) [frozenset({1, 2}), frozenset({1, 3})] >>> sorted([frozenset({1,3}), frozenset({1,2})]) # THE SAME [frozenset({1, 3}), frozenset({1, 2})] # DIFFERENT SORT RESULT # sorted(stuff) != sorted(reversed(stuff)), if not strictly totally ordered

Edición: Ignacio dice: "Si bien no puedes usar ordenado () en dictados arbitrarios, los kwarg tendrán claves de acceso". Esto es totalmente correcto. Por lo tanto, esto no es un problema para las claves, aunque posiblemente sea algo que se debe tener en cuenta para los valores si usted (o la reproducción improbable) confía en la clasificación de alguna manera.

Respecto al uso de str :

Es el caso de que la mayoría de los datos funcionen bien, pero es posible que un adversario (por ejemplo, en un contexto de vulnerabilidad de seguridad) cree una colisión. No es fácil, porque la mayoría de las repr predeterminadas usan muchos grupos buenos y escapan. De hecho, no pude encontrar semejante colisión. Pero es posible con implementaciones de terceros incompletas o con repr incompletas.

También considere lo siguiente: Si está almacenando claves como ((<map object at 0x1377d50>,), frozenset(...)) y ((<list_iterator object at 0x1377dd0>,<list_iterator object at 0x1377dd0>), frozenset(...)) , su caché crecerá sin límites solo con llamar a los mismos elementos. (Quizás podría solucionar este problema con una expresión regular ...) Y tratar de consumir los generadores estropeará la semántica de la función que está utilizando. Sin embargo, este comportamiento puede ser deseado si desea memorizar sobre la igualdad de estilo en lugar de la igualdad de estilo == .

¡También hacer algo como str({1:object()}) en el intérprete devolverá un objeto en la misma ubicación en la memoria cada vez! Creo que este es el recolector de basura en el trabajo. Esto sería desastroso, ya que si aparece el hash <some object at 0x???????> y se crea un objeto del mismo tipo en la misma ubicación de memoria más adelante (debido a la recolección de basura), obtendrá resultados incorrectos de la función memoized. Como se mencionó, una posible solución de hackers es detectar dichos objetos con una expresión regular.