buiten pakketfout op het hoogste niveau in relatieve import

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 packagebevind, voer ik bijvoorbeeld:

cd ..
python -m package.test_A.test

alles is in orde.

Nu is mijn vraag:
wanneer ik in de map van packageben, en ik voer de module in het test_A subpakket uit als test_A.test, gebaseerd op mijn begrip, ..Agaat slechts één niveau omhoog, wat zich nog steeds in de map packagebevindt, 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.testdoet, negeert het eigenlijk gewoon de kennis dat test_A.testdaadwerkelijk is opgeslagen in package(dwz packagewordt niet als pakket beschouwd). Poging tot from ..A import fooprobeert 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 pathin 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 pathen python laten uitzoeken waar dat is met $PATHen $PYTHONPATH.

Als je python -m package.test_A.testgebruikt, wordt het gebruik van from ..A import fooprima opgelost omdat het bijhield wat er in packageen 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 packagebevindt, zijn Aen test_Aafzonderlijke pakketten.

Conclusie:
..Aimporten 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.pathkunnen 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:

  1. Het is de bestandsnaam, voorafgegaan door de mapstructuur, gescheiden door punten.
    Voor bijvoorbeeld: package.test_A.test
    Hier kent Python de bovenliggende mappen: voor testkomt test_Aen dan package.
    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.pyaanroept:

#  root.py
from package.test_A import test
  1. 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 wordt test.pyhet 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:

voer hier de afbeeldingsbeschrijving in

  • root.pyimporteert test.py. (ingangspunt, __name__ == __main__).
  • test.py(relatief) importeert foo.py.
  • foo.pyzegt 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 PYTHONPATHof 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.testvoer je test_A/test.pyuit met __name__ == '__main__'en __file__ == '/absolute/path/to/test_A/test.py'

Dat betekent dat je in test.pyje absolute importsemi-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__.pyin een bovenste map heeft, kunt u de import initialiseren als
import file/path as aliasin 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 xxxhebben, 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.pydoet from ..A import xxxontsnapt 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 xxxresulteren in een nieuwe module met FQMN=package.xxx, wat acceptabel is.

Terwijl voor CASE1 zal de ..van binnen from .. import xxxuit 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_classzonder grote drama’s.


Antwoord 13

Ik heb

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

in A/__init__.pyimporteer 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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

eleven + 12 =

Other episodes