python基础教程之词法分析

python基础教程之词法分析


2. 词法分析

Python程序由解析器读取。输入到解析器中的是由词法分析器生成的词符流。本章讲述词法分析器如何把一个文件拆分成词符。

Python程序的文本使用7比特ASCII字符集。

2.3版中新增:可以使用编码声明指出字符串字面值和注释使用一种不同于ASCII的编码。

为了和旧的版本兼容,如果发现8比特字符,Python只会给出警告。修正这些警告的方法是声明显式的编码,或者对非字符的二进制数据字节使用转义序列。

运行时的字符集取决于与程序连接的I/O设备,但通常都是ASCII的超集。

未来兼容性的注意事项:可能会假设8比特字符的字符集是ISO Latin-1(ASCII字符集的超集,覆盖了大部分使用拉丁字母的西方语言),但是在未来Unicode文本编辑器可能变得通用。这些编辑器通常使用UTF-8编码,它也是ASCII的超集,但是序数在128-255之间的字符的用法非常不一样。关于这个主题还没有一致的意见,假设是Latin-1或UTF-8都不明智,即使当前的实现似乎倾向于Latin-1。源文件的字符集和运行时的字符集都适用。


2.1. 行结构

一个Python程序被分割成若干逻辑行


2.1.1. 逻辑行

逻辑行的结束由NEWLINE词符表示。除非语法允许NEWLINE(例如,复合语句的语句之间),否则语句不能跨越逻辑行的边界。通过遵循显式或隐式的行连接规则,一个逻辑行由一个或多个物理行构成。


2.1.2. 物理行

一个物理行是一个被行结束序列终止的字符序列。在源文件中,任何标准平台的行终止序列都可以使用 – Unix方式使用ASCII的LF(换行),Windows方式使用ASCII序列CR LF(回车和换行),旧的Macintosh方式使用ASCII的CR(回车)字符。

在嵌入Python时,对于换行符源代码字符串应该使用标准C的习惯传递给Python API(代表ASCII LF的\n字符是行终止符)。


2.1.3. 注释

注释以非字符串字面值中的井号字符(#)开始,在物理行的末尾结束。除非引起隐式的行连接规则,否则注释意味着逻辑行的结束。语法会忽略注释;它们不是词符。


2.1.4. 编码声明

如果Python脚本的第一行或者第二行的注释匹配正则表达式coding[=:]\s*([-\w.]+),那么这行注释将作为编码声明处理;该表达式的第一个分组指出源文件编码的名字。建议的表达式形式是

# -*- coding: <encoding-name> -*-

它也能被GNU Emacs识别,或者

# vim:fileencoding=<encoding-name>

它能被Bram Moolenaar的VIM识别。除此之外,如果文件开始几个字节是UTF-8的字节顺序标记('\xef\xbb\xbf'),声明的文件编码将是UTF-8(这个特性也被微软的notepad和其它编辑器支持。)

如果声明了编码,那么编码的名字必须能够被Python识别。编码将用于所有的词法分析,特别是寻找字符串的结束,和解释Unicode字面值的内容。字符串字面值会被转换成Unicode来做语法分析,然后在解释开始之前被转换回它们初始的编码。编码声明必须出现在它自己单独的一行上。


2.1.5. 显式的行连接

两个或多个物理行可以使用反斜杠字符(\)连接成一个逻辑行,方式如下:当一个物理行以一个不在字符串中或注释中的反斜杠结束时,它会和接下来的一行连接形成一个单独的逻辑行,反斜杠和后面的换行符会被删掉。例如:

if 1900 < year < 2100 and 1 <= month <= 12 \
   and 1 <= day <= 31 and 0 <= hour < 24 \
   and 0 <= minute < 60 and 0 <= second < 60:   # Looks like a valid date
        return 1

以反斜杠结束的行不能带有注释。反斜杠不能延续注释的效果(例如:####\  然后换行,其不能延续注释的效果 )。反斜杠不能延续除了字符串常量以外的词符(即不是字符串字面值的词符不能使用反斜杠分割跨多个物理行)。一行中位于字符串字面值以外其它地方的反斜杠是非法的。(也就是说,\在字符串中是转义字符,在一行尾部是链接符,其他地方就是非法的。)


2.1.6. 隐式的行连接

圆括号、方括号以及花括号中的表达式可以分割成多个物理行而不使用反斜杠。例如:

month_names = ['Januari', 'Februari', 'Maart',      # These are the
               'April',   'Mei',      'Juni',       # Dutch names
               'Juli',    'Augustus', 'September',  # for the months
               'Oktober', 'November', 'December']   # of the year

隐式的续行可以带注释。续行的缩进不重要。允许空白的续行。隐式的续行之间没有NEWLINE词符。隐式的续行也可以发生在三引号的字符串中(见下面):在这种情况下它们不可以带注释。


2.1.7. 空白行

只包含空格符、制表符、换页符和注释的逻辑行会被忽略(即不会有NEWLINE词符生成)。在交互式输入语句时,空白行的处理可能不同,这取决于read-eval-print循环的实现。在标准实现中,一个完全的空白逻辑行(甚至不包括空格与注释的行)会终止多行语句。


2.1.8. 缩进

逻辑行开始的前导空白(空格和制表符)用来计算行的缩进层级,然后用它决定语句的分组。

首先,每个制表符被替换(从左到右)成八个空格,这样包括替换后的字符的总数是八的整数(这是为了和Unix 使用一样的规则)。非空白字符之前的空格总数决定该行的缩进。缩进不可以使用反斜杠分割成多个物理行;直到第一个反斜杠处的空白决定了缩进。

跨平台兼容性的注意事项:由于非UNIX平台上的文本编辑器的天性,在一个源文件中缩进混合使用空格和制表符是不明智的。还应该注意到不同的平台可能明确地限定最大缩进的层级。

行的开始可能会出现换页符;它将被上述缩进的计算忽略。在前导空白其它地方出现的换页符的作用没有定义(例如,它们可能会重置空格的数量为零)。

连续行的缩进层级用于生成INDENT和DEDENT词符,这个过程使用了栈,如下所述。

在读入文件第一行之前, 一个零被压入堆栈中;
它将再也不会被弹出。压入堆栈中的数字将永远从底部往顶部增长。在每一个逻辑行的开始,该行的缩进层级会与栈顶比较。如果相等,什么都不会发生。如果大于栈顶,将其压入栈,并生成一个INDENT词符。如果小于栈顶,它必须是堆栈中已存在的数字中的一个;栈中所有大于它的数都将被弹出,并且每个弹出的数字都生成一个DEDENT词符。到达文件尾时,栈中剩下的每一个大于零的数字也生成一个DEDENT词符。

这儿是一个正确缩进的Python代码片段的例子(虽然有点乱):

def perm(l):
        # Compute the list of all permutations of l
    if len(l) <= 1:
                  return [l]
    r = []
    for i in range(len(l)):
             s = l[:i] + l[i+1:]
             p = perm(s)
             for x in p:
              r.append(l[i:i+1] + x)
    return r

下面的例子展示了各种缩进错误:

 def perm(l):                       # error: first line indented
for i in range(len(l)):             # error: not indented
    s = l[:i] + l[i+1:]
        p = perm(l[:i] + l[i+1:])   # error: unexpected indent
        for x in p:
                r.append(l[i:i+1] + x)
            return r                # error: inconsistent dedent

(事实上,前三个错误是由解析器发现的;仅仅最后一个错误是由词法分析器找到的 — return r 的缩进层级与堆栈中弹出的数字没有匹配的层级。)


2.1.9. 词符之间的空白

除非位于逻辑行起始处或者字符串字面值当中,空格、制表符和换页符这些空白字符可以等同地用于分隔词符。空白仅当两个词符连接在一起可以理解成一个不同的词符时才需要(例如,ab是一个词符,但a b是两个词符)。


2.2. 其它的词符

除了NEWLINE、INDENT和DEDENT,还存在以下几类词符:标识符关键字字面值操作符分隔符空白字符(前面讨论的断行符除外)不是词符,而是用于分隔词符。有歧义存在时,词符由形成合法词符的最长字符串组成(自左向右读取)。


2.3. 标识符和关键字

标识符(也称为名字)由以下词法定义描述:

identifier ::=  (letter|"_") (letter | digit | "_")*
letter     ::=  lowercase | uppercase
lowercase  ::=  "a"..."z"
uppercase  ::=  "A"..."Z"
digit      ::=  "0"..."9"

标识符长度没有限制。区分大小写。


2.3.1. 关键字

以下标识符用作保留字,或者叫做语言的关键字,并且不能作为普通的标识符使用。它们必须像下面那样准确拼写:

and       del       from      not       while
as        elif      global    or        with
assert    else      if        pass      yield
break     except    import    print
class     exec      in        raise
continue  finally   is        return
def       for       lambda    try

版本2.4 中的变化:None成为一个常量并且被编译器识别为内建对象None的名字。尽管不是关键字,你也不可以给它赋值一个不同的对象。

版本2.5 中的变化:使用aswith作为标识符会引发警告。要使用它们作为关键字,需启用with_statement这个未来特性。

版本2.6 中的变化:aswith成为真正的关键字。


2.3.2 保留类别的标识符

有几种特定类别的标识符(关键字除外)具有特殊的含义。这些类别有标志性的模式就是开始和尾部的下划线:

_*

不会被from module import *导入。_这个特殊的标识符用于在交互式解释器中存储上一次计算的结果;它存储在__builtin__模块。不在交互式模式时,_没有特别的含义且是未定义的。参看import 语句一节。

注意

名字_经常用于国际化;关于这个惯例的更多信息请参考gettext模块的文档。

__*__
系统定义的名字。这些名字由解释器及其实现(包括标准库)定义。这些系统定义的名字在特殊方法的名字一节和其它地方讨论。未来版本的Python 可能会定义更多的系统名字。无论什么情况,任何不遵守明确的文档使用说明的__*__使用,都可能会带来破坏而没有警告。
__*
类私有变量。这种类别的名字,在类定义的语境中使用时,会被使用一种变形的形式重写以避免基类和继承类的“私有”属性之间的冲突。参考标识符(名称)一节。

2.4. 字面值

字面值是部分内建类型的常量值符号。


2.4.1. 字符串字面值

字符串字面值由以下词法定义描述:

stringliteral   ::=  [stringprefix](shortstring | longstring)
stringprefix    ::=  "r" | "u" | "ur" | "R" | "U" | "UR" | "Ur" | "uR"
                     | "b" | "B" | "br" | "Br" | "bR" | "BR"
shortstring     ::=  "'" shortstringitem* "'" | '"' shortstringitem* '"'
longstring      ::=  "'''" longstringitem* "'''"
                     | '"""' longstringitem* '"""'
shortstringitem ::=  shortstringchar | escapeseq
longstringitem  ::=  longstringchar | escapeseq
shortstringchar ::=  <any source character except "\" or newline or the quote>
longstringchar  ::=  <any source character except "\">
escapeseq       ::=  "\" <any ASCII character>

上面产生式中没有表示出来一个的语法限制是, 在stringprefix与其余字符串字面值之间不允许出现空白字符。字符集由编码声明定义;如果源文件中没有指定编码声明,则为ASCII;参考编码声明一节。

用简单的中文来描述就是:字符串字面值可以包含在配对的单引号(')或双引号(")中。它们也可以包含在配对的三个单引号或双引号组中(这些字符串一般称为三引号字符串)。反斜杠(\) 用于转义具有特殊意义的字符,例如换行、反斜杠本身或者引号。字符串字面值可以加一个前缀字母'r' 或者'R'这些字符串称为原始字符串 并且使用不同的规则解释反斜杠转义的序列。前缀'u''U'使得字符串成为一个Unicode字符串。Unicode 字符串使用由Unicode 协会和ISO 10646定义的Unicode 字符集。Unicode 字符串中有效的一些额外转义序列会在下面描述。前缀'b''B'在Python 2中被忽略;在Python 3中,它表示那个字面值应该是一个字节型字面值(例如,用2to3自动转换代码的时候)。前缀'u''b'后面可以跟随一个前缀'r'

在三引号字符串中,没有转义的换行和引号是允许的(并且会被保留),除非三个未转义的引号终止了字符串。(引号指用于开始字符串的字符,例如'"。)

除非出现前缀'r''R',否则字符串中的转义序列依照类似标准C 使用的规则解释。可识别的转义序列有:

转义序列 含义 说明
\newline 忽略  
\\ 反斜杠 (\)  
\' 单引号 (')  
\" 双引号 (")  
\a ASCII响铃(BEL)  
\b ASCII退格(BS)  
\f ASCII换页(FF)  
\n ASCII换行(LF)  
\N{name} Unicode数据库中名为name的字符(Unicode only)  
\r ASCII回车(CR)  
\t ASCII水平制表(TAB)  
\uxxxx 16位的十六进制值为xxxx的字符(Unicode only) (1)
\Uxxxxxxxx 32位的十六进制值为xxxx的字符(Unicode only) (2)
\v ASCII垂直制表(VT)  
\ooo 八进制值为ooo的字符 (3,5)
\xhh 十六进制值为hh的字符 (4,5)

说明:

  1. 形成代理对一部分的代码单元可以使用这种转义序列编码。
  2. 任何Unicode字符都可以用这种方式编码,但如果Python 编译成使用16比特代码单元(默认行为),位于基本多语言平面(BMP)之外的字符将用代理对编码。
  3. 与标准C 一样,最多接受三个八进制数字。
  4. 与标准C 不同,要求两个精确的十六进制数字。
  5. 在字符串字面值中, 十六进制和八进制转义字符表示具有给定值的字节;没有必要再用那个字节编码源字符集中的字符。在Unicode字面值中,这些转义字符表示一个具有给定值的Unicode字符。

与标准C 不同, 所有不能识别的转义序列都会保留留在字符串中维持不变,例如,反斜杠会保留在字符串串中(这个行为在调试的时候特别有用:如果敲错一个转义序列,输出的结果可以很容易看出是有问题的。)同样要注意,上面表格中标记为“(Unicode only)”的转义序列,在非Unicode字符串字面值中属于不能识别的类别。

当出现前缀'r''R'时,紧跟在反斜杠后面的字符会包含在字符串中不变,并且所有的反斜杠都会保留在字符串中例如,字符串字面值r"\n" 由两个字符组成:一个反斜杠和一个小写的'n'字符串的引号可以用反斜杠转义,但是反斜杠会保留在字符串中;例如,r"\"" 是一个由两个字符组成的合法的字符串字面值:一个反斜杠和一个双引号;r"\" 不是一个合法的字符串字面值(即使是原始字符串也不能以奇数个反斜杠结束)。特别地, 原始字符串不能以一个反斜线结束(因为反斜杠会转义随后的引用字符)。还要注意后面紧跟着换行符的反斜杠被解释为字符串中的两个字符,而是作为续行处理。

如果前缀'r' 或者'R' 和前缀'u' or 'U'一起使用,那么转义序列uXXXXUXXXXXXXX 会被处理而其它所有反斜杠会保留在字符串中例如,字符串字面值ur"\u0062\n" 有三个Unicode 字符组成:‘拉丁小写字母b’、‘反斜杠’和‘拉丁小写字母n’。反斜杠可以用前面的反斜杠转义;然而,两个都会保留在字符串中。结果,转义序列\uXXXX只有在奇数个反斜杠的时候才能识别。


2.4.2. 字符串字面值的连接

多个相邻的字符串字面值(由空白分隔),可能使用不同的引用习惯,是允许的且含义是把它们连接起来。因此,"hello" 'world'等同于"helloworld"这个特性能够用于减少需要的反斜杠的数目以方便地把很长的字符串分成几行,或者甚至给字符串的某些部分加上注释,例如:

re.compile("[A-Za-z_]"       # letter or underscore
           "[A-Za-z0-9_]*"   # letter, digit or underscore
          )

注意,这个特性在语法层面上定义,但是在编译的时候实现。‘+’运算符用于连接字符串表达式必须在运行的时候。还需要注意,字面值的连接可以为每个部分使用不同的引号风格(即使是混合原始字符串和三引号字符串)。


2.4.3. 数值字面值

有四种类型的数值字面值:普通整数、长整数、浮点数和虚数。没有复数字面值(复数字面值可以由一个实数和一个虚数相加形成)。

注意数值字面值不包括符号;像-1这样的短语实际上是由一元运算符‘-’和字面值1组成的表达式。


2.4.4. 整数和长整数字面值

整数和长整数字面值可以用以下词法定义描述:

longinteger    ::=  integer ("l" | "L")
integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::=  nonzerodigit digit* | "0"
octinteger     ::=  "0" ("o" | "O") octdigit+ | "0" octdigit+
hexinteger     ::=  "0" ("x" | "X") hexdigit+
bininteger     ::=  "0" ("b" | "B") bindigit+
nonzerodigit   ::=  "1"..."9"
octdigit       ::=  "0"..."7"
bindigit       ::=  "0" | "1"
hexdigit       ::=  digit | "a"..."f" | "A"..."F"

大于最大可表示普通整数的普通整数字面值(例如,2147483647)会被当作长整数。[1]长整数字面值大小没有限制,除了可用内存的容量。

一些普通整数字面值(第一行)和长整数字面值(第二行和第三行)的例子:

7     2147483647                        0177
3L    79228162514264337593543950336L    0377L   0x100000000L
      79228162514264337593543950336             0xdeadbeef

2.4.5. 浮点数字面值

浮点数字面值由以下词法定义描述:

floatnumber   ::=  pointfloat | exponentfloat
pointfloat    ::=  [intpart] fraction | intpart "."
exponentfloat ::=  (intpart | pointfloat) exponent
intpart       ::=  digit+
fraction      ::=  "." digit+
exponent      ::=  ("e" | "E") ["+" | "-"] digit+

注意浮点数的整数部分和指数部分可能看上去像八进制数,但仍然用十进制解释。例如,077e010是合法的,它和77e10表示同一个数。浮点数字面值允许的范围依赖于具体的实现。一些浮点数字面值的例子:

3.14    10.    .001    1e100    3.14e-10    0e0

注意数值字面值不包括符号;像-1这样的短语实际上是由一元运算符‘-’和字面值1组成的表达式。


2.4.6. 虚数字面值

虚数字面值由以下词法定义描述:

imagnumber ::=  (floatnumber | intpart) ("j" | "J")

虚数字面值生成一个实部为0.0 的复数。复数由一对具有相同取值范围的浮点数表示。若要创建一个实部非零的复数,可以给它相加一个浮点数,例如,(3+4j)一些虚数字面值的例子:

3.14j   10.j    10j     .001j   1e100j  3.14e-10j

2.5. 操作符

以下词符是操作符:

+       -       *       **      /       //      %
<<      >>      &       |       ^       ~
<       >       <=      >=      ==      !=      <>

比较操作符<>!=是相同操作符的两个可选的拼写。推荐使用!=拼写,<>已经过时。


2.6. 分隔符

以下词符用作文法中的分隔符:

(       )       [       ]       {       }      @
,       :       .       `       =       ;
+=      -=      *=      /=      //=     %=
&=      |=      ^=      >>=     <<=     **=

句号也可以出现在浮点数和虚数字面值中。一个连续三个句号的序列在切片中具有省略号这样特殊的含义。列表的第二部分,即参数化赋值运算符,在词法上是作为分隔符处理,但也执行运算。

以下可打印ASCII字符作为其它词符的一部分有着特殊的含义,或者对于词法分析器具有重要作用:

'       "       #       \

以下可打印ASCII字符在Python中没有使用。它们出现在字符串字面值和注释之外是无条件的一个错误:

$       ?

脚注

[1] 在Python 2.4之前的版本中,在最大的可表示普通整数和最大的无符号32位数字4294967296(在一台使用32位算术的机器上)范围之间八进制和十六进制字面值会通过减去4294967296作为负的普通整数。

留下回复