☰
Python:词法分析(行结构与显式、隐式行拼接)
2026/10/27 9:24:20 网站建设 项目流程

相关阅读

Pythonhttps://blog.csdn.net/weixin_45791458/category_12403403.html?spm=1001.2014.3001.5482


1、逻辑结构

一个Python程序由许多逻辑行组成,字面意义上的一行指的是末尾有换行符(\n),但在不同的情况下,行末尾的换行符(\n)可能有不同的意义。

2、逻辑行

逻辑行的结尾的换行符(\n)被解析为NEWLINE标记。一般来说,一条语句不能跨越逻辑行而存在(除非是复合语句,如if复合语句可以由多个逻辑行组成),但一个逻辑行可能有多条语句,如使用分号分隔语句。

print(1) # 逻辑行 if (a): # 逻辑行 print(1) # 逻辑行 else: # 逻辑行 print(2) # 逻辑行 print(3);print(4) # 逻辑行

如果是交互式Python解释:对于简单语句来说,逻辑行末尾的换行符表示将文本提供给解释器进行执行;对于复合语句来说,最后一个逻辑行末尾的换行符表示将文本提供给解释器进行执行。

3、物理行

根据显式或隐式行拼接规则,若干个物理行可以组成逻辑行,物理行的结尾的换行符(\n)可以被解析为NEWLINE标记(如果这个物理行是一个逻辑行中的最后一个物理行),或者NL标记(如果这个物理行不是一个逻辑行中的最后一个物理行),或者在进行显式行拼接时与反斜杠(\)一起被删除。

total = (1 + 2 + 3 + # 物理行 # 逻辑行 4 + 5 + 6) # 物理行

如果是交互式Python解释:对于简单语句来说,物理行末尾的换行符表示将文本提供给解释器进行编译,如果这个物理行是一个逻辑行中的最后一个物理行,还会执行。

4、注释

注释是一种特殊的逻辑行,对于单行是完全的注释,它末尾的换行符(\n)会被解析为NL标记。而一个位于逻辑行末尾的注释,它末尾的换行符(\n)则会被解释为NEWLINE标记。但无论是哪种解释,注释代表着从#到换行符(\n)之间的内容(不包括换行符(\n))会被解析为一个整体,即注释标记,而对程序无任何影响。

5、显式行拼接

两个及两个以上的物理行可用反斜杠(\)显式拼接为一个逻辑行,规则如下:除了注释中的反斜杠(\)加换行符(\n)结尾外(因为这时的反斜杠(\)已经被当做了注释,这也代表着注释无论如何不会影响到下一行),物理行将与从上到下一次拼接直到一个逻辑行,并删除末尾的反斜杠(\)及其后的换行符(\n),当使用反斜杠(\)时,末尾不允许有注释,因为这样反斜杠(\)后面就没有换行符(\n)也就无法行拼接了。需要注意的是,显式行拼接无法拼接除字符串字面量外的标记,而后面谈到的的隐式拼接无法拼接任何标记,标记即一个解析的整体,例如:函数print是一个标记,字符串"aaa"是一个标记,而1 + 1包含三个标记,因为其中不属于行首的空格、制表符等空白符不会被解析为标记,而行首的空格、制表符被解析为缩进标记。

下面来看几个例子:

if 1900 < year < 2100 and 1 <= month <= 12 \ and 1 <= day <= 31 and 0 <= hour < 24 \ and 0 <= minute < 60 and 0 <= second < 60: # 这里的注释是允许的,而上两行末尾再加注释是不允许的,因为这样\后面就没有换行符了 return 1 #正确的显式行拼接,因为并没有分割任何标记 a = 1 + \ 2 #错误的显式行拼接,因为分割了标记,语法分析时会产生错误解析 pr\ int("hello world!") #正确的显式行拼接,虽然字符串"aaa"是一个标记,但这是唯一的例外 a = "aa\ a"

如何更加直观的验证上面的这些分析有什么方法吗?当然有,tokenize包可以帮助我们解析一串代码,下面给出一段测试程序。

import tokenize from io import BytesIO code = """The code you are testing""" # 将代码字符串转换为字节流 code_bytes = code.encode('utf-8') code_stream = BytesIO(code_bytes) # 使用tokenize.tokenize生成tokens tokens = tokenize.tokenize(code_stream.readline) # 遍历tokens并打印信息 for token in tokens: print(token)

根据自己的需要,你可以将任何一段程序粘贴进上面的相应位置进行验证,使用三引号(""")的原因是它能保留跨行字符串而不使用反斜杠(\)。这很重要,因为如果使用双引号("),则无法测试下一节没有反斜杠(\)的隐式行连接,因为这代表着没有反斜杠(\)的字符串被分成了两行,但上面说过隐式行连接无法拼接任何标记,所以甚至连测试程序都无法通过语法分析,下面我们看一个测试结果。

import tokenize from io import BytesIO code = """a = 1 + \ 2""" # 将代码字符串转换为字节流 code_bytes = code.encode('utf-8') code_stream = BytesIO(code_bytes) # 使用tokenize.tokenize生成tokens tokens = tokenize.tokenize(code_stream.readline) # 遍历tokens并打印信息 for token in tokens: print(token) TokenInfo(type=62 (ENCODING), string='utf-8', start=(0, 0), end=(0, 0), line='') TokenInfo(type=1 (NAME), string='a', start=(1, 0), end=(1, 1), line='a = 1 + 2') TokenInfo(type=54 (OP), string='=', start=(1, 2), end=(1, 3), line='a = 1 + 2') TokenInfo(type=2 (NUMBER), string='1', start=(1, 4), end=(1, 5), line='a = 1 + 2') TokenInfo(type=54 (OP), string='+', start=(1, 6), end=(1, 7), line='a = 1 + 2') TokenInfo(type=2 (NUMBER), string='2', start=(1, 8), end=(1, 9), line='a = 1 + 2') TokenInfo(type=4 (NEWLINE), string='', start=(1, 9), end=(1, 10), line='') TokenInfo(type=0 (ENDMARKER), string='', start=(2, 0), end=(2, 0), line='')

从上面的解析结果可以看出,确实两物理行被拼接成了一逻辑行,而末尾的反斜杠(\)与换行符(\n)消失所以没有解析成标记。

6、隐式行拼接

隐式拼接的圆括号、方括号、花括号内的表达式可以分成多个物理行,不必使用反斜杠。例如:

month_names = ['Januari', 'Februari', 'Maart', # 这里 'April', 'Mei', 'Juni', # 可以 'Juli', 'Augustus', 'September', # 加注释 'Oktober', 'November', 'December'] # 这并不会有影响 #正确的隐式行拼接,没有分隔标记 a = (1 + 2) #正确的隐式行拼接,没有分隔标记 a = [1, 2] #正确的隐式行拼接,没有分隔标记 a = {1, 2} #错误的隐式行拼接,分隔了标记 prin t("hello world!")

下面我们看一个测试结果:

import tokenize from io import BytesIO code = """a = (1 + 2)""" # 将代码字符串转换为字节流 code_bytes = code.encode('utf-8') code_stream = BytesIO(code_bytes) # 使用tokenize.tokenize生成tokens tokens = tokenize.tokenize(code_stream.readline) # 遍历tokens并打印信息 for token in tokens: print(token) TokenInfo(type=62 (ENCODING), string='utf-8', start=(0, 0), end=(0, 0), line='') TokenInfo(type=1 (NAME), string='a', start=(1, 0), end=(1, 1), line='a = (1 +\n') TokenInfo(type=54 (OP), string='=', start=(1, 2), end=(1, 3), line='a = (1 +\n') TokenInfo(type=54 (OP), string='(', start=(1, 4), end=(1, 5), line='a = (1 +\n') TokenInfo(type=2 (NUMBER), string='1', start=(1, 5), end=(1, 6), line='a = (1 +\n') TokenInfo(type=54 (OP), string='+', start=(1, 7), end=(1, 8), line='a = (1 +\n') TokenInfo(type=61 (NL), string='\n', start=(1, 8), end=(1, 9), line='a = (1 +\n') TokenInfo(type=2 (NUMBER), string='2', start=(2, 0), end=(2, 1), line='2)') TokenInfo(type=54 (OP), string=')', start=(2, 1), end=(2, 2), line='2)') TokenInfo(type=4 (NEWLINE), string='', start=(2, 2), end=(2, 3), line='') TokenInfo(type=0 (ENDMARKER), string='', start=(3, 0), end=(3, 0), line='')

从上面的解析结果可以看出,第一行末尾的换行符(\n)被解析为NL标记,而第二行末尾的换行符(\n)被解析为NEWLINE标记。对于上文谈到的其他特性和结果,可以自行使用程序进行测试,在此不进行详述。

7、缩进问题

在Python中,逻辑行的缩进(空格、制表符)被当做是层次结构的体现,而非逻辑行中的第一行的物理行的缩进则无此效果。

a = 1 # 该行缩进表示层次结构 b = (1 + # 该行缩进表示层次结构 2) # 该行缩进不表示层次结构

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

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

立即咨询