2013-05-19 11 views
6

voglio il codice in grado di analizzare una chiamata di funzione come questa:funzione di parsing Python chiama per ottenere posizioni di argomento

whatever(foo, baz(), 'puppet', 24+2, meow=3, *meowargs, **meowargs) 

E ritorno le posizioni di tutti e di ogni argomento, in questo caso foo, baz(), 'puppet', 24+2 , meow=3, *meowargs, **meowargs.

Ho provato a utilizzare il modulo _ast e sembra che sia proprio la cosa giusta per il lavoro, ma purtroppo ci sono stati dei problemi. Ad esempio, in un argomento come baz() che è una chiamata di funzione stessa, non sono riuscito a trovare un modo semplice per ottenere la sua lunghezza. (E anche se ne trovassi uno, non voglio un sacco di casi speciali per ogni diverso tipo di argomento.)

Ho anche guardato il modulo tokenize ma non ho visto come usarlo per ottenere gli argomenti .

Qualche idea su come risolvere questo problema?

+0

"e tornare alla posizioni di ogni argomento, in questo caso 'foo',' baz() ',' 'puppet'', '24 + 2',' meow = 3', '* meowargs',' ** meowargs'. " cosa vuoi tornare? come pensi che la tua chiamata sarebbe? per quale uso? non è chiaro cosa si voglia fare – octoback

+0

Non sono sicuro di cosa si stia tentando di fare, ma sono abbastanza sicuro che il modo corretto, migliore e più efficace per farlo è quello di guardare l'AST (preferibilmente via il modulo 'ast',' _ast' è un dettaglio di implementazione e 'ast' aggiunge alcune funzionalità utili).Hai bisogno di capire il concetto di AST e attraversamento degli alberi, ma senza di esso sei comunque obbligato a produrre una soluzione lenta, complessa, limitata e fragile. – delnan

+0

@antitrust Le posizioni, ovvero gli indici del loro inizio e fine nella stringa. L'uso è per uno script IDE. Non riuscivo a capire la tua domanda sulla chiamata. –

risposta

3

Questo codice utilizza una combinazione di ast (per trovare gli offset argomento iniziale) e le espressioni regolari (per identificare i confini degli argomenti):

import ast 
import re 

def collect_offsets(call_string): 
    def _abs_offset(lineno, col_offset): 
     current_lineno = 0 
     total = 0 
     for line in call_string.splitlines(): 
      current_lineno += 1 
      if current_lineno == lineno: 
       return col_offset + total 
      total += len(line) 
    # parse call_string with ast 
    call = ast.parse(call_string).body[0].value 
    # collect offsets provided by ast 
    offsets = [] 
    for arg in call.args: 
     a = arg 
     while isinstance(a, ast.BinOp): 
      a = a.left 
     offsets.append(_abs_offset(a.lineno, a.col_offset)) 
    for kw in call.keywords: 
     offsets.append(_abs_offset(kw.value.lineno, kw.value.col_offset)) 
    if call.starargs: 
     offsets.append(_abs_offset(call.starargs.lineno, call.starargs.col_offset)) 
    if call.kwargs: 
     offsets.append(_abs_offset(call.kwargs.lineno, call.kwargs.col_offset)) 
    offsets.append(len(call_string)) 
    return offsets 

def argpos(call_string): 
    def _find_start(prev_end, offset): 
     s = call_string[prev_end:offset] 
     m = re.search('(\(|,)(\s*)(.*?)$', s) 
     return prev_end + m.regs[3][0] 
    def _find_end(start, next_offset): 
     s = call_string[start:next_offset] 
     m = re.search('(\s*)$', s[:max(s.rfind(','), s.rfind(')'))]) 
     return start + m.start() 

    offsets = collect_offsets(call_string) 

    result = [] 
    # previous end 
    end = 0 
    # given offsets = [9, 14, 21, ...], 
    # zip(offsets, offsets[1:]) returns [(9, 14), (14, 21), ...] 
    for offset, next_offset in zip(offsets, offsets[1:]): 
     #print 'I:', offset, next_offset 
     start = _find_start(end, offset) 
     end = _find_end(start, next_offset) 
     #print 'R:', start, end 
     result.append((start, end)) 
    return result 

if __name__ == '__main__': 
    try: 
     while True: 
      call_string = raw_input() 
      positions = argpos(call_string) 
      for p in positions: 
       print ' ' * p[0] + '^' + ((' ' * (p[1] - p[0] - 2) + '^') if p[1] - p[0] > 1 else '') 
      print positions 
    except EOFError, KeyboardInterrupt: 
     pass 

uscita:

whatever(foo, baz(), 'puppet', 24+2, meow=3, *meowargs, **meowargs) 
     ^^ 
      ^^
        ^ ^
          ^^ 
            ^^
              ^ ^
                 ^ ^
[(9, 12), (14, 19), (21, 29), (31, 35), (37, 43), (45, 54), (56, 66)] 
f(1, len(document_text) - 1 - position) 
^
    ^       ^
[(2, 3), (5, 38)] 
+0

Impressionante hack. Speravo che fosse possibile creare una soluzione che non usasse la regex (dato che in genere è un cattivo strumento per tali compiti) ma accetto che potrebbe non essere possibile. –

+0

Tuttavia, la soluzione non riesce per "" Foo (x = y, \ n ** kwargs) "'. –

+0

Ho aggiornato la mia risposta. – utapyngo

0

È possibile ottenere l'albero di sintassi astratto per una chiamata di funzione della propria funzione.

Here is a python recipe to do so, basato sul modulo ast.

Il modulo di Python viene utilizzato per analizzare la stringa di codice e creare un nodo smart . Quindi attraversa il nodo ast.AST risultante per trovare le funzioni utilizzando una sottoclasse NodeVisitor.

Funzione explain esegue l'analisi. Qui è di analizzare il chiamata di funzione, e quello che si ottiene

>>> explain('mymod.nestmod.func("arg1", "arg2", kw1="kword1", kw2="kword2", 
     *args, **kws') 
    [Call( args=['arg1', 'arg2'],keywords={'kw1': 'kword1', 'kw2': 'kword2'}, 
     starargs='args', func='mymod.nestmod.func', kwargs='kws')] 
+1

Non vedo come questo aiuti. Ad esempio, se uno dei tuoi argomenti è una funzione chiamata stessa, come fai a sapere la sua posizione iniziale e la sua posizione finale? –

0

Se ho capito bene, dal vostro esempio si desidera qualcosa di simile:

--> arguments("whatever(foo, baz(), 'puppet', 24+2, meow=3, *meowargs, **meowkwds)") 
{ 
    'foo': slice(9, 12), 
    'baz()': slice(14, 19), 
    '24+2': slice(21, 29), 
    'meow=3': slice(32, 38), 
    '*meowargs': slice(41, 50), 
    '**meowkwds': slice(53, 63), 
} 

Nota che ho cambiato il nome del tuo ultimo argomento, dato che non puoi avere due argomenti con lo stesso nome.

Se questo è ciò che si desidera, è necessario disporre della stringa originale in questione (non dovrebbe essere un problema se si crea un IDE) e occorre un parser di stringa. Una semplice macchina a stati dovrebbe fare il trucco.

+0

Apprezzo che tu abbia cercato di aiutarmi, ma quando dici "Una semplice macchina a stati dovrebbe fare il trucco", non so cosa intendi e come costruirla, in modo che funzioni effettivamente e restituisca i risultati che voglio. (E sì, ho la stringa.) –

+0

(Ho imparato a conoscere le macchine statali nelle scuole superiori, ma per me non è chiaro da una soluzione funzionante.) –

+0

@RamRachum: Non sono troppo difficili - detto questo, non ne ho mai effettivamente implementato uno. Vedrò se non riesco a trovare il tempo di prenderne uno insieme. –