importlib Verständnisproblem

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
Vetsch
User
Beiträge: 7
Registriert: Sonntag 10. Juni 2018, 11:00

Hallo miteinander,

ich habe ein kleines Problem mit importlib. Ich habe einen Code in einem Modul, welcher über ein Verzeichnis iteriert und versucht, die enthaltenen Module einzubinden. Merkwürdigerweise funktioniert derselbe Code, welcher in der Befehlszeilte einwandfrei arbeitet, in einer Klasse nicht mehr.

Ich kann mir keinen Grund erklären, außer das ich einen gehörigen Knoten im Hirn habe. Folgenden Code hatte ich in die Befehlszeile eingegeben:
Python 3.6.2 (v3.6.2:5fd33b5) 32 Bit
Win7

Code: Alles auswählen

def plugin_walk( p_path ):
	tmp_wd = os.getcwd()
	os.chdir( p_path )
	for element in os.listdir(  ):
		element = element.split( '.' )
		if element[-1] == 'py':
			plug_tmp = importlib.import_module( element[0] )
			plug = plug_tmp.Plugin
	os.chdir( tmp_wd )
Code Ausschnitt aus dem eigentlichen Modul:

Code: Alles auswählen

    def plugin_walk( self ):
        tmp_wd = os.getcwd()
        os.chdir( self.plugin_path )
        for element in os.listdir( ):
            element = element.split( '.' )
            if element[-1] == 'py':
                plug_tmp = importlib.import_module( element[0] )
                plug_tmp.conf_dict.update( { 'master':self } )
                plug = plug_tmp.Plugin( plug_tmp.conf_dict )
                self.plugins.update( { element[0] : plug } )
                self.event_fire( 'datahub.plugin.add', element )
        os.chdir( tmp_wd )
Bei der zweiten Variante erhalten ich jedoch folgende Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:\Users\hasifee\Documents\pyprojects\mediahub\main.py", line 188, in <module>
    DH = DataHub( )
  File "C:\Users\hasifee\Documents\pyprojects\mediahub\main.py", line 82, in __init__
    self.plugin_walk()
  File "C:\Users\hasifee\Documents\pyprojects\mediahub\main.py", line 109, in plugin_walk
    plug_tmp = importlib.import_module( element[0] )
  File "C:\Users\hasifee\AppData\Local\Programs\Python\Python36-32\lib\importlib\__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 978, in _gcd_import
  File "<frozen importlib._bootstrap>", line 961, in _find_and_load
  File "<frozen importlib._bootstrap>", line 948, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'test_my'
Meine Intention ist es, die Klasse 'Plugin' von jedem Modul in einem Verzeichnis zu instanzieren. Wie geschrieben, kann ich einfach nicht feststellen, warum die Befehlszeilen Variante funktioniert und die in einem Modul ausgeführte Variante nicht.

Nur nebenbei, ich weiß, dass viele Fehlerüberprüfungen fehlen. Ich würde jedoch gern vorher in Erfahrung bringen, warum ich diesen Fehler erhalte.

Ich danke euch schonmal für eure Mühe und wünsche einen schönen Sonntag.
Benutzeravatar
__blackjack__
User
Beiträge: 13073
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Vetsch: `os.chdir()` ist eine ganz schlechte Idee. Das ist globaler Zustand der da geändert wird. Das skaliert nicht, denn wenn Du das änderst fällt a) anderer Code auf die Nase der darauf baut das sich das Arbeitsverzeichnis nicht einfach so ändert, und b) kannst Du dann nichts mehr aufrufen was das Arbeitsverzeichnis ändert. Das kann ganz schnell sehr unübersichtlich werden welcher Code denn nun eigentlich von welchem Verzeichnis ausgeht. Bei Fehlern kann auch ganz schnell mal passieren das so ein `os.chdir()` zum zurückwechseln nicht ausgeführt wird.

Mach ein ``os.listdir(self.plugin_path)`` stattdessen und alles ist gut.

Ist das Verzeichnis mit den Plugins denn auch in `sys.path` enthalten? Vielleicht liegt da ja der Unterschied, dass Du das von der Konsole zufällig in einem Verzeichnis ausführst in dem das der Fall ist.

Den Namen `element` in einer Schleife sowohl für ein Element aus dem Iterable als auch für das an Punkten aufgeteilte `element` zu verwenden, ist verwirrend. Ein Namen sollte im gleichen Kontext immer an die selbe Art von Werten gebunden werden. Sonst verliert man den Leser irgendwann. Ich frage mich da nämlich zum Beispiel gerade ob bei dem `event_fire()` tatsächlich die Liste oder nicht vielleicht doch besser eine Zeichenkette mit dem Modulnamen als zweites Argument übergeben werden sollte‽ Denn das zweite Element in der Liste ist ja grundsätzlich 'py', bietet also keine wirkliche Information. Hier würde sich auch die Funktion `os.path.splitext()` anbieten, die ja genau dafür gedacht ist.

Um *ein* Schlüssel/Wert-Paar in ein Wörterbuch einzufügen verwendet man nicht die `update()`-Methode.

Das verändern von `conf_dict` in dem Modul halte ich aber sowieso für fragwürdig, weil's globaler Zustand ist. Zumal das dann auch noch mal an `Plugin` aus dem Modul übergeben wird, obwohl das ja schon so Zugriff hätte, weil es im gleichen Modul ist. Es macht also eigentlich nur Sinn wenn dort nicht der selbe Wert übergeben wird, sondern ein verändertes Wörterbuch.

Grunddatentypen gehören nicht in Namen.

`plug_tmp` könnte besser `plugin_module` heissen, denn genau das ist es ja.

Könnte dann unegfähr so aussehen:

Code: Alles auswählen

    def plugin_walk(self):
        for filename in os.listdir(self.plugin_path):
            name, extension = os.path.splitext(filename)
            if extension == 'py':
                module = importlib.import_module(name)
                configuration = dict(module.configuration)
                configuration['master'] = self
                self.plugins[name] = module.Plugin(configuration)
                self.event_fire('datahub.plugin.add', name)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Vetsch
User
Beiträge: 7
Registriert: Sonntag 10. Juni 2018, 11:00

Salutos @__blackjack__,
__blackjack__ hat geschrieben: Sonntag 10. Juni 2018, 12:20 @Vetsch: `os.chdir()` ist eine ganz schlechte Idee. Das ist globaler Zustand der da geändert wird. Das skaliert nicht, denn wenn Du das änderst fällt a) anderer Code auf die Nase der darauf baut das sich das Arbeitsverzeichnis nicht einfach so ändert, und b) kannst Du dann nichts mehr aufrufen was das Arbeitsverzeichnis ändert. Das kann ganz schnell sehr unübersichtlich werden welcher Code denn nun eigentlich von welchem Verzeichnis ausgeht. Bei Fehlern kann auch ganz schnell mal passieren das so ein `os.chdir()` zum zurückwechseln nicht ausgeführt wird.

Mach ein ``os.listdir(self.plugin_path)`` stattdessen und alles ist gut.
Aus meiner Sicht war es zunächst kein Problem os.chdir zu verwenden, da diese Funktion nur im __init__ aufgerufen wird und anschliessend nicht mehr verwendet wird. Somit ändere ich kurz den globalen Zustand und stelle Ihn anschließend wieder her. Allerdings kann ich jetzt auch sehen, dass es eventuell Probleme geben könnte, wenn ich während dieser Funktion versuche weitere Standardmodule zu inkludieren. Ich berücksichtige das und stelle den Code entsprechend um.
__blackjack__ hat geschrieben: Sonntag 10. Juni 2018, 12:20 Ist das Verzeichnis mit den Plugins denn auch in `sys.path` enthalten? Vielleicht liegt da ja der Unterschied, dass Du das von der Konsole zufällig in einem Verzeichnis ausführst in dem das der Fall ist.
Das war der Hinweis den ich gebraucht habe. Danke schön. Ich habe sys.path geprüft und tatsächlich in der Befehlszeile den betreffenden Pfad gefunden während in der ausgeführten Variante dieser nicht erscheint. Das genügt mir schon als Ansatz.

Deine verbleibenden Hinweise nehme ich gern als Ratschlag entgegen, zu diesen kann ich nicht viel sagen, da die Argumentation ja für sich spricht.

Danke für deine Mühe.
Antworten