Reversão da importação do módulo *

9

Eu tenho uma base de código onde estou limpando algumas decisões confusas do desenvolvedor anterior. Freqüentemente, ele fez algo como:

from scipy import *
from numpy import *

... Isso, é claro, polui o namespace e torna difícil dizer de onde um atributo no módulo é originalmente.

Existe alguma maneira de fazer com que o Python analise e corrija isso para mim? Alguém fez um utilitário para isso? Se não, como pode um utilitário como este ser feito?

    
por Kelketek 07.03.2013 в 19:44
fonte

4 respostas

0

Eu agora fiz um pequeno utilitário para fazer isso, que chamo de 'dedazzler'. Ele encontrará linhas que são 'do módulo import *' e, em seguida, expande o 'dir' dos módulos de destino, substituindo as linhas.

Depois de executá-lo, você ainda precisa executar um linter. Aqui está a parte particularmente interessante do código:

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))

Estou mantendo isso em um formulário de utilidade autônomo mais útil aqui:

link

    
por Kelketek 17.05.2013 / 20:31
fonte
3

Acho que as soluções do manual assistido do PurityLake e do Martijn Pieters são provavelmente o melhor caminho a percorrer. Mas não é impossível fazer isso de forma programática.

Primeiro, você precisa obter uma lista de todos os nomes existentes no dicionário do módulo que possam ser usados no código. Estou assumindo que seu código não está chamando diretamente qualquer dunder funções, etc.

Em seguida, você precisa fazer uma iteração por meio deles, usando inspect.getmodule() para descobrir em qual módulo cada objeto foi originalmente definido. E suponho que você não esteja usando nada que tenha sido duplamente from foo import * -ed. Faça uma lista de todos os nomes que foram definidos nos módulos numpy e scipy .

Agora, você pode pegar essa saída e substituir cada foo por numpy.foo .

Então, juntando tudo, algo assim:

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 alguma das suposições estiver errada, não será difícil alterar o código. Além disso, talvez você queira usar tempfile.NamedTemporaryFile e outras melhorias para garantir que não substitua acidentalmente as coisas por arquivos temporários. (Eu simplesmente não queria lidar com a dor de cabeça de escrever algo de plataforma cruzada; se você não está executando no Windows, é fácil.) E adicione algum tratamento de erro, obviamente, e provavelmente alguns relatórios.

    
por abarnert 07.03.2013 / 19:58
fonte
3

Sim. Remova as importações e execute um linter no módulo.

Eu recomendo usar flake8 , embora também possa gerar muito ruído sobre erros de estilo.

Simplesmente remover as importações e tentar executar o código provavelmente não será suficiente, pois muitos erros de nome não serão gerados até que você execute apenas a linha de código correta com a entrada correta. Um linter irá analisar o código analisando e irá detectar NameError s sem ter que executar o código.

Isso tudo presume que não há testes de unidade confiáveis ou que os testes não fornecem cobertura suficiente.

Nesse caso, onde há várias % linhas from module import * , fica um pouco mais doloroso, pois você precisa descobrir para cada nome ausente qual módulo forneceu esse nome. Isso exigirá trabalho manual, mas você pode simplesmente importar o módulo em um interpretador python e testar se o nome ausente está definido nesse módulo:

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

Você precisa levar em conta que, neste caso específico, há sobreposição entre os módulos numpy e scipy ; para qualquer nome definido em ambos os módulos, o módulo importado por último ganha.

Note que deixar qualquer from module import * linha no lugar significa que o linter não será capaz de detectar quais nomes podem gerar NameErrors!

    
por Martijn Pieters 07.03.2013 / 19:47
fonte
-1

ok, isso é o que eu acho que você poderia fazer, quebrar o programa. remova as importações e observe os erros que são feitos. Em seguida, importe apenas os módulos que você quer, isso pode demorar um pouco, mas esta é a única maneira que eu sei de fazer isso, ficarei feliz em saber se alguém conhece uma ferramenta para ajudar

EDITAR: ah sim, um linter, eu não tinha pensado nisso.

    
por PurityLake 07.03.2013 / 19:47
fonte