Klassenname aus String (import, objekterzeugung)

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
Gerenuk
User
Beiträge: 69
Registriert: Donnerstag 21. Januar 2010, 22:27

Ich möchte programmieren, dass Klassen dynamisch importiert werden, wenn der Klassenname in einem string steht. Also in etwa

a="MyClass"
import a # der py file heißt genauso wie die Klasse
x=a(**myargs)

Er soll also praktisch
import MyClass
x=MyClass(**myargs)
ausführen.

Wie ginge sowas denn?? :|
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ähm, welchem Zweck dient das? Da gibt es bestimmt einen besseren Weg.
sma hatte zu etwas ähnlichem aber erst kürzlich ein schönes Beispiel geschrieben, siehe hier: http://www.python-forum.de/viewtopic.ph ... 69#p190969
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Gerenuk
User
Beiträge: 69
Registriert: Donnerstag 21. Januar 2010, 22:27

Ich möchte eine Art einfache Plugins schreiben. Also Klassen mit bestimmten Fähigkeiten. Diese sollen in einer "Konfigurationsdatei" konfiguriert und freigeschaltet werden.
Zum Beispiel
myplugins={"MyClass":myargs, "MyClass2":{"a":1,"b":2}}

Die Konfiguration und die Anmeldung der Plugins möchte dynamisch und losgelöst machen. Vielleicht gibt es Auswahlfenster oder die Plugin Namen werden aus irgendwas geparst. Dann hätte ich den Klassennamen ja nur als String da.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Schau dir doch mal die Plugins bei Mercurial an.

Statt `__import__` wie in smas Beispiel, sollte man aber lieber das `imp` Modul nutzen.
Gerenuk
User
Beiträge: 69
Registriert: Donnerstag 21. Januar 2010, 22:27

Also mit imp ist wohl der eine Teil schon leicht gelöst.

Und wie würde man es mit dem Klassennamen machen? Also das erzeugen der Objektinstanz.
BlackJack

@Gerenuk: Das Klassenobjekt mit `getattr()` vom Modulobjekt holen und aufrufen.
Gerenuk
User
Beiträge: 69
Registriert: Donnerstag 21. Januar 2010, 22:27

Ah OK. Danke. Ich glaube ich habe es verstanden.
Ich probiers dann mal aus.

Und ist diese Lösung zum Plugin programmieren denn so abwegig? Ich möchte halt maximalen Komfort und Dynamik bei der Registrierung.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Nein, das ist überhaupt nicht abwegig, aber in deinem ersten Post, kam das sehr konfus rüber. (Zudem man bei Mehrzeiligem, bzw. längerem Pythoncode auch die Python-Code-Tags beim Posten nutzen kann)
Was ich hierbei noch bemerken würde, von der **-Magie sollte man dennoch abraten und direkt eine Dictionary übergeben, welche du ja beim Parsen der Konfigurationsdatei erstellen kannst.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
lunar

@Xynon1: Es ist wenig sinnvoll, pauschal von der Verwendung von "**" abzuraten. In diesem Fall ist der Quelltext unter Verzicht auf diese Möglichkeit wahrscheinlich schwerer zu verstehen, weil man dann de facto selbst programmieren muss, was ansonsten der Interpreter implizit übernimmt: Das übergebene Wörterbuch auf Vollständigkeit prüfen, insbesondere auf überflüssige Schlüssel, die auf einen Tippfehler des Nutzers hindeuten könnten, und dessen Inhalte zu entpacken.

Es gibt im Übrigen einen semantischen Unterschied zwischen der Übergabe eines Wörterbuchs und der Übergabe von Schlüsselwortargumente. Nimmt eine Methode ein Wörterbuch entgegen, so gehe ich davon aus, dass sie sich generisch verhält, der konkrete Inhalt nicht wichtig ist, und jeder Schlüssel dieselbe Bedeutung hat. Schlüsselwortargumente dagegen sind individuell, jeder Schlüssel hat dann eine einige, spezifische Bedeutung, und andere Auswirkungen.

Das Importieren eines Objekts über eine Zeichenkette ist im Übrigen kein allzu seltener Vorgang, es gibt Implementierungen dieser Funktionalität in diversen Bibliotheken (iirc beispielsweise "werkzeug.import_string()"), die man relativ direkt übernehmen kann.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

lunar hat geschrieben: Es gibt im Übrigen einen semantischen Unterschied zwischen der Übergabe eines Wörterbuchs und der Übergabe von Schlüsselwortargumente. Nimmt eine Methode ein Wörterbuch entgegen, so gehe ich davon aus, dass sie sich generisch verhält, der konkrete Inhalt nicht wichtig ist, und jeder Schlüssel dieselbe Bedeutung hat. Schlüsselwortargumente dagegen sind individuell, jeder Schlüssel hat dann eine einige, spezifische Bedeutung, und andere Auswirkungen.
Mir fiel dabei dieser alte Thread wieder ein. Es gibt also durchaus Fälle, bei denen man darüber vortrefflich streiten kann, was Daten (generisches Verhalten) und was spezifische Namen sind. Bei den Templates würde ich dabei zu lunars Sichtweise tendieren.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@lunar
Dann verstehe ich aber nicht, warum mir immer von der Nutzung der **-Optionen in Tkinter abgeraten wurde. Dort hat doch auch jeder Schlüssel eine spezifische Bedeutung/Auswirkung und das mit Optionen gefütterte Wörterbuch hat überhaupt keine generischen Zweck, oder sehe ich das falsch?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
lunar

@Xynon1: Wenn Du möchtest, dass ich dazu etwas sage, dann musst Du mir bitte erklären, was ich mir unter der „Nutzung der **-Optionen“ in Tkinter vorzustellen habe. Nicht jeder ist bewandert in der Tkinter-API ...
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Es gibt im großen und ganzen ein Basis Widget auf dem alle anderen aufbauen und 3 Geometry Manager, auf denen Tkinter basiert. Der Aufbau sieht so aus, das man eine Widget Instanz erzeugt, konfiguriert und dann mit einem Geometry Manager platziert. Beim Instanzieren des Widgets hat man die Wahl ob und wie die Optionen für des Widgets lauten sollen, man kann die Optionen für das Widget also als Dictionary oder als Key Arguments übergeben. bzw die Option auch später noch für das Widget über eine "config"-Methode nachziehen. Bei der "config"-Methode hat man auch wieder die Wahl ob man eine Dictionary pder Key Arguments übergibt, diese werden dann entsprechend auf das Widget ausgeführt. Die Funktionsköpfe sehen im Normalfall fast immer so aus

Code: Alles auswählen

def ...(self, ..., cnf={}, **kw):
. Wenn eine Dictionary(cnf) und Keyword Arguments(kw) übergeben werden, so werden die Keywords Arguments mit in die Dictionary übernommen, bereits vorhandene Werte werden überschrieben.

Bei den Optionen für die Widgets(Label, Button,...) gibt es eine gewisse Anzahl von Optionen, diese beschreiben die Daten des Widgets. So hat man z.B. Befehl beim anklicken, Hinter-/Vordergrundfarbe, innere/äußere Ausrichtung, eventuell Bild, Höhe/Breite oder den Status des Widgets und vieles mehr. Diese Optionen sind keine direkten Attribute des Widgets, also nicht über den Punkt-Operator "." erreichbar, sind aber über "__getitem__" (mir Fehlt der genaue Begriff) erreichbar.

Dazu kommen noch die Geometry Manager, diese Besitzen jeder für sich auch nochmal völlig unterschiedliche Optionen, wie Zeile/Spalte, auf welche Seite das Widget gepackt werden soll, Abstand, Ob und wie sich das Widget strecken darf/soll usw. Diese Optionen können nicht geändert werden, um diese zu ändern muss man das Widget erst wieder von dem Widget auf das es gepackt wurde "lösen" und kann dann woanders plaziert werden. Dennoch verhält sich es mit der Übergabe der Optionen genauso wie beim Widget.

Ich hoffe das ist weitest gehend verständlich, wenn Fragen sind, her damit. :D
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

*push*

@lunar
Vergessen? Oder kannst du dich dazu nicht sicher äußern? Brauchst du mehr Informationen?
Ich würde es durchaus verstehen, wenn du dich hier nicht festlegen möchtest, dennoch würde ich gerne deine Meinung dazu hören (ähm, lesen).
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Ich glaube ein Link auf den Thread, in dem dir davon abgeraten wurde, wäre da auch hilfreich. Soweit ich mich erinnere, hast du irgendwelche Parameter in eine Liste od. ein Dict gepackt und diese/s dann direkt in der darauf folgenden Zeile wieder entpackt, weil du das irgendwie übersichtlicher fandst, war das nicht so?

Konnte den Thread gerade nichts mehr finden und will dir natürlich auch nichts Unzutreffendes unterstellen - aber irgendwie meine ich, dass sich die Kritik nicht auf den *(*)-Operator generell, sondern auf diese konkrete Art der Verwendung bezog.

Wie gesagt: Falls ich dich da mit irgendwem verwechsle, bitte ich um Nachsicht.

Besten Gruß,

brb
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Nein, du liegst da völlig richtig, das war der Thread http://www.python-forum.de/viewtopic.php?f=18&t=25302. Das habe ich ja schon eingesehen, den Grund hatte ich auch schon mehrfach dort geschildert. Das ist hier aber ein anderer Fall - dort hatte ich im Grunde keine wirkliche Wahl, hier schon.
Die meisten von "Tkinter"s-Funktionen und Klassen bieten diese aber. Ich möchte das en für alle mal in meinen Schädel bekommen. Damit ich verstehe wann man nun die eine und wann die andere Vorgehensweise nutzen sollte.

Hier stellt sich mir auch die Frage, wenn ich von einem Tkinter-Widget erbe um andere Widgets anzubieten, soll ich dann auch beide Varianten anbieten?

Momentan sehen die Ableitungen bei mir immer so aus:

Code: Alles auswählen

class OtherWidget(tkinter.Widget):
    def __init__(self, master, cnf=()):
        tkinter.Widget(self, master, cnf)
Wenn ich die zweite Variante mit anbieten sollte, müsst es ja so aussehen:

Code: Alles auswählen

class OtherWidget(tkinter.Widget):
    def __init__(self, master, cnf=(), **kw):
        tkinter.Widget(self, master, cnf, **kw)
Sollte man das wirklich tun?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
lunar

@Xynon1: Ich habe in der Tat vergessen, verzeih mir. Mehr als "kommt darauf an" kann und will ich dazu aber eigentlich nicht sagen. Ich halte wenig von Dogmen, und würde insofern nur dazu raten, die Angelegenheit pragmatisch zu sehen. Wähle die Variante, welche Dir selbst am verständlichsten erscheint. Du musst Dich auch einfach auch mal auf Dein eigenes Urteilsvermögen verlassen, und eine bestimmte Entwurfsentscheidung vertreten können. Solange Du keine offensichtlich überzeugenden Argumente gegen diese Entscheidung ignorierst, wird Dir niemand einen Strick daraus drehen, wenn Du eine bestimmte Variante vorziehst, weil sie Dir selbst besser gefällt.

Ich persönlich würde bei Tkinter-Steuerelementen wohl Schlüsselwortargumente nutzen, aber für meinen Teil spricht auch nichts gegen die Übergabe eines Wörterbuchs. Allenfalls im Bezug auf die Diskussion, welche barabbas verlinkt hat, würde ich dauerbaustelle sofort zustimmen. Die Art, auf welche Du dort die Konfiguration übergibst, finde ich unschön, weil das Wörterbuch per se eigentlich vollkommen überflüssig ist. Die Gründe, welche Du dafür genannt hast, kann ich nicht nachvollziehen, aber das mag mithin an meiner Unkenntnis von Tkinter liegen.

Allerdings gibt es in dieser Diskussion auch Aussagen, die ich nicht unterschreiben würde, beispielsweise die, dass man Wörterbücher üblicherweise als Literalform und nicht über "dict()" erzeugt. Daraus würde ich auch kein Dogma machen wollen, und grob geschätzt kommen in meinem Quelltext beide Varianten mit etwa gleicher Häufigkeit vor.

Worauf ich also hinaus will, ist, dass man es auch übertreiben kann, sowohl mit dem Beharren auf "idiomatischem Python", bzw. dem, was man selbst dafür hält, als auch mit dem "sich an anderen orientieren".
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@lunar
Das du es vergessen hattest, hatte ich mir ja schon gedacht :D
Danke für das Statement, im Grunde schon das was ich in Tkinter immer gemacht habe (bis auf das aus dem verlinkten Thread, welches wirklich nicht schön war) Ich versuche es dann nur in meiner API einheitlich zu halten und nicht so wie in der Tkinterlib.

Und "dict()"s welche ich zum initialisieren eines Dictionary genommen waren bisher immer einzeilig. Ich denke da spricht eigentlich nichts dagegen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Antworten