Seite 1 von 2

Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 07:01
von pixewakb
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?

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 07:39
von Sirius3
@pixewakb: Du meinst wahrscheinlich statt Variable Attribut? None ist der Wert für kein-Wert.

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 09:40
von pixewakb
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???

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 09:52
von pixewakb
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???

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 09:59
von Sirius3
@pixewakb: Das was Du suchst ist None und nicht False!
So wie Du es beschreibst, könnte cached_property etwas für Dich sein.

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 10:09
von /me
None ist definitiv der richtige Wert.

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)

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 12:04
von snafu
@/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.

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 12:30
von snafu
Und falls man trotzdem Caching haben möchte:

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)
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).

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 13:15
von BlackJack
@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.

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 13:53
von snafu
@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.
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.
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.

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 14:29
von pixewakb
Ich versuche mal mein Problem mit Pseudocode zu erläutern:

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
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!?

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 14:34
von Sirius3
@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".

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 14:44
von pixewakb
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!?

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 14:56
von Sirius3
@pixewakb: Du hast recht, das ist natürlich für Dich überflüssig. Die einfache Form sieht so aus:

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
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 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]

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 23:01
von snafu
@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?

Re: Variable vorbelegen

Verfasst: Dienstag 14. April 2015, 23:08
von BlackJack
@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.

Re: Variable vorbelegen

Verfasst: Mittwoch 15. April 2015, 00:31
von snafu
Dann für die Nachwelt mal etwas umgeschrieben:

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
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.

Re: Variable vorbelegen

Verfasst: Mittwoch 15. April 2015, 06:54
von pixewakb
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?

Re: Variable vorbelegen

Verfasst: Mittwoch 15. April 2015, 07:53
von /me
pixewakb hat geschrieben: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.

Re: Variable vorbelegen

Verfasst: Mittwoch 15. April 2015, 08:01
von Sirius3
@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:

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
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.