32.7. tokenize — Analyseur lexical de Python

Code source : Lib/tokenize.py


Le module tokenize fournit un analyseur lexical pour Python, implémenté en Python. L’analyseur de ce module renvoie les commentaire sous forme de token, se qui le rend intéressant pour implémenter des pretty-printers, typiquement pour faire de la coloration syntaxique.

To simplify token stream handling, all Opérateurs and Délimiteurs tokens are returned using the generic token.OP token type. The exact type can be determined by checking the exact_type property on the named tuple returned from tokenize.tokenize().

32.7.1. Analyse Lexicale

Le point d’entrée principal est un générateur :

tokenize.tokenize(readline)

Le générateur tokenize() prend un argument readline qui doit être un objet appelable exposant la même interface que la méthode io.IOBase.readline() des objets fichiers. Chaque appel a la fonction doit renvoyer une ligne sous forme de bytes.

Le générateur fournit des quintuplet contenants : le type du token, sa chaîne, un couple d’entiers (srow, scol) indiquant la ligne et la colonne où le token commence, un couple d’entiers (erow, ecol) indiquant la ligne et la colonne où il se termine, puis la ligne dans laquelle il a été trouvé. La ligne donnée (le dernier élément du tuple) est la ligne « logique », les continuation lines étant incluses. Le tuple est renvoyé sous forme de named tuple dont les noms sont : type string start end line.

The returned named tuple has an additional property named exact_type that contains the exact operator type for token.OP tokens. For all other token types exact_type equals the named tuple type field.

Modifié dans la version 3.1: Soutien ajouté pour tuples nommé.

Modifié dans la version 3.3: Soutien ajouté pour exact_type.

tokenize() détermine le codage source du fichier en recherchant une nomenclature UTF-8 ou un cookie d’encodage, selon la PEP 263.

All constants from the token module are also exported from tokenize, as are three additional token type values:

tokenize.COMMENT

Valeur du jeton utilisée pour indiquer un commentaire.

tokenize.NL

Token value used to indicate a non-terminating newline. The NEWLINE token indicates the end of a logical line of Python code; NL tokens are generated when a logical line of code is continued over multiple physical lines.

tokenize.ENCODING

Token value that indicates the encoding used to decode the source bytes into text. The first token returned by tokenize() will always be an ENCODING token.

Une autre fonction est fournie pour inverser le processus de tokenisation. Ceci est utile pour créer des outils permettant de codifier un script, de modifier le flux de jetons et de réécrire le script modifié.

tokenize.untokenize(iterable)

Convertit les jetons en code source Python. L”iterable doit renvoyer des séquences avec au moins deux éléments, le type de jeton et la chaîne de caractères associée. Tout élément de séquence supplémentaire est ignoré.

Le script reconstruit est renvoyé sous la forme d’une chaîne unique. Le résultat est garanti pour que le jeton corresponde à l’entrée afin que la conversion soit sans perte et que les allers et retours soient assurés. La garantie ne s’applique qu’au type de jeton et à la chaîne de jetons car l’espacement entre les jetons (positions des colonnes) peut changer.

It returns bytes, encoded using the ENCODING token, which is the first token sequence output by tokenize().

tokenize() a besoin de détecter le codage des fichiers sources qu’il code. La fonction utilisée pour cela est disponible :

tokenize.detect_encoding(readline)

La fonction detect_encoding() est utilisée pour détecter l’encodage à utiliser pour décoder un fichier source Python. Il nécessite un seul argument, readline, de la même manière que le générateur tokenize().

Il appelle readline au maximum deux fois et renvoie le codage utilisé (sous forme de chaîne) et une liste de toutes les lignes (non décodées à partir des octets) dans lesquelles il a été lu.

It detects the encoding from the presence of a UTF-8 BOM or an encoding cookie as specified in PEP 263. If both a BOM and a cookie are present, but disagree, a SyntaxError will be raised. Note that if the BOM is found, 'utf-8-sig' will be returned as an encoding.

Si aucun codage n’est spécifié, la valeur par défaut, 'utf-8', sera renvoyée.

Utilisez open() pour ouvrir les fichiers source Python : ça utilise detect_encoding() pour détecter le codage du fichier.

tokenize.open(filename)

Ouvre un fichier en mode lecture seule en utilisant l’encodage détecté par dectect_encoding().

Nouveau dans la version 3.2.

exception tokenize.TokenError

Déclenché lorsque soit une docstring soit une expression qui pourrait être divisée sur plusieurs lignes n’est pas complété dans le fichier, par exemple :

"""Beginning of
docstring

ou :

[1,
 2,
 3

Note that unclosed single-quoted strings do not cause an error to be raised. They are tokenized as ERRORTOKEN, followed by the tokenization of their contents.

32.7.2. Utilisation en ligne de commande.

Nouveau dans la version 3.3.

Le module tokenize peut être exécuté en tant que script à partir de la ligne de commande. C’est aussi simple que :

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

Les options suivantes sont acceptées :

-h, --help

Montre ce message d’aide et quitte

-e, --exact

Affiche les noms de jetons en utilisant le même type.

Si filename.py est spécifié, son contenu est tokenisé vers stdout. Sinon, la tokenisation est effectuée sur ce qui est fourni sur stdin.

32.7.3. Exemples

Exemple d’un script qui transforme les littéraux de type float en type Decimal

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

Exemple de tokenisation à partir de la ligne de commande. Le script :

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

say_hello()

sera tokenisé à la sortie suivante où la première colonne est la plage des coordonnées de la ligne/colonne où se trouve le jeton, la deuxième colonne est le nom du jeton, et la dernière colonne est la valeur du jeton (le cas échéant)

$ 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      ''

The exact token type names can be displayed using the -e option:

$ 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      ''