Pregunta

Voy a implementar un tokenizer en Python y me preguntaba si usted podría ofrecer algunos consejos de estilo?

He implementado un tokenizer antes en C y en Java, así que estoy bien con la teoría, me gustaría asegurarse de que estoy siguiendo python estilos y las mejores prácticas.

Listado De Tipos De Token:

En Java, por ejemplo, me gustaría tener una lista de los campos así:

public static final int TOKEN_INTEGER = 0

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

Devolver Las Fichas De La Tokenizer:

Existe una mejor alternativa a simplemente devuelve una lista de tuplas, por ejemplo,

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

Saludos,

Pete

¿Fue útil?

Solución

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

Otros consejos

Hay una clase indocumentado en el módulo denominado re 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

también se

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

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

En muchas situaciones, exp. al analizar flujos de entrada larga, puede que le resulte más útil para usted implementar tokenizer en función del generador. De esta manera usted puede recorrer fácilmente sobre todas las fichas sin necesidad de mucha memoria para construir la lista de tokens en primer lugar.

Para ver el generador de propuesta original u otros documentos en línea

Gracias por su ayuda, he empezado a llevar estas ideas juntas, y yo he llegado con 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()

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

Nop. Funciona muy bien.

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

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

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

tipos Token se declaran como "constantes", variables es decir, con los nombres ALL_CAPS, a nivel de módulo. Por ejemplo,

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

y así sucesivamente. He utilizado un guión bajo delante del nombre de señalar que de alguna manera esos campos son "privado" para el módulo, pero realmente no sé si esto es típico o aconsejable, ni siquiera la cantidad de Pythonic. (También, probablemente voy zanja números a favor de las cadenas, porque durante la depuración que son mucho más legible.)

Los créditos son devueltos 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, símbolo [0]).

Por último, me he dado cuenta de que a veces, sobre todo escribir pruebas, yo prefiero pasar una cadena a la tokenizer en lugar de un objeto de archivo. Yo lo llamo un "lector", y tener un método específico para abrirlo y dejar que el acceso tokenizer 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)

Cuando empiezo algo nuevo en Python Acostumbro a ver por primera vez en algunos módulos o bibliotecas para su uso. Hay 90% + posibilidad de que ya hay somthing disponible.

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

He implementado un señalizador de un lenguaje de programación C-similares. Lo que hice fue dividir la creación de fichas en dos capas:

  • a escáner de superficie : É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, ...); éste produce tuplas (tokenname, scannedstring, PosInicial, endpos).
  • a tokenizer : Esto consume las tuplas de la primera capa, convirtiéndolos en objetos simbólicos (tuplas nombrados harían así, creo). Su propósito es detectar algunas dependencias de largo alcance en la corriente simbólico, en particular cuerdas (con su apertura y cotizaciones de cierre) y comentarios (con su apertura de una lexems de cierre; - sí, que quería conservar los comentarios!) Y obligarlos a solo fichas. La corriente resultante de los objetos de fichas se devuelve entonces a un analizador de consumir.

Ambos son generadores. Los beneficios de este enfoque son:

  • La lectura del texto sin formato se realiza sólo en la forma más primitiva, con expresiones regulares simples -. Rápidas y limpias
  • La segunda capa ya se implementa como un analizador primitiva, para detectar los literales de cadena y comentarios - re-uso de la tecnología del analizador.
  • Usted no tiene que forzar el escáner de superficie con detecciones complejos.
  • Pero la verdadera analizador recibe fichas en el nivel semántico de la lengua a ser analizados (de nuevo cuerdas, comentarios).

Me siento muy feliz con este enfoque por capas.

Me dirijo a la excelente Procesamiento de texto en Python por David Mertz

Al ser esta una tardía respuesta, ahora hay algo en la documentación oficial: Escribir un tokenizer con el re de la biblioteca estándar.Este es el contenido en la documentación de Python 3 que no está en el Py 2.7 docs.Pero todavía es aplicable a personas mayores de Pitones.

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

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

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

he tenido que implementar un tokenizer, pero es necesario un enfoque más complejo que una lista de tuplas, por lo tanto, he implementado una clase por cada ficha. A continuación, puede devolver una lista de instancias de clases, o si desea ahorrar recursos, puede volver algo que implementa la interfaz iterador y generar el siguiente token, mientras que avanza en el análisis.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top