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

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


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

برای ساده‌سازی مدیریت جریان توکن، تمام توکن‌های عملگر و جداکننده و Ellipsis با استفاده از نوع توکن عام OP برگردانده می‌شوند. نوع دقیق را می‌توان با بررسی ویژگی exact_type روی named tuple برگشتی از tokenize.tokenize() تعیین کرد.

هشدار

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

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

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

tokenize.tokenize(readline)

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

این تولیدگر، تاپل‌های ۵تایی را با این اعضا تولید می‌کند: نوع توکن؛ رشته‌ی توکن؛ یک تاپل ۲تایی (srow, scol) از اعداد صحیح که ردیف و ستون آغاز توکن در منبع را مشخص می‌کند؛ یک تاپل ۲تایی (erow, ecol) از اعداد صحیح که ردیف و ستون پایان توکن در منبع را مشخص می‌کند؛ و سطری که توکن در آن یافت شد. سطر ارسال‌شده (آخرین آیتم تاپل) خط فیزیکی است. این تاپل ۵تایی به‌صورت یک named tuple با نام فیلدها: type string start end line بازگشت داده می‌شود.

named tuple برگردانده‌شده یک ویژگی اضافی به نام exact_type دارد که شامل نوع دقیق عملگر برای توکن‌های OP است. برای همه‌ی انواع دیگر توکن، exact_type برابر با فیلد type در named tuple است.

تغییر یافته در نسخه‌ی 3.1: پشتیبانی از تاپل‌های نام‌دار (named tuples) اضافه شد.

تغییر یافته در نسخه‌ی 3.3: پشتیبانی از exact_type افزوده شد.

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

tokenize.generate_tokens(readline)

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

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

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

تمام ثابت‌های ماژول 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)