Próbowałem przeczytać pytania dotyczące importu rodzeństwa, a nawet dokumentacji paczki , ale jeszcze nie znalazłem odpowiedzi.
O następującej strukturze:
├── LICENSE.md
├── README.md
├── api
│ ├── __init__.py
│ ├── api.py
│ └── api_key.py
├── examples
│ ├── __init__.py
│ ├── example_one.py
│ └── example_two.py
└── tests
│ ├── __init__.py
│ └── test_one.py
Jak można skrypty w examples
i tests
katalogi importować z
api
modułem i być uruchamiane z linii poleceń?
Ponadto chciałbym uniknąć brzydkiego sys.path.insert
włamania do każdego pliku. Z pewnością można to zrobić w Pythonie, prawda?
python
packages
python-import
siblings
zachwill
źródło
źródło
sys.path
hacki i przeczytać jedyne aktualne rozwiązanie, które zostało opublikowane (po 7 latach!).Odpowiedzi:
Siedem lat później
Ponieważ napisałem odpowiedź poniżej, modyfikowanie
sys.path
nadal jest szybką i brudną sztuczką, która działa dobrze w przypadku prywatnych skryptów, ale wprowadzono kilka ulepszeńsetup.cfg
przechowywanie metadanych)-m
flagi i działania jako pakietu również działa (ale okaże się nieco niewygodne, jeśli chcesz przekonwertować katalog roboczy na pakiet instalowalny).sys.path
hacki dla CiebieTo naprawdę zależy od tego, co chcesz zrobić. Jednak w twoim przypadku, ponieważ wydaje się, że Twoim celem jest przygotowanie odpowiedniego pakietu w pewnym momencie, instalacja za pomocą
pip -e
jest prawdopodobnie najlepszym rozwiązaniem, nawet jeśli nie jest jeszcze idealna.Stara odpowiedź
Jak już powiedziano gdzie indziej, okropną prawdą jest to, że musisz robić brzydkie hacki, aby umożliwić import z modułów rodzeństwa lub pakietu rodziców z
__main__
modułu. Problem jest szczegółowo opisany w PEP 366 . PEP 3122 próbował zająć się importem w bardziej racjonalny sposób, ale Guido odrzucił go na konto( tutaj )
Chociaż używam tego wzoru regularnie
Oto
path[0]
folder nadrzędny działającego skryptu i folderdir(path[0])
najwyższego poziomu.Nadal nie byłem jednak w stanie używać do tego importu względnego, ale pozwala on na import absolutny z najwyższego poziomu (w
api
folderze nadrzędnym twojego przykładu ).źródło
-m
formularza lub instalujesz pakiet (pip i virtualenv ułatwiają)__package__ = "examples"
. Dlaczego go używasz? 2. W jakiej sytuacji jest,__name__ == "__main__"
ale__package__
nie jestNone
?__packages__
pomaga, jeśli chcesz bezwzględną ścieżkę, na przykładexamples.api
do pracy w trybie iirc (ale minęło już dużo czasu, odkąd to ostatnio zrobiłem), a sprawdzenie, czy pakiet nie jest żaden, było w większości przypadków niezawodne w przypadku dziwnych sytuacji i zabezpieczenia na przyszłość.Masz dość hacków sys.path?
Dostępnych jest wiele
sys.path.append
hacków, ale znalazłem alternatywny sposób rozwiązania problemu.Podsumowanie
packaged_stuff
)setup.py
skryptu którym używasz setuptools.setup () .pip install -e <myproject_folder>
from packaged_stuff.modulename import function_name
Ustawiać
Punktem wyjścia jest podana przez Ciebie struktura plików, zawinięta w folder o nazwie
myproject
.Zadzwonię do
.
folderu korzenia, aw moim przykładowym przypadku to znajduje sięC:\tmp\test_imports\
.api.py
Jako przypadek testowy użyjmy następującego pliku ./api/api.py
test_one.py
Spróbuj uruchomić test_one:
Próbowanie importu względnego również nie zadziała:
Używanie
from ..api.api import function_from_api
spowodujeKroki
Zawartość
setup.py
będzie *Jeśli znasz środowiska wirtualne, aktywuj jedno i przejdź do następnego kroku. Korzystanie ze środowisk wirtualnych nie jest absolutnie wymagane, ale naprawdę pomoże ci w dłuższej perspektywie (jeśli masz więcej niż jeden projekt w toku ..). Najbardziej podstawowe kroki to (uruchom w folderze głównym)
python -m venv venv
source ./venv/bin/activate
(Linux, macOS) lub./venv/Scripts/activate
(Win)Aby dowiedzieć się więcej na ten temat, po prostu wypróbuj Google „samouczek wirtualnego środowiska python” lub podobny. Prawdopodobnie nigdy nie potrzebujesz żadnych innych poleceń niż tworzenie, aktywowanie i dezaktywowanie.
Po utworzeniu i aktywacji środowiska wirtualnego konsola powinna podać nazwę środowiska wirtualnego w nawiasie
a twoje drzewo folderów powinno wyglądać tak **
Zainstaluj pakiet najwyższego poziomu
myproject
za pomocąpip
. Sztuką jest użycie-e
flagi podczas instalacji. W ten sposób jest on instalowany w stanie edytowalnym, a wszystkie zmiany dokonane w plikach .py zostaną automatycznie uwzględnione w zainstalowanym pakiecie.W katalogu głównym uruchom
pip install -e .
(zwróć uwagę na kropkę, oznacza „bieżący katalog”)Możesz również zobaczyć, że jest on instalowany przy użyciu
pip freeze
myproject.
do importuPamiętaj, że będziesz musiał dodać
myproject.
tylko do importu, który inaczej nie działałby. Importowanie, które działało bezsetup.py
&pip install
nadal będzie działać. Zobacz przykład poniżej.Przetestuj rozwiązanie
Teraz przetestujmy rozwiązanie przy użyciu
api.py
zdefiniowanych powyżej itest_one.py
zdefiniowanych poniżej.test_one.py
uruchomienie testu
* Zobacz pełne dokumenty setuptools, aby uzyskać bardziej szczegółowe przykłady setup.py.
** W rzeczywistości środowisko wirtualne można umieścić w dowolnym miejscu na dysku twardym.
źródło
-e git+https://[email protected]/folder/myproject.git@f65466656XXXXX#egg=myproject
Jakiś pomysł na rozwiązanie?ModuleNotFoundError
? Zainstalowałem „myproject” w virtualenv, wykonując następujące kroki, a kiedy wchodzę w interpretowaną sesję i uruchamiamimport myproject
, otrzymujęModuleNotFoundError: No module named 'myproject'
?pip list installed | grep myproject
pokazuje, że tam jest, katalog jest poprawny, a zarówno weryfikacja , jakpip
ipython
weryfikacja poprawności.pip list
pokazuje pakiety, apip freeze
pokazuje dziwne nazwy, jeśli jest zainstalowany z flagą -eOto kolejna alternatywa, którą wstawiam u góry plików Pythona w
tests
folderze:źródło
..
tutaj jest względna do katalogu, z którego wykonujesz --- nie do katalogu zawierającego ten test / przykładowy plik. Wykonuję z katalogu projektu i./
zamiast tego potrzebowałem . Mam nadzieję, że to pomaga komuś innemu.sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@JoshuaDetwilerNie potrzebujesz i nie powinieneś hakować,
sys.path
chyba że jest to konieczne, aw tym przypadku nie jest to konieczne. Posługiwać się:Uruchom z katalogu projektu:
python -m tests.test_one
.Prawdopodobnie powinieneś przenieść się
tests
(jeśli są to najbardziej nieprzystosowane interfejsy API) do wewnątrzapi
i uruchomić,python -m api.test
aby uruchomić wszystkie testy (zakładając, że są__main__.py
) lubpython -m api.test.test_one
uruchomićtest_one
zamiast nich.Możesz także usunąć
__init__.py
zexamples
(nie jest to pakiet Python) i uruchomić przykłady w virtualenv, gdzieapi
jest zainstalowany, np.pip install -e .
W virtualenv zainstalowałbyapi
pakiet inplace , jeśli masz odpowiedniesetup.py
.źródło
python -m api.test.test_one
z dowolnego miejsca, gdy virtualenv jest aktywowany. Jeśli nie możesz skonfigurować PyCharm do uruchamiania testów, spróbuj zadać nowe pytanie o przepełnienie stosu (jeśli nie możesz znaleźć istniejącego pytania na ten temat).Nie rozumiem jeszcze Pythonologii niezbędnej do zobaczenia zamierzonego sposobu udostępniania kodu między niepowiązanymi projektami bez rodzeństwa / względnego importu. Do tego dnia jest to moje rozwiązanie. W przypadku
examples
lubtests
do importowania rzeczy..\api
wyglądałoby to tak:źródło
W przypadku importu pakietu rodzeństwa możesz użyć metody insert lub append modułu [sys.path] [2] :
Działa to, jeśli uruchamiasz skrypty w następujący sposób:
Z drugiej strony możesz także użyć importu względnego:
W takim przypadku będziesz musiał uruchomić skrypt z argumentem „-m” (zwróć uwagę, że w tym przypadku nie możesz podać rozszerzenia „.py” ):
Oczywiście możesz łączyć oba podejścia, aby skrypt działał bez względu na to, jak się nazywa:
źródło
__file__
globalnego, więc musiałem zastosować następujące:sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))
Ale teraz działa w dowolnym kataloguTLDR
Ta metoda nie wymaga narzędzi konfiguracyjnych, hacków ścieżek, dodatkowych argumentów wiersza poleceń ani określania najwyższego poziomu pakietu w każdym pliku projektu.
Po prostu stwórz skrypt w katalogu nadrzędnym tego, do czego wołasz
__main__
i uruchom wszystko od tego miejsca. Aby uzyskać dalsze wyjaśnienia, czytaj dalej.Wyjaśnienie
Można to osiągnąć bez zhakowania razem nowej ścieżki, dodatkowych argumentów wiersza poleceń lub dodania kodu do każdego z programów w celu rozpoznania jego rodzeństwa.
Przyczyną niepowodzenia tego, jak sądzę, wspomniano wcześniej, jest to, że wywoływane programy mają
__name__
ustawiony na__main__
. Gdy tak się dzieje, wywoływany skrypt akceptuje się na najwyższym poziomie pakietu i odmawia rozpoznania skryptów w katalogach rodzeństwa.Jednak wszystko poniżej najwyższego poziomu katalogu nadal rozpoznaje WSZYSTKIE POZOSTAŁE pod najwyższym poziomem. Oznacza to, że jedyną rzeczą, którą musisz zrobić, aby pliki w katalogach rodzeństwa były rozpoznawane / wykorzystywane, jest wywoływanie ich ze skryptu w katalogu nadrzędnym.
Dowód koncepcji W reż. O następującej strukturze:
Main.py
zawiera następujący kod:sib1 / call.py zawiera:
i sib2 / Callsib.py zawiera:
Jeśli odtworzysz ten przykład, zauważysz, że wywołanie
Main.py
spowoduje wydrukowanie napisu „Got Called” zgodnie z definicją,sib2/callsib.py
nawet jeśli zostanieszsib2/callsib.py
wywołanysib1/call.py
. Jeśli jednak ktoś zadzwoni bezpośredniosib1/call.py
(po wprowadzeniu odpowiednich zmian w imporcie), zgłasza wyjątek. Mimo że zadziałał, gdy został wywołany przez skrypt w katalogu nadrzędnym, nie będzie działał, jeśli uważa, że jest na najwyższym poziomie pakietu.źródło
Zrobiłem przykładowy projekt, aby zademonstrować, jak sobie z tym poradziłem, co jest rzeczywiście kolejnym hackem sys.path, jak wskazano powyżej. Przykład importu Python Sibling , który opiera się na:
if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())
Wydaje się to dość skuteczne, o ile katalog roboczy pozostaje w katalogu głównym projektu Python. Jeśli ktoś wdroży to w prawdziwym środowisku produkcyjnym, dobrze byłoby usłyszeć, czy tam również działa.
źródło
Musisz sprawdzić, jak zapisywane są instrukcje importu w powiązanym kodzie. Jeśli
examples/example_one.py
używa następującej instrukcji importu:... oczekuje, że katalog główny projektu znajdzie się w ścieżce systemowej.
Najłatwiejszym sposobem obsługi tego bez żadnych włamań (jak to ująłeś) byłoby uruchomienie przykładów z katalogu najwyższego poziomu, jak poniżej:
źródło
$ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api
. Ja też to samo rozumiemimport api.api
.Na wypadek, gdyby ktoś korzystający z Pydev na Eclipse skończył tutaj: możesz dodać ścieżkę rodzica rodzeństwa (a tym samym rodzica modułu wywołującego) jako folder biblioteki zewnętrznej, korzystając z Project-> Właściwości i ustawiając Biblioteki zewnętrzne w lewym menu Pydev-PYTHONPATH . Następnie możesz zaimportować z rodzeństwa, np
from sibling import some_class
.źródło
Po pierwsze, należy unikać plików o tej samej nazwie co sam moduł. Może zniszczyć inny import.
Podczas importowania pliku interpreter najpierw sprawdza bieżący katalog, a następnie przeszukuje katalogi globalne.
Wewnątrz
examples
lubtests
możesz zadzwonić:źródło
Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
__init__.py
plik do katalogu najwyższego poziomu. W przeciwnym razie Python nie będzie mógł potraktować tego jako modułu__name__
jest__main__
zamiastpackage.module
, Python nie widzi swoją paczkę rodzica, więc.
punkty do niczego.