#18. トークン化

トークン化は、CPython のコンパイル パイプラインの最初の構造段階です。

Python ソース テキストを受け取り、トークンのストリームを生成します。パーサーはそのトークン ストリームを消費し、そこから構文構造を構築します。

この段階では、CPython はプログラムが意味があるかどうかをまだ知りません。名前、数値、文字列、演算子、改行、インデント、ファイルの終わりマーカーなどの語彙単位のみを認識します。

トークナイザーは次のように変換します。python def add(a, b): return a + b 次のような形のストリームになります。text NAME "def" NAME "add" LPAR "(" NAME "a" COMMA "," NAME "b" RPAR ")" COLON ":" NEWLINE "\n" INDENT " " NAME "return" NAME "a" PLUS "+" NAME "b" NEWLINE "\n" DEDENT "" ENDMARKER "" 正確なトークン名とパーサー インターフェイスは CPython のバージョンによって異なりますが、中心的な考え方は安定しています。 Python 標準ライブラリは、次の方法で Python レベルのトークナイザーを公開します。tokenize一方、CPython のパーサーは内部で独自の C トークナイザーを使用します。一般の人々tokenizeこのモジュールは、コメントと初期エンコード トークンも返すため、フォーマッタや構文ハイライタなどのツールに適しています。 ([Python ドキュメント][1])

18.1 コンパイルパイプラインでの位置

ソースから実行までの完全なパスは次のとおりです。```text bytes from file or string ↓ encoding detection ↓ decoded source text ↓ tokenizer ↓ token stream ↓ parser ↓ abstract syntax tree ↓ symbol table ↓ code object ↓ bytecode execution


トークナイザーは次のような質問に答えます。```text
Where does this logical line end?
Is this identifier a name?
Is this numeric literal well-formed?
Is this string literal closed?
Did indentation increase or decrease?
Is this character part of an operator?
Has the source reached end-of-file?
```パーサーはさまざまな質問に答えます。```text
Is this a valid function definition?
Is this expression allowed here?
Does this statement match a grammar rule?
Does this sequence form a valid pattern match?
How should these tokens be grouped into an AST?
```トークナイザーは AST を構築しません。一連の語彙イベントのみを生成します。

## 18.2 ソース入力とエンコーディング

Python ソースは通常、バイトで始まります。

トークン化を続行する前に、CPython はこれらのバイトをテキストにデコードする方法を決定する必要があります。 Python ソース ファイルはデフォルトでは UTF-8 ですが、ファイルの先頭近くで別のエンコーディングを宣言する場合があります。

典型的なエンコーディング宣言:```python
# -*- coding: latin-1 -*-
```または:```python
# coding: utf-8
```トークナイザーは、ソースがデコードされるまでソース文字を確実に分類できないため、これを早期に処理する必要があります。

Python レベルでは、`tokenize.tokenize()`を返すことから始まります`ENCODING`トークン。の`token`ドキュメントにはこれが記載されています`ENCODING`Pythonにはトークンが必要です`tokenize`モジュールであり、CPython  C トークナイザーでは同じように使用されません。 ([Python ドキュメント][2])

実用的なモデルは次のとおりです。```text
read first source lines
detect encoding declaration if present
decode source bytes
normalize line handling
begin lexical scanning
```これが、トークン化が単なる文字のループではない理由です。また、外部ソース バイトと内部ソース テキスト間の境界も所有します。

## 18.3 物理回線と論理回線

Pythonのソースには物理行と論理行があります。

物理行はソース ファイル内の 1 行です。

論理行は、パーサーから見た 1 つの完全な Python ステートメントまたは式単位です。

通常、それらは同じです:```python
x = 1
y = 2
```ここで、各物理回線は論理回線でもあります。

ただし、Python ではバックスラッシュを使用した明示的な行結合が可能です。```python
x = 1 + \
    2 + \
    3
```これは、3 つの物理回線にまたがる 1 つの論理回線です。

Python では、括弧、大括弧、中括弧内での暗黙的な行結合も許可されています。```python
values = [
    1,
    2,
    3,
]
```グループ区切り文字の内側では、改行は論理ステートメントを終了しません。トークナイザーはネストの深さを追跡するため、重要な改行と重要でない改行を区別できます。

概念的には:```text
paren_level = 0

when "(" or "[" or "{" appears:
    paren_level += 1

when ")" or "]" or "}" appears:
    paren_level -= 1

when newline appears:
    if paren_level == 0:
        emit NEWLINE
    else:
        ignore as statement terminator
```このルールは、Python の読みやすい複数行構文にとって不可欠です。

##18.4`NEWLINE`そして`NL`Python レベルのトークン化では、論理行の改行と終端しない改行が区別されます。

公の場で`tokenize`レベル:

|トークン |意味 |
| --------- | -------------------------------------- |
|`NEWLINE`|論理行を終了します |
|`NL`|論理行を終了しない改行 |

例:```python
x = (
    1 +
    2
)
y = 3
```括弧内の改行はステートメント終了文字ではありません。パーサーは処理すべきではありません`1 +`完全なステートメントとして。これらの改行は文法のためではなく、レイアウトのために存在します。

ツールに面したトークナイザーでは、次のように表示されます。`NL`。パーサー側のモデルでは、これらは無視されるか、実際の論理改行とは異なる方法で扱われます。

この区別は、フォーマッタとリンターにとって重要です。フォーマッタはすべての物理的な改行を考慮する場合があります。パーサーに必要なのは論理構造のみです。

## 18.5 トークンとしてのインデント

Python は構文としてインデントを使用します。

つまり、トークナイザーは先頭の空白をトークンに変換する必要があります。

例:```python
if ready:
    run()
    log()
finish()
```パーサーは、名前と句読点だけを使用してこのソースを理解することはできません。明示的なブロック境界が必要です。

トークナイザーは次のものを出力します。```text
NAME      "if"
NAME      "ready"
COLON     ":"
NEWLINE   "\n"
INDENT    "    "
NAME      "run"
LPAR      "("
RPAR      ")"
NEWLINE   "\n"
NAME      "log"
LPAR      "("
RPAR      ")"
NEWLINE   "\n"
DEDENT    ""
NAME      "finish"
LPAR      "("
RPAR      ")"
NEWLINE   "\n"
ENDMARKER ""
```インデントにより仮想ブロックの開始部分が作成されます。へこみにより仮想ブロック端が作成されます。

CPython はインデント スタックを維持します。論理行の先頭で、トークナイザーは先頭の空白を測定し、それを現在のインデント レベルと比較します。

簡略化したモデル:```text
indent_stack = [0]

at beginning of logical line:
    col = indentation_column()

    if col > indent_stack[-1]:
        push col
        emit INDENT

    else if col == indent_stack[-1]:
        emit no indentation token

    else:
        while col < indent_stack[-1]:
            pop
            emit DEDENT

        if col != indent_stack[-1]:
            report indentation error
```このスタック規律は、インデントの不一致が通常の解析を続行する前に語彙エラーとなる理由を説明します。

## 18.6 タブ、スペース、およびインデント列

インデントは、生の文字ではなく列単位で測定されます。

スペースは 1 列ずつ進みます。タブは次のタブストップに進みます。 Python のタブ処理は互換性のために存在しますが、タブとスペースを混在させると、あいまいなインデントやエラーが発生する可能性があります。

例:```python
if x:
\tprint("tab")
    print("spaces")
```視覚的な配置はエディターの設定に依存する場合があります。 CPython は人間の編集者がこれを表示する方法を信頼できません。言語のタブ ルールを使用してインデントを計算し、インデントに一貫性がない場合はエラーが発生します。

内部的に重要な点は、インデントが「先頭の文字数」として保存されないことです。インデントレベルに変換されます。これらのレベルはインデント スタックと比較されます。

## 18.7 空白行とコメントのみの行

通常、空白行はパーサーにとって重要なトークンを生成しません。

例:```python
x = 1

y = 2
```空行はブロックを終了したり、ステートメントを作成したりしません。

コメントのみの行は、パーサーに対して同様に動作します。```python
x = 1
# comment
y = 2
```一般の人々`tokenize`ツールがコメントを必要とするため、モジュールはコメントを返します。 CPython のパーサーはコメントを構文として扱いません。

この違いは重要です。

|消費者 |コメントが必要ですか? |理由 |
| ------------------ | --------------: | ---------------------------------- |
|パーサー |              いいえ |コメントは文法に影響しません |
|フォーマッタ |             はい |コメントは保存する必要があります |
|構文ハイライト |             はい |コメントにはスタイル設定が必要です |
|リンター |             はい |コメントにはディレクティブが含まれる場合があります。
|タイプチェッカー |       時々 |コメントにはタイプ コメントを含めることができます |

パブリック トークナイザーはツール API です。 C トークナイザーはコンパイラー フロントエンドの一部です。

## 18.8 名前、キーワード、およびソフトキーワード

識別子は名前としてトークン化されます。

例:```python
total = price + tax
```トークナイザーは次のものを確認します。```text
NAME "total"
EQUAL "="
NAME "price"
PLUS "+"
NAME "tax"
```従来の Python キーワードには次のような単語が含まれます。```text
def
class
if
else
while
for
try
except
return
yield
import
from
with
lambda
```語彙レベルでは、これらは名前の形をしたシーケンスです。トークナイザーまたはパーサーは、文法のニーズに応じてそれらを分類できます。

最新の Python にもソフト キーワードがあります。ソフト キーワードは、文法の特定の位置でのみキーワードのように機能します。

例には、パターン マッチングで使用される単語が含まれます。```python
match value:
    case 0:
        pass

matchそしてcase文法が許可する他のコンテキストでも通常の名前として使用できます。

これが、トークン化と解析が連携する必要がある理由の 1 つです。すべての出現を永続的に変換するトークナイザーmatchハード キーワード トークンに組み込むと、次のようなコンテキストで有効なコードが拒否されます。matchは名前だけです。

実際的なルール:```text hard keyword: reserved everywhere soft keyword: special only in selected grammar positions name: ordinary identifier


Python 識別子には多くの Unicode 文字が含まれる場合があります。

例:```python
π = 3.14159
面积 = 42
```トークナイザーは、Python の識別子の規則に従って、識別子の開始文字と継続文字を認識する必要があります。これにより、Python ソース コードに広範な Unicode サポートが与えられます。

ただし、識別子は言語規則に従って正規化され、チェックされます。すべての Unicode 文字が名前に使用できるわけではなく、見た目が似ている文字の中には区別できるものもあります。

内部的な観点から見ると、識別子の処理には以下が必要です。```text
Unicode-aware character classification
identifier start validation
identifier continuation validation
normalization rules
error reporting for invalid characters
```これにより、Python のトークン化は ASCII のみの言語のトークナイザーよりも複雑になります。

## 18.10 数値リテラル

トークナイザーは、パーサーが式を構築する前に数値リテラルを認識します。

例:```python
123
0b1010
0o755
0xff
1_000_000
3.14
10.
.5
1e9
1.2e-3
3j
```これらは数値トークンになります。

トークナイザーは語彙形式を検証する必要があります。```text
base prefixes
digits allowed in each base
underscore placement
decimal points
exponents
imaginary suffix
```一部の無効な数値はトークン化中に失敗します。```python
0b102
1__2
```トークナイザーは任意の算術を評価しません。リテラルのトークンのみを認識します。後のコンパイル段階では、トークン テキストが対応する Python オブジェクトに変換されます。

例:```python
x = 1 + 2
```トークン化では次のものが表示されます。```text
NAME     "x"
EQUAL    "="
NUMBER   "1"
PLUS     "+"
NUMBER   "2"
```という事実`1 + 2`に折り畳むことができます`3`トークン化ではなく、後のコンパイラ最適化に属します。

## 18.11 文字列リテラル

文字列のトークン化は、数値のトークン化よりも複雑です。

Python は以下をサポートします。```python
"hello"
'hello'
"""hello"""
'''hello'''
r"\n"
b"bytes"
f"value={x}"
fr"path={name}\n"
```トークナイザーは以下を識別する必要があります。```text
string prefixes
quote style
single-line or triple-quoted form
raw strings
bytes strings
f-strings
escape sequences
line continuation rules
string termination
```通常の文字列トークンは 1 つの字句単位として認識されます。```python
x = "hello"
```トークンストリーム:```text
NAME    "x"
EQUAL   "="
STRING  "\"hello\""
```三重引用符で囲まれた文字列は物理的な行にまたがることがあります。```python
text = """
line 1
line 2
"""
```トークナイザーは、一致する三重引用符が見つかるまでスキャンを続ける必要があります。

終了していない文字列はトークナイザー エラーです。```python
x = "missing end
```トークナイザーは有効なトークン ストリームを生成できないため、パーサーは終端されていない文字列から意味のある文法を復元できません。

## 18.12 F ストリング

F 文字列は、リテラル文字列コンテンツと埋め込まれた Python 式の両方を含むため、特別です。

例:```python
name = "Ada"
text = f"hello {name.upper()}"
```文字列内のこの部分はリテラル テキストです。```text
hello 
```この部分は Python 式の構文です。```python
name.upper()
```この入れ子構造を処理するには、トークナイザーとパーサーが連携する必要があります。

概念的には:```text
enter f-string mode
scan literal characters
when "{" starts expression:
    tokenize embedded Python expression
    parse embedded expression
return to f-string literal scanning
finish at closing quote
```ネストされた式の処理により、f-string は通常の文字列リテラルよりもはるかに豊富になります。これらは、後でテキストが置換される単なる文字列トークンではありません。これらには、式ノードに解析する必要がある構文が含まれています。

## 18.13 演算子と区切り文字

Python の演算子と区切り文字には、単一文字形式と複数文字形式が含まれます。

例:```text
+   -   *   /   //   %   **
=   ==  !=  <   <=   >   >=
:=  ->  @   @=
(   )   [   ]   {   }
,   :   .   ;   ...
```トークナイザーは通常、最長一致動作を適用します。

たとえば、本を読んでいるとき、`**=`、ではなく 1 つの権限割り当て演算子トークンを生成する必要があります。`*`、`*`、 そして`=`。

単純化されたロジック:```text
if next characters form "**=":
    emit DOUBLESTAR_EQUAL
else if next characters form "**":
    emit DOUBLESTAR
else if next character is "*":
    emit STAR
```このルールはトークナイザーでは一般的です。これにより、パーサーは複数文字の演算子を小さな部分から再構築する必要がなくなります。

## 18.14 エラートークンと語彙エラー

解析する前にいくつかのエラーが表示されます。

例:```python
x = "unterminated
if x:
  a = 1
 b = 2
x = 0b123
```これらは語彙またはインデントのエラーです

トークナイザーは有用な診断に必要な十分な情報を報告する必要があります。```text
filename
line number
column offset
source line
error type
error message
```トークン化段階の一般的なエラーは次のとおりです

|エラー |原因 |
| ------------------ | ------------------------------------------ |
|`SyntaxError`|無効な語彙構造またはトークン シーケンス |
|`IndentationError`|無効なインデント レベル |
|`TabError`|タブとスペースによるあいまいなインデント |
|`TokenError`|不完全な入力によるパブリック トークナイザー エラー |

すべてではありません`SyntaxError`トークン化に由来します多くは解析から来ていますしかしトークナイザーには有効なトークン ストリームの存在を妨げるエラーが存在します

## 18.15 ファイルの終わりと合成デデント

ファイルの終わりでCPython は開いているインデント ブロックをすべて閉じる必要があります

:```python
if x:
    if y:
        run()
```ソースは 2 つのインデント レベルがアクティブなまま終了しますトークナイザーは合成メッセージを出力します`DEDENT`前のトークン`ENDMARKER`。

概念的には:```text
NAME      "if"
NAME      "x"
COLON     ":"
NEWLINE
INDENT
NAME      "if"
NAME      "y"
COLON     ":"
NEWLINE
INDENT
NAME      "run"
LPAR
RPAR
NEWLINE
DEDENT
DEDENT
ENDMARKER
```これにより明示的な右中括弧がない場合でもパーサーはブロックの終わりを認識できるようになります

したがってトークナイザーはソース ファイル内に直接的な文字を持たないトークンを作成します。`INDENT`、`DEDENT`、 そして`ENDMARKER`構造トークンです

## 18.16 トークナイザーの状態

トークナイザーはステートフルです

次のことを覚えておく必要があります。```text
current input pointer
current line
current column
current indentation stack
current nesting level
whether scanning begins a line
whether inside a string
whether inside an f-string expression
whether an encoding was detected
whether interactive mode is active
pending INDENT or DEDENT tokens
error state
```意味はレイアウトとコンテキストに依存するためステートレス スキャナーは Python には不十分です

:```python
x = [
    1,
    2,
]
```後の改行`1,`括弧内に表示されます論理的になってはいけません`NEWLINE`。

:```python
if x:
    y = 1
z = 2
```前の先頭の空白`z`を引き起こす`DEDENT`。

これらの決定には記憶された状態が必要です

## 18.17 インタラクティブなトークン化

対話型入力には特殊なケースがあります

REPL ではCPython は入力が完了したかどうかを判断する必要があることがよくあります

:```python
>>> if x:
...
```ブロック本体が必要なためこれは不完全です

:```python
>>> x = (1 +
...
```括弧で囲まれた式が開いたままであるためこれは不完全です

トークナイザーとパーサーは協力して別の行をリクエストするかエラーを発生させるかを決定しますしたがって対話モードはファイル モードとは異なりますファイル内の入力の終わりは真の EOF を意味します REPL での入力終了は、「さらにテキストを要求することを意味する場合があります

## 18.18 公開`tokenize`モジュール

標準ライブラリは次の方法でトークン化を公開します。`tokenize`。

:```python
from io import BytesIO
import tokenize

src = b"x = 1 + 2\n"

for tok in tokenize.tokenize(BytesIO(src).readline):
    print(tok)
```出力は次のような形になります。```text
TokenInfo(type=ENCODING, string='utf-8', ...)
TokenInfo(type=NAME, string='x', ...)
TokenInfo(type=OP, string='=', ...)
TokenInfo(type=NUMBER, string='1', ...)
TokenInfo(type=OP, string='+', ...)
TokenInfo(type=NUMBER, string='2', ...)
TokenInfo(type=NEWLINE, string='\n', ...)
TokenInfo(type=ENDMARKER, string='', ...)
```パブリック トークナイザーは次の場合に役立ちます。```text
formatters
linters
code generators
syntax highlighters
refactoring tools
documentation tools
source-to-source transforms
````tokenize`ドキュメントではこれを Python ソースの字句スキャナーとして説明しておりコメントをトークンとして返すためプリティプリンターやカラーライザーに役立つと述べています ([Python ドキュメント][1])

## 18.19 C トークナイザーと Python トークナイザー

CPython には関連するトークナイザーの概念が 2 つあります

|コンポーネント |場所 |目的 |
| ----------------- | --------------------------------- | ----------------------------------- |
| C トークナイザー | CPython パーサー/コンパイラーの内部 |コンパイル中にパーサーにフィードを与える |
|`Lib/tokenize.py`|標準ライブラリ |トークン化を Python ツールに公開する |

これらは同一のインターフェイスではありません

C トークナイザーはCPython のコンパイラー パイプライン用に最適化されていますパーサーが必要とするものを生成します

Python トークナイザーはパブリック ツール インターフェイスですコメントを保持しエンコーディングを公開し豊富な値を返します`TokenInfo`オブジェクトであり外部消費者向けに設計されています

この違いはトークンが次からストリーミングされる理由を説明します。`tokenize`パーサーが無視する情報が含まれる可能性があります

## 18.20 トークン化の例の詳細

次のソースを考えてみましょう。```python
def area(r):
    pi = 3.14159
    return pi * r * r
```簡略化されたトークン ストリーム:```text
NAME       "def"
NAME       "area"
LPAR       "("
NAME       "r"
RPAR       ")"
COLON      ":"
NEWLINE    "\n"
INDENT     "    "
NAME       "pi"
EQUAL      "="
NUMBER     "3.14159"
NEWLINE    "\n"
NAME       "return"
NAME       "pi"
STAR       "*"
NAME       "r"
STAR       "*"
NAME       "r"
NEWLINE    "\n"
DEDENT     ""
ENDMARKER  ""
```重要な点:

1.`def`は語彙的には名前の形をしていますが文法的にはキーワードとして機能します
2. その後インデントが増えるため関数本体が開始されます。`NEWLINE`。
3.`3.14159`単一の数字トークンです
4.`return pi * r * r` 1 つの論理行です
5. 関数本体は合成によって終了します。`DEDENT`。
6. ファイルの終わりは次のとおりです`ENDMARKER`。

パーサーはこのストリームを受け取り関数定義スイート代入return ステートメントおよび式の文法規則と照合します

## 18.21 トークン化は完全なセマンティクスを理解しない

トークナイザーはこの名前が未定義であることを認識しません。```python
print(missing_name)
```この呼び出しが失敗することはわかりません。```python
1()
```このインポートが存在するかどうかはわかりません。```python
import does_not_exist
```トークンのみを発行します

セマンティック チェックは後で多くの場合実行時に行われます

トークン化は意図的に浅く行われていますプログラムの意味ではなく語彙形式を認識します

## 18.22 トークン化が重要な理由

トークン化は小さいように見えますが言語全体を形成します

以下を定義します。```text
how indentation becomes syntax
how source bytes become characters
how comments are ignored or preserved
how strings are delimited
how f-strings embed expressions
how operators are recognized
how logical lines are formed
how parser input is structured
```CPython の貢献者にとってトークナイザーのバグは構文診断ツール互換性セキュリティに影響を与える可能性があります字句を少し変更するとすべての Python ファイルの解析方法が変わる可能性があります

ツール作成者にとってトークン化は多くの場合作業するのに最適なレイヤーですコメント正確な間隔物理行演算子のスペルなどAST が破棄するソースレベルの情報が保存されます

## 18.23 最小限のメンタルモデル

このモデルを使用します。```text
The tokenizer reads decoded Python source.
It emits lexical tokens.
It tracks indentation, nesting, strings, and line boundaries.
It inserts structural tokens such as INDENT, DEDENT, and ENDMARKER.
It reports lexical errors before parsing.
The parser consumes tokens and builds syntax structure.
```これは生のソーステキストから文法への橋渡しです