Frage zu imports

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
trequ
User
Beiträge: 6
Registriert: Mittwoch 12. August 2009, 09:31

Hallo,

ich habe folgendes Problem:
Ich möchte ein Modul das eine Klasse enthält dynamisch zur Laufzeit importieren und die Klasse verwenden.
Der Name der Klasse und des Moduls sind Identisch, nur die Klasse ist uppercase.

Beispiel:

Code: Alles auswählen

tool.py install 
Das Tool startet und ruft den Constructor der Klasse INSTALL im Modul install auf.

Code: Alles auswählen

tool.py remove
Das Tool startet und ruft den Constructor der Klasse REMOVE im Modul remove auf.
usw...

So weit alles ok, allerdings habe ich ein Problem mit den Namensräumen.
Ich habe mir eine Klasse TOOL gebaut, welche zwei private Funktionen hat: __load_modules() und __call_module()
Mein Problem ist nun, dass ich nach dem Aufruf von

Code: Alles auswählen

from install import *
in der Funktion __load_modules() zwar erfolgreich INSTALL() aufrufen kann, allerdings nicht in __call_module()

Hier sind die zwei Funktionen von meiner TOOL Klasse:

Code: Alles auswählen

 #=============================================================================
  # module loader
  #=============================================================================
  def __module_loader(self):
    print ">> Start: module loader"

    # Read the contents of the module directory 
    modules = []
    for module_file in os.listdir("./modules"):
      if re.search(".pyc", module_file):
        continue
      modules.append( { "name": module_file.strip(".py"),
                        "file": module_file
                      } )

    # Import all modules
    sys.path.append("./modules")
    for module in modules:
      print ">>> Importing module '"+ module["name"] + "'\t from file '" + module["file"] + "'..."
      importstring = "from install import *"
      print importstring
      exec importstring
      INSTALL()     # <-- Das ist erfolgreich

    print ">> Ended: module loader"

  #=============================================================================
  # module caller
  #=============================================================================
  def __module_caller(self):
    print ">> Start: module caller"
    INSTALL()     # <- Das ist nicht erfolgreich
    # classname = "INSTALL"
    # print ">>> Calling class '" + classname + "'..."
    # eval(classname)()
    print ">> Start: module caller"
Hier ist die Ausgabe:

Code: Alles auswählen

>> Start: module loader
>>> Importing module 'install'	 from file 'install.py'...
from install import *
>>>> install.py imported.
>>> Start: Install constructor
>>> Ended: Install constructor
>> Ended: module loader
>> Start: module caller
Traceback (most recent call last):
  File "./tool.py", line 77, in <module>
    TEST()
  File "./tool.py", line 70, in __init__
    self.__module_caller()
  File "./tool.py", line 57, in __module_caller
    INSTALL()
NameError: global name 'INSTALL' is not defined
Ich glaube ich verstehe da irgendwas bezüglich der Gültigkeit im Namespace nicht.
Ich dachte bisher immer, wenn ich etwas mit from x import * importiere, ist es dann global verfügbar.
Ist es aber anscheinend nicht. Wie mache ich es dann global verfügbar?

Danke
Daniel
Zuletzt geändert von Anonymous am Samstag 7. August 2010, 14:38, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Shaldy
User
Beiträge: 123
Registriert: Sonntag 2. März 2008, 22:49

Code: Alles auswählen

eval(classname)
Wird einfach nur

Code: Alles auswählen

INSTALL
und damit kann Python afaik nichts anfangen. Versuch mal

Code: Alles auswählen

eval(classname + "()")
Dann rufst du ja auch wirklich den Konstruktor auf.

EDIT: Args, hab mich verlesen. Schau mal mit dem Debugger, ob INSTALL zur Zeit des Funktionsaufrufes überhaupt schon bekannt ist, da liegt nämlich der Fehler.
Dies ist keine Signatur!
BlackJack

@trequ: Der Quelltext ist ziemlich "unpythonisch". In welcher Sprache willst Du denn da *eigentlich* programmieren!?

Einrückung ist per Konvention vier Leerzeichen pro Ebene. Klassennamen werden nicht durchgängig gross geschrieben, sondern in "MixedCase". Das müsste also `Tool`, `Remove`, und `Install` heissen. Wobei `Tool` ziemlich nichtssagend ist und `Remove` und `Install` keine guten Klassennamen sind, denn Klassen modellieren "Dinge" und keine Tätigkeiten.

Das nächste sind "private" Attribute: Die doppelten Unterstriche sind nicht dafür gedacht. Implementierungsdetails werden konventionell nur mit einem führenden Unterstrich kenntlich gemacht. Doppelte sind für Mixin-Klassen gedacht, um versehentliche Namenskollisionen zu vermeiden.

Sternchen-Importe sollte man vermeiden. Genauso wie das zusammenbasteln von Quelltext um ihn dann mit `eval()` oder ``exec`` auszuführen.

Keine der beiden "Methoden" sind auch wirklich Methoden. Warum stecken die in einer Klasse!?

Bei `__module_loader()` filterst Du '*.pyc'-Dateien aus, aber alles andere nicht. Das ist sehr fehleranfällig, weil da ja auch noch andere Dateien liegen könnten. Viele Texteditoren legen zum Beispiel Sicherungskopien an. Wenn man mit so einem Editor eine '*.py'-Datei in dem 'modules'-Verzeichnis ändert, wird am Ende auch versucht eine vorhandene '*.py.bak' oder '*.py~' zu importieren. Das Filterkriterium sollte man also besser positiv formulieren und nur das in die Liste aufnehmen was man auch wirklich haben möchte. Und für so einfache Tests bitte nicht das `re`-Modul anwerfen. Zeichenketten haben eine `endswith()`-Methode. Ansonsten könnte man auch gleich `glob.glob()` ins Auge fassen.

`str.strip()` macht nicht dass was Du denkst:

Code: Alles auswählen

In [601]: 'plugin.py'.strip('.py')
Out[601]: 'lugin'

In [602]: 'puppy.py'.strip('.py')
Out[602]: 'u'
Vielleicht nur eine Kleinigkeit, aber Du fügst den Pfad zu den Modulen bei jedem Aufruf der Funktion zu `sys.path` hinzu. Einmal würde genügen.

Zum Importieren von Modulen von denen Du den Namen als Zeichenkette hast, gibt es im `imp`-Modul passende Funktionen. Und wenn Du in anderen Methoden darauf zugreifen willst, dann übergibt das Modul beim Aufruf oder binde es an das gemeinsame Objekt, aber bitte such nicht nach Wegen das Ganze in den globalen Namensraum zu bringen.

Du sagst `INSTALL` ist eine Klasse? Warum? Denn Du verwendest es wie eine Funktion. Wenn Du mit dem erzeugten Objekt nichts machst, ist es doch ziemlich sinnlos das als Klasse zu formulieren!?
trequ
User
Beiträge: 6
Registriert: Mittwoch 12. August 2009, 09:31

BlackJack hat geschrieben:@trequ: Der Quelltext ist ziemlich "unpythonisch". In welcher Sprache willst Du denn da *eigentlich* programmieren!?
Python. Ich bin hier noch recht neu, schreibe nicht so viel damit.
Die ganzen Python Dinge die ich bisher geschrieben habe, haben halt nicht gerade viel mit OOP zu tun, da sie von oben nach unten laufen und fertig. Das hier soll ein Konzept für eine etwas größere Anwendung werden.
Aktuell muss es nur erst mal die Basisfunktionen vorzeigen können.
BlackJack hat geschrieben:Einrückung ist per Konvention vier Leerzeichen pro Ebene. Klassennamen werden nicht durchgängig gross geschrieben, sondern in "MixedCase". Das müsste also `Tool`, `Remove`, und `Install` heissen. Wobei `Tool` ziemlich nichtssagend ist und `Remove` und `Install` keine guten Klassennamen sind, denn Klassen modellieren "Dinge" und keine Tätigkeiten.
Danke für die Info.
Ich bitte um Nachsicht, ist mein erstes Python Test-Script welches überhaupt mit Klassen und Methoden arbeitet.
BlackJack hat geschrieben:Das nächste sind "private" Attribute: Die doppelten Unterstriche sind nicht dafür gedacht. Implementierungsdetails werden konventionell nur mit einem führenden Unterstrich kenntlich gemacht. Doppelte sind für Mixin-Klassen gedacht, um versehentliche Namenskollisionen zu vermeiden.
Ok, demnach wäre _xyz() dann privat?
BlackJack hat geschrieben:Sternchen-Importe sollte man vermeiden. Genauso wie das zusammenbasteln von Quelltext um ihn dann mit `eval()` oder ``exec`` auszuführen.
Das Tool soll später mal wie folgt funktionieren:
Es gibt ein /modules Directory, welches diverse Files enthält. Standardmäßig sind 4-5 dabei und es soll möglich sein, dass dort noch weitere abgelegt werden. Und zwar ohne die Module noch irgendwie zu registrieren oder irgendwo einzutragen.
Ich muss die Module also dynamisch importieren, andere Möglichkeiten sehe ich nicht, das zu erreichen.
BlackJack hat geschrieben:Keine der beiden "Methoden" sind auch wirklich Methoden. Warum stecken die in einer Klasse!?
Amazon hat das Buch über Objektorientierte Programmierung noch nicht geliefert :(
BlackJack hat geschrieben:Bei `__module_loader()` filterst Du '*.pyc'-Dateien aus, aber alles andere nicht. Das ist sehr fehleranfällig, weil da ja auch noch andere Dateien liegen könnten. Viele Texteditoren legen zum Beispiel Sicherungskopien an. Wenn man mit so einem Editor eine '*.py'-Datei in dem 'modules'-Verzeichnis ändert, wird am Ende auch versucht eine vorhandene '*.py.bak' oder '*.py~' zu importieren. Das Filterkriterium sollte man also besser positiv formulieren und nur das in die Liste aufnehmen was man auch wirklich haben möchte. Und für so einfache Tests bitte nicht das `re`-Modul anwerfen. Zeichenketten haben eine `endswith()`-Methode. Ansonsten könnte man auch gleich `glob.glob()` ins Auge fassen.
Da es sich um Systemverwaltungs-Software handelt denke ich über die Möglichkeit, dass da irgend wer was anderes reinlegt aktuell nicht nach, da alles root gehört und sonst da keiner Schreibrechte hat. Du hast aber Recht.
BlackJack hat geschrieben:`str.strip()` macht nicht dass was Du denkst:

Code: Alles auswählen

In [601]: 'plugin.py'.strip('.py')
Out[601]: 'lugin'

In [602]: 'puppy.py'.strip('.py')
Out[602]: 'u'
Versteh ich nicht:

Code: Alles auswählen

$ python
Python 2.6.4 (r264:75706, Jun  4 2010, 18:20:31) 
[GCC 4.4.4 20100503 (Red Hat 4.4.4-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = "teststring.py blubb.py test.py"
>>> a.strip(".py")
'teststring.py blubb.py test'
Wie würdest du denn das machen?
BlackJack hat geschrieben:Vielleicht nur eine Kleinigkeit, aber Du fügst den Pfad zu den Modulen bei jedem Aufruf der Funktion zu `sys.path` hinzu. Einmal würde genügen.
Fixed. Danke.
BlackJack hat geschrieben:Zum Importieren von Modulen von denen Du den Namen als Zeichenkette hast, gibt es im `imp`-Modul passende Funktionen. Und wenn Du in anderen Methoden darauf zugreifen willst, dann übergibt das Modul beim Aufruf oder binde es an das gemeinsame Objekt, aber bitte such nicht nach Wegen das Ganze in den globalen Namensraum zu bringen.
Ich hab gerade die Doku zu imp gelesen. Werde mich mal daran versuchen.
Was den Namensraum angeht, ist mir einfach nur teilweise schleierhaft, wann etwas wo verfügbar ist.
BlackJack hat geschrieben:Du sagst `INSTALL` ist eine Klasse? Warum? Denn Du verwendest es wie eine Funktion. Wenn Du mit dem erzeugten Objekt nichts machst, ist es doch ziemlich sinnlos das als Klasse zu formulieren!?
Mein Plan war es, dass es eine Superklasse (Tool [vorläufiger Programmname]) gibt in der alle möglichen Dinge definiert werden und immer wieder benötigte Funktionen enthalten sind. Die Klassen von den Modulen wie install sind dann alles Unter-Klassen von der Superklasse.
Beim Programmstart wird dann z.B. mit

Code: Alles auswählen

tool.py install
install aufgerufen. Dann geht die Toolklasse hin und wirft den Constructor von install an, der alles weitere macht.

Ich lasse mich aber gerne von besseren Techniken überzeugen, aktuell ist das alles nur Test und Überlegungen wie man sowas am besten bauen könnte.
BlackJack

@trequ: Wenn Du Klassen noch nicht brauchst, solltest Du sie auch noch nicht auf "Vorrat" verwenden. Klassen sind kein Selbstzweck.

`_xyz` wäre quasi "privat". Wer damit was macht und sich dabei in den Fuss schiesst, ist selbst schuld.

Dass Du die Module dynamisch importieren musst, ist schon klar. Allerdings solltest Du sie dann auch dynamisch in einer Datenstruktur "registrieren" wenn Du von anderen Funktionen oder Methoden dann darauf zugreifen willst. Und es könnte Sinn machen Module und Funktionsregistration voneinander zu trennen. Also das Dein Hauptprogramm die Module importiert und falls vorhanden eine Initialisierungsfunktion aufruft, die dann aber die eigentlichen Funktionen selbst registriert. So kann es auch Module geben die gar keine Funktionalität registrieren, sondern nur gemeinsamen Code für andere Plugins enthalten, oder Module die mehr als eine Funktionalität registrieren.

Vielleicht solltest Du die Dokumentation zu `str.strip()` noch einmal gründlich lesen, *was* diese Methode *wo* entfernt. Das Argument ist als Menge von Zeichen aufzufassen und nicht als Folge von Zeichen die auch so in der Zeichenkette vorkommen müssen und `strip()` entfernt an *beiden* Enden, nicht nur hinten. Nur nochmal so als Beispiel (das Argument von `strip()` beachten!):

Code: Alles auswählen

In [603]: 'plugin.py'.strip('y.p')
Out[603]: 'lugin'

In [604]: 'puppy.py'.strip('y.p')
Out[604]: 'u'
Du müsstest halt testen ob die Zeichenkette mit '.py' endet und falls ja mittels "slice" eine Zeichenkette ohne die letzten drei Zeichen erzeugen. Alternativ `os.path.splitext()` anschauen.

Wenn `Install` eine Unterklasse von `Tool` ist, dann wirft `Tool` ganz sicher nicht den Konstruktor von `Install` an. Eher umgekehrt -- in der `Install.__init__()` wird man eventuell die `Tool.__init__()` aufrufen.

Bei den Klassennamen würden vielleicht `Plugin` statt `Tool` und `Installer` statt `Install` sinnvoller sein. Oder `InstallPlugin` oder `InstallAction`. Also irgendetwas was eher ein "Ding" beschreibt oder zumindest ein Hauptwort/Nomen ist.

Wenn man es einfach halten möchte sollte man aber vielleicht auch erst einmal bei Modulen bleiben. Also ein `plugin`-Modul mit den allgemeinen Hilfsfunktionen und ein Modul pro Plugin mit einer `install()`-Funktion, die sich zum Beispiel über eine Hilfsfunktion im `plugin`-Modul selbst registriert.
trequ
User
Beiträge: 6
Registriert: Mittwoch 12. August 2009, 09:31

Hi,

ok, sicherlich alles richtig, allerdings hilft es mir aktuell nicht weiter.
Mein Problem ist aktuell, dass nicht mal die einfachsten Test-Scripts laufen, weil ich irgend was mit dem Namespace falsch mache.

Ich hab mir mal ganz ganz einfache Testfiles gebaut und verstehs immer noch nicht:
Das hier funktioniert. Datei testa.py:

Code: Alles auswählen

#!/usr/bin/python
class TestA():
	testvar = "Hello"
	def __init__(self):
		print "> Start A"	
		print "> Testvar: " + TestA.testvar
		print "> Ended A"	

class TestB():
	def __init__(self):
		print ">> Start B"	
		print "> Testvar: " + TestA.testvar
		print ">> Ended B"	

TestA()
TestB()
print TestA.testvar
Das funktioniert genau wie erwartet:

Code: Alles auswählen

> Start A
> Testvar: Hello
> Ended A
>> Start B
> Testvar: Hello
>> Ended B
Hello
Sobald ich aber mit import arbeite, klappt gar nichts mehr:
Konkret wollte ich die zweite Klasse, die von der ersten abgeleitet ist, in ein externes Modul packen.
Datei testb.py:

Code: Alles auswählen

#!/usr/bin/python
class Test1():
	testvar = "Hello"
	def __init__(self):
		print "> Start 1"	
		print "> Testvar: " + Test1.testvar
		print "> Ended 1"	
import testc
Test1()
testc.Test2()
print Test1.testvar
Datei testc.py:

Code: Alles auswählen

#!/usr/bin/python
class Test2(Test1):
	def __init__(self):
		print ">> Start 2"	
		print ">>> Testvar: " + Test1.testvar
		print ">> Ended 2"	
Folger Fehler kommt:

Code: Alles auswählen

Traceback (most recent call last):
  File "./testb.py", line 8, in <module>
    import testc
  File "[...]/test/testc.py", line 2, in <module>
    class Test2(Test1):
NameError: name 'Test1' is not defined
Ich hab jetzt den ganzen Tag hin und her probiert, auch in dem ich die testb in der Datei testc.py per import geholt habe, aber ich kriege dieses simple Beispiel nicht zum laufen.

Es würde mir echt weiter helfen, wenn mir jemand das mit den Namensräumen erklären kann.
Die ganze andere Geschichte mit Klassen oder nicht Klassen, groß oder klein oder mittel geschriebenen Klassennamen ist zwar gut zu wissen, aber ich würde bei dem grundsätzlichen Problem (siehe 1er Post) gerne weiter kommen.
BlackJack

@trequ: Alles was auf Modulebene definiert wird, ist dort auf Modulebene auch bekannt, allerdings natürlich erst nachdem die Zeile mit der Definition abgearbeitet wurde.

In Deiner zweiten Datei wird `Test1` nirgends definiert also ist das in dem Modul nicht bekannt und führt zu einem `NameError`.

Du müsstest den Namen importieren bevor er in der Klassendefinition von `Test2` verwendet werden kann. Da bekommst Du aber auch Probleme weil sich die Module dann gegenseitig importieren müssten. Das ist auch wieder sehr kompliziert. Da ist die Frage wann was schon definiert ist, weil ja zwangsläufig in einem Modul das andere zu einem Zeitpunkt importiert werden muss, an dem das zuerst importierte Modul noch nicht vollständig alle Definitionen abgearbeitet hat.

Wenn sich Module gegenseitig importieren ist das im Allgemeinen ein Hinweis darauf, dass der Entwurf nicht gut ist. Entweder gehören dann beide Module so eng zusammen, dass es keinen Sinn macht den Inhalt auf zwei Module aufzuteilen, oder man sollte besser die gemeinsamen Abhängigkeiten in ein drittes Modul auslagern.
Antworten