Klasseninstanz als Attribut verwenden?

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.
Antworten
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Hallo zusammen!
Ich möchte ein Skript schreiben, mit dem ich einen Versuch auswerten kann. Der Versuchsaufbau hat viele Optionen, die aber meistens gleich sind. Für die Auswertung wiederrum hat man viele Möglichkeiten. Damit die Attribute von Versuchsaufbau und Auswertung getrennt und damit auch übersichtlicher sind, möchte ich zwei Klassen definieren: Versuchsaufbau und Auswertung. Den Versuchsaufbau möchte ich dann der Klasse Auswertung verwenden. Weil sich der Versuchsaufbau selten ändert, soll es möglich sein, dass man direkt mit der Auswertung startet, ohne sich um den Aufbau kümmern zu müssen. Ich habe mir dann folgendes Prinzip überlegt:

Code: Alles auswählen

class Versuchsaufbau:
    def __init__(self, konfiguration = 3):
        self.konfiguration = konfiguration
        # Es folgen viele Parameter

class Auswertung:
    def __init__(self, versuchsaufbau = Versuchsaufbau()):
        self.versuchsaufbau = versuchsaufbau

    def parameterbestimmung(self, messwert):
        ergebnis = messwert * self.versuchsaufbau.konfiguration
        return ergebnis
    
    #Es folgen viele Methoden

if __name__ == "__main__":
    versuch = Auswertung()
    print(versuch.parameterbestimmung(5))
Zu funktionieren scheint es auf die Art, aber es fühlt sich irgendwie nicht ganz "sauber" an, eine Klasseninstanz beim Aufrufen einer anderen Klasse als Attribut zu setzen. Darf man das so machen oder können hierbei Probleme auftreten, die ich nicht auf dem Schirm habe?
Zuletzt geändert von Anonymous am Samstag 4. Juni 2016, 18:42, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@aug_lager: Also das was Du *beschreibst* ist kein Problem, das ist sogar normal, denn wenn man das nicht machen könnte/würde, bedeutete das ja das man nur Grunddatentypen als Attribute haben könnte. Was Du allerdings *machst* ist falsch/gefährlich, denn Defaultargumente werden nur einmal ausgewertet, nämlich wenn die ``def``-Anweisung ausgeführt wird, und *nicht* jedes mal wenn die Funktion oder Methode aufgerufen wird. Es gibt also nur *ein* `Versuchsaufbau`-Exemplar für *alle* `Auswertung`-Exemplare bei denen dieses Argument nicht übergeben wurde. Das kann zu Überraschungen führen. Daher nimmt man an der Stelle üblicherweise `None` als Defaultwert und testet in der Funktion oder Methode dagegen und erstellt gegebenfalls ein Exemplar.

Zudem ist der Begriff „Klasseninstanz“ falsch. Du bindest da einfach nur eine Instanz (eigentlich eine falsche Übersetzung von „instance“) an ein Attribut. Eine Klasseninstanz wäre wahrscheinlich eine Klasse selbst, denn das sind in Python ja auch Objekte.
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Ich glaube ich kann dir nicht ganz folgen... :K Was du beschreibst ist doch eigentlich auch das, was ich erreichen möchte. Es gibt einen Standard-Versuchsaufbau, ich kann den hundert mal benutzen, ohne dass sich da etwas ändern würde. Deswegen lege ich den als Defaultargument fest. Die Auswertung erfolgt ja für einen bestimmten Versuchsaufbau, also gibt es keinen Grund, den Aufbau nachträglich zu ändern. Wird der Versuch einmal doch anders aufgebaut, kann ich das Defaultargument durch ein angepasstes Versuchsaufbau-Exemplar ersetzen. Der veränderte Aufbau bleibt für dieses Auswertungsexemplar dann wieder fest. Und selbst wenn ich dann bemerke, dass ich mich beim Aufbau vertan habe, kann ich den doch nachträglich immernoch anpassen. So meine ich:

Code: Alles auswählen

>>> erster_versuch = Auswertung()
>>> erster_versuch.versuchsaufbau.konfiguration = 4
>>> erster_versuch.parameterbestimmung(5)
20
Hier ist mir eingefallen, dass die Konfiguration statt 3 eigentlich 4 war. Habe es geändert und dann den Versuch ausgewertet.
Oder hab ich dich falsch verstanden? :roll:
BlackJack

@aug_lager: Genau beim Codebeispiel erwischt Dich das Problem. Du hast damit nicht nur den Versuchsaufbau für `erster_versuch` geändert sondern für alle gleichzeitig existierenden und nachfolgenden Auswertungen. Selbst wenn Du den Versuchsaufbau nicht änderst, würde ich dringend davon abraten ein potentiell veränderliches Objekt als Defaultwert zu verwenden, sondern so etwas grundsätzlich zu vermeiden. Irgendwann fällt einem das dann doch mal auf die Füsse, und dann sucht man in der Regel eine ganze Weile bis man den Grund gefunden hat. Falls `Versuchsaufbau` unveränderlich sein sollte, würde ich mindestens vorher das Exemplar als Konstante erstellen und die als Default angeben, damit beim lesen klar(er) ist, dass es sich um ein quasi globales Objekt handelt, dass man nicht verändern sollte.
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Ahh, jetzt klingelts. Dann würde ich das also so lösen:

Code: Alles auswählen

default_versuchsaufbau = Versuchsaufbau()
default_versuchsaufbau ist dann mein Defaultargument. Sollte sich der Versuchsaufbau ändern muss ich ein neues Exemplar Versuchsaufbau mit den geänderten Bedingungen erstellen und das dann an die Auswertung übergeben. Das Problem, dass ich mir alle Auswertungen, die default_versuchsaufbau verwenden, zerschieße, sobald ich in default_versuchsaufbau etwas ändere, bleibt dann ja bestehen. Das lässt sich nicht verhindern oder? Der Nutzer muss also einfach wissen, dass default_versuchsaufbau nicht verändert werden darf und bei Bedarf ein neues Exemplar erstellt werden muss.
BlackJack

@aug_lager: Wenn es eine Konstante ist, schreibt man den Namen per Konvention komplett in Grossbuchstaben, damit der Leser auch sicher weiss, dass es eine Konstante sein soll.

Du könntest wie gesagt auch einfach `None` als Defaultwert nehmen und darauf in der `__init__()` prüfen und dort dann bei Bedarf für jede Auswertung ein neues, frisches Exemplar von `Versuchsaufbau` erzeugen falls keine übergeben wurde.

Code: Alles auswählen

class Auswertung(object):
    
    def __init__(self, versuchsaufbau=None):
        if versuchsaufbau is None:
            versuchsaufbau = Versuchsaufbau()
        self.versuchsaufbau = versuchsaufbau
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Ok, hab's mal durchprobiert und die Variante mit None ist genau das, was ich brauche. Aber jetzt merke ich, dass ich's doch noch nicht verstanden habe. Wieso funktioniert das:

Code: Alles auswählen

def __init__(self, versuchsaufbau = None):
    if versuchsaufbau is None:
        versuchsaufbau = Versuchsaufbau()
    self.versuchsaufbau = versuchsaufbau
aber das nicht:

Code: Alles auswählen

def __init__(self, versuchsaufbau = Versuchsaufbau()):
    self.versuchsaufbau = versuchsaufbau
Ich verstehe das so, dass bei beiden Varianten ein neues Exemplar Versuchsaufbau() erstellt wird, sobald ein Exemplar Auswertung() erstellt wird. Aber warum beeinflusst eine Veränderung bei der zweiten Variante alle anderen Exemplare Auswertung(), die erste Variante aber nicht? Was macht das prüfen auf None für einen Unterschied?
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

aug_lager hat geschrieben:Ich verstehe das so, dass bei beiden Varianten ein neues Exemplar Versuchsaufbau() erstellt wird, sobald ein Exemplar Auswertung() erstellt wird.
Das ist falsch. Versuchsaufbau() wird einmal erstellt, alle Aufrufe bekommen dasselbe Objekt übergeben.
BlackJack

@aug_lager: Es ist eben genau nicht der Fall das in beiden Fällen mit jedem `Auswertung`-Exemplar auch ein neues `Versuchsaufbau`-Exemplar erstellt wird. Defaultwerte werden nur *einmal* ausgewertet wenn die ``def``-Anweisung ausgeführt wird, also innerhalb der Ausführung der ``class``-Anweisung. Was ja nur einmal passiert wenn die Klasse erstellt wird. Das ist vom Effekt das selbe wie:

Code: Alles auswählen

VERSUCHSAUFBAU = Versuchsaufbau()


class Auswertung(object):

    def __init__(self, versuchsaufbau=VERSUCHSAUFBAU):
        self.versuchsaufbau = versuchsaufbau
Nur das man dem Wert vorher keinen eigenen Namen gegeben hat.
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Ach, jetzt ist der Groschen aber wirklich gefallen :D . Vielen Dank für eure Hilfe. Das sollte ich wirklich behalten und am besten gleich ein paar andere Skripte durchgehen, von denen ich jetzt weiß, dass sie Probleme machen könnten. :roll:
Antworten