Python: wzajemne importowanie modułów

Nie jestem pewien, skąd wzięło się takie prze­świad­czenie, ale ostat­nio kilka osób próbowało mnie prze­konać, że w Pythonie nie da się wykonać dwukierun­kowego importu.

Roz­ważmy naiwny przy­kład programu ładującego wtyczkę, która z kolei chce korzystać z API swojego hosta. Zgadzam się, że takie dwukierun­kowe zależ­no­ści to na ogół oznaka bar­dzo złego designu, są jed­nak sytuacje, w których ciężko ich uniknąć.

Pozostaje problem implemen­tacji. Okazuje się, że zwolen­nicy teo­rii nie da się dzielą się na tych, którzy nigdy nie próbowali, ale ktoś coś wspominał oraz na tych, którzy oczekiwali od Pythona zdol­no­ści myślenia. Dwukierun­kowy import na poziomie global­nym modułów nie wymaga specjal­nych zabiegów, trzeba być jed­nak przy­gotowanym na dwa ograniczenia:

  • Impor­tować można tylko wstecz, a więc wyłącz­nie sym­bole zdefiniowane przed impor­tem, który doprowadził do powstania pętli (co jest dość logiczne, gdyż w pozostałych przy­pad­kach mamy do czynienia z problemem jajka i kury)
  • W pew­nych okolicz­no­ściach kod zostanie wykonany dwukrotnie

Działającą pętlę impor­tów pokazuje poniż­szy przykład:

Uwaga: na diagramie jest literówka, oczywi­ście moduł bar powinien impor­tować sym­bol a zamiast b.

Circular dependencies in Python

6 Responses to “Python: wzajemne importowanie modułów”


  • Dla mnie zmorą są importy zaszyte np. w środku (pół biedy gdy są zaraz obok definicji) metody/funkcji.
    Czasami ciężko się połapać w takich potwor­kach jeśli są nie­udokumen­towane przez co wdrażanie/testowanie aplikacji jest o wiele bar­dziej upier­dliwe nie mówiąc o pracy z takim kodem.

  • Eee… a czy w trzeciej linijce bar.py nie powinno być przy­pad­kiem „import c” albo „import a”? Nie do końca rozumiem, jak to miałoby działać — czy jeśli impor­towana z pętli zmienna jest zdefiniowana (przy­pisana) powyżej, a następ­nie zredefiniowana *poniżej* importu tworzącego pętlę — czy z pętli dostaniemy pierw­szą war­tość, czy błąd? Co jeśli zamiast redefinicji mamy zmianę mutowal­nego stanu?

    Wydaje mi się, że czę­ściowe wspar­cie dla pętli impor­tów to więcej zamętu niż korzy­ści. Bo i wła­ściwie po co?

  • japhy:

    Powinno być „a”, literówka.

    Dlaczego „czę­ściowe” wspar­cie? Jest kom­pletne. Po co? Choćby Haystack — pierw­sza z brzegu aplikacja Django, która impor­tuje pozostałe aplikacje projektu, zbierając modele ORM do indek­sowania. Z kolei twój kod chciałby pew­nie móc coś w tym idek­sie wyszukać, co wymaga zaim­por­towania Haystacka.

  • Jest czę­ściowe, bo kom­plet­nego nie bar­dzo jest jak zaim­plemen­tować (nie zrobisz „from foo import *”, a nawet „import foo” w bar.py, prawda?).

    A Haystacka albo impor­tuję w models.py i sam wołam haystack.site.register() — albo:
    1. w urls.py projektu impor­tuję haystack
    2. wołam haystack.autodiscover()
    3. autodiscover impor­tuje search_indexes.py w tych aplikacjach, w których jest dostępne
    4. search_indexes.py może spo­koj­nie zaim­por­tować haystack — już został wcześniej zaim­por­towany przez urls.py, moduł jest już w pamięci.
    Discovery nie leci w czasie impor­towania haystacka — jest wykonywane póź­niej. Więc pudło.

  • japhy:

    Oczywi­ście, że w jedną stronę możesz zażyczyć sobie import foo albo i from foo import *. Spraw­dzenie tego nie zaj­mie ci więcej czasu niż napisanie komentarza.

    W cale nie pudło. Ostat­nio problem w biurze był właśnie z aplikacją third-party (nazwy nie podam), która do spółki z Haystac­kiem tworzyła pętlę zależ­no­ści (Haystack wymusza wczesne załadowanie urls, sprawdź w kodzie; obie aplikacje robiły swoje autodiscover metodą brute-force).

  • Zabawne, i „print foo.c” w bar.py rzuca AttributeEr­ror, a nie błąd w impor­cie — czyli dostajemy czę­ściowo załadowany moduł; cel pew­nie jest taki, żeby móc się do cało­ści modułu odwołać już w funk­cji czy metodzie, wtedy nazwa roz­wijana jest dopiero po załadowaniu całego foo — ale faj­nie musi się coś takiego debugować ;)

    O Haystacku pisałem na pod­stawie jego dokumen­tacji, jesz­cze go nie używałem; może bar­dziej zaawan­sowane wykorzystanie robi pętle — ale to IMO więcej kłopotu niż to warte (właśnie ze względu na zabawę z debugowaniem jeśli jed­nak coś pój­dzie ina­czej, niż myślimy — a kiedyś pójdzie).

Leave a Reply