oltre l'errore del pacchetto di livello superiore nell'importazione relativa


317

Sembra che ci siano già alcune domande sull'importazione relativa in Python 3, ma dopo averne esaminate molte non ho ancora trovato la risposta al mio problema. quindi ecco la domanda.

Ho un pacchetto mostrato di seguito

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

e ho una sola riga in test.py:

from ..A import foo

ora sono nella cartella di packagee corro

python -m test_A.test

Ho ricevuto un messaggio

"ValueError: attempted relative import beyond top-level package"

ma se mi trovo nella cartella padre di package, ad esempio, corro:

cd ..
python -m package.test_A.test

va tutto bene.

Ora la mia domanda è: quando mi trovo nella cartella di package, ed eseguo il modulo all'interno del sotto-pacchetto test_A poiché test_A.test, in base alla mia comprensione, ..Asale solo un livello, che è ancora all'interno della packagecartella, perché dà un messaggio beyond top-level package. Qual è esattamente il motivo che causa questo messaggio di errore?


49
quel post non spiegava il mio errore "oltre il pacchetto di livello superiore"
riparo il

4
Ho pensato qui, quindi quando esegui test_A.test come modulo, '..' supera test_A, che è già il livello più alto dell'importazione test_A.test, penso che il livello del pacchetto non sia il livello della directory, ma quanti livelli in cui importi il ​​pacchetto.
rifugio

2
Ti prometto che capirai tutto sull'importazione relativa dopo aver visto questa risposta stackoverflow.com/a/14132912/8682868 .
pzjzeason

vedere ValueError: tentativo di importazione relativa oltre il pacchetto di livello superiore per una spiegazione dettagliata di questo problema.
napuzba,

C'è un modo per evitare di fare importazioni relative? Come il modo in cui PyDev in Eclipse vede tutti i pacchetti in <PydevProject> / src?
Mushu909,

Risposte:


173

EDIT: ci sono risposte migliori / più coerenti a questa domanda in altre domande:


Perché non funziona? È perché Python non registra da dove è stato caricato un pacchetto. Quindi, quando lo fai python -m test_A.test, fondamentalmente scarta solo la conoscenza che test_A.testè effettivamente memorizzata package(cioè packagenon è considerata un pacchetto). Tentare from ..A import foodi accedere alle informazioni che non ha più (ad es. Directory fratelli di una posizione caricata). È concettualmente simile a consentire from ..os import pathin un file in math. Questo sarebbe male perché vuoi che i pacchetti siano distinti. Se hanno bisogno di usare qualcosa da un altro pacchetto, allora dovrebbero fare riferimento a loro a livello globale con from os import pathe lasciare che Python risolva dove si trova con $PATHe $PYTHONPATH.

Quando lo usi python -m package.test_A.test, l'utilizzo si from ..A import foorisolve bene perché ha tenuto traccia di ciò che è dentro packagee stai solo accedendo a una directory figlio di una posizione caricata.

Perché python non considera la directory di lavoro corrente come un pacchetto? NO CLUE , ma cavolo sarebbe utile.


2
Ho modificato la mia risposta per fare riferimento a una risposta migliore a una domanda che equivale alla stessa cosa. Esistono solo soluzioni alternative. L'unica cosa che ho visto effettivamente funzionare è ciò che ha fatto l'OP, che è usare il -mflag ed eseguire dalla directory sopra.
Multihunter,

1
Va notato che questa risposta , dal link fornito da Multihunter, non comporta l' sys.pathhacking, ma l'uso di setuptools , che è molto più interessante secondo me.
Angelo Cardellicchio,

157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Prova questo. Ha funzionato per me.


10
Umm ... come funziona? Ogni singolo file di prova avrebbe questo?
George Mauer,

Il problema qui è se, per esempio, A/bar.pyesiste e in foo.pyte esiste from .bar import X.
user1834164

9
Ho dovuto rimuovere il .. da "from ..A import ..." dopo aver aggiunto sys.path.append ("..")
Jake OPJ,

2
Se lo script viene eseguito dall'esterno della directory esistente, ciò non funzionerebbe. Invece, devi modificare questa risposta per specificare il percorso assoluto di detto script .
Manavalan Gajapathy

questa è l'opzione migliore, meno complicata
Alex R

43

Presupposto:
se ci si trova nella packagedirectory Ae ci test_Asono pacchetti separati.

Conclusione: le
..Aimportazioni sono consentite solo all'interno di un pacchetto.

Ulteriori note:
Rendere le relative importazioni disponibili solo all'interno dei pacchetti è utile se si desidera forzare che i pacchetti possano essere posizionati su qualsiasi percorso situato sys.path.

MODIFICARE:

Sono l'unico che pensa che questo sia pazzo !? Perché nel mondo l'attuale directory di lavoro non è considerata un pacchetto? - Multihunter

La directory di lavoro corrente si trova di solito in sys.path. Quindi, tutti i file sono importabili. Questo è un comportamento da Python 2 quando i pacchetti non esistevano ancora. Rendere la directory in esecuzione un pacchetto consentirebbe l'importazione di moduli come "import .A" e come "import A" che sarebbero quindi due moduli diversi. Forse questa è un'incoerenza da considerare.


86
Sono l'unico che pensa che questo sia pazzo !? Perché nel mondo la directory corrente non è considerata un pacchetto?
Multihunter,

13
Non solo è folle, ma non è utile ... quindi come si eseguono i test? Chiaramente ciò che l'OP stava chiedendo e perché sono sicuro che anche molte persone sono qui.
George Mauer,

La directory in esecuzione si trova di solito in sys.path. Quindi, tutti i file sono importabili. Questo è un comportamento da Python 2 quando i pacchetti non esistevano ancora. - risposta modificata.
Utente

Non seguo l'incoerenza. Il comportamento di python -m package.test_A.testsembra fare ciò che si desidera e la mia argomentazione è che dovrebbe essere l'impostazione predefinita. Quindi, puoi darmi un esempio di questa incoerenza?
Multihunter

In realtà sto pensando, c'è una richiesta di funzionalità per questo? Questo è davvero folle. Lo stile C / C ++ #includesarebbe molto utile!
Nicholas Humphrey,

29

Nessuna di queste soluzioni ha funzionato per me in 3.6, con una struttura di cartelle come:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

Il mio obiettivo era importare da module1 in module2. Ciò che alla fine ha funzionato per me è stato, stranamente:

import sys
sys.path.append(".")

Nota il singolo punto in contrapposizione alle soluzioni a due punti menzionate finora.


Modifica: Quanto segue mi ha aiutato a chiarire questo per me:

import os
print (os.getcwd())

Nel mio caso, la directory di lavoro era (inaspettatamente) la radice del progetto.


2
funziona localmente ma non funziona sull'istanza di aws ec2, ha senso?
thebeancounter,

Questo ha funzionato anche per me - nel mio caso la directory di lavoro era anche la radice del progetto. Stavo usando una scorciatoia da un editor di programmazione (TextMate)
JeremyDouglass

@thebeancounter Same! Funziona localmente sul mio mac ma non funziona su ec2, quindi mi sono reso conto che stavo eseguendo il comando in un sottodir su ec2 ed eseguendolo in root localmente. Una volta eseguito da root su ec2 ha funzionato.
Logan Yang,

Questo ha funzionato anche per me molto apprezzato. Da quel metodo sys ora posso semplicemente chiamare il pacchetto senza la necessità di ".."
RamWill

sys.path.append(".")ha funzionato perché lo stai chiamando nella directory principale, nota che .rappresenta sempre la directory in cui esegui il comando python.
KevinZhou

13

from package.A import foo

Penso che sia più chiaro di

import sys
sys.path.append("..")

4
è sicuramente più leggibile ma necessita ancora sys.path.append(".."). testato su Python 3.6
MFA,

Come le risposte più vecchie
nrofis,

12

Come suggerisce la risposta più popolare, fondamentalmente è perché il tuo PYTHONPATHo sys.pathinclude .ma non il percorso del pacchetto. E l'importazione relativa è relativa alla directory di lavoro corrente, non al file in cui avviene l'importazione; stranamente.

Puoi risolvere questo problema cambiando prima l'importazione relativa in assoluta e quindi avviandola con:

PYTHONPATH=/path/to/package python -m test_A.test

O forzando il percorso python quando viene chiamato in questo modo, perché:

Con python -m test_A.testl'esecuzione test_A/test.pycon __name__ == '__main__'e__file__ == '/absolute/path/to/test_A/test.py'

Ciò significa che in test.pyte potresti usare il tuo importsemi-protetto assoluto nella condizione del caso principale e anche fare una manipolazione del percorso Python una tantum:

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())

8

Modifica: 2020-05-08: Sembra che il sito web che ho citato non sia più controllato dalla persona che ha scritto il consiglio, quindi sto rimuovendo il link al sito. Grazie per avermi fatto conoscere baxx.


Se qualcuno sta ancora lottando un po 'dopo le ottime risposte già fornite, ho trovato consigli su un sito Web che non è più disponibile.

Citazione essenziale dal sito che ho citato:

"Lo stesso può essere specificato a livello di codice in questo modo:

import sys

sys.path.append ( '..')

Ovviamente il codice sopra deve essere scritto prima dell'altra importazione dichiarazione di .

È abbastanza ovvio che deve essere così, pensandoci dopo il fatto. Stavo cercando di utilizzare sys.path.append ('..') nei miei test, ma ho riscontrato il problema pubblicato da OP. Aggiungendo la definizione import e sys.path prima delle altre mie importazioni, sono stato in grado di risolvere il problema.


il link che hai pubblicato è morto.
baxx,

Grazie per avermi fatto sapere. Sembra che il nome di dominio non sia più controllato dalla stessa persona. Ho rimosso il link.
Mierpo

5

se hai un __init__.pyin una cartella superiore, puoi inizializzare l'importazione come import file/path as aliasin quel file init. Quindi puoi usarlo su script inferiori come:

import alias

0

A mio modesto parere, capisco questa domanda in questo modo:

[CASO 1] Quando si avvia un'importazione assoluta come

python -m test_A.test

o

import test_A.test

o

from test_A import test

stai effettivamente impostando import-anchor in modo che sia test_A, in altre parole, il pacchetto di livello superiore è test_A. Quindi, quando abbiamo test.py farefrom ..A import xxx , stai scappando dall'ancora e Python non lo consente.

[CASO 2] Quando lo fai

python -m package.test_A.test

o

from package.test_A import test

la vostra ancora diventa package, così package/test_A/test.pyfacendo from ..A import xxxnon sfugge l'ancora (ancora all'interno packagedella cartella), e Python accetta volentieri questo.

In breve:

  • L'importazione assoluta modifica l'ancora corrente (= ridefinisce il pacchetto di livello superiore);
  • L'importazione relativa non modifica l'ancoraggio ma si limita ad esso.

Inoltre, possiamo utilizzare il nome del modulo completo (FQMN) per esaminare questo problema.

Controlla FQMN in ogni caso:

  • [CASE2] test.__name__=package.test_A.test
  • [CASE1] test.__name__=test_A.test

Quindi, per CASE2, un from .. import xxxrisulterà in un nuovo modulo con FQMN =package.xxx , il che è accettabile.

Mentre per CASE1, ..dall'interno from .. import xxxsalterà fuori dal nodo iniziale (ancora) di test_A, e questo NON è permesso da Python.


2
Questo è molto più complicato di quanto debba essere. Questo per quanto riguarda Zen of Python.
AtilioA

0

Non sono sicuro in Python 2.x ma in Python 3.6, supponendo che tu stia cercando di eseguire l'intera suite, devi solo usare -t

-t, --top-level-directory directory Directory di livello superiore del progetto (impostazione predefinita per avviare la directory)

Quindi, su una struttura come

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

Ad esempio si potrebbe usare:

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

E ancora importare my_module.my_classsenza grandi drammi.

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.