10
votes

Inverser de l'importation de module *

J'ai une base de code où je nettoie des décisions désordonnées par le développeur précédent. Fréquemment, il a fait quelque chose comme: xxx

... Ceci, bien sûr, pollue l'espace de nom et il est difficile de dire où un attribut dans le module est originaire.

Y a-t-il un moyen d'analyser Python et de résoudre ce problème pour moi? Quelqu'un a-t-il fait une utilité pour cela? Sinon, comment une utilité pourrait-elle être faite?


3 commentaires

Je compatis. J'espère que vous trouverez un bon outil. (+1)


Encore mieux, j'espère que vous écrivez un bon outil (si basé sur ma réponse ou non) et de le publier sur PYPI, donc si j'ai besoin d'une telle chose, je n'ai pas à le faire moi-même. :)


Voir aussi cette question: Existe-t-il une IDE / Utility sur le refacteur Python * Importations pour utiliser le module standard.Member Syntaxe?


4 Réponses :


-1
votes

OK, c'est ce que je pense que vous pourriez faire, casser le programme. Supprimez les importations et remarquez les erreurs effectuées. Ensuite, importer uniquement les modules que vous voulez, cela peut prendre un certain temps, mais c'est la seule façon de le faire, je serai heureusement surpris si quelqu'un connaît un outil pour aider

EDIT: Ah oui, un Linter, je n'avais pas pensé à cela.


0 commentaires

3
votes

Oui. Supprimez les importations et exécutez un Linter sur le module.

Je recommande d'utiliser flake8 , bien que cela puisse également créer beaucoup de bruit sur les erreurs de style.

simplement enlever les importations et essayer d'exécuter le code ne sera probablement pas suffisant, car de nombreuses erreurs de nom ne seront pas nécessaires. Soyez élevé jusqu'à ce que vous exécutiez juste la bonne ligne de code avec juste la bonne entrée. Un Linter analysera plutôt le code en analysant et détectera le potentiel nomError S sans avoir à exécuter le code.

Tout cela suppose qu'il n'y a pas de tests d'unité fiables, ni que le Les tests ne fournissent pas assez de couverture.

Dans ce cas, où il y a plusieurs à partir de modules import * lignes, il obtient un peu plus douloureux dans ce Vous devez déterminer pour chaque nom manquant quel module a fourni ce nom. Cela nécessitera des travaux manuels, mais vous pouvez simplement importer le module dans un interprète Python et tester si le nom manquant est défini sur ce module: xxx

vous devez prendre en compte compte que dans ce cas particulier, il y a un chevauchement entre les modules numpy et SCIPY ; Pour n'importe quel nom défini dans les deux modules, le module importé les derniers victoires.

Notez que laisser tout à partir de l'importation de module * ligne en place signifie que le Linter sera ne pas être capable de détecter quels noms pourraient élever la nomenclature!


12 commentaires

Si je supprimais les importations, comment cela me dit-il quel module les noms viennent? Cela ne ferait-il pas que NameError sur tous les attributs qui sont maintenant montés sur la liste?


@Kelketek: Oui, et vous devrez déterminer pour chacun quel module il est supposé de venir de. Ce n'est pas si difficile, chanceux.


Si vous les retirez un à la fois, cela est probablement plus facile.


Vraisemblablement, je devrais exécuter le Linter d'abord pour obtenir des problèmes généraux qu'il pourrait trouver, les résoudre, supprimez une ligne d'importation, exécutez-le à nouveau pour voir quels noms apparaissent comme étant des erreurs de nom, puis les ajoutent à une importation "de" importation. Ensuite, répétez le processus avec chaque importation générique?


@Martijnpieters: Cela peut en réalité être étonnamment douloureux. Pour emporter l'exemple de à partir de SciPy Importer * et à partir de NUMPY Import * , la sémantique de quelques fonctions, telles que log10 dépend de laquelle L'importation est arrivée en dernier (ils existent dans les deux modules mais ne se comportent pas les mêmes). Voir Stackoverflow.com/questions/6200910/...


@Kelketek: Il y a un problème avec cela: laissant l'un des à partir du module import * lignes en place signifie que Linter ne sera pas en mesure de savoir quels noms augmentent les erreurs de noms. Un Linter statique ne peut pas voir quel module fournit quel nom.


Hmmm ... où est le mandataire +3? Intelligent ... Je ne veux pas courir votre hack trop souvent. Quoi qu'il en soit, +1 de moi.


Je vous donne une uppote de cette réponse, car c'est probablement le moyen le plus propre de le faire. Cependant, je pense que la méthode d'Abamert (en général) est ce que je vais avoir besoin - le développeur précédent avait un très grand nombre de déclarations d'importation comme celle-ci dans un seul module. Parfois plus de cinq ans.


@Kelketek: Honnêtement, même si je suis polie mon outil au point où il était fiable (ou que vous l'avez fait vous-même), vous auriez probablement besoin de gérer certains cas manuellement. (À tout le moins, tout en débogage de l'outil!) Donc, c'est une réponse importante de toute façon.


Oui, je me rends compte que. Cependant, cela fera beaucoup de problème plus facilement. Je traite des dizaines et des dizaines de modules comme celle-ci. C'est un cauchemar.


@Kelketek: Pourquoi les modifier tous à la fois? Pourquoi ne pas approcher cela sur une base de module par module? Sont-ils tous cassés? Sinon, ne réparez pas avant d'avoir à être!


Je déciderai toujours de mon approche pratique exacte. Mais quel que soit le cas, j'ai beaucoup trop d'entre eux pour ne pas envisager une assistance automatisée pour suivre les noms.



3
votes

Je pense que les solutions manuelles de PurityLake et Mantijn Pieters sont probablement la meilleure façon d'y aller. Mais ce n'est pas impossible em> de faire cela par programme.

Premièrement, vous devez obtenir une liste de tous les noms existants dans le dictionnaire du module pouvant être utilisé dans le code. Je suppose que votre code n'appelle pas directement des fonctions forts> Dunder forts>, etc. p>

Ensuite, vous devez les introduire à travers eux, en utilisant inspect.getmodule () Pour savoir quel module chaque objet a été défini à l'origine. Et je suppose que vous n'utilisez rien qui a été doublement de FOO Import * code> -ed. Faites une liste de tous les noms définis dans les modules code> numpy code> et scipy code>. P>

Vous pouvez maintenant prendre cette sortie et simplement remplacer chaque foo code> avec numpy.foo code>. p>

Alors, le mettre ensemble, quelque chose comme ceci: p>

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


10 commentaires

Je ne pense pas que cela fonctionnera tout à fait. Qu'en est-il des variables nommées my_foo . Tout à coup, vous obtenez my_numpy.foo . Oups. Bien sûr, étant donné un analyseur approprié (je pense ast ), vous pourriez probablement faire cela.


C'est probablement plus ce que je cherche. Le gars avait occasionnellement des modules où cinq importations de caractères génériques différentes ont été faites. C'est la folie. La recherche et remplacer ici est un peu dangereuse, car cela fait des hypothèses, mais cette idée me met dans la bonne direction.


En fin de compte, cela constitue un début raisonnable à un outil qui pourrait être utile pour l'ensemble de la communauté. Je me demande aussi comment ce genre de chose interagirait avec __ tout __ . Pour l'obtenir réellement, vous voudrez probablement filtrer les choses qui ne sont pas dans le module . Tous __ s'il existe.


@MGILSON: Vous avez raison, et si vous avez my_foo et foo , il n'y a aucun moyen de le réparer sans casser l'autre. Vous n'avez même pas vraiment besoin d'un analyseur approprié ici, juste un Lexer, qui est beaucoup plus simple. Mais vous avez raison, nous avons déjà avoir un analyseur approprié dans ast , alors peut-être que c'est la voie à suivre.


@MGILSON: Ma pensée originale était que je ne le ferais pas de cette façon - je vous fierais sur des tests de Linter et de l'unité et d'édition manuelle, comme dans la réponse de Martijn Pieters. Mais maintenant que vous le mentionnez, si quelqu'un devait prendre cela et le polonotage, il pourrait s'agir d'un outil utile à usage général.


@MGILSON, même étant donné un analyseur approprié, vous ne pouvez pas nécessairement faire cela. Si vous importez un nom foo , vous pouvez toujours avoir une variable locale nommée FOO dans l'une des fonctions, que vous ne pouvez pas remplacer par bar.foo


@MGILSON: Pendant ce temps, __ tous __ n'est-ce pas en réalité un problème ici. Je ne remplace pas toutes les choses que numpy exporte, mais plutôt toutes les choses importées à partir de numpy . L'inverse peut être plus facile ( numpy .__ tous __ s'il existe, dir (numpy) sinon), mais cela ne fonctionnera pas dans les cas où il y a plusieurs de FOO Import * lignes avec des noms contradictoires. Comment savons-nous si une instance de, dire, log10 est numpy.log10 ou scipe.log10 sans regarder l'objet actuel?


@ shx2 - Un analyseur approprié vous permettrait de savoir que foo à l'intérieur de la fonction est local . C'est une raison pour laquelle vous besoin un analyseur approprié.


@Abarnert - Quelqu'un est appelé deuxième :)


@Abarnert: Vous pouvez capturer un dict de noms à identifiant ( {k: id (v) pour k, v dans les globaux (). éléments ()} ), avant la première importation, et encore après Le premier de FOO importer * et vérifiez quels éléments de votre dict ont été modifiés. Là encore après la seconde ( à partir de la barre import * ) et vérifiez à nouveau.



0
votes

J'ai maintenant fait un petit utilité pour faire cela que j'appelle 'Dressazzler'. Il trouvera des lignes qui sont "à partir de l'importation de module *", puis développeront le "dir" des modules cible, en remplaçant les lignes.

Après l'exécution, vous devez toujours exécuter un Linter. Voici la partie particulièrement intéressante du code: xxx

Je suis en train de le maintenir sur un formulaire utilitaire autonome plus utile ici:

https://github.com/usgm/deazzler/


0 commentaires