2012-11-01 7 views
10

Per prefigurare, penso di aver capito come far funzionare questo codice (basato su Changing module variables after import), ma la mia domanda riguarda davvero il seguente comportamento, quindi posso capire cosa non fare in futuro.Importazione moduli: __main__ vs import come modulo

Ho tre file. Il primo è mod1.py:

# mod1.py 

import mod2 

var1A = None 

def func1A(): 
    global var1 
    var1 = 'A' 
    mod2.func2() 

def func1B(): 
    global var1 
    print var1 

if __name__ == '__main__': 
    func1A() 

successivo ho mod2.py:

# mod2.py 

import mod1 

def func2(): 
    mod1.func1B() 

Finalmente ho driver.py:

# driver.py 

import mod1 

if __name__ == '__main__': 
    mod1.func1A() 

Se eseguo il comando python mod1.py poi il l'output è None. Sulla base del collegamento a cui ho fatto riferimento sopra, sembra che ci sia una certa distinzione tra mod1.py importato come __main__ e mod1.py importato da mod2.py. Pertanto, ho creato driver.py. Se eseguo il comando python driver.py, ottengo l'output previsto: A. In un certo senso vedo la differenza, ma non vedo davvero il meccanismo o il motivo. Come e perché accade questo? Sembra controintuitivo che lo stesso modulo esisterebbe due volte. Se eseguo python mod1.py, sarebbe possibile accedere alle variabili nella versione __main__ di mod1.py invece delle variabili nella versione importata da mod2.py?

+1

Ti farai un favore se rifattori di eliminare l'importazione circolare. – eryksun

risposta

19

La variabile __name__ contiene sempre il nome del modulo, eccetto quando il file è stato caricato nell'interprete come script. Quindi la variabile è impostata sulla stringa '__main__'.

Dopo tutto, lo script viene quindi eseguito come il file principale dell'intero programma, tutto il resto sono moduli importati direttamente o indirettamente dal file principale. Testando la variabile __name__, è possibile rilevare se un file è stato importato come modulo o è stato eseguito direttamente.

All'interno dei moduli viene assegnato un dizionario dello spazio dei nomi, memorizzato come parte dei metadati per ciascun modulo, in sys.modules. Il file principale, lo script eseguito, è memorizzato nella stessa struttura di '__main__'.

Ma quando si importa un file come modulo, python prima cerca in sys.modules per vedere se quel modulo è già stato importato in precedenza. Quindi, import mod1 significa che prima cerchiamo in sys.modules per il modulo mod1. Creerà una nuova struttura di modulo con uno spazio dei nomi se mod1 non è ancora lì.

Quindi, se si esegue sia mod1.py come il file principale, e successivamente importato come modulo python, otterrà due voci namespace in sys.modules. Uno come '__main__', quindi in seguito come 'mod1'. Questi due spazi dei nomi sono completamente separati. Il tuo numero var1 globale è memorizzato in sys.modules['__main__'], ma è sys.modules['mod1'] per var1, dove è None.

Ma quando si utilizza python driver.py, driver.py diventa il file principale del programma '__main__', e mod1 verranno importati solo una volta nella struttura sys.modules['mod1']. Questa volta, func1A negozi var1 nella struttura sys.modules['mod1'], ed è quello che troverà func1B.

+0

Questo sarebbe OK allora? 'if __name__ == '__main__': sys.modules ['mod1'] = sys.modules ['__ main__']; func1A() ' – Kos

+1

@Kos: Non lo farei, penso che scoprirai che un sacco di ipotesi si romperanno. Evita invece di utilizzare i moduli come script. –

+0

Sto usando 'if __name__ == '__main __':' per scrivere una routine di test per ciascuno dei miei moduli all'interno del modulo. C'è un buon modo per continuare questa pratica, o dovrei scrivere routine di test come funzioni, quindi avere un driver in un file separato che non fa altro che importare il modulo da testare e chiamare la routine di test? – Brendan

1

quanto riguarda una soluzione pratica per utilizzare un modulo opzionale come script principale - supporto coerenti incrociati importazioni:

Soluzione 1:

Vedi esempio nel modulo pdb di Python, come viene eseguito come uno script mediante l'importazione di sé quando si esegue come __main__ (alla fine):

#! /usr/bin/env python 
"""A Python debugger.""" 
# (See pdb.doc for documentation.) 
import sys 
import linecache 

... 

# When invoked as main program, invoke the debugger on a script 
if __name__ == '__main__': 
    import pdb 
    pdb.main() 

Proprio lo consiglio a riorganizzare l'avvio __main__ all'inizio dello script come questo:

#! /usr/bin/env python 
"""A Python debugger.""" 
# When invoked as main program, invoke the debugger on a script 
import sys 
if __name__ == '__main__':   
    ##assert os.path.splitext(os.path.basename(__file__))[0] == 'pdb' 
    import pdb 
    pdb.main() 
    sys.exit(0) 

import linecache 
... 

In questo modo il corpo del modulo non viene eseguito due volte, il che è "costoso", indesiderabile e talvolta critico.

Soluzione 2:

In casi più rari è desiderabile esporre il modulo effettivo script di __main__ anche direttamente come alias modulo effettivo (mod1):

# mod1.py  
import mod2 

... 

if __name__ == '__main__': 
    # use main script directly as cross-importable module 
    _mod = sys.modules['mod1'] = sys.modules[__name__] 
    ##_modname = os.path.splitext(os.path.basename(os.path.realpath(__file__)))[0] 
    ##_mod = sys.modules[_modname] = sys.modules[__name__] 
    func1A() 

inconvenienti noti:

  • reload(_mod) fallisce
  • classi pickle 'bisogno di mappi aggiuntivi ng per deselezionare (find_global ..)