Lassen sich Instanzattribute vor Instanziierung abfragen?

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
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Guten Morgen!

Kann ich einer Klasse, z. B.

Code: Alles auswählen

class Morning(object):
    def __init__(self, coffee=True, beer=False):
        self.coffee = coffee
        self.beer = beer
die Attributnamen `coffee` und `beer` entlocken?

Momentan würde ich es so

Code: Alles auswählen

class Morning(object):
    NAMES = ('coffee', 'beer')
    ...
machen. Gibt es noch 'ne andere Möglichkeit?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo mutetella,
woher soll denn Python wissen, was in __init__ gemacht wird, bevor es __init__ ausführt?
Um sagen zu können, wie Du das Problem am besten angehst, solltest Du uns noch verraten, was Du mit den Attributen überhaupt anstellen willst.
xeike
User
Beiträge: 83
Registriert: Donnerstag 28. Februar 2013, 09:58

Suchst du so etwas hier:

Code: Alles auswählen

[element for element in dir(breakfast) if element not in dir(Morning)]
Mit set() würde das sicher auch eleganter gehen.

Xe
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@Sirius3
Während ich gerade versucht habe, Dir mein Problem zu schildern, kam ich selbst auf die Lösung. Ich war mal wieder nicht in der Lage, aus 2 Richtungen zu denken... :wink:
Ich habe eine `Recurrence` Klasse und leite von dieser `Daily`-, `Weekly`-, und `MonthlyRecurrence` ab. Neben den Basisattributen von `Recurrence` haben die Unterklassen jeweils weitere Attribute. Zum Erstellen einer monatlichen Wiederholung bekomme ich vom Parser z. B.

Code: Alles auswählen

{'recurrence': 'm', 'interval': '2', 'weekday': 'mo', 'weekday_position': '0'}
Um daraus ein Exemplar von `MonthlyRecurrence` zu erstellen wollte ich über dessen Attribute gehen und mit dem dictionary abgleichen. Natürlich kann ich aber auch über das dictionary gehen und nicht vorhandene Attribute in der Klasse handeln... blöd von mir!

@xeike
Nein, das suche ich nicht. Zumal ich ursprünglich versucht hatte, an die Attributnamen eines potentiellen Exemplares `breakfast` zu gelangen. Würde `breakfast` bereits existieren, würde ich zum Abfragen von Attributnamen auch nicht `dir()` sondern `__dict__` verwenden.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@mutetella: so etwas löst man in Python einfach, in dem man versucht ein Objekt mit diesen Keywords zu erstellen:

Code: Alles auswählen

class MonthlyRecurrence(object):
    def __init__(interval, weekday, weekday_position):
        pass

RECURRENCES = {
    'd':DailyRecurrence,
    'w':WeeklyRecurrence,
    'm':MonthlyRecurrence,
}

args = {'recurrence': 'm', 'interval': '2', 'weekday': 'mo', 'weekday_position': '0'}
recurrence = RECURRENCES[args.pop('recurrence')](**args)
Jetzt fehlt nur noch das Exceptionhandling um bei TypeError und KeyError eine sinnvolle Fehlerbehandlung zu machen.
xeike
User
Beiträge: 83
Registriert: Donnerstag 28. Februar 2013, 09:58

mutetella hat geschrieben:Würde `breakfast` bereits existieren, würde ich zum Abfragen von Attributnamen auch nicht `dir()` sondern `__dict__` verwenden.
Ah, danke, wieder was dazu gelernt. :)

Xe
BlackJack

@xeike: Lern das lieber nicht denn `dir()` ist der offzielle Weg mit dem man an die Attribute kommt. `__dict__` funktioniert nur bei Typen die auch tatsächlich ein solches Attribut besitzen und berücksichtigt auch nicht das Ergebnis einer eventuell vorhandenen `__dir__()`-Methode. Wenn es zu einem direkten Zugriff auf einen „__magic__”-Namen eine Alternative gibt, sollte man in der Regel die Variante mit weniger Magie wählen.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@BlackJack:
Wenn ich die Attributnamen eines Klassenexemplares abfragen möchte, dann bevorzuge ich `__dict__`, da ich dann nicht prüfen muss, ob es sich um ein Klassenattribut oder ein mit dem Exemplar erzeugtes Attribut handelt.
Und solange eine Klasse nicht mit `__slots__` hantiert, besitzt das Exemplar doch auch ein `__dict__`, oder?

Welchen Weg gehst Du, wenn Du Attributnamen eines Exemplares erhalten möchtest?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: `__slot__`\s sind eine Möglichkeit, in C implementierte Datentypen, die sich nicht die Mühe machen ein `__dict__` zur Verfügung zu stellen eine andere. Und dann gibt es ja noch `__getattr__()` und `__getattribute__()` die einem entgehen können.

Und das bezieht sich jetzt auf CPython. Soweit ich weiss ist `__dict__` kein garantiertes Attribut, es kann also auch Python-Implementierungen komplett ohne dieses Attribut geben.

Attributnamen: `dir()`.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@BlackJack
Wenn ich Dich richtig verstehe,
  • gibt es keinen allgemein gültigen Weg, um ausschließlich an Attribute eines Klassenexemplares zu gelangen, so wie es, falls vorhanden, via `__dict__` möglich ist.
  • liegt es am Entwickler, über ein Klassenattribut oder die Verwendung von `__slots__` eine Möglichkeit zu schaffen, Attributnamen potentieller Exemplare abrufbar zu machen.
Zum Beispiel:

Code: Alles auswählen

class Miracle(object):
    NAMES = ('foo', 'bar')
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
oder

Code: Alles auswählen

class Miracle(object):
    __slots__ = ('foo', 'bar')
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
Inwieweit mir `__getattr__()` / `__getattribute__()` bei der Ermittlung eines Attributnamens weiterhilft, verstehe ich nicht.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@mutetella: es macht keinen Unterschied, ob es sich um ein Klassenattribut oder ein Attribut einer Instanz der Klasse handelt. Solange ich Attribute nur lese, verhalten sie sich absolut gleich, wenn ich ein Attribut schreibe, wird es immer als Attribut der Instanz geschrieben. Mit »__dict__« nimmst Du Dir nur die Freiheit, die Implementierung der Klasse irgendwie zu verändern (und sei es nur aus einem Attribut ein Property zu machen).
BlackJack

@mutetella: `__getattr__()` und `__getattribute__()` helfen Dir nicht weiter Attributnamen zu ermitteln sondern die können auch Attribute zur Verfügung stellen die man nicht über `__dict__` ermitteln kann. Properties hatten wir auch noch nicht angesprochen. Da es diese vielen verschiedenen Wege gibt Attribute zu setzen, ist IMHO der sauberste, weil zuverlässigste, Weg so etwas wie `Miracle.NAMES`. Explizit vs. Implizit.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sirius3 hat geschrieben:

Code: Alles auswählen

class MonthlyRecurrence(object):
    def __init__(interval, weekday, weekday_position):
        pass

RECURRENCES = {
    'd':DailyRecurrence,
    'w':WeeklyRecurrence,
    'm':MonthlyRecurrence,
}

args = {'recurrence': 'm', 'interval': '2', 'weekday': 'mo', 'weekday_position': '0'}
recurrence = RECURRENCES[args.pop('recurrence')](**args)
So in der Art hätte ich es auch vorgeschlagen. Das Dispatching hätte ich allerdings ein bißchen anders gemacht:

Code: Alles auswählen

RECURRENCES[identifier](**options)
Das ist IMHO halbwegs sauber und kommt ohne die problematische Introspektion aus.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@snafu
Weshalb kommt Dein `identifier` ohne Introspektion aus? Irgendwo muss der Verweis ja erstellt werden? Oder meinst Du "problematisch" im Blick auf eine Fehlerbehandlung?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@mutetella: wenn Du Python einfach prüfen läßt, ob alle Optionen von __init__ Deiner Klasse akzeptiert werden mußt Du nicht selbst einen Mechanismus entwickeln, die Attribute der Klasse herauszufinden. Alternativ kannst Du auch eine Klassenmethode »create_from_options« definieren, um das Erzeugen der Objekte mit externen Parametern sauber von einem internen Aufruf zu trennen.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@mutella: Ja klar wird von Python am Ende auch Introspektion verwendet, um auf die Instanzattribute zuzugreifen. Dies verbirgt sich ja letztlich nur hinter einer höheren Abstraktionsschicht. Und genau diese höhere Schicht sollte man als Programmierer benutzen. Das was du versuchst (quasi "manuelle" Introspektion), ist nur unnnötig kompliziert und fehleranfällig. Ich sehe da keinen Mehrwert gegenüber dem "offiziellen" Weg des gezeigten Dictionary-Unpackings via `**options`. Warum sollte man sich Probleme machen, die nicht vorhanden wären, wenn man sich an gewisse Standards halten würde...?

Ich verstehe auch überhaupt nicht, warum man in diesem Zusammenhang schon vor der Erstellung einer Instanz etwas über die zu erwartenden Attribute wissen muss. Eine Klasse hat normalerweise eine klar definierte Schnittstelle. Durch Typprüfung bzw aufgrund des Klassennamens kann man daher eine recht klare Aussage über die Art und Reihenfolge der Parameter machen, die für eine Initialisierung der Klasse nötig sind (also z.B. was an `__init__` übergeben werden muss). Diese Information sollte ein Anwender deines Programms kennen, meist indem er die Dokumentation liest. Wenn er dann sagt "ich möchte jetzt ein Objekt vom Typ XY erzeugen", dann muss eigentlich klar sein, dass von ihm auch die dementsprechend korrekten Parameter erwartet werden. Der übliche Weg wäre es, diese vom Anwender übergebenen Parameter "auf doof" an die Initialisierungsmethode der passenden Klasse weiterzureichen. Kommt es dann bei der Initialisierung zu irgendwelchen Schwierigkeiten, würde die Klasse eine Exception werfen. Und mit dieser Exception könnte man dann auf geeignete Weise umgehen. Bei einem Kommandozeilen-Tool würde man vielleicht eine angepasste Meldung in STDERR schreiben, oder sowas, damit der Benutzer nicht direkt mit den "nackten" Python-Tracebacks in Berührung kommt. Diese Meldung sollte sich aber aus dem Umstand ergeben, dass man dem Inhalt der Exception vertraut und *nicht*, dass man vorher die Parameter auf Korrektheit geprüft hat. Wenn man dies nämlich vorher tut, dann hat man an 2 verschiedenen Stellen im Programm eine solche Prüfung: Einmal innerhalb der Initalisierungsmethode der Klasse, wo Python einen gewissen Teil selbst übernimmt und man vielleicht zusätzlich noch ein paar Typ- und Wertprüfungen dazuprogrammiert hat. Und dann noch *zusätzlich* eine Vorab-Prüfung, weil man schlau sein wollte und aufgrund von Inspizierung der Instanzattribute irgendwelche allgemeinen Fehlermeldungen a la "Parameter `abc` ist nicht vorhanden" für den Anwender ausgibt. Ich nehme doch mal an, dies ist der Hintergrund deines Vorhabens, oder bin ich auf dem falschen Dampfer?
Antworten