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

相关阅读

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

你可能感兴趣的:(Python,python)