Python: importowanie pakietu podrzędnego lub modułu podrzędnego

90

Korzystając już z płaskich pakietów, nie spodziewałem się problemu z pakietami zagnieżdżonymi. Tutaj jest…

Układ katalogu

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

Zawartość init .py

Obie package/__init__.pyi package/subpackage/__init__.pysą puste.

Zadowolony z module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

Zawartość test.py(3 wersje)

Wersja 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

To zły i niebezpieczny sposób importowania rzeczy (importuj wszystko zbiorczo), ale działa.

Wersja 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

Bezpieczniejszy sposób importowania, pozycja po pozycji, ale zawodzi, Python nie chce tego: kończy się niepowodzeniem z komunikatem: „Brak modułu o nazwie moduł”. Jednak …

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

… Mówi <module 'package.subpackage.module' from '...'>. Więc to jest moduł, ale to nie jest moduł / -P 8-O ... uh

Wersja 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

Ten działa. Więc albo jesteś zmuszony do używania przedrostka overkill przez cały czas, albo używasz niebezpiecznego sposobu, jak w wersji # 1 i zabroniony przez Python do korzystania z bezpiecznego, wygodnego sposobu? Lepszy sposób, który jest bezpieczny i pozwala uniknąć zbędnych długich przedrostków, jest jedynym, który Python odrzuca? Czy to dlatego, że kocha, import *czy dlatego, że kocha zbyt długie przedrostki (co nie pomaga w egzekwowaniu tej praktyki)?

Przepraszam za trudne słowa, ale już dwa dni próbuję obejść to głupie zachowanie. O ile gdzieś się nie pomyliłem, będę miał wrażenie, że coś jest naprawdę zepsute w modelu pakietu i pakietów podrzędnych Pythona.

Uwagi

  • Nie chcę polegać na sys.pathglobalnych efektach ubocznych ani na *.pthplikach, które są po prostu kolejnym sposobem na zabawę sys.pathz tymi samymi efektami globalnymi. Aby roztwór był czysty, musi być tylko lokalny. Albo Python jest w stanie obsłużyć podpakiet, albo nie, ale nie powinien wymagać zabawy z konfiguracją globalną, aby móc obsługiwać lokalne rzeczy.
  • Próbowałem również użyć importu w package/subpackage/__init__.py, ale nic nie rozwiązało, robi to samo, a narzeka subpackagenie jest znanym modułem, podczas gdy print subpackagemówi, że to moduł (znowu dziwne zachowanie).

Być może całkowicie się mylę - trudna opcja (wolałbym), ale to sprawia, że ​​czuję się bardzo rozczarowany Pythonem.

Jakikolwiek inny znany sposób poza trzema, których próbowałem? Coś, o czym nie wiem?

(westchnienie)

-----% <----- edytuj ----->% -----

Dotychczasowe wnioski (po komentarzach ludzi)

W Pythonie nie ma nic podobnego do prawdziwego pakietu podrzędnego, ponieważ wszystkie odwołania do pakietów trafiają tylko do globalnego słownika, co oznacza, że ​​nie ma lokalnego słownika, co oznacza, że ​​nie ma możliwości zarządzania odwołaniami do lokalnego pakietu.

Musisz użyć pełnego prefiksu lub krótkiego prefiksu lub aliasu. Jak w:

Pełna wersja prefiksu

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

Wersja z krótkim prefiksem (ale z powtarzającym się prefiksem)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

Albo odmiana powyższego.

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

Wersja faktoryzowana

Jeśli nie masz nic przeciwko zaimportowaniu wielu encji naraz w partii, możesz:

from package.subpackage.module import attribute1, attribute2
# and etc.

Nie w moim pierwszym ulubionym guście (wolę mieć jedno oświadczenie dotyczące importu na importowany podmiot), ale może być tym, które osobiście faworyzuję.

Aktualizacja (2012-09-14):

Wreszcie wydaje się, że w praktyce jest OK, z wyjątkiem komentarza dotyczącego układu. Zamiast powyższego użyłem:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.
Hibou57
źródło
Jak się mają sprawy, gdy piszesz „from. Import module” do „/package/subpackage/__init__.py”?
Markus Unterwaditzer
Twoja „wersja na czynniki” wydaje się być odpowiednia do tego, co chcesz zrobić. Jeśli wykonujesz oddzielną linię importu dla atrybutu1 i atrybutu2 (jak „wolisz”), po prostu celowo poświęcasz sobie więcej pracy. Nie ma powodu, żeby to robić.
BrenBarn
Przepraszam, ale nie rozumiem tego, czego chcesz. Czy mógłbyś sformułować swoje pytanie jaśniej? Co dokładnie chciałbyś zrobić? Chodzi mi o to, co chciałbyś napisać, co nie działa i jak byś oczekiwał, że zadziała? Z tego, co przeczytałem, myślę, że obejmuje semantykę importu, taką jak Java, a może C. Ostatnia rzecz: możesz sprawić, by moduł „import-gwiazdka” był bezpieczny, dodając __all__zmienną zawierającą listę nazw, które powinny być eksportowane podczas importu z gwiazdą. edycja: OK, czytając odpowiedź BrenBarna, zrozumiałem, co masz na myśli.
Bakuriu

Odpowiedzi:

68

Wydaje się, że nie rozumiesz, w jaki sposób importwyszukuje się moduły. Kiedy używasz instrukcji importu, zawsze przeszukuje ona aktualną ścieżkę modułu (i / lub sys.modules); nie korzysta z obiektów modułów w lokalnej przestrzeni nazw, które istnieją z powodu poprzednich importów. Kiedy robisz:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

Druga linia szuka pakietu wywołanego package.subpackagei importuje go modulez tego pakietu. Ta linia nie ma wpływu na trzecią linię. Trzecia linia po prostu szuka modułu o nazwie modulei go nie znajduje. Nie "ponownie używa" obiektu wywołanego module, który otrzymałeś z powyższej linii.

Innymi słowy from someModule import ..., nie oznacza „z modułu o nazwie jakiśModuł, który wcześniej zaimportowałem…”, to znaczy „z modułu o nazwie jakiśModuł, który znajdziesz na sys.path…”. Nie ma możliwości „przyrostowego” budowania ścieżki modułu poprzez importowanie pakietów, które do niego prowadzą. Podczas importu zawsze musisz odwołać się do całej nazwy modułu.

Nie jest jasne, co próbujesz osiągnąć. Jeśli chcesz zaimportować tylko określony atrybut obiektu1, po prostu zrób to from package.subpackage.module import attribute1i skończ z tym. Nigdy nie musisz się martwić o długi czas package.subpackage.modulepo zaimportowaniu z niego żądanej nazwy.

Jeśli nie chcesz mieć dostęp do modułu dostępu do innych nazw później, to można zrobić from package.subpackage import modulei jak widzieliście można wtedy zrobić module.attribute1i tak dalej jak lubisz.

Jeśli chcesz mieć oba - to znaczy, jeśli chcesz mieć attribute1bezpośredni dostęp i chcesz, aby były moduledostępne, wykonaj obie powyższe czynności:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

Jeśli nie lubisz pisać package.subpackagenawet dwa razy, możesz po prostu ręcznie utworzyć lokalne odniesienie do atrybutu1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
BrenBarn
źródło
Twoje komentarze idą w tym samym kierunku, co komentarze Ignacio Vazquez-Abrams (skomentowałem jego wiadomość). module.attribute1Myślałem o dodaniu na końcu, o używaniu , ale pomyślałem, że byłby sposób na uniknięcie konieczności stosowania prefiksu wszędzie. Więc muszę albo użyć prefiksu w każdym miejscu, albo utworzyć lokalny alias, powtarzając nazwę. Nie taki styl, jakiego się spodziewałem, ale jeśli nie ma sposobu (w końcu jestem przyzwyczajony do Ady, która wymaga czegoś podobnego z deklaracjami zmiany nazwy).
Hibou57
@ Hibou57: Nadal nie jest dla mnie jasne, co próbujesz osiągnąć w swojej „wersji 2”. Co chcesz zrobić, co nie jest możliwe? Chcesz nigdy nie wpisywać ponownie żadnej części nazwy pakietu / modułu / atrybutu, ale nadal importować zarówno moduł, jak i jego atrybut?
BrenBarn
Chciałem mieć odniesienie do pakietu lokalnego, tak jak w przypadku odniesienia do obiektu lokalnego. Wygląda na to, że w końcu istnieją lokalne odniesienia do modułów, ale nie można ich importować. To mieszanka lokalnych i globalnych ze śmiesznym smakiem (niektóre rzeczy mogą być lokalne, inne muszą być globalne, nie podoba mi się to, ale nic mi nie jest, o ile lepiej rozumiem, jak to działa). Dzięki za wiadomość.
Hibou57
1
Nie jestem pewien, czy nadal rozumiesz, jak to działa. W każdym razie, że zrobiłeś to w 2012 roku.
Hejazzman
1
Za każdym razem, gdy wracam do Pythona po 6-miesięcznej przerwie, ląduję tutaj. Gdybym tylko mógł zagłosować za każdym razem, gdy odwiedzam tę stronę! Zrobię gigantyczny plakat z następującym zdaniem: „Nie ma sposobu, aby„ przyrostowo ”budować ścieżkę modułu poprzez importowanie pakietów, które do niego prowadzą”.
PatrickT
10

Przyczyną niepowodzenia # 2 jest to, że sys.modules['module']nie istnieje (procedura importu ma swój własny zakres i nie widzi modulenazwy lokalnej), a modulena dysku nie ma modułu ani pakietu. Pamiętaj, że możesz oddzielić wiele importowanych nazw przecinkami.

from package.subpackage.module import attribute1, attribute2, attribute3

Również:

from package.subpackage import module
print module.attribute1
Ignacio Vazquez-Abrams
źródło
Twoje odniesienie, o sys.modules['name']którym nie wiedziałem do tej pory, sprawiło, że pomyślałem, że właśnie tego się obawiałem (a BrenBarn potwierdza): w Pythonie nie ma nic lepszego niż prawdziwe pakiety podrzędne. sys.modules, jak sugeruje jego nazwa, jest globalny i jeśli wszystkie odniesienia do modułów na nim polegają, to nie ma nic lepszego niż lokalne odniesienie do modułu (może pochodzić z Python 3.x?).
Hibou57
Twoje użycie „odniesienia” jest niejednoznaczne; pierwsza importw # 2 generuje odwołanie lokalne do package.subpackage.modulepowiązania module.
Ignacio Vazquez-Abrams
Tak, ale to jest „moduł”, z którego nie mogę zaimportować ;-)
Hibou57
0

Jeśli wszystko, co próbujesz zrobić, to uzyskać atrybut1 w globalnej przestrzeni nazw, wersja 3 wydaje się w porządku. Dlaczego jest to przedrostek przesady?

W wersji 2 zamiast

from module import attribute1

możesz to zrobić

attribute1 = module.attribute1
Thomas Vander Stichele
źródło
attribute1 = module.attribute1jest po prostu powtarzaniem nazwy bez wartości dodanej. Wiem, że to działa, ale nie podoba mi się ten styl (co nie oznacza, że ​​nie podoba mi się twoja odpowiedź).
Hibou57
2
Wydaje mi się, że podobnie jak wszyscy komentujący tutaj nie rozumiem, co chcesz robić. We wszystkich przykładach, które podajesz, wygląda na to, że chcesz otrzymać symbol z podpakietu w Twojej przestrzeni nazw. Twój niedziałający przykład (przykład 2) chce to zrobić, importując podmoduł z pakietu, a następnie importując symbol z tego podmodułu. Nie wiem, dlaczego chcesz to zrobić w dwóch krokach zamiast w jednym. Może wyjaśnij więcej, jakie byłoby Twoje idealne rozwiązanie i dlaczego.
Thomas Vander Stichele