Het lijkt erop dat er hier al nogal wat vragen zijn over relatieve import in python 3, maar nadat ik er veel van heb doorgenomen, heb ik nog steeds geen antwoord op mijn probleem gevonden.
dus hier is de vraag.
Ik heb een pakket hieronder weergegeven
package/
__init__.py
A/
__init__.py
foo.py
test_A/
__init__.py
test.py
en ik heb een enkele regel in test.py:
from ..A import foo
nu ben ik in de map van package
, en ik voer
python -m test_A.test
Ik heb een bericht ontvangen
"ValueError: attempted relative import beyond top-level package"
maar als ik me in de bovenliggende map van package
bevind, voer ik bijvoorbeeld:
cd ..
python -m package.test_A.test
alles is in orde.
Nu is mijn vraag:
wanneer ik in de map van package
ben, en ik voer de module in het test_A subpakket uit als test_A.test
, gebaseerd op mijn begrip, ..A
gaat slechts één niveau omhoog, wat zich nog steeds in de map package
bevindt, waarom het een bericht geeft met de tekst beyond top-level package
. Wat is precies de reden dat deze foutmelding wordt veroorzaakt?
Antwoord 1, autoriteit 100%
EDIT: er zijn betere/meer samenhangende antwoorden op deze vraag in andere vragen:
Waarom werkt het niet?Het is omdat python niet registreert waar een pakket vandaan is geladen. Dus als je python -m test_A.test
doet, negeert het eigenlijk gewoon de kennis dat test_A.test
daadwerkelijk is opgeslagen in package
(dwz package
wordt niet als pakket beschouwd). Poging tot from ..A import foo
probeert toegang te krijgen tot informatie die het niet meer heeft (d.w.z. broers en zussen van een geladen locatie). Het is conceptueel vergelijkbaar met het toestaan van from ..os import path
in een bestand in math
. Dit zou slecht zijn omdat u wilt dat de pakketten verschillend zijn. Als ze iets uit een ander pakket moeten gebruiken, dan moeten ze er globaal naar verwijzen met from os import path
en python laten uitzoeken waar dat is met $PATH
en $PYTHONPATH
.
Als je python -m package.test_A.test
gebruikt, wordt het gebruik van from ..A import foo
prima opgelost omdat het bijhield wat er in package
en je hebt alleen toegang tot een onderliggende map van een geladen locatie.
Waarom beschouwt python de huidige werkmap niet als een pakket?GEEN CLUE, maar god, het zou handig zijn.
Antwoord 2, autoriteit 93%
import sys
sys.path.append("..") # Adds higher directory to python modules path.
Probeer dit eens.
Werkte voor mij.
Antwoord 3, autoriteit 23%
Aanname:
Als u zich in de directory package
bevindt, zijn A
en test_A
afzonderlijke pakketten.
Conclusie:
..A
importen zijn alleen toegestaan binnen een pakket.
Verdere opmerkingen:
Het is handig om de relatieve importen alleen binnen pakketten beschikbaar te maken als u wilt dat pakketten op elk pad op sys.path
kunnen worden geplaatst.
BEWERKEN:
Ben ik de enige die dit waanzinnig vindt!? Waarom wordt de huidige werkdirectory in hemelsnaam niet als een pakket beschouwd? Multihunter
De huidige werkdirectory bevindt zich meestal in sys.path. Dus alle bestanden daar zijn importeerbaar. Dit is gedrag sinds Python 2 toen pakketten nog niet bestonden. Door van de actieve directory een pakket te maken, kunnen modules worden geïmporteerd als “import .A” en als “import A”, wat dan twee verschillende modules zouden zijn. Misschien is dit een inconsistentie om te overwegen.
Antwoord 4, autoriteit 17%
Geen van deze oplossingen werkte voor mij in 3.6, met een mappenstructuur zoals:
package1/
subpackage1/
module1.py
package2/
subpackage2/
module2.py
Mijn doel was om van module1 naar module2 te importeren. Wat uiteindelijk voor mij werkte, was, vreemd genoeg:
import sys
sys.path.append(".")
Let op de enkele punt in tegenstelling tot de oplossingen met twee punten die tot nu toe zijn genoemd.
Bewerken: het volgende hielp me dit te verduidelijken:
import os
print (os.getcwd())
In mijn geval was de werkdirectory (onverwacht) de hoofdmap van het project.
Antwoord 5, autoriteit 11%
Dit is erg lastig in Python.
Ik zal eerst uitleggen waarom je dat probleem hebt en dan zal ik twee mogelijke oplossingen noemen.
Wat is er aan de hand?
Je moet deze paragraaf uit de Python documentatiein overweging:
Houd er rekening mee dat relatieve importen zijn gebaseerd op de naam van de huidige
module. Aangezien de naam van de hoofdmodule altijd “main” is,
modules bedoeld voor gebruik als de hoofdmodule van een Python-toepassing
moet altijd absolute import gebruiken.
En ook het volgende van PEP 328:
Relatieve importen gebruiken het kenmerk namevan een module om dat te bepalen
de positie van de module in de pakkethiërarchie. Als de naam van de module dat doet
geen pakketinformatie bevatten (het is bijvoorbeeld ingesteld op ‘main‘)
dan worden relatieve importen opgelost alsof de module een topniveau is
module, ongeacht waar de module zich daadwerkelijk in het bestand bevindt
systeem.
Relatieve importen werken vanuit de bestandsnaam (__name__
attribuut), die twee waarden kan aannemen:
- Het is de bestandsnaam, voorafgegaan door de mapstructuur, gescheiden door punten.
Voor bijvoorbeeld:package.test_A.test
Hier kent Python de bovenliggende mappen: voortest
komttest_A
en danpackage
.
Je kunt dus de puntnotatie gebruiken voor relatieve import.
# package.test_A/test.py
from ..A import foo
Je kunt dan een rootbestand in de rootdirectory hebben dat test.py
aanroept:
# root.py
from package.test_A import test
- Wanneer u de module (
test.py
) rechtstreeks uitvoert, wordt deze het toegangspunt tot het programma, dus__name__
==__main__
. De bestandsnaam heeft geen indicatie van de directorystructuur, dus Python weet niet hoe hij omhoog moet gaan in de directory. Voor Python wordttest.py
het script op het hoogste niveau, er staat niets boven. Daarom kunt u geen relatieve import gebruiken.
Mogelijke oplossingen
A)Een manier om dit op te lossen is om een rootbestand (in de rootdirectory) te hebben dat de modules/pakketten als volgt aanroept:
root.py
importeerttest.py
. (ingangspunt,__name__ == __main__
).test.py
(relatief) importeertfoo.py
.foo.py
zegt dat de module is geïmporteerd.
De uitvoer is:
package.A.foo has been imported
Module's name is: package.test_A.test
B)Als u de code als module wilt uitvoeren en niet als een script op het hoogste niveau, kunt u dit vanaf de opdrachtregel proberen:
python -m package.test_A.test
Alle suggesties zijn welkom.
Controleer ook: Relative imports voor de miljardste keer, speciaal het antwoord van BrenBarn.
Antwoord 6, autoriteit 10%
from package.A import foo
Ik denk dat het duidelijker is dan
import sys
sys.path.append("..")
Antwoord 7, autoriteit 7%
Zoals het meest populaire antwoord suggereert, komt dit in feite omdat uw PYTHONPATH
of sys.path
.
maar niet uw pad naar uw pakket. En de relatieve import is relatief aan je huidige werkdirectory, niet het bestand waar de import plaatsvindt; vreemd.
U kunt dit oplossen door eerst uw relatieve import te wijzigen in absoluut en vervolgens te beginnen met:
PYTHONPATH=/path/to/package python -m test_A.test
OF het pad van de python forceren wanneer het op deze manier wordt aangeroepen, omdat:
Met python -m test_A.test
voer je test_A/test.py
uit met __name__ == '__main__'
en __file__ == '/absolute/path/to/test_A/test.py'
Dat betekent dat je in test.py
je absolute import
semi-beveiligd kunt gebruiken in de hoofdlettergebruikconditie en ook wat eenmalige Python-padmanipulatie kunt doen:
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())
Antwoord 8, autoriteit 4%
Bewerken: 08-05-2020: Het lijkt erop dat de website die ik heb geciteerd niet langer wordt beheerd door de persoon die het advies heeft geschreven, dus verwijder ik de link naar de site. Bedankt voor het laten weten van baxx.
Als iemand nog steeds een beetje worstelt na de geweldige antwoorden die al zijn gegeven, heb ik advies gevonden op een website die niet langer beschikbaar is.
Belangrijk citaat van de site die ik noemde:
“Hetzelfde kan op deze manier programmatisch worden gespecificeerd:
systeem importeren
sys.path.append(‘..’)
Natuurlijk moet de bovenstaande code worden geschreven voor de andere import
verklaring.
Het is vrij duidelijk dat het zo moet, als je er achteraf over nadenkt. Ik probeerde de sys.path.append(‘..’) in mijn tests te gebruiken, maar kwam het probleem tegen dat door OP was gepost. Door de import- en sys.path-definitie toe te voegen vóór mijn andere importen, kon ik het probleem oplossen.
Antwoord 9, autoriteit 3%
als u een __init__.py
in een bovenste map heeft, kunt u de import initialiseren als
import file/path as alias
in dat init-bestand. Dan kun je het op lagere scripts gebruiken als:
import alias
Antwoord 10, autoriteit 2%
In mijn geval moest ik dit veranderen:
Oplossing 1 (meer beter die afhankelijk is van het huidige py-bestandspad. Eenvoudig te implementeren)
Gebruik pathlib.Path.parents make code cleaner
import sys
import os
import pathlib
target_path = pathlib.Path(os.path.abspath(__file__)).parents[3]
sys.path.append(target_path)
from utils import MultiFileAllowed
Oplossing 2
import sys
import os
sys.path.append(os.getcwd())
from utils import MultiFileAllowed
Antwoord 11
Naar mijn bescheiden mening begrijp ik deze vraag als volgt:
[CASE 1] Wanneer u een absolute import start zoals
python -m test_A.test
of
import test_A.test
of
from test_A import test
je stelt eigenlijk het import-anchorin op test_A
, met andere woorden, het pakket op het hoogste niveau is test_A
. Dus als we test.py do from ..A import xxx
hebben, ontsnapt u uit het anker, en Python staat dit niet toe.
[CASE 2] Wanneer u dat doet
python -m package.test_A.test
of
from package.test_A import test
uw anker wordt package
, dus package/test_A/test.py
doet from ..A import xxx
ontsnapt niet aan het anker( nog steeds in de map package
), en Python accepteert dit graag.
Kortom:
- Absolute-import verandert huidig anker (=definieert opnieuw wat het pakket op het hoogste niveau is);
- Relative-import verandert het anker niet, maar beperkt zich ertoe.
Bovendien kunnen we volledig gekwalificeerde modulenaam( FQMN) om dit probleem te inspecteren.
Controleer FQMN in elk geval:
- [CASE2]
test.__name__
=package.test_A.test
- [CASE1]
test.__name__
=test_A.test
Dus voor CASE2 zal een from .. import xxx
resulteren in een nieuwe module met FQMN=package.xxx
, wat acceptabel is.
Terwijl voor CASE1 zal de ..
van binnen from .. import xxx
uit het startknooppunt(anker) van test_A
, en dit is NIET toegestaan door Python.
Antwoord 12
Niet zeker in python 2.x, maar in python 3.6, ervan uitgaande dat je de hele suite probeert uit te voeren, hoef je alleen maar -t
te gebruiken
-t, –top-directory-directory
Directory op het hoogste niveau van project (standaard om directory te starten)
Dus, op een structuur als
project_root
|
|----- my_module
| \
| \_____ my_class.py
|
\ tests
\___ test_my_func.py
Je zou bijvoorbeeld kunnen gebruiken:
python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/
En importeer nog steeds de my_module.my_class
zonder grote drama’s.
Antwoord 13
Ik heb
package/
__init__.py
A/
__init__.py
foo.py
test_A/
__init__.py
test.py
in A/__init__.py
importeer foo
:
from .foo import foo
bij het importeren van A/
uit test_A/
import sys, os
sys.path.append(os.path.abspath('../A'))
# then import foo
import foo
Antwoord 14
Deze werkte niet voor mij omdat ik Django 2.1.3 gebruik:
import sys
sys.path.append("..") # Adds higher directory to python modules path.
Ik koos voor een aangepaste oplossing waarbij ik een commando aan het opstartscript van de server toevoeg om mijn gedeelde script te kopiëren naar de django ‘app’ die het gedeelde python-script nodig had.
Het is niet ideaal, maar aangezien ik alleen een persoonlijke website aan het ontwikkelen ben, past het bij mij. Ik zal hier opnieuw posten als ik de django-manier kan vinden om code te delen tussen Django-apps binnen een enkele website.