I nodi AST Python hanno attributi lineno
e col_offset
, che indicano l'inizio del rispettivo intervallo di codici. C'è un modo semplice per ottenere anche la fine della gamma di codici? Una libreria di terze parti?Come ottenere l'origine corrispondente a un nodo AST Python?
risposta
EDIT: ultimo codice (testato in Python 3.4 e 3.5) è qui: https://bitbucket.org/plas/thonny/src/master/thonny/ast_utils.py
Come non ho trovato un modo semplice, ecco un modo più duro (e probabilmente non ottimale). Potrebbe bloccarsi e/o funzionare in modo errato se ci sono più bug lineno/col_offset nel parser Python di quelli menzionati (e aggirati) nel codice. Testato in Python 3.3:
def mark_code_ranges(node, source):
"""
Node is an AST, source is corresponding source as string.
Function adds recursively attributes end_lineno and end_col_offset to each node
which has attributes lineno and col_offset.
"""
NON_VALUE_KEYWORDS = set(keyword.kwlist) - {'False', 'True', 'None'}
def _get_ordered_child_nodes(node):
if isinstance(node, ast.Dict):
children = []
for i in range(len(node.keys)):
children.append(node.keys[i])
children.append(node.values[i])
return children
elif isinstance(node, ast.Call):
children = [node.func] + node.args
for kw in node.keywords:
children.append(kw.value)
if node.starargs != None:
children.append(node.starargs)
if node.kwargs != None:
children.append(node.kwargs)
children.sort(key=lambda x: (x.lineno, x.col_offset))
return children
else:
return ast.iter_child_nodes(node)
def _fix_triple_quote_positions(root, all_tokens):
"""
http://bugs.python.org/issue18370
"""
string_tokens = list(filter(lambda tok: tok.type == token.STRING, all_tokens))
def _fix_str_nodes(node):
if isinstance(node, ast.Str):
tok = string_tokens.pop(0)
node.lineno, node.col_offset = tok.start
for child in _get_ordered_child_nodes(node):
_fix_str_nodes(child)
_fix_str_nodes(root)
# fix their erroneous Expr parents
for node in ast.walk(root):
if ((isinstance(node, ast.Expr) or isinstance(node, ast.Attribute))
and isinstance(node.value, ast.Str)):
node.lineno, node.col_offset = node.value.lineno, node.value.col_offset
def _fix_binop_positions(node):
"""
http://bugs.python.org/issue18374
"""
for child in ast.iter_child_nodes(node):
_fix_binop_positions(child)
if isinstance(node, ast.BinOp):
node.lineno = node.left.lineno
node.col_offset = node.left.col_offset
def _extract_tokens(tokens, lineno, col_offset, end_lineno, end_col_offset):
return list(filter((lambda tok: tok.start[0] >= lineno
and (tok.start[1] >= col_offset or tok.start[0] > lineno)
and tok.end[0] <= end_lineno
and (tok.end[1] <= end_col_offset or tok.end[0] < end_lineno)
and tok.string != ''),
tokens))
def _mark_code_ranges_rec(node, tokens, prelim_end_lineno, prelim_end_col_offset):
"""
Returns the earliest starting position found in given tree,
this is convenient for internal handling of the siblings
"""
# set end markers to this node
if "lineno" in node._attributes and "col_offset" in node._attributes:
tokens = _extract_tokens(tokens, node.lineno, node.col_offset, prelim_end_lineno, prelim_end_col_offset)
#tokens =
_set_real_end(node, tokens, prelim_end_lineno, prelim_end_col_offset)
# mark its children, starting from last one
# NB! need to sort children because eg. in dict literal all keys come first and then all values
children = list(_get_ordered_child_nodes(node))
for child in reversed(children):
(prelim_end_lineno, prelim_end_col_offset) = \
_mark_code_ranges_rec(child, tokens, prelim_end_lineno, prelim_end_col_offset)
if "lineno" in node._attributes and "col_offset" in node._attributes:
# new "front" is beginning of this node
prelim_end_lineno = node.lineno
prelim_end_col_offset = node.col_offset
return (prelim_end_lineno, prelim_end_col_offset)
def _strip_trailing_junk_from_expressions(tokens):
while (tokens[-1].type not in (token.RBRACE, token.RPAR, token.RSQB,
token.NAME, token.NUMBER, token.STRING,
token.ELLIPSIS)
and tokens[-1].string not in ")}]"
or tokens[-1].string in NON_VALUE_KEYWORDS):
del tokens[-1]
def _strip_trailing_extra_closers(tokens, remove_naked_comma):
level = 0
for i in range(len(tokens)):
if tokens[i].string in "({[":
level += 1
elif tokens[i].string in ")}]":
level -= 1
if level == 0 and tokens[i].string == "," and remove_naked_comma:
tokens[:] = tokens[0:i]
return
if level < 0:
tokens[:] = tokens[0:i]
return
def _set_real_end(node, tokens, prelim_end_lineno, prelim_end_col_offset):
# prelim_end_lineno and prelim_end_col_offset are the start of
# next positioned node or end of source, ie. the suffix of given
# range may contain keywords, commas and other stuff not belonging to current node
# Function returns the list of tokens which cover all its children
if isinstance(node, _ast.stmt):
# remove empty trailing lines
while (tokens[-1].type in (tokenize.NL, tokenize.COMMENT, token.NEWLINE, token.INDENT)
or tokens[-1].string in (":", "else", "elif", "finally", "except")):
del tokens[-1]
else:
_strip_trailing_extra_closers(tokens, not isinstance(node, ast.Tuple))
_strip_trailing_junk_from_expressions(tokens)
# set the end markers of this node
node.end_lineno = tokens[-1].end[0]
node.end_col_offset = tokens[-1].end[1]
# Try to peel off more tokens to give better estimate for children
# Empty parens would confuse the children of no argument Call
if ((isinstance(node, ast.Call))
and not (node.args or node.keywords or node.starargs or node.kwargs)):
assert tokens[-1].string == ')'
del tokens[-1]
_strip_trailing_junk_from_expressions(tokens)
# attribute name would confuse the "value" of Attribute
elif isinstance(node, ast.Attribute):
if tokens[-1].type == token.NAME:
del tokens[-1]
_strip_trailing_junk_from_expressions(tokens)
else:
raise AssertionError("Expected token.NAME, got " + str(tokens[-1]))
#import sys
#print("Expected token.NAME, got " + str(tokens[-1]), file=sys.stderr)
return tokens
all_tokens = list(tokenize.tokenize(io.BytesIO(source.encode('utf-8')).readline))
_fix_triple_quote_positions(node, all_tokens)
_fix_binop_positions(node)
source_lines = source.split("\n")
prelim_end_lineno = len(source_lines)
prelim_end_col_offset = len(source_lines[len(source_lines)-1])
_mark_code_ranges_rec(node, all_tokens, prelim_end_lineno, prelim_end_col_offset)
Hi So che la sua molto tardi, ma credo che questo è quello che stai cercando, sto facendo il parsing solo per le definizioni funzione nel modulo. Possiamo ottenere la prima e l'ultima riga del nodo ast con questo metodo. In questo modo è possibile ottenere le linee del codice sorgente di una definizione di funzione analizzando il file sorgente leggendo solo le linee di cui abbiamo bisogno. Questo è un esempio molto semplice,
st='def foo():\n print "hello" \n\ndef bla():\n a = 1\n b = 2\n
c= a+b\n print c'
import ast
tree = ast.parse(st)
for function in tree.body:
if isinstance(function,ast.FunctionDef):
# Just in case if there are loops in the definition
lastBody = func.body[-1]
while isinstance (lastBody,(ast.For,ast.While,ast.If)):
lastBody = lastBody.Body[-1]
lastLine = lastBody.lineno
print "Name of the function is ",function.name
print "firstLine of the function is ",function.lineno
print "LastLine of the function is ",lastLine
print "the source lines are "
if isinstance(st,str):
st = st.split("\n")
for i , line in enumerate(st,1):
if i in range(function.lineno,lastLine+1):
print line
Grazie! Sfortunatamente non mi aiuta. Ho bisogno di posizioni per tutti i nodi e non solo numeri di riga ma anche colonne. – Aivar
oh mi dispiace davvero. Lo cercherò e ti terrò aggiornato. –
ma hai bisogno della fine dell'intervallo del codice giusto? –
Abbiamo avuto una simile esigenza, e ho creato la biblioteca asttokens per questo scopo. Mantiene l'origine sia in formato testo che tokenizzato e contrassegna i nodi AST con informazioni token, da cui il testo è anche facilmente disponibile.
Funziona con Python 2 e 3 (testato con 2.7 e 3.5). Per esempio:
import ast, asttokens
st='''
def greet(a):
say("hello") if a else say("bye")
'''
atok = asttokens.ASTTokens(st, parse=True)
for node in ast.walk(atok.tree):
if hasattr(node, 'lineno'):
print atok.get_text_range(node), node.__class__.__name__, atok.get_text(node)
Stampe
(1, 50) FunctionDef def greet(a):
say("hello") if a else say("bye")
(17, 50) Expr say("hello") if a else say("bye")
(11, 12) Name a
(17, 50) IfExp say("hello") if a else say("bye")
(33, 34) Name a
(17, 29) Call say("hello")
(40, 50) Call say("bye")
(17, 20) Name say
(21, 28) Str "hello"
(40, 43) Name say
(44, 49) Str "bye"
- 1. Ottenere genitore del nodo AST in Python
- 2. nodo AST imprevisto HQL: {vector}
- 3. Ottenere AST per C++?
- 4. Elaborazione AST Python
- 5. Dato un AST, c'è una libreria funzionante per ottenere l'origine?
- 6. Genera .pyc da Python AST?
- 7. espressione regolare python corrispondente a qualsiasi cosa
- 8. Come posso ottenere un nodo adiacente a un nodo univoco usando Scala?
- 9. Come costruire manualmente un AST?
- 10. Rimozione di un elemento dall'elenco corrispondente a una sottostringa - Python
- 11. Visualizzazione dell'albero di analisi/modello di nodo/AST in xtext
- 12. Espressione regolare Python non corrispondente
- 13. Ottenere il codice sorgente di clang AST
- 14. Posso ottenere AST dal codice scala live?
- 15. come ottenere l'oggetto tz_info corrispondente al fuso orario corrente?
- 16. Come posso costruire un AST usando ANTLR4?
- 17. Come posso ottenere clang per scaricare AST senza colore?
- 18. Come posso dichiarare un PropType corrispondente a un numero nullable?
- 19. regex Python corrispondente alle proprietà Unicode
- 20. Ottenere il testo da un nodo
- 21. Parse HQL a AST Struttura e convertire AST torna a HQL
- 22. ottenere il testo interno di un nodo
- 23. Come ottenere un FieldSymbol Roslyn da un nodo FieldDeclarationSyntax?
- 24. Dove posso imparare a costruire AST per i macro Scala?
- 25. Elegante modello AST
- 26. Interprete AST?
- 27. Ottenere attributo di un nodo padre
- 28. Erlang: RPC a un nodo con output su quel nodo
- 29. Ottenere nodo figlio di un altro nodo, dato il nome del nodo
- 30. Come ottenere un nodo gradiente con mxnet.jl e Julia?
ho anche bisogno di un modo di annotare i nodi con informazioni end-offset (come la soluzione), con il supporto python2 pure. Sto pensando di creare un modulo standalone che lo faccia. Sarebbe interessante? @'Aivar, sei felice del tuo approccio? –
@DS Non sono soddisfatto della mia soluzione, perché al momento è incompleta e occasionalmente si verificano alcuni bug. Ma non vedo altra buona soluzione. Un'alternativa sarebbe scrivere un nuovo parser che raccolga più informazioni, ma non sono pronto a farlo da solo. Un pacchetto separato sarebbe davvero bello - ci sono diversi progetti che potrebbero trarne vantaggio. Vedi ad esempio questa idea di ragazzi: http://stackoverflow.com/questions/40639652/tracing-python-expression-valutazione-step-by-step – Aivar
Sto provando un approccio che sembra promettente finora, che lega ogni nodo ai token (dal modulo tokenize). Potresti condividere alcuni esempi che causano problemi? –