tokenize
token: n. 象徵;標誌; adj. 作爲標誌的;
-ize: suff. 使成…狀態;使…化;
tokenize:標識化;標記化;
tokenize
提供了“對 Python 代碼使用的”詞彙掃描器,是用 Python 實現的。掃描器可以給 Python 代碼打上標記後返回,你可以看到每一個詞或者字符是什麼類型的。掃描器甚至將註釋也單獨標記,這樣某些需要對代碼進行特定風格展示的地方就很方便了。
爲了簡化標記流(token stream)的處理,所有的運算符(Operators)、分隔符(Delimiters) 和 Ellipsis
(不是英文,就是 Python 中的一個變量,和省略號一樣)都會被標記爲 OP(一個表示標識類型的常量)類型。具體的類型可以通過tokenize.tokenize()
返回的具名元祖對象的 .exact_type
屬性查看。
exact_type
是一個 @property
修飾的方法,所以只有調用時才精確的查看到底是什麼類型的文本,這樣就簡化了標記流的處理
標記的輸入
主要的入口是一個生成器:
tokenize.tokenize(readline)
生成器 tokenize()
需要一個參數:readline,它必須是一個可調用的對象,並且提供了與文件對象的 io.IOBase.readline()
相同的接口。每次調用這個函數,都應該返回一行字節類型的輸入
生成器會生成有5個元素的具名元組,內容是:
type
:標記類型string
:被標記的字符串start
:一個整數組成的 2-元組:(srow, scol)
,這個標記的開始位置的行和列。s:start;end
:一個整數組成的 2-元組:(erow, ecol)
,這個標記的結束爲止的行和列。e:end;line
:被標記的字符串所在的那一行,就是輸入的那一行的內容
返回的具名元組還有一個額外的屬性 exact_type
,標識了類型爲 OP 詞的確切操作類型。對於所有 OP 以外的標記,exact_type
的值等於 type
的值。
tokenize()
通過查找 UTF-8 BOM 或者編碼 cookie 來確認文件的源編碼。
tokenize.generate_tokens(readline)
將對 unicode 類型的字符串進行標記,而不是字節類型。
像 tokenize() 一樣,readline 參數需要可調用,並且返回輸入的一行,但是需要返回 str 對象,而不是 bytes。
返回的結果是一個迭代器,返回的具名元祖和 tokenize()
的完全一樣。只不過沒有 ENCODING(一種表示標識類型的常量)類型的標記。(tokenize()
第一個返回的就是 ENCODING 標記的內容)
ENCODING 和 OP 一樣是常量,還有很多,都是用來標記類型的,在 tokenize
庫裏直接用即可,是從 token
包裏直接導過來的。
還有一個函數提供反轉標記過程的功能。有些工具要標記化一個腳本、修改標記流、回寫修改後的腳本,這個函數就能派上用場了。
tokenize.untokenize(iterable)
把標記轉裝成 Python 源代碼(指用 Python 寫成的代碼)。可迭代對象 iterable 返回的序列中每一個對象至少要有兩個元素構成:標記類型和標記的字符串。其他的元素都會被忽略。
反轉生成的腳本會作爲一個單獨的字符串返回。
返回的是字節類型的,使用 ENCODING 標記的內容進行編碼,如果輸入中沒有這個標記的,那就返回 str 類型的。
tokenize()
需要查出源文件的編碼,它用於執行此操作的函數也是可用的:
tokenize.detect_encoding(readline)
detect_encoding() 函數用來檢測應該用於解碼 Pyhton 源文件的編碼。它需要一個參數 readline,和生成器 tokenize() 所需的相同
它最多會調用 readline
兩次,然後返回要使用的編碼(一個字符串)和它已讀入的每一行(不是從字節解碼的)組成的列表
它根據 PEP 263 中規定的方式從 UTF-8 BOM 或者編碼 cookie 中檢測編碼方式。如果 BOM 和 cookie 都存在但不一致,會拋出 SyntaxError
。如果找到 BOM,'utf-8-sig'
將作爲編碼返回。
如果沒有指定編碼,就返回默認的 'utf-8'
。
使用 open() 打開 Python 源文件:它使用 detect_encoding() 檢測文件編碼
tokenize.open(filename)
使用 detect_encoding() 檢測到的編碼通過只讀方式打開一個文件
異常:tokenize.TokenError
當一個文檔字符串或表達式可能被分割成多行,但在文件中的任何地方都沒能完成時拋出。
例如:
"""文檔字符串
開頭
或者
[
1,
2,
3
注意:未關閉的單引號字符串不會引發錯誤。它們會被標記爲 ERRORTOKEN(一種標記類型常量),然後是其內容的標記化。
命令行用法
tokenize
包可以從命令行以腳本的形式執行。
python -m tokenize [-e] [filename.py]
有以下可選參數
-h, --help
展示幫助信息
-e, --exact
使用確切的類型展示標識類型
如果 filename.py
指定,它裏面的內容就用作標記化,否則就在 stdin 獲取輸入。
示例
1、將浮點文字轉換爲 Decimal 對象的腳本重寫器
from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP
from io import BytesIO
def decistmt(s):
"""用 Decimal 替換語句字符串中的浮點數。
>>> from decimal import Decimal
>>> s = 'print(+21.3e-5*-.1234/81.7)'
>>> decistmt(s)
"print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))"
在不同的平臺,下面這句的結果可能不同。第一個是在 macOS,第二個是在 Win10。
>>> exec(s)
-3.21716034272e-07
-3.217160342717258e-07
在所有平臺上,Decimal 的輸出應該都是一致的。
>>> exec(decistmt(s))
-3.217160342717258261933904529E-7
"""
result = []
g = tokenize(BytesIO(s.encode('utf-8')).readline) # 標記化字符串
for toknum, tokval, _, _, _ in g:
if toknum == NUMBER and '.' in tokval: # 把數字類型的轉換後保存
result.extend([
(NAME, 'Decimal'),
(OP, '('),
(STRING, repr(tokval)),
(OP, ')')
])
else:
result.append((toknum, tokval))
return untokenize(result).decode('utf-8')
2、使用命令行的例子
腳本:
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 ''
3、以編程方式標記文件的例子
1、用 generate_tokens() 讀取 unicode 字符串而不是字節類型的。
import tokenize
with tokenize.open('hello.py') as f:
tokens = tokenize.generate_tokens(f.readline)
for token in tokens:
print(token)
結果如下,可見用 generate_tokens() 是得不到 ENCODING 的
TokenInfo(type=1 (NAME), string='def', start=(1, 0), end=(1, 3), line='def say_hello():\n')
TokenInfo(type=1 (NAME), string='say_hello', start=(1, 4), end=(1, 13), line='def say_hello():\n')
TokenInfo(type=54 (OP), string='(', start=(1, 13), end=(1, 14), line='def say_hello():\n')
TokenInfo(type=54 (OP), string=')', start=(1, 14), end=(1, 15), line='def say_hello():\n')
TokenInfo(type=54 (OP), string=':', start=(1, 15), end=(1, 16), line='def say_hello():\n')
TokenInfo(type=4 (NEWLINE), string='\n', start=(1, 16), end=(1, 17), line='def say_hello():\n')
TokenInfo(type=5 (INDENT), string=' ', start=(2, 0), end=(2, 4), line=' print("Hello, World!")\n')
TokenInfo(type=1 (NAME), string='print', start=(2, 4), end=(2, 9), line=' print("Hello, World!")\n')
TokenInfo(type=54 (OP), string='(', start=(2, 9), end=(2, 10), line=' print("Hello, World!")\n')
TokenInfo(type=3 (STRING), string='"Hello, World!"', start=(2, 10), end=(2, 25), line=' print("Hello, World!")\n')
TokenInfo(type=54 (OP), string=')', start=(2, 25), end=(2, 26), line=' print("Hello, World!")\n')
TokenInfo(type=4 (NEWLINE), string='\n', start=(2, 26), end=(2, 27), line=' print("Hello, World!")\n')
TokenInfo(type=61 (NL), string='\n', start=(3, 0), end=(3, 1), line='\n')
TokenInfo(type=6 (DEDENT), string='', start=(4, 0), end=(4, 0), line='say_hello()')
TokenInfo(type=1 (NAME), string='say_hello', start=(4, 0), end=(4, 9), line='say_hello()')
TokenInfo(type=54 (OP), string='(', start=(4, 9), end=(4, 10), line='say_hello()')
TokenInfo(type=54 (OP), string=')', start=(4, 10), end=(4, 11), line='say_hello()')
TokenInfo(type=4 (NEWLINE), string='', start=(4, 11), end=(4, 12), line='')
TokenInfo(type=0 (ENDMARKER), string='', start=(5, 0), end=(5, 0), line='')
2、或者直接使用 tokenize() 讀取字節類型的:
import tokenize
with open('hello.py', 'rb') as f:
tokens = tokenize.tokenize(f.readline)
for token in tokens:
print(token)
標記化的結果與 例2 中一致,只是多了一些信息。
附表
所有的標記類型
Operators
以下形符屬於運算符:
+ - * ** / // % @
<< >> & | ^ ~ :=
< > <= >= == !=
Delimiters
以下形符在語法中歸類爲分隔符:
( ) [ ] { }
, : . ; @ = ->
+= -= *= /= //= %= @=
&= |= ^= >>= <<= **=
句點也可出現於浮點數和虛數字面值中。連續三個句點有表示一個省略符的特殊含義。以上列表的後半部分爲增強賦值操作符,在詞法中作爲分隔符,但也起到運算作用。
以下可打印 ASCII 字符作爲其他形符的組成部分時具有特殊含義,或是對詞法分析器有重要意義:
' " # \