Memorizando o kwarg padrão no Python

É assim que você pode memorizar uma função Python:

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

A recepção é imerecidamente pouco conhecida; portanto, analisaremos como funciona e para que serve.

Primeiro, como e por que funciona. memo_square (como qualquer outra função) é um objeto da classe de função, que, entre outros atributos, possui uma tupla memo_square.__defaults__ preenchida ao criar o objeto. Primeiro, ele contém um dicionário vazio, conforme indicado no cabeçalho da função:

 >>> memo_square.__defaults__ ({},) 

__defaults__ é uma tupla regular e você não pode alterar seus elementos. É verdade que você pode substituir todo o conjunto de valores padrão de uma só vez, mas apenas para outra 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 artigo não chegará a Picaba. Bem, tudo bem, isso não é importante. O importante é que, com exceção do código muito esperto, a func.__defaults__ é criada uma vez durante o tempo do programa com todos os seus elementos. Uma tupla e seus elementos não serão recriados a cada chamada de função, eles serão usados ​​enquanto a função existir. Mas, para mudar, se os elementos em si são mutáveis, ninguém os proíbe. Incapacidade de trabalhar com esses elementos é uma das maneiras mais comuns de se matar no python . Mas, na verdade, salvar valores entre chamadas de função pode ser bastante útil. Após várias chamadas, memo_square.__defaults__ terá a seguinte aparência:

 >>> 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},) 

Se a função já tiver sido chamada para o mesmo valor, o valor será calculado e, portanto, o cache não será reabastecido. Para um quadrado, o benefício é pequeno (estritamente falando, para um quadrado, o benefício é negativo, porque pesquisar em um dicionário é mais caro do que multiplicar dois números), mas, por funções realmente caras, a memorização / armazenamento em cache pode ser útil. Obviamente, você pode fornecê-lo em python de mais de uma maneira. Aqui estão as alternativas que temos:

  • @ functools.lru_cache . Um decorador do módulo functools que lembra as últimas chamadas de função. É confiável e simples, mas usa todos os parâmetros da função como chaves, o que significa que requer capacidade de hashabability e não pode notar que dois valores de parâmetros formalmente diferentes são equivalentes. Com o primeiro requisito, tudo fica claro, sobre funções de conjuntos, por exemplo, você pode esquecer. Bem, ou quando ligar, converta-os para frozenset. Quanto ao segundo, por exemplo, eu tenho uma função que usa uma conexão SQL e um número como entrada e faz alguma manipulação dos dados associados a esse número. A conexão pode muito bem ser desconectada e restabelecida durante a operação do programa, e o cache lru_cache será desativado. Mas ele sabe como armazenar em cache apenas um número limitado de chamadas (evitando vazamentos de memória) e está bem documentado.
  • Função externa de cache:

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

    O significado é o mesmo, mas muito mais complicado. Além disso, a variável de cache é visível fora da função, embora não seja usada para nada além de memorizá-la. O cache durante a memorização com o argumento padrão é acessível externamente apenas por meio de func.__defaults__ , que são bastante difíceis de acessar por engano.
  • Libere um objeto completo com um cache e torne sua função um método. Bom em termos de arquitetura e testabilidade, ele permite manter uma lógica de cache arbitrariamente complexa, mas ainda mais complicada devido ao padrão no código do objeto. Além disso, não está claro do que herdar e se deve herdar alguma coisa, se houver mais de uma função memorizável.

A principal coisa que esse método de memorização perde é que não é muito idiomático. Pessoalmente, quando me deparei com essa decisão pela primeira vez, pensei por alguns minutos sobre o que estava acontecendo aqui e por quê. Por outro lado, nesses minutos, comecei a entender um pouco melhor como o Python funciona e seus argumentos são organizados. Portanto, mesmo que você não use os argumentos padrão (para memorização ou, por exemplo, para acelerar a resolução de nomes ), conhecer essa técnica ainda é útil para qualquer nutricionista.

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


All Articles