Jak zaimportować moduł z pełną ścieżką?

1139

Jak mogę załadować moduł Python, mając pełną ścieżkę? Pamiętaj, że plik może znajdować się w dowolnym miejscu w systemie plików, ponieważ jest to opcja konfiguracji.

derfred
źródło
21
Ładne i proste pytanie - i przydatne odpowiedzi, ale sprawiają, że zastanawiam się, co się stało z mantrą pythonową „Jest jeden oczywisty sposób”, aby to zrobić. Nie wydaje się, że jest to jakaś pojedyncza odpowiedź lub prosta i oczywista odpowiedź. Wydaje się absurdalnie hackerski i zależny od wersji dla tak podstawowej operacji (i wygląda na bardziej rozdętą w nowszych wersjach ...).
Inger

Odpowiedzi:

1280

W przypadku języka Python 3.5+:

import importlib.util
spec = importlib.util.spec_from_file_location("module.name", "/path/to/file.py")
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)
foo.MyClass()

W przypadku Python 3.3 i 3.4 użyj:

from importlib.machinery import SourceFileLoader

foo = SourceFileLoader("module.name", "/path/to/file.py").load_module()
foo.MyClass()

(Chociaż zostało to przestarzałe w Pythonie 3.4.)

W przypadku Python 2 użyj:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

Istnieją równoważne funkcje wygody dla skompilowanych plików i bibliotek DLL Pythona.

Zobacz także http://bugs.python.org/issue21436 .

Sebastian Rittau
źródło
53
Gdybym znał przestrzeń nazw - „module.name” - już bym użył __import__.
Sridhar Ratnakumar,
62
@SridharRatnakumar wartość pierwszego argumentu imp.load_sourceustawia tylko .__name__wartość zwracanego modułu. nie wpływa na ładowanie.
Dan D.
17
@DanD. - pierwszy argument imp.load_source()określa klucz nowego wpisu utworzonego w sys.modulessłowniku, więc pierwszy argument rzeczywiście wpływa na ładowanie.
Brandon Rhodes,
9
impModuł jest nieaktualna od wersji 3.4: impPakiet czeka Wycofanie na korzyść importlib.
Chiel ten Brinke
9
@AXO i do tego stopnia, że ​​zastanawiasz się, dlaczego coś tak prostego i podstawowego musi być tak skomplikowane. Nie ma go w wielu innych językach.
skalisty
422

Zaletą dodania ścieżki do sys.path (w porównaniu z impem) jest to, że upraszcza to rzeczy podczas importowania więcej niż jednego modułu z jednego pakietu. Na przykład:

import sys
# the mock-0.3.1 dir contains testcase.py, testutils.py & mock.py
sys.path.append('/foo/bar/mock-0.3.1')

from testcase import TestCase
from testutils import RunTests
from mock import Mock, sentinel, patch
Daryl Spitzer
źródło
13
Jak używamy sys.path.appenddo wskazywania pojedynczego pliku python zamiast katalogu?
Phani
28
:-) Być może twoje pytanie byłoby bardziej odpowiednie jako pytanie StackOverflow, a nie komentarz do odpowiedzi.
Daryl Spitzer
3
Ścieżka Pythona może zawierać archiwa zip, „jajka” (złożony rodzaj archiwów zip) itp. Można z nich importować moduły. Tak więc elementy ścieżki są rzeczywiście kontenerami plików, ale niekoniecznie są katalogami.
Alexis
12
Uważaj na to, że Python buforuje instrukcje importu. W rzadkim przypadku, gdy masz dwa różne foldery o tej samej nazwie klasy (classX), podejście polegające na dodaniu ścieżki do sys.path, importowaniu classX, usunięciu ścieżki i powtórzeniu dla pozostałych ścieżek nie będzie działać. Python zawsze ładuje klasę z pierwszej ścieżki z pamięci podręcznej. W moim przypadku chciałem stworzyć system wtyczek, w którym wszystkie wtyczki implementują określoną klasę X. Skończyło się na użyciu SourceFileLoader , zauważ, że jego wycofanie jest kontrowersyjne .
ComFreek
3
Zauważ, że to podejście pozwala importowanemu modułowi importować inne moduły z tego samego katalogu, co często robią moduły, podczas gdy podejście zaakceptowanej odpowiedzi nie (przynajmniej w wersji 3.7). importlib.import_module(mod_name)może być użyty zamiast jawnego importu tutaj, jeśli nazwa modułu nie jest znana w czasie wykonywania, dodałbym jednak sys.path.pop()na końcu, zakładając, że importowany kod nie próbuje zaimportować większej liczby modułów, ponieważ jest używany.
Eli_B,
43

Jeśli moduł najwyższego poziomu nie jest plikiem, ale jest spakowany jako katalog z __init__.py, wówczas zaakceptowane rozwiązanie prawie działa, ale nie całkiem. W Python 3.5+ potrzebny jest następujący kod (zwróć uwagę na dodany wiersz rozpoczynający się od „sys.modules”):

MODULE_PATH = "/path/to/your/module/__init__.py"
MODULE_NAME = "mymodule"
import importlib
import sys
spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module 
spec.loader.exec_module(module)

Bez tego wiersza, gdy wykonywany jest moduł exec_module, próbuje on powiązać import względny z twojego najwyższego poziomu __init__.py z nazwą modułu najwyższego poziomu - w tym przypadku „mymodule”. Ale „mymoduł” nie jest jeszcze załadowany, więc pojawi się błąd „SystemError: Moduł macierzysty„ mymoduł ”nie został załadowany, nie może wykonać importu względnego”. Musisz więc powiązać nazwę przed załadowaniem. Powodem tego jest podstawowy niezmiennik względnego systemu importu: „Niezmiennym wstrzymaniem jest to, że jeśli masz sys.modules ['spam'] i sys.modules ['spam.foo'] (tak jak po powyższym imporcie ), ten drugi musi występować jako atrybut foo pierwszego ”, jak omówiono tutaj .

Sam Grondahl
źródło
Wielkie dzięki! Ta metoda umożliwia względny import między podmodułami. Świetny!
tebanep,
Ta odpowiedź pasuje do dokumentacji tutaj: docs.python.org/3/library/… .
Tim Ludwinski,
1
ale co to jest mymodule?
Gulzar
@Gulzar, jest to jakakolwiek nazwa, którą chcesz nadać swojemu modułowi, dzięki czemu możesz później: „z mycodu importu myclass”
Idodo
Więc ... /path/to/your/module/jest właściwie /path/to/your/PACKAGE/? a mymodulemasz na myśli myfile.py?
Gulzar
37

Aby zaimportować moduł, musisz dodać jego katalog do zmiennej środowiskowej, tymczasowo lub na stałe.

Tymczasowo

import sys
sys.path.append("/path/to/my/modules/")
import my_module

Na stałe

Dodanie następującego wiersza do .bashrcpliku (w systemie Linux) i wykonanie source ~/.bashrcw terminalu:

export PYTHONPATH="${PYTHONPATH}:/path/to/my/modules/"

Źródło / Źródło: saarrrr , kolejne pytanie dotyczące wymiany stosów

Milady
źródło
3
To rozwiązanie „temp” jest świetną odpowiedzią, jeśli chcesz popchnąć projekt w notatniku jupyter w innym miejscu.
fordy
Ale ... niebezpiecznie manipulowanie ścieżką
Shai Alon
@ShaiAlon Dodajesz ścieżki, więc nie ma niebezpieczeństwa poza przeniesieniem kodów z jednego komputera na inny, ścieżki mogą zostać pomieszane. Tak więc w celu opracowania pakietu importuję tylko pakiety lokalne. Ponadto nazwy pakietów powinny być unikalne. Jeśli się martwisz, skorzystaj z tymczasowego rozwiązania.
Miladiouss,
28

Wygląda na to, że nie chcesz specjalnie importować pliku konfiguracyjnego (który ma wiele skutków ubocznych i dodatkowych komplikacji), po prostu chcesz go uruchomić i mieć dostęp do wynikowej przestrzeni nazw. Biblioteka standardowa udostępnia interfejs API specjalnie w tym celu w postaci runpy.run_path :

from runpy import run_path
settings = run_path("/path/to/file.py")

Ten interfejs jest dostępny w Python 2.7 i Python 3.2+

ncoghlan
źródło
Podoba mi się ta metoda, ale kiedy otrzymuję wynik run_path, jest to słownik, do którego nie mam dostępu?
Stephen Ellwood,
Co rozumiesz przez „nie można uzyskać dostępu”? Nie można importować z niego (dlatego jest to tylko opcja dobre, gdy w rzeczywistości nie jest wymagany dostęp import-style), ale zawartość powinna być dostępna poprzez regularne dict (API result[name], result.get('name', default_value)itp)
ncoghlan
Ta odpowiedź jest niedoceniana. To bardzo krótkie i proste! Co więcej, jeśli potrzebujesz odpowiedniej przestrzeni nazw modułów, możesz zrobić coś takiego from runpy import run_path; from argparse import Namespace; mod = Namespace(**run_path('path/to/file.py'))
RuRo
20

Możesz także zrobić coś takiego i dodać katalog, w którym znajduje się plik konfiguracyjny, do ścieżki ładowania Pythona, a następnie po prostu wykonać normalny import, zakładając, że znasz wcześniej nazwę pliku, w tym przypadku „config”.

Bałagan, ale działa.

configfile = '~/config.py'

import os
import sys

sys.path.append(os.path.dirname(os.path.expanduser(configfile)))

import config
ctcherry
źródło
To nie jest dynamiczne.
Shai Alon,
Próbowałem: config_file = 'setup-for-chats', setup_file = get_setup_file (config_file + ".py"), sys.path.append (os.path.dirname (os.path.expanduser (setup_file))), importuj config_file >> „ImportError: Brak modułu o nazwie config_file”
Shai Alon,
17

Możesz użyć

load_source(module_name, path_to_file) 

metoda z modułu imp .

zuber
źródło
... a imp.load_dynamic(module_name, path_to_file)dla bibliotek DLL
HEKTO,
34
główka, że ​​chochlik jest już przestarzały.
t1m0
13
def import_file(full_path_to_module):
    try:
        import os
        module_dir, module_file = os.path.split(full_path_to_module)
        module_name, module_ext = os.path.splitext(module_file)
        save_cwd = os.getcwd()
        os.chdir(module_dir)
        module_obj = __import__(module_name)
        module_obj.__file__ = full_path_to_module
        globals()[module_name] = module_obj
        os.chdir(save_cwd)
    except:
        raise ImportError

import_file('/home/somebody/somemodule.py')
Chris Calloway
źródło
37
Po co pisać 14 linii błędnego kodu, skoro jest to już zajęte przez standardową bibliotekę? Nie wykonałeś sprawdzania błędów formatu lub zawartości pełnego_ścieżki_do_modułu lub systemu operacyjnego. Niezależnie od operacji; a użycie except:klauzuli catch-all rzadko jest dobrym pomysłem.
Chris Johnson
Powinieneś użyć tutaj więcej „prób na koniec”. Np.save_cwd = os.getcwd() try: … finally: os.chdir(save_cwd)
kay - SE is evil
11
@ChrisJohnson this is already addressed by the standard librarytak, ale python ma nieprzyjemny zwyczaj nie być kompatybilny wstecz ... ponieważ sprawdzona odpowiedź mówi, że istnieją 2 różne sposoby przed i po 3.3. W takim przypadku wolałbym napisać własną funkcję uniwersalną niż sprawdzić wersję w locie. I tak, może ten kod nie jest zbyt dobrze chroniony przed błędami, ale pokazuje pomysł (którym jest os.chdir (), o czym nie myślałem), na podstawie którego mogę napisać lepszy kod. Stąd +1.
Sushi271
13

Oto kod, który działa we wszystkich wersjach Pythona, od 2.7-3.5 i prawdopodobnie nawet w innych.

config_file = "/tmp/config.py"
with open(config_file) as f:
    code = compile(f.read(), config_file, 'exec')
    exec(code, globals(), locals())

Przetestowałem to. Może to być brzydkie, ale jak dotąd jest to jedyny, który działa we wszystkich wersjach.

sorin
źródło
1
Ta odpowiedź działała dla mnie, gdzie load_sourcenie, ponieważ importuje skrypt i zapewnia skryptowi dostęp do modułów i globałów podczas importowania.
Klik
13

Wymyśliłem nieco zmodyfikowaną wersję cudownej odpowiedzi @ SebastianRittau (myślę, że dla Pythona> 3.4), która pozwoli ci załadować plik z dowolnym rozszerzeniem jako moduł używający spec_from_loaderzamiast spec_from_file_location:

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

spec = spec_from_loader("module.name", SourceFileLoader("module.name", "/path/to/file.py"))
mod = module_from_spec(spec)
spec.loader.exec_module(mod)

Zaletą jawnego kodowania ścieżki jest SourceFileLoaderto, że maszyneria nie będzie próbowała ustalić typu pliku z rozszerzenia. Oznacza to, że .txtza pomocą tej metody można załadować plik podobny do pliku, ale nie można tego zrobić spec_from_file_locationbez określenia modułu ładującego, ponieważ .txtnie ma go w katalogu importlib.machinery.SOURCE_SUFFIXES.

Szalony fizyk
źródło
13

Masz na myśli ładowanie czy importowanie?

Możesz manipulować sys.pathlistą, określić ścieżkę do modułu, a następnie zaimportować moduł. Na przykład, biorąc pod uwagę moduł w:

/foo/bar.py

Mógłbyś:

import sys
sys.path[0:0] = ['/foo'] # puts the /foo directory at the start of your path
import bar
Pszenica
źródło
1
@Wheat Dlaczego sys.path [0: 0] zamiast sys.path [0]?
user618677,
5
B / c sys.path [0] = xy nadpisuje pierwszy element ścieżki, podczas gdy ścieżka [0: 0] = xy jest równoważna path.insert (0, xy)
dom0
2
hm path.insert działał dla mnie, ale sztuczka [0: 0] nie.
jsh
11
sys.path[0:0] = ['/foo']
Kevin Edwards
6
Explicit is better than implicit.Dlaczego więc nie sys.path.insert(0, ...)zamiast sys.path[0:0]?
winklerrr
8

Wierzę, że możesz użyć imp.find_module()i imp.load_module()załadować określony moduł. Musisz podzielić nazwę modułu ze ścieżki, tzn. Jeśli chcesz załadować /home/mypath/mymodule.py, musisz:

imp.find_module('mymodule', '/home/mypath/')

... ale to powinno załatwić sprawę.

Matt
źródło
6

Możesz użyć pkgutilmodułu (w szczególności walk_packagesmetody), aby uzyskać listę pakietów w bieżącym katalogu. Stamtąd użycie importlibmaszyny do importowania wymaganych modułów jest banalne :

import pkgutil
import importlib

packages = pkgutil.walk_packages(path='.')
for importer, name, is_package in packages:
    mod = importlib.import_module(name)
    # do whatever you want with module now, it's been imported!
bob_twinkles
źródło
5

Utwórz moduł python test.py

import sys
sys.path.append("<project-path>/lib/")
from tes1 import Client1
from tes2 import Client2
import tes3

Utwórz moduł python test_check.py

from test import Client1
from test import Client2
from test import test3

Możemy zaimportować zaimportowany moduł z modułu.

abhimanyu
źródło
4

Ten obszar Pythona 3.4 wydaje się niezwykle trudny do zrozumienia! Jednak przy odrobinie hakowania przy użyciu kodu Chrisa Calloway'a na początku udało mi się coś załatwić. Oto podstawowa funkcja.

def import_module_from_file(full_path_to_module):
    """
    Import a module given the full path/filename of the .py file

    Python 3.4

    """

    module = None

    try:

        # Get module name and path from full path
        module_dir, module_file = os.path.split(full_path_to_module)
        module_name, module_ext = os.path.splitext(module_file)

        # Get module "spec" from filename
        spec = importlib.util.spec_from_file_location(module_name,full_path_to_module)

        module = spec.loader.load_module()

    except Exception as ec:
        # Simple error printing
        # Insert "sophisticated" stuff here
        print(ec)

    finally:
        return module

Wygląda na to, że korzysta z nieaktualnych modułów z Python 3.4. Nie udaję, że rozumiem dlaczego, ale wydaje się, że działa z poziomu programu. Odkryłem, że rozwiązanie Chrisa działało w wierszu poleceń, ale nie w programie.

Redlegjed
źródło
4

Nie twierdzę, że jest lepiej, ale ze względu na kompletność chciałem zasugerować execfunkcję, dostępną zarówno w Pythonie 2, jak i 3. execpozwala na wykonanie dowolnego kodu w zakresie globalnym lub wewnętrznym, dostarczone jako słownik.

Na przykład, jeśli masz moduł zapisany w "/path/to/module„z funkcją” foo(), możesz go uruchomić, wykonując następujące czynności:

module = dict()
with open("/path/to/module") as f:
    exec(f.read(), module)
module['foo']()

To sprawia, że ​​jest nieco bardziej wyraźne, że ładujesz kod dynamicznie i daje ci dodatkową moc, taką jak możliwość dostarczania niestandardowych wbudowanych funkcji.

A jeśli dostęp za pośrednictwem atrybutów, zamiast kluczy jest dla Ciebie ważny, możesz zaprojektować niestandardową klasę dict dla globałów, która zapewnia taki dostęp, np .:

class MyModuleClass(dict):
    def __getattr__(self, name):
        return self.__getitem__(name)
yoniLavi
źródło
4

Aby zaimportować moduł z podanej nazwy pliku, możesz tymczasowo rozszerzyć ścieżkę i przywrócić ścieżkę systemową w odwołaniu do bloku na końcu :

filename = "directory/module.py"

directory, module_name = os.path.split(filename)
module_name = os.path.splitext(module_name)[0]

path = list(sys.path)
sys.path.insert(0, directory)
try:
    module = __import__(module_name)
finally:
    sys.path[:] = path # restore
Peter Zhu
źródło
3

To powinno działać

path = os.path.join('./path/to/folder/with/py/files', '*.py')
for infile in glob.glob(path):
    basename = os.path.basename(infile)
    basename_without_extension = basename[:-3]

    # http://docs.python.org/library/imp.html?highlight=imp#module-imp
    imp.load_source(basename_without_extension, infile)
Hengjie
źródło
4
Bardziej ogólny sposób, aby wyciąć out przedłużacza wynosi: name, ext = os.path.splitext(os.path.basename(infile)). Ta metoda działa, ponieważ poprzednie ograniczenie rozszerzenia .py. Powinieneś również prawdopodobnie zaimportować moduł do jakiegoś wpisu zmiennej / słownika.
ReneSac,
3

Jeśli mamy skrypty w tym samym projekcie, ale w innym katalogu, możemy rozwiązać ten problem, stosując następującą metodę.

W tej sytuacji utils.pyjestsrc/main/util/

import sys
sys.path.append('./')

import src.main.util.utils
#or
from src.main.util.utils import json_converter # json_converter is example method
Kumar Immanuel
źródło
2

Zrobiłem impdla ciebie pakiet . Nazywam to import_filei tak to się używa:

>>>from import_file import import_file
>>>mylib = import_file('c:\\mylib.py')
>>>another = import_file('relative_subdir/another.py')

Możesz go uzyskać na:

http://pypi.python.org/pypi/import_file

lub o

http://code.google.com/p/import-file/

ubershmekel
źródło
1
os.chdir? (minimalna liczba znaków do zatwierdzenia komentarza).
ychaouche
Spędziłem cały dzień na rozwiązywaniu problemu z importem w exe wygenerowanym przez pyinstaller. W końcu to jedyna rzecz, która działała dla mnie. Dziękuję bardzo za zrobienie tego!
frakman1
2

Importuj moduły pakietów w czasie wykonywania (przepis na Python)

http://code.activestate.com/recipes/223972/

###################
##                #
## classloader.py #
##                #
###################

import sys, types

def _get_mod(modulePath):
    try:
        aMod = sys.modules[modulePath]
        if not isinstance(aMod, types.ModuleType):
            raise KeyError
    except KeyError:
        # The last [''] is very important!
        aMod = __import__(modulePath, globals(), locals(), [''])
        sys.modules[modulePath] = aMod
    return aMod

def _get_func(fullFuncName):
    """Retrieve a function object from a full dotted-package name."""

    # Parse out the path, module, and function
    lastDot = fullFuncName.rfind(u".")
    funcName = fullFuncName[lastDot + 1:]
    modPath = fullFuncName[:lastDot]

    aMod = _get_mod(modPath)
    aFunc = getattr(aMod, funcName)

    # Assert that the function is a *callable* attribute.
    assert callable(aFunc), u"%s is not callable." % fullFuncName

    # Return a reference to the function itself,
    # not the results of the function.
    return aFunc

def _get_class(fullClassName, parentClass=None):
    """Load a module and retrieve a class (NOT an instance).

    If the parentClass is supplied, className must be of parentClass
    or a subclass of parentClass (or None is returned).
    """
    aClass = _get_func(fullClassName)

    # Assert that the class is a subclass of parentClass.
    if parentClass is not None:
        if not issubclass(aClass, parentClass):
            raise TypeError(u"%s is not a subclass of %s" %
                            (fullClassName, parentClass))

    # Return a reference to the class itself, not an instantiated object.
    return aClass


######################
##       Usage      ##
######################

class StorageManager: pass
class StorageManagerMySQL(StorageManager): pass

def storage_object(aFullClassName, allOptions={}):
    aStoreClass = _get_class(aFullClassName, StorageManager)
    return aStoreClass(allOptions)
użytkownik10370
źródło
2

W Linuksie działa dodawanie dowiązania symbolicznego do katalogu, w którym znajduje się skrypt Pythona.

to znaczy:

ln -s /absolute/path/to/module/module.py /absolute/path/to/script/module.py

Python utworzy /absolute/path/to/script/module.pyci zaktualizuje go, jeśli zmienisz zawartość/absolute/path/to/module/module.py

następnie dołącz następujące w mypythonscript.py

from module import *
użytkownik2760152
źródło
1
To jest hack, którego użyłem i sprawił mi pewne problemy. Jednym z bardziej bolesnych było to, że IDEA ma problem polegający na tym, że nie odbiera zmienionego kodu z łącza, ale próbuje zapisać to, co według niego istnieje. Warunek wyścigu, w którym ostatni do uratowania jest tym, co się trzyma ... Straciłem przez to przyzwoitą ilość pracy.
Gripp
@Gripp nie jestem pewien, czy rozumiem twój problem, ale często (prawie wyłącznie) edytuję skrypty na zdalnym serwerze z mojego pulpitu za pośrednictwem SFTP z klientem takim jak CyberDuck, a w tym przypadku jest to zły pomysł, aby spróbować edytuj symlinkowany plik, zamiast tego znacznie bezpieczniej jest edytować oryginalny plik. Możesz złapać niektóre z tych problemów, używając giti sprawdzając, git statusczy twoje zmiany w skrypcie faktycznie powodują powrót do dokumentu źródłowego i nie gubią się w eterze.
user5359531,
2

Napisałem własną globalną i przenośną funkcję importu opartą na importlibmodule dla:

  • Być w stanie zaimportować oba moduły jako submoduły i zaimportować zawartość modułu do modułu nadrzędnego (lub do globali, jeśli nie ma modułu nadrzędnego).
  • Możliwość importowania modułów ze znakami kropki w nazwie pliku.
  • Możliwość importowania modułów z dowolnym rozszerzeniem.
  • Możliwość używania niezależnej nazwy dla submodułu zamiast nazwy pliku bez rozszerzenia, która jest domyślnie.
  • Być w stanie zdefiniować kolejność importu na podstawie uprzednio zaimportowanego modułu zamiast zależeć od sys.pathlub od kiedykolwiek przechowywanej ścieżki wyszukiwania.

Struktura katalogu przykładów:

<root>
 |
 +- test.py
 |
 +- testlib.py
 |
 +- /std1
 |   |
 |   +- testlib.std1.py
 |
 +- /std2
 |   |
 |   +- testlib.std2.py
 |
 +- /std3
     |
     +- testlib.std3.py

Zależność i porządek włączenia:

test.py
  -> testlib.py
    -> testlib.std1.py
      -> testlib.std2.py
    -> testlib.std3.py 

Realizacja:

Sklep z najnowszymi zmianami: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/python/tacklelib/tacklelib.py

test.py :

import os, sys, inspect, copy

SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("test::SOURCE_FILE: ", SOURCE_FILE)

# portable import to the global space
sys.path.append(TACKLELIB_ROOT) # TACKLELIB_ROOT - path to the library directory
import tacklelib as tkl

tkl.tkl_init(tkl)

# cleanup
del tkl # must be instead of `tkl = None`, otherwise the variable would be still persist
sys.path.pop()

tkl_import_module(SOURCE_DIR, 'testlib.py')

print(globals().keys())

testlib.base_test()
testlib.testlib_std1.std1_test()
testlib.testlib_std1.testlib_std2.std2_test()
#testlib.testlib.std3.std3_test()                             # does not reachable directly ...
getattr(globals()['testlib'], 'testlib.std3').std3_test()     # ... but reachable through the `globals` + `getattr`

tkl_import_module(SOURCE_DIR, 'testlib.py', '.')

print(globals().keys())

base_test()
testlib_std1.std1_test()
testlib_std1.testlib_std2.std2_test()
#testlib.std3.std3_test()                                     # does not reachable directly ...
globals()['testlib.std3'].std3_test()                         # ... but reachable through the `globals` + `getattr`

testlib.py :

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("1 testlib::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/std1', 'testlib.std1.py', 'testlib_std1')

# SOURCE_DIR is restored here
print("2 testlib::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/std3', 'testlib.std3.py')

print("3 testlib::SOURCE_FILE: ", SOURCE_FILE)

def base_test():
  print('base_test')

testlib.std1.py :

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std1::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/../std2', 'testlib.std2.py', 'testlib_std2')

def std1_test():
  print('std1_test')

testlib.std2.py :

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std2::SOURCE_FILE: ", SOURCE_FILE)

def std2_test():
  print('std2_test')

testlib.std3.py :

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std3::SOURCE_FILE: ", SOURCE_FILE)

def std3_test():
  print('std3_test')

Wyjście ( 3.7.4):

test::SOURCE_FILE:  <root>/test01/test.py
import : <root>/test01/testlib.py as testlib -> []
1 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std1/testlib.std1.py as testlib_std1 -> ['testlib']
import : <root>/test01/std1/../std2/testlib.std2.py as testlib_std2 -> ['testlib', 'testlib_std1']
testlib.std2::SOURCE_FILE:  <root>/test01/std1/../std2/testlib.std2.py
2 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std3/testlib.std3.py as testlib.std3 -> ['testlib']
testlib.std3::SOURCE_FILE:  <root>/test01/std3/testlib.std3.py
3 testlib::SOURCE_FILE:  <root>/test01/testlib.py
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'os', 'sys', 'inspect', 'copy', 'SOURCE_FILE', 'SOURCE_DIR', 'TackleGlobalImportModuleState', 'tkl_membercopy', 'tkl_merge_module', 'tkl_get_parent_imported_module_state', 'tkl_declare_global', 'tkl_import_module', 'TackleSourceModuleState', 'tkl_source_module', 'TackleLocalImportModuleState', 'testlib'])
base_test
std1_test
std2_test
std3_test
import : <root>/test01/testlib.py as . -> []
1 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std1/testlib.std1.py as testlib_std1 -> ['testlib']
import : <root>/test01/std1/../std2/testlib.std2.py as testlib_std2 -> ['testlib', 'testlib_std1']
testlib.std2::SOURCE_FILE:  <root>/test01/std1/../std2/testlib.std2.py
2 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std3/testlib.std3.py as testlib.std3 -> ['testlib']
testlib.std3::SOURCE_FILE:  <root>/test01/std3/testlib.std3.py
3 testlib::SOURCE_FILE:  <root>/test01/testlib.py
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'os', 'sys', 'inspect', 'copy', 'SOURCE_FILE', 'SOURCE_DIR', 'TackleGlobalImportModuleState', 'tkl_membercopy', 'tkl_merge_module', 'tkl_get_parent_imported_module_state', 'tkl_declare_global', 'tkl_import_module', 'TackleSourceModuleState', 'tkl_source_module', 'TackleLocalImportModuleState', 'testlib', 'testlib_std1', 'testlib.std3', 'base_test'])
base_test
std1_test
std2_test
std3_test

Testowany w Pythonie 3.7.4, 3.2.5,2.7.16

Plusy :

  • Może importować oba moduły jako submoduły i może importować zawartość modułu do modułu nadrzędnego (lub do globali, jeśli nie ma modułu nadrzędnego).
  • Może importować moduły z kropkami w nazwie pliku.
  • Może importować dowolny moduł rozszerzeń z dowolnego modułu rozszerzeń.
  • Może używać autonomicznej nazwy dla podmodułu zamiast nazwy pliku bez rozszerzenia, która jest domyślnie (na przykład testlib.std.pyas testlib, testlib.blabla.pyas testlib_blablai tak dalej).
  • Nie zależy od sys.pathani miejsca przechowywania ścieżki wyszukiwania.
  • Nie wymaga zapisywania / przywracania zmiennych globalnych, takich jak SOURCE_FILEi SOURCE_DIRmiędzy wywołaniami tkl_import_module.
  • [za 3.4.xi wyżej] Czy wymieszać nazw modułu w zagnieżdżonych tkl_import_modulepołączeń (ex: named->local->namedalbo local->named->locali tak dalej).
  • [dla 3.4.xi wyżej] Może automatycznie eksportować zmienne globalne / funkcje / klasy, skąd są zadeklarowane do wszystkich modułów potomnych importowanych przez tkl_import_module(poprzez tkl_declare_globalfunkcję).

Wady :

  • [dla 3.3.xi niższych] Wymagaj deklarowania tkl_import_modulewe wszystkich modułach, które wywołują tkl_import_module(duplikacja kodu)

Aktualizacja 1,2 (tylko dla wersji 3.4.xwyższej i wyższej):

W Pythonie 3.4 i nowszych można ominąć wymóg deklarowania tkl_import_modulew każdym module przez deklarację tkl_import_modulew module najwyższego poziomu, a funkcja wstrzykiwałaby się do wszystkich modułów potomnych w jednym wywołaniu (jest to rodzaj importu samodzielnego wdrażania).

Aktualizacja 3 :

Dodano funkcję tkl_source_moduleanalogową do bash sourcez ochroną wykonania wsparcia podczas importu (zaimplementowano poprzez scalenie modułu zamiast importu).

Aktualizacja 4 :

Dodano funkcję tkl_declare_globalautomatycznego eksportowania globalnej zmiennej modułowej do wszystkich modułów potomnych, w których globalna zmienna modułowa nie jest widoczna, ponieważ nie jest częścią modułu potomnego.

Aktualizacja 5 :

Wszystkie funkcje zostały przeniesione do biblioteki tacklelib, patrz link powyżej.

Andry
źródło
2

Jest pakiet dedykowany do tego w szczególności:

from thesmuggler import smuggle

# À la `import weapons`
weapons = smuggle('weapons.py')

# À la `from contraband import drugs, alcohol`
drugs, alcohol = smuggle('drugs', 'alcohol', source='contraband.py')

# À la `from contraband import drugs as dope, alcohol as booze`
dope, booze = smuggle('drugs', 'alcohol', source='contraband.py')

Jest testowany we wszystkich wersjach Pythona (również Jython i PyPy), ale może być nadmierną umiejętnością w zależności od wielkości projektu.

fny
źródło
1

Dodanie tego do listy odpowiedzi, ponieważ nie mogłem znaleźć niczego, co działałoby. Umożliwi to import skompilowanych (pyd) modułów python w 3.4:

import sys
import importlib.machinery

def load_module(name, filename):
    # If the Loader finds the module name in this list it will use
    # module_name.__file__ instead so we need to delete it here
    if name in sys.modules:
        del sys.modules[name]
    loader = importlib.machinery.ExtensionFileLoader(name, filename)
    module = loader.load_module()
    locals()[name] = module
    globals()[name] = module

load_module('something', r'C:\Path\To\something.pyd')
something.do_something()
David
źródło
1

dość prosty sposób: załóżmy, że chcesz zaimportować plik ze ścieżką względną ../../MyLibs/pyfunc.py


libPath = '../../MyLibs'
import sys
if not libPath in sys.path: sys.path.append(libPath)
import pyfunc as pf

Ale jeśli zdołasz przejść bez osłony, możesz w końcu uzyskać bardzo długą ścieżkę

Andrei Keino
źródło
1

Proste rozwiązanie wykorzystujące importlibzamiast imppakietu (przetestowane dla Python 2.7, chociaż powinno również działać dla Python 3):

import importlib

dirname, basename = os.path.split(pyfilepath) # pyfilepath: '/my/path/mymodule.py'
sys.path.append(dirname) # only directories should be added to PYTHONPATH
module_name = os.path.splitext(basename)[0] # '/my/path/mymodule.py' --> 'mymodule'
module = importlib.import_module(module_name) # name space of defined module (otherwise we would literally look for "module_name")

Teraz możesz bezpośrednio korzystać z przestrzeni nazw importowanego modułu, w następujący sposób:

a = module.myvar
b = module.myfunc(a)

Zaletą tego rozwiązania jest to , że nie musimy nawet znać rzeczywistej nazwy modułu, który chcielibyśmy zaimportować , aby użyć go w naszym kodzie. Jest to przydatne, np. W przypadku, gdy ścieżka modułu jest argumentem konfigurowalnym.

Ataksja
źródło
W ten sposób modyfikujesz sys.path, który nie pasuje do każdego przypadku użycia.
bgusach
@bgusach Może to być prawda, ale w niektórych przypadkach jest również pożądane (dodanie ścieżki do sys.path upraszcza rzeczy podczas importowania więcej niż jednego modułu z jednego pakietu). W każdym razie, jeśli nie jest to pożądane, można to zrobić natychmiastsys.path.pop()
Ataxias
0

Ta odpowiedź stanowi uzupełnienie odpowiedzi Sebastiana Rittau na komentarz: „ale co, jeśli nie masz nazwy modułu?” Jest to szybki i nieprzyzwoity sposób na uzyskanie prawdopodobnej nazwy modułu Pythona podając nazwę pliku - po prostu idzie w górę drzewa, dopóki nie znajdzie katalogu bez __init__.pypliku, a następnie zamienia go z powrotem w nazwę pliku. W przypadku Python 3.4+ (używa pathlib), co ma sens, ponieważ ludzie Py2 mogą używać „imp” lub innych sposobów importowania względnego:

import pathlib

def likely_python_module(filename):
    '''
    Given a filename or Path, return the "likely" python module name.  That is, iterate
    the parent directories until it doesn't contain an __init__.py file.

    :rtype: str
    '''
    p = pathlib.Path(filename).resolve()
    paths = []
    if p.name != '__init__.py':
        paths.append(p.stem)
    while True:
        p = p.parent
        if not p:
            break
        if not p.is_dir():
            break

        inits = [f for f in p.iterdir() if f.name == '__init__.py']
        if not inits:
            break

        paths.append(p.stem)

    return '.'.join(reversed(paths))

Z pewnością istnieją możliwości ulepszenia, a opcjonalne __init__.pypliki mogą wymagać innych zmian, ale jeśli tak __init__.py, to załatwia sprawę.

Michael Scott Cuthbert
źródło
-1

Myślę, że najlepszym sposobem jest oficjalna dokumentacja ( 29.1. Imp - Dostęp do wewnętrznych elementów importu ):

import imp
import sys

def __import__(name, globals=None, locals=None, fromlist=None):
    # Fast path: see if the module has already been imported.
    try:
        return sys.modules[name]
    except KeyError:
        pass

    # If any of the following calls raises an exception,
    # there's a problem we can't handle -- let the caller handle it.

    fp, pathname, description = imp.find_module(name)

    try:
        return imp.load_module(name, fp, pathname, description)
    finally:
        # Since we may exit via an exception, close fp explicitly.
        if fp:
            fp.close()
Zompa
źródło
1
To rozwiązanie nie pozwala podać ścieżki, o którą pyta pytanie.
Micah Smith