Klassen aus einer Liste heraus instanziieren. Darf man das?

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:

Hallo,

ich habe ein Modul 'recurrence', das verschiedene Klassen beinhaltet, mit denen ich in meinem Kalenderprogramm Wiederholungstypen darstelle. Momentan gehören dazu die Standardwiederholungen wie täglich, wöchentlich etceterapepe.
Ich möchte mir die Möglichkeit offen halten, das Modul einmal um weitere Klassen zu erweitern, ähnlich einem Plugin.
Meine erste Überlegung, eine mögliche recurrence aufzurufen, ist folgende:

Code: Alles auswählen

import recurrence

RECURRENCE = (recurrence.DailyRecurrence, 
    recurrence.WeeklyRecurrence, recurrence.MonthlyRecurrence,
    recurrence.YearlyRecurrence)

recurrence = RECURRENCE[i](parameter)
Sind solche Sachen in Ordnung, handel ich mir damit Probleme ein oder geht das vielleicht sogar viel einfacher?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Die Frage ist doch, was gewinnst Du damit? Du musst in Deinem Falle jetzt nicht mehr den Namen der Klassen kennen, sondern eben den Index. Ist das irgend wie nützlich?

(Zudem müßte man die Namen im Moment ja doch kennen, um das Tupel zu füllen :P )
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Du hast Recht. Alles noch überhaupt nicht ausgegoren... :(

Ich stelle mir vor, dass ich in meiner Konfiguration festlegen kann, dass es z. B. 2 Wiederholungstypen, namentlich DailyRecurrence und WeeklyRecurrence, gibt. Jetzt hab' ich aber keine Vorstellung davon, wie ich aus einem String 'DailyRecurrence', den ich aus meiner Konfiguration einlese, die Anweisung

Code: Alles auswählen

instance = stringfromconfigfile(**parameter)
aufrufen kann.

Bin ratlos!
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mutetella hat geschrieben: Ich stelle mir vor, dass ich in meiner Konfiguration festlegen kann, dass es z. B. 2 Wiederholungstypen, namentlich DailyRecurrence und WeeklyRecurrence, gibt. Jetzt hab' ich aber keine Vorstellung davon, wie ich aus einem String 'DailyRecurrence', den ich aus meiner Konfiguration einlese, die Anweisung

Code: Alles auswählen

instance = stringfromconfigfile(**parameter)
aufrufen kann.
Ach so, na das ist doch kein Problem! Erstelle Dir ein Dictionary, in dem Du Namen für die Klassen auf die Klassenobjekte mappst:

Code: Alles auswählen

RECURRENCIES = {
    "daily": DailyRecurrence,
    "weekly": DailyRecurrence,
    ...
}
Nun kannst Du anhand eines bekannten Strings (z.B. aus einer ini-Datei) ein Objekt der korespondierenden Klasse erstellen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@mutetella: Das Modul `recurrence` ist ein Objekt und die Klassen sind Attribute davon. Und es gibt die Funtktion `getattr()`. Hilft das?
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

BlackJack hat geschrieben:Und es gibt die Funtktion `getattr()`. Hilft das?
Bin ich mir noch nicht sicher... :) Ich hab' probiert, damit an die potentiell vorhandenen Klassen zu gelangen. Aber wie der Name schon sagt: Die Funktion gibt mir das Attribut, das ich explizit angeben muss, zurück. Da komm' ich also nicht wirklich weiter bzw. lässt sich das auch in der Konfiguration festlegen. Oder hast Du an was anderes gedacht?
Hyperion hat geschrieben:Ach so, na das ist doch kein Problem!
Nein, das ist kein Problem. Die Frage für mich war eben, ob 'sowas' überhaupt gemacht werden soll oder ob man damit früher oder später auf die Nase fällt. Ich hab' Aufrufe wie

Code: Alles auswählen

recurrence = RECURRENCE[i](**parameter)
oder wie Dein Beispiel

Code: Alles auswählen

RECURRENCIES['daily'](**parameter)
noch nicht gesehen. Deshalb war ich mir nicht sicher, ob das ein gängier Weg ist oder es vielleicht elegantere Lösungen gibt...

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mutetella hat geschrieben:
BlackJack hat geschrieben:Und es gibt die Funtktion `getattr()`. Hilft das?
Bin ich mir noch nicht sicher... :) Ich hab' probiert, damit an die potentiell vorhandenen Klassen zu gelangen. Aber wie der Name schon sagt: Die Funktion gibt mir das Attribut, das ich explizit angeben muss, zurück. Da komm' ich also nicht wirklich weiter bzw. lässt sich das auch in der Konfiguration festlegen. Oder hast Du an was anderes gedacht?
BlackJack hat es Dir doch sogar schon mit den passenden "Mappings" der Komponenten geschildert. Klassen sind eben auch Attribute im Kontext eines Moduls.

Code: Alles auswählen

In [14]: import collections

In [16]: getattr(collections, "deque")
Out[16]: <type 'collections.deque'>
deque ist eben eine Klasse und keine Funktion ;-)
Nein, das ist kein Problem. Die Frage für mich war eben, ob 'sowas' überhaupt gemacht werden soll oder ob man damit früher oder später auf die Nase fällt. Ich hab' Aufrufe wie

Code: Alles auswählen

recurrence = RECURRENCE[i](**parameter)
oder wie Dein Beispiel

Code: Alles auswählen

RECURRENCIES['daily'](**parameter)
noch nicht gesehen. Deshalb war ich mir nicht sicher, ob das ein gängier Weg ist oder es vielleicht elegantere Lösungen gibt...
Nunja, BlackJacks Ansatz ist sicherlich der elegantere, wenn man die Namen 1:1 aus Python übernimmt. Solche Aufrufe habe ich schon mal im Kontext von "werkzeug" gesehen; afair ging es da um das Mapping von URLs auf Endpoints, die letztlich Funktionen in einem separaten Modul "views" sind. URL Dispatching wäre also ein Beispiel dafür.

Mir ist halt noch nicht klar, was Du da exakt "lösen" willst. Also geht es um Persistenz von Deinem Kalender oder doch um etwas anderes?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Auch wenn ich ständig versuche, das zu verbergen: Von vielem, das zu schreibst, habe ich keine bzw. bestenfalls eine halbe Ahnung... :?
Ich möchte aber keinesfalls so unwissend bleiben! Darum werd' ich jetzt mal nachhaken:
Hyperion hat geschrieben:BlackJack hat es Dir doch sogar schon mit den passenden "Mappings" der Komponenten geschildert.
Meinst Du damit BlackJacks Aussage:
BlackJack hat geschrieben:... `recurrence` ist ein Objekt und die Klassen sind Attribute davon...
?
Hyperion hat geschrieben:Klassen sind eben auch Attribute im Kontext eines Moduls.
Das ist mir jetzt klar, nur: Woher komme ich an die Info, welche Klassen sich im Modul befinden? Um 'getattr()' zu verwenden muss ich ja bereits den Namen des Attributes wissen.
Hyperion hat geschrieben:

Code: Alles auswählen

In [14]: import collections

In [16]: getattr(collections, "deque")
Out[16]: <type 'collections.deque'>

deque ist eben eine Klasse und keine Funktion ;-)
Woran siehst Du das?
Hyperion hat geschrieben:Nunja, BlackJacks Ansatz ist sicherlich der elegantere, wenn man die Namen 1:1 aus Python übernimmt.
Meinst Du mit 1:1 aus Python das, was ich noch nicht verstanden habe? Mit 'getattr()' irgendwie an die Recurrence-Klassen aus dem recurrence-Modul zu gelangen? Drehen wir uns im Kreis? :?
Hyperion hat geschrieben:Mir ist halt noch nicht klar, was Du da exakt "lösen" willst.
Vielleicht ist mein Problem tatsächlich viel einfacher zu lösen, als ich es wahrhaben möchte. Wer möchte am Ende auch sagen müssen, dass sein Programm einfach zu lösen war... :wink:
Im Sinne von "Wer schreibt, schaut sich beim Denken zu" versuche ich nochmal, mein Problem zu formulieren:
Ich habe ein Modul recurrence, das verschiedene Klassen zur Wiederholung von Terminen bereithält. Ich möchte die Möglichkeit, weitere Wiederholungstypen zu definieren, ähnlich wie man das von Plugins kennt.
Jeder Termin wird durch eine Instanz der Klasse Entry() dargestellt und erhält ein Attribut 'recurrence', das je nach Wiederholungstyp auf eine Klasse aus dem recurrence-Modul verweist.
Wie lässt sich nun ermitteln, welche Wiederholungen verfügbar sind? Meine erste (und bisher einzige) Idee war eben, die vorhandenen Klassen 'händisch' in die Konfiguration einzutragen. Mein Gefühl sagt mir aber, dass sich das auch schöner lösen lässt.
Vielleicht mit 'getattr()'? Aber wie lässt sich dieser Ansatz von BlackJack als Lösung meines Problems verwenden? Genau da hänge ich nach wie vor... :(

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

@mutetella: Mein Ansatz funktioniert so einfach natürlich nur, wenn die Recurrence-Klasse alle in dem Modul sind, und die Namen bekannt sind. Für Plugins wird es etwas schwieriger. Da braucht man dann doch eine Datenstruktur die "zentral" Namen auf Klassen abbildet.

Da gibt es verschiedene Wege. Zum Beispiel in dem Modul wo die Basis-Recurrence-Klassen drin sind, ein Dictionary das Namen auf Klassen abbildet und Quelltext der die Basisklassen dort einträgt. Zum Beispiel über eine Funktion in dem Modul, über die man neue (Name, Klasse)-Paare dort eintragen kann. Oder auch allgemeiner gesprochen (Name, "callable")-Paare. Funktion deshalb, weil man dort einen Test einbauen kann, ob der Name schon einmal verwendet wurde, damit nicht schon vorhandene Namen "überschrieben" werden ohne das der Benutzer mindestens eine Warnung bekommt.

Und Plugins könnten darüber dann weitere Recurrence-Klassen registrieren.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Die Frage ist ja auch, ob das ganze zur Laufzeit erfolgen soll, oder man es dem Entwickler schon zumuten kann, seinen Code in das bestehende Modul selber einzutragen.

Alternativ könnte man es wie Pygments über EntryPoints lösen, auch wenn ich das noch nicht wirklich durchschaut habe :-D (Ich habe es immer nur hinbekommen, meine Module bekannt zu machen, indem ich ein Script im Modulordner hab laufen lassen, das wohl den eigenen Quellcode modifiziert und dort die Klassen einträgt)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

BlackJack hat geschrieben:Mein Ansatz funktioniert so einfach natürlich nur, wenn die Recurrence-Klasse alle in dem Modul sind, und die Namen bekannt sind.
Sorry, wenn ich da jetzt nochmal nachhake, aber ich glaube, ich habe die 'getattr()'-Geschichte noch nicht ganz begriffen: Wenn ich die Namen der Klassen kenne, wozu brauche ich dann 'getattr()'?
BlackJack hat geschrieben:... in dem Modul wo die Basis-Recurrence-Klassen drin sind, ein Dictionary das Namen auf Klassen abbildet ...
So werd' ich das wohl machen. Ich fand die Idee (wenn auch nicht als Dictionary sondern als Liste) so schlicht und unspektakulär... :wink:
Hyperion hat geschrieben:Die Frage ist ja auch, ob das ganze zur Laufzeit erfolgen soll, oder man es dem Entwickler schon zumuten kann, seinen Code in das bestehende Modul selber einzutragen.
Wenn es einmal soweit ist, dass eine weltweit verstreute Community Plugins zu meinem Kalenderprojekt beisteuert, dann tendiere ich eher dazu, dass jeder sein Süppchen von außen dranklebt und nicht einfach so in mein geliebtes recurrence-Modul kopiert.
Bis es aber soweit ist, können die Entwickler... ähm, werde ich neue Wiederholungstypen einfach ins Modul reinschreiben... :)
Aber im Ernst: Deine Frage wird sich mir sicher noch stellen, wenn ich mir konkret über Erweiterungen, die über den 'normalen' Funktionsumfang eines Kalenders hinausgehen, Gedanken mache. Ideen hätte ich da schon viele! Momentan geht es mir aber erst einmal darum, grundsätzlich den Weg dazu nicht durch eine in sich geschlossene Struktur zu verbauen.

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

@mutetella: Also ich hatte das so verstanden, dass Du den Namen der Klasse in einer Konfigurationsdatei stehen hast. Der also als Zeichenkette vorliegt. Dein Beispiel war ``instance = stringfromconfigfile(**parameter)``. Und dazu ist IMHO der offensichtlichste Weg `getattr()` wenn alle Klassen in einem Modul liegen:

Code: Alles auswählen

import the_module
# ...
instance = getattr(the_module, stringfromconfigfile)(**parameter)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@BlackJack:
Ok, dann ist mir jetzt schon alles klar! In dem ganzen WirrWarr hatte ich mich bereits auf eine Lösung mittels Liste bzw. Dictionary eingeschossen. Und da liegen die Klassen ja bereits 'callable' drin. Dass der 'stringfromconfigfile' letztlich zur aufrufbaren Klasse verwiesen werden muss, daran dachte ich schon gar nicht mehr.

Da zeigt sich mir mal wieder, dass ich einen Thread erst dann eröffnen sollte, wenn ich über die "Oh Gott, wie mach' ich das denn nur?"-Phase hinaus bin und dann das Problem präziser eingrenzen kann... :wink:

Jedenfalls vielen Dank für Eure Hilfe!

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