Frage

Ich werde eine tokenizer in Python implementieren, und ich frage mich, ob Sie etwas Art Beratung bieten können?

ich eine tokenizer vor in C implementiert haben und in Java so mit der Theorie, die ich mir gut, würde ich nur, um sicherzustellen, wie ich pythonic Designs und Best Practices bin nach.

Listing Token-Typen:

In Java, zum Beispiel, würde ich eine Liste der Felder in etwa so:

public static final int TOKEN_INTEGER = 0

Aber offensichtlich gibt es keine Möglichkeit (glaube ich) eine konstante Variable in Python zu erklären, so konnte ich dies nur mit normalen Variablendeklarationen ersetzen aber das macht mich nicht als eine große Lösung schlagen, da die Erklärungen geändert werden könnten.

Return-Tokens von den Tokenizer:

Gibt es eine bessere Alternative zu einfach nur eine Liste von Tupeln z Rückkehr

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

Cheers,

Pete

War es hilfreich?

Lösung

Python nimmt einen „wir sind alle mündig Erwachsene“ -Ansatz, um Informationen zu verstecken. Es ist OK, Variablen zu verwenden, als ob sie Konstanten waren, und darauf vertrauen, dass die Benutzer des Code nicht etwas Dummes tun.

Andere Tipps

Es gibt eine undokumentierte Klasse im re Modul namens re.Scanner. Es ist sehr einfach für eine tokenizer zu verwenden:

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

wird auch in

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

Ich habe re.Scanner einen ziemlich raffinierte Konfiguration / strukturierten Datenformat-Parser in nur ein paar hundert Zeilen zu schreiben.

In vielen Situationen exp. wenn lange Eingangsströme Parsen, können Sie finden es nützlicher Sie tokenizer als Generator-Funktion zu implementieren. Auf diese Weise können alle Token, ohne dass viel Speicher leicht iterieren zuerst die Liste der Token zu bauen.

Für Generator finden Sie in der oder anderen Online-docs rel="noreferrer">

Danke für Ihre Hilfe, ich habe angefangen, zusammen, um diese Ideen zu bringen, und ich habe mit dem folgenden kommen. Gibt es etwas schrecklich falsch mit dieser Implementierung (besonders ich mache mir Sorgen um ein Dateiobjekt zum tokenizer vorbei):

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()

„Gibt es eine bessere Alternative zu einfach nur eine Liste von Tupeln Rückkehr?“

Nein. Es funktioniert wirklich gut.

„Gibt es eine bessere Alternative zu einfach nur eine Liste von Tupeln Rückkehr?“

Das ist der Ansatz des „tokenize“ Modul für Quellcode Python Parsen. eine einfache Liste von Tupeln zurückkehrend kann sehr gut funktionieren.

Ich habe vor kurzem gebaut ein tokenizer, auch, und durch einige Ihrer Fragen übergeben.

Token-Typen werden als „Konstanten“ deklariert, das heißt Variablen mit ALL_CAPS Namen, auf Modulebene. Zum Beispiel:

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

und so weiter. Ich habe einen Unterstrich vor dem Namen für das Modul darauf hin, dass irgendwie diese Felder sind „private“ verwendet, aber ich weiß wirklich nicht, ob dieses typische oder ratsam ist, nicht einmal, wie viel Pythonic. (Auch ich werde wahrscheinlich Zahlen Graben zugunsten von Strings, weil während des Debuggens sind sie viel besser lesbar.)

Token werden als benannte Tupel zurückgegeben.

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

Ich habe den Namen Tupeln verwendet, weil der Client-Code des tokenizer (zum Beispiel des Parser) ein wenig klarer scheint, während Namen (zum Beispiel token.value) anstelle von Indizes (z Token [0]) verwendet wird.

Schließlich habe ich bemerkt, dass manchmal, vor allem Schreiben von Tests, ziehe ich einen String an das tokenizer passieren anstelle eines Dateiobjekt. Ich nenne es einen „Leser“, und eine spezifische Methode, um es zu öffnen und den tokenizer Zugriff es durch die gleiche Schnittstelle lassen.

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)

Ich habe eine tokenizer für eine C-ähnliche Programmiersprache implementiert. Was ich tat, war die Schaffung von Tokens in zwei Schichten aufgeteilt:

  • a Oberflächenscanner : Diese man eigentlich den Text liest und verwendet regulären Ausdruck es aufteilen in nur die primitivste Token (Betreiber, Bezeichner, Zahlen, ...); dies ergibt Tupel (tokenname, scannedstring, startpos, endpos).
  • a tokenizer : Diese die Tupel aus der ersten Schicht verbraucht, so dass sie in Token-Objekte drehen (mit dem Namen Tupel würde auch tun, glaube ich). Sein Zweck ist es, einige weitreichende Abhängigkeiten in dem Token-Strom zu erfassen, vor allem Streicher (mit ihrem Öffnen und Schließen Anführungszeichen) und Kommentaren (mit ihren einer Schließung Lexeme zu öffnen; - ja, wollte ich Kommentare halten!) Und zwingen sie in einzelnen Token. Der resultierende Strom von Token Objekte wird dann zu einem Verbraucher-Parser.

Beide sind Generatoren. Die Vorteile dieses Ansatzes waren:

  • Beim Lesen des rohen Textes nur in der primitivsten Art und Weise durchgeführt wird, mit einfachen regexps -. Schnell und sauber
  • Die zweite Schicht ist bereits als primitive Parser implementiert, Stringliterale und Kommentare zu erkennen - Wiederverwendung von Parser-Technologie.
  • Sie haben nicht die Oberflächenscanner mit komplexen Erkennungen zu belasten.
  • Aber der eigentliche Parser bekommt Token auf der semantischen Ebene der Sprache analysiert werden (wieder Strings, Kommentare).

Ich fühle mich sehr glücklich mit diesem geschichteten Ansatz.

ich die hervorragenden drehen würde Textverarbeitung in Python von David Mertz

Dies ist eine späte Antwort ist, gibt es jetzt etwas in der offiziellen Dokumentation: Schreibe eine tokenizer mit dem re Standard-Bibliothek. Dies ist Inhalt in der Python 3-Dokumentation, die nicht in dem Py 2.7 docs ist. Aber es ist immer noch für ältere Pythons.

Dies umfasst sowohl Kurzcode, einfache Einrichtung und das Schreiben eines Generators als mehrere Antworten hier vorgeschlagen haben.

Wenn die Dokumente nicht Pythonic sind, ich weiß nicht, was ist: -)

„Gibt es eine bessere Alternative zu einfach nur eine Liste von Tupeln Rückkehr“

Ich hatte eine tokenizer zu implementieren, aber es erforderlich, einen komplexeren Ansatz als eine Liste von Tupeln, also implementiert ich eine Klasse für jedes Token. Sie können dann eine Liste von Klasseninstanzen zurückgeben, oder wenn Sie Ressourcen sparen möchten, können Sie etwas Implementierung der Iterator-Schnittstelle zurückzukehren und das nächste Token zu generieren, während Sie in der Parsing Fortschritt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top