pythonic module-organisatie - hoe verwijs ik naar bestanden in de root-directory?

Ik heb mijn python-code in een map met de naam "project", dus mijn codebestanden staan ​​in project/*. Py. Ik wil submodules erin hebben, bijvoorbeeld

project/code.py # where code lives
project/mymodule1  # where more code lives
project/mymodule2

elke module-map heeft zijn eigen init .py-bestand, bijvoorbeeld

project/mymodule1/__init__.py

stel dat ik een bestand "test.py" heb in mymodule1 (project/mymodule1/test.py) en ik wil graag verwijzen naar iets uit "code", bijvoorbeeld importeer de functie "myfunc"

== project/mymodule1/test.py ==
from code import myfunc

het probleem is dat "code" niet zal worden gevonden tenzij de gebruiker de map "project /" in zijn PYTHONPATH heeft geplaatst. Is er een manier om dit te vermijden en een soort "relatief pad" te gebruiken om myfunc te importeren, bijvoorbeeld

from ../code import myfunc

eigenlijk wil ik gebruikers van de code niet dwingen om het PYTHONPATH te veranderen, tenzij ik het programmatisch kan doen vanuit mijn script. Ik zou graag willen dat het uit de doos werkt.

Hoe kan dit worden gedaan? beide oplossingen zijn goed: PYTHONPATH programmatisch aanpassen, of idealiter, verwijzen naar "code" door een soort relatieve import te gebruiken, want hoewel ik niet weet waar "project/code.py" op de computer van de gebruiker leeft, weet ik waar het is gerelateerd aan "myfunc".

EDIT: Kan iemand een goed voorbeeld van intra-pakketimport laten zien? Ik heb geprobeerd om vanuit "mymodule1":

from .. import foo

waar "foo" is in code.py, maar het werkt niet. Ik heb init .py in mymodule1, dus:

project/code.py
project/mymodule1/__init__.py
project/mymodule1/module1_code.py

waarbij module1_code.py foo probeert te importeren, een functie die is gedefinieerd in "code.py".

EDIT: De grootste verwarring die ik nog steeds heb is dat zelfs na het aannemen van het voorbeeld gegeven in reactie op mijn bericht, het tonen van de project/sub1/testhiërarchie, je nog steeds niet "cd" kunt in sub1 en "python test.py" kunt doen en het kunt hebben werk. De gebruiker moet in de map staan ​​met "project" en "project.sub1.test importeren". Ik zou willen dat dit werkt, ongeacht in welke directory de gebruiker zich bevindt. De gebruiker moet in dit geval het bestand "test.py" uitvoeren, dat in project/sub1/woont. Dus de testcase is:

$ cd project/sub1
$ python test.py

die de fout oplevert:

ValueError: Attempted relative import in non-package

hoe kan dit worden opgelost?

bedankt.

6
Is code.py ook mymodule1/test.py importeren? Als dat zo is, wil je kijken naar het reorganiseren van je code. Circulaire invoer moet zo veel mogelijk worden vermeden.
toegevoegd de auteur Wilduck, de bron

2 antwoord

Dit is mogelijk in Python 2.5 en hoger. Raadpleeg de documentatie over Verwijzingen binnen pakketten .

Een paar dingen om op te merken:

Als u van plan bent dat uw gebruikers uw pakket ergens installeren , bijvoorbeeld met behulp van distutils of setuptools, dan is project waarschijnlijk al in het zoekpad en kunt u het relatieve importeren naar van project.code import ... of iets dergelijks.

In het geval dat uw gebruikers uw pakket installeren naar een niet-standaard directory (bijv. Hun homedirectory, ergens anders dat niet in sys.path is, enz.), Is mijn mening dat het leidt tot minder verwarring om de gebruiker te instrueren om PYTHONPATH aan te passen in plaats van programmatisch sys.path te wijzigen.

In het geval dat u niet van plan bent dat uw gebruikers uw code überhaupt installeren - zij zullen bijvoorbeeld simpelweg de bron ontmoedigen, naar de bovenliggende map van project gaan, en een script uitvoeren - dan zullen intra-pakketreferenties en relatieve import waarschijnlijk prima werken.

EDIT: per request, here's an example:

Stel dat de lay-out van het pakket als volgt is:

project/
    __init__.py (empty)
    code.py
    sub1/
        __init__.py (empty)
        test.py

De inhoud van project/code.py is nu:

# code.py (module that resides in main "project" package)

def foo():
    print "this is code.foo"

En de inhoud van project/sub1/test.py is:

# test.py (module that resides in "sub1" subpackage of main "project" package)

from ..code import foo
foo()

Dus, test.py importeert de naam foo van het relatieve pad .. code , die intra-pakket verwijzingen gebruikt om ons terug te brengen naar de code.py module binnen de bovenliggende (dat is de .. deel) van het sub1.test pakket waar we uitvoeren vanaf.

Om dit te testen:

shell$ (cd to directory where "project" package is located)
shell$ python
Python 2.6.1 (r261:67515, Aug  2 2010, 20:10:18) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import project.sub1.test
this is code.foo

Merk op dat de dubbele punt in de syntaxis from .. import X u alleen naar het bovenliggende pakket haalt, maar u kunt modules binnen dat pakket specificeren.

Met andere woorden, from .. import X is in dit geval equivalent aan van projectimport X , en dus moet X een module zijn in project of een klasse/functie/naam binnen project/__ init __. py .

Dus van ..code import X komt overeen met van project.code import X .

3
toegevoegd
Kan iemand een goed voorbeeld van intra-pakketimport laten zien? Ik probeerde, van "mymodule1" te doen: van .. import foo, waarbij "foo" in code.py is maar het werkt niet. Ik heb init .py in mymodule1
toegevoegd de auteur user248237dfsf, de bron
wat ik niet snap is waarom als je naar "sub1" cd en doe: python test.py, krijg je de fout "ValueError: Poging relatieve import in niet-pakket"
toegevoegd de auteur user248237dfsf, de bron
@ user248237: zie mijn gewijzigde reactie inclusief een voorbeeld. Het belangrijkste verschil is dat from .. import foo gelijk is aan het doorzoeken van project/__ init __. Py , niet code.py .
toegevoegd de auteur bjlaub, de bron

de beste manier om dit te voorkomen, is om al uw code in een src map te bewaren of deze beter te benoemen in uw project, bijvoorbeeld myproject en bewaar een __ init __. py daar, zodat je dit kunt doen

from myproject import code

dus je mappenstructuur zou zijn

project
    main.py
    myproject
        __init__.py
        code.py
        module1
        module2

main.py or whatever name you give it should not have much code, it should get required module from myproject and run it e.g.

from myproject import myapp
myapp.run()

zie een goed artikel over het regelen van je python-project.

1
toegevoegd
dat lost het probleem niet op - hoe kan de code van module1 dingen stroomopwaarts gedefinieerd noemen? in bovenliggende mappen?
toegevoegd de auteur user248237dfsf, de bron
myproject bevindt zich in een pythonpad en de everymodule kan elke module importeren, bijvoorbeeld van myproject.module1 import xxx
toegevoegd de auteur Anurag Uniyal, de bron
@JonathanHartley Ik gaf een alternatieve en betere organisatie die in de toekomst ook helpt wanneer hij myproject wil inpakken als een bibliotheek of het als een zelfstandige app wil uitvoeren, het belangrijkste toegangspunt is main.py in mijn oplossing, niet code.py
toegevoegd de auteur Anurag Uniyal, de bron
@JonathanHartley er kunnen veel manieren zijn om dit te doen, path mangling is er een, maar ik geef presoneel de voorkeur aan slechts twee approches a) maak van elk project een goede module onder mijn project of b) als alle projecten te disjunct zijn en apart worden ontwikkeld, zal ik gewoon maak ze een bibliotheek en installeer ze zoals elke andere python-bibliotheek
toegevoegd de auteur Anurag Uniyal, de bron
1) Ik denk dat je bedoelt dat 'project' in PYTHONPATH zal zijn, niet 'myproject', en (2) Nee, dat zal niet gebeuren als het proces is gestart met 'python myproject/code.py', wat de situatie van het OP is. is nog steeds niet opgelost, IMHO.
toegevoegd de auteur Jonathan Hartley, de bron
@AnuragUniyal Begrepen, bedankt. Over het algemeen ben ik het volledig eens met uw aanpak. Ik twijfel er echter vandaag over, omdat ik een project heb geërfd dat vele tientallen opdrachtregelscripts bevat, niet slechts één "main.py". De oorspronkelijke auteur koos ervoor om deze te categoriseren in verschillende project-submappen, in plaats van ze allemaal in de map met het hoogste niveau van het project te plaatsen. Helaas, daarom verminken ze allemaal sys.path om te kunnen importeren. Verplaatst ze alles naar het project TLD mijn enige alternatief? Ik denk dat ik bijna meeliep met het mangling. Advies welkom.
toegevoegd de auteur Jonathan Hartley, de bron
@AnuragUniyal oh, en mijn punt (1) hierboven staat nog steeds. Vergis ik me?
toegevoegd de auteur Jonathan Hartley, de bron
Als u zegt "maak van elk project een goede module onder mijn project", wat bedoelt u dan? Ik heb maar één project. Het bevat veel uitvoerbare scripts.
toegevoegd de auteur Jonathan Hartley, de bron