web-dev-qa-db-fra.com

Recherche de code mort dans un grand projet python

J'ai vu Comment pouvez-vous trouver des fonctions inutilisées dans Python? mais c'est vraiment vieux et ne répond pas vraiment à ma question.

J'ai un grand projet python avec plusieurs bibliothèques partagées par plusieurs scripts de point d'entrée. Ce projet s'accroît depuis de nombreuses années avec de nombreux auteurs, donc il y a beaucoup de code mort. Vous savez la perceuse.

Je sais que trouver tout le code mort est indécis. Tout ce dont j'ai besoin, c'est d'un outil qui trouvera toutes les fonctions qui ne sont appelées nulle part. Nous ne faisons rien d'extraordinaire avec les fonctions d'appel basées sur la chaîne du nom de la fonction, donc je ne m'inquiète pas de quoi que ce soit de pathologique ...

Je viens d'installer pylint, mais il semble être basé sur des fichiers, et ne prêtant pas beaucoup d'attention aux dépendances d'interfaces, ou même aux dépendances de fonctions.

De toute évidence, je pourrais chercher par défaut dans tous les fichiers, obtenir tous les noms de fonction à partir de cela, et faire un grep pour chacun de ces noms de fonction. J'espère juste qu'il y a déjà quelque chose d'un peu plus intelligent que ça.

ETA: Veuillez noter que je n'attends pas ou ne veux pas quelque chose de parfait. Je connais ma résistance aux problèmes d'arrêt aussi bien que n'importe qui (non vraiment j'ai enseigné la théorie du calcul, je sais quand je regarde quelque chose qui est récursivement énumérable). Toute chose qui essaie de l'approcher en exécutant réellement le code va prendre beaucoup trop de temps. Je veux juste quelque chose qui passe syntaxiquement par le code et dit "Cette fonction est certainement utilisée. Cette fonction POURRAIT être utilisée, et cette fonction n'est certainement PAS utilisée, personne d'autre ne semble même savoir qu'elle existe!" Et les deux premières catégories ne sont pas importantes.

67
Brian Postow

Vous voudrez peut-être essayer vautour . Il ne peut pas tout attraper en raison de la nature dynamique de Python, mais il attrape un peu sans avoir besoin d'une suite de tests complète comme coverage.py et d'autres doivent fonctionner.

36
Keith Gaughan

Essayez d'exécuter Ned Batchelder 's coverage.py .

Coverage.py est un outil pour mesurer la couverture de code des programmes Python. Il surveille votre programme, notant quelles parties du code ont été exécutées, puis analyse la source pour identifier le code qui aurait pu être exécuté mais ne l'était pas.

15
Peter Wood

Il est très difficile de déterminer quelles fonctions et méthodes sont appelées sans exécuter le code, même si le code ne fait rien de compliqué. Les invocations de fonctions simples sont assez faciles à détecter, mais les appels de méthode sont vraiment difficiles. Juste un exemple simple:

class A(object):
    def f(self):
        pass

class B(A):
    def f(self):
        pass

a = []
a.append(A())
a.append(B())
a[1].f()

Rien de spécial ici, mais tout script qui essaye de déterminer si A.f() ou B.f() est appelé aura du mal à le faire sans réellement exécuter le code.

Bien que le code ci-dessus ne fasse rien d'utile, il utilise certainement des modèles qui apparaissent dans le vrai code - à savoir mettre des instances dans des conteneurs. Le vrai code fera généralement des choses encore plus complexes - décapage et décapage, structures de données hiérarchiques, conditionnelles.

Comme indiqué précédemment, il suffit de détecter les invocations de fonctions simples du formulaire

function(...)

ou

module.function(...)

sera plutôt facile. Vous pouvez utiliser le module ast pour analyser vos fichiers source. Vous devrez enregistrer toutes les importations et les noms utilisés pour importer d'autres modules. Vous devrez également suivre les définitions de fonction de niveau supérieur et les appels à l'intérieur de ces fonctions. Cela vous donnera un graphique de dépendance, et vous pouvez utiliser NetworkX pour détecter les composants connectés de ce graphique.

Bien que cela puisse sembler assez complexe, cela peut probablement être fait avec moins de 100 lignes de code. Malheureusement, presque tous les grands projets Python utilisent des classes et des méthodes, donc cela ne sera d'aucune utilité.

7
Sven Marnach

Voici la solution que j'utilise au moins provisoirement:

grep 'def ' *.py > defs
# ...
# edit defs so that it just contains the function names
# ...
for f in `cat defs` do
    cat $f >> defCounts
    cat *.py | grep -c $f >> defCounts
    echo >> defCounts
done

Ensuite, je regarde les fonctions individuelles qui ont très peu de références (<3 disent)

c'est moche, et ça ne me donne que des réponses approximatives, mais je pense que c'est assez bon pour commencer. Quelles sont vos pensées à tous?

6
Brian Postow

Avec la ligne suivante, vous pouvez lister toutes les définitions de fonctions qui ne sont évidemment pas utilisées comme attribut, appel de fonction, décorateur ou valeur de retour. C'est donc approximativement ce que vous recherchez. Ce n'est pas parfait, c'est lent, mais je n'ai jamais eu de faux positifs. (Avec linux, vous devez remplacer ack par ack-grep)

for f in $(ack --python --ignore-dir tests -h --noheading "def ([^_][^(]*).*\):\s*$" --output '$1' | sort| uniq); do c=$(ack --python -ch "^\s*(|[^#].*)(@|return\s+|\S*\.|.*=\s*|)"'(?<!def\s)'"$f\b"); [ $c == 0 ] && (echo -n "$f: "; ack --python --noheading "$f\b"); done
4
diefans

Si votre code est couvert de nombreux tests (il est très utile du tout), exécutez-le avec le plugin de couverture de code et vous pourrez alors voir le code inutilisé.)

1
yedpodtrzitko

IMO qui pourrait être réalisé assez rapidement avec un simple plugin pylint qui:

  • rappelez-vous chaque fonction/méthode (/ classe?) analysée dans un ensemble S1
  • suivre chaque fonction/méthode (/ classe?) appelée dans un ensemble S2
  • afficher S1 - S2 dans un rapport

Ensuite, vous devrez appeler pylint sur toute votre base de code pour obtenir quelque chose de sensé. Bien sûr, comme cela a été dit, cela devra être vérifié, car il peut y avoir eu des échecs d'inférence ou tels qui introduiraient de faux positifs. Quoi qu'il en soit, cela réduirait probablement considérablement le nombre de grep à faire.

Je n'ai pas encore beaucoup de temps pour le faire moi-même mais n'importe qui trouverait de l'aide sur la liste de diffusion [email protected].

1
sthenault