Memorización de kwarg predeterminado en Python

Así es como puede memorizar una función de Python:

def memo_square(a, cache={}): if a not in cache: cache[a] = a*a return cache[a] 

La recepción es inmerecidamente poco conocida, por lo que bajo el corte, analizaremos cómo funciona y para qué sirve.

Primero, cómo y por qué funciona. memo_square (como cualquier otra función) es un objeto de la clase de función, que, entre otros atributos, tiene una tupla memo_square.__defaults__ completada al crear el objeto. Primero, contiene un diccionario vacío, como se indica en el encabezado de la función:

 >>> memo_square.__defaults__ ({},) 

__defaults__ es una tupla normal y no puede cambiar sus elementos. Es cierto que puede reemplazar todo el conjunto de valores predeterminados a la vez, pero solo por otra tupla:

 >>> def test(a=1, b=2): ... print(a, b) ... >>> test.__defaults__ (1, 2) >>> test() 1 2 >>> test.__defaults__ = (', ', '') >>> test() ,  >>> test.__defaults__[1] = '' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> test.__defaults__ = {0: ', ', 1: ''} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __defaults__ must be set to a tuple object 

Soryan, este artículo no llegará a Picaba. Bueno, está bien, esto no es importante. Lo importante es que, con la excepción del código muy astuto, func.__defaults__ se crea una vez durante el tiempo del programa con todos sus elementos. Una tupla y sus elementos no se recrearán con cada llamada de función, se utilizarán mientras exista la función. Pero para cambiar, si los elementos mismos son mutables, nadie los prohíbe. La incapacidad para trabajar con tales elementos es una de las formas más comunes de dispararse en la pitón . Pero en realidad guardar valores entre llamadas a funciones puede ser bastante útil. Después de varias llamadas, memo_square.__defaults__ se verá así:

 >>> memo_square(2) 4 >>> memo_square.__defaults__ ({2: 4},) >>> memo_square(5) 25 >>> memo_square.__defaults__ ({2: 4, 5: 25},) >>> memo_square(2) 4 >>> memo_square.__defaults__ ({2: 4, 5: 25},) 

Si ya se ha llamado a la función para el mismo valor, entonces se calcula el valor y, en consecuencia, la memoria caché no se repone. Para el cuadrado, el beneficio es pequeño (estrictamente hablando, para el cuadrado, el beneficio es negativo, porque buscar en el diccionario es más costoso que multiplicar dos números), pero para funciones realmente costosas, la memorización / almacenamiento en caché puede ser útil. Por supuesto, puede proporcionarlo en Python de más de una manera. Aquí están las alternativas que tenemos:

  • @ functools.lru_cache . Un decorador del módulo functools que recuerda las últimas llamadas a funciones. Es confiable y simple, pero utiliza todos los parámetros de la función como teclas, lo que significa que requiere capacidad de hash y no puede notar que dos valores de parámetros formalmente diferentes son equivalentes. Con el primer requisito, todo está claro, sobre las funciones de los conjuntos, por ejemplo, puede olvidarse. Bueno, o cuando llames, conviértelos a congelados. En cuanto a la segunda, por ejemplo, tengo una función que toma una conexión SQL y un número como entrada y realiza alguna manipulación de los datos asociados con este número. La conexión bien puede desconectarse y restablecerse durante la operación del programa, y ​​la caché lru_cache se volará. Pero él sabe cómo almacenar en caché solo un número limitado de llamadas (evitando pérdidas de memoria) y está bien documentado.
  • Función externa de caché:

     def square(a): return a**a cache = {} for x in values: if x not in cache: cache[x] = x**x print cache[x] 

    El significado es el mismo, pero mucho más engorroso. Además, la variable de caché es visible fuera de la función, aunque no se usa para otra cosa que no sea memorizarla. El caché durante la memorización con el argumento predeterminado es accesible externamente solo a través de func.__defaults__ , que son bastante difíciles de acceder por error.
  • Vacíe un objeto completo con un caché y convierta su función en un método. Bueno en términos de arquitectura y capacidad de prueba, le permite mantener una lógica de almacenamiento en caché arbitrariamente compleja, pero aún más engorrosa debido a la repetitiva en el código objeto. Además, no está claro de qué heredar y si heredar de algo, si hay más de una función memorable.

Lo principal que pierde este método de memorización es que no es muy idiomático. Personalmente, cuando me topé con esta decisión por primera vez, pensé por un par de minutos sobre lo que estaba sucediendo aquí y por qué. Por otro lado, en estos dos minutos comencé a comprender un poco mejor cómo funciona Python y sus argumentos. Entonces, incluso si no utiliza los argumentos predeterminados (para la memorización o, por ejemplo, para acelerar la resolución de nombres ), conocer esta técnica sigue siendo útil para cualquier nutricionista.

Source: https://habr.com/ru/post/es426341/


All Articles