Sauberer Weg um an Pfad zum Modul zu kommen

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
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hallo!

Ich möchte mir mit einem simplen Skript den Pfad zu einem Modul anzeigen lassen ohne dafür in den Python-Interpreter zu müssen. Bisher löse ich das mit __file__.

Existiert __file__ nicht, so nimmt das Skript ein Built-in Modul an. Ansonsten wird der letzte Buchstabe abgeschnitten, um die zugehörige py statt der pyc auszugeben.

Ich persönlich finde den Weg etwas unsauber. Da sich __file__ ja auf das aktuell geladene Modul bezieht, wäre meine Lösung sogar bei der ersten Ausführung eines Moduls (wo ja noch keine pyc existiert) sogar falsch.

(Klar ließe sich der Pfad auseinanderklamüsern und auf seine Endung prüfen, aber das lasse ich jetzt mal außen vor.)

Gibt es da noch andere Möglichkeiten? Wie macht z.B. IPython das?

Code: Alles auswählen

import sys

def main():
    modname = sys.argv[1]
    mod = __import__(modname)
    try:
        print mod.__file__[:-1]
    except AttributeError:
        print 'built-in'

if __name__ == '__main__':
    main()
Zuletzt geändert von snafu am Freitag 9. Januar 2009, 17:29, insgesamt 3-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich habe das ganze jetzt doch noch etwas angepasst. Bei der Erstellung eines Testmoduls fiel mir auf, dass durch __import__ natürlich das Modul ausgeführt wird, so dass u.U. bereits Ausgaben und Programmteile ausgeführt werden, wenn es "unsauber" (sprich: ohne if __name__ == '__main__'-Konstrukt) geschrieben ist. Um dies zu vermeiden, wäre ich nun erst recht an einer anderen Möglichkeit interessiert...

Code: Alles auswählen

import sys

def main():
    modname = sys.argv[1]
    mod = __import__(modname)
    if hasattr(mod, '__file__'):
        path = mod.__file__
        if path.endswith('pyc'):
            print path[:-1]
        else:
            print path
    else:
        print 'built-in'

if __name__ == '__main__':
    main()
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Wie wäre es denn mit dem Weg vom imp Modul die Methode find_modul zu verwenden?

Code: Alles auswählen

In [1]: import imp

In [2]: imp.find_module("os")
Out[2]:
(<open file 'C:\Python25\lib\os.py', mode 'U' at 0x00EAB800>,
 'C:\\Python25\\lib\\os.py',
 ('.py', 'U', 1))

In [3]: imp.find_module("sys")
Out[3]: (None, 'sys', ('', '', 6))
Bei dieser Aktion wird Modul zum lesen geöffnet, aber nicht ausgeführt.
Tätest wahrscheinlich nicht schlecht daran das file-Objekt direkt wieder zu schließen, weiß aber nicht ob das wirklich nötig ist.
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vielen Dank. Imp hatte ich mir noch gar nicht angeschaut. Übrigens denke ich nicht, dass ein Schließen nötig ist, denn die Doku spricht erst bei load_module() davon und ich glaube kaum, dass die das eine Funktion davor einfach vergessen haben.

Code: Alles auswählen

import imp
import sys

def main():
    name = sys.argv[1]
    if imp.is_builtin(name) != 0:
        print 'built-in'
    else:
        print imp.find_module(name)[1]

if __name__ == '__main__':
    main()
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Ich würde sagen, es ist bei `load_module` erwähnt, weil man ja ein Dateiobjekt übergibt und ja nicht weiß, was die Funktion damit macht. Also wird darauf hingewiesen, dass der Aufrufer für das Schließen der Datei verantwortlich ist.
Bei `find_modul` hingegen wird eine geöffnete Datei zurückgegeben, und geöffnete Dateien sollten IMHO explizit geschlossen werden, wenn sie nicht mehr benötigt werden, auch wenn der GC sie beim Entsorgen schließt.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Überredet und etwas ausgebaut.

Code: Alles auswählen

"Locate modules"

import imp
import sys


class BuiltinException(Exception):
    "Raised when module is built-in."
    def __init__(self, module):
        self.module = module
    def __str__(self):
        return "'%s' is a built-in module." % self.module


def main():
    "Use first argument from command-line as name for module to locate."
    try:
        module = sys.argv[1]
    except IndexError:
        print >> sys.stderr, 'usage: %s module' % sys.argv[0]
        exit(1) 
    try:
        print locate(module)
    except BuiltinException, why:
        print >> sys.stderr, why
        exit(2)
    except ImportError, why:
        print >> sys.stderr, why
        exit(3)


def locate(module):
    "Try to get path of module. When module is built-in, raise Exception."  
    path = get_path(module)
    if not path:
        if is_builtin(module):
            raise BuiltinException, module
    return path


def get_path(module):
    "Return module's path. Return None if module is no file."
    f, path = imp.find_module(module)[:2]
    if f == None:
        return None
    try:
        return path
    finally:
        f.close()


def is_builtin(module):
    "Return True or False depending on module's state."
    if imp.is_builtin(module) != 0:
        return True
    else:
        return False


if __name__ == '__main__':
    main()
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier habe ich doch noch eine Frage.

imp.find_module() findet Module wie os.path nicht (also wo ein Punkt dazwischen ist). Dummerweise finde ich keinen Ansatz, dies zu implementieren. Ein stumpfes durchsuchen von sys.path wird wohl nicht immer was bringen, denn im besagten Fall heißt die zugehörige Datei z.B. posixpath.py.

Es soll also ein Modul nur geladen, aber nicht initialisiert werden. imp.load_module() bringt mich da leider nicht weiter, weil hier der Pfad benötigt wird (den zu suchen es ja gerade gilt). Ich kann mir auch nicht angucken, wie imp intern arbeitet, da dieses Modul ein Built-in ist. Jemand ne Idee?
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Statt

Code: Alles auswählen

def is_builtin(module): 
    "Return True or False depending on module's state." 
    if imp.is_builtin(module) != 0: 
        return True 
    else: 
        return False
kann man auch

Code: Alles auswählen

def is_builtin(module): 
    "Return True or False depending on module's state." 
    return imp.is_builtin(module) != 0
schreiben und dabei viel Platz sparen.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

An so Sachen wie ``os.path`` wird es immer scheitern, wenn man es nicht importieren will, weil das os-Modul eben ``sys.modules['os.path'] = path`` macht, was man ohne es wirklich zu importieren nicht rausbekommt. Und ansonsten musst du bei Packages eben mit dem äußersten anfangen und dann `find_module` machen und als Pfad den Pfad übergeben, den du vom äußersten bekommen hast. Bei "xml.etree.ElementTree" müsstest du also erst "xml" suchen und mit dem Pfad, der da zurückgegeben wird, dann nach "etree" suchen und mit dem Pfad dann wiederum nach "ElementTree" suchen.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Naja, immerhin hätte ich ja die pyc mit:

Code: Alles auswählen

sys.modules['os.path'].__file__
Womit ich aber wieder beim eingangs genannten Problem wäre...

Den letzten Teil verstehe ich nicht so wirklich. Wie soll ich von os auf posixpath.py kommen?

BTW: @derdon: Vorschlag dankend umgesetzt. ;)

Der aktuelle Stand ist übrigens hier: http://paste.pocoo.org/show/98678/

EDIT: os prüft ja auf welchem System es läuft und bei Posix wird entsprechend importiert. Das scheint ja die entscheidende Stelle zu sein.

Code: Alles auswählen

if 'posix' in _names:
    name = 'posix'
    linesep = '\n'
    from posix import *
    try:
        from posix import _exit
    except ImportError:
        pass
    import posixpath as path

    import posix
    __all__.extend(_get_exports_list(posix))
    del posix
Aber auf solche Dinge zu prüfen, halte ich für übertrieben. Naja, dann kann mein Skript das halt nicht...
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Das ist doch genau das, was ich gesagt habe: Es gibt keinen Weg, darauf zu kommen, ohne das Modul auszuführen und dann in sys.modules nachzuschauen und falls es da nicht ist, den Weg über das Eltern-Package zu nehmen.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Die Idee mit os.path habe ich endgültig verworfen. Der Vollständigkeit halber aber hier eine Variante, die auch xml.etree und ähnliches findet und os.path als NotImplementedError behandelt: http://paste.pocoo.org/show/98825/
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Was spricht gegen die Verwendung von `imp.find_module`? Hier eine Version, die auch "xml.etree" findet und dabei konsequent die Funktionen aus [mod]imp[/mod]-Modul verwendet. Die Fehlermeldungen könnte man noch anpassen, wenn man lustig ist, dass da auch die Elternpakete angezeigt werden.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vorinfo: locmod ist ein Alias für mein Modul, test.py steht für deins.

Zum einen finde ich die Ausgabe nicht immer passend bei dir:

Code: Alles auswählen

~$ python test.py xml.etree.ElementTree.xml
No module named xml
~$ locmod xml.etree.ElementTree.xml
No module named xml.etree.ElementTree.xml
Da find ich es schon besser, den gesamten Namen auszugeben. (Aber vielleicht meintest du das auch mit den Elternpaketen)

Zum anderen versagt deine Version bei Modulen unterhalb des Arbeitsverzeichnisses:

Code: Alles auswählen

~$ python test.py test.test
No module named test
~$ locmod test.test
/home/sebastian/test/test.py
Übrigens verstehe ich auch nicht, was path and [path] bringen soll. Wenn ich das teste, wird immer nur [path] benutzt. Meintest du vielleicht "or"?

Desweitern wird "realname" in deiner "find_module()" gar nicht verwendet. War das für so os.path-Geschichten gedacht?

Vermutlich gibt es einen Mittelweg, der imp.find_module so konsequent wie möglich verwendet, aber trotzdem alle Möglichkeiten abdeckt. Ich werde mal sehen, inwieweit ich das optimieren kann. Trotzdem natürlich Danke für deine Anregungen. Vielleicht hast du ja auch noch Ideen, die in die besagte Richtung gehen... :)
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

snafu hat geschrieben:Zum einen finde ich die Ausgabe nicht immer passend bei dir: [...] Da find ich es schon besser, den gesamten Namen auszugeben. (Aber vielleicht meintest du das auch mit den Elternpaketen)
Ja, genau das war gemeint, und dafür war auch ``realname`` gedacht. Eigentlich wollte ich es noch implementieren, habe mich aber dann dagegen entschieden (sowohl aus Faulheit als auch aus Klarheit: ich wollte nur die Funktionalität zeigen und nicht mit überflüssigem Code verwirren). Leider habe ich dann beim Pasten das ``realname`` übersehen, was ich dann aber wirklich keinen neuen Paste wert fand.
snafu hat geschrieben: Zum anderen versagt deine Version bei Modulen unterhalb des Arbeitsverzeichnisses:

Code: Alles auswählen

~$ python test.py test.test
No module named test
~$ locmod test.test
/home/sebastian/test/test.py
Das kann ich nur reproduzieren, wenn in "test" die "__init__.py" fehlt, was dann aber kein Versagen sondern korrektes Verhalten wäre (deine Version findet dann übrigens trotzdem "test.test"). Außerdem finde ich es eh verwirrend, sowohl ein Modul "test" zu haben (meine Version, die du wohl "test.py" genannt hast) als auch ein Package "test".
snafu hat geschrieben:Übrigens verstehe ich auch nicht, was path and [path] bringen soll. Wenn ich das teste, wird immer nur [path] benutzt. Meintest du vielleicht "or"?
Nein, das ``and`` stimmt schon so. Wenn `imp.find_module` kein ``path``-Argument übergeben wird oder selbiges ``None`` als Wert hat, wird (u.a.) ``sys.path`` durchsucht. Also wird zunächst ``path`` auf ``None`` gesetzt. Dann wird nach dem äußersten Package gesucht. Dabei soll eben ``sys.path`` komplett durchsucht werden, was es auch wird, da ``path and [path]`` zu ``None`` evaluiert wird. Danach wird ``path`` überschrieben, und zwar mit dem Pfad des Packages. Danach wird das nächstinnere Modul/Package gesucht, diesmal soll aber nur im Verzeichnis des äußeren Packages gesucht werden. Und diesmal evaluiert ``path and [path]`` eben zu einer Liste mit einem Element, dem Pfad des äußeren Packages.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

snafu, wie oft wollen wir denn noch das Spiel spielen dass du den Thread als gelöst markierst und ich die Markierung dann lösche? Vorschlag: lass es doch einfach; das ist sowieso schlauer und die Gründe dafür wurden auch mehrfach im Forum diskutiert.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ach, du warst das immer. ^^

Okay, ich werd mich dran halten. Sorry.

@Trundle: Ich lege dieses kleine Projekt erstmal auf Eis. Eventuell befass ich mich in ein paar Tagen wieder damit, vielleicht aber auch erst in ein paar Wochen. Also nicht wundern, wenn länger nichts kommt. ;)
Antworten