Topic: Import relativi  (Letto 112 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline tommyb1992

  • python neanderthalensis
  • ****
  • Post: 300
  • Punti reputazione: 0
    • Mostra profilo
Import relativi
« il: Marzo 17, 2020, 09:02 »
Onestamente da quando hanno modificato il modo per fare gli import relativi in python3 non li ho più utilizzati mettendo tutto nella stessa cartella. Al massimo se mettevo in cartelle differenti poi installavo con pip e gli passavo il percorso diretto.
Problema:
Ipotizzando che io voglia utilizzare un modulo senza installarlo con pip e la sua struttura sia:
+
|
+--- tests/
|         +--- test1.py
|         |
|         +--- test2.py
|
+--- utils/common.py
|
+--- core/main.py

Ora io vorrei da "core/main" importare un modulo da utils/common.py, come faccio?

Grazie

Offline nuzzopippo

  • python erectus
  • ***
  • Post: 186
  • Punti reputazione: 0
    • Mostra profilo
Re:Import relativi
« Risposta #1 il: Marzo 17, 2020, 12:31 »
Ciao @Tommy, giusto ieri mi son rimesso, dopo un periodo di "altro", a riprendere una ipotesi di applicativo di studio ed ho provato a strutturalo come package, incontrando la difficoltà in essere, ossia che quando un modulo ha assegnato il "__main__" quale "__name__" non viene valorizzata la proprietà "__package__", quindi gli import relativi falliscono.

Mi son sbattuto parecchio per cercare di comprendere il fatto e come ovviarlo ma è certamente entrata in gioco la mia ignoranza dell'inglese e non ne son venuto a capo dalla docs, ho pertanto optato per un "metodo" artigianalmente intuitivo e definito un modulo di avvio "esterno" al package (temo sia uno sporco trucco da ex "programmatore" VB ma funziona ;) )
prosta una struttura 'si fatta

NzP:~$ tree
.
├── anagapp
│   ├── app
│   │   ├── data_models
│   │   │   ├── anagrafiche.sql
│   │   │   └── __init__.py
│   │   ├── guiviews
│   │   │   ├── gui_anag.py
│   │   │   ├── __init__.py
│   │   │   ├── my_tk_object.py
│   │   │   ├── risorse.py
│   │   │   ├── tbl_orig.py
│   │   │   └── tktable.py
│   │   ├── __init__.py
│   │   ├── interfaces
│   │   │   ├── __init__.py
│   │   │   └── observers.py
│   │   │
│   │   └──  main.py
│   └── run.py

il "package" ricade nella directory "app" ed il lancio avviene da "run.py" che si limita al solo import e lancio di "main.py"
#-*- coding: utf-8 -*-

from app import main

main.start()


Ovviamente, dato il problema, il "main.py" è stato adattato alla problematica sopra accennata.
#-*- coding: utf-8 -*-

from app.guiviews.gui_anag import GUIAnag

#if __name__ == '__main__':
    # da definire processi di inizializzazione
def _init():
    pass

def start():
    win = GUIAnag()
    win.mainloop()


Come detto, non credo sia una soluzione "canonica" ma funziona, ovvio che sto cercando qualcosa di più indicato, se trovo batto un colpo

Ciao :)

[Edit] P.S. : nelle mie ricerche, ho trovato vari "metodi" relativi alla problematica, metodi che andavano dall'utilizzo "sys.path" alla ridefinizione del "PYTHONPATH" all'avvio del programma da linea di comando con opzioni varie, spesso non comprese e tutte trovate insoddisfacenti per le mie mire di "semplice utilizzo"
« Ultima modifica: Marzo 17, 2020, 12:58 da nuzzopippo »

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 2.969
  • Punti reputazione: 9
    • Mostra profilo
Re:Import relativi
« Risposta #2 il: Marzo 17, 2020, 12:54 »
@Nunzio
Sì certo, con gli import assoluti si possono fare delle cose. L'OP però chiedeva degli import relativi...


Allora...
Oddio, i relative imports... tutte le volte sono una pena.

Allora, la cosa migliore sarebbe leggere con attenzione questa risposta: https://stackoverflow.com/a/14132912 è lunga e complicata ma vale la pena.
Ciò che dico qui sotto è parziale e si riferisce comunque ai concetti spiegati lì.

Per prima cosa, dal fatto che chiami il modulo "main", suppongo che tu lo stia *eseguendo*... E allora la prima regola è: non puoi fare import relativi in un modulo che *esegui*, ma solo in un modulo che, a sua volta, viene *importato*. La ragione è che se il modulo è eseguito, allora il suo nome cambia in __main__ e le informazioni relative alla sua collocazione nel package vanno perdute, e quindi anche gli import relativi che dovesse contenere, saltano senza pietà.

Tolta questa prima cosa banale, la seconda regola è: non puoi fare import relativi da package "fratelli", se a sua volta il "main" (ovvero il modulo che *esegui*) è "fratello" di quei package. Ovvero, se tu hai questa struttura:

base/foo/foo.py
base/bar/bar.py
base/main.py   -> questo è il modulo che esegui

allora non puoi fare un import relativo da bar.py a foo.py o viceversa.
Ovvero, per esempio, non puoi scrivere dentro a bar.py qualcosa come "from ..foo.foo import something" perché il salto all'indietro ti fa già uscire dal package top-level, dal momento che il modulo che esegui è solo "fratello" di foo e bar.

Se però il modulo che esegui, invece di essere fratello, è "genitore" dei due package fratelli, allora tutto funziona, perché quando salti all'indietro non esci dal package top-level.
Ti faccio un esempio di import relativo tra package "fratelli" che funziona: il motivo lo puoi capire precisamente solo se leggi con cura la risposta che ti ho linkato.

=== struttura dei file ====
base/src/foo/foo.py
base/src/bar/bar.py
base/main.py
=== contenuto dei file ===

# file bar.py
def getname():
    print('il nome di bar/bar.py:', __name__)

def bar_funct():
    print('sono bar_funct in bar/bar.py')
# -------------------------------------------
# file foo.py
from ..bar.bar import bar_funct  # import relativo tra package fratelli

def getname():
    print('il nome di foo/foo.py:', __name__)

def foo_funct():
    print('sono foo_funct in foo/foo.py')
    print('adesso eseguo bar_funct:')
    bar_funct()
# --------------------------------------------
# file main.py
from src.foo.foo import getname as fooname
from src.bar.bar import getname as barname

fooname()
barname()

from src.foo.foo import foo_funct

foo_funct()

Le due funzioni "getname" che ti restituiscono il nome del modulo come lo intende Python, ti servono sempre in relazione alla risposta che ti ho linkato, e che devi leggere (devi proprio, sorry).
Ora, se tu invece provi a fare la stessa cosa ma eliminando il "livello" del package "src", ovvero torni alla tua struttura iniziale:

base/foo/foo.py
base/bar/bar.py
base/main.py
=== contenuto dei file ===

# file bar.py  come prima
# -------------------------------------------
# file foo.py  come prima
# --------------------------------------------
# file main.py
from foo.foo import getname as fooname
from bar.bar import getname as barname

fooname()
barname()

from foo.foo import foo_funct

foo_funct()

vedi che adesso l'import relativo non funziona. E vedi però anche come cambiano i nomi di foo.py e bar.py


Detto tutto questo, attenzione però anche a una cosa:
primo, gli import relativi sono un antipattern e andrebbero evitati.
Secondo, in ogni caso il meccanismo di "import" non serve a caricare codice da posizioni arbitrarie nel file system. Non è come quando dalla shell fai "cd ../../foo/../bar" e ti sposti da un punto a qualsiasi altro punto. Gli import devono pur sempre rimanere nell'ambito di uno stesso package (e subpackage) così come il package viene inteso da python in base alla locazione del modulo "main" che stai eseguendo (sempre, vedi la risposta che ti ho linkato).
Ora, nel caso di package "fratelli" puoi pur sempre usare gli import assoluti. Anche nel secondo scenario (quello che non funziona con gli import relativi), se invece di fare from ..bar.bar import bar_funct tu fai from bar.bar import bar_funct, quello funziona con gli import assoluti (perché la path completa del package dipende dalla locazione del "main" eseguito).
Ma in generale, se vuoi importare qualcosa che sta arbitrariamente fuori dal package "top-level", non puoi farlo neanche con gli import assoluti. In quel caso, è un problema più ampio di organizzazione del tuo progetto. Puoi usare PYTHONPATH (ma è un hack che funziona solo sulla tua macchina, ovviamente) oppure puoi installare con pip (ma ha senso solo con delle librerie che a loro volta potrebbero essere distribuite a parte su PyPI, non certo con un modulo "utils" qualunque). Ma comunque devi porti il problema a livello più ampio di organizzazione del progetto.


Edit: ho rinominato in "src" il package che nella prima versione di questo post avevo chiamato "all"... un po' perché "all" è un nome riservato, e mi sono vergognato... Ma soprattutto perché tutto questo ha anche a che vedere con la discussione di usare "src" per strutturare i progetti... cosa che per alcune ragioni un po' complesse è in realtà consigliabile e che adesso sta andando più di moda che in passato. Per capirne qualcosa, https://hynek.me/articles/testing-packaging/ ma non è una lettura facilissima.
« Ultima modifica: Marzo 17, 2020, 13:40 da RicPol »

Offline nuzzopippo

  • python erectus
  • ***
  • Post: 186
  • Punti reputazione: 0
    • Mostra profilo
Re:Import relativi
« Risposta #3 il: Marzo 17, 2020, 13:19 »
Ringrazio, @Ric, leggerò e, magari, chiederò se sarà necessario :)