Importieren Sie ein Python-Modul ohne die Erweiterung .py

73

Ich habe eine Datei namens foobar (ohne Erweiterung .py). Im selben Verzeichnis habe ich eine andere Python-Datei, die versucht, sie zu importieren:

import foobar

Dies funktioniert aber nur, wenn ich die Datei in foobar.py umbenenne. Ist es möglich, ein Python-Modul ohne die Erweiterung .py zu importieren?

Update: Die Datei hat keine Erweiterung, da ich sie auch als eigenständiges Skript verwende und die Erweiterung .py nicht eingeben möchte, um sie auszuführen.

Update2: Ich werde mich für die unten erwähnte Symlink-Lösung entscheiden.

compie
quelle
1
Ich bin fasziniert. Warum haben Sie eine Python-Datei ohne die pyErweiterung?
Esteban Küber
3
Manchmal ist es hilfreich, Python für Konfigurationsdateien (Erweiterung als .conf) zu verwenden oder einen bestimmten Dateityp zu kennzeichnen. In meinem Fall wäre es für einen Administrator eher eine Annehmlichkeit.
NuclearPeon
1
Ich habe eine Datei mit Konfiguration, die sowohl als Python-Datei als auch als Bash-Skript verwendet wird. Ich gab es eine pyshVerlängerung ...
Sergey Orshanskiy
Wenn dies konfigurationsbezogene Dinge sind, empfehle ich die Verwendung ConfigParser. wiki.python.org/moin/ConfigParserExamples
Chemischer Programmierer
3
@voyager Ein Grund sind Python-Skripte mit .cgi-Erweiterungen anstelle der .py-Erweiterung
repzero

Antworten:

52

Mit der imp.load_sourceFunktion (aus dem impModul) können Sie ein Modul dynamisch aus einem bestimmten Dateisystempfad laden.

import imp
foobar = imp.load_source('foobar', '/path/to/foobar')

Diese SO-Diskussion zeigt auch einige interessante Optionen.

Eli Bendersky
quelle
Fest. [Es ist jedoch konstruktiver, eine Bearbeitung vorzuschlagen]
Eli Bendersky
2
Was nützt das erste Argument, 'foobar'wenn es im Rückgabewert zugewiesen ist?
Anmol Singh Jaggi
2
@AnmolSinghJaggi Legt die Moduleigenschaft fest __name__. Normalerweise wird dies anhand des Dateinamens bestimmt. Da Sie jedoch einen nicht standardmäßigen Dateinamen verwenden (der möglicherweise überhaupt keine gültige Python-ID enthält), müssen Sie den Modulnamen angeben. Der Variablenname, in dem Sie einen Verweis auf das erstellte Modulobjekt speichern, ist irrelevant, so als ob import foo.bar as bazdas Modul, auf das die Variable verweist baz, weiterhin das Original hat __name__.
Ben
3
Dies ist seit 3.4 veraltet. Haben Sie eine Idee, wie Sie aus einer Datei ohne die Erweiterung .py in 3.4+ importieren können?
Exhuma
2
Für Python 3.4+: Diese Antwort ist genauer: stackoverflow.com/questions/2601047/…
tres.14159
22

Hier ist eine Lösung für Python 3.4+:

from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader 

spec = spec_from_loader("foobar", SourceFileLoader("foobar", "/path/to/foobar"))
foobar = module_from_spec(spec)
spec.loader.exec_module(foobar)

Wenn Sie a verwenden spec_from_loaderund explizit angeben, SourceFileLoaderwird die Maschine gezwungen , die Datei als Quelle zu laden, ohne zu versuchen, den Dateityp aus der Erweiterung herauszufinden. Dies bedeutet, dass Sie die Datei laden können, obwohl sie nicht in aufgeführt ist importlib.machinery.SOURCE_SUFFIXES.

Wenn Sie die Datei nach dem ersten Laden weiterhin nach Namen importieren möchten, fügen Sie das Modul hinzu zu sys.modules:

sys.modules['foobar'] = foobar
Verrückter Physiker
quelle
2
Dieses Modul braucht foobar = importlib.nice_import('foobar')dringend einen Helfer.
Ciro Santilli 法轮功 冠状 病 六四 事件 8
@Ciro. Das hat es schon. Ich denke nicht, dass das /some/arbitrary/file.weird_extensionals "nett" qualifiziert ist. Davon abgesehen habe ich begonnen, Python-Code für alle meine Konfigurationsdateien zu verwenden, sobald ich dies entdeckt habe. Es ist einfach so bequem.
Mad Physicist
Was ist, wenn ich * importieren möchte?
Alex Harvey
1
@ AlexHarvey. Dies gibt Ihnen ein Modulobjekt. Sie können so etwas globals().update(foobar.__dict__)oder so tun , aber ich würde dagegen empfehlen.
Mad Physicist
17

Wie andere bereits erwähnt haben, können Sie imp.load_source verwenden, dies erschwert jedoch das Lesen Ihres Codes. Ich würde es wirklich nur empfehlen, wenn Sie Module importieren müssen, deren Namen oder Pfade erst zur Laufzeit bekannt sind.

Was ist Ihr Grund, warum Sie die Erweiterung .py nicht verwenden möchten? Der häufigste Fall, wenn Sie die Erweiterung .py nicht verwenden möchten, ist, dass das Python-Skript auch als ausführbare Datei ausgeführt wird, Sie jedoch möchten, dass andere Module es importieren können. In diesem Fall kann es hilfreich sein, die Funktionalität in eine .py-Datei mit einem ähnlichen Namen zu verschieben und dann foobarals Wrapper zu verwenden.


quelle
14
Oder anstatt zu verpacken, einfach symobar foobar.py mit foobar (vorausgesetzt, Sie sind nicht unter Windows)
whaley
@waley, ja, das wäre viel sauberer. Sie können eine .bat für Windows verwenden, um dasselbe zu erreichen.
Ich habe einen ordentlichen Anwendungsfall - eine Readme-Datei mit Beispielen, die doctest validieren möchte. Ich hoffe, ein doktestes Markdown-Dokument zu erstellen, das funktioniert ...
Danny Staple
Und die Antwort lautet (für diesen Anwendungsfall) - verwenden Sie doctest.loadfile!
Danny Staple
Das Problem mit Wrappern ist, dass wenn jemand nur den Wrapper naiv in ein bin/Verzeichnis kopiert , das Programm nicht funktioniert, wenn es vom Pfad ausgeführt wird.
cjs
14

imp.load_source(module_name, path)sollte tun oder Sie können die ausführlichere imp.load_module(module_name, file_handle, ...)Route tun, wenn Sie stattdessen ein Dateihandle haben

Daniel DiPaolo
quelle
7

importlib Hilfsfunktion

Hier ist ein praktischer, gebrauchsfertiger Helfer imp, der anhand eines Beispiels ersetzt werden kann, das auf den folgenden Informationen basiert: https://stackoverflow.com/a/43602645/895245

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
1

Wenn Sie das Skript mit dem Paketmanager (deb oder ähnlich) installieren, können Sie auch setuptools verwenden:

"... es gibt keine einfache Möglichkeit, den Dateinamen eines Skripts auf Windows- und POSIX-Plattformen mit den lokalen Konventionen abzustimmen. Zum anderen müssen Sie häufig eine separate Datei nur für das" Hauptskript "erstellen, wenn Ihr eigentliches" Hauptskript "ein ist Funktion in einem Modul irgendwo ... setuptools behebt all diese Probleme, indem automatisch Skripte mit der richtigen Erweiterung für Sie generiert werden. Unter Windows wird sogar eine EXE-Datei erstellt ... "

https://pythonhosted.org/setuptools/setuptools.html#automatic-script-creation

user2745509
quelle
-1

Ich habe alle oben genannten Vorschläge ausprobiert und kann keine Lösung für einen wirklich einfachen Fall finden, in dem ich ein CLI-Skript ohne .pyErweiterung testen möchte .

Warum wird spec.loader.exec_module()ein IsADirectoryError auf " ." ausgelöst ?

Ich habe zwei Dateien in einem Verzeichnis:

%. ls

cli_with_no_dot_py      test_cli_with_no_dot_py.py

%. cat cli_with_no_dot_py

#!/usr/bin/env python3

cool_thing = True
print("cool_thing is", cool_thing)

%. ./cli_with_no_dot_py

cool_thing is True

%. cat test_cli_with_no_dot_py.py

#!/usr/bin/env python3

from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader
spec = spec_from_loader("cli_with_no_dot_py",
    SourceFileLoader("cli_with_no_dot_py", "."))
print("spec:", spec)
cli_with_no_dot_py = module_from_spec(spec)
print("cli_with_no_dot_py):", cli_with_no_dot_py)
print("dir(cli_with_no_dot_py:", dir(cli_with_no_dot_py))
print("spec.loader:", spec.loader)
spec.loader.exec_module(cli_with_no_dot_py)  # !!! IsADirectoryError !!!

def test_cool_thing():
    print("cli_with_no_dot_py.cool_thing is", cli_with_no_dot_py.cool_thing)
    assert cli_with_no_dot_py.cool_thing

%. pytest

======================================================================== test session starts ========================================================================
platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /Users/cclauss/Python/pytest_a_cli_tool
collected 0 items / 1 error

============================================================================== ERRORS ===============================================================================
____________________________________________________________ ERROR collecting test_cli_with_no_dot_py.py ____________________________________________________________
test_cli_with_no_dot_py.py:11: in <module>
    spec.loader.exec_module(cli_with_no_dot_py)
<frozen importlib._bootstrap_external>:779: in exec_module
    ???
<frozen importlib._bootstrap_external>:915: in get_code
    ???
<frozen importlib._bootstrap_external>:972: in get_data
    ???
E   IsADirectoryError: [Errno 21] Is a directory: '.'
-------------------------------------------------------------------------- Captured stdout --------------------------------------------------------------------------
spec: ModuleSpec(name='cli_with_no_dot_py', loader=<_frozen_importlib_external.SourceFileLoader object at 0x10e106f70>, origin='.')
cli_with_no_dot_py: <module 'cli_with_no_dot_py' from '.'>
dir(cli_with_no_dot_py): ['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
spec.loader: <_frozen_importlib_external.SourceFileLoader object at 0x10e106f70>
====================================================================== short test summary info ======================================================================
ERROR test_cli_with_no_dot_py.py - IsADirectoryError: [Errno 21] Is a directory: '.'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
========================================================================= 1 error in 0.09s ==========================================================================
cclauss
quelle
Dies ist eine Frage und keine Antwort.
Jamesdlin