Ich schreibe eine Klasse in Python und habe ein Attribut, dessen Berechnung relativ lange dauert. Daher möchte ich es nur einmal ausführen . Außerdem wird es nicht von jeder Instanz der Klasse benötigt, daher möchte ich es nicht standardmäßig in tun__init__
.
Ich bin neu in Python, aber nicht in der Programmierung. Ich kann mir einen Weg ausdenken, dies ziemlich einfach zu tun, aber ich habe immer wieder festgestellt, dass die 'Pythonic'-Art, etwas zu tun, oft viel einfacher ist als das, was ich mir aus meiner Erfahrung in anderen Sprachen ausgedacht habe.
Gibt es in Python einen "richtigen" Weg, dies zu tun?
python
memoization
mwolfe02
quelle
quelle
Foo.something_expensive
. Alle diese Antworten beziehen sich auf zwischengespeicherte Instanzeigenschaften , was bedeutetsomething_expensive
, dass für jede neue Instanz neu berechnet wird, was in den meisten Fällen nicht optimal istAntworten:
Python ≥ 3.8
@property
und@functools.lru_cache
wurden in kombiniert@cached_property
.import functools class MyClass: @functools.cached_property def foo(self): print("long calculation here") return 21 * 2
Python ≥ 3,2 <3,8
Sie sollten beide
@property
und@functools.lru_cache
Dekorateure verwenden:import functools class MyClass: @property @functools.lru_cache() def foo(self): print("long calculation here") return 21 * 2
Diese Antwort enthält detailliertere Beispiele und erwähnt auch einen Backport für frühere Python-Versionen.
Python <3.2
Das Python-Wiki verfügt über einen zwischengespeicherten Property Decorator (MIT-lizenziert), der folgendermaßen verwendet werden kann:
import random # the class containing the property must be a new-style class class MyClass(object): # create property whose value is cached for ten minutes @cached_property(ttl=600) def randint(self): # will only be evaluated every 10 min. at maximum. return random.randint(0, 100)
Oder jede Implementierung, die in den anderen Antworten erwähnt wird und Ihren Anforderungen entspricht.
Oder der oben erwähnte Backport.
quelle
lru_cache
hat eine Standardgröße von 128, wodurch die Eigenschaftsfunktion möglicherweise zweimal aufgerufen wird. Wenn Sielru_cache(None)
alle Instanzen verwenden, bleiben sie dauerhaft am Leben.@property @functools.lru_cache()
Methode gibt mir einenTypeError: unhashable type
Fehler, vermutlich weil sieself
nicht hashbar ist.functools.lru_cache
dass Instanzen der Klasse GC vermeiden, solange sie sich im Cache befinden. Bessere Lösung istfunctools.cached_property
in Python 3.8.Früher habe ich das so gemacht, wie es der Knabber vorschlug, aber irgendwann hatte ich die kleinen Reinigungsschritte satt.
Also habe ich meinen eigenen Deskriptor erstellt:
class cached_property(object): """ Descriptor (non-data) for building an attribute on-demand on first use. """ def __init__(self, factory): """ <factory> is called such: factory(instance) to build the attribute. """ self._attr_name = factory.__name__ self._factory = factory def __get__(self, instance, owner): # Build the attribute. attr = self._factory(instance) # Cache the value; hide ourselves. setattr(instance, self._attr_name, attr) return attr
So würden Sie es verwenden:
class Spam(object): @cached_property def eggs(self): print 'long calculation here' return 6*2 s = Spam() s.eggs # Calculates the value. s.eggs # Uses cached value.
quelle
cached_property
Paket auf PyPI . Es enthält thread-sichere und zeitlich abgelaufene Versionen. (Auch danke, @Florian, für die Erklärung.)cached_property
Deskriptor verwenden__slots__
. Slots werden mithilfe von Datendeskriptoren implementiert, und die Verwendung einescached_property
Deskriptors überschreibt einfach den generierten Slot-Deskriptor, sodass dersetattr()
Aufruf nicht funktioniert, da__dict__
das Attribut nicht festgelegt werden kann und der einzige für diesen Attributnamen verfügbare Deskriptor dercached_property
.. Einfach ausgedrückt ist hier, um anderen zu helfen, diese Falle zu vermeiden.Der übliche Weg wäre, das Attribut zu einer Eigenschaft zu machen und den Wert bei der ersten Berechnung zu speichern
import time class Foo(object): def __init__(self): self._bar = None @property def bar(self): if self._bar is None: print "starting long calculation" time.sleep(5) self._bar = 2*2 print "finished long caclulation" return self._bar foo=Foo() print "Accessing foo.bar" print foo.bar print "Accessing foo.bar" print foo.bar
quelle
@property + @functools.lru_cache()
? Der quasi-private Attributweg scheint an Java / Setter / Getter zu erinnern; Meiner bescheidenen Meinung nach ist es pythonischer, nur mit lru_cache zu dekorieren@functools.lru_cache()
würde das mit dem Argument verschlüsselte Ergebnis zwischenspeichernself
, und dies würde auch verhindern, dass diese Instanz GC-fähig wird, solange sie sich im Cache befindet.Python 3.8 enthält den
functools.cached_property
Dekorator.Dieses Beispiel stammt direkt aus den Dokumenten:
from functools import cached_property class DataSet: def __init__(self, sequence_of_numbers): self._data = sequence_of_numbers @cached_property def stdev(self): return statistics.stdev(self._data) @cached_property def variance(self): return statistics.variance(self._data)
Die Einschränkung besteht darin, dass das Objekt mit der Eigenschaft, die zwischengespeichert werden soll, ein
__dict__
Attribut haben muss , das eine veränderbare Zuordnung ist und Klassen mit ausschließt,__slots__
sofern dies nicht__dict__
in definiert ist__slots__
.quelle
class MemoizeTest: _cache = {} def __init__(self, a): if a in MemoizeTest._cache: self.a = MemoizeTest._cache[a] else: self.a = a**5000 MemoizeTest._cache.update({a:self.a})
quelle
Sie könnten versuchen, sich mit Memoisierung zu befassen. Wenn Sie einer Funktion dieselben Argumente übergeben, wird das zwischengespeicherte Ergebnis zurückgegeben. Weitere Informationen zur Implementierung in Python finden Sie hier .
Abhängig davon, wie Ihr Code eingerichtet ist (Sie sagen, dass er nicht von allen Instanzen benötigt wird), können Sie auch versuchen, ein Fliegengewichtsmuster oder ein verzögertes Laden zu verwenden.
quelle
Das
dickens
Paket (nicht meine) Angebotecachedproperty
,classproperty
undcachedclassproperty
Dekorateure.So speichern Sie eine Klasseneigenschaft zwischen :
from descriptors import cachedclassproperty class MyClass: @cachedclassproperty def approx_pi(cls): return 22 / 7
quelle
Der einfachste Weg, dies zu tun, wäre wahrscheinlich, einfach eine Methode zu schreiben (anstatt ein Attribut zu verwenden), die das Attribut umschließt (Getter-Methode). Beim ersten Aufruf berechnet, speichert und gibt diese Methode den Wert zurück. später wird nur der gespeicherte Wert zurückgegeben.
quelle
Mit Python 2, aber nicht mit Python 3, mache ich Folgendes. Dies ist ungefähr so effizient wie möglich:
class X: @property def foo(self): r = 33 self.foo = r return r
Erläuterung: Grundsätzlich überlade ich nur eine Eigenschaftsmethode mit dem berechneten Wert. Nach dem ersten Zugriff auf die Eigenschaft (für diese Instanz) ist
foo
sie keine Eigenschaft mehr und wird zu einem Instanzattribut. Der Vorteil dieses Ansatzes besteht darin, dass ein Cache-Treffer so billig wie möglichself.__dict__
ist, da er als Cache verwendet wird, und dass kein Instanz-Overhead entsteht, wenn die Eigenschaft nicht verwendet wird.Dieser Ansatz funktioniert nicht mit Python 3.
quelle