☰
Python 源代码的分词器
2026/10/28 10:12:49 网站建设 项目流程

源代码的分词器, 它的作用是把源代码切成一个一个的小块。

源码: Lib/.py

这个模块为 源码提供了一个靠 来搞定的词法扫描器。在模块里面, 这个扫描器还会把那些注释也给当成标记扔出来。正因为这样, 它就能拿来给那种叫“美化打印器”的东西去用, 像什么为了弄个屏幕显示代码着色效果的那些工具, 也能拿来用了。

为了能简化标记流的处理这一情况, 所有的项目以及项目在返回时候的状态都是会打上那种通用的标记的。然后呢, 是可以通过项目在项目返回时候所产生的那个对象里的属性这个部分来获取到确切存在的标记类型信息的。

警告

请注意本模块中的函数被设计为仅能解析符合语法的 代码(当使用 解析代码时不会引发异常)。 在提供无效的 代码时本模块中函数的行为是 未定义 的并可能在任何时候发生改变。

对输入进行分词

主要的入口是一个。

.()

这个生成器需要接收一个参数, 对于该参数的类型, 它必须是一个可以被调用的对象, 而且, 还要能够提供出和文件对象的 方法所具备的相似接口情况来就是这样一个要求存在。每当进行调用操作的时候, 都需要让这一动作返回关于字节类型输入的一行数据内容, 如此这般, 才算满足条件。

生成器会产生含有这些成员的元组, 也就是令牌类型这种成员;然后是令牌字符串;再就是指定令牌在源代码里面开始的位置的行和列构成的二项元组;接下来又是指定令牌在源代码里面结束的行的和列所组成的二元组;最后还有发现令牌的那一行。

这里所传递过去的行指的是那个最后一个项里面的内容, 也就是说这个具体的行数才是真正的行数。返回的数据是 5 个元组, 这些元组的字段名分别是 type, start, end, line。

返回的结果中, 是存在有一个额外的属性和这个额外的属性的名字是的, 然后它里面包含了那个标记所代表的确切的操作符号的类型信息的。对于所有其余的其他类型的标记来说, 在结果里面的那个值也是等于命名元组的字段类型的那个值的。

到了版本号是3.1的时候, 发生了一些变更的地方, 其中就包括增加了对命名元组这个东西的支持。

到了 3.3 版本的时候, 发生了改动, 这次改动增添了对相关内容的支持。

根据 PEP 263 所规定的规则, 系统在读取文件的时候, 会去寻找 UTF-8 BOM, 或者通过其他的编码标记, 进而用来确定该文件的源头编码到底是哪一个。

.()

对读取的对象进行标记, 这个对象是针对字符串而不是字节进行操作。

和某个未明确说明的实体一样, 某个参数是一个返回单行输入的可调用对象。然而, 这里所指的某个特性希望某个结果返回一个类型为字符型字符串的对象, 而不是字节类型的序列。

最后得到的结果是一个用来产生具名元组对象的迭代器, 它和之前提到的那个东西是完全一样的, 而且它在操作的过程中, 根本就不会生成 标记。

模块里面所有的常量这些东西, 也会从这个地方导出。

它提供了另外一个函数, 这个函数的作用是来逆转标记化这个过程, 也就是让之前弄乱的东西重新恢复到以前的状态。这对那些工具来说是有用的, 因为需要这些工具去把那个脚本进行标记的操作, 然后再去修改一下那个标记后的流, 最后再把这个修改过以后的脚本给写回到原来的地方去。

.()

你需要把这个令牌转化成源代码<。必须要把结果返回成为由至少两个元素组成的序列, 其中包含的是标记的这个类型还有这个标记的字符串部分。对于那些出现在这个序列里面的其它多余元素, 系统会采取忽略的态度, 不予理会。

结果将保证会被进行分词, 使其与输入内容相匹配, 所以这种转换的过程是不会丢数据的, 并且能够确保在两个方向上来回反复处理。这项保证只适用于分词的类别以及分词具体的文本字符串, 是因为在不同分词之间的间隔, 也就是具体的列位置, 可能会出现变化。

它返回的是字节, 这些字节是使用标记来进行编码的, 而这正是第一个由那个输出出来的标记序列所对应的内容。如果在这个输入内容里面根本就没有包含那些编码好的令牌的话, 它将会返回一个字符串类型的结果。

需要先对它所标记的那个源文件的编码情况去做一下检测, 而它来实现这整个事情的函数现在也是已经能够提供使用的状态。

.()

这个函数是用来检测在需要对源文件进行解码的时候究竟应该使用什么样的一种编码方式的。它所要求的那个参数, 和之前所说的生成器在使用起来的时候是完全一样的那个样子的方式去操作的。

它最多会调用两次, 并且把所使用的编码以字符串的形式返回回来, 同时还会把它所读取进来的任何一行内容整理成一个列表给返回出去, 不过这里要说明白, 这些行不是从字节那种方式解码来的。

它借助存在于代码里面的 UTF-8 BOM 或者明确的编码声明来把编码格式给检测出来, 这一点是按照 PEP 263 的说明来做的。要是这两个条件都满足的话, 也就是 BOM 和编码声明都有, 但是它们之间互相不一致, 那就肯定会引发一个错误出来。

需要特别注意的是, 只要程序在文件开头找到了那个 BOM 标记, 就会直接返回 'utf-8-sig' 作为一个标准的编码格式供用户使用。

在编码这个内容没有被指定清楚的时候, 系统将会把那个被称为 'utf-8' 的默认编码给返回出来给用户。

通过使用, 来打开源文件: 它使用, 来检测文件编码。

.open()

使用由 检测到的编码,以只读模式打开一个文件。

.

当该文件中无论处在哪个位置, 只要存在尚未完结的情况, 或者可能存在被拆分成多行的这种表达式的时候, 就会激发相关操作, 比如说像下面这些情况一样:

"""Beginning of

docstring

或者:

[1,

2,

3

命令行用法

模块可以被看作是脚本, 这样就可以从命令行那边去执行了, 操作起来是特别简单的, 你只需要做一件事就行。

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

以下是可以被接受的选项。

-h, 双长横线接着写help。

显示出这个帮助信息, 然后退出。

-e, --exact

请使用确切的那一种类型来显示出那个令牌的名字。

如果.py已经被指定的话, 那么它的内容就会被进行标记处理。如果没有被指定, 则会在stdin上进行标记化处理。

Added in 3.15: 输出默认为彩色并且可以 。

例子

这是一个脚本改写器的例子, 这个示例把浮点数值给转成对象。

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'))"

指数的格式继承自平台的 C 库。

已知用例有 "e-007" (Windows) 和 "e-07" (非 Windows)。

由于我们只显示 12 个数位,且第 13 位的值不到 5,因此

输出的其余部分应当是不依赖于具体平台的。

>>> exec(s) #doctest: +ELLIPSIS

-3.21716034272e-0...7

使用 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: # 替换 NUMBER 词元

result.extend([

(NAME, 'Decimal'),

(OP, '('),

(STRING, repr(tokval)),

(OP, ')')

])

else:

result.append((toknum, tokval))

return untokenize(result).decode('utf-8')

这是一个来自于命令行方面的, 关于进行标记化的例子。相关的脚本如下所示:

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

大家在使用 选项这样做的情况下, 就都能够把确切的标记类型名称给显示出来。

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

有一个例子可以说明如何用编程的方式对文件进行标记, 在这种情况之下, 我们使用的是读取这个字符串, 而不是使用的字节。

import tokenize

with tokenize.open('hello.py') as f:

tokens = tokenize.generate_tokens(f.readline)

for token in tokens:

print(token)

或者我们可以直接读取那个字节数据。

import tokenize

with open('hello.py', 'rb') as f:

tokens = tokenize.tokenize(f.readline)

for token in tokens:

print(token)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询