Hoe “Poging tot relatieve import in niet-pakket” op te lossen, zelfs met __init__.py

Ik probeer PEP 328te volgen, met de volgende directory structuur:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

In core_test.pyheb ik het volgende importstatement

from ..components.core import GameLoopEvents

Als ik echter ren, krijg ik de volgende foutmelding:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

Zoeken vond ik “relatief pad dat niet werkt, zelfs niet met __init__. py” en “Importeer een module van een familielid pad” maar ze hebben niet geholpen.

Is er iets dat ik hier mis?


Antwoord 1, autoriteit 100%

Ja. Je gebruikt het niet als pakket.

python -m pkg.tests.core_test

Antwoord 2, autoriteit 93%

Om het antwoord van Ignacio Vazquez-Abramsuit te leggen:

Het Python-importmechanisme werkt relatief ten opzichte van de __name__van het huidige bestand. Wanneer u een bestand rechtstreeks uitvoert, heeft het niet zijn gebruikelijke naam, maar heeft het in plaats daarvan "__main__"als naam. Relatieve import werkt dus niet.

Je kunt, zoals Igancio voorstelde, het uitvoeren met de optie -m. Als je een deel van je pakket hebt dat bedoeld is om als script te worden uitgevoerd, kun je ook het kenmerk __package__gebruiken om dat bestand te vertellen welke naam het in de pakkethiërarchie hoort te hebben.

Zie http://www.python.org/dev/peps/pep- 0366/voor details.


Antwoord 3, autoriteit 46%

U kunt import components.corerechtstreeks gebruiken als u de huidige map toevoegt aan sys.path:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

Antwoord 4, autoriteit 45%

Het hangt ervan af hoe u uw script wilt starten.

Als je je UnitTest op een klassieke manier wilt starten vanaf de opdrachtregel, is dat:

python tests/core_test.py

Vermits in dit geval ‘components’en ‘tests’mappen voor broers en zussen zijn, kunt u de relatieve module importeren met behulp van de insertof de methode appendvan de sys.path-module.
Iets als:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

Anders kunt u uw script starten met het argument ‘-m’( merk op dat we in dit geval over een pakket praten, en dus moet u de extensie ‘.py’niet geven), dat wil zeggen:

python -m pkg.tests.core_test

In zo’n geval kunt u gewoon de relatieve import gebruiken zoals u aan het doen was:

from ..components.core import GameLoopEvents

Je kunt eindelijk de twee benaderingen combineren, zodat je script werkt, hoe het ook wordt genoemd.
Bijvoorbeeld:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents

Antwoord 5, autoriteit 6%

Doe in core_test.py het volgende:

import sys
sys.path.append('../components')
from core import GameLoopEvents

Antwoord 6, autoriteit 2%

Als uw gebruiksvoorbeeld is voor het uitvoeren van tests, en het lijkt erop dat dit het geval is, kunt u het volgende doen. In plaats van uw testscript uit te voeren als python core_test.py, gebruikt u een testframework zoals pytest. Vervolgens kunt u op de opdrachtregel

. invoeren

$$ py.test

Dat zal de tests in uw directory uitvoeren. Dit omzeilt het probleem dat __name____main__is, waarop werd gewezen door @BrenBarn. Plaats vervolgens een leeg bestand __init__.pyin uw testdirectory, hierdoor wordt de testdirectory onderdeel van uw pakket. Dan zul je in staat zijn om

from ..components.core import GameLoopEvents

Als u uw testscript echter als hoofdprogramma uitvoert, zal het opnieuw mislukken. Gebruik dus gewoon de testrunner. Misschien werkt dit ook met andere testlopers zoals nosetestsmaar ik heb het niet gecontroleerd. Ik hoop dat dit helpt.


Antwoord 7, autoriteit 2%

Mijn snelle oplossing is om de map toe te voegen aan het pad:

import sys
sys.path.insert(0, '../components/')

Antwoord 8, autoriteit 2%

Er is een probleem met uw testmethode,

je hebt python core_test.py

geprobeerd

dan krijg je deze foutmelding
ValueError: Poging tot relatieve import in niet-pakket

Reden: u test uw verpakking van niet-verpakkingsbron.

dus test je module vanuit de pakketbron.

als dit uw projectstructuur is,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

cd pkg

python -m tests.core_test # dont use .py

of van buiten pkg/

python -m pkg.tests.core_test

enkele .als u wilt importeren uit een map in dezelfde map.
voor elke stap terug voeg je er nog een toe.

hi/
  hello.py
how.py

in how.py

from .hi import hello

voor als je hoe van hello.py wilt importeren

from .. import how

Antwoord 9

Als Paolozei, we hebben 2 aanroepmethoden:

1) python -m tests.core_test
2) python tests/core_test.py

Een verschil tussen beide is de tekenreeks sys.path[0]. Aangezien de interpreter zoekt in sys.path bij het importeren, kunnen we doen met tests/core_test.py:

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

En meer hierna kunnen we core_test.py met andere methoden uitvoeren:

cd tests
python core_test.py
python -m core_test
...

Opmerking, alleen py36 getest.


Antwoord 10

Oud topic. Ik ontdekte dat het toevoegen van een __all__= ['submodule', ...]aan de
__init__.pybestand en gebruik vervolgens de from <CURRENT_MODULE> import *in het doel werkt prima.


Antwoord 11

Je kunt from pkg.components.core import GameLoopEventsgebruiken, ik gebruik bijvoorbeeld pycharm, het onderstaande is mijn projectstructuurafbeelding, ik importeer gewoon vanuit het rootpakket, dan werkt het:

p>

voer hier de afbeeldingsbeschrijving in


Antwoord 12

Deze aanpak werkte voor mij en is minder rommelig dan sommige oplossingen:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

De bovenliggende map bevindt zich in mijn PYTHONPATH en er zijn __init__.pybestanden in de bovenliggende map en deze map.

Het bovenstaande werkte altijd in python 2, maar python 3 raakte soms een ImportError of ModuleNotFoundError (de laatste is nieuw in python 3.6 en een subklasse van ImportError), dus de volgende aanpassing werkt voor mij in zowel python 2 als 3:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents

Antwoord 13

Probeer dit

import components
from components import *

Antwoord 14

Als iemand op zoek is naar een tijdelijke oplossing, kwam ik er een tegen. Hier is een beetje context. Ik wilde een van de methoden testen die ik in een bestand heb. Als ik het van binnenuit uitvoer

if __name__ == "__main__":

het klaagde altijd over de relatieve invoer. Ik heb geprobeerd de bovenstaande oplossingen toe te passen, maar dat lukte niet, omdat er veel geneste bestanden waren, elk met meerdere importen.

Dit is wat ik deed. Ik heb zojuist een opstartprogramma gemaakt, een extern programma dat de nodige methoden zou importeren en ze zou aanroepen. Hoewel het geen geweldige oplossing is, werkt het.


Antwoord 15

Omdat je alles al als een module hebt gemarkeerd, is het niet nodig om de relatieve referentie te gebruiken als je als python-module start.

In plaats van

from ..components.core import GameLoopEvents

gewoon

from pkg.components.core import GameLoopEvents

Als je start vanaf de bovenliggende pagina van pkg, gebruik dan het volgende

python -m pkg.tests.core_test

Antwoord 16

Alleen voor mij werkte dit: ik moest de waarde van packageexpliciet instellen op de bovenliggende map en de bovenliggende map toevoegen aan sys.path

from os import path
import sys
if __package__ is None:
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    __package__= "myparent"
from .subdir import something # the . can now be resolved

Ik kan mijn script nu rechtstreeks uitvoeren met python myscript.py.


Antwoord 17

Hier is een manier die iedereen irriteert, maar die redelijk goed werkt. In uitgevoerde tests:

ln -s ../components components

Importeer vervolgens componenten zoals u normaal zou doen.


Antwoord 18

Dit is erg verwarrend, en als je IDE gebruikt zoals pycharm, is het iets meer verwarrend.
Wat werkte voor mij:
1. Maak pycharm-projectinstellingen (als u python uitvoert vanuit een VE- of python-directory)
2. Er is niets mis met de manier die je hebt gedefinieerd. soms werkt het met
uit map1.bestand1 importklasse

als het niet werkt, gebruik
map importeren1.bestand1
3. Uw omgevingsvariabele moet correct in het systeem worden vermeld of in uw opdrachtregelargument worden opgegeven.


Antwoord 19

python <main module>.pywerkt niet met relatieve import

Het probleem is relatieve importwerkt niet wanneer u een module __main__uitvoert vanaf de opdrachtregel

python <main_module>.py

Het staat duidelijk vermeld in PEP 338.

De release van 2.5b1 toonde een verrassende (hoewel duidelijk achteraf) interactie tussen deze PEP en PEP 328 – expliciete relatieve import werkt niet vanuit een hoofdmodule. Dit komt door het feit dat relatieve imports afhankelijk zijn van __name__om de positie van de huidige module in de pakkethiërarchie te bepalen. In een hoofdmodule is de waarde van __name__altijd '__main__', dus expliciete relatieve importen zullen altijd mislukken(omdat ze alleen werken voor een module in een pakket).

Oorzaak

Het probleem is niet echt uniek voor de schakeloptie -m. Het probleem is dat relatieve importen gebaseerd zijn op __name__, en in de hoofdmodule heeft __name__altijd de waarde __main__. Vandaar dat relatieve imports momenteel niet goed kunnen werken vanuit de hoofdmodule van een applicatie, omdat de hoofdmodule niet weet waar deze echt past in de Python-modulenaamruimte( dit is op zijn minst in theorie te repareren voor de hoofdmodules die worden uitgevoerd via de -m-schakelaar, maar direct uitgevoerde bestanden en de interactieve interpreter hebben volledig pech).

Voor meer informatie, zie Relatieve importen in Python 3voor de gedetailleerde uitleg en hoe je dit kunt oplossen.


Antwoord 20

Omdat uw code if __name__ == "__main__"bevat, die niet als pakket wordt geïmporteerd, kunt u beter sys.path.append()om het probleem op te lossen.

Other episodes