解读 Python 世界的修炼体系#

视频

假设你是万能的造物主,可以使用你的能力创造一个世界!

你所在的世界为零级世界(最高级别的世界),你借助电脑(一级世界)创造了 Python(二级世界)。

约定一些名称

造物主

一个用于启动和维系 Python 世界的正常运转的造物主。

Python 解释器

该工具用于翻译 Python 世界的语言,解释给电脑世界。实现电脑世界与 Python 世界的沟通。

Python 环境

Python 世界下辖的 Python 小世界(三级及其以下的世界)。

具体的创建环境的方法见:如何构造 Python 环境

编辑器

用于编写管理 Python 世界的代码工具(一系列指令)。

该工具是你操纵电脑去管理 Python 世界的大门。

通用法则#

一个 Python 世界是一个真实的世界,它需要遵循一些固有的通用法则。

逻辑#

重要

通用法则1:Python 世界是遵循逻辑的。

为了方便表示逻辑判断的结果,定义 逻辑值

表示一个命题判定为对的、合乎逻辑的。

表示一个命题判定为不对的、不合乎逻辑的。

调试

常用于对命题做出假设,辅助于逻辑判断。

对象#

重要

通用法则2:Python 中的一切皆是 对象

一个对象总是存在:

每次创建一个新的对象都要从零开始,效率太低了。考虑先构建一个“模具”。具有以下构造:

  1. 一个唯一的 编号(可以简写为 ID,在 CPython 中表示对象在内存中的地址):作为对象的身份象征,不可变

  2. 一个特定的 类型:定义对象的行为和取值范围(即 ),也具有不变性

  3. 一个确切的 :定义了对象的

编号类型 共同描述了对象的

重要

通用法则3:Python 中的任何一个对象,都存在唯一的 编号、特定的 类型 与 确切的 。其中,编号 指代该对象自身,用于区别于其他对象。

换言之,Python 对象的编号便是其最本质的东西,可以用于区别于其他对象。

提示

不可变对象

对象的最外层值不能改变。

可变对象

对象的最外层值可以改变。

模块与包#

模块

你是通过操作电脑中的 文件 以操纵着 Python 世界。假设你使用编辑器在一个文件中写满了关于操作 Python 的指令,然后借由 Python 解释器将此文件的指令翻译给电脑,电脑便真正意义上运转着 Python 世界。由于此文件对 Python 世界有着这么重要的作用,所以 Python 对其专门定制了一个名称叫做 模块,且该文件还有一个特殊的文件后缀 .py

如若 Python 世界很简单,仅仅一个模块文件管理,还是不错的。但是,要是 Python 世界很复杂,你可能需要在一个文件中写上几十万,乃至几百万的代码。对于电脑来说这没有什么大问题,可是苦了你自己。

为了减轻你管理 Python 模块的工作量,你把一个模块分成了多个文件,并放置在一个文件夹中,并且引入了 导入系统importing) 在此文件夹中协助管理这些文件。此文件夹叫做 常规包。包是可以嵌套的,即包存在子包。

常规包

通常以一个包含 __init__.py 文件的目录形式实现。

例如,以下文件系统布局定义了一个最高层级的 parent 包和三个子包:

parent/
    __init__.py
    one/
        __init__.py
    two/
        __init__.py
    three/
        __init__.py

有了常规包这个模型,你管理 Python 世界轻松多了。

很不幸!常规包模型想要跨目录导入将会变得很困难。PEP 420 提出 命名空间包 的概念,有效地解决此问题。

命名空间包

命名空间包是由多个 portion 构成的,每个 portion 为父包增加一个子包。各个 portion 可能处于文件系统的不同位置。portion 也可能处于 zip 文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。

命名空间包没有 parent/__init__.py 文件。实际上,在导入搜索期间可能找到多个 parent 目录,每个都由不同的 portion 所提供。因此 parent/one 的物理位置不一定与 parent/two 相邻。 在这种情况下,Python 将为顶级的 parent 包创建一个命名空间包,无论是它本身还是它的某个子包被导入。

另请参阅 PEP 420 了解对命名空间包的规格描述。

常规包命名空间包 合称为 ,从语义范畴来看,Python 的包是特殊的模块。

形符化#

如果人们想要让电脑(这里的电脑指的是广义的,比如打印机也可以看作是微型电脑)进行某些活动,那么,需要给它提供一条条的命令(或者称为 指令)来引导其运转。这些指令一般被称为 程序

我们是通过彼此可以听得懂的语言还向他人转达信息的,同样,如果我们想要给电脑传达一些信息,那么,“编程语言”便是我们和电脑沟通的桥梁。

编程 就是我们为了完成某项任务,将解决问题的步骤,用计算机能够理解的语言(编程语言)写成指令,计算机会根据这些指令完成我们的任务。

Python 的世界不懂我们的语言,需要专门为其创造一些形符(token)用于转译我们的语言。换言之,需要为 Python 世界创造一种通用语言,这个过程便是形符化(详细见:tokenize)。

重要

  • Python 的常用形符有:字面值、标识符、运算符 、分隔符、换行(NEWLINE)、缩进(INDENTDEDENT)、#(用于注释) 等。

  • 标识符 就是 Python 用来识别对象的标识名称或者代号;

  • 字面值 用来记录对象的数量、语言等;

  • 类型 是对象所存在的空间,决定着对象的行为以及可能的取值。

字面值#

字面值 是 Python 内置类型常量值的表示法。字面值 包含:字符串、字节串、数字。

字面值是构成 Python 世界的基础之一,其中数字提供了计数体系,而字符串与字节串提供了文字体系。

数字#

数字字面值有三种类型:整数、浮点数、虚数。

可以使用用下划线分组数字。

整数字面值

可以理解为数学中的非负整数。表示方法包含:十进制、二进制、八进制、十六进制。如以下内容均是合法的:

7     2147483647                        0o177    0b100110111
3     79228162514264337593543950336     0o377    0xdeadbeef
    100_000_000_000                   0b_1110_0101
浮点数字面值

可以理解为数学中的非负实数的近似值,可以是:

3.14    10.    .001    1e100    3.14e-10    0e0    3.14_15_93
虚数字面值

可以理解为数学中的非负虚数的近似值。

示例如下:

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

没有复数字面值,但是可以表示:复数字面值=浮点数字面值(或者整数字面值)+ 虚数字面值,比如:3.5+4j

字符串与字节串#

详细内容见:stringprefixbytesprefix

字符串与字节串即造物对象所说的“语言”,比如 "你好"、b'搞事情!'

Python 中的对象需要通过语言与文字交流,可以这样比喻:

字符串

对象间口头表达的话语、肢体语言等。

使用引号(包含单引号('...')、双引号("...")、三引号('''...''' 或者 """...""")直接包裹的东西,比如:"你好"

字节串

对象间交流的书面语言,如人类的文字等。

字节串的表示方法,只需要在字符串的基础上添加前缀 b 或者 B 即可。

还有一些不可打印的语义,需要借助符号 \ 进行转义。如果,需要保留被转义的字符,则需要在字符串或者字节串上添加前缀 rR

还有一种特殊的字符串叫 格式字符串,即在字符串前添加 f 或者 F 前缀。

运算符#

有了记录对象的“语言”系统,当然,还要有施加于对象的作用或者对象之间行为或者作用的符号,即运算符。

运算符如下所示:

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

分隔符#

还需要分隔符:

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

句点也可以用于浮点数和虚数字面值。三个连续句点表示省略符。清单后半部分是增强赋值操作符,用作词法分隔符,但也可以执行运算。

以下 ASCII 字符具有特殊含义,对 Python 有重要意义:

'       "       #       \

以下 ASCII 字符不用于 Python。在字符串字面值或注释外使用时,将直接报错:

$       ?       `

标识符#

对象需要一个 名称(也称为 标识符),用于代表其身份,可以在对象之间使用,而不会被轻易遗忘。详细命名规则见 PEP 3131

简单的说,标识符是由字母(包括汉字、英文字母等特定的 Unicode 符号)、数字与下划线组合而成的。但是,数字不能作为标识符的开头字段(也许是因为数字本来就是 Python 的特定符号,即字面值)。

有一类特殊的标识符,被称为 保留字,或称 关键字。此类标识符不可用于普通标识符:

False      await      else       import     pass
None       break      except     in         raise
True       class      finally    is         return
and        continue   for        lambda     try
as         def        from       nonlocal   while
assert     del        global     not        with
async      elif       if         or         yield

行结构#

使用编辑器打开一个模块文件,可以看到模块被组织成行结构。你当下看到的行,被称为 物理行

由于你管理的 Python 世界是符合逻辑的,故而模块按照逻辑单元组织起来的。换言之,一个 Python 模块可以拆分为多个 逻辑行

物理行不香吗?为什么添加了一个新的术语?

那是因为,一个逻辑行可能由多个连续的物理行组成。确切地说,逻辑行 是由形符 NEWLINE 结束的。

显式拼接行#

两个及两个以上的物理行可用反斜杠(\)拼接为一个逻辑行,规则如下:以不在字符串或注释内的反斜杠结尾时,物理行将与下一行拼接成一个逻辑行,并删除反斜杠及其后的换行符。例如:

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

备注

这里使用了关键字、数字、运算符、分隔符以及 #,它们的具体含义,暂且不表。

以反斜杠结尾的行,不能加注释;反斜杠也不能拼接注释。除字符串字面值外,反斜杠不能拼接形符(如,除字符串字面值外,不能用反斜杠把形符切分至两个物理行)。反斜杠只能在代码的字符串字面值里,在其他任何位置都是非法的。

小技巧

尽量少用 显式拼接行。因为显式拼接行并不可爱,很容易出错或者说不 Pythonic。

隐式拼接行#

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

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 形符。三引号字符串支持隐式拼接行,但不支持注释。

注释#

注释以井号 (#) 开头,在物理行末尾截止。注意,井号不是字符串字面值。除非应用隐式行拼接规则,否则,注释代表逻辑行结束。解释器不解析注释。

空白行#

只包含空格符、制表符、换页符、注释的逻辑行会被忽略(即不生成 NEWLINE 形符)。

缩进#

逻辑行的开头空白符(空格符和制表符)用于计算该行的 缩进层级

制表符(从左至右)被替换为一至八个空格,缩进空格的总数是八的倍数(与 Unix 的规则保持一致)。首个非空字符前的空格数决定了该行的缩进层次。缩进不能用反斜杠进行多行拼接;首个反斜杠所在物理行开头的空白符决定了缩进的层次。

提示

混用制表符和空格符缩进,可能造成逻辑混乱!

下面的 Python 代码缩进示例虽然正确,但含混不清:

def perm(l):
        # 计算 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):                       # 错误:首行缩进
for i in range(len(l)):             # 错误:没有缩进
    s = l[:i] + l[i+1:]
        p = perm(l[:i] + l[i+1:])   # 错误:意外的缩进
        for x in p:
                r.append(l[i:i+1] + x)
            return r                # 错误:不一致的缩进

(实际上,解释器可以识别前三个错误;只有最后一个错误由词法分析器识别 — return r 的缩进无法匹配从栈里移除的缩进层级。)

备注

连续行的缩进层级以堆栈形式生成 INDENTDEDENT 形符,说明如下:

读取文件第一行前,先向栈推入一个零值,该零值不会被移除。推入栈的层级值从底至顶持续增加。每个逻辑行开头的行缩进层级将与栈顶行比较。如果相等,则不做处理。如果新行层级较高,则会被推入栈顶,并生成一个 INDENT 形符。如果新行层级较低,则应当是栈中的层级数值之一;栈中高于该层级的所有数值都将被移除,每移除一级数值生成一个 DEDENT 形符。文件末尾,栈中剩余的每个大于零的数值生成一个 DEDENT 形符。

形符间的空白字符#

除非在逻辑行开头或字符串内,空格符、制表符、换页符等空白符都可以分隔形符。要把两个相连形符解读为不同形符,需要用空白符分隔(例如,ab 是一个形符,a b 则是两个形符)。

名称绑定与对象引用#

Python 标识符可以细分为两类:常量与变量。

常量

标识符所引用的对象编号不可改变的量。

变量

标识符所引用的对象编号可能改变的量。

标识符也可称之为 名称

对象之间相互独立,多个名称可能 绑定 到同一个对象。其他编程语言称之为 别名

一个名称可能 引用 多个对象。

表达式#

下面可以利用形符、分隔符、运算符组合生成有意义的可以 求值 的语义单元:表达式。

参数

表达式的参考数据。

算术转换#

算术运算符支持算术转换:数域等级就高原则。

  • 如果任一参数为复数,另一参数会被转换为复数;

  • 否则,如果任一参数为浮点数,另一参数会被转换为浮点数;

  • 否则,两者应该都为整数,不需要进行转换。

原子#

“原子”指表达式的最基本构成元素。最简单的原子是标识符和字面值。以圆括号、方括号或花括号包括的形式在语法上也被归类为原子。原子的句法为:

atom      ::=  identifier | literal | enclosure
enclosure ::=  parenth_form | list_display | dict_display | set_display
               | generator_expression | yield_atom

原型#

对于单个对象,可能存在以下特定的行为:属性引用、抽取、切片、调用。此类行为被称为 原型。句法:

primary ::=  atom | attributeref | subscription | slicing | call

为了更好的阐明概念,假定创建了一个对象,该对象是一个名称为 马仙洪 的人。

属性引用

用来描述对象的性质。

表示方法是:对象后面带有一个句点(.)加一个名称的原型,即 对象.名称

比如,想要获取 马仙洪 的身高,可以使用属性引用:马仙洪.身高

抽取

用来获取对象的一部分。

表示方法是:对象后面带有一个方括号([...])抽取器的原型,即 对象[抽取的内容]

比如,想要对 马仙洪 二升血,可以:马仙洪[二升血]

切片

用来获取对象的阶段性的东西。

表示方法是 [开始:结束:间隔]。如果 间隔1,则可以省略。

比如,想要查看 马仙洪 \(2 \sim 18\) 岁的每隔两年的记忆,可以:马仙洪[两岁的记忆:十六岁的记忆:间隔2年]

调用

用于操纵对象的行为,或者对其进行改造。

表示方法是 运算(一系列可能为空的参数)

比如,可以要求 马仙洪 去学习英语,可以使用 学习(英语) 这种表示方法。

作用域#

Python 世界有一个“基准”平面控制了其环境的边界。为了方便管理 Python 世界,还需要将此“基准”平面划分为不同的区域,称之为 作用域

每个作用域在横向拓展时,受控于形符 INDENT(即缩进的开始)和 DEDENT (即缩进的结束),而纵向拓展时受控于形符 NEWLINE#

  • NEWLINE 是逻辑行结束符。

  • 注释 (解释 Python 世界的语言给你看)以井号 (#) 开头,在物理行末尾截止。

  • 形符 INDENTDEDENT 控制 缩进 级别。

重要

  • 可以把 Python 世界自身视为基层作用域。

  • 可以把 Python 世界的环境视为顶级作用域。

  • 作用域是有等级之分的。

关于作用域的等级

假设某个 顶级作用域 (也可以称之为 零级作用域) 中构建了一个人类世界。

可以按照人类世界占据的不同区域将人类世界细化为不同的子区域,那便是国家,称之为 一级作用域

每个国家都有自己的一套管理机制,各自细化自己的领域,如划分为区或者省,称之为 二级作用域

每个区或者省也有自己的一套管理机制,各自细化自己的领域,如划分为县或者乡,称之为 三级作用域

每个县或者乡也有自己的一套管理机制,各自细化到个人的家庭,称之为 四级作用域

每个家庭也有自己的一套管理机制,各自细化到个人,称之为 五级作用域

顶级组件#

Python 解释器可以从多种源获得输入:作为标准输入或程序参数传入的脚本,以交互方式键入的语句,导入的模块源文件等等。

完整的 Python 程序#

一个完整的 Python 程序会在最小初始化环境中被执行:所有内置和标准模块均为可用,但均处于未初始化状态,只有模块 sys(各种系统服务)、builtins (内置 函数异常以及 None) 和 __main__ 除外。最后一个模块用于为完整程序的执行提供局部和全局 命名空间

适用于一个完整 Python 程序的语法即下节所描述的文件输入。

解释器也可以通过交互模式被发起调用;在此情况下,它并不读取和执行一个完整程序,而是每次读取和执行一条语句(可能为复合语句)。此时的初始环境与一个完整程序的相同;每条语句会在 __main__ 的命名空间中被执行。

一个完整程序可通过三种形式被传递给解释器:使用 -c 字符串 命令行选项,使用一个文件作为第一个命令行参数,或者使用标准输入。如果文件或标准输入是一个 tty 设置,解释器会进入交互模式;否则的话,它会将文件当作一个完整程序来执行。

文件输入#

所有从非交互式文件读取的输入都具有相同的形式:

file_input ::=  (NEWLINE | statement)*

此语法用于下列几种情况:

  • 解析一个完整 Python 程序时(从文件或字符串);

  • 解析一个模块时;

  • 解析一个传递给 exec() 的字符串时;

交互式输入#

交互模式下的输入使用以下语法进行解析:

interactive_input ::=  [stmt_list] NEWLINE | compound_stmt NEWLINE

请注意在交互模式下一条(最高层级)复合语句必须带有一个空行;这对于帮助解析器确定输入的结束是必须的。

表达式输入#

eval() 被用于表达式输入。它会忽略开头的空白。传递给 eval() 的字符串参数必须具有以下形式:

eval_input ::=  expression_list NEWLINE*

简单语句#

简单语句由一个单独的逻辑行构成。多条简单语句可以存在于同一行内并以分号分隔。简单语句的句法为:

simple_stmt ::=  expression_stmt
                 | assert_stmt
                 | assignment_stmt
                 | augmented_assignment_stmt
                 | annotated_assignment_stmt
                 | pass_stmt
                 | del_stmt
                 | return_stmt
                 | yield_stmt
                 | raise_stmt
                 | break_stmt
                 | continue_stmt
                 | import_stmt
                 | future_stmt
                 | global_stmt
                 | nonlocal_stmt

表达式 可以理解为运算。

复合语句#

复合语句是包含其它语句(语句组)的语句;它们会以某种方式影响或控制所包含其它语句的执行。通常,复合语句会跨越多行,虽然在某些简单形式下整个复合语句也可能包含于一行之内。

if, whilefor 语句用来实现传统的 控制流程 构造。try 语句为一组语句指定异常处理和/和清理代码,而 with 语句允许在一个代码块周围执行初始化和终结化代码。函数和类定义在语法上也属于复合语句。

一条复合语句由一个或多个 “子句” 组成。 一个子句则包含一个 “句头” 和一个 “句体”(suite)。特定复合语句的子句头都处于相同的缩进层级。每个子句头以一个作为唯一标识的关键字开始并以一个冒号结束。 子句体是由一个子句控制的一组语句。子句体可以是在子句头的冒号之后与其同处一行的一条或由分号分隔的多条简单语句,或者也可以是在其之后缩进的一行或多行语句。只有后一种形式的子句体才能包含嵌套的复合语句;以下形式是不合法的,这主要是因为无法分清某个后续的 else 子句应该属于哪个 if 子句。

if test1: if test2: print(x)

还要注意的是在这种情形下分号的绑定比冒号更紧密,因此在以下示例中,所有 print() 调用或者都不执行,或者都执行:

if x < y < z: print(x); print(y); print(z)

总结:

compound_stmt ::=  if_stmt
                   | while_stmt
                   | for_stmt
                   | try_stmt
                   | with_stmt
                   | match_stmt
                   | funcdef
                   | classdef
                   | async_with_stmt
                   | async_for_stmt
                   | async_funcdef
suite         ::=  stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement     ::=  stmt_list NEWLINE | compound_stmt
stmt_list     ::=  simple_stmt (";" simple_stmt)* [";"]

请注意语句总是以 NEWLINE 结束,之后可能跟随一个 DEDENT。还要注意可选的后续子句总是以一个不能作为语句开头的关键字作为开头,因此不会产生歧义(“悬空的 else” 问题在 Python 中是通过要求嵌套的 if 语句必须缩进来解决的)。

名称的解析#

del 语句的目标也被视作一种绑定(虽然其实际语义为解除名称绑定)。

名称绑定包括:

  • 函数的形参,

  • 类定义,

  • 函数定义,

  • 赋值表达式

  • as 的目标

  • import 语句

import 语句的形式是 from ... import * 绑定除了那些以下划线开头的所有在导入模块中定义的名字。这种形式只能在模块层面上使用。

每条赋值或导入语句均发生于类或函数内部定义的代码块中,或是发生于模块层级(即最高层级的代码块)。

名称绑定在一个代码块中,除非声明为 nonlocalglobal,否则为该代码块的 局部变量。如果名称绑定在模块层级,则为 全局变量。(模块代码块的变量既为局部变量又为全局变量。)如果变量在一个代码块中被使用但不是在其中定义,则为 自由变量

每个在程序文本中出现的名称是指由以下名称解析规则所建立的对该名称的 绑定

作用域 定义了一个代码块中名称的可见性。如果代码块中定义了一个局部变量,则其作用域包含该代码块。如果定义发生于函数代码块中,则其作用域会扩展到该函数所包含的任何代码块,除非有某个被包含代码块引入了对该名称的不同绑定。

当一个名称在代码块中被使用时,会由包含它的最近作用域来解析。对一个代码块可见的所有这种作用域的集合称为该代码块的 环境

当一个名称完全找不到时,将会引发 NameError 异常。如果当前作用域为函数作用域,且该名称指向一个局部变量,而此变量在该名称被使用的时候尚未绑定到特定值,将会引发 UnboundLocalErrorNameError 的子类) 异常。

如果一个代码块内的任何位置发生名称绑定操作,则代码块内所有对该名称的使用会被认为是对当前代码块的引用。当一个名称在其被绑定前就在代码块内被使用时则会导致错误。这个一个很微妙的规则。Python 缺少声明语法,并允许名称绑定操作发生于代码块内的任何位置。一个代码块的局部变量可通过在整个代码块文本中扫描名称绑定操作来确定。

如果 global 语句出现在一个代码块中,那么该语句中指定的名称的所有使用都是指这些名称在顶层名称空间中的绑定关系。在顶层名称空间中,通过搜索全局名称空间(即包含代码块的模块的名称空间)和内置名称空间(即模块 builtins 的名称空间)来解析名称。全局名称空间会先被搜索到。如果在那里找不到这些名字,就会搜索 buildins 的名字空间。global 必须在所有列出的名字使用之前声明。

global 语句与同一代码块中名称绑定具有相同的作用域。如果一个自由变量的最近包含作用域中有一条 global 语句,则该自由变量也会被当作是全局变量。

nonlocal 语句会使得相应的名称指向之前在最近包含函数作用域中绑定的变量。如果指定名称不存在于任何包含函数作用域中则将在编译时引发 SyntaxError

模块的作用域会在模块第一次被导入时自动创建。一个脚本的主模块总是被命名为 __main__

类定义代码块以及传给 exec()eval() 的参数是名称解析上下文中的特殊情况。类定义是可能使用并定义名称的可执行语句。这些引用遵循正常的名称解析规则,例外之处在于未绑定的局部变量将会在全局命名空间中查找。类定义的命名空间会成为该类的属性字典。在类代码块中定义的名称的作用域会被限制在类代码块中;它不会扩展到方法的代码块中 – 这也包括推导式和生成器表达式,因为它们都是使用函数作用域实现的。这意味着以下代码将会失败:

class A:
    a = 42
    b = list(a + i for i in range(10))