2014-09-24 7 views
6

Vorrei creare un elenco di tutte le funzioni utilizzate in un file di codice. Per esempio, se abbiamo seguente codice in un file chiamato 'add_random.py'Come estrarre le funzioni utilizzate in un file di codice Python?

`

import numpy as np 
from numpy import linalg 

def foo(): 
    print np.random.rand(4) + np.random.randn(4) 
    print linalg.norm(np.random.rand(4)) 

`

vorrei estrarre il seguente elenco: [numpy.random.rand, np.random.randn, np.linalg.norm, np.random.rand]

L'elenco contiene le funzioni utilizzate nel codice con il loro nome reale sotto forma di 'module.submodule.function'. C'è qualcosa di costruito in Python che può aiutarmi a fare questo?

+4

Questo non è così facile come si pensa che potrebbe essere. Che dire di callables memorizzati come riferimenti in qualcos'altro? Supponiamo che tu abbia un dizionario con '{'foo': np.random.rand, 'bar': linalg.norm}' quindi usa quelle chiamabili tramite i tasti nel dizionario. Prendi in considerazione che il tuo codice può quindi riassociare anche quelle chiavi, scambiando dinamicamente i nomi .. –

+2

In altre parole, la risoluzione dei nomi completi completi non è necessariamente semplice né tagliente e secca. –

+1

Detto questo, è possibile catturare tutti i nodi 'ast.Call' ed estrarre l'espressione' func' (sarà un albero più piccolo di nodi 'ast', compresi' ast.Name' e 'ast.Attribute'). –

risposta

3

È possibile estrarre tutte le espressioni di chiamata con:

import ast 

class CallCollector(ast.NodeVisitor): 
    def __init__(self): 
     self.calls = [] 
     self.current = None 

    def visit_Call(self, node): 
     # new call, trace the function expression 
     self.current = '' 
     self.visit(node.func) 
     self.calls.append(self.current) 
     self.current = None 

    def generic_visit(self, node): 
     if self.current is not None: 
      print "warning: {} node in function expression not supported".format(
       node.__class__.__name__) 
     super(CallCollector, self).generic_visit(node) 

    # record the func expression 
    def visit_Name(self, node): 
     if self.current is None: 
      return 
     self.current += node.id 

    def visit_Attribute(self, node): 
     if self.current is None: 
      self.generic_visit(node) 
     self.visit(node.value) 
     self.current += '.' + node.attr 

Utilizzare questo con un albero ast parse:

tree = ast.parse(yoursource) 
cc = CallCollector() 
cc.visit(tree) 
print cc.calls 

Demo:

>>> tree = ast.parse('''\ 
... def foo(): 
...  print np.random.rand(4) + np.random.randn(4) 
...  print linalg.norm(np.random.rand(4)) 
... ''') 
>>> cc = CallCollector() 
>>> cc.visit(tree) 
>>> cc.calls 
['np.random.rand', 'np.random.randn', 'linalg.norm'] 

È possibile che questo deambulatore gestisce solo i nomi e attributi; se hai bisogno di un supporto per le espressioni più complesso, dovrai estenderlo.

Si noti che la raccolta di nomi come questo è non è un compito banale. Qualsiasi riferimento indiretto non verrebbe gestito. È possibile creare un dizionario nel proprio codice di funzioni per chiamare e scambiare dinamicamente gli oggetti funzione, e l'analisi statica come quella precedente non sarà in grado di seguirla.

+0

Funziona molto bene per una funzione semplice. Ma fallisce quando abbiamo una composizione di funzioni come in 'np.random.rand (4) .mean()'. Cosa si dovrebbe provare a fare in questo caso? Idealmente mi piacerebbe estrarre sia 'np.random.rand' che' mean'. –

+0

@ShishirPandey: Dovrai 'sapere' quali sono i tipi di ritorno delle chiamate alle funzioni e con un pacchetto come NumPy che richiede di aver raccolto prima tali informazioni; Le estensioni Python C non sono ancora introspettabili (sebbene Python 3.4/3.5 stia lavorando per rimediare a questo). Piuttosto che re-inventare questa ruota, forse hai bisogno di guardare pacchetti come [CodeIntel] (https://github.com/mook/komodo-codeintel2), che ti permette di produrre un codice auto-completatore per Python e altre lingue (SublimeCodeIntel lo usa, tra gli altri). –

1

In generale, questo problema è indecidibile, si consideri ad esempio getattribute(random, "random")().

Se si desidera l'analisi statica, il meglio che c'è ora è jedi

Se si accetta soluzioni dinamiche, poi coprire la copertura è il tuo migliore amico. Mostrerà tutte le funzioni utilizzate, piuttosto che solo referenziate direttamente.

Infine si può sempre rotolare il proprio strumentazione dinamica lungo le linee di:

import random 
import logging 

class Proxy(object): 
    def __getattr__(self, name): 
     logging.debug("tried to use random.%s", name) 
     return getattribute(_random, name) 

_random = random 
random = Proxy() 
Problemi correlati