tokenize
--- 對 Python 代碼使用的標(biāo)記解析器?
源碼: Lib/tokenize.py
tokenize
模塊為 Python 源代碼提供了一個詞法掃描器,用 Python 實(shí)現(xiàn)。該模塊中的掃描器也將注釋作為標(biāo)記返回,這使得它對于實(shí)現(xiàn)“漂亮的輸出器”非常有用,包括用于屏幕顯示的著色器。
為了簡化標(biāo)記流的處理,所有的 運(yùn)算符 和 定界符 以及 Ellipsis
返回時都會打上通用的 OP
標(biāo)記。 可以通過 tokenize.tokenize()
返回的 named tuple 對象的 exact_type
屬性來獲得確切的標(biāo)記類型。
對輸入進(jìn)行解析標(biāo)記?
主要的入口是一個 generator:
- tokenize.tokenize(readline)?
生成器
tokenize()
需要一個 readline 參數(shù),這個參數(shù)必須是一個可調(diào)用對象,且能提供與文件對象的io.IOBase.readline()
方法相同的接口。每次調(diào)用這個函數(shù)都要 返回字節(jié)類型輸入的一行數(shù)據(jù)。生成器產(chǎn)生 5 個具有這些成員的元組:令牌類型;令牌字符串;指定令牌在源中開始的行和列的 2 元組
(srow, scol)
;指定令牌在源中結(jié)束的行和列的 2 元組(erow, ecol)
;以及發(fā)現(xiàn)令牌的行。所傳遞的行(最后一個元組項)是 實(shí)際的 行。 5 個元組以 named tuple 的形式返回,字段名是:type string start end line
。返回的 named tuple 有一個額外的屬性,名為
exact_type
,包含了OP
標(biāo)記的確切操作符類型。 對于所有其他標(biāo)記類型,exact_type
等于命名元組的type
字段。在 3.1 版更改: 增加了對 named tuple 的支持。
在 3.3 版更改: 添加了對
exact_type
的支持。根據(jù):pep:263 ,
tokenize()
通過尋找 UTF-8 BOM 或編碼 cookie 來確定文件的源編碼。
- tokenize.generate_tokens(readline)?
對讀取 unicode 字符串而不是字節(jié)的源進(jìn)行標(biāo)記。
和
tokenize()
一樣, readline 參數(shù)是一個返回單行輸入的可調(diào)用參數(shù)。然而,generate_tokens()
希望 readline 返回一個 str 對象而不是字節(jié)。其結(jié)果是一個產(chǎn)生具名元組的的迭代器,與
tokenize()
完全一樣。 它不會產(chǎn)生ENCODING
標(biāo)記。
所有來自 token
模塊的常量也可從 tokenize
導(dǎo)出。
提供了另一個函數(shù)來逆轉(zhuǎn)標(biāo)記化過程。這對于創(chuàng)建對腳本進(jìn)行標(biāo)記、修改標(biāo)記流并寫回修改后腳本的工具很有用。
- tokenize.untokenize(iterable)?
將令牌轉(zhuǎn)換為 Python 源代碼。 iterable 必須返回至少有兩個元素的序列,即令牌類型和令牌字符串。任何額外的序列元素都會被忽略。
重構(gòu)的腳本以單個字符串的形式返回。 結(jié)果被保證為標(biāo)記回與輸入相匹配,因此轉(zhuǎn)換是無損的,并保證來回操作。 該保證只適用于標(biāo)記類型和標(biāo)記字符串,因?yàn)闃?biāo)記之間的間距(列位置)可能會改變。
它返回字節(jié),使用
ENCODING
標(biāo)記進(jìn)行編碼,這是由tokenize()
輸出的第一個標(biāo)記序列。如果輸入中沒有編碼令牌,它將返回一個字符串。
tokenize()
需要檢測它所標(biāo)記源文件的編碼。它用來做這件事的函數(shù)是可用的:
- tokenize.detect_encoding(readline)?
detect_encoding()
函數(shù)用于檢測解碼 Python 源文件時應(yīng)使用的編碼。它需要一個參數(shù), readline ,與tokenize()
生成器的使用方式相同。它最多調(diào)用 readline 兩次,并返回所使用的編碼(作為一個字符串)和它所讀入的任何行(不是從字節(jié)解碼的)的 list 。
它從 UTF-8 BOM 或編碼 cookie 的存在中檢測編碼格式,如 PEP 263 所指明的。 如果 BOM 和 cookie 都存在,但不一致,將會引發(fā)
SyntaxError
。 請注意,如果找到 BOM ,將返回'utf-8-sig'
作為編碼格式。如果沒有指定編碼,那么將返回默認(rèn)的
'utf-8'
編碼.使用
open()
來打開 Python 源文件:它使用detect_encoding()
來檢測文件編碼。
- tokenize.open(filename)?
使用由
detect_encoding()
檢測到的編碼,以只讀模式打開一個文件。3.2 新版功能.
- exception tokenize.TokenError?
當(dāng)文件中任何地方?jīng)]有完成 docstring 或可能被分割成幾行的表達(dá)式時觸發(fā),例如:
"""Beginning of docstring
或者:
[1, 2, 3
注意,未封閉的單引號字符串不會導(dǎo)致錯誤發(fā)生。它們被標(biāo)記為 ERRORTOKEN
,然后是其內(nèi)容的標(biāo)記化。
命令行用法?
3.3 新版功能.
tokenize
模塊可以作為一個腳本從命令行執(zhí)行。這很簡單:
python -m tokenize [-e] [filename.py]
可以接受以下選項:
- -h, --help?
顯示此幫助信息并退出
- -e, --exact?
使用確切的類型顯示令牌名稱
如果 filename.py
被指定,其內(nèi)容會被標(biāo)記到 stdout 。否則,標(biāo)記化將在 stdin 上執(zhí)行。
例子?
腳本改寫器的例子,它將 float 文本轉(zhuǎn)換為 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')
從命令行進(jìn)行標(biāo)記化的例子。 腳本:
def say_hello():
print("Hello, World!")
say_hello()
將被標(biāo)記為以下輸出,其中第一列是發(fā)現(xiàn)標(biāo)記的行 / 列坐標(biāo)范圍,第二列是標(biāo)記的名稱,最后一列是標(biāo)記的值(如果有)。
$ 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
選項來顯示確切的標(biāo)記類型名稱。
$ 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 ''
以編程方式對文件進(jìn)行標(biāo)記的例子,用 generate_tokens()
讀取 unicode 字符串而不是字節(jié):
import tokenize
with tokenize.open('hello.py') as f:
tokens = tokenize.generate_tokens(f.readline)
for token in tokens:
print(token)
或者通過 tokenize()
直接讀取字節(jié)數(shù)據(jù):
import tokenize
with open('hello.py', 'rb') as f:
tokens = tokenize.tokenize(f.readline)
for token in tokens:
print(token)