Quindi di recente ho fatto una domanda sulla memoizzazione e ho ottenuto ottime risposte, e ora voglio portarlo al livello successivo. Dopo un po 'di ricerca su google, non sono riuscito a trovare un'implementazione di riferimento di un decoratore memoize che fosse in grado di memorizzare nella cache una funzione che richiedeva argomenti per le parole chiave. In effetti, la maggior parte di essi utilizzava semplicemente *args
come chiave per la ricerca della cache, il che significa che si interromperebbe anche se si desidera memorizzare una funzione che accetta liste o dict come argomenti.Esiste un modo pitonico per supportare gli argomenti delle parole chiave per un decoratore memoize in Python?
Nel mio caso, il primo argomento della funzione è un identificatore univoco di per sé, adatto per l'uso come chiave dict per le ricerche della cache, tuttavia volevo la possibilità di utilizzare gli argomenti delle parole chiave e accedere comunque alla stessa cache. Quello che intendo è, my_func('unique_id', 10)
e my_func(foo=10, func_id='unique_id')
, se entrambi devono restituire lo stesso risultato memorizzato nella cache.
Per fare ciò, ciò di cui abbiamo bisogno è un modo pulito e pitonico di dire "ispezionare kwargs per qualsiasi parola chiave corrisponde al primo argomento". Questo è quello che mi è venuto in mente:
class memoize(object):
def __init__(self, cls):
if type(cls) is FunctionType:
# Let's just pretend that the function you gave us is a class.
cls.instances = {}
cls.__init__ = cls
self.cls = cls
self.__dict__.update(cls.__dict__)
def __call__(self, *args, **kwargs):
"""Return a cached instance of the appropriate class if it exists."""
# This is some dark magic we're using here, but it's how we discover
# that the first argument to Photograph.__init__ is 'filename', but the
# first argument to Camera.__init__ is 'camera_id' in a general way.
delta = 2 if type(self.cls) is FunctionType else 1
first_keyword_arg = [k
for k, v in inspect.getcallargs(
self.cls.__init__,
'self',
'first argument',
*['subsequent args'] * (len(args) + len(kwargs) - delta)).items()
if v == 'first argument'][0]
key = kwargs.get(first_keyword_arg) or args[0]
print key
if key not in self.cls.instances:
self.cls.instances[key] = self.cls(*args, **kwargs)
return self.cls.instances[key]
La cosa pazzesca è che questo funziona davvero. Ad esempio, se si decorare in questo modo:
@memoize
class FooBar:
instances = {}
def __init__(self, unique_id, irrelevant=None):
print id(self)
Poi dal codice è possibile chiamare o FooBar('12345', 20)
o FooBar(irrelevant=20, unique_id='12345')
e effettivamente ottenere la stessa istanza di FooBar. È quindi possibile definire una classe diversa con un nome diverso per il primo argomento, perché funziona in un modo generale (cioè, il decoratore non ha bisogno di sapere nulla di specifico sulla classe che sta decorando in modo che funzioni).
Il problema è, si tratta di un pasticcio empio ;-)
Funziona perché inspect.getcallargs
restituisce un dict mappatura delle parole chiave definite agli argomenti che fornite, così ho fornirla alcuni argomenti fasulli e quindi ispezionare il dict per il primo argomento che è passato.
Cosa sarebbe molto più bello, se una cosa del genere dovesse esistere anche, è un analogo a inspect.getcallargs
che ha restituito entrambi i tipi di argomenti unificati come un elenco degli argomenti anziché come un dotto degli argomenti delle parole chiave. Ciò consentirebbe qualcosa di simile:
def __call__(self, *args, **kwargs):
key = inspect.getcallargsaslist(self.cls.__init__, None, *args, **kwargs)[1]
if key not in self.cls.instances:
self.cls.instances[key] = self.cls(*args, **kwargs)
return self.cls.instances[key]
L'altro modo che posso vedere di affrontare questo sarebbe utilizzando il dict fornito dal inspect.getcallargs
come la chiave di cache di ricerca direttamente, ma che richiederebbe un modo ripetibile per fare le stringhe identici da hash identici, che è qualcosa che ho sentito non può essere invocato (credo che dovrei costruire la stringa io stesso dopo aver ordinato i tasti).
Qualcuno ha qualche idea su questo? È sbagliato voler chiamare una funzione con argomenti di parole chiave e memorizzare i risultati? O solo molto difficile?
Mi piace l'aspetto di questo, ma sono preoccupato per la parte in cui 'key' restituisce' tuple (a.items()) '. È garantito per ordinare le chiavi nello stesso ordine per dizioni distinte ma identiche? Ho sentito dire che le dict non sono ordinate e basarsi su cose come 'str ({1: 2,3: 4})' per produrre stringhe ripetibili dato input identici è Molto Molto Errante. – robru
Sembra che 'inspect.getargspec (func) .args [0]' è la risposta precisa alla domanda specifica che ho chiesto (come trovare il nome del primo argomento), ma mi piacerebbe espandere questo in un soluzione più generale. Pubblicherò i miei risultati più tardi una volta che avrò avuto un po 'di tempo per migliorarlo. – robru
@Robru: buon punto per l'ordinamento dei dit. Modificato su 'tuple (sort (a.items()))' (un'altra opzione sarebbe 'frozenset (a.items())'). – georg