Tak więc niedawno zadałem pytanie dotyczące memoizacji i otrzymałem kilka świetnych odpowiedzi, a teraz chcę zabrać go na wyższy poziom. Po dość googlowaniu nie mogłem znaleźć referencyjnej implementacji dekoratora memoize, który byłby w stanie buforować funkcję, która pobierała argumenty słów kluczowych. W rzeczywistości większość z nich po prostu używała *args
jako klucza do wyszukiwań w pamięci podręcznej, co oznacza, że byłby również przerywany, gdybyś chciał zapamiętać funkcję, która akceptowała listy lub dyktuje jako argumenty.Czy istnieje pythonic sposób obsługi argumentów słów kluczowych dla dekoratora memoize w Pythonie?
W moim przypadku pierwszy argument do funkcji jest unikalnym identyfikatorem sam w sobie, odpowiedni do użycia jako klucz dyktujący dla wyszukiwań w pamięci podręcznej, jednak chciałem możliwość używania argumentów słów kluczowych i nadal mieć dostęp do tej samej pamięci podręcznej. Mam na myśli to, że oba zwracają ten sam wynik w pamięci podręcznej.
W tym celu potrzebujemy czystego i pytonowego sposobu powiedzenia "sprawdź kwargs dla dowolnego słowa kluczowego, które odpowiada pierwszemu argumentowi". Oto, co wymyśliłem:
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]
Szaloną rzeczą jest to, że to faktycznie działa. Na przykład, jeśli ozdobić tak:
@memoize
class FooBar:
instances = {}
def __init__(self, unique_id, irrelevant=None):
print id(self)
Następnie z kodu można wywołać albo FooBar('12345', 20)
lub FooBar(irrelevant=20, unique_id='12345')
i rzeczywiście uzyskać tę samą instancję foobar. Następnie możesz zdefiniować inną klasę o innej nazwie dla pierwszego argumentu, ponieważ działa ona w sposób ogólny (tzn. Dekorator nie musi znać żadnych szczegółów dotyczących klasy, którą dekoruje, aby to działało).
Problemem jest to, że jest to bezbożny bałagan ;-)
To działa, ponieważ inspect.getcallargs
zwraca dict mapowanie zdefiniowane słowa kluczowe do argumentów go zaopatrują, więc dostarczać go niektóre fałszywe argumenty, a następnie sprawdzić dict dla pierwszy argument, który został przyjęty.
Co by było o wiele ładniej, gdyby coś takiego istniało, jest analogiem do inspect.getcallargs
, który zwrócił oba rodzaje argumentów zunifikowane jako lista argumentów, zamiast jako dyktat argumentów słów kluczowych. Które pozwoliłyby coś takiego:
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]
inny sposób mogę zobaczyć w walce z tym będzie przy użyciu dict dostarczone przez inspect.getcallargs
jako klucz wyszukiwanie cache bezpośrednio, ale to wymagałoby powtarzalny sposób, aby identyczne ciągi z identyczne hashe, o czym słyszałem, nie można polegać (chyba po skonstruowaniu klawiszy muszę samemu skonstruować strunę).
Czy ktoś ma jakieś przemyślenia na ten temat? Czy nie należy wywoływać funkcji z argumentami słów kluczowych i buforować wyniki? Lub po prostu bardzo trudne?
Podoba mi się wygląd tego, ale niepokoi mnie część, w której 'key' zwraca' tuple (a.items()) '. Czy to gwarantuje sortowanie kluczy w tej samej kolejności dla wyraźnych, ale identycznych dyktatur? Słyszałem, że dyktura jest nieuporządkowana i polegając na takich rzeczach jak 'str ({1: 2,3: 4})' do tworzenia powtarzalnych łańcuchów przy identycznych danych wejściowych jest bardzo źle. – robru
Wygląda na to, że 'inspect.getargspec (func) .args [0]' jest dokładną odpowiedzią na konkretne pytanie, które zadałem (jak znaleźć nazwę pierwszego argumentu), ale chciałbym rozwinąć to w bardziej ogólne rozwiązanie. Opublikuję wyniki później, gdy będę miał trochę czasu, aby to poprawić. – robru
@Robru: dobry punkt na temat sortowania dyktowanego. Zmieniono na 'tuple (posortowane (a.items()))' (inną opcją byłoby 'frozenset (a.items())'). – georg