2009-03-27 19 views
12

Voy a implementar un tokenizador en Python y me pregunto si podrías ofrecer algunos consejos de estilo.Manera pitónica de implementar un tokenizer

He implementado un tokenizador antes en C y en Java, así que estoy de acuerdo con la teoría, solo me gustaría asegurarme de seguir los estilos y las mejores prácticas de Pythonic.

Listado de tipos de tokens:

En Java, por ejemplo, me gustaría tener una lista de campos de este modo:

public static final int TOKEN_INTEGER = 0 

Pero, obviamente, no hay manera (creo) para declarar una constante variable en Python, así que podría reemplazar esto con declaraciones de variables normales, pero eso no me parece una gran solución, ya que las declaraciones podrían ser alteradas.

que vuelven tokens desde el Tokenizer:

hay una mejor alternativa a simplemente devolviendo una lista de tuplas, por ejemplo,

[ (TOKEN_INTEGER, 17), (TOKEN_STRING, "Sixteen")]? 

Cheers,

Pete

Respuesta

30

Python tiene un enfoque "todos somos adultos que consienten" a la ocultación de información. Está bien usar variables como si fueran constantes, y confiar en que los usuarios de su código no harán algo estúpido.

4

"¿Existe una mejor alternativa que simplemente devolver una lista de tuplas?"

Nope. Funciona realmente bien.

2

"¿Hay una mejor alternativa que simplemente devolver una lista de tuplas?"

Ese es el enfoque utilizado por el módulo "tokenize" para analizar el código fuente de Python. Devolver una lista simple de tuplas puede funcionar muy bien.

10

En muchas situaciones, exp. al analizar flujos de entrada largos, puede resultarle más útil implementar tokenizer como función de generador. De esta forma, puede iterar fácilmente sobre todos los tokens sin la necesidad de mucha memoria para compilar primero la lista de tokens.

Para generador de ver los original proposal u otros documentos en línea

+0

Ooh, eso es inteligente. En cualquier caso, el enfoque iterador/generador se siente más natural que una lista de tokens. –

0

"¿Hay una mejor alternativa a simplemente devolviendo una lista de tuplas"

que tenía que poner en práctica un tokenizer, pero se requiere un enfoque más complejo que una lista de tuplas, por lo tanto, implementé una clase para cada token. A continuación, puede devolver una lista de instancias de clase, o si desea guardar recursos, puede devolver algo implementando la interfaz del iterador y generar el próximo token mientras progresa en el análisis sintáctico.

7

Gracias por su ayuda, he comenzado a juntar estas ideas, y he encontrado lo siguiente. ¿Hay algo terriblemente mal con esta aplicación (en particular, me preocupa que pasa un objeto de archivo a la tokenizer):

class Tokenizer(object): 

    def __init__(self,file): 
    self.file = file 

    def __get_next_character(self): 
     return self.file.read(1) 

    def __peek_next_character(self): 
     character = self.file.read(1) 
     self.file.seek(self.file.tell()-1,0) 
     return character 

    def __read_number(self): 
     value = "" 
     while self.__peek_next_character().isdigit(): 
      value += self.__get_next_character() 
     return value 

    def next_token(self): 
     character = self.__peek_next_character() 

     if character.isdigit(): 
      return self.__read_number() 
+3

Debería perder los nombres del método de subrayado doble. – hyperboreean

+0

A menos que realmente desee codificar un tokenizer desde cero, es posible que desee buscar en una solución como pyparsing: http://pyparsing.wikispaces.com/ o algunos de los enlaces en http://nedbatchelder.com/text/python -parsers.html – Miles

+1

-1: double _'s: no sirve para nada. No los use a menos que esté escribiendo parte del intérprete de Python. –

2

recientemente he construido un tokenizer, también, y pasa a través de algunos de sus problemas.

Los tipos de tokens se declaran como "constantes", es decir, variables con nombres ALL_CAPS, en el nivel del módulo. Por ejemplo,

_INTEGER = 0x0007 
_FLOAT = 0x0008 
_VARIABLE = 0x0009 

y así sucesivamente. He usado un guión bajo en frente del nombre para señalar que de alguna manera esos campos son "privados" para el módulo, pero realmente no sé si esto es típico o aconsejable, ni siquiera cuánto Pythonic. (Además, probablemente descartaré los números a favor de las cadenas, porque durante la depuración son mucho más legibles.)

Los tokens se devuelven como tuplas con nombre.

from collections import namedtuple 
Token = namedtuple('Token', ['value', 'type']) 
# so that e.g. somewhere in a function/method I can write... 
t = Token(n, _INTEGER) 
# ...and return it properly 

he utilizado tuplas nombradas porque el código de cliente de la tokenizer (por ejemplo, el analizador) parece un poco más claro, mientras que el uso de nombres (por ejemplo token.value) en lugar de índices (por ejemplo simbólico [0]).

Finalmente, he notado que a veces, especialmente al escribir pruebas, prefiero pasar una cadena al tokenizer en lugar de un objeto de archivo. Lo llamo "lector" y tengo un método específico para abrirlo y permitir que el tokenizador acceda a él a través de la misma interfaz.

def open_reader(self, source): 
    """ 
    Produces a file object from source. 
    The source can be either a file object already, or a string. 
    """ 
    if hasattr(source, 'read'): 
     return source 
    else: 
     from io import StringIO 
     return StringIO(source) 
1

Cuando empiezo algo nuevo en Python generalmente miro primero algunos módulos o bibliotecas para usar. Hay un 90% de posibilidades de que ya haya algo disponible.

Para tokenizadores y analizadores esto es ciertamente así. ¿Has mirado PyParsing?

52

Hay una clase no documentada en el módulo re llamada re.Scanner. Es muy fácil de usar para un tokenizer:

import re 
scanner=re.Scanner([ 
    (r"[0-9]+",  lambda scanner,token:("INTEGER", token)), 
    (r"[a-z_]+",  lambda scanner,token:("IDENTIFIER", token)), 
    (r"[,.]+",  lambda scanner,token:("PUNCTUATION", token)), 
    (r"\s+", None), # None == skip token. 
]) 

results, remainder=scanner.scan("45 pigeons, 23 cows, 11 spiders.") 
print results 

dará lugar a

[('INTEGER', '45'), 
('IDENTIFIER', 'pigeons'), 
('PUNCTUATION', ','), 
('INTEGER', '23'), 
('IDENTIFIER', 'cows'), 
('PUNCTUATION', ','), 
('INTEGER', '11'), 
('IDENTIFIER', 'spiders'), 
('PUNCTUATION', '.')] 

Solía ​​re.Scanner escribir una configuración/estructurado analizador de formato de datos bastante ingeniosa en sólo un par de cientos de líneas.

+6

¿Hay alguna razón por la cual esta clase no está documentada? ¿No soportado? ¿Depreciado? – Peter

+1

¡Si tan solo lo supiera! Creo que utiliza características no documentadas en el motor de expresión regular sre subyacente de Python, así que puede ser. Hay un poco de discusión al respecto aquí: http://mail.python.org/pipermail/python-dev/2003-April/035075.html – AKX

1

Implementé un tokenizador para un lenguaje de programación similar a C. Lo que hice fue dividir la creación de fichas en dos capas:

  • un superficie del escáner: Éste realmente lee el texto y utiliza la expresión regular para dividirlo en sólo las fichas más primitve (operadores, identificadores, números, ...); este produce tuplas (tokenname, scannedstring, startpos, endpos).
  • a tokenizer: Esto consume las tuplas de la primera capa, convirtiéndolas en objetos simbólicos (las tuplas nombradas también lo harían, creo). Su propósito es detectar algunas dependencias de largo alcance en el flujo de tokens, particularmente cadenas (con sus comillas de apertura y cierre) y comentarios (con su apertura un lexemas de cierre; - ¡sí, quería retener comentarios!) Y forzarlos a solteros tokens. La secuencia resultante de objetos token se devuelve a un analizador consumidor.

Ambos son generadores.Los beneficios de este enfoque son:

  • lectura del texto sin formato se realiza sólo en la forma más primitiva, con expresiones regulares simples - rápido y limpio.
  • La segunda capa ya está implementada como un analizador primitivo, para detectar cadenas literales y comentarios: reutilización de la tecnología del analizador.
  • No es necesario forzar el escáner de superficie con detecciones complejas.
  • Pero el analizador real obtiene tokens en el nivel semántico del lenguaje que se analizará (nuevamente cadenas, comentarios).

Me siento muy feliz con este enfoque en capas.

2

Al tratarse de una respuesta tardía, ahora hay algo en la documentación oficial: Writing a tokenizer con la biblioteca estándar re. Esto es contenido en la documentación de Python 3 que no está en los documentos Py 2.7. Pero todavía es aplicable a las pitones más antiguas.

Esto incluye el código corto, la configuración fácil y la escritura de un generador, como han propuesto varias respuestas aquí.

Si los documentos no son Pythonic, no sé lo que es :-)

Cuestiones relacionadas