Ich habe eine Klasse, die Daten aus dem Netz lädt und bereitstellt. Im Programm wird die HTML-Seite erst nach und nach ausgewertet. Wie kann ich eine Variable vorbelegen, dass ich im Programm prüfen kann, dass die Variable noch keinen Wert hat. Dachte schon an False?
Sagen wir mal eine Variable konnte nicht belegt werden. Welchen Wert würdet ihr zuweisen. None?
Variable vorbelegen
Also ich meine Eigenschaften. Die Klasse hat viele Eigenschaften und modelliert eine HTML-SEITE, die verschiedene Werte präsentiert. Ich möchte die Daten aber nicht alle laden, sondern immer nur die Daten, die ich auch tatsächlich brauche.
Eine Div-Box enthält viele Daten. Wenn ich auf eine Eigenschaft zugreife, kann es sein, dass die Box schon ausgewertet wurde, weil eine andere Eigenschaft schon abgerufen wurde. Dann soll die Box nicht erneut ausgewertet werden.
Wenn es die div-Box nicht gibt, dann soll die Eigenschaft das irgendwie rückmelden. Da dachte ich an None???
Eine Div-Box enthält viele Daten. Wenn ich auf eine Eigenschaft zugreife, kann es sein, dass die Box schon ausgewertet wurde, weil eine andere Eigenschaft schon abgerufen wurde. Dann soll die Box nicht erneut ausgewertet werden.
Wenn es die div-Box nicht gibt, dann soll die Eigenschaft das irgendwie rückmelden. Da dachte ich an None???
Zuletzt geändert von pixewakb am Dienstag 14. April 2015, 09:53, insgesamt 1-mal geändert.
Eigenschaft = Attribut. Ich las gerade nach Sorry.
Ich möchte im Kern wissen, ob es dafür eine übliche, bewehrte Lösung geht, weil ich da einiges umstricken muss.
Dachte an etwas der Form: Wenn Attribut x == False, dann werte die zugehörige Box aus und gib x zurück. Sonst gib x zurück.
Bei numpy gibt es NaN (Not a Number). Gibt es hier etwas Vergleichbares (möglichst Standardbibliothek) und möglichst performant???
Ich möchte im Kern wissen, ob es dafür eine übliche, bewehrte Lösung geht, weil ich da einiges umstricken muss.
Dachte an etwas der Form: Wenn Attribut x == False, dann werte die zugehörige Box aus und gib x zurück. Sonst gib x zurück.
Bei numpy gibt es NaN (Not a Number). Gibt es hier etwas Vergleichbares (möglichst Standardbibliothek) und möglichst performant???
@pixewakb: Das was Du suchst ist None und nicht False!
So wie Du es beschreibst, könnte cached_property etwas für Dich sein.
So wie Du es beschreibst, könnte cached_property etwas für Dich sein.
None ist definitiv der richtige Wert.
Der folgende Code demonstriert, wie ein Attribut bei Bedarf erst berechnet wird.
Der folgende Code demonstriert, wie ein Attribut bei Bedarf erst berechnet wird.
Code: Alles auswählen
class Thing:
def __init__(self, width, length):
self.width = width
self.length = length
self._area = None
@property
def area(self):
if self._area is None:
print('calculating area') # just for demonstration
self._area = self.width * self.length
return self._area
value = Thing(3, 4)
print(value.area)
print(value.area)
@/me: Dein Beispiel zeigt jetzt aber eher etwas, wo man Caching IMHO *nicht* verwenden sollte. Denn die Berechnung ist alles andere als aufwändig und so kann auch nicht flexibel auf Änderungen an den beiden zugrundeliegenden Instanzattributen reagiert werden. Soll heißen: Wenn ``area`` einmal abgefragt wurde, dann bleibt dieses Ergebnis immer gleich. Selbst dann, wenn später an ``width`` oder ``length`` Veränderungen vorgenommen wurden. Das fände ich als Benutzer der API eher verwirrend.
Und falls man trotzdem Caching haben möchte:
Das sollte jetzt im Wesentlichen demonstrieren, dass man beim Caching manchmal etwas mehr tun muss als nur einmalig zu gucken, ob man bereits eine Berechnung durchgeführt hat. Es kommt halt darauf an, welche nachträglichen Änderungen ich an einem Objekt erlaube. Wenn ich das nicht möchte, dann muss ich die fraglichen Attribute entweder "private" machen oder explizit dokumentieren, dass zum Beispiele alle Parameter nur bei der Instanziierung übergeben werden können und dass man nachträgliche Änderungen an den Attributen unterlassen sollte.
Und wie schon erwähnt: Solche Verrenkungen bzw Einschränkungen würde ich nur dann vornehmen, wenn mir das Caching tatsächlich einen Performance-Vorteil bringt. Da muss man manchmal abwägen zwischen zusätzlicher Komplexität (hier: Caching) und Ausführungsgeschwindigkeit (= gucken, was die Zeitmessungen ergeben).
Code: Alles auswählen
class Thing:
def __init__(self, width, length):
self.width = self._old_width = width
self.length = self._old_length = length
self._area = width * length
@property
def area(self):
if self.width != self._old_width or self.length != self._old_length:
print('calculating area...')
self._area = self.width * self.length
self._old_width = self.width
self._old_length = self.length
return self._area
value = Thing(3, 4)
print(value.area)
print(value.area)
value.width = 5
print(value.area)
Und wie schon erwähnt: Solche Verrenkungen bzw Einschränkungen würde ich nur dann vornehmen, wenn mir das Caching tatsächlich einen Performance-Vorteil bringt. Da muss man manchmal abwägen zwischen zusätzlicher Komplexität (hier: Caching) und Ausführungsgeschwindigkeit (= gucken, was die Zeitmessungen ergeben).
@snafu: Die Berechnung war ja nur ein Beispiel für eine Berechnung die im Original dann natürlich aufwändiger ist. Und das zugrundeliegende <div> welches nur bei Bedarf nach dem Wert durchsucht werden soll wird sich sehr warscheinlich auch nicht ändern, also braucht man da nichts prüfen. So eine Prüfung wäre selber ja auch aufwändig wenn man zwei Elementbäume auf gleichheit testet. Wahrscheinlich macht einem das den Vorteil vom Cachen schon fast wieder kaputt. Da würde man wenn dann schon eher auch aus den anderen Werten Properties machen die das `_area` im Beispiel wieder auf `None` setzen wenn sie neu gesetzt wurden.
@BlackJack: Bezogen auf den Anwendungsfall des Threaderstellers stimme ich zu, dass eine Prüfung des kompletten Baums den Vorteil des Cachings wieder zunichte macht. Ich bezog mich jedoch vorwiegend auf das Beispiel von /me. Und selbstverständlich ist die dortige Multiplikation bloß als grobe Vereinfachung gedacht. Das ändert aber nichts daran, dass bei gespeicherten Berechnungen immer die möglichen Abhängigkeiten von anderen Parametern mitgedacht werden müssen, damit keine ungültigen Informationen aus dem Cache bezogen werden. Ob dies für den Threadersteller relevant ist, weiß ich nicht und habe ich auch nicht behauptet. Aber schaden kann dieser Hinweis ja trotzdem nicht.
Gute Idee. Das ist zwar etwas mehr Code für das Definieren der nötigen Getter und Setter, aber vermutlich trotzdem transparenter. Auch ist für die Entscheidung, ob der Cache verwendet werden soll, dann nur noch eine einfache Prüfung notwendig, ob `self._area` auf `None` steht.BlackJack hat geschrieben:Da würde man wenn dann schon eher auch aus den anderen Werten Properties machen die das `_area` im Beispiel wieder auf `None` setzen wenn sie neu gesetzt wurden.
Ich versuche mal mein Problem mit Pseudocode zu erläutern:
Wichtig ist, dass ich auf den Daten eigentlich keine Berechnungen durchführe, sondern die Klasse nur Daten aus dem Netz lädt und bereitstellt. Mit den Attributen arbeite ich dann (ggf.). Die Metadaten brauche ich immer, d. h. die werte ich immer aus. Die anderen Sachen brauche ich aber nur sporadisch, d. h. da wäre ein Herauslösen nur auf Anfrage erforderlich.
Im Moment wird immer alles ausgewertet und bereitgestellt, was teils etwas länger dauert, da möchte ich von weg. Bei der obigen Klasse kann ich z. B. prüfen, dass die Seite für die messdaten a nur ausgewertet wird, wenn die Liste noch leer ist. Ich hätte gern gewusst, ob ich das so machen kann oder ob das anders formuliert vielleicht besser ist. Aktuell hat die Klasse etwa 30 Attribute und wird wahrscheinlich, wenn ich alles unterstütze so bei 60 bis 90 liegen. Die Attribute enthalten von Ganzzahlen über Strings bis Fließkommazahlen so ziemlich alles, also nicht nur Listen oder Wörterbücher.
Wird meine Frage verständlicher!? Gegenüber dem obigen Beispiel möchte ich noch sagen, dass mein Problem ist, dass ich ja gerade nicht die Attribute mit echten Werten vorbelegen kann, sondern da die Messdaten abfragen müsste. Und das möchte ich halt nur machen, wenn es [1] angefordert wird und [2] eben nicht schon erfolgt ist. D. h. gegen die alten Daten prüfen, ist mir nicht möglich.
Das Problem mit dem None ist für mich, dass ich m. E. zwei Fälle unterscheiden muss:
a.) Wenn es noch nicht geladen wurde, müsste ich wissen, gegen was ich prüfe? Da hatte ich an Attribut ist ungeprüft, dann Attribut = False gedacht.
b.) Wenn es nicht verfügbar ist, dann glaube ich, würde None für die anderen Klassen Sinn machen.
Wird mein Problem verständlich!?
Code: Alles auswählen
class Datenset(object):
html
objekt_id # wird immer geladen
name # wird immer geladen
messdaten_a
messdaten_b
messdaten_c
def __init__(self, url):
self.html = self.seite_laden(url)
def get_messdaten_a(self):
if self.messdaten_a == []:
# Wenn die Seite noch nicht
# ausgewertet wurde, dann
# erledige das
try:
self.messdaten_a = self.auswerten(self.html)
# Wenn das Objekt keine Messdaten a
# besitzt, dann None
except:
self.messdaten_a = None
return self.messdaten_a
Im Moment wird immer alles ausgewertet und bereitgestellt, was teils etwas länger dauert, da möchte ich von weg. Bei der obigen Klasse kann ich z. B. prüfen, dass die Seite für die messdaten a nur ausgewertet wird, wenn die Liste noch leer ist. Ich hätte gern gewusst, ob ich das so machen kann oder ob das anders formuliert vielleicht besser ist. Aktuell hat die Klasse etwa 30 Attribute und wird wahrscheinlich, wenn ich alles unterstütze so bei 60 bis 90 liegen. Die Attribute enthalten von Ganzzahlen über Strings bis Fließkommazahlen so ziemlich alles, also nicht nur Listen oder Wörterbücher.
Wird meine Frage verständlicher!? Gegenüber dem obigen Beispiel möchte ich noch sagen, dass mein Problem ist, dass ich ja gerade nicht die Attribute mit echten Werten vorbelegen kann, sondern da die Messdaten abfragen müsste. Und das möchte ich halt nur machen, wenn es [1] angefordert wird und [2] eben nicht schon erfolgt ist. D. h. gegen die alten Daten prüfen, ist mir nicht möglich.
Das Problem mit dem None ist für mich, dass ich m. E. zwei Fälle unterscheiden muss:
a.) Wenn es noch nicht geladen wurde, müsste ich wissen, gegen was ich prüfe? Da hatte ich an Attribut ist ungeprüft, dann Attribut = False gedacht.
b.) Wenn es nicht verfügbar ist, dann glaube ich, würde None für die anderen Klassen Sinn machen.
Wird mein Problem verständlich!?
@pixewakb: genau dafür ist ja so eine cached-Property da. Da braucht man sich nicht um die Vorbelegung von irgendwelchen Attributen kümmern. Jeder Wert wird nur einmal dann wenn er gebraucht wird ausgewertet und kann auch jeden Wert annehmen, also egal ob None, False oder "Rumpelstilzchen".
Das wäre ergänzend Option 2: Ist mir bzw. war mir zu hoch. Sorry.
Ich versuche das mal runterzubrechen: Sagen wir mal ich habe ein Heft mit 30 DIN A4-Seiten. Im Moment geht meine Klasse hin und liest alle 30 Seiten, wenn ich nur den Titel und den Autor brauche.
Ich möchte dahin, dass ich sagen kann: Letzter Absatz, Seite 20 und erst dann die Seite 20 ausgewertet wird. Wenn ich dann sage, erster Absatz, Seite 20, dann soll das Programm die Seite, die ja ausgewertet wurde, nicht erneut prüfen, sondern mir die belegte Variable zum ersten Absatz zurückgeben.
Ich muss ganz blöd fragen: Cached Properties sieht für mich nach einer Zeitsteuerung aus. Im Kern möchte ich den Auswertungsaufwand auf das reduzieren, was ich tatsächlich brauche. Das bekomme ich mit Cached Properties hin!?
Ich versuche das mal runterzubrechen: Sagen wir mal ich habe ein Heft mit 30 DIN A4-Seiten. Im Moment geht meine Klasse hin und liest alle 30 Seiten, wenn ich nur den Titel und den Autor brauche.
Ich möchte dahin, dass ich sagen kann: Letzter Absatz, Seite 20 und erst dann die Seite 20 ausgewertet wird. Wenn ich dann sage, erster Absatz, Seite 20, dann soll das Programm die Seite, die ja ausgewertet wurde, nicht erneut prüfen, sondern mir die belegte Variable zum ersten Absatz zurückgeben.
Ich muss ganz blöd fragen: Cached Properties sieht für mich nach einer Zeitsteuerung aus. Im Kern möchte ich den Auswertungsaufwand auf das reduzieren, was ich tatsächlich brauche. Das bekomme ich mit Cached Properties hin!?
@pixewakb: Du hast recht, das ist natürlich für Dich überflüssig. Die einfache Form sieht so aus:
Wenn Du jetzt aber anfängst, dass Du verschiedene Seiten laden willst, nimmst Du am besten ein Wörterbuch, das prüft, ob die Seite schon geladen ist:
Code: Alles auswählen
class cached_property(object):
"""
Decorator that creates converts a method with a single
self argument into a property cached on the instance.
"""
def __init__(self, func):
self.func = func
def __get__(self, instance, type):
res = instance.__dict__[self.func.__name__] = self.func(instance)
return res
class Beispiel(object):
def __init__(self, url):
self.url = url
@cached_property
def contents(self):
return fetch_url(self.url)
beispiel = Beispiel('http://www.python-forum.de/')
print beispiel.contents # wird geladen
print beispiel.contents # ist im Speicher
Code: Alles auswählen
class Beispiel(object):
def __init__(self, url):
self.url = url
self.pages = {}
def __getitem__(self, page):
if page not in self.pages:
self.pages[page] = load_page(self.url, page)
return self.pages[page]
@Sirius3: Wodurch tut dein Code mit dem Deskriptor das, was er tut? Wird `__get__()` nur dann aufgerufen, wenn der Name des Attributes nicht im `__dict__` der Instanz gefunden wird? Und wird durch Hinzufügen des Attributnamens mitsamt des berechneten Wertes bewirkt, dass nachfolgende Attributaufrufe den Wert aus dem Dictionary verwenden? Oder gibt es eine andere Erklärung?
@snafu: `__get__()` wird immer dann aufgerufen wenn der Wert als Attribut von dem Objekt abgerufen wird. Da der Wert selber bei diesem Aufruf im `__dict__` überschrieben wird, passiert das nur einmal, denn beim nächsten Abruf ist an das Attribut ja der berechnete Wert gebunden. Ich persönlich hätte das mit dem `__dict__` wohl vermieden und ``setattr(instance, self.func.__name__, self.func(instance))`` geschrieben.
Dann für die Nachwelt mal etwas umgeschrieben:
Es wird also zunächst eine Getter-Methode mit `@cached_property` dekoriert. Diese Methode wird jedoch nicht aufgerufen, sondern es soll nur auf den Methodennamen zugegriffen werden, ähnlich wie man das bei mit `@property` dekorierten Getter-Methoden kennt. Der `@cached_property`-Dekorator sorgt dafür, dass der Methodenaufruf durch `__get__()` gewrappt wird. Und in `__get__()` wird dann die ursprüngliche Getter-Methode, nachdem sie ein Ergebnis geliefert hat, durch genau dieses Ergebnis überschrieben. Für den Benutzer sieht es dann so aus, als sei der Wert für das abgefragte Attribut schon immer der erst "auf Zuruf" berechnete Wert gewesen. Die Getter-Methode bleibt ihm also verborgen.
Sehr magisch, aber irgendwie auch cool. Muss man erstmal drauf kommen.
Es sollte aber wohl trotzdem erwähnt werden, dass sich der Unterschied zwischen `@cached_property` und `@property` selbstverständlich erst bei einem wiederholten Zugriff auf dasselbe Attribut zeigt. In vielen Fällen sollte das `@property` aus der Standardbibliothek ausreichen. Denn eine "Just-In-Time-Berechnung" hat man dort ja auch schon und kann somit potenziell unnötige Berechnungen weglassen.
Code: Alles auswählen
class cached_property(object):
def __init__(self, func):
self.func = func
def __get__(self, instance, type):
result = self.func(instance)
# Reuse result for subsequent accesses
setattr(instance, self.func.__name__, result)
return result
Sehr magisch, aber irgendwie auch cool. Muss man erstmal drauf kommen.
Es sollte aber wohl trotzdem erwähnt werden, dass sich der Unterschied zwischen `@cached_property` und `@property` selbstverständlich erst bei einem wiederholten Zugriff auf dasselbe Attribut zeigt. In vielen Fällen sollte das `@property` aus der Standardbibliothek ausreichen. Denn eine "Just-In-Time-Berechnung" hat man dort ja auch schon und kann somit potenziell unnötige Berechnungen weglassen.
Mir ist auch nicht klar, was diese Funktion macht. Es wird etwas dauern, bis ich mir den Quellcode ansehen kann.
Eine Vorbelegung mit False (nicht abgerufen), eine Prüfung, ob ein Abruf erforderlich ist und Rückgabe, ist eine schlechte Idee?
Eine Vorbelegung mit False (nicht abgerufen), eine Prüfung, ob ein Abruf erforderlich ist und Rückgabe, ist eine schlechte Idee?
False ist logisch immer noch der falsche Ansatz. Wenn der Eigenschaftswert (noch) nicht gesetzt wurde muss es None sein.pixewakb hat geschrieben:Eine Vorbelegung mit False (nicht abgerufen), eine Prüfung, ob ein Abruf erforderlich ist und Rückgabe, ist eine schlechte Idee?
@pixewakb: Dekoratoren sind dazu da, komplizierte Logik irgendwo zu verstecken: z.B diese ganze Überprüfung, ob ein Wert schon berechnet wurde und das Aufrufen einer Funktion, usw. Das wird alles hinter der internen Logik von Python beim Zugriff auf Attribute versteckt (schauen, ob ein Attribut in __dict__ existiert, wenn nicht das Attribut auf Klassenebene suchen, usw.). Natürlich kann man das auch mit zusätzlichen Abfragen schreiben:
Beachte hier, dass, weil nicht klar ist, welcher Wert für "noch nicht berechnet" stehen kann, weil prinzipiell sowohl None als auch False, als auch irgendein anderer Wert der "berechnete" Wert sein könnte, es ein zusätzliches Attribut als Flag gibt.
Code: Alles auswählen
class cached_property(object):
def __init__(self, func):
self.func = func
self.value = None
self.is_value_set = False
def __get__(self, instance, type):
if not self.is_value_set:
self.value = self.func(instance)
self.is_value_set = True
return self.value