2013-03-07 10 views
9

Ho una base di codice in cui sto ripulendo alcune decisioni disordinate da parte dello sviluppatore precedente. Spesso, ha fatto qualcosa di simile:Inversione dall'importazione modulo *

from scipy import * 
from numpy import * 

... Questo, naturalmente, inquina lo spazio nome e rende difficile dire dove un attributo del modulo è di origine.

C'è un modo per analizzare Python e risolvere il problema per me? Qualcuno ha fatto un'utilità per questo? Altrimenti, come potrebbe essere realizzata un'utilità come questa?

+1

che provo per te. Spero che tu trovi qualche bell'attrezzo. (+1) – NPE

+0

Ancora meglio, spero che tu scriva un bell'attrezzo (che sia basato sulla mia risposta o meno) e lo pubblichi su PyPI, quindi se mai avrò bisogno di una cosa del genere, non dovrò farlo da solo. :) – abarnert

+0

Vedi anche questa domanda: [Esiste un IDE/utilità per il refactoring di Python * per l'utilizzo della sintassi modulo.member standard?] (Http://stackoverflow.com/questions/12677061/is-there-an-ide- utility-to-refactor-python-imports-to-use-standard-module-memb) –

risposta

0

Ora ho creato una piccola utilità per fare ciò che chiamo "dedazzler". Troverà le linee che sono "dal modulo di importazione *", quindi espanderà la "dir" dei moduli di destinazione, sostituendo le linee.

Dopo averlo eseguito, è comunque necessario eseguire un linter. Ecco la parte particolarmente interessante del codice:

import re 

star_match = re.compile('from\s(?P<module>[\.\w]+)\simport\s[*]') 
now = str(time.time()) 
error = lambda x: sys.stderr.write(x + '\n') 

def replace_imports(lines): 
    """ 
    Iterates through lines in a Python file, looks for 'from module import *' 
    statements, and attempts to fix them. 
    """ 
    for line_num, line in enumerate(lines): 
     match = star_match.search(line) 
     if match: 
      newline = import_generator(match.groupdict()['module']) 
      if newline: 
       lines[line_num] = newline 
    return lines 

def import_generator(modulename): 
    try: 
     prop_depth = modulename.split('.')[1:] 
     namespace = __import__(modulename) 
     for prop in prop_depth: 
      namespace = getattr(namespace, prop) 
    except ImportError: 
     error("Couldn't import module '%s'!" % modulename) 
     return 
    directory = [ name for name in dir(namespace) if not name.startswith('_') ] 
    return "from %s import %s\n"% (modulename, ', '.join(directory)) 

sto mantenendo questo in un più utile forma di utilità stand-alone qui:

https://github.com/USGM/dedazzler/

-1

ok, questo è quello che penso che si possa fare, interrompere il programma. rimuovere le importazioni e notare gli errori che sono stati fatti. Quindi importare solo i moduli desiderati, questo potrebbe richiedere un po ', ma questo è l'unico modo che conosco per fare questo, sarò felicemente sorpreso se qualcuno sapesse di uno strumento per aiutare

EDIT: ah sì, un linter, non ci avevo pensato.

3

Sì. Rimuovere le importazioni ed eseguire un linter sul modulo.

Si consiglia di utilizzare flake8, sebbene possa anche creare molto rumore sugli errori di stile.

La semplice rimozione delle importazioni e il tentativo di eseguire il codice probabilmente non saranno sufficienti, poiché molti errori di nome non verranno generati finché non si eseguirà solo la riga di codice corretta con il giusto input. Un linter analizzerà invece il codice analizzando e rileverà i potenziali NameError s senza dover eseguire il codice.

Tutto ciò presuppone che non vi siano test di unità affidabili o che i test non forniscano una copertura sufficiente.

In questo caso, dove ci sono più from module import * righe, diventa un po 'più doloroso in quanto è necessario capire per ciascuno e per ogni nome mancante quale modulo fornito quel nome. Ciò richiederà un lavoro manuale, ma si può semplicemente importare il modulo in un interprete python e verificare se il nome mancante è definito in quel modulo:

>>> import scipy, numpy 
>>> 'loadtxt' in dir(numpy) 
True 

Si ha bisogno di tener conto del fatto che in questo caso specifico, che c'è sovrapposizione tra i moduli numpy e scipy; per ogni nome definito in entrambi i moduli, il modulo ha importato le ultime vincite.

Nota che lasciare qualsiasifrom module import * linea sul posto significa che il linter non sarà in grado di rilevare quali nomi potrebbero generare NameErrors!

+0

Se rimuovo le importazioni, come mi dirà da quale modulo provengono i nomi? Non nominerebbe semplicemente su tutti gli attributi che ora non sono elencati? – Kelketek

+0

@Kelketek: Sì, e dovrai capire per ognuno di quale modulo è * presunto * di venire. Non è difficile, per fortuna. –

+0

Se li rimuovi uno alla volta, è presumibilmente più semplice. – geoffspear

3

Penso che le soluzioni per il manuale assistito di PurityLake e Martijn Pieters siano probabilmente la soluzione migliore. Ma non è impossibile per farlo a livello di programmazione.

Innanzitutto, è necessario ottenere un elenco di tutti i nomi esistenti nel dizionario del modulo che potrebbero essere utilizzati nel codice. Sto assumendo il codice non chiama direttamente eventuali Dunder funzioni, ecc

Quindi, è necessario per scorrere attraverso di loro, utilizzando inspect.getmodule() per scoprire quale modulo ogni oggetto è stato originariamente definito. E io sono partendo dal presupposto che non stai usando nulla che sia stato doppiamente from foo import * -ed.Creare un elenco di tutti i nomi definiti nei moduli numpy e scipy.

Ora è possibile prendere quell'uscita e sostituire semplicemente ogni foo con numpy.foo.

Così, mettendo insieme, qualcosa di simile:

for modname in sys.argv[1:]: 
    with open(modname + '.py') as srcfile: 
     src = srcfile.read() 
    src = src.replace('from numpy import *', 'import numpy') 
    src = src.replace('from scipy import *', 'import scipy') 
    mod = __import__(modname) 
    for name in dir(mod): 
     original_mod = inspect.getmodule(getattr(mod, name)) 
     if original_mod.__name__ == 'numpy': 
      src = src.replace(name, 'numpy.'+name) 
     elif original_mod.__name__ == 'scipy': 
      src = src.replace(name, 'scipy.'+name) 
    with open(modname + '.tmp') as dstfile: 
     dstfile.write(src) 
    os.rename(modname + '.py', modname + '.bak') 
    os.rename(modname + '.tmp', modname + '.py') 

Se una delle ipotesi è sbagliato, non è difficile modificare il codice. Inoltre, potresti voler utilizzare tempfile.NamedTemporaryFile e altri miglioramenti per assicurarti di non sovrascrivere accidentalmente cose con file temporanei. (Non volevo affrontare il problema di scrivere qualcosa di multipiattaforma, se non stai lavorando su Windows, è facile.) E aggiungi qualche gestione degli errori, ovviamente, e probabilmente alcuni rapporti.

+0

Non penso che funzionerà abbastanza. Che dire delle variabili chiamate 'my_foo'. All'improvviso ottieni 'my_numpy.foo'. Ops. Naturalmente, dato un parser adeguato (sto pensando a 'ast'), probabilmente potresti farlo. – mgilson

+0

Questo è probabilmente più quello che sto cercando. Il ragazzo occasionalmente aveva moduli in cui venivano fatte cinque diverse importazioni di caratteri jolly. È follia. La ricerca e la sostituzione qui sono un po 'pericolose, in quanto fanno alcune supposizioni, ma questa idea mi porta nella giusta direzione. – Kelketek

+0

In definitiva, questo è un inizio ragionevole di uno strumento che potrebbe essere utile per l'intera comunità. Mi chiedo anche come questo genere di cose interagirebbe con '__all__'. Per farlo correttamente, probabilmente vorrai filtrare le cose che non sono in 'module .__ all__' se esiste. – mgilson