Importieren Sie eine beliebige Python-Quelldatei. (Python 3.3+)

72

Wie kann ich eine beliebige Python-Quelldatei (deren Dateiname beliebige Zeichen enthalten kann und nicht immer mit endet .py) in Python 3.3+ importieren ?

Ich habe imp.load_modulewie folgt verwendet:

>>> import imp
>>> path = '/tmp/a-b.txt'
>>> with open(path, 'U') as f:
...     mod = imp.load_module('a_b', f, path, ('.py', 'U', imp.PY_SOURCE))
...
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

Es funktioniert immer noch in Python 3.3, ist aber laut imp.load_moduleDokumentation veraltet:

Veraltet seit Version 3.3 : Nicht erforderlich, da Loader zum Laden von Modulen verwendet werden sollten und find_module () veraltet ist.

In der impModuldokumentation wird empfohlen, Folgendes zu verwenden importlib:

Hinweis Neue Programme sollten importlib anstelle dieses Moduls verwenden.

Was ist der richtige Weg, um eine beliebige Python-Quelldatei in Python 3.3+ zu laden, ohne die veraltete imp.load_moduleFunktion zu verwenden?

falsetru
quelle
4
Kann ich fragen, warum Sie das tun? Ich bin der Betreuer von importlib und habe versucht, Antworten von Leuten zu erhalten, warum sie imp.load_module()über eine direkte Importanweisung verwendet werden. Erwarten Sie, das Modul später nach Namen zu importieren (z. B. import a_b)? Interessiert es Sie, dass bei diesem Ansatz keine benutzerdefinierten Importeure verwendet werden? Erwarten Sie, dass das Modul über alle Funktionen verfügt (z. B. definieren __name__und __loader__)?
Brett Cannon
3
@BrettCannon, Ein Programm eines Drittanbieters ändert regelmäßig (einmal pro Stunde) eine Textdatei, die Python-Anweisungen enthält (hauptsächlich THIS='blah'wie Zeilen). Der Name der Datei wird nicht mit beendet .py. Mein Programm hat diese Datei gelesen.
Falsetru
1
@BrettCannon, mir sind keine benutzerdefinierten Importeure bekannt. Es ist mir egal, ob das Modul voll ausgestattet ist.
Falsetru
1
IOW verwendet Python als wirklich einfaches Datenstrukturformat. Danke für die Information!
Brett Cannon
1
@BrettCannon - Ich bin gerade auf einen Fall gestoßen, in dem ich Python-Code aus einem Verzeichnis importieren musste, das als Versionsnummer benannt wurde (z. B. "v1.0.2"). Obwohl dies möglich ist, wäre es höchst unerwünscht, das Verzeichnis umzubenennen. Ich habe die unten stehende Lösung von stefan-scherfke verwendet.
Andrew Miner

Antworten:

80

Ich habe eine Lösung aus dem importlibTestcode gefunden .

Verwenden von importlib.machinery.SourceFileLoader :

>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = loader.load_module()
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

HINWEIS : Funktioniert nur in Python 3.3+ .

UPDATE Loader.load_module ist seit Python 3.4 veraltet. Verwenden Sie Loader.exec_modulestattdessen:

>>> import types
>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = types.ModuleType(loader.name)
>>> loader.exec_module(mod)
>>> mod
<module 'a_b'>

>>> import importlib.machinery
>>> import importlib.util
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> spec = importlib.util.spec_from_loader(loader.name, loader)
>>> mod = importlib.util.module_from_spec(spec)
>>> loader.exec_module(mod)
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>
falsetru
quelle
23
Downvoter: Wie kann ich die Antwort verbessern? Wenn Sie einen besseren Weg haben, um das zu erreichen, was ich will, lassen Sie es mich bitte wissen.
Falsetru
3
Es gibt eine hilfreiche Warnung, load_moduledie via ignoriert warnings.catch_warnings. Wenn Sie stattdessen verwenden mod = imp.load_source('a_b', '/tmp/a-b.txt'), wird die folgende Warnung (Verwendung -Wall) ausgegeben : DeprecationWarning: imp.load_source() is deprecated; use importlib.machinery.SourceFileLoader(name, pathname).load_module() instead.
Eryk So
1
@eryksun, du hast recht. Danke für den Kommentar. Übrigens zeigt Python 3.4 (rc1) im Gegensatz zu Python 3.3.x nicht die alternative Verwendung an.
Falsetru
1
@ihavenoidea, Bitte posten Sie eine separate Frage, damit andere Ihnen antworten können und andere Benutzer auch Antworten lesen können.
Falsetru
1
@falsetru Tatsächlich habe ich hier bisher keine Antwort. Ich habe hier in den Kommentaren gepostet, weil ich nach dem Posten meiner Frage auf Ihre Antwort gestoßen bin. Wenn Sie wissen, wie das geht, würde ich es begrüßen!
Ihavenoidea
24

Kürzere Version der Lösung von @falsetru:

>>> import importlib.util
>>> spec = importlib.util.spec_from_file_location('a_b', '/tmp/a-b.py')
>>> mod = importlib.util.module_from_spec(spec)
>>> spec.loader.exec_module(mod)
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

Ich habe es mit Python 3.5 und 3.6 getestet.

Laut den Kommentaren funktioniert es nicht mit beliebigen Dateierweiterungen.

Stefan Scherfke
quelle
2
importlib.util.spec_from_file_location(..)kehrt Nonefür mich zurück; Auslösen einer Ausnahme für den folgenden importlib.util.module_from_spec(..)Aufruf. (Siehe i.imgur.com/ZjyFhif.png )
falsetru
3
importlib.util.spec_from_file_locationArbeiten für bekannte Dateinamenerweiterungen ( .py, .so, ..), aber nicht für andere ( .txt...)
falsetru
Oh, ich verwende es nur mit Python-Dateien, habe aber mein Beispiel so geändert, dass es wie das obige aussieht, und habe es nicht getestet. Ich habe es aktualisiert.
Stefan Scherfke
10

Ähnlich wie @falsetru, jedoch für Python 3.5+ und unter Berücksichtigung der Angaben im importlibDokument zur Verwendung von importlib.util.module_from_specover types.ModuleType:

Diese Funktion [ importlib.util.module_from_spec] wird types.ModuleTypeder Erstellung eines neuen Moduls vorgezogen, da mit spec so viele importgesteuerte Attribute wie möglich für das Modul festgelegt werden.

Wir können jede Datei importliballeine importieren, indem wir die importlib.machinery.SOURCE_SUFFIXESListe ändern .

import importlib

importlib.machinery.SOURCE_SUFFIXES.append('') # empty string to allow any file
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# if desired: importlib.machinery.SOURCE_SUFFIXES.pop()
Alex Walczak
quelle
1
Interessanterweise, während dieser Hack die leere Zeichenfolge in der Liste der Quelle Suffixe anhängen funktioniert gut für den Import von Modulen Python umbenannt Quelle, das äquivalent für umbenannt Erweiterungsmodule Import nicht funktioniert ... Das heißt, mit importlib.machinery.EXTENSION_SUFFIXES.append('')noch macht importlib.util.spec_from_file_locationkeine Rückkehr.
mxxk
vermutlich importlib.util.spec_from_file_locationsollte immer noch mit Erweiterungen funktionieren, wenn Sie einen Lader angeben
Alex Walczak
4

importlib Hilfsfunktion

Hier ist ein praktischer, gebrauchsfertiger Helfer, der durch impein Beispiel ersetzt werden kann. Die Technik ist die gleiche wie bei https://stackoverflow.com/a/19011259/895245 . Dies bietet lediglich eine bequemere Funktion.

main.py.

#!/usr/bin/env python3

import os
import importlib

def import_path(path):
    module_name = os.path.basename(path).replace('-', '_')
    spec = importlib.util.spec_from_loader(
        module_name,
        importlib.machinery.SourceFileLoader(module_name, path)
    )
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    sys.modules[module_name] = module
    return module

notmain = import_path('not-main')
print(notmain)
print(notmain.x)

nicht-main

x = 1

Lauf:

python3 main.py

Ausgabe:

<module 'not_main' from 'not-main'>
1

Ich ersetze -durch, _weil meine importierbaren ausführbaren Python-Dateien ohne Erweiterung Bindestriche haben. Dies ist nicht obligatorisch, führt jedoch zu besseren Modulnamen.

Dieses Muster wird auch in den Dokumenten unter https://docs.python.org/3.7/library/importlib.html#importing-a-source-file-directly erwähnt

Am Ende bin ich dazu übergegangen, weil nach dem Update auf Python 3.7 Folgendes import impgedruckt wird:

DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses

und ich weiß nicht, wie ich das ausschalten soll, das wurde gefragt bei:

Getestet in Python 3.7.3.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle