tokenize — Tokenizer für Python-Quellcode

Quellcode: Lib/tokenize.py


Das Modul tokenize stellt einen lexikalischen Scanner für Python-Quellcode bereit, der selbst in Python geschrieben ist. Der Scanner dieses Moduls gibt auch Kommentare als Tokens zurück und eignet sich damit für Pretty-Printer, etwa für die farbige Darstellung auf dem Bildschirm.

Damit sich der Tokenstrom einfacher verarbeiten lässt, werden alle Tokens für Operatoren und Trennzeichen sowie Ellipsis mit dem allgemeinen Tokentyp OP zurückgegeben. Das Property TokenInfo.exact_type nennt den genauen Tokentyp.

Warnung

Beachte, dass die Funktionen dieses Moduls nur darauf ausgelegt sind, syntaktisch gültigen Python-Code zu parsen, also Code, der beim Parsen mit ast.parse() keine Ausnahme auslöst. Bei ungültigem Python-Code ist das Verhalten der Funktionen dieses Moduls undefiniert und kann sich jederzeit ändern.

Eingabe zerlegen

Der wichtigste Einstiegspunkt ist ein Generator:

tokenize.tokenize(readline)

Der Generator tokenize() erwartet ein Argument, readline. Es muss ein aufrufbares Objekt sein, das dieselbe Schnittstelle bietet wie die Methode io.IOBase.readline() von Dateiobjekten. Jeder Aufruf sollte eine Eingabezeile als Bytes zurückgeben.

Der Generator erzeugt TokenInfo-Objekte.

Geändert in Version 3.1: Liefert jetzt TokenInfo-Objekte statt einfacher Tupel.

tokenize() ermittelt die Kodierung der Quelldatei, indem es gemäß PEP 263 nach einem UTF-8-BOM oder einem Encoding-Cookie sucht.

tokenize.generate_tokens(readline)

Zerlegt einen Quelltext, der als Unicode-Zeichenketten statt als Bytes gelesen wird.

Wie bei tokenize() ist das Argument readline ein aufrufbares Objekt, das eine einzelne Eingabezeile zurückgibt. Allerdings erwartet generate_tokens(), dass readline ein str-Objekt statt Bytes liefert.

Das Ergebnis ist ein Iterator, der genau wie bei tokenize() TokenInfo-Objekte liefert. Ein ENCODING-Token liefert er nicht.

class tokenize.TokenInfo(type, string, start, end, line)

Ein benanntes Tupel, das ein Token beschreibt. Es hat fünf Felder und zusätzlich das Property exact_type.

Added in version 3.1.

type

Eine ganze Zahl, die den Tokentyp angibt, entsprechend den Konstanten in token, etwa NAME und STRING. Operatoren und Trennzeichen haben den Typ OP. Für deren genauen Tokentyp dient exact_type.

string

Der Text des Tokens als str. Bei STRING-Tokens sind die Anführungszeichen, ein etwaiges Präfix und die Escape-Sequenzen enthalten, ohne dass sie ausgewertet werden.

Bei einem ENCODING-Token ist das der Name der Kodierung. Tokens, die ohne zugehörigen Quelltext eingefügt werden, können eine leere Zeichenkette enthalten. In Tokens FSTRING_MIDDLE und TSTRING_MIDDLE werden doppelte geschweifte Klammern durch einfache ersetzt.

start

Ein Zweiertupel (srow, scol) aus ganzen Zahlen, das die Anfangsposition des Tokens im Quelltext angibt, einschließlich dieser Position. Zeilen werden ab 1 gezählt, Spalten ab 0. Spalten zählen Unicode-Codepunkte. Ein Tabulator zählt als eine Spalte und wird nicht bis zum nächsten Tabulatorstopp aufgefüllt.

Bei einem ENCODING-Token ist das (0, 0).

end

Ein Zweiertupel (erow, ecol) aus ganzen Zahlen, das die Endposition des Tokens angibt, diese Position selbst ausgenommen. Zeilen werden ab 1 gezählt, Spalten ab 0. Reicht ein Token über mehrere Zeilen, ist erow die letzte Zeile und ecol die Spalte in dieser Zeile.

Beim Namen spam am Anfang der ersten Zeile ist zum Beispiel start gleich (1, 0) und end gleich (1, 4).

Bei einem ENCODING-Token ist das (0, 0). Ein Token NEWLINE oder NL, das am Ende einer Eingabe ohne abschließenden Zeilenumbruch eingefügt wird, kann eine Endspalte haben, die einen Schritt über den Quelltext hinausgeht.

line

Ein str mit der vollständigen Quelltextzeile, in der das Token gefunden wurde, samt umgebendem Text und, falls vorhanden, dem Zeilenendezeichen. Reicht ein Token über mehrere Zeilen, kann das mehrere Quelltextzeilen umfassen. Bei Tokens ENCODING und ENDMARKER ist es leer.

exact_type

Eine ganze Zahl, die bei OP-Tokens den genauen Operatortyp angibt, entsprechend den Konstanten in token. Bei allen anderen Tokentypen entspricht sie type. Ein Token + hat zum Beispiel type gleich OP und exact_type gleich PLUS.

Added in version 3.3.

Alle Konstanten des Moduls token werden auch von tokenize exportiert.

Eine weitere Funktion macht die Zerlegung wieder rückgängig. Das ist nützlich für Werkzeuge, die ein Skript zerlegen, den Tokenstrom verändern und das geänderte Skript zurückschreiben.

tokenize.untokenize(iterable)

Wandelt Tokens zurück in Python-Quellcode. iterable muss Sequenzen mit mindestens zwei Elementen liefern: den Tokentyp und die Zeichenkette des Tokens. Weitere Elemente der Sequenz werden übergangen.

Das Ergebnis lässt sich garantiert so zerlegen, dass es der Eingabe entspricht. Die Umwandlung ist damit verlustfrei und ein Hin und Zurück sicher. Die Garantie gilt nur für Tokentyp und Zeichenkette des Tokens, denn die Abstände zwischen den Tokens, also die Spaltenpositionen, können sich ändern.

Zurückgegeben werden Bytes, kodiert nach dem ENCODING-Token, das tokenize() als erste Tokensequenz ausgibt. Fehlt in der Eingabe ein Encoding-Token, wird stattdessen ein str zurückgegeben.

tokenize() muss die Kodierung der Quelldateien erkennen, die es zerlegt. Die Funktion, die es dafür verwendet, ist zugänglich:

tokenize.detect_encoding(readline)

Die Funktion detect_encoding() erkennt, mit welcher Kodierung eine Python-Quelldatei dekodiert werden soll. Sie erwartet ein Argument, readline, genau wie der Generator tokenize().

Sie ruft readline höchstens zweimal auf und gibt die verwendete Kodierung als Zeichenkette sowie eine Liste der gelesenen Zeilen zurück, die nicht aus Bytes dekodiert sind.

Sie erkennt die Kodierung an einem UTF-8-BOM oder an einem Encoding-Cookie gemäß PEP 263. Sind BOM und Cookie beide vorhanden, widersprechen sich aber, wird SyntaxError ausgelöst. Beachte, dass bei gefundenem BOM 'utf-8-sig' als Kodierung zurückgegeben wird.

Ist keine Kodierung angegeben, wird die Vorgabe 'utf-8' zurückgegeben.

Zum Öffnen von Python-Quelldateien dient open(): Sie erkennt die Kodierung der Datei mit detect_encoding().

tokenize.open(filename)

Öffnet eine Datei im Nur-Lese-Modus mit der Kodierung, die detect_encoding() erkannt hat.

Added in version 3.2.

exception tokenize.TokenError

Wird ausgelöst, wenn ein Docstring oder ein Ausdruck, der über mehrere Zeilen gehen darf, nirgends in der Datei abgeschlossen wird, zum Beispiel:

"""Beginning of
docstring

oder:

[1,
 2,
 3

Verwendung über die Befehlszeile

Added in version 3.3.

Das Modul tokenize lässt sich von der Befehlszeile als Skript ausführen. Das geht so einfach wie:

python -m tokenize [-e] [filename.py]

Die folgenden Optionen sind möglich:

-h, --help

diese Hilfe anzeigen und beenden

-e, --exact

Tokennamen mit dem genauen Typ anzeigen

Ist filename.py angegeben, wird dessen Inhalt zerlegt und nach stdout ausgegeben. Andernfalls wird stdin zerlegt.

Added in version 3.15: Die Ausgabe ist standardmäßig farbig und lässt sich über Umgebungsvariablen steuern.

Beispiele

Beispiel für einen Skriptumschreiber, der Float-Literale in Decimal-Objekte umwandelt:

from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP
from io import BytesIO

def decistmt(s):
    """Substitute Decimals for floats in a string of statements.

    >>> from decimal import Decimal
    >>> s = 'print(+21.3e-5*-.1234/81.7)'
    >>> decistmt(s)
    "print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))"

    The format of the exponent is inherited from the platform C library.
    Known cases are "e-007" (Windows) and "e-07" (not Windows).  Since
    we're only showing 12 digits, and the 13th isn't close to 5, the
    rest of the output should be platform-independent.

    >>> exec(s)  #doctest: +ELLIPSIS
    -3.21716034272e-0...7

    Output from calculations with Decimal should be identical across all
    platforms.

    >>> exec(decistmt(s))
    -3.217160342717258261933904529E-7
    """
    result = []
    g = tokenize(BytesIO(s.encode('utf-8')).readline)  # tokenize the string
    for toknum, tokval, _, _, _ in g:
        if toknum == NUMBER and '.' in tokval:  # replace NUMBER tokens
            result.extend([
                (NAME, 'Decimal'),
                (OP, '('),
                (STRING, repr(tokval)),
                (OP, ')')
            ])
        else:
            result.append((toknum, tokval))
    return untokenize(result).decode('utf-8')

Beispiel für das Zerlegen von der Befehlszeile aus. Das Skript:

def say_hello():
    print("Hello, World!")

say_hello()

wird zu der folgenden Ausgabe zerlegt. Die erste Spalte ist der Bereich der Zeilen- und Spaltenkoordinaten, in dem das Token gefunden wurde, die zweite Spalte der Name des Tokens und die letzte Spalte, falls vorhanden, sein Wert:

$ python -m tokenize hello.py
0,0-0,0:            ENCODING       'utf-8'
1,0-1,3:            NAME           'def'
1,4-1,13:           NAME           'say_hello'
1,13-1,14:          OP             '('
1,14-1,15:          OP             ')'
1,15-1,16:          OP             ':'
1,16-1,17:          NEWLINE        '\n'
2,0-2,4:            INDENT         '    '
2,4-2,9:            NAME           'print'
2,9-2,10:           OP             '('
2,10-2,25:          STRING         '"Hello, World!"'
2,25-2,26:          OP             ')'
2,26-2,27:          NEWLINE        '\n'
3,0-3,1:            NL             '\n'
4,0-4,0:            DEDENT         ''
4,0-4,9:            NAME           'say_hello'
4,9-4,10:           OP             '('
4,10-4,11:          OP             ')'
4,11-4,12:          NEWLINE        '\n'
5,0-5,0:            ENDMARKER      ''

Die genauen Namen der Tokentypen lassen sich mit der Option -e anzeigen:

$ python -m tokenize -e hello.py
0,0-0,0:            ENCODING       'utf-8'
1,0-1,3:            NAME           'def'
1,4-1,13:           NAME           'say_hello'
1,13-1,14:          LPAR           '('
1,14-1,15:          RPAR           ')'
1,15-1,16:          COLON          ':'
1,16-1,17:          NEWLINE        '\n'
2,0-2,4:            INDENT         '    '
2,4-2,9:            NAME           'print'
2,9-2,10:           LPAR           '('
2,10-2,25:          STRING         '"Hello, World!"'
2,25-2,26:          RPAR           ')'
2,26-2,27:          NEWLINE        '\n'
3,0-3,1:            NL             '\n'
4,0-4,0:            DEDENT         ''
4,0-4,9:            NAME           'say_hello'
4,9-4,10:           LPAR           '('
4,10-4,11:          RPAR           ')'
4,11-4,12:          NEWLINE        '\n'
5,0-5,0:            ENDMARKER      ''

Beispiel für das Zerlegen einer Datei aus dem Programm heraus, wobei mit generate_tokens() Unicode-Zeichenketten statt Bytes gelesen werden:

import tokenize

with tokenize.open('hello.py') as f:
    tokens = tokenize.generate_tokens(f.readline)
    for token in tokens:
        print(token)

Oder, mit tokenize() direkt Bytes gelesen:

import tokenize

with open('hello.py', 'rb') as f:
    tokens = tokenize.tokenize(f.readline)
    for token in tokens:
        print(token)