Come eseguire le importazioni relative in Python?


527

Immagina questa struttura di directory:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

Sto codificando mod1e devo importare qualcosa da mod2. Come dovrei farlo?

Ho provato from ..sub2 import mod2ma sto ottenendo un "Tentativo di importazione relativa in non pacchetto".

Ho cercato su Google ma ho trovato solo sys.pathhack di " manipolazione". Non c'è un modo pulito?


Modifica: tutti i miei __init__.pysono attualmente vuoti

Edit2: Sto cercando di fare questo perché sub2 contiene classi che sono condivisi tra i pacchetti di sub ( sub1, subX, ecc).

Edit3: il comportamento che sto cercando è lo stesso descritto in PEP 366 (grazie John B)


8
Ti consiglio di aggiornare la tua domanda per chiarire che stai descrivendo il problema risolto in PEP 366.
John B

2
È una spiegazione lunga ma controlla qui: stackoverflow.com/a/10713254/1267156 Ho risposto a una domanda molto simile. Ho avuto lo stesso problema fino a ieri sera.
Sevvy325,

3
Per coloro che desiderano caricare un modulo situato in un percorso arbitrario, vedere questo: stackoverflow.com/questions/67631/…
Evgeni Sergeev

2
In una nota correlata, Python 3 cambierà la gestione predefinita delle importazioni in modo che sia assoluta per impostazione predefinita; le importazioni relative dovranno essere esplicitamente specificate.
Ross,

Risposte:


337

Tutti sembrano voler dirti cosa dovresti fare invece di rispondere alla domanda.

Il problema è che stai eseguendo il modulo come '__main__' passando il mod1.py come argomento all'interprete.

Da PEP 328 :

Le importazioni relative utilizzano l'attributo __name__ di un modulo per determinare la posizione di quel modulo nella gerarchia dei pacchetti. Se il nome del modulo non contiene informazioni sul pacchetto (ad es. È impostato su '__main__'), le importazioni relative vengono risolte come se il modulo fosse un modulo di livello superiore, indipendentemente da dove si trova effettivamente il modulo sul file system.

In Python 2.6, stanno aggiungendo la possibilità di fare riferimento ai moduli relativi al modulo principale. PEP 366 descrive la modifica.

Aggiornamento : Secondo Nick Coghlan, l'alternativa consigliata è far funzionare il modulo all'interno del pacchetto usando l'opzione -m.


2
La risposta qui implica fare confusione con sys.path in ogni punto di accesso al programma. Immagino sia l'unico modo per farlo.
Nick Retallack,

76
L'alternativa consigliata è eseguire i moduli all'interno dei pacchetti utilizzando l' -mopzione, anziché specificando direttamente il loro nome file.
ncoghlan,

127
Non capisco: dov'è la risposta qui? Come si possono importare moduli in una tale struttura di directory?
Tom,

27
@ Tom: In questo caso, mod1 lo farebbe from sub2 import mod2. Quindi, per eseguire mod1, dall'interno dell'app, fare python -m sub1.mod1.
Xiong Chiamiov,

11
@XiongChiamiov: questo significa che non puoi farlo se il tuo python è incorporato in un'applicazione, quindi non hai accesso alle opzioni della riga di comando di Python?
LarsH,

130

Ecco la soluzione che funziona per me:

Faccio le importazioni relative come from ..sub2 import mod2 e poi, se voglio correre, mod1.pyvado nella directory padre apped eseguo il modulo usando l'opzione python -m come python -m app.sub1.mod1.

Il vero motivo per cui questo problema si verifica con le importazioni relative è che le importazioni relative funzionano prendendo la __name__proprietà del modulo. Se il modulo viene eseguito direttamente, __name__è impostato su __main__e non contiene alcuna informazione sulla struttura del pacchetto. Ed è per questo che Python si lamenta relative import in non-packagedell'errore.

Quindi, usando l'opzione -m fornisci le informazioni sulla struttura del pacchetto a Python, attraverso le quali può risolvere con successo le relative importazioni.

Ho riscontrato questo problema molte volte durante le importazioni relative. E, dopo aver letto tutte le risposte precedenti, non ero ancora in grado di capire come risolverlo, in modo pulito, senza la necessità di inserire il codice boilerplate in tutti i file. (Sebbene alcuni dei commenti siano stati davvero utili, grazie a @ncoghlan e @XiongChiamiov)

Spero che questo aiuti qualcuno che sta lottando con il problema delle importazioni relative, perché passare attraverso PEP non è davvero divertente.


9
Risposta migliore IMHO: non solo spiega perché OP ha avuto il problema, ma trova anche un modo per risolverlo senza cambiare il modo in cui i suoi moduli effettuano le importazioni . Dopotutto, le importazioni relative di OP andavano bene. Il colpevole era la mancanza di accesso ai pacchetti esterni durante l'esecuzione diretta come script, qualcosa è -mstato progettato per risolvere.
MestreLion

26
Prendi anche nota: questa risposta è avvenuta 5 anni dopo la domanda. Queste funzionalità non erano disponibili al momento.
JeremyKun,

1
Se vuoi importare un modulo dalla stessa directory, puoi farlo from . import some_module.
Rotareti,

124
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Tu corri python main.py.
  2. main.py fa: import app.package_a.module_a
  3. module_a.py fa import app.package_b.module_b

In alternativa 2 o 3 potrebbero usare: from app.package_a import module_a

Funzionerà per tutto il tempo che hai appnel tuo PYTHONPATH. main.pypotrebbe essere ovunque allora.

Quindi scrivi a setup.pyper copiare (installare) l'intero pacchetto dell'app e i pacchetti secondari nelle cartelle Python del sistema di main.pydestinazione e nelle cartelle degli script del sistema di destinazione.


3
Risposta eccellente. C'è un modo per importare in quel modo senza installare il pacchetto in PYTHONPATH?
auraham,


6
quindi, un giorno, è necessario modificare il nome dell'app in test_app. cosa succederebbe? Dovrai cambiare tutti i codici sorgente, importare app.package_b.module_b -> test_app.package_b.module_b. questa è una pratica assolutamente MALE ... E dovremmo provare a usare l'importazione relativa all'interno del pacchetto.
Spybdai,

49

"Guido vede gli script in esecuzione all'interno di un pacchetto come anti-pattern" (rifiutato PEP-3122 )

Ho trascorso così tanto tempo a cercare una soluzione, leggendo i post correlati qui su Stack Overflow e dicendo a me stesso "deve esserci un modo migliore!". Sembra che non ci sia.


10
Nota: già menzionato pep-366 (creato all'incirca nello stesso periodo di pep-3122 ) offre le stesse funzionalità ma utilizza un'implementazione compatibile con le versioni precedenti, ad esempio, se si desidera eseguire un modulo all'interno di un pacchetto come script e utilizzare importazioni relative esplicite in esso puoi eseguirlo usando -mswitch: python -m app.sub1.mod1o invocare app.sub1.mod1.main()da uno script di livello superiore (ad esempio, generato dai entry_points di setuptools definiti in setup.py).
jfs,

+1 per l'utilizzo di setuptools e punti di ingresso - è un modo corretto di impostare script che verranno eseguiti dall'esterno, in una posizione ben definita, anziché hackerare all'infinito PYTHONPATH
RecencyEffect

38

Questo è risolto al 100%:

  • app /
    • main.py
  • impostazioni/
    • local_setings.py

Importa impostazioni / local_setting.py in app / main.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')

2
grazie! tutti i ppl mi stavano costringendo a eseguire il mio script in modo diverso invece di dirmi come risolverlo all'interno dello script. Ma ho dovuto cambiare il codice da usare sys.path.insert(0, "../settings")e poifrom local_settings import *
Vit Bernatik,

25
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

Sto usando questo frammento per importare moduli da percorsi, spero che sia d'aiuto


2
Sto usando questo frammento, combinato con il modulo imp (come spiegato qui [1]) con grande efficacia. [1]: stackoverflow.com/questions/1096216/...
Xiong Chiamiov

7
Probabilmente, sys.path.append (path) dovrebbe essere sostituito con sys.path.insert (0, path) e sys.path [-1] dovrebbe essere sostituito con sys.path [0]. Altrimenti la funzione importerà il modulo sbagliato, se esiste già un modulo con lo stesso nome nel percorso di ricerca. Ad esempio, se nella directory corrente è presente "some.py", import_path ("/ imports / some.py") importerà il file errato.
Alex Che,

Sono d'accordo! A volte altre importazioni relative avranno la precedenza. Usa sys.path.insert
iElectric

Come replicheresti il ​​comportamento di from x import y (o *)?
Levesque,

Non è chiaro, si prega di specificare il pieno utilizzo di questo script per risolvere il problema OP.
mrgloom,

21

spiegazione della nosklo'srisposta con esempi

nota: tutti i __init__.pyfile sono vuoti.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

app / package_a / fun_a.py

def print_a():
    print 'This is a function in dir package_a'

app / package_b / fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

se lo esegui $ python main.pyrestituisce:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py fa: from app.package_b import fun_b
  • fun_b.py lo fa from app.package_a.fun_a import print_a

quindi file nella cartella package_butilizzato file nella cartella package_a, che è quello che vuoi. Giusto??


12

Questo è purtroppo un hack sys.path, ma funziona abbastanza bene.

Ho riscontrato questo problema con un altro livello: avevo già un modulo con il nome specificato, ma era il modulo sbagliato.

quello che volevo fare era il seguente (il modulo da cui lavoravo era module3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Nota che ho già installato mymodule, ma nella mia installazione non ho "mymodule1"

e otterrei un ImportError perché stava cercando di importare dai miei moduli installati.

Ho provato a fare un sys.path.append e non ha funzionato. Ciò che ha funzionato è stato un sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

È una specie di hack, ma ha funzionato tutto! Quindi tieni a mente, se vuoi che la tua decisione abbia la precedenza su altri percorsi, allora devi usare sys.path.insert (0, nome percorso) per farlo funzionare! Questo è stato un punto critico molto frustrante per me, molte persone dicono di usare la funzione "append" a sys.path, ma non funziona se hai già un modulo definito (lo trovo un comportamento molto strano)


sys.path.append('../')funziona bene per me (Python 3.5.2)
Nister

Penso che vada bene poiché localizza l'hack sull'eseguibile e non influenza altri moduli che potrebbero dipendere dai tuoi pacchetti.
Tom Russell,

10

Lascia che lo metta qui per mio riferimento. So che non è un buon codice Python, ma avevo bisogno di uno script per un progetto su cui stavo lavorando e volevo mettere lo script in una scriptsdirectory.

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

9

Come dice @EvgeniSergeev nei commenti all'OP, è possibile importare il codice da un .pyfile in una posizione arbitraria con:

import imp

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

Questo è tratto da questa risposta SO .



2

Dal documento Python ,

In Python 2.5, puoi cambiare il comportamento dell'importazione in importazioni assolute usando una from __future__ import absolute_importdirettiva. Questo comportamento di importazione assoluta diventerà l'impostazione predefinita in una versione futura (probabilmente Python 2.7). Una volta che le importazioni assolute sono predefinite, import stringtroverà sempre la versione della libreria standard. Si suggerisce agli utenti di iniziare a utilizzare il più possibile le importazioni assolute, quindi è preferibile iniziare a scrivere from pkg import stringnel codice


1

Ho trovato più facile impostare la variabile di ambiente "PYTHONPATH" sulla cartella principale:

bash$ export PYTHONPATH=/PATH/TO/APP

poi:

import sub1.func1
#...more import

ovviamente PYTHONPATH è "globale", ma non ha ancora sollevato problemi per me.


Questo è essenzialmente il modo virtualenvin cui ti consente di gestire le tue dichiarazioni di importazione.
byxor,

1

Oltre a ciò che ha detto John B, sembra che l'impostazione della __package__variabile dovrebbe aiutare, invece di cambiare__main__ che potrebbe rovinare altre cose. Ma per quanto ho potuto testare, non funziona completamente come dovrebbe.

Ho lo stesso problema e né PEP 328 o 366 risolvono completamente il problema, poiché entrambi, alla fine della giornata, hanno bisogno che il capo del pacchetto sia incluso in sys.path , per quanto ho potuto capire.

Dovrei anche menzionare che non ho trovato il modo di formattare la stringa che dovrebbe andare in quelle variabili. È "package_head.subfolder.module_name"o cosa?


0

Devi aggiungere il percorso del modulo a PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"

1
Questo è più o meno lo stesso della manipolazione sys.path, poiché sys.pathviene inizializzato daPYTHONPATH
Joril il

@Joril È corretto ma sys.pathdeve essere codificato nel codice sorgente al contrario del PYTHONPATHquale è una variabile di ambiente e può essere esportata.
Giorgos Myrianthous,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.