tokenize --- توکن‌ساز (Tokenizer) برای کد منبع پایتون

کد منبع: Lib/tokenize.py


ماژول tokenize یک پویشگر واژگانی برای کد منبع پایتون فراهم می‌کند که با پایتون پیاده‌سازی شده است. پویشگر این ماژول کامنت‌ها را نیز به‌صورت توکن برمی‌گرداند و به همین دلیل برای پیاده‌سازی «زیبانویس»ها، از جمله رنگ‌آمیزکننده‌های نمایش روی صفحه، مفید است.

برای ساده‌سازی مدیریت جریان توکن‌ها، تمام توکن‌های عملگر و جداکننده و همچنین Ellipsis با استفاده از نوع توکن عام OP بازگردانده می‌شوند. ویژگی TokenInfo.exact_type نوع دقیق توکن را می‌دهد.

هشدار

توجه داشته باشید که توابع این ماژول فقط برای تجزیه کد پایتون معتبر از نظر سینتکسی طراحی شده‌اند (کدی که هنگام تجزیه با استفاده از ast.parse() استثنایی پرتاب نمی‌کند). رفتار توابع این ماژول در صورت ارائه کد پایتون نامعتبر تعریف‌نشده است و ممکن است در هر زمانی تغییر کند.

توکن‌بندی ورودی

نقطه ورود اصلی یک تولیدگر است:

tokenize.tokenize(readline)

تولیدگر tokenize() به یک آرگومان، readline، نیاز دارد که باید یک شیء فراخوانی‌پذیر باشد و همان رابط متد io.IOBase.readline() اشیای پرونده را فراهم کند. هر فراخوانی این تابع باید یک سطر از ورودی را به‌صورت بایت برگرداند.

این تولیدگر اشیای TokenInfo را تولید می‌کند.

تغییر یافته در نسخه‌ی 3.1: تغییر کرد تا به‌جای تاپل‌های ساده، اشیای TokenInfo را بازگرداند (yield کند).

tokenize() کدگذاری منبع پرونده را با جست‌وجوی BOM UTF-8 یا کوکی کدگذاری، طبق PEP 263 تعیین می‌کند.

tokenize.generate_tokens(readline)

یک منبع را با خواندن رشته‌های یونیکد به‌جای بایت‌ها توکن‌بندی می‌کند.

مانند tokenize()، آرگومان readline یک شیء فراخوانی‌پذیر است که یک سطر ورودی را برمی‌گرداند. با این حال، generate_tokens() انتظار دارد که readline به‌جای bytes یک شیء str برگرداند.

نتیجه یک پیمایش‌گر است که اشیای TokenInfo را تولید می‌کند، دقیقاً مانند tokenize(). این تابع یک توکن ENCODING تولید نمی‌کند.

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

یک تاپل نام‌دار که یک توکن را توصیف می‌کند. این شیء دارای پنج فیلد و یک ویژگی exact_type اضافی است.

اضافه شده در نسخه‌ی 3.1.

type

یک عدد صحیح که نوع توکن را مشخص می‌کند، همان‌طور که توسط ثابت‌های موجود در ماژول token مانند NAME و STRING تعریف شده است. عملگرها و جداکننده‌ها دارای نوع OP هستند؛ از exact_type برای نوع توکن خاص آن‌ها استفاده کنید.

string

متن توکن به صورت str. برای توکن‌های STRING، این شامل نقل‌قول‌ها، هر پیشوند و دنباله‌های گریز می‌شود، بدون اینکه آن‌ها را ارزیابی کند.

برای یک توکن ENCODING، این نام کدگذاری است. توکن‌هایی که بدون متن منبع متناظر درج می‌شوند می‌توانند یک رشته‌ی خالی داشته باشند. در توکن‌های FSTRING_MIDDLE و TSTRING_MIDDLE، آکولادهای دوتایی با آکولادهای تکی جایگزین می‌شوند.

start

یک ۲-تایی (srow, scol) از اعداد صحیح که موقعیت شروع فراگیر توکن را در منبع مشخص می‌کند. ردیف‌ها از ۱ و ستون‌ها از 0 شماره‌گذاری می‌شوند. ستون‌ها نقطه‌کدهای یونیکد را می‌شمارند؛ یک تب به عنوان یک ستون محسوب می‌شود، بدون اینکه آن را به یک توقف تب گسترش دهد.

برای یک توکن ENCODING، این مقدار (0, 0) است.

end

یک ۲-تاپل (erow, ecol) از اعداد صحیح که موقعیت پایانی انحصاری توکن را مشخص می‌کند. ردیف‌ها از ۱ و ستون‌ها از ۰ شماره‌گذاری می‌شوند. برای توکنی که چندین سطر را در بر می‌گیرد، erow ردیف نهایی و ecol ستون واقع در آن ردیف است.

برای مثال، نام spam در ابتدای سطر اول دارای start برابر با (1, 0) و end برابر با (1, 4) است.

برای یک توکن ENCODING، این مقدار برابر با (0, 0) است. یک توکن NEWLINE یا NL که در انتهای ورودی بدون نویسه‌ی جدید (newline) نهایی درج شده باشد، می‌تواند ستون پایانی را یک واحد فراتر از متن منبع داشته باشد.

line

یک str حاوی سطر منبع کاملی که توکن در آن پیدا شده است، شامل متن پیرامون و پایان‌دهنده‌ی سطر (در صورت وجود). برای توکنی که چندین سطر را در بر می‌گیرد، این مورد می‌تواند شامل چندین سطر منبع باشد. این مقدار برای توکن‌های ENCODING و ENDMARKER خالی است.

exact_type

یک عدد صحیح که نوع عملگر خاص را برای توکن‌های OP، همان‌طور که توسط ثابت‌های موجود در token تعریف شده است، شناسایی می‌کند. برای سایر انواع توکن‌ها، این مقدار برابر با type است. برای مثال، یک توکن + دارای type برابر با OP و exact_type برابر با PLUS است.

اضافه شده در نسخه‌ی 3.3.

تمام ثابت‌های ماژول token نیز از tokenize اکسپورت می‌شوند.

تابع دیگری برای معکوس کردن فرآیند توکن‌بندی (tokenization) ارائه شده است. این تابع برای ساخت ابزارهایی مفید است که یک اسکریپت را توکن‌بندی می‌کنند، جریان توکن را تغییر می‌دهند و اسکریپت اصلاح‌شده را بازمی‌نویسند.

tokenize.untokenize(iterable)

توکن‌ها را دوباره به کد منبع پایتون تبدیل می‌کند. iterable باید دنباله‌هایی با دست‌کم دو عنصر برگرداند: نوع توکن و رشته‌ی توکن. سایر عناصر دنباله نادیده گرفته می‌شوند.

تضمین می‌شود که نتیجه دوباره توکن‌بندی شود (tokenize back) تا با ورودی مطابقت داشته باشد، به‌گونه‌ای که تبدیل بدون از دست رفتن داده باشد و رفت‌وبرگشت‌ها تضمین شوند. این تضمین فقط به نوع توکن و رشته‌ی توکن اعمال می‌شود، زیرا فاصله‌گذاری بین توکن‌ها (موقعیت‌های ستونی) ممکن است تغییر کند.

این تابع بایت‌هایی را برمی‌گرداند که با استفاده از توکن ENCODING کدگذاری شده‌اند؛ این توکن نخستین دنباله‌ی توکنی است که توسط tokenize() خروجی داده می‌شود. اگر توکن کدگذاری در ورودی وجود نداشته باشد، در عوض یک str برمی‌گرداند.

tokenize() باید کدگذاری پرونده‌های منبعی را که توکن‌بندی (tokenize) می‌کند، تشخیص دهد. تابعی که برای این کار استفاده می‌کند، در دسترس است:

tokenize.detect_encoding(readline)

تابع detect_encoding() برای تشخیص کدگذاری‌ای استفاده می‌شود که باید برای کدگشایی یک پرونده منبع پایتون به کار رود. این تابع، همانند تولیدگر tokenize()، به یک آرگومان، readline، نیاز دارد.

readline را حداکثر دو بار فراخوانی می‌کند و کدگذاری استفاده‌شده (به‌صورت یک رشته) و فهرستی از سطرهایی را که خوانده است (که از بایت‌ها کدگشایی نشده‌اند) برمی‌گرداند.

کدگذاری بر اساس وجود نشانگر ترتیب بایت (BOM) مربوط به UTF-8 یا کوکی کدگذاری (encoding cookie)، همان‌طور که در PEP 263 مشخص شده است، تشخیص داده می‌شود. اگر هر دو BOM و کوکی موجود باشند، اما با یکدیگر مغایرت داشته باشند، یک SyntaxError پرتاب خواهد شد. توجه داشته باشید که اگر BOM یافت شود، 'utf-8-sig' به‌عنوان کدگذاری برگردانده خواهد شد.

اگر هیچ کدگذاری‌ای مشخص نشده باشد، مقدار پیش‌فرض 'utf-8' برگردانده خواهد شد.

برای باز کردن پرونده‌های منبع پایتون، از open() استفاده کنید: این تابع از detect_encoding() برای تشخیص کدگذاری پرونده استفاده می‌کند.

tokenize.open(filename)

باز کردن یک پرونده در حالت فقط‌خواندنی با استفاده از کدگذاری تشخیص‌داده‌شده توسط detect_encoding().

اضافه شده در نسخه‌ی 3.2.

exception tokenize.TokenError

هنگامی پرتاب می‌شود که یا یک رشته مستندسازی یا عبارتی که ممکن است در چند سطر تقسیم شود، در هیچ جای پرونده تکمیل نشده باشد، برای مثال:

"""Beginning of
docstring

یا:

[1,
 2,
 3

استفاده از خط فرمان

اضافه شده در نسخه‌ی 3.3.

ماژول tokenize می‌تواند به‌عنوان یک اسکریپت از خط فرمان اجرا شود. این کار به سادگی زیر است:

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

گزینه‌های زیر پذیرفته می‌شوند:

-h, --help

این پیام راهنما را نمایش می‌دهد و خارج می‌شود

-e, --exact

نمایش نام‌های توکن با استفاده از نوع دقیق

اگر filename.py مشخص شده باشد، محتوای آن به stdout توکن‌سازی می‌شود. در غیر این صورت، توکن‌سازی روی stdin انجام می‌شود.

مثال‌ها

مثالی از یک بازنویس اسکریپت که مقادیر لفظی float را به اشیای 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')

مثالی از توکن‌بندی (tokenizing) از خط فرمان. اسکریپت:

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

say_hello()

به خروجی زیر توکن‌بندی خواهد شد که در آن ستون اول، بازه‌ی مختصات سطر/ستون محل یافت شدن توکن، ستون دوم نام توکن و ستون آخر مقدار توکن (در صورت وجود) است.

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

می‌توان نام‌های دقیق نوع توکن را با استفاده از گزینه‌ی -e نمایش داد:

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

نمونه‌ای از توکن‌بندی یک پرونده به‌صورت برنامه‌ای، خواندن رشته‌های یونیکد به‌جای بایت‌ها با generate_tokens():

import tokenize

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

یا خواندن مستقیم بایت‌ها با tokenize():

import tokenize

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