python3:input单独执行正确,代码整执行invalid的意思 literal for int(?

3.1. 对象、值与类型

对象 是 Python 中对数据的抽象。 Python 程序中的所有数据都是由对象或对象间关系来表示的。 (从某种意义上说,按照冯·诺依曼的“存储程序计算机”模型,代码本身也是由对象来表示的。)

每个对象都有各自的编号、类型和值。一个对象被创建后,它的 编号 就绝不会改变;你可以将其理解为该对象在内存中的地址。 '' 运算符可以比较两个对象的编号是否相同; 函数能返回一个代表其编号的整型数。

对象的类型决定该对象所支持的操作 (例如 "对象是否有长度属性?") 并且定义了该类型的对象可能的取值。 函数能返回一个对象的类型 (类型本身也是对象)。与编号一样,一个对象的 类型

有些对象的 可以改变。值可以改变的对象被称为 可变的;值不可以改变的对象就被称为 不可变的。(一个不可变容器对象如果包含对可变对象的引用,当后者的值改变时,前者的值也会改变;但是该容器仍属于不可变对象,因为它所包含的对象集是不会改变的。因此,不可变并不严格等同于值不能改变,实际含义要更微妙。) 一个对象的可变性是由其类型决定的;例如,数字、字符串和元组是不可变的,而字典和列表是可变的。

对象绝不会被显式地销毁;然而,当无法访问时它们可能会被作为垃圾回收。允许具体的实现推迟垃圾回收或完全省略此机制 --- 如何实现垃圾回收是实现的质量问题,只要可访问的对象不会被回收即可。

CPython implementation detail: CPython 目前使用带有 (可选) 延迟检测循环链接垃圾的引用计数方案,会在对象不可访问时立即回收其中的大部分,但不保证回收包含循环引用的垃圾。请查看 模块的文档了解如何控制循环垃圾的收集相关信息。其他实现会有不同的行为方式,CPython 现有方式也可能改变。不要依赖不可访问对象的立即终结机制 (所以你应当总是显式地关闭文件)。

注意:使用实现的跟踪或调试功能可能令正常情况下会被回收的对象继续存活。还要注意通过 '...' 语句捕捉异常也可能令对象保持存活。

有些对象包含对 "外部" 资源的引用,例如打开文件或窗口。当对象被作为垃圾回收时这些资源也应该会被释放,但由于垃圾回收并不确保发生,这些对象还提供了明确地释放外部资源的操作,通常为一个 close() 方法。强烈推荐在程序中显式关闭此类对象。'...' 语句和 '' 语句提供了进行此种操作的更便捷方式。

有些对象包含对其他对象的引用;它们被称为 容器。容器的例子有元组、列表和字典等。这些引用是容器对象值的组成部分。在多数情况下,当谈论一个容器的值时,我们是指所包含对象的值而不是其编号;但是,当我们谈论一个容器的可变性时,则仅指其直接包含的对象的编号。因此,如果一个不可变容器 (例如元组) 包含对一个可变对象的引用,则当该可变对象被改变时容器的值也会改变。

类型会影响对象行为的几乎所有方面。甚至对象编号的重要性也在某种程度上受到影响: 对于不可变类型,会得出新值的运算实际上会返回对相同类型和取值的任一现有对象的引用,而对于可变类型来说这是不允许的。例如在 a = 1; b = 1 之后,ab 可能会也可能不会指向同一个值为一的对象,这取决于具体实现,但是在 c = []; d = [] 之后,cd 保证会指向两个不同、单独的新建空列表。(请注意 c = d = [] 则是将同一个对象赋值给 cd。)

3.2. 标准类型层级结构

以下是 Python 内置类型的列表。扩展模块 (具体实现会以 C, Java 或其他语言编写) 可以定义更多的类型。未来版本的 Python 可能会加入更多的类型 (例如有理数、高效存储的整型数组等等),不过新增类型往往都是通过标准库来提供的。

以下部分类型的描述中包含有 '特殊属性列表' 段落。这些属性提供对具体实现的访问而非通常使用。它们的定义在未来可能会改变。

此类型只有一种取值。是一个具有此值的单独对象。此对象通过内置名称 None 访问。在许多情况下它被用来表示空值,例如未显式指明返回值的函数将返回 None。它的逻辑值为假。

此类型只有一种取值。 是一个具有该值的单独对象。 此对象通过内置名称 NotImplemented 访问。 数值方法和丰富比较方法如未实现指定运算符表示的运算则应返回该值。 (解释器会根据具体运算符继续尝试反向运算或其他回退操作。) 它不应被解读为布尔值。

在 3.9 版更改: 作为布尔值来解读 NotImplemented 已被弃用。 虽然它目前会被解读为真值,但将同时发出 。 它将在未来的 Python 版本中引发 。

此类型只有一种取值。是一个具有此值的单独对象。此对象通过字面值 ... 或内置名称 Ellipsis 访问。它的逻辑值为真。

此类对象由数字字面值创建,并会被作为算术运算符和算术内置函数的返回结果。数字对象是不可变的;一旦创建其值就不再改变。Python 中的数字当然非常类似数学中的数字,但也受限于计算机中的数字表示方法。

  • 它们是有效的数字字面值,当被传给它们的类构造器时,将会产生具有原数字值的对象。

  • 表示形式会在可能的情况下采用 10 进制。

  • 开头的零,除小数点前可能存在的单个零之外,将不会被显示。

  • 末尾的零,除小数点后可能存在的单个零之外,将不会被显示。

  • 正负号仅在当数字为负值时会被显示。

Python 区分整型数、浮点型数和复数:

此类对象表示数学中整数集合的成员 (包括正数和负数)。

整型数可细分为两种类型:

此类对象表示任意大小的数字,仅受限于可用的内存 (包括虚拟内存)。在变换和掩码运算中会以二进制表示,负数会以 2 的补码表示,看起来像是符号位向左延伸补满空位。

此类对象表示逻辑值 False 和 True。代表 FalseTrue 值的两个对象是唯二的布尔对象。布尔类型是整型的子类型,两个布尔值在各种场合的行为分别类似于数值 0 和 1,例外情况只有在转换为字符串时分别返回字符串

整型数表示规则的目的是在涉及负整型数的变换和掩码运算时提供最为合理的解释。

此类对象表示机器级的双精度浮点数。其所接受的取值范围和溢出处理将受制于底层的机器架构 (以及 C 或 Java 实现)。Python 不支持单精度浮点数;支持后者通常的理由是节省处理器和内存消耗,但这点节省相对于在 Python 中使用对象的开销来说太过微不足道,因此没有理由包含两种浮点数而令该语言变得复杂。

此类对象以一对机器级的双精度浮点数来表示复数值。有关浮点数的附带规则对其同样有效。一个复数值 z 的实部和虚部可通过只读属性 z.realz.imag 来获取。

此类对象表示以非负整数作为索引的有限有序集。内置函数 可返回一个序列的条目数量。当一个序列的长度为 n 时,索引集包含数字 0, 1, ..., n-1。序列

j。当用作表达式时,序列的切片就是一个与序列类型相同的新序列。新序列的索引还是从 0 开始。

序列可根据其可变性来加以区分:

不可变序列类型的对象一旦创建就不能再改变。(如果对象包含对其他对象的引用,其中的可变对象就是可以改变的;但是,一个不可变对象所直接引用的对象集是不能改变的。)

以下类型属于不可变对象:

字符串是由 Unicode 码位值组成的序列。范围在 U+0000 - U+10FFFF 之内的所有码位值都可在字符串中使用。Python 没有 char 类型;而是将字符串中的每个码位表示为一个长度为 1 的字符串对象。内置函数 可将一个码位由字符串形式转换成一个范围在 0 10FFFF 之内的整型数转换为长度为 1 的对应字符串对象。 可以使用指定的文本编码将 转换为 ,而 则可以实现反向的解码。

一个元组中的条目可以是任意 Python 对象。包含两个或以上条目的元组由逗号分隔的表达式构成。只有一个条目的元组 ('单项元组') 可通过在表达式后加一个逗号来构成 (一个表达式本身不能创建为元组,因为圆括号要用来设置表达式分组)。一个空元组可通过一对内容为空的圆括号创建。

字节串对象是不可变的数组。其中每个条目都是一个 8 位字节,以取值范围 0 <= x < 256 的整型数表示。字节串字面值 (例如 b'abc') 和内置的 构造器可被用来创建字节串对象。字节串对象还可以通过

可变序列在被创建后仍可被改变。下标和切片标注可被用作赋值和 (删除) 语句的目标。

目前有两种内生可变序列类型:

列表中的条目可以是任意 Python 对象。列表由用方括号括起并由逗号分隔的多个表达式构成。(注意创建长度为 0 或 1 的列表无需使用特殊规则。)

字节数组对象属于可变数组。可以通过内置的 构造器来创建。除了是可变的 (因而也是不可哈希的),在其他方面字节数组提供的接口和功能都与不可变的

扩展模块 提供了一个额外的可变序列类型示例, 模块也是如此。

此类对象表示由不重复且不可变对象组成的无序且有限的集合。因此它们不能通过下标来索引。但是它们可被迭代,也可用内置函数 返回集合中的条目数。集合常见的用处是快速成员检测,去除序列中的重复项,以及进行交、并、差和对称差等数学运算。

对于集合元素所采用的不可变规则与字典的键相同。注意数字类型遵循正常的数字比较规则: 如果两个数字相等 (例如 11.0),则同一集合中只能包含其中一个。

目前有两种内生集合类型:

此类对象表示可变集合。它们可通过内置的 构造器创建,并且创建之后可以通过方法进行修改,例如 add()

此类对象表示不可变集合。它们可通过内置的 构造器创建。由于 frozenset 对象不可变且 ,它可以被用作另一个集合的元素或是字典的键。

此类对象表示由任意索引集合所索引的对象的集合。通过下标 a[k] 可在映射 a 中选择索引为 k 的条目;这可以在表达式中使用,也可作为赋值或 语句的目标。内置函数 可返回一个映射中的条目数。

目前只有一种内生映射类型:

此类对象表示由几乎任意值作为索引的有限个对象的集合。不可作为键的值类型只有包含列表或字典或其他可变类型,通过值而非对象编号进行比较的值,其原因在于高效的字典实现需要使用键的哈希值以保持一致性。用作键的数字类型遵循正常的数字比较规则: 如果两个数字相等 (例如 11.0) 则它们均可来用来索引同一个字典条目。

字典会保留插入顺序,这意味着键将以它们被添加的顺序在字典中依次产生。 替换某个现有的键不会改变其顺序,但是移除某个键再重新插入则会将其添加到末尾而不会保留其原有位置。

字典是可变的;它们可通过 {...} 标注来创建 (参见 小节)。

扩展模块 和 提供了额外的映射类型示例, 模块也是如此。

在 3.7 版更改: 在 Python 3.6 版之前字典不会保留插入顺序。 在 CPython 3.6 中插入顺序会被保留,但这在当时被当作是一个实现细节而非确定的语言特性。

此类型可以被应用于函数调用操作 (参见 小节):

用户定义函数对象可通过函数定义来创建 (参见 小节)。它被调用时应附带一个参数列表,其中包含的条目应与函数所定义的形参列表一致。

该函数的文档字符串,没有则为 None;不会被子类继承。

该函数所属模块的名称,没有则为 None

由具有默认值的参数的默认参数值组成的元组,如无任何参数具有默认值则为 None

表示编译后的函数体的代码对象。

对存放该函数中全局变量的字典的引用 --- 函数所属模块的全局命名空间。

命名空间支持的函数属性。

None 或包含该函数可用变量的绑定的单元的元组。有关 cell_contents 属性的详情见下。

包含形参标注的字典。 字典的键是形参名,而如果提供了 'return' 则是用于返回值标注。 有关如何使用此属性的更多信息,请参阅 。

仅包含关键字参数默认值的字典。

大部分标有 "Writable" 的属性均会检查赋值的类型。

函数对象也支持获取和设置任意属性,例如这可以被用来给函数附加元数据。使用正规的属性点号标注获取和设置此类属性。注意当前实现仅支持用户定义函数属性。未来可能会增加支持内置函数属性。

单元对象具有 cell_contents 属性。这可被用来获取以及设置单元的值。

有关函数定义的额外信息可以从其代码对象中提取;参见下文对内部类型的描述。 类型可以在 模块中访问。

实例方法用于结合类、类实例和任何可调用对象 (通常为用户定义函数)。

作用相同);__module__ 为方法所属模块的名称,没有则为 None

方法还支持获取 (但不能设置) 下层函数对象的任意函数属性。

用户定义方法对象可在获取一个类的属性时被创建 (也可能通过该类的一个实例),如果该属性为用户定义函数对象或类方法对象。

当通过从类实例获取一个用户定义函数对象的方式创建一个实例方法对象时,类实例对象的 __self__ 属性即为该实例,并会绑定方法对象。该新建方法的 __func__ 属性就是原来的函数对象。

当通过从类或实例获取一个类方法对象的方式创建一个实例对象时,实例对象的 __self__ 属性为该类本身,其 __func__ 属性为类方法对应的下层函数对象。

当一个实例方法对象被调用时,会调用对应的下层函数 (__func__),并将类实例 (__self__) 插入参数列表的开头。例如,当 C 是一个包含了 f() 函数定义的类,而 xC 的一个实例,则调用 x.f(1) 就等同于调用 C.f(x, 1)

当一个实例方法对象是衍生自一个类方法对象时,保存在 __self__ 中的 "类实例" 实际上会是该类本身,因此无论是调用 x.f(1) 还是 C.f(1) 都等同于调用 f(C,1),其中 f 为对应的下层函数。

请注意从函数对象到实例方法对象的变换会在每一次从实例获取属性时发生。在某些情况下,一种高效的优化方式是将属性赋值给一个本地变量并调用该本地变量。还要注意这样的变换只发生于用户定义函数;其他可调用对象 (以及所有不可调用对象) 在被获取时都不会发生变换。还有一个需要关注的要点是作为一个类实例属性的用户定义函数不会被转换为绑定方法;这样的变换 仅当 函数是类属性时才会发生。

使用 来定义的函数或方法就被称为 协程函数。这样的函数在被调用时会返回一个 对象。它可能包含 表达式以及 和 语句。详情可参见 一节。

内置函数对象是对于 C 函数的外部封装。内置函数的例子包括 和 ( 是一个标准内置模块)。内置函数参数的数量和类型由 C 函数决定。特殊的只读属性: __doc__ 是函数的文档字符串,如果没有则为 None; 是函数的名称;

此类型实际上是内置函数的另一种形式,只不过还包含了一个传入 C 函数的对象作为隐式的额外参数。内置方法的一个例子是 alist.append(),其中 alist 为一个列表对象。在此示例中,特殊的只读属性 __self__ 会被设为 alist 所标记的对象。

模块是 Python 代码的基本组织单元,由 创建,由 语句发起调用,或者通过 和内置的 等函数发起调用。 模块对象具有由字典对象实现的命名空间(这是被模块中定义的函数的 __globals__ 属性引用的字典)。 属性引用被转换为该字典中的查找,例如 m.x 相当于 m.__dict__["x"]。 模块对象不包含用于初始化模块的代码对象(因为初始化完成后不需要它)。

预先定义的(可写)属性:

模块的文档字符串,如果不可用则为 None

被加载模块所对应文件的路径名称,如果它是从文件加载的话。 对于某些类型的模块来说 属性可能是缺失的,例如被静态链接到解释器中的 C 模块。 对于从共享库动态加载的扩展模块来说,它将是共享库文件的路径名称。

包含在模块体执行期间收集的 的字典。 有关使用 __annotations__ 的最佳实践,请参阅 。

特殊的只读属性: 为以字典对象表示的模块命名空间。

CPython implementation detail: 由于 CPython 清理模块字典的设定,当模块离开作用域时模块字典将会被清理,即使该字典还有活动的引用。想避免此问题,可复制该字典或保持模块状态以直接使用其字典。

自定义类这种类型一般通过类定义来创建 (参见 一节)。每个类都有通过一个字典对象实现的独立命名空间。类属性引用会被转化为在此字典中查找,例如 C.x 会被转化为 C.__dict__["x"] (不过也存在一些钩子对象以允许其他定位属性的方式)。当未在其中发现某个属性名称时,会继续在基类中查找。这种基类查找使用 C3 方法解析顺序,即使存在 '钻石形' 继承结构即有多条继承路径连到一个共同祖先也能保持正确的行为。有关 Python 使用的 C3 MRO 的详情可查看配合 2.3

当一个类属性引用 (假设类名为 C) 会产生一个类方法对象时,它将转化为一个 __self__ 属性为 C 的实例方法对象。当其会产生一个静态方法对象时,它将转化为该静态方法对象所封装的对象。从类的 所包含内容以外获取属性的其他方式请参看 一节。

类属性赋值会更新类的字典,但不会更新基类的字典。

类对象可被调用 (见上文) 以产生一个类实例 (见下文)。

类定义所在模块的名称。

包含类命名空间的字典。

包含基类的元组,按它们在基类列表中的出现先后排序。

类的文档字符串,如果未定义则为 None

包含在类体执行期间收集的 的字典。 有关使用 __annotations__ 的最佳实践,请参阅 。

如果类实例具有某些特殊名称的方法,就可以伪装为数字、序列或映射。参见 一节。

特殊属性: 为属性字典; 为实例对应的类。

I/O 对象 (或称文件对象)

表示一个打开的文件。有多种快捷方式可用来创建文件对象: 内置函数,以及 , 和 socket 对象的 方法 (还可能使用某些扩展模块所提供的其他函数或方法)。

sys.stdin, sys.stdoutsys.stderr 会初始化为对应于解释器标准输入、输出和错误流的文件对象;它们都会以文本模式打开,因此都遵循 抽象类所定义的接口。

某些由解释器内部使用的类型也被暴露给用户。它们的定义可能随未来解释器版本的更新而变化,为内容完整起见在此处一并介绍。

代码对象表示 编译为字节的 可执行 Python 代码,或称 。代码对象和函数对象的区别在于函数对象包含对函数全局对象 (函数所属的模块) 的显式引用,而代码对象不包含上下文;而且默认参数值会存放于函数对象而不是代码对象内 (因为它们表示在运行时算出的值)。与函数对象不同,代码对象不可变,也不包含对可变对象的引用 (不论是直接还是间接)。

特殊的只读属性: co_name 给出了函数名; co_argcount 为位置参数的总数量 (包括仅限位置参数和带有默认值的参数); co_posonlyargcount 为仅限位置参数的数量 为一个包含局部变量名称的元组 (参数名排在最前面); co_cellvars 为一个包含被嵌套函数所引用的局部变量名称的元组; co_freevars 为一个包含自由变量名称的元组; co_code 为一个表示字节码指令序列的字符口中; co_consts 为一个包含字节码所使用的字面值的元组; co_names 为一个包含字节码所使用的名称的元组; co_filename 为被编码代码所在的文件名; co_firstlineno 为函数首行的行号; co_lnotab 为一个字符串,其中编码了从字节码偏移量到行号的映射 (详情参见解释器的源代码); co_stacksize 为要求的栈大小; co_flags 为一个整数,其中编码了解释器所用的多个旗标。

以下是可用于 co_flags 的标志位定义:如果函数使用 *arguments 语法来接受任意数量的位置参数,则 0x04 位被设置;如果函数使用 **keywords 语法来接受任意数量的关键字参数,则 0x08 位被设置;如果函数是一个生成器,则 0x20 位被设置。

co_flags 中的其他位被保留为内部使用。

如果代码对象表示一个函数,co_consts 中的第一项将是函数的文档字符串,如果未定义则为 None

帧对象表示执行帧。它们可能出现在回溯对象中 (见下文),还会被传递给注册跟踪函数。

特殊的只读属性: f_back 为前一堆栈帧 (指向调用者),如是最底层堆栈帧则为 None; f_code 为此帧中所执行的代码对象; f_locals 为用于查找本地变量的字典; f_globals 则用于查找全局变量; f_builtins 用于查找内置 (固有) 名称; f_lasti 给出精确指令 (这是代码对象的字节码字符串的一个索引)。

特殊的可写属性: f_trace,如果不为 None,则是在代码执行期间调用各类事件的函数 (由调试器使用)。通常每个新源码行会触发一个事件 - 这可以通过将 f_trace_lines 设为 来禁用。

来允许按操作码请求事件。请注意如果跟踪函数引发的异常逃逸到被跟踪的函数中,这可能会导致未定义的解释器行为。

f_lineno 为帧的当前行号 --- 在这里写入从一个跟踪函数内部跳转的指定行 (仅用于最底层的帧)。调试器可以通过写入 f_lineno 实现一个 Jump 命令 (即设置下一语句)。

此方法清除该帧持有的全部对本地变量的引用。而且如果该帧属于一个生成器,生成器会被完成。这有助于打破包含帧对象的循环引用 (例如当捕获一个异常并保存其回溯在之后使用)。

如果该帧当前正在执行则会引发 。

回溯对象表示一个异常的栈跟踪记录。当异常发生时会隐式地创建一个回溯对象,也可能通过调用 显式地创建。

对于隐式地创建的回溯对象,当查找异常句柄使得执行栈展开时,会在每个展开层级的当前回溯之前插入一个回溯对象。当进入一个异常句柄时,栈跟踪将对程序启用。(参见 一节。) 它可作为 sys.exc_info() 所返回的元组的第三项,以及所捕获异常的

当程序不包含可用的句柄时,栈跟踪会 (以良好的格式) 写入标准错误流;如果解释器处于交互模式,它也可作为 sys.last_traceback 对用户启用。

对于显式创建的回溯对象,则由回溯对象的创建者来决定应该如何链接 tb_next 属性来构成完整的栈跟踪。

特殊的只读属性: tb_frame 指向当前层级的执行帧; tb_lineno 给出发生异常所在的行号; tb_lasti 标示具体指令。如果异常发生于没有匹配的 except 子句或有 finally 子句的 语句中,回溯对象中的行号和最后指令可能与相应帧对象中行号不同。

特殊的可写属性: tb_next 为栈跟踪中的下一层级 (通往发生异常的帧),如果没有下一层级则为 None

在 3.7 版更改: 回溯对象现在可以使用 Python 代码显式地实例化,现有实例的 tb_next 属性可以被更新。

特殊的只读属性: start 为下界; stop 为上界; step 为步长值; 各值如省略则为 None。这些属性可具有任意类型。

切片对象支持一个方法:

此方法接受一个整型参数 length 并计算在切片对象被应用到 length 指定长度的条目序列时切片的相关信息应如何描述。 其返回值为三个整型数组成的元组;这些数分别为切片的 startstop 索引号以及 step 步长值。索引号缺失或越界则按照与正规切片相一致的方式处理。

静态方法对象提供了一种胜过上文所述将函数对象转换为方法对象的方式。 静态方法对象是对任意其他对象的包装器,通常用来包装用户自定义的方法对象。 当从类或类实例获取一个静态方法对象时,实际返回的是经过包装的对象,它不会被进一步转换。 静态方法对象也是可调用对象。 静态方法对象可通过内置的 构造器来创建。

类方法对象和静态方法一样是对其他对象的封装,会改变从类或类实例获取该对象的方式。类方法对象在此类获取操作中的行为已在上文 "用户定义方法" 一节中描述。类方法对象可通过内置的 构造器来创建。

3.3. 特殊方法名称

在实现模拟任何内置类型的类时,很重要的一点是模拟的实现程度对于被模拟对象来说应当是有意义的。例如,提取单个元素的操作对于某些序列来说是适宜的,但提取切片可能就没有意义。(这种情况的一个实例是 W3C 的文档对象模型中的 NodeList 接口。)

调用以创建一个 cls 类的新实例。 是一个静态方法 (因为是特例所以你不需要显式地声明),它会将所请求实例所属的类作为第一个参数。其余的参数会被传递给对象构造器表达式 (对类的调用)。 的返回值应为新对象实例 (通常是 cls 的实例)。

方法来创建一个类的新实例,然后根据需要修改新创建的实例再将其返回。

如果 未返回一个 cls 的实例,则新实例的 方法就不会被执行。

的目的主要是允许不可变类型的子类 (例如 int, str 或 tuple) 定制实例创建过程。它也常会在自定义元类中被重载以便定制类创建过程。

在实例 (通过 ) 被创建之后,返回调用者之前调用。其参数与传递给类构造器表达式的参数相同。一个基类如果有 方法,则其所派生的类如果也有

协作构造完成的 (由 创建,并由 定制),所以 返回的值只能是 None,否则会在运行时引发 。

在实例将被销毁时调用。 这还被称为终结器或析构器(不适当)。 如果一个基类具有 方法,则其所派生的类如果也有 方法,就必须显式地调用它以确保实例基类部分的正确清除。

方法可以 (但不推荐!) 通过创建一个该实例的新引用来推迟其销毁。这被称为对象 重生。 是否会在重生的对象将被销毁时再次被调用是由具体实现决定的 ;当前的 实现只会调用一次。

当解释器退出时不会确保为仍然存在的对象调用 方法。

del x 并不直接调用 x.__del__() --- 前者会将 x 的引用计数减一,而后者仅会在 x 的引用计数变为零时被调用。

由于调用 方法时周边状况已不确定,在其执行期间发生的异常将被忽略,改为打印一个警告到 sys.stderr。特别地:

  • 可在任意代码被执行时启用,包括来自任意线程的代码。如果 需要接受锁或启用其他阻塞资源,可能会发生死锁,例如该资源已被为执行

  • 可以在解释器关闭阶段被执行。因此,它需要访问的全局变量(包含其他模块)可能已被删除或设为 None。Python 会保证先删除模块中名称以单个下划线打头的全局变量再删除其他全局变量;如果已不存在其他对此类全局变量的引用,这有助于确保导入的模块在 方法被调用时仍然可用。

由 内置函数调用以输出一个对象的“官方”字符串表示。如果可能,这应类似一个有效的 Python 表达式,能被用来重建具有相同取值的对象(只要有适当的环境)。如果这不可能,则应返回形式如 <...some useful description...> 的字符串。返回值必须是一个字符串对象。如果一个类定义了 但未定义 ,则在需要该类的实例的“非正式”字符串表示时也会使用 。

此方法通常被用于调试,因此确保其表示的内容包含丰富信息且无歧义是很重要的。

通过 以及内置函数 和 调用以生成一个对象的“非正式”或格式良好的字符串表示。返回值必须为一个 对象。

此方法与 的不同点在于 并不预期返回一个有效的 Python 表达式:可以使用更方便或更准确的描述信息。

内置类型 所定义的默认实现会调用 。

通过 调用以生成一个对象的字节串表示。这应该返回一个

通过 内置函数、扩展、 的求值以及 方法调用以生成一个对象的“格式化”字符串表示。 format_spec 参数为包含所需格式选项描述的字符串。 format_spec 参数的解读是由实现 的类型决定的,不过大多数类或是将格式化委托给某个内置类型,或是使用相似的格式化选项语法。

请参看 了解标准格式化语法的描述。

返回值必须为一个字符串对象。

在 3.4 版更改: object 本身的 __format__ 方法如果被传入任何非空字符,将会引发一个 。

以上这些被称为“富比较”方法。运算符号与方法名称的对应关系如下:x<y 调用 x.__lt__(y)x<=y 调用

如果指定的参数对没有相应的实现,富比较方法可能会返回单例对象 NotImplemented。按照惯例,成功的比较会返回 FalseTrue。不过实际上这些方法可以返回任意值,因此如果比较运算符是要用于布尔值判断(例如作为 if 语句的条件),Python 会对返回值调用 以确定结果为真还是假。

在默认情况下,object 通过使用 is 来实现 ,并在比较结果为假值时返回 并对结果取反,除非结果为 NotImplemented。 比较运算符之间没有其他隐含关系或默认实现;例如,(x<y or x==y) 为真并不意味着 x<=y。 要根据单根运算自动生成排序操作,请参看 。

请查看 的相关段落,了解创建可支持自定义比较运算并可用作字典键的 对象时要注意的一些事项。

这些方法并没有对调参数版本(在左边参数不支持该操作但右边参数支持时使用);而是 和 互为对方的反射, 和 互为对方的反射,而 和 则是它们自己的反射。如果两个操作数的类型不同,且右操作数类型是左操作数类型的直接或间接子类,则优先选择右操作数的反射方法,否则优先选择左操作数的方法。虚拟子类不会被考虑。

通过内置函数 调用以对哈希集的成员进行操作,属于哈希集的类型包括 、 以及 。 应该返回一个整数。对象比较结果相同所需的唯一特征属性是其具有相同的哈希值;建议的做法是把参与比较的对象全部组件的哈希值混在一起,即将它们打包为一个元组并对该元组做哈希运算。例如:

会从一个对象自定义的 方法返回值中截断为 Py_ssize_t 的大小。通常对 64 位构建为 8 字节,对 32 位构建为 4 字节。如果一个对象的 必须在不同位大小的构建上进行互操作,请确保检查全部所支持构建的宽度。做到这一点的简单方法是使用 python

如果一个类没有定义 方法,那么也不应该定义 操作;如果它定义了 但没有定义 ,则其实例将不可被用作可哈希集的项。如果一个类定义了可变对象并实现了 方法,则不应该实现 ,因为可哈希集的实现要求键的哈希集是不可变的(如果对象的哈希值发生改变,它将处于错误的哈希桶中)。

用户定义的类默认带有 和 方法;使用它们与任何对象(自己除外)比较必定不相等,并且 x.__hash__() 会返回一个恰当的值以确保 x ==

一个类如果重载了 且没有定义 则会将其 隐式地设为 时,该类的实例将在一个程序尝试获取其哈希值时正确地引发 ,并会在检测 isinstance(obj,

如果一个没有重载 的类需要去掉哈希支持,则应该在类定义中包含 __hash__ =

在默认情况下,str 和 bytes 对象的 值会使用一个不可预知的随机值“加盐”。 虽然它们在一个单独 Python 进程中会保持不变,但它们的值在重复运行的 Python

改变哈希值会影响集合的迭代次序。Python 也从不保证这个次序不会被改变(通常它在 32 位和 64 位构建上是不一致的)。

在 3.3 版更改: 默认启用哈希随机化。

调用此方法以实现真值检测以及内置的 bool() 操作;应该返回 FalseTrue。如果未定义此方法,则会查找并调用 并在其返回非零值时视对象的逻辑值为真。如果一个类既未定义 也未定义 则视其所有实例的逻辑值为真。

可以定义下列方法来自定义对类实例属性访问(x.name 的使用、赋值或删除)的具体含义.

当默认属性访问因引发 而失败时被调用 (可能是调用 时由于 name 不是一个实例属性或 self 的类关系树中的属性而引发了 ;或者是对 name 特性属性调用 时引发了 )。此方法应当返回(找到的)属性值或是引发一个 异常。

请注意如果属性是通过正常机制找到的, 就不会被调用。(这是在 和 之间故意设置的不对称性。)这既是出于效率理由也是因为不这样设置的话 将无法访问实例的其他属性。要注意至少对于实例变量来说,你不必在实例属性字典中插入任何值(而是通过插入到其他对象)就可以模拟对它的完全控制。请参阅下面的 方法了解真正获取对属性访问的完全控制权的办法。

此方法会无条件地被调用以实现对类实例属性的访问。如果类还定义了 ,则后者不会被调用,除非 显式地调用它或是引发了 。此方法应当返回(找到的)属性值或是引发一个 异常。为了避免此方法中的无限递归,其实现应该总是调用具有相同名称的基类方法来访问它所需要的任何属性,例如 object.__getattribute__(self,

此方法在作为通过特定语法或内置函数隐式地调用的结果的情况下查找特殊方法时仍可能会被跳过。参见 。

此方法在一个属性被尝试赋值时被调用。这个调用会取代正常机制(即将值保存到实例字典)。 name 为属性名称, value 为要赋给属性的值。

如果 想要赋值给一个实例属性,它应该调用同名的基类方法,例如 object.__setattr__(self,

类似于 但其作用为删除而非赋值。此方法应该仅在 del obj.name 对于该对象有意义时才被实现。

此方法会在对相应对象调用 时被调用。返回值必须为一个序列。 会把返回的序列转换为列表并对其排序。

特殊名称 __getattr____dir__ 还可被用来自定义对模块属性的访问。模块层级的 __getattr__ 函数应当接受一个参数,其名称为一个属性名,并返回计算结果值或引发一个 。如果通过正常查找即 未在模块对象中找到某个属性,则 __getattr__ 会在模块的 __dict__ 中查找,未找到时会引发一个 。如果找到,它会以属性名被调用并返回结果值。

__dir__ 函数应当不接受任何参数,并且返回一个表示模块中可访问名称的字符串序列。 此函数如果存在,将会重载一个模块中的标准 查找。

想要更细致地自定义模块的行为(设置属性和特性属性等待),可以将模块对象的 __class__ 属性设置为一个 的子类。例如:

定义模块的 __getattr__ 和设置模块的 __class__ 只会影响使用属性访问语法进行的查找 -- 直接访问模块全局变量(不论是通过模块内的代码还是通过对模块全局字典的引用)是不受影响的。

以下方法仅当一个包含该方法的类(称为 描述器 类)的实例出现于一个 所有者 类中的时候才会起作用(该描述器必须在所有者类或其某个上级类的字典中)。在以下示例中,“属性”指的是名称为所有者类 中的特征属性的键名的属性。

调用此方法以获取所有者类的属性(类属性访问)或该类的实例的属性(实例属性访问)。 可选的 owner 参数是所有者类而 instance 是被用来访问属性的实例,如果通过 owner 来访问属性则返回 None

此方法应当返回计算得到的属性值或是引发 异常。

指明 为带有一至二个参数的可调用对象。 Python 自身内置的描述器支持此规格定义;但是,某些第三方工具可能要求必须带两个参数。 Python 自身的 实现总是会传入两个参数,无论它们是否被要求提供。

调用此方法以设置 instance 指定的所有者类的实例的属性为新值 value

请注意,添加 或 会将描述器变成“数据描述器”。 更多细节请参阅 。

调用此方法以删除 instance 指定的所有者类的实例的属性。

模块解读为指定此对象定义所在的类(正确设置此属性有助于动态类属性的运行时内省)。对于可调用对象来说,它可以指明预期或要求提供一个特定类型(或子类)的实例作为第一个位置参数(例如,CPython 会为实现于 C 中的未绑定方法设置此属性)。

属性访问的默认行为是从一个对象的字典中获取、设置或删除属性。例如,a.x 的查找顺序会从 a.__dict__['x'] 开始,然后是 type(a).__dict__['x'],接下来依次查找 type(a) 的上级基类,不包括元类。

但是,如果找到的值是定义了某个描述器方法的对象,则 Python 可能会重载默认行为并转而发起调用描述器方法。这具体发生在优先级链的哪个环节则要根据所定义的描述器方法及其被调用的方式来决定。

描述器发起调用的开始点是一个绑定 a.x。参数的组合方式依 a 而定:

最简单但最不常见的调用方式是用户代码直接发起调用一个描述器方法: x.__get__(a)

函数是作为数据描述器来实现的。因此实例不能重载特性属性的行为。

  • 如果一个类定义的位置在某个基类中也有定义,则由基类位置定义的实例变量将不可访问(除非通过直接从基类获取其描述器的方式)。这会使得程序的含义变成未定义。未来可能会添加一个防止此情况的检查。

  • 非空的 __slots__ 不适用于派生自“可变长度”内置类型例如 、 和 的派生类。

当所在类派生子类时此方法就会被调用。cls 将指向新的子类。如果定义为一个普通实例方法,此方法将被隐式地转换为类方法。

传入一个新类的关键字参数会被传给父类的 __init_subclass__。为了与其他使用 __init_subclass__ 的类兼容,应当根据需要去掉部分关键字参数再将其余的传给基类,例如:

object.__init_subclass__ 的默认实现什么都不做,只在带任意参数调用时引发一个错误。

元类提示 metaclass 将被其它类型机制消耗掉,并不会被传给 __init_subclass__ 的实现。实际的元类(而非显式的提示)可通过 type(cls) 访问。

如果在类定义中出现的基类不是 的实例,则使用 __mro_entries__ 方法对其进行搜索,当找到结果时,它会以原始基类元组做参数进行调用。此方法必须返回类的元组以替代此基类被使用。元组可以为空,在此情况下原始基类将被忽略。

如果元类没有 __prepare__ 属性,则类命名空间将初始化为一个空的有序映射。

类主体会以(类似于) exec(body, globals(), namespace) 的形式被执行。普通调用与 的关键区别在于当类定义发生于函数内部时,词法作用域允许类主体(包括任何方法)引用来自当前和外部作用域的名称。

但是,即使当类定义发生于函数内部时,在类内部定义的方法仍然无法看到在类作用域层次上定义的名称。类变量必须通过实例的第一个形参或类方法来访问,或者是通过下一节中描述的隐式词法作用域的 __class__ 引用。

如果类主体中有任何方法引用了 __class__super,这个类对象会通过零参数形式的 . __class__ 所引用,这是由编译器所创建的隐式闭包引用。这使用零参数形式的 能够正确标识正在基于词法作用域来定义的类,而被用于进行当前调用的类或实例则是基于传递给方法的第一个参数来标识的。

type.__new__ 调用,以便能正确地初始化该类。 如果不这样做,在 Python 3.8 中将引发 。

在类对象创建之后,它会被传给包含在类定义中的类装饰器(如果有的话),得到的对象将作为已定义的类绑定到局部命名空间。

当通过 type.__new__ 创建一个新类时,提供以作为命名空间形参的对象会被复制到一个新的有序映射并丢弃原对象。这个新副本包装于一个只读代理中,后者则成为类对象的 属性。

3.3.4. 自定义实例及子类检查

以下方法被用来重载 和 内置函数的默认行为。

特别地,元类 实现了这些方法以便允许将抽象基类(ABC)作为“虚拟基类”添加到任何类或类型(包括内置类型),包括其他 ABC 之中。

请注意这些方法的查找是基于类的类型(元类)。它们不能作为类方法在实际的类中被定义。这与基于实例被调用的特殊方法的查找是一致的,只有在此情况下实例本身被当作是类。

按照 key 参数指定的类型返回一个表示泛型类的专门化对象。

调用此方法以实现内置函数 。应该返回对象的长度,以一个 >= 0 的整数表示。此外,如果一个对象未定义 方法而其 方法返回值为零,则在布尔运算中会被视为假值。

sys.maxsize 则某些特性 (例如 ) 可能会引发 。要通过真值检测来防止引发 OverflowError,对象必须定义 方法。

调用此方法以实现 。 应该返回对象长度的估计值(可能大于或小于实际长度)。 此长度应为一个 >= 0 的整数。 返回值也可以为 ,这会被视作与 __length_hint__ 方法完全不存在时一样处理。 此方法纯粹是为了优化性能,并不要求正确无误。

切片是通过下述三个专门方法完成的。以下形式的调用

其他形式以此类推。略去的切片项总是以 None 补全。

调用此方法以实现向 self[key] 赋值。注意事项与 相同。为对象实现此方法应该仅限于需要映射允许基于键修改值或添加键,或是序列允许元素被替换时。不正确的 key 值所引发的异常应与 方法的情况相同。

调用此方法以实现 self[key] 的删除。注意事项与 相同。为对象实现此方法应该权限于需要映射允许移除键,或是序列允许移除元素时。不正确的 key 值所引发的异常应与 方法的情况相同。

此方法由 . 在找不到字典中的键时调用以实现 dict 子类的 self[key]

此方法(如果存在)会被 内置函数调用以实现逆向迭代。它应当返回一个新的以逆序逐个迭代容器内所有对象的迭代器对象。

如果未提供 方法,则 内置函数将回退到使用序列协议 ( 和 )。支持序列协议的对象应当仅在能够提供比 所提供的实现更高效的实现时才提供 方法。

成员检测运算符 ( 和 ) 通常以对容器进行逐个迭代的方式来实现。 不过,容器对象可以提供以下特殊方法并采用更有效率的实现,这样也不要求对象必须为可迭代对象。

调用此方法以实现成员检测运算符。如果 itemself 的成员则应返回真,否则返回假。对于映射类型,此检测应基于映射的键而不是值或者键值对。

对于未定义 的对象,成员检测将首先尝试通过 进行迭代,然后再使用 的旧式序列迭代协议,参看

定义以下方法即可模拟数字类型。特定种类的数字不支持的运算(例如非整数不能进行位运算)所对应的方法应当保持未定义状态。

&, ^, |)。例如,求表达式 x + y 的值,其中 x 是具有 方法的类的一个实例,则会调用 x.__add__(y)。 方法应该等价于使用 和 ,它不应该被关联到 。请注意如果要支持三元版本的内置 函数,则 的定义应该接受可选的第三个参数。

如果这些方法中的某一个不支持与所提供参数进行运算,它应该返回 NotImplemented

调用这些方法来实现具有反射(交换)操作数的二进制算术运算 (+, -, *, @, /, &, ^, |)。这些成员函数仅会在左操作数不支持相应运算 且两个操作数类型不同时被调用。 例如,求表达式 x - y 的值,其中 y 是具有

请注意三元版的 并不会尝试调用 (因为强制转换规则会太过复杂)。

如果右操作数类型为左操作数类型的一个子类,且该子类提供了指定运算的反射方法,则此方法将先于左操作数的非反射方法被调用。 此行为可允许子类重载其祖先类的运算符。

&=, ^=, |=)。这些方法应该尝试进行自身操作 (修改 self) 并返回结果 (结果应该但并非必须为 self)。如果某个方法未被定义,相应的扩展算术赋值将回退到普通方法。例如,如果 x 是具有 方法的类的一个实例,则 x x.__add__(y)y.__radd__(x)。在某些情况下,扩展赋值可导致未预期的错误 (参见 ),但此行为实际上是数据模型的一个组成部分。

调用此方法以实现一元算术运算 (-, +, 和

调用这些方法以实现内置函数 , 和 。应当返回一个相应类型的值。

调用此方法以实现 以及 Python 需要无损地将数字对象转换为整数对象的场合(例如切片或是内置的 , 和 函数)。 存在此方法表明数字对象属于整数类型。 必须返回一个整数。

如果未定义 , 和 则相应的内置函数 , 和 将回退为 。

调用这些方法以实现内置函数 以及 函数 , 和 。 除了将 ndigits 传给 __round__() 的情况之外这些方法的返回值都应当是原对象截断为 (通常为 )。

上下文管理器 是一个对象,它定义了在执行 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需运行时上下文以执行代码块。 通常使用 with 语句(在 中描述),但是也可以通过直接调用它们的方法来使用。

上下文管理器的典型用法包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等等。

要了解上下文管理器的更多信息,请参阅 。

进入与此对象相关的运行时上下文。 语句将会绑定这个方法的返回值到 as 子句中指定的目标,如果有的话。

退出关联到此对象的运行时上下文。 各个参数描述了导致上下文退出的异常。 如果上下文是无异常地退出的,三个参数都将为 。

如果提供了异常,并且希望方法屏蔽此异常(即避免其被传播),则应当返回真值。 否则的话,异常将在退出此方法时按正常流程处理。

请注意 方法不应该重新引发被传入的异常,这是调用者的责任。

3.3.10. 定制类模式匹配中的位置参数

当在模式中使用类名称时,默认不允许模式中出现位置参数,例如 case MyClass(x, y) 通常是无效的,除非 MyClass 提供了特别支持。 要能使用这样的模式,类必须定义一个

该类变量可以被赋值为一个字符串元组。 当该类被用于带位置参数的类模式时,每个位置参数都将被转换为关键字参数,并使用 __match_args__ 中的对应值作为关键字。 缺失此属性就等价于将其设为 ()

对于自定义类来说,特殊方法的隐式发起调用仅保证在其定义于对象类型中时能正确地发挥作用,而不能定义在对象实例字典中。 该行为就是以下代码会引发异常的原因。:

以这种方式不正确地尝试发起调用一个类的未绑定方法有时被称为‘元类混淆’,可以通过在查找特殊方法时绕过实例的方式来避免:

协程也具有下面列出的方法,它们类似于生成器的对应方法 (参见 )。 但是,与生成器不同,协程并不直接支持迭代。

此方法会使得协程清理自身并退出。 如果协程被挂起,此方法会先委托给导致协程挂起的迭代器的 方法,如果存在该方法。 然后它会在挂起点引发 ,使得协程立即清理自身。 最后,协程会被标记为已结束执行,即使它根本未被启动。

当协程对象将要被销毁时,会使用以上处理过程来自动关闭。

异步迭代器 可以在其 __anext__ 方法中调用异步代码。

异步迭代器可在 语句中使用。

必须返回一个 异步迭代器 对象。

必须返回一个 可迭代对象 输出迭代器的下一结果值。 当迭代结束时应该引发 错误。

异步可迭代对象的一个示例:

3.4.4. 异步上下文管理器

异步上下文管理器上下文管理器 的一种,它能够在其 __aenter____aexit__ 方法中暂停执行。

异步上下文管理器可在 语句中使用。

在语义上类似于 ,仅有的区别是它必须返回一个 可等待对象

在语义上类似于 ,仅有的区别是它必须返回一个 可等待对象

异步上下文管理器类的一个示例:

如果只是想要一个独立的程序,以便用户不必预先安装 Python 即可下载和运行它,则不需要将 Python 编译成 C 代码。有许多工具可以检测程序所需的模块,并将这些模块与 Python 二进制程序捆绑在一起生成单个可执行文件。

一种方案是使用 freeze 工具,它在 Python 源码目录的 Tools/freeze 中提供。它能把 Python 字节码转换为 C 数组;一个 C 编译器可以将所有模块嵌入到一个新程序中,然后将其与标准 Python 模块链接。

它的工作原理是递归扫描源代码,获取两种格式的 import 语句,并在标准 Python 路径和源码目录(用于内置模块)检索这些模块。然后,把这些模块的 Python 字节码转换为 C 代码(可以利用 marshal 模块转换为代码对象的数组初始化器),并创建一个定制的配置文件,该文件仅包含程序实际用到的内置模块。然后,编译生成的 C 代码并将其与 Python 解释器的其余部分链接,形成一个自给自足的二进制文件,其功能与 Python 脚本代码完全相同。

下列包可以用于帮助创建控制台和 GUI 的可执行文件:

因为在函数内部某处添加了一条赋值语句,导致之前正常工作的代码报出 UnboundLocalError 错误,这可能是有点令人惊讶。

正常工作,但是以下代码

原因就是,当对某作用域内的变量进行赋值时,该变量将成为该作用域内的局部变量,并覆盖外部作用域中的同名变量。由于 foo 的最后一条语句为 x 分配了一个新值,编译器会将其识别为局部变量。因此,前面的 print(x) 试图输出未初始化的局部变量,就会引发错误。

在上面的示例中,可以将外部作用域的变量声明为全局变量以便访问:

与类和实例变量貌似但不一样,其实以上是在修改外部作用域的变量值,为了提示这一点,这里需要显式声明一下。

你可以使用 关键字在嵌套作用域中执行类似的操作:

函数内部只作引用的 Python 变量隐式视为全局变量。如果在函数内部任何位置为变量赋值,则除非明确声明为全局变量,否则均将其视为局部变量。

起初尽管有点令人惊讶,不过考虑片刻即可释然。一方面,已分配的变量要求加上 可以防止意外的副作用发生。另一方面,如果所有全局引用都要加上 global ,那处处都得用上 global 了。那么每次对内置函数或导入模块中的组件进行引用时,都得声明为全局变量。这种杂乱会破坏 global 声明用于警示副作用的有效性。

假设用 for 循环来定义几个取值各异的 lambda(即便是普通函数也一样):

以上会得到一个包含5个 lambda 函数的列表,这些函数将计算 x**2。大家或许期望,调用这些函数会分别返回 014916。然而,真的试过就会发现,他们都会返回 16

这是因为 x 不是 lambda 函数的内部变量,而是定义于外部作用域中的,并且 x 是在调用 lambda 时访问的——而不是在定义时访问。循环结束时 x 的值是 4 ,所以此时所有的函数都将返回 4**2 ,即 16 。通过改变 x 的值并查看 lambda 的结果变化,也可以验证这一点。

为了避免发生上述情况,需要将值保存在 lambda 局部变量,以使其不依赖于全局 x 的值:

以上 n=x 创建了一个新的 lambda 本地变量 n,并在定义 lambda 时计算其值,使其与循环当前时点的 x 值相同。这意味着 n 的值在第 1 个 lambda 中为 0 ,在第 2 个 lambda 中为 1 ,在第 3 个中为 2,依此类推。因此现在每个 lambda 都会返回正确结果:

请注意,上述表现并不是 lambda 所特有的,常规的函数也同样适用。

请在代码文件的首部就导入模块。这样代码所需的模块就一目了然了,也不用考虑模块名是否在作用域内的问题。每行导入一个模块则增删起来会比较容易,每行导入多个模块则更节省屏幕空间。

按如下顺序导入模块就是一种好做法:

为了避免循环导入引发的问题,有时需要将模块导入语句移入函数或类的内部。Gordon McMillan 的说法如下:

当两个模块都采用 "import <module>" 的导入形式时,循环导入是没有问题的。但如果第 2 个模块想从第 1 个模块中取出一个名称("from module import name")并且导入处于代码的最顶层,那导入就会失败。原因是第 1 个模块中的名称还不可用,这时第 1 个模块正忙于导入第 2 个模块呢。

如果只是在一个函数中用到第 2 个模块,那这时将导入语句移入该函数内部即可。当调用到导入语句时,第 1 个模块将已经完成初始化,第 2 个模块就可以进行导入了。

如果某些模块是平台相关的,可能还需要把导入语句移出最顶级代码。这种情况下,甚至有可能无法导入文件首部的所有模块。于是在对应的平台相关代码中导入正确的模块,就是一种不错的选择。

只有为了避免循环导入问题,或有必要减少模块初始化时间时,才把导入语句移入类似函数定义内部的局部作用域。如果根据程序的执行方式,许多导入操作不是必需的,那么这种技术尤其有用。如果模块仅在某个函数中用到,可能还要将导入操作移入该函数内部。请注意,因为模块有一次初始化过程,所以第一次加载模块的代价可能会比较高,但多次加载几乎没有什么花费,代价只是进行几次字典检索而已。即使模块名超出了作用域,模块在

请利用函数参数列表中的标识符 *** 归集实参;结果会是元组形式的位置实参和字典形式的关键字实参。然后就可利用 *** 在调用其他函数时传入这些实参:

是指出现在函数定义中的名称,而 则是在调用函数时实际传入的值。 形参定义了一个函数能接受何种类型的实参。 例如,对于以下函数定义:

或许大家很想知道,为什么在 y 中添加一个元素时, x 也会改变。

产生这种结果有两个因素:

  1. 变量只是指向对象的一个名称。执行 y = x 并不会创建列表的副本——而只是创建了一个新变量 y,并指向 x 所指的同一对象。这就意味着只存在一个列表对象,xy 都是对它的引用。

  2. 列表属于 对象,这意味着它的内容是可以修改的。

在调用 append() 之后,该可变对象的内容由 [] 变为 [10]。由于 x 和 y 这两个变量引用了同一对象,因此用其中任意一个名称所访问到的都是修改后的值

如果把赋给 x 的对象换成一个不可变对象:

可见这时 xy 就不再相等了。因为整数是 对象,在执行 x = x + 1 时,并不会修改整数对象 5,给它加上 1;而是创建了一个新的对象(整数对象 6 )并将其赋给 x (也就是改变了 x 所指向的对象)。在赋值完成后,就有了两个对象(整数对象 65 )和分别指向他俩的两个变量( x 现在指向 6y 仍然指向

某些操作(例如 y.append(10)y.sort() )会直接修改原对象,而看上去相似的另一些操作(例如 y = y + [10]sorted(y) )则会创建新的对象。通常在 Python 中(以及所有标准库),直接修改原对象的方法将会返回 None ,以助避免混淆这两种不同类型的操作。因此如果误用了 y.sort() 并期望返回 y 的有序副本,则结果只会是 None ,这可能就能让程序引发一条容易诊断的错误。

不过还存在一类操作,用不同的类型执行相同的操作有时会发生不同的行为:即增量赋值运算符。例如,+= 会修改列表,但不会修改元组或整数(a_list += [1, 2, 3]

  • 对于一个可变对象( 、 、 等等),可以利用某些特定的操作进行修改,所有引用它的变量都会反映出改动情况。

  • 对于一个不可变对象( 、 、 等),所有引用它的变量都会给出相同的值,但所有改变其值的操作都将返回一个新的对象。

如要知道两个变量是否指向同一个对象,可以利用 运算符或内置函数 。

请记住,Python 中的实参是通过赋值传递的。由于赋值只是创建了对象的引用,所以调用方和被调用方的参数名都不存在别名,本质上也就不存在按引用调用的方式。通过以下几种方式,可以得到所需的效果。

  1. 这差不多是最明晰的解决方案了。

  2. 使用全局变量。这不是线程安全的方案,不推荐使用。

  3. 传递一个可变(即可原地修改的) 对象:

  4. 传入一个接收可变对象的字典:

  5. 或者把值用类实例封装起来:

    没有什么理由要把问题搞得这么复杂。

最佳选择就是返回一个包含多个结果值的元组。

有两种选择:嵌套作用域、可调用对象。假定需要定义 linear(a,b) ,其返回结果是一个计算出 a*x+b 的函数 f(x)。 采用嵌套作用域的方案如下:

或者可采用可调用对象:

可调用对象的方案有个缺点,就是速度稍慢且生成的代码略长。不过值得注意的是,同一组可调用对象能够通过继承来共享签名(类声明):

对象可以为多个方法的运行状态进行封装:

假定 x 是一个用户自定义类的实例,dir(x) 将返回一个按字母排序的名称列表,其中包含了实例的属性及由类定义的方法和属性。

一般而言这是无法实现的,因为对象并不存在真正的名称。赋值本质上是把某个名称绑定到某个值上;defclass 语句同样如此,只是值换成了某个可调用对象。比如以下代码:

可以不太严谨地说,上述类具有一个名称:即便它绑定了两个名称并通过名称 B 发起调用,可是创建出来的实例仍被视为是类 A 的实例。但无法说出实例的名称是 a 还是 b,因为这两个名称都被绑定到同一个值上了。

代码一般没有必要去“知晓”某个值的名称。通常这种需求预示着还是改变方案为好,除非真的是要编写内审程序。

这就像要知道家门口的那只猫的名字一样:猫(对象)自己不会说出它的名字,它根本就不在乎自己叫什么——所以唯一方法就是问一遍你所有的邻居(命名空间),这是不是他们家的猫(对象)……

……并且如果你发现它有很多名字或根本没有名字,那也不必惊讶!

逗号不是 Python 的运算符。 请看以下例子:

由于逗号不是运算符,而只是表达式之间的分隔符,因此上述代码就相当于:

对于各种赋值运算符( =+= 等)来说同样如此。他们并不是真正的运算符,而只是赋值语句中的语法分隔符。

在 Python 2.5 引入上述语法之前,通常的做法是使用逻辑运算符:

然而这种做法并不保险,因为当 on_true 为布尔值“假”时,结果将会出错。所以肯定还是采用 ... if ... else ... 形式为妙。

可以。通常是在 中嵌套 lambda 来实现的。请参阅以下三个来自 Ulf Bartelt 的示例代码:

请不要在家里尝试,骚年!

函数参数列表中的斜杠表示在它之前的形参全都仅限位置形参。仅限位置形参没有可供外部使用的名称。在调用仅接受位置形参的函数时,实参只会根据位置映射到形参上。假定 是一个仅接受位置形参的函数。 它的帮助文档如下所示:

形参列表尾部的斜杠说明,两个形参都是仅限位置形参。因此,用关键字参数调用 将会引发错误:

要给出八进制数,需在八进制数值前面加上一个零和一个小写或大写字母 "o" 作为前缀。例如,要将变量 "a" 设为八进制的 "10" (十进制的 8),写法如下:

十六进制数也很简单。只要在十六进制数前面加上一个零和一个小写或大写的字母 "x"。十六进制数中的字母可以为大写或小写。比如在 Python 解释器中输入:

这主要是为了让 i % j 的正负与 j 一致,如果期望如此,且期望如下等式成立:

那么整除就必须返回向下取整的结果。C 语言同样要求保持这种一致性,于是编译器在截断 i // j 的结果时需要让 i % j 的正负与

对于 i % j 来说 j 为负值的应用场景实际上是非常少的。 而 j 为正值的情况则非常多,并且实际上在所有情况下让 i

对于整数,可使用内置的 类型构造器,例如 int('144') == 144。 类似地,可使用

如果只是想把字符串转为数字,请不要使用内置函数 。 的速度慢很多且存在安全风险:别人可能会传入带有不良副作用的 Python 表达式。比如可能会传入 __import__('os').system("rm -rf $HOME") ,这会把 home 目录给删了。

还有把数字解析为 Python 表达式的后果,因此如 eval('09') 将会导致语法错误,因为 Python 不允许十进制数带有前导

无法修改,因为字符串是不可变对象。 在大多数情况下,只要将各个部分组合起来构造出一个新字符串即可。如果需要一个能原地修改 Unicode 数据的对象,可以试试 对象或

  • 最好的做法是采用一个字典,将字符串映射为函数。其主要优势就是字符串不必与函数名一样。这也是用来模拟 case 结构的主要技巧:

  • 请注意 可用于任何对象,包括类、类实例、模块等等。

    标准库就多次使用了这个技巧,例如:

可以使用 S.rstrip("\r\n") 从字符串 S 的末尾删除所有的换行符,而不删除其他尾随空格。如果字符串 S 表示多行,且末尾有几个空行,则将删除所有空行的换行符:

由于通常只在一次读取一行文本时才需要这样做,所以使用 S.rstrip() 这种方式工作得很好。

总的来说,这是个棘手的问题。在进一步讨论之前,首先应该记住以下几件事:

  • 不同的 Python 实现具有不同的性能特点。 本 FAQ 着重解答的是 。

  • 不同操作系统可能会有不同表现,尤其是涉及 I/O 和多线程时。

  • 在尝试优化代码 之前 ,务必要先找出程序中的热点(请参阅 模块)。

  • 编写基准测试脚本,在寻求性能提升的过程中就能实现快速迭代(请参阅

  • 强烈建议首先要保证足够高的代码测试覆盖率(通过单元测试或其他技术),因为复杂的优化有可能会导致代码回退。

话虽如此,Python 代码的提速还是有很多技巧的。以下列出了一些普适性的原则,对于让性能达到可接受的水平会有很大帮助:

  • 相较于试图对全部代码铺开做微观优化,优化算法(或换用更快的算法)可以产出更大的收益。

  • 使用正确的数据结构。参考 和 模块的文档。

  • 如果标准库已为某些操作提供了基础函数,则可能(当然不能保证)比所有自编的函数都要快。对于用 C 语言编写的基础函数则更是如此,比如内置函数和一些扩展类型。例如,一定要用内置方法 或 函数进行排序(某些高级用法的示例请参阅 )。

  • 抽象往往会造成中间层,并会迫使解释器执行更多的操作。如果抽象出来的中间层级太多,工作量超过了要完成的有效任务,那么程序就会被拖慢。应该避免过度的抽象,而且往往也会对可读性产生不利影响,特别是当函数或方法比较小的时候。

如果你已经达到纯 Python 允许的限制,那么有一些工具可以让你走得更远。 例如, 可以将稍微修改的 Python 代码版本编译为 C 扩展,并且可以在许多不同的平台上使用。 Cython 可以利用编译(和可选的类型注释)来使代码明显快于解释运行时的速度。 如果您对 C 编程技能有信心,也可以自己

对象是不可变的,因此连接多个字符串的效率会很低,因为每次连接都会创建一个新的对象。一般情况下,总耗时与字符串总长是二次方的关系。

如果要连接多个 对象,通常推荐的方案是先全部放入列表,最后再调用 :

(还有一种合理高效的习惯做法,就是利用 )

如果要连接多个 对象,推荐做法是用 对象的原地连接操作( += 运算符)追加数据:

类型构造器 tuple(seq) 可将任意序列(实际上是任意可迭代对象)转换为数据项和顺序均不变的元组。

('a', 'b', 'c') 。 如果参数就是元组,则不会创建副本而是返回同一对象,因此如果无法确定某个对象是否为元组时,直接调用 也没什么代价。

类型构造器 list(seq) 可将任意序列或可迭代对象转换为数据项和顺序均不变的列表。例如,list((1, 2, 3)) 会生成 [1, 2,

Python 序列的索引可以是正数或负数。索引为正数时,0 是第一个索引值, 1 为第二个,依此类推。索引为负数时,-1 为倒数第一个索引值,-2 为倒数第二个,依此类推。可以认为 seq[-n] 就相当于 seq[len(seq)-n]

使用负数序号有时会很方便。 例如 S[:-1] 就是原字符串去掉最后一个字符,这可以用来移除某个字符串末尾的换行符。

原序列不会变化,而是构建一个逆序的新副本以供遍历。

许多完成此操作的的详细介绍,可参阅 Python Cookbook:

如果列表允许重新排序,不妨先对其排序,然后从列表末尾开始扫描,依次删除重复项:

如果列表的所有元素都能用作集合的键(即都是 ),以下做法速度往往更快:

以上操作会将列表转换为集合,从而删除重复项,然后返回成列表。

类似于删除重复项,一种做法是反向遍历并根据条件删除。不过更简单快速的做法就是切片替换操作,采用隐式或显式的正向迭代遍历。以下是三种变体写法:

列表推导式可能是最快的。

列表在时间复杂度方面相当于 C 或 Pascal 的数组;主要区别在于,Python 列表可以包含多种不同类型的对象。

array 模块也提供了一些创建具有紧凑格式的固定类型数组的方法,但其索引访问速度比列表慢。 并请注意 NumPy 和其他一些第三方包也定义了一些各具特色的数组类结构。

若要得到 Lisp 风格的列表,可以用元组模拟 cons 元素:

若要具备可变性,可以不用元组而是用列表。模拟 lisp car 函数的是 lisp_list[0] ,模拟 cdr 函数的是 lisp_list[1] 。仅当真正必要时才会这么用,因为通常这种用法要比 Python 列表慢得多。

多维数组或许会用以下方式建立:

但如果给某一项赋值,结果会同时在多个位置体现出来:

原因在于用 * 对列表执行重复操作并不会创建副本,而只是创建现有对象的引用。 *3 创建的是包含 3 个引用的列表,每个引用指向的是同一个长度为 2 的列表。1 处改动会体现在所有地方,这一定不是应有的方案。

推荐做法是先创建一个所需长度的列表,然后将每个元素都填充为一个新建列表。

以上生成了一个包含 3 个列表的列表,每个子列表的长度为 2。也可以采用列表推导式:

或者你还可以使用提供矩阵类型的扩展包;其中最著名的是 。

这是由两个因素共同导致的,一是增强赋值运算符属于 赋值 运算符,二是 Python 可变和不可变对象之间的差别。

只要元组的元素指向可变对象,这时对元素进行增强赋值,那么这里介绍的内容都是适用的。在此只以 list+= 举例。

触发异常的原因显而易见: 1 会与指向(1)的对象 a_tuple[0] 相加,生成结果对象 2,但在试图将运算结果 2 赋值给元组的 0 号元素时就会报错,因为元组元素的指向无法更改。

其实在幕后,上述增强赋值语句的执行过程大致如下:

由于元组是不可变的,因此赋值这步会引发错误。

这时触发异常会令人略感惊讶,更让人吃惊的是虽有报错,但加法操作却生效了:

要明白为何会这样,需要知道 (a) 如果一个对象实现了 __iadd__ 魔法方法,在执行 += 增强赋值时就会调用它,并采纳其返回值;(b) 对于列表而言,__iadd__ 相当于在列表上调用 extend 并返回该列表。因此对于列表可以说 += 就是 list.extend 的“快捷方式”:

a_list 所引用的对象已被修改,而引用被修改对象的指针又重新被赋值给 a_list。 赋值的最终结果没有变化,因为它是引用 a_list 之前所引用的同一对象的指针,但仍然发生了赋值操作。

因此,在此元组示例中,发生的事情等同于:

__iadd__ 成功执行,因此列表得到了扩充,但是虽然 result 指向了 a_tuple[0] 已经指向的同一对象,最后的赋值仍然导致了报错,因为元组是不可变的。

将它们合并到元组的迭代器中,对结果列表进行排序,然后选择所需的元素。

类是通过执行 class 语句创建的某种对象的类型。创建实例对象时,用 Class 对象作为模板,实例对象既包含了数据(属性),又包含了数据类型特有的代码(方法)。

类可以基于一个或多个其他类(称之为基类)进行创建。基类的属性和方法都得以继承。这样对象模型就可以通过继承不断地进行细化。比如通用的 Mailbox 类提供了邮箱的基本访问方法.,它的子类 MboxMailbox

方法是属于对象的函数,对于对象 x ,通常以 x.name(arguments...) 的形式调用。方法以函数的形式给出定义,位于类的定义内:

请注意 还会检测派生自 的虚继承。 因此对于已注册的类,即便没有直接或间接继承自抽象基类,对抽象基类的检测都将返回 True 。要想检测“真正的继承”,请扫描类的 :

请注意,大多数程序不会经常用 对用户自定义类进行检测。 如果是自已开发的类,更合适的面向对象编程风格应该是在类中定义多种方法,以封装特定的行为,而不是检查对象属于什么类再据此干不同的事。假定有如下执行某些操作的函数:

更好的方法是在所有类上定义一个 search() 方法,然后调用它:

委托是一种面向对象的技术(也称为设计模式)。假设对象 x 已经存在,现在想要改变其某个方法的行为。可以创建一个新类,其中提供了所需修改方法的新实现,而将所有其他方法都委托给 x 的对应方法。

Python 程序员可以轻松实现委托。比如以下实现了一个类似于文件的类,只是会把所有写入的数据转换为大写:

这里 UpperOut 类重新定义了 write() 方法,在调用下层的 self._outfile.write() 方法之前,会将参数字符串转换为大写。其他所有方法则都被委托给下层的 self._outfile 对象。委托是通过 __getattr__ 方法完成的;请参阅 了解有关控制属性访问的更多信息。

请注意,更常见情况下,委托可能会变得比较棘手。如果属性既需要写入又需要读取,那么类还必须定义 __setattr__() 方法,而这时就必须十分的小心。基础的 __setattr__() 实现代码大致如下:

大多数 __setattr__() 实现必须修改 self.__dict__ 来为自身保存局部状态,而不至于引起无限递归。

可以为基类赋一个别名并基于该别名进行派生。这样只要修改赋给该别名的值即可。顺便提一下,如要动态地确定(例如根据可用的资源)该使用哪个基类,这个技巧也非常方便。例如:

Python 支持静态数据和静态方法(以 C++ 或 Java 的定义而言)。

静态数据只需定义一个类属性即可。若要为属性赋新值,则必须在赋值时显式使用类名:

c 自身重载,或者被从 c.__class__ 回溯到基类 C 的搜索路径上的某个类所重载。

注意:在 C 的某个方法内部,像 self.count = 42 这样的赋值将在 self 自身的字典中新建一个名为 "count" 的不相关实例。 想要重新绑定类静态数据名称就必须总是指明类名,无论是在方法内部还是外部:

不过为了获得静态方法的效果,还有一种做法直接得多,也即使用模块级函数即可:

如果代码的结构化比较充分,每个模块只定义了一个类(或者多个类的层次关系密切相关),那就具备了应有的封装。

这个答案实际上适用于所有方法,但问题通常首先出现于构造函数的应用场景中。

在 C++ 中,代码会如下所示:

在 Python 中,只能编写一个构造函数,并用默认参数捕获所有情况。例如:

这不完全等同,但在实践中足够接近。

也可以试试采用变长参数列表,例如:

上述做法同样适用于所有方法定义。

以双下划线打头的变量名会被“破坏”,以便以一种简单高效的方式定义类私有变量。任何形式为 __spam 的标识符(至少前缀两个下划线,至多后缀一个下划线)文本均会被替换为 _classname__spam,其中 classname 为去除了全部前缀下划线的当前类名称。

这并不能保证私密性:外部用户仍然可以访问 "_classname__spam" 属性,私有变量值也在对象的 __dict__ 中可见。 许多 Python 程序员根本不操心要去使用私有变量名。

del 语句不一定调用 __del__() —— 它只是减少对象的引用计数,如果(引用计数)达到零,才会调用 __del__()

如果数据结构包含循环链接(比如树的每个子节点都带有父节点的引用,而每个父节点也带有子节点的列表),则引用计数永远不会回零。尽管 Python 偶尔会用某种算法检测这种循环引用,但在数据结构的最后一条引用消失之后,垃圾收集器可能还要过段时间才会运行,因此 __del__() 方法可能会在不方便和随机的时刻被调用。这对于重现一个问题,是非常不方便的。更糟糕的是,各个对象的 __del__() 方法是以随机顺序执行的。虽然可以运行 来强制执行垃圾回收工作,但 仍会存在 一些对象永远不会被回收的失控情况。

尽管有垃圾回收器的存在,但为对象定义显式的 close() 方法,只要一用完即可供调用,这依然是一个好主意。这样 close() 方法即可删除引用子对象的属性。请勿直接调用 __del__() —— 而

另一种避免循环引用的做法是利用 模块,该模块允许指向对象但不增加其引用计数。例如,树状数据结构应该对父节点和同级节点使用弱引用(如果真要用的话!)

最后提一下,如果 __del__() 方法引发了异常,会将警告消息打印到 。

返回一个整数,该整数在对象的生命周期内保证是唯一的。 因为在 CPython 中,这是对象的内存地址,所以经常发生在从内存中删除对象之后,下一个新创建的对象被分配在内存中的相同位置。 这个例子说明了这一点:

这两个 id 属于不同的整数对象,之前先创建了对象,执行 id() 调用后又立即被删除了。若要确保检测 id 时的对象仍处于活动状态,请再创建一个对该对象的引用:

身份相等性最重要的特性就是对象总是等同于自身,a is a 一定返回 True。身份相等性测试的速度通常比相等性测试要快。而且与相等性测试不一样,身份相等性测试会确保返回布尔值

但是,身份相等性测试 只能 在对象身份确定的场景下才可替代相等性测试。一般来说,有以下3种情况对象身份是可以确定的:

1) 赋值操作创建了新的名称但没有改变对象身份。 在赋值操作 new = old 之后,可以保证 new is old

2) 将对象置入存放对象引用的容器,对象身份不会改变。在列表赋值操作 s[0] = x 之后,可以保证 s[0] is x

3) 单例对象,也即该对象只能存在一个实例。在赋值操作 a = Noneb = None 之后,可以保证 a

其他大多数情况下,都不建议使用身份相等性测试,而应采用相等性测试。尤其是不应将身份相等性测试用于检测常量值,例如 和 ,因为他们并不一定是单例对象:

同样地,可变容器的新实例,对象身份一定不同:

在标准库代码中,给出了一些正确使用对象身份测试的常见模式:

1) 正如 所推荐的,对象身份测试是 None 值的推荐检测方式。这样的代码读起来就像自然的英文,并可以避免与其他可能为布尔值且计算结果为 False 的对象相混淆。

3) 编写容器的实现代码时,有时需要用对象身份测试来加强相等性检测。这样代码就不会被 float('NaN') 这类与自身不相等的对象所干扰。

缓存方法的两个主要工具是 和 。 前者在实例层级上存储结果而后者在类层级上存储结果。

cached_property 方式仅适用于不接受任何参数的方法。 它不会创建对实例的引用。 被缓存的方法结果将仅在实例的生存其内被保留。

lru_cache 方式适用于具有可哈希参数的方法。 它会创建对实例的引用,除非特别设置了传入弱引用。

最少近期使用算法的优点是缓存会受指定的 maxsize 限制。 它的缺点是实例会保持存活,直到其达到生存期或者缓存被清空。

这个例子演示了几种不同的方式:

上面的例子假定 station_id 从不改变。 如果相关实例属性是可变对象,则 cached_property 方式就不再适用,因为它无法检测到属性的改变。

lru_cache 方式仍然能够适用,但类需要定义 __eq____hash__ 方法以便缓存能够检测到相关属性的更新:

当首次导入模块时(或当前已编译文件创建之后源文件发生了改动),在``.py``文件所在目录的``__pycache__``子目录下会创建一个包含已编译代码的``.pyc``文件。该``.pyc``文件的名称开头部分将与``.py``文件名相同,并以``.pyc``为后缀,中间部分则依据创建它的``python``版本而各不相同。(详见 。)

.pyc 文件有可能会无法创建,原因之一是源码文件所在的目录存在权限问题,这样就无法创建 __pycache__ 子目录。假如以某个用户开发程序而以另一用户运行程序,就有可能发生权限问题,测试 Web 服务器就属于这种情况。

除非设置了 环境变量,否则导入模块并且 Python 能够创建``__pycache__``子目录并把已编译模块写入该子目录(权限、存储空间等等)时,.pyc 文件就将自动创建。

在最高层级运行的 Python 脚本不会被视为经过了导入操作,因此不会创建 .pyc 文件。假定有一个最高层级的模块文件 foo.py,它导入了另一个模块 xyz.py,当运行 xyz 是被导入的,但不会为 foo 创建 .pyc 文件,因为 foo.py 不是被导入的。

若要为 foo 创建 .pyc 文件 —— 即为未做导入的模块创建 .pyc 文件 —— 可以利用 和 模块。

模块能够手动编译任意模块。 一种做法是交互式地使用该模块中的 compile() 函数:

这将会将 .pyc 文件写入与 foo.py 相同位置下的 __pycache__ 子目录(或者你也可以通过可选参数 cfile 来重载该行为)。

模块自动编译一个或多个目录下的所有文件。只要在命令行提示符中运行 compileall.py 并给出要编译的 Python 文件所在目录路径即可:

模块可以查看预定义的全局变量 __name__ 获悉自己的名称。如其值为 '__main__' ,程序将作为脚本运行。通常,许多通过导入使用的模块同时也提供命令行接口或自检代码,这些代码只在检测到处于 __name__

问题是解释器将执行以下步骤:

  • foo 创建空的全局变量

  • 编译 foo 并开始执行

  • bar 创建空的全局变量

  • 编译 bar 并开始执行

  • bar 导入 foo (该步骤无操作,因为已经有一个名为 foo 的模块)。

最后一步失败了,因为 Python 还没有完成对 foo 的解释,foo 的全局符号字典仍然是空的。

当你使用 import foo ,然后尝试在全局代码中访问 foo.foo_var 时,会发生同样的事情。

这个问题有(至少)三种可能的解决方法。

Guido van Rossum 建议完全避免使用 from <module> import ... ,并将所有代码放在函数中。全局变量和类变量的初始化只应使用常量或内置函数。这意味着导入模块中的所有内容都以

Jim Roskind 建议每个模块都应遵循以下顺序:

  • 导出(全局变量、函数和不需要导入基类的类)

  • 本模块的功能代码(包括根据导入值进行初始化的全局变量)。

Matthias Urlichs 建议对代码进行重构,使得递归导入根本就没必要发生。

这些解决方案并不相互排斥。

出于效率和一致性的原因,Python 仅在第一次导入模块时读取模块文件。否则,在一个多模块的程序中,每个模块都会导入相同的基础模块,那么基础模块将会被一而再、再而三地解析。如果要强行重新读取已更改的模块,请执行以下操作:

警告:这种技术并非万无一失。尤其是模块包含了以下语句时:

仍将继续使用前一版的导入对象。如果模块包含了类的定义,并 不会 用新的类定义更新现有的类实例。这样可能会导致以下矛盾的行为:

只要把类对象的 id 打印出来,问题的性质就会一目了然:

我要回帖

更多关于 invalid的意思 的文章

 

随机推荐