Encontrar código muerto en un proyecto de python grande

He visto ¿Cómo puedes encontrar funciones no utilizadas en el código Python? pero eso es realmente viejo, y realmente no responde mi pregunta.

Tengo un gran proyecto de Python con varias bibliotecas que son compartidas por múltiples scripts de punto de entrada. Este proyecto se ha acrecentado durante muchos años con muchos autores, por lo que hay muchos códigos muertos. Ya sabes que hacer.

Sé que encontrar todo el código muerto es indecidible. Todo lo que necesito es una herramienta que encontrará todas las funciones que no se llaman en ningún lugar. No estamos haciendo nada sofisticado con las funciones de llamada basadas en la cadena del nombre de la función, así que no me preocupa nada patológico …

Acabo de instalar pylint, pero parece estar basado en archivos y no presta mucha atención a las dependencias interconectadas, o incluso a las dependencias de funciones.

Claramente, podría grep para def en todos los archivos, obtener todos los nombres de funciones de eso y hacer un grep para cada uno de esos nombres de funciones. Solo espero que haya algo un poco más inteligente que eso por ahí ya.

ETA: Tenga en cuenta que no espero o quiero algo perfecto. Conozco mi prueba de problemas a prueba de problemas, así como cualquiera (no, realmente enseñé teoría de computación, lo sé cuando estoy viendo algo recursivamente enumerable). Cualquier cosa que intente aproximarlo ejecutando realmente el código va a tomar demasiado tiempo. Solo quiero algo que sintácticamente pase por el código y diga “Esta función definitivamente se usa. Esta función PUEDE usarse, y esta función definitivamente NO se usa, ¡nadie más parece saber que existe!” Y las dos primeras categorías no son importantes.

Es posible que desee probar buitre . No puede capturar todo debido a la naturaleza dinámica de Python, pero capta bastante sin necesidad de un conjunto de pruebas completo como cobertura.py y otros necesitan trabajar.

Intenta ejecutar la cobertura de Ned Batchelder.py .

Coverage.py es una herramienta para medir la cobertura de código de los progtwigs Python. Supervisa su progtwig, observa qué partes del código se han ejecutado y luego analiza la fuente para identificar el código que podría haberse ejecutado pero no se ejecutó.

Es muy difícil determinar a qué funciones y métodos se llama sin ejecutar el código, incluso si el código no hace nada sofisticado. Las invocaciones de funciones simples son bastante fáciles de detectar, pero las llamadas a métodos son realmente difíciles. Solo un ejemplo 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() 

No hay nada lujoso aquí, pero cualquier secuencia de comandos que intente determinar si se llama a Af() o Bf() tendrá un tiempo bastante difícil para hacerlo sin ejecutar realmente el código.

Si bien el código anterior no hace nada útil, ciertamente utiliza patrones que aparecen en el código real, es decir, poniendo instancias en contenedores. El código real por lo general hará cosas aún más complejas: decapado y descifrado, estructuras de datos jerárquicas, condicionales.

Como se dijo antes, simplemente detectando invocaciones de la función simple de la forma

 function(...) 

o

 module.function(...) 

Será bastante fácil. Puede utilizar el módulo ast para analizar sus archivos de origen. Deberá registrar todas las importaciones y los nombres utilizados para importar otros módulos. También deberá realizar un seguimiento de las definiciones de funciones de nivel superior y las llamadas dentro de estas funciones. Esto le dará un gráfico de dependencia, y puede usar NetworkX para detectar los componentes conectados de este gráfico.

Si bien esto puede sonar bastante complejo, probablemente se puede hacer con menos de 100 líneas de código. Desafortunadamente, casi todos los proyectos importantes de Python usan clases y métodos, por lo que será de poca ayuda.

Aquí está la solución que estoy usando al menos tentativamente:

 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 

Luego miro las funciones individuales que tienen muy pocas referencias (<3 dicen)

es feo, y solo me da respuestas aproximadas, pero creo que es lo suficientemente bueno para empezar. ¿Qué son todos los pensamientos?

Con la siguiente línea puede enumerar todas las definiciones de funciones que obviamente no se utilizan como un atributo, una llamada de función, un decorador o un valor de retorno. Así que es aproximadamente lo que estás buscando. No es perfecto, es lento, pero nunca obtuve ningún falso positivo. (Con Linux tienes que reemplazar ack con 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*|)"'(? 

Si tiene su código cubierto con muchas pruebas (es bastante útil en absoluto), ejecútelo con el complemento de cobertura de código y podrá ver el código no utilizado en ese momento.)

OMI que podría lograrse con bastante rapidez con un simple complemento de pylint que:

  • recuerda cada función / método analizado (/ clase?) en un conjunto S1
  • rastrear cada función / método llamado (/ class?) en un conjunto S2
  • mostrar S1 – S2 en un informe

Entonces deberías llamar a pylint en toda tu base de código para obtener algo que tenga sentido. Por supuesto, como se dijo, esto tendría que comprobarse, ya que puede haber fallas de inferencia o similares que introducirían falsos positivos. De todos modos, eso probablemente reduciría en gran medida la cantidad de grep a realizar.

Todavía no tengo mucho tiempo para hacerlo, pero cualquiera encontraría ayuda en la lista de correo python-projects@logilab.org.