Variable vorbelegen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

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?
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@pixewakb: Du meinst wahrscheinlich statt Variable Attribut? None ist der Wert für kein-Wert.
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

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???
Zuletzt geändert von pixewakb am Dienstag 14. April 2015, 09:53, insgesamt 1-mal geändert.
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

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???
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@pixewakb: Das was Du suchst ist None und nicht False!
So wie Du es beschreibst, könnte cached_property etwas für Dich sein.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

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)
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@/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.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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).
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.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@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.
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

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!?
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@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".
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

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!?
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@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]
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@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?
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.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

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?
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

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.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

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