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.
py
Erweiterung?pysh
Verlängerung ...ConfigParser
. wiki.python.org/moin/ConfigParserExamplesAntworten:
Mit der
imp.load_source
Funktion (aus demimp
Modul) 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.
quelle
'foobar'
wenn es im Rückgabewert zugewiesen ist?__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 obimport foo.bar as baz
das Modul, auf das die Variable verweistbaz
, weiterhin das Original hat__name__
.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_loader
und explizit angeben,SourceFileLoader
wird 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 istimportlib.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
quelle
foobar = importlib.nice_import('foobar')
dringend einen Helfer./some/arbitrary/file.weird_extension
als "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.globals().update(foobar.__dict__)
oder so tun , aber ich würde dagegen empfehlen.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
foobar
als Wrapper zu verwenden.quelle
bin/
Verzeichnis kopiert , das Programm nicht funktioniert, wenn es vom Pfad ausgeführt wird.imp.load_source(module_name, path)
sollte tun oder Sie können die ausführlichereimp.load_module(module_name, file_handle, ...)
Route tun, wenn Sie stattdessen ein Dateihandle habenquelle
importlib
HilfsfunktionHier ist ein praktischer, gebrauchsfertiger Helfer
imp
, der anhand eines Beispiels ersetzt werden kann, das auf den folgenden Informationen basiert: https://stackoverflow.com/a/43602645/895245main.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:
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 imp
gedruckt 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.
quelle
Wenn Sie das Skript mit dem Paketmanager (deb oder ähnlich) installieren, können Sie auch setuptools verwenden:
https://pythonhosted.org/setuptools/setuptools.html#automatic-script-creation
quelle
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
.py
Erweiterung testen möchte .Warum wird
spec.loader.exec_module()
ein IsADirectoryError auf ".
" ausgelöst ?Ich habe zwei Dateien in einem Verzeichnis:
%.
ls
%.
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 ==========================================================================
quelle