Python:词法分析(行结构与显式、隐式行拼接)

相关阅读

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


1、逻辑结构

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

2、逻辑行

逻辑行的结尾的换行符(\n)被解析为NEWLINE标记。一般来说,一条语句不能跨越逻辑行而存在(除非是复合语句,如if复合语句可以由多个逻辑行组成)。根据显式或隐式行拼接规则,若干个物理行可以组成逻辑行。

3、物理行

物理行的结尾的换行符(\n)可能被解析为NEWLINE标记(如果这个物理行是一个逻辑行中的最后一个物理行),或者NL标记(如果这个物理行不是一个逻辑行中的最后一个物理行),或者也可能在显式行拼接时与反斜杠(\)一起被删除。

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标记。对于上文谈到的其他特性和结果,可以自行使用程序进行测试,在此不进行详述。

相关推荐
明月_清风9 分钟前
FastAPI 从入门到实战:3 分钟构建高性能异步 API
后端·python·fastapi
bellus-16 分钟前
ubuntu26测试win10的ollama大模型性能
python
水木流年追梦17 分钟前
大模型入门-Reward 奖励模型训练
开发语言·python·算法·leetcode·正则表达式
JavaWeb学起来17 分钟前
Python学习教程(六)数据结构List(列表)
数据结构·python·python基础·python教程
liuyunshengsir30 分钟前
PyTorch 动态量化(Dynamic Quantization)
人工智能·pytorch·python
电子云与长程纠缠39 分钟前
UE5制作六边形包裹球体效果
开发语言·python·ue5
DFT计算杂谈1 小时前
KPROJ编译教程
java·前端·python·算法·conda
念恒123061 小时前
Python(循环中断)
开发语言·python
tsfy20032 小时前
Python 处理中文文件名的3个坑(附 Flask 上传解决函数)
开发语言·python·flask·文件上传·中文编码
AI技术控2 小时前
KV Cache 缓存机制的原理和应用:从 Transformer 推理到大模型服务优化
人工智能·python·深度学习·缓存·自然语言处理·transformer