3. 数据模型 — Python 文档

来自菜鸟教程
Python/docs/3.8/reference/datamodel
跳转至:导航、​搜索

3. 数据模型

3.1. 对象、值和类型

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

每个对象都有一个身份、一个类型和一个值。 对象的 标识 一旦创建就永远不会改变; 您可以将其视为对象在内存中的地址。 'is' 运算符比较两个对象的身份; id() 函数返回一个表示其身份的整数。

对象的类型决定了该对象支持的操作(例如,“它有长度吗?”)并且还定义了该类型对象的可能值。 type() 函数返回一个对象的类型(它是一个对象本身)。 与其身份一样,对象的 类型 也是不可更改的。 1

某些对象的 可以更改。 值可以改变的对象被称为 mutable; 其值一旦创建就不可更改的对象称为 immutable。 (包含对可变对象的引用的不可变容器对象的值可以在后者的值改变时改变;但是容器仍然被认为是不可变的,因为它包含的对象集合不能改变。 因此,不变性与具有不可改变的值并不严格相同,它更微妙。)一个对象的可变性由它的类型决定; 例如,数字、字符串和元组是不可变的,而字典和列表是可变的。

对象永远不会被显式销毁; 但是,当它们无法访问时,它们可能会被垃圾收集。 允许一个实现推迟垃圾收集或完全忽略它——垃圾收集如何实现是一个实现质量的问题,只要没有收集到仍然可以访问的对象。

请注意,使用实现的跟踪或调试工具可能会使通常可收集的对象保持活动状态。 另请注意,使用 'try...except' 语句捕获异常可能会使对象保持活动状态。

一些对象包含对“外部”资源的引用,例如打开的文件或窗口。 据了解,这些资源在对象被垃圾回收时会被释放,但由于垃圾回收并不能保证一定会发生,因此此类对象也提供了显式的释放外部资源的方式,通常是close()方法。 强烈建议程序明确关闭此类对象。 'tryfinally'语句和'with'语句提供了方便的方法来做到这一点。

一些对象包含对其他对象的引用; 这些被称为 容器 。 容器的例子是元组、列表和字典。 引用是容器值的一部分。 在大多数情况下,当我们谈论容器的价值时,我们暗示的是价值,而不是所包含对象的身份; 然而,当我们谈论容器的可变性时,只隐含了直接包含的对象的身份。 因此,如果不可变容器(如元组)包含对可变对象的引用,则如果该可变对象发生更改,则其值也会更改。

类型几乎影响对象行为的所有方面。 甚至对象标识的重要性在某种意义上也会受到影响:对于不可变类型,计算新值的操作实际上可能返回对任何具有相同类型和值的现有对象的引用,而对于可变对象,这是不允许的。 例如,在 a = 1; b = 1 之后,ab 可能会或可能不会引用值为 1 的同一个对象,具体取决于实现,但在 c = []; d = [] 之后, cd 保证引用两个不同的、唯一的、新创建的空列表。 (请注意,c = d = [] 将相同的对象分配给 cd。)


3.2. 标准类型层次结构

下面是 Python 内置的类型列表。 扩展模块(用 C、Java 或其他语言编写,取决于实现)可以定义其他类型。 Python 的未来版本可能会向类型层次结构添加类型(例如,有理数、有效存储的整数数组等),尽管这些添加通常会通过标准库提供。

下面的一些类型描述包含一个列出“特殊属性”的段落。 这些是提供对实现的访问的属性,不用于一般用途。 他们的定义将来可能会改变。

没有任何

这种类型只有一个值。 有一个具有此值的对象。 该对象通过内置名称 None 访问。 它在许多情况下用于表示没有值,例如,它是从不显式返回任何内容的函数中返回的。 它的真值是假的。

未实现

这种类型只有一个值。 有一个具有此值的对象。 该对象通过内置名称 NotImplemented 访问。 如果数值方法和富比较方法没有实现对提供的操作数的操作,它们应该返回这个值。 (然后解释器将尝试反射操作,或其他一些回退,这取决于操作符。)它的真值是真的。

有关更多详细信息,请参阅 实现算术运算

省略

这种类型只有一个值。 有一个具有此值的对象。 该对象通过文字 ... 或内置名称 Ellipsis 访问。 它的真值是真的。

numbers.Number

它们由数字文字创建,并由算术运算符和算术内置函数作为结果返回。 数字对象是不可变的; 一旦创造了它们的价值就永远不会改变。 Python 数字当然与数学数字密切相关,但受到计算机中数字表示的限制。

__repr__()__str__() 计算的数字类的字符串表示具有以下属性:

  • 它们是有效的数字文字,当传递给它们的类构造函数时,会产生一个具有原始数字值的对象。

  • 如果可能,表示以 10 为底。

  • 未显示前导零,可能除了小数点前的单个零。

  • 未显示尾随零,可能除了小数点后的单个零。

  • 仅当数字为负时才显示符号。

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

numbers.Integral

这些代表来自数学整数集(正数和负数)的元素。

有两种类型的整数:

整数 (int)

这些代表无限范围内的数字,仅受可用(虚拟)内存的影响。 出于移位和掩码操作的目的,假设使用二进制表示,并且负数以 2 的补码的变体表示,这会产生向左延伸的无限符号位串的错觉。

布尔值 (bool)

这些代表真值 False 和 True。 表示值 FalseTrue 的两个对象是唯一的布尔对象。 布尔类型是整数类型的子类型,布尔值的行为分别类似于值 0 和 1,在几乎所有上下文中,例外是当转换为字符串时,字符串 "False" 或 [X218X ] 分别返回。

整数表示规则旨在对涉及负整数的移位和掩码操作给出最有意义的解释。

numbers.Real (float)

这些代表机器级双精度浮点数。 对于可接受的范围和溢出处理,您受底层机器架构(以及 C 或 Java 实现)的支配。 Python 不支持单精度浮点数; 处理器和内存使用量的节省通常是使用这些的原因,但与在 Python 中使用对象的开销相比,这是相形见绌的,因此没有理由用两种浮点数使语言复杂化。

numbers.Complex (complex)

这些将复数表示为一对机器级双精度浮点数。 同样的警告也适用于浮点数。 复数 z 的实部和虚部可以通过只读属性 z.realz.imag 获取。

序列

这些表示由非负数索引的有限有序集。 内置函数 len() 返回序列的项数。 当序列长度为n时,索引集包含数字0、1、……、n-1。 序列a的项目ia[i]选择。

序列也支持切片:a[i:j] 选择所有索引为 k 的项目,使得 i <= k < ] j。 当用作表达式时,切片是相同类型的序列。 这意味着索引集被重新编号,使其从 0 开始。

某些序列还支持带有第三个“步长”参数的“扩展切片”:a[i:j:k] 选择 a 的所有项目,索引为 x,其中 x = i + n*kn >= 0i <= x [X2218X][X2218X][X223X]X 。

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

不可变序列

不可变序列类型的对象一旦创建就不能更改。 (如果对象包含对其他对象的引用,则这些其他对象可能是可变的并且可能会被更改;但是,不可变对象直接引用的对象集合不能更改。)

以下类型是不可变序列:

字符串

字符串是表示 Unicode 代码点的值序列。 U+0000 - U+10FFFF 范围内的所有代码点都可以用一个字符串表示。 Python 没有 char 类型; 相反,字符串中的每个代码点都表示为长度为 1 的字符串对象。 内置函数 ord() 将代码点从其字符串形式转换为 0 - 10FFFF 范围内的整数; chr()0 - 10FFFF 范围内的整数转换为对应长度的 1 字符串对象。 str.encode() 可用于使用给定的文本编码将 str 转换为 bytes,以及 bytes.decode()可用于实现相反的目的。

元组

元组的项是任意的 Python 对象。 两个或多个项目的元组由逗号分隔的表达式列表构成。 可以通过将逗号附加到表达式(表达式本身不会创建元组,因为括号必须可用于表达式分组)来形成一个项目的元组(“单例”)。 空元组可以由一对空括号组成。

字节

bytes 对象是一个不可变数组。 这些项是 8 位字节,由 0 <= x < 256 范围内的整数表示。 字节文字(如 b'abc')和内置的 bytes() 构造函数可用于创建字节对象。 此外,字节对象可以通过 decode() 方法解码为字符串。

可变序列

可变序列在创建后可以更改。 订阅和切片符号可以用作赋值和 del(删除)语句的目标。

目前有两种固有的可变序列类型:

列表

列表的项目是任意的 Python 对象。 列表是通过将逗号分隔的表达式列表放在方括号中来形成的。 (请注意,形成长度为 0 或 1 的列表不需要特殊情况。)

字节数组

bytearray 对象是一个可变数组。 它们由内置的 bytearray() 构造函数创建。 除了可变(因此不可散列)之外,字节数组还提供与不可变的 bytes 对象相同的接口和功能。

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

设置类型

这些代表无序、有限的唯一、不可变对象集。 因此,它们不能被任何下标索引。 但是,它们可以迭代,内置函数 len() 返回集合中的项目数。 集合的常见用途是快速成员资格测试、从序列中删除重复项以及计算交集、并集、差和对称差等数学运算。

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

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

这些代表一个可变集。 它们由内置的 set() 构造函数创建,之后可以通过多种方法进行修改,例如 add()

冷冻套装

这些代表一个不可变的集合。 它们由内置的 frozenset() 构造函数创建。 由于frozenset 是不可变的并且是hashable,因此它可以再次用作另一个集合的元素,或用作字典键。

映射

这些表示由任意索引集索引的有限对象集。 下标符号a[k]从映射a中选择由k索引的项; 这可以在表达式中使用,也可以作为赋值或 del 语句的目标。 内置函数 len() 返回映射中的项目数。

目前有一个内在映射类型:

字典

这些表示由几乎任意值索引的有限对象集。 唯一不可接受作为键的值类型是包含列表或字典或其他可变类型的值,这些值通过值而不是对象标识进行比较,原因是字典的有效实现需要键的哈希值保持不变。 用于键的数字类型遵循数字比较的正常规则:如果两个数字比较相等(例如,11.0),那么它们可以互换使用以索引相同的字典条目。

字典保留插入顺序,这意味着键将按照它们在字典中顺序添加的相同顺序生成。 替换现有密钥不会更改顺序,但是删除密钥并重新插入会将其添加到末尾而不是保留其旧位置。

字典是可变的; 它们可以通过 {...} 符号创建(请参阅 字典显示 部分)。

扩展模块 dbm.ndbmdbm.gnu 提供了映射类型的其他示例,collections 模块也是如此。

3.7 版更改: 字典在 3.6 之前的 Python 版本中不保留插入顺序。 在 CPython 3.6 中,插入顺序被保留,但当时它被认为是一个实现细节,而不是语言保证。

可调用类型

这些是可以应用函数调用操作的类型(参见 Calls):

用户定义函数

用户定义的函数对象由函数定义创建(请参阅 函数定义 部分)。 应该使用包含与函数的形式参数列表相同数量的项目的参数列表调用它。

特殊属性:

属性

意义

__doc__

函数的文档字符串,或者 None 如果不可用; 不被子类继承。

可写

__name__

函数的名称。

可写

__qualname__

函数的 限定名称

3.3 版中的新功能。

可写

__module__

定义函数的模块的名称,如果不可用,则为 None

可写

__defaults__

包含具有默认值的参数的默认参数值的元组,如果没有参数具有默认值,则包含 None

可写

__code__

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

可写

__globals__

对保存函数全局变量的字典的引用——定义函数的模块的全局命名空间。

只读

__dict__

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

可写

__closure__

None 或包含函数自由变量绑定的元组。 有关 cell_contents 属性的信息,请参见下文。

只读

__annotations__

包含参数注释的字典。 dict 的键是参数名称,'return' 用于返回注释(如果提供)。

可写

__kwdefaults__

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

可写

大多数标记为“Writable”的属性检查分配值的类型。

函数对象还支持获取和设置任意属性,例如,可用于将元数据附加到函数。 常规属性点符号用于获取和设置此类属性。 请注意,当前实现仅支持用户定义函数的函数属性。 将来可能会支持内置函数的函数属性。

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

可以从其代码对象中检索有关函数定义的附加信息; 请参阅下面的内部类型说明。 cell类型可以在types模块中访问。

实例方法

实例方法对象结合了一个类、一个类实例和任何可调用对象(通常是用户定义的函数)。

特殊只读属性:__self__为类实例对象,__func__为函数对象; __doc__ 是方法的文档(与 __func__.__doc__ 相同); __name__为方法名(同__func__.__name__); __module__ 是定义方法的模块的名称,如果不可用,则为 None

方法还支持访问(但不设置)底层函数对象上的任意函数属性。

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

当通过其实例之一从类中检索用户定义的函数对象来创建实例方法对象时,其 __self__ 属性是实例,并且该方法对象被称为已绑定。 新方法的 __func__ 属性是原始函数对象。

当通过从类或实例中检索类方法对象来创建实例方法对象时,其 __self__ 属性是类本身,其 __func__ 属性是类方法底层的函数对象。

当一个实例方法对象被调用时,底层函数(__func__)被调用,在参数列表前面插入类实例(__self__)。 例如,当 C 是一个包含函数 f() 定义的类,而 xC 的实例时,调用 [ X149X]相当于调用C.f(x, 1)

当实例方法对象派生自类方法对象时,存储在 __self__ 中的“类实例”实际上是类本身,因此调用 x.f(1)C.f(1)相当于调用 f(C,1),其中 f 是底层函数。

请注意,每次从实例中检索属性时,都会发生从函数对象到实例方法对象的转换。 在某些情况下,一种富有成效的优化是将属性分配给局部变量并调用该局部变量。 另请注意,此转换仅适用于用户定义的函数; 其他可调用对象(以及所有不可调用对象)无需转换即可检索。 同样重要的是要注意,作为类实例属性的用户定义函数不会转换为绑定方法; 这个 only 当函数是类的属性时发生。

发电机功能

使用 yield 语句(参见 yield 语句 部分)的函数或方法称为 生成器函数 。 这样的函数在调用时总是返回一个迭代器对象,该对象可用于执行函数体:调用迭代器的 iterator.__next__() 方法将导致函数执行直到它提供一个值使用 yield 语句。 当函数执行 return 语句或掉到末尾时,会引发 StopIteration 异常,迭代器将到达要返回的值集的末尾。

协程函数

使用 async def 定义的函数或方法称为 协程函数 。 这样的函数在调用时会返回一个 coroutine 对象。 它可能包含 await 表达式,以及 async withasync for 语句。 另请参阅 协程对象 部分。

异步生成器函数

使用 async def 定义并使用 yield 语句的函数或方法称为 异步生成器函数 。 这样的函数在调用时返回一个异步迭代器对象,可以在 async for 语句中使用该对象来执行函数体。

调用异步迭代器的 aiterator.__anext__() 方法将返回一个 awaitable,它在等待时将执行,直到它使用 yield 表达式提供一个值。 当函数执行空的 return 语句或掉到末尾时,会引发 StopAsyncIteration 异常,异步迭代器将到达要生成的值集的末尾。

内置功能

内置函数对象是 C 函数的包装器。 内置函数的例子有 len()math.sin()math 是一个标准的内置模块)。 参数的数量和类型由 C 函数决定。 特殊的只读属性:__doc__ 是函数的文档字符串,或者 None 如果不可用; __name__ 是函数名; __self__ 设置为 None(但见下一项); __module__ 是定义函数的模块的名称,如果不可用,则为 None

内置方法

这确实是内置函数的另一种伪装,这次包含作为隐式额外参数传递给 C 函数的对象。 内置方法的一个例子是 alist.append(),假设 alist 是一个列表对象。 在这种情况下,特殊的只读属性 __self__ 被设置为由 alist 表示的对象。

班级

类是可调用的。 这些对象通常充当它们自己的新实例的工厂,但对于覆盖 __new__() 的类类型,可能会发生变化。 调用的参数传递给 __new__(),在典型情况下,传递给 __init__() 以初始化新实例。

类实例

通过在类中定义 __call__() 方法,可以使任意类的实例可调用。

模块

模块是 Python 代码的基本组织单元,由 import 语句调用或通过调用诸如 importlib.import_module( ) 和内置的 __import__()。 模块对象具有由字典对象实现的命名空间(这是由模块中定义的函数的 __globals__ 属性引用的字典)。 属性引用被翻译成在这个字典中查找,例如,m.x 等价于 m.__dict__["x"]。 模块对象不包含用于初始化模块的代码对象(因为一旦初始化完成就不需要它了)。

属性赋值更新模块的命名空间字典,例如,m.x = 1 等价于 m.__dict__["x"] = 1

预定义(可写)属性:__name__ 是模块的名称; __doc__ 是模块的文档字符串,或者 None 如果不可用; __annotations__(可选)是一个包含变量注释在模块主体执行过程中收集的字典; __file__ 是加载模块的文件的路径名,如果它是从文件加载的。 某些类型的模块可能缺少 __file__ 属性,例如静态链接到解释器的 C 模块; 对于从共享库动态加载的扩展模块,它是共享库文件的路径名。

特殊只读属性:__dict__ 是模块的命名空间作为字典对象。

自定义类

自定义类类型通常由类定义创建(请参阅 类定义 部分)。 一个类有一个由字典对象实现的命名空间。 类属性引用被转换为该字典中的查找,例如,C.x 被转换为 C.__dict__["x"](尽管有许多钩子允许其他方式定位属性)。 如果在那里找不到属性名称,则继续在基类中搜索属性。 这种对基类的搜索使用 C3 方法解析顺序,即使在存在“菱形”继承结构的情况下,该方法的行为也正确,其中存在多个返回到共同祖先的继承路径。 有关 Python 使用的 C3 MRO 的其他详细信息,请参见 https://www.python.org/download/releases/2.3/mro/ 2.3 版本随附的文档。

当类属性引用(例如,对于类 C)将产生类方法对象时,它会被转换为一个实例方法对象,其 __self__ 属性为 C。 当它产生一个静态方法对象时,它被转换成由静态方法对象包装的对象。 请参阅 实现描述符 部分,了解从类中检索的属性可能与其 __dict__ 中实际包含的属性不同的另一种方式。

类属性赋值更新类的字典,而不是基类的字典。

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

特殊属性:__name__为类名; __module__ 是定义类的模块名称; __dict__ 是包含类的命名空间的字典; __bases__ 是一个包含基类的元组,按照它们在基类列表中出现的顺序; __doc__ 是类的文档字符串,或者 None 如果未定义; __annotations__(可选)是一个包含变量注释在类体执行期间收集的字典。

类实例

类实例是通过调用类对象创建的(见上文)。 类实例具有作为字典实现的命名空间,这是搜索属性引用的第一个位置。 如果在那里找不到属性,并且实例的类具有该名称的属性,则搜索将继续使用类属性。 如果发现类属性是用户定义的函数对象,则将其转换为实例方法对象,其 __self__ 属性是实例。 静态方法和类方法对象也进行了转换; 见上文“类”部分。 请参阅 实现描述符 部分,了解通过其实例检索的类的属性可能与实际存储在该类的 __dict__ 中的对象不同的另一种方式。 如果未找到类属性,并且对象的类具有 __getattr__() 方法,则调用该方法以满足查找。

属性分配和删除更新实例的字典,而不是类的字典。 如果类具有 __setattr__()__delattr__() 方法,则调用该方法而不是直接更新实例字典。

如果类实例具有具有某些特殊名称的方法,则它们可以伪装成数字、序列或映射。 请参阅部分 特殊方法名称

特殊属性:__dict__为属性字典; __class__ 是实例的类。

I/O 对象(也称为文件对象)

文件对象 表示打开的文件。 多种快捷方式可用于创建文件对象:open() 内置函数,以及 os.popen()os.fdopen()、和套接字对象的 makefile() 方法(也可能是由扩展模块提供的其他函数或方法)。

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

内部类型

解释器内部使用的一些类型暴露给用户。 它们的定义可能会随着解释器的未来版本而改变,但为了完整起见,这里提到它们。

代码对象

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

特殊只读属性:co_name 给出函数名; co_argcount 是位置参数的总数(包括仅位置参数和具有默认值的参数); co_posonlyargcount 是仅位置参数的数量(包括具有默认值的参数); co_kwonlyargcount 是关键字参数的个数(包括带默认值的参数); co_nlocals为函数使用的局部变量个数(包括参数); co_varnames 是一个包含局部变量名称的元组(从参数名称开始); 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 被设置。

未来的功能声明 (from __future__ import division) 还使用 co_flags 中的位来指示代码对象是否是在启用特定功能的情况下编译的:如果函数是使用编译的,则位 0x2000 被设置未来的分裂启用; 位 0x100x1000 用于早期版本的 Python。

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

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

框架对象

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

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

访问 f_code 会引发一个 审计事件 object.__getattr__,参数为 obj"f_code"

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

实现 可以 允许通过将 f_trace_opcodes 设置为 True 来请求每个操作码事件。 请注意,如果跟踪函数引发的异常转义到被跟踪的函数,则这可能会导致未定义的解释器行为。

f_lineno 是帧的当前行号——从跟踪函数中写入它会跳转到给定的行(仅适用于最底部的帧)。 调试器可以通过写入 f_lineno 来实现跳转命令(又名 Set Next Statement)。

框架对象支持一种方法:

frame.clear()

此方法清除对框架保存的局部变量的所有引用。 此外,如果帧属于生成器,则生成器已完成。 这有助于打破涉及框架对象的引用循环(例如,在捕获异常并存储其回溯以供以后使用时)。

RuntimeError 如果框架当前正在执行,则会引发。

3.4 版中的新功能。

回溯对象

Traceback 对象表示异常的堆栈跟踪。 出现异常时会隐式创建回溯对象,也可以通过调用 types.TracebackType 显式创建。

对于隐式创建的回溯,当搜索异常处理程序展开执行堆栈时,在每个展开的级别上,都会在当前回溯之前插入一个回溯对象。 当进入异常处理程序时,堆栈跟踪可供程序使用。 (请参阅 try 语句 部分。)它可以作为 sys.exc_info() 返回的元组的第三项以及捕获的异常的 __traceback__ 属性访问。

当程序不包含合适的处理程序时,堆栈跟踪被写入(格式良好)到标准错误流; 如果解释器是交互式的,它也可以作为 sys.last_traceback 提供给用户。

对于显式创建的回溯,由回溯的创建者决定如何链接 tb_next 属性以形成完整的堆栈跟踪。

特殊只读属性:tb_frame指向当前关卡的执行帧; tb_lineno 给出异常发生的行号; tb_lasti表示精确指令。 如果异常发生在没有匹配的 except 子句或 finally 子句的 try 语句中,则回溯中的行号和最后一条指令可能与其框架对象的行号不同。

访问 tb_frame 会引发一个 审计事件 object.__getattr__,参数为 obj"tb_frame"

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

3.7 版更改: 现在可以从 Python 代码显式实例化 Traceback 对象,并且可以更新现有实例的 tb_next 属性。

切片对象

切片对象用于表示 __getitem__() 方法的切片。 它们也是由内置的 slice() 函数创建的。

特殊只读属性:start为下界; stop为上限; step为步长值; 如果省略,每个都是 None。 这些属性可以是任何类型。

切片对象支持一种方法:

slice.indices(self, length)

此方法采用单个整数参数 length 并计算切片对象在应用于 length 项序列时将描述的切片的信息。 它返回一个由三个整数组成的元组; 这些分别是 startstop 索引和 step 或切片的步幅长度。 缺失或越界索引的处理方式与常规切片一致。

静态方法对象

静态方法对象提供了一种阻止上述函数对象到方法对象的转换的方法。 静态方法对象是任何其他对象的包装器,通常是用户定义的方法对象。 当从类或类实例中检索静态方法对象时,实际返回的对象是包装后的对象,不进行任何进一步的转换。 静态方法对象本身不可调用,尽管它们通常包装的对象是可调用的。 静态方法对象由内置的 staticmethod() 构造函数创建。

类方法对象

类方法对象与静态方法对象一样,是另一个对象的包装器,它改变了从类和类实例中检索该对象的方式。 类方法对象在此类检索时的行为在上面的“用户定义的方法”中进行了描述。 类方法对象由内置的 classmethod() 构造函数创建。


3.3. 特殊方法名称

类可以通过定义具有特殊名称的方法来实现由特殊语法(例如算术运算或下标和切片)调用的某些操作。 这是 Python 实现 运算符重载 的方法,允许类根据语言运算符定义自己的行为。 例如,如果一个类定义了一个名为 __getitem__() 的方法,而 x 是这个类的一个实例,那么 x[i] 大致相当于 type(x).__getitem__(x, i)。 除非另有说明,否则在未定义适当方法时尝试执行操作会引发异常(通常为 AttributeErrorTypeError)。

将特殊方法设置为None表示对应的操作不可用。 例如,如果一个类将 __iter__() 设置为 None,则该类不可迭代,因此在其实例上调用 iter() 将引发 TypeError ](不回退到 __getitem__())。 2

当实现一个模拟任何内置类型的类时,重要的是模拟只实现到它对被建模的对象有意义的程度。 例如,某些序列可能适用于检索单个元素,但提取切片可能没有意义。 (W3C 的文档对象模型中的 NodeList 接口就是一个例子。)

3.3.1. 基本定制

object.__new__(cls[, ...])

调用以创建类 cls 的新实例。 __new__() 是一个静态方法(特殊情况,所以你不需要这样声明它),它将请求实例的类作为它的第一个参数。 其余参数是传递给对象构造函数表达式(对类的调用)的参数。 __new__() 的返回值应该是新的对象实例(通常是 cls 的实例)。

典型的实现通过使用具有适当参数的 super().__new__(cls[, ...]) 调用超类的 __new__() 方法来创建类的新实例,然后在返回之前根据需要修改新创建的实例。

如果在对象构造期间调用 __new__() 并返回 cls 的实例,则新实例的 __init__() 方法将像 __init__(self[, ...]),其中 self 是新实例,其余参数与传递给对象构造函数的参数相同。

如果 __new__() 没有返回 cls 的实例,则不会调用新实例的 __init__() 方法。

__new__() 主要用于允许不可变类型(如 int、str 或 tuple)的子类自定义实例创建。 它也通常在自定义元类中被覆盖,以自定义类创建。

object.__init__(self[, ...])

在创建实例之后(通过 __new__())调用,但在它返回给调用者之前调用。 参数是传递给类构造函数表达式的参数。 如果基类具有 __init__() 方法,则派生类的 __init__() 方法(如果有)必须显式调用它以确保正确初始化实例的基类部分; 例如:super().__init__([args...])

因为 __new__()__init__() 在构造对象时协同工作(__new__() 来创建它,而 __init__() 到自定义),__init__() 不能返回非 None 值; 这样做会导致在运行时引发 TypeError

object.__del__(self)

在实例即将被销毁时调用。 这也称为终结器或(不正确地)析构函数。 如果基类具有 __del__() 方法,则派生类的 __del__() 方法(如果有)必须显式调用它以确保正确删除实例的基类部分.

__del__() 方法可以(虽然不推荐!)通过创建对实例的新引用来推迟实例的销毁。 这称为对象 复活__del__() 是否在复活对象即将被销毁时第二次调用取决于实现; 当前的 CPython 实现只调用一次。

不保证 __del__() 方法在解释器退出时仍然存在的对象被调用。

笔记

del x 不直接调用 x.__del__() — 前者将 x 的引用计数减一,后者仅在 x 的引用时调用计数达到零。

警告

由于调用 __del__() 方法的不稳定情况,在执行过程中发生的异常将被忽略,而是向 sys.stderr 打印警告。 特别是:

  • __del__() 可以在执行任意代码时调用,包括从任意线程调用。 如果 __del__() 需要获取锁或调用任何其他阻塞资源,它可能会死锁,因为该资源可能已经被中断执行 __del__() 的代码占用。

  • __del__() 可以在解释器关闭期间执行。 因此,它需要访问的全局变量(包括其他模块)可能已经被删除或设置为 None。 Python 保证名称以下划线开头的全局变量在删除其他全局变量之前从其模块中删除; 如果不存在对此类全局变量的其他引用,这可能有助于确保在调用 __del__() 方法时导入的模块仍然可用。


object.__repr__(self)

repr() 内置函数调用以计算对象的“官方”字符串表示。 如果可能的话,这应该看起来像一个有效的 Python 表达式,可用于重新创建具有相同值的对象(给定适当的环境)。 如果这是不可能的,则应返回 <...some useful description...> 形式的字符串。 返回值必须是字符串对象。 如果一个类定义了 __repr__() 而不是 __str__(),那么 __repr__() 也用于该类实例的“非正式”字符串表示是必须的。

这通常用于调试,因此表示信息丰富且明确是很重要的。

object.__str__(self)

str(object) 和内置函数 format()print() 调用以计算“非正式”或可很好打印的字符串表示目的。 返回值必须是 string 对象。

此方法与 object.__repr__() 的不同之处在于不期望 __str__() 返回有效的 Python 表达式:可以使用更方便或更简洁的表示。

内置类型 object 定义的默认实现调用 object.__repr__()

object.__bytes__(self)

bytes 调用以计算对象的字节字符串表示。 这应该返回一个 bytes 对象。

object.__format__(self, format_spec)

format() 内置函数调用,通过扩展,对 格式化字符串文字str.format() 方法的评估,以产生一个对象的“格式化”字符串表示。 format_spec 参数是一个字符串,其中包含所需格式选项的描述。 format_spec 参数的解释取决于实现 __format__() 的类型,但是大多数类要么将格式委托给内置类型之一,要么使用类似的格式选项句法。

有关标准格式语法的说明,请参阅 格式规范迷你语言

返回值必须是字符串对象。

3.4 版更改: object 的 __format__ 方法本身会在传递任何非空字符串时引发 TypeError

3.7 版更改: object.__format__(x, ) 现在等同于 str(x) 而不是 format(str(self), )

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

这些就是所谓的“丰富的比较”方法。 操作符和方法名的对应关系如下: x<y调用x.__lt__(y)x<=y调用x.__le__(y)x==y调用x.__eq__(y)x!=y调用x.__ne__(y)x>y调用x.__gt__(y)x>=y调用x.__ge__(y)

如果富比较方法没有实现给定参数对的操作,则它可能返回单例 NotImplemented。 按照惯例,返回 FalseTrue 以进行成功比较。 但是,这些方法可以返回任何值,因此如果在布尔上下文中使用比较运算符(例如,在 if 语句的条件下),Python 将调用 bool() on用于确定结果是真还是假的值。

默认情况下,object 通过使用 is 实现 __eq__(),在错误比较的情况下返回 NotImplementedTrue if x is y else NotImplemented。 对于 __ne__(),默认情况下它委托给 __eq__() 并反转结果,除非它是 NotImplemented。 比较运算符或默认实现之间没有其他隐含关系; 例如,(x<y or x==y) 的真相并不意味着 x<=y。 要从单个根操作自动生成排序操作,请参阅 functools.total_ordering()

有关创建支持自定义比较操作并可用作字典键的 hashable 对象的一些重要说明,请参阅 __hash__() 上的段落。

这些方法没有交换参数版本(当左参数不支持操作但右参数支持时使用); 相反,__lt__()__gt__() 是彼此的反映,__le__()__ge__() 是彼此的反映,而 __eq__()__ne__() 是他们自己的反映。 如果操作数的类型不同,且右操作数的类型是左操作数类型的直接或间接子类,则右操作数的反射方法优先,否则左操作数的方法优先。 不考虑虚拟子类化。

object.__hash__(self)

由内置函数 hash() 调用,用于对散列集合成员的操作,包括 setfrozensetdict__hash__() 应该返回一个整数。 唯一需要的属性是比较相等的对象具有相同的哈希值; 建议将对象组件的散列值混合在一起,这些组件也在对象比较中起作用,方法是将它们打包成一个元组并对元组进行散列。 例子:

def __hash__(self):
    return hash((self.name, self.nick, self.color))

笔记

hash() 将从对象的自定义 __hash__() 方法返回的值截断为 Py_ssize_t 的大小。 这在 64 位构建上通常是 8 个字节,在 32 位构建上通常是 4 个字节。 如果对象的 __hash__() 必须在不同位大小的构建上互操作,请务必检查所有支持构建的宽度。 一个简单的方法是使用 python -c "import sys; print(sys.hash_info.width)"

如果一个类没有定义 __eq__() 方法,它也不应该定义 __hash__() 操作; 如果它定义了 __eq__() 而不是 __hash__(),它的实例将不能用作可散列集合中的项目。 如果一个类定义了可变对象并实现了 __eq__() 方法,它不应该实现 __hash__(),因为可散列集合的实现要求键的散列值是不可变的(如果对象的哈希值发生变化,它将在错误的哈希桶中)。

用户定义的类默认有 __eq__()__hash__() 方法; 对于它们,所有对象都比较不相等(除了它们自己)并且 x.__hash__() 返回一个适当的值,使得 x == y 意味着 x is yhash(x) == hash(y)

覆盖 __eq__() 且未定义 __hash__() 的类将其 __hash__() 隐式设置为 None。 当类的 __hash__() 方法为 None 时,类的实例将在程序尝试检索其哈希值时引发适当的 TypeError,并将也可以在检查 isinstance(obj, collections.abc.Hashable) 时正确识别为 unhashable。

如果覆盖 __eq__() 的类需要保留父类中 __hash__() 的实现,则必须通过设置 __hash__ = <ParentClass>.__hash__ 来明确告知解释器。

如果一个没有覆盖 __eq__() 的类希望抑制散列支持,它应该在类定义中包含 __hash__ = None。 定义自己的 __hash__() 的类显式引发 TypeError 将被 isinstance(obj, collections.abc.Hashable) 调用错误地标识为可散列。

笔记

默认情况下, str 和 bytes 对象的 __hash__() 值是用不可预测的随机值“加盐”的。 尽管它们在单个 Python 进程中保持不变,但它们在 Python 的重复调用之间是不可预测的。

这旨在提供保护,防止由精心选择的输入引起的拒绝服务,这些输入利用了 dict 插入的最坏情况性能,O(n^2) 复杂度。 有关详细信息,请参阅 http://www.ocert.org/advisories/ocert-2011-003.html。

更改哈希值会影响集合的迭代顺序。 Python 从未对此排序做出保证(并且它通常在 32 位和 64 位版本之间变化)。

另见 PYTHONHASHSEED

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

object.__bool__(self)
调用实现真值测试和内置操作bool(); 应返回 FalseTrue。 如果未定义此方法,则调用 __len__()(如果已定义),并且如果其结果非零,则该对象被视为真。 如果一个类既没有定义 __len__() 也没有定义 __bool__(),那么它的所有实例都被认为是真的。


3.3.2. 自定义属性访问

可以定义以下方法来自定义类实例的属性访问(x.name 的使用、分配或删除)的含义。

object.__getattr__(self, name)

当默认属性访问失败并出现 AttributeError 时调用(__getattribute__() 引发 AttributeError 因为 name 不是实例属性或self 的类树中的属性;或 name 属性的 __get__() 引发 AttributeError)。 此方法应返回(计算的)属性值或引发 AttributeError 异常。

请注意,如果通过正常机制找到该属性,则不会调用 __getattr__()。 (这是 __getattr__()__setattr__() 之间有意的不对称。)这样做既是出于效率原因,也是因为否则 __getattr__() 将没有访问实例其他属性的方法。 请注意,至少对于实例变量,您可以通过不在实例属性字典中插入任何值(而是将它们插入到另一个对象中)来伪造完全控制。 请参阅下面的 __getattribute__() 方法,了解如何真正完全控制属性访问。

object.__getattribute__(self, name)

无条件调用以实现对类实例的属性访问。 如果该类还定义了 __getattr__(),则不会调用后者,除非 __getattribute__() 显式调用它或引发 AttributeError。 此方法应返回(计算的)属性值或引发 AttributeError 异常。 为了避免这个方法的无限递归,它的实现应该总是调用同名的基类方法来访问它需要的任何属性,例如,object.__getattribute__(self, name)

笔记

作为通过语言语法或内置函数进行隐式调用的结果,在查找特殊方法时仍可能绕过此方法。 参见 特殊方法查找

object.__setattr__(self, name, value)

尝试进行属性分配时调用。 这被称为而不是正常机制(即 将值存储在实例字典中)。 name 是属性名称,value 是分配给它的值。

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

object.__delattr__(self, name)
__setattr__() 但用于属性删除而不是赋值。 仅当 del obj.name 对对象有意义时才应实现。
object.__dir__(self)
在对象上调用 dir() 时调用。 必须返回一个序列。 dir() 将返回的序列转换为列表并对其进行排序。

3.3.2.1. 自定义模块属性访问

特殊名称 __getattr____dir__ 也可用于自定义对模块属性的访问。 模块级别的 __getattr__ 函数应接受一个参数,即属性名称并返回计算值或引发 AttributeError。 如果通过正常查找在模块对象上找不到属性,即 object.__getattribute__(),然后在模块 __dict__ 中搜索 __getattr__,然后引发 AttributeError。 如果找到,则使用属性名称调用它并返回结果。

__dir__ 函数不应接受任何参数,并返回一个字符串序列,表示模块上可访问的名称。 如果存在,此函数会覆盖模块上的标准 dir() 搜索。

为了对模块行为进行更细粒度的定制(设置属性、属性等),可以将模块对象的 __class__ 属性设置为 types.ModuleType 的子类。 例如:

import sys
from types import ModuleType

class VerboseModule(ModuleType):
    def __repr__(self):
        return f'Verbose {self.__name__}'

    def __setattr__(self, attr, value):
        print(f'Setting {attr}...')
        super().__setattr__(attr, value)

sys.modules[__name__].__class__ = VerboseModule

笔记

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


3.5 版更改:__class__ 模块属性现在可写。


3.7 版新增:__getattr____dir__ 模块属性。


也可以看看

PEP 562 - 模块 __getattr__ 和 __dir__
描述模块上的 __getattr____dir__ 功能。


3.3.2.2. 实现描述符

以下方法仅适用于包含该方法的类的实例(所谓的 descriptor 类)出现在 owner 类中(描述符必须在所有者的类字典中)或在其父母之一的类字典中)。 在下面的示例中,“属性”是指名称为所有者类'__dict__ 中的属性键的属性。

object.__get__(self, instance, owner=None)

调用以获取所有者类的属性(类属性访问)或该类的实例的属性(实例属性访问)。 可选的 owner 参数是所有者类,而 instance 是通过 访问属性的实例,或 None所有者

此方法应返回计算出的属性值或引发 AttributeError 异常。

PEP 252 指定 __get__() 可以用一个或两个参数调用。 Python 自己的内置描述符支持这个规范; 但是,某些第三方工具可能具有需要两个参数的描述符。 Python 自己的 __getattribute__() 实现总是传入两个参数,无论它们是否需要。

object.__set__(self, instance, value)

调用以将所有者类的实例 实例 上的属性设置为新值

注意,添加 __set__()__delete__() 会将描述符的类型更改为“数据描述符”。 有关更多详细信息,请参阅 调用描述符

object.__delete__(self, instance)
调用以删除所有者类的实例 实例 上的属性。
object.__set_name__(self, owner, name)

在创建拥有类 owner 时调用。 描述符已分配给 name

笔记

__set_name__() 仅作为 type 构造函数的一部分被隐式调用,因此在初始创建后将描述符添加到类时,需要使用适当的参数显式调用它:

class A:
   pass
descr = custom_descriptor()
A.attr = descr
descr.__set_name__(A, 'attr')

有关更多详细信息,请参阅 创建类对象

3.6 版中的新功能。

属性 __objclass__inspect 模块解释为指定定义此对象的类(适当设置它可以帮助动态类属性的运行时内省)。 对于可调用对象,它可能表示需要或需要给定类型(或子类)的实例作为第一个位置参数(例如,CPython 为在 C 中实现的未绑定方法设置此属性)。


3.3.2.3. 调用描述符

通常,描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖:__get__()__set__()__delete__() . 如果为对象定义了这些方法中的任何一个,则称其为描述符。

属性访问的默认行为是从对象的字典中获取、设置或删除属性。 例如,a.x 有一个从 a.__dict__['x'] 开始的查找链,然后是 type(a).__dict__['x'],并继续通过 type(a) 的基类,不包括元类。

但是,如果查找值是定义描述符方法之一的对象,则 Python 可能会覆盖默认行为并改为调用描述符方法。 这在优先链中发生的位置取决于定义了哪些描述符方法以及如何调用它们。

描述符调用的起点是一个绑定,a.x。 参数的组合方式取决于 a

直接呼叫
最简单和最不常见的调用是当用户代码直接调用描述符方法时:x.__get__(a)
实例绑定
如果绑定到一个对象实例,a.x 被转换为调用:type(a).__dict__['x'].__get__(a, type(a))
类绑定
如果绑定到一个类,A.x 被转换为调用:A.__dict__['x'].__get__(None, A)
超级绑定
如果 asuper 的实例,则绑定 super(B, obj).m()obj.__class__.__mro__ 中搜索紧接在 B 然后通过以下调用调用描述符:A.__dict__['m'].__get__(obj, obj.__class__)

对于实例绑定,描述符调用的优先级取决于定义了哪些描述符方法。 描述符可以定义 __get__()__set__()__delete__() 的任意组合。 如果它没有定义 __get__(),那么访问该属性将返回描述符对象本身,除非该对象的实例字典中有一个值。 如果描述符定义了__set__()和/或__delete__(),则为数据描述符; 如果两者都没有定义,则它是一个非数据描述符。 通常,数据描述符定义了 __get__()__set__(),而非数据描述符只有 __get__() 方法。 定义了 __set__()__get__() 的数据描述符总是覆盖实例字典中的重新定义。 相反,非数据描述符可以被实例覆盖。

Python 方法(包括 staticmethod()classmethod())被实现为非数据描述符。 因此,实例可以重新定义和覆盖方法。 这允许单个实例获得与同一类的其他实例不同的行为。

property() 函数作为数据描述符实现。 因此,实例不能覆盖属性的行为。


3.3.2.4. __插槽__

__slots__ 允许我们显式声明数据成员(如属性)并拒绝创建 __dict____weakref__(除非在 __slots__ 中明确声明)或在父母中可用。)

使用 __dict__ 节省的空间可能很重要。 属性查找速度也可以显着提高。

object.__slots__
可以为此类变量分配一个字符串、可迭代对象或具有实例使用的变量名称的字符串序列。 __slots__ 为声明的变量保留空间,并防止为每个实例自动创建 __dict____weakref__
3.3.2.4.1. 使用注意事项 __插槽__
  • 从没有 __slots__ 的类继承时,实例的 __dict____weakref__ 属性将始终可访问。
  • 如果没有 __dict__ 变量,则无法为实例分配未在 __slots__ 定义中列出的新变量。 尝试分配给未列出的变量名称会引发 AttributeError。 如果需要动态分配新变量,则将 '__dict__' 添加到 __slots__ 声明中的字符串序列。
  • 如果每个实例没有 __weakref__ 变量,定义 __slots__ 的类不支持对其实例的弱引用。 如果需要弱引用支持,则将 '__weakref__' 添加到 __slots__ 声明中的字符串序列。
  • __slots__ 通过为每个变量名称创建描述符(Implementing Descriptors)在类级别实现。 因此,类属性不能用于为 __slots__ 定义的实例变量设置默认值; 否则,类属性将覆盖描述符分配。
  • __slots__ 声明的作用不限于定义它的类。 在父类中声明的 __slots__ 在子类中可用。 但是,子子类将获得 __dict____weakref__ 除非它们还定义了 __slots__(它应该只包含任何 附加 槽的名称) .
  • 如果一个类定义了一个也在基类中定义的槽,则基类槽定义的实例变量是不可访问的(除非直接从基类检索其描述符)。 这使得程序的含义未定义。 将来,可能会添加检查以防止这种情况发生。
  • 非空 __slots__ 不适用于从“可变长度”内置类型派生的类,例如 intbytestuple
  • 任何非字符串可迭代对象都可以分配给 __slots__。 也可以使用映射; 但是,将来可能会为每个键对应的值赋予特殊含义。
  • __class__ 赋值仅在两个类具有相同的 __slots__ 时才有效。
  • 可以使用具有多个槽父类的多重继承,但只允许一个父类具有由槽创建的属性(其他基必须具有空槽布局) - 违规引发 TypeError
  • 如果迭代器用于 __slots__ 则为迭代器的每个值创建一个描述符。 但是, __slots__ 属性将是一个空迭代器。


3.3.3. 自定义类创建

每当一个类从另一个类继承时,就会在该类上调用 __init_subclass__。 这样,就可以编写改变子类行为的类。 这与类装饰器密切相关,但在类装饰器仅影响它们所应用的特定类的情况下,__init_subclass__ 仅适用于定义该方法的类的未来子类。

classmethod object.__init_subclass__(cls)

每当包含类被子类化时,就会调用此方法。 cls 然后是新的子类。 如果定义为普通实例方法,则此方法会隐式转换为类方法。

提供给新类的关键字参数将传递给父类 __init_subclass__。 为了与使用 __init_subclass__ 的其他类兼容,应该取出所需的关键字参数并将其他参数传递给基类,如下所示:

class Philosopher:
    def __init_subclass__(cls, /, default_name, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.default_name = default_name

class AustralianPhilosopher(Philosopher, default_name="Bruce"):
    pass

默认实现 object.__init_subclass__ 什么都不做,但如果使用任何参数调用它,则会引发错误。

笔记

元类提示 metaclass 被其余的类型机制使用,并且永远不会传递给 __init_subclass__ 实现。 实际的元类(而不是显式提示)可以作为 type(cls) 访问。

3.6 版中的新功能。

3.3.3.1. 元类

默认情况下,类是使用 type() 构造的。 类体在新的命名空间中执行,类名在本地绑定到 type(name, bases, namespace) 的结果。

可以通过在类定义行中传递 metaclass 关键字参数,或通过从包含此类参数的现有类继承来自定义类创建过程。 在以下示例中,MyClassMySubclass 都是 Meta 的实例:

class Meta(type):
    pass

class MyClass(metaclass=Meta):
    pass

class MySubclass(MyClass):
    pass

在类定义中指定的任何其他关键字参数都将传递给下面描述的所有元类操作。

执行类定义时,会发生以下步骤:

  • MRO 条目已解决;
  • 确定适当的元类;
  • 类命名空间已准备好;
  • 类体被执行;
  • 类对象被创建。


3.3.3.2. 解决 MRO 条目

如果出现在类定义中的基类不是 type 的实例,则在其上搜索 __mro_entries__ 方法。 如果找到,则使用原始基元组调用它。 此方法必须返回将用于代替此基类的类的元组。 元组可能为空,在这种情况下,原始基数将被忽略。

也可以看看

PEP 560 - 对输入模块和泛型类型的核心支持


3.3.3.3. 确定合适的元类

类定义的适当元类确定如下:

  • 如果没有给出基类和显式元类,则使用 type()
  • 如果给出了一个显式元类并且它是 不是 type() 的一个实例,那么它被直接用作元类;
  • 如果 type() 的实例作为显式元类给出,或定义了基类,则使用最派生的元类。

从显式指定的元类(如果有)和元类(即 type(cls)) 的所有指定基类。 派生最多的元类是这些候选元类的 all 的子类型。 如果没有一个候选元类符合该标准,则类定义将失败并显示 TypeError


3.3.3.4. 准备类命名空间

一旦确定了适当的元类,就准备好类命名空间。 如果元类具有 __prepare__ 属性,则称为 namespace = metaclass.__prepare__(name, bases, **kwds)(其中附加关键字参数(如果有)来自类定义)。 __prepare__ 方法应该实现为 classmethod()__prepare__ 返回的命名空间被传递给 __new__,但是当创建最终的类对象时,命名空间被复制到一个新的 dict 中。

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

也可以看看

PEP 3115 - Python 3000 中的元类
引入了 __prepare__ 命名空间挂钩


3.3.3.5. 执行类体

类主体执行(大约)为 exec(body, globals(), namespace)。 与正常调用 exec() 的主要区别在于,词法作用域允许类主体(包括任何方法)在类定义发生在函数内部时从当前作用域和外部作用域引用名称。

然而,即使类定义出现在函数内部,类内部定义的方法仍然看不到在类范围内定义的名称。 类变量必须通过实例或类方法的第一个参数访问,或者通过下一节中描述的隐式词法作用域 __class__ 引用访问。


3.3.3.6. 创建类对象

通过执行类主体填充类命名空间后,通过调用 metaclass(name, bases, namespace, **kwds) 创建类对象(此处传递的附加关键字与传递给 __prepare__ 的关键字相同)。

此类对象将被 super() 的零参数形式引用。 __class__ 是编译器创建的隐式闭包引用,如果类体中的任何方法引用 __class__super。 这允许 super() 的零参数形式根据词法范围正确识别正在定义的类,而用于进行当前调用的类或实例根据传递给的第一个参数进行识别方法。

当使用默认元类 type 或任何最终调用 type.__new__ 的元类时,在创建类对象后调用以下额外的自定义步骤:

  • 首先,type.__new__ 收集类命名空间中定义 __set_name__() 方法的所有描述符;
  • 其次,所有这些 __set_name__ 方法都使用正在定义的类和该特定描述符的指定名称来调用;
  • 最后, __init_subclass__() 钩子按照方法解析顺序在新类的直接父类上调用。

创建类对象后,将其传递给类定义(如果有)中包含的类装饰器,并将生成的对象作为定义的类绑定在本地命名空间中。

type.__new__创建一个新类时,作为命名空间参数提供的对象被复制到一个新的有序映射中,原始对象被丢弃。 新副本包装在只读代理中,成为类对象的 __dict__ 属性。

也可以看看

PEP 3135 - 新超级
描述隐式 __class__ 闭包引用


3.3.3.7. 元类的用途

元类的潜在用途是无限的。 已经探索的一些想法包括枚举、日志记录、接口检查、自动委托、自动属性创建、代理、框架和自动资源锁定/同步。


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

以下方法用于覆盖 isinstance()issubclass() 内置函数的默认行为。

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

class.__instancecheck__(self, instance)
如果 instance 应被视为 class 的(直接或间接)实例,则返回 true。 如果已定义,则调用以实现 isinstance(instance, class)
class.__subclasscheck__(self, subclass)
如果 subclass 应被视为 class 的(直接或间接)子类,则返回 true。 如果已定义,则调用以实现 issubclass(subclass, class)

请注意,这些方法是在类的类型(元类)上查找的。 它们不能在实际类中定义为类方法。 这与在实例上调用的特殊方法的查找一致,仅在这种情况下,实例本身就是一个类。

也可以看看

PEP 3119 - 引入抽象基类
包括用于通过 __instancecheck__()__subclasscheck__() 自定义 isinstance()issubclass() 行为的规范,以此为动机将抽象基类(参见 abc 模块)添加到语言的上下文中的功能。


3.3.5. 模拟泛型类型

可以通过定义一种特殊方法来实现 PEP 484(例如 List[int])指定的通用类语法:

classmethod object.__class_getitem__(cls, key)
通过在 key 中找到的类型参数返回表示泛型类的特化的对象。

这个方法是在类对象本身上查找的,当在类体中定义时,这个方法隐式是一个类方法。 请注意,此机制主要保留用于静态类型提示,不鼓励其他用途。

也可以看看

PEP 560 - 对输入模块和泛型类型的核心支持


3.3.6. 模拟可调用对象

object.__call__(self[, args...])
当实例作为函数被“调用”时调用; 如果定义了此方法,则 x(arg1, arg2, ...) 大致转换为 type(x).__call__(x, arg1, ...)


3.3.7. 模拟容器类型

可以定义以下方法来实现容器对象。 容器通常是序列(如列表或元组)或映射(如字典),但也可以表示其他容器。 第一组方法用于模拟序列或模拟映射; 不同之处在于,对于序列,允许的键应该是整数 k 其中 0 <= k < N 其中 N 是序列或切片对象的长度,其中定义一个项目范围。 还建议映射提供方法 keys()values()items()get()clear()setdefault()pop()popitem()copy()update() 的行为类似于 Python 标准字典对象的行为。 collections.abc 模块提供了一个 MutableMapping 抽象基类,以帮助从 __getitem__()__setitem__()__delitem__()keys()。 可变序列应提供方法 append()count()index()extend()insert()pop()remove()reverse()sort(),类似于 Python 标准列表对象。 最后,序列类型应该通过定义方法 __add__()__radd__()__iadd__()__mul__()、[ X190X] 和 __imul__() 如下所述; 他们不应该定义其他数字运算符。 建议映射和序列都实现 __contains__() 方法以允许有效使用 in 运算符; 对于映射,in 应该搜索映射的键; 对于序列,它应该搜索值。 进一步建议映射和序列都实现 __iter__() 方法,以允许通过容器进行高效迭代; 对于映射,__iter__() 应该遍历对象的键; 对于序列,它应该遍历值。

object.__len__(self)
调用以实现内置函数 len()。 应该返回对象的长度,一个整数 >= 0。 此外,未定义 __bool__() 方法且其 __len__() 方法返回零的对象在布尔上下文中被认为是假的。
object.__length_hint__(self)

调用以实现 operator.length_hint()。 应该返回对象的估计长度(可能大于或小于实际长度)。 长度必须是整数 >= 0。 返回值也可能是 NotImplemented,这被视为 __length_hint__ 方法根本不存在。 这种方法纯粹是一种优化,永远不需要正确性。

3.4 版中的新功能。

笔记

切片仅使用以下三种方法完成。 一个电话就像

a[1:2] = b

被翻译成

a[slice(1, 2, None)] = b

等等。 缺失的切片项目总是用 None 填充。


object.__getitem__(self, key)

调用以实现对 self[key] 的评估。 对于序列类型,接受的键应该是整数和切片对象。 请注意,负索引的特殊解释(如果类希望模拟序列类型)取决于 __getitem__() 方法。 如果 key 的类型不合适,可能会引发 TypeError; 如果序列的索引集之外的值(在对负值进行任何特殊解释之后),则应引发 IndexError。 对于映射类型,如果 key 缺失(不在容器中),则应引发 KeyError

笔记

for 循环预计会为非法索引引发 IndexError,以允许正确检测序列的结尾。

object.__setitem__(self, key, value)
调用以实现对 self[key] 的赋值。 与 __getitem__() 相同的注释。 如果对象支持更改键的值,或者如果可以添加新键,或者如果可以替换元素,则应该仅针对映射实现此功能。 对于不正确的 key 值,应该引发与 __getitem__() 方法相同的异常。
object.__delitem__(self, key)
调用实现删除self[key]。 与 __getitem__() 相同的注释。 如果对象支持删除键,则只应为映射实现,或者如果可以从序列中删除元素,则应为序列实现。 对于不正确的 key 值,应该引发与 __getitem__() 方法相同的异常。
object.__missing__(self, key)
当 key 不在字典中时,由 dict.__getitem__() 调用以实现 dict 子类的 self[key]
object.__iter__(self)

当容器需要迭代器时调用此方法。 此方法应返回一个新的迭代器对象,该对象可以迭代容器中的所有对象。 对于映射,它应该遍历容器的键。

迭代器对象也需要实现这个方法; 他们必须自己返回。 有关迭代器对象的更多信息,请参阅 迭代器类型

object.__reversed__(self)

由内置的 reversed() 调用(如果存在)以实现反向迭代。 它应该返回一个新的迭代器对象,该对象以相反的顺序迭代容器中的所有对象。

如果未提供 __reversed__() 方法,内置的 reversed() 将回退到使用序列协议 (__len__()]__getitem__())。 支持序列协议的对象应该只提供 __reversed__(),前提是它们可以提供比 reversed() 提供的实现更有效的实现。

成员资格测试运算符(innot in)通常作为通过容器的迭代实现。 但是,容器对象可以提供以下具有更高效实现的特殊方法,这也不需要对象是可迭代的。

object.__contains__(self, item)

调用以实现成员资格测试运算符。 如果 itemself 中,应该返回 true,否则返回 false。 对于映射对象,这应该考虑映射的键,而不是值或键项对。

对于没有定义 __contains__() 的对象,成员资格测试首先通过 __iter__() 尝试迭代,然后通过 __getitem__() 尝试旧的序列迭代协议,请参阅语言参考 中的 此部分。


3.3.8. 模拟数字类型

可以定义以下方法来模拟数字对象。 与所实现的特定类型的数字不支持的操作相对应的方法(例如,非整数的按位操作)应该保持未定义。

object.__add__(self, other)
object.__sub__(self, other)
object.__mul__(self, other)
object.__matmul__(self, other)
object.__truediv__(self, other)
object.__floordiv__(self, other)
object.__mod__(self, other)
object.__divmod__(self, other)
object.__pow__(self, other[, modulo])
object.__lshift__(self, other)
object.__rshift__(self, other)
object.__and__(self, other)
object.__xor__(self, other)
object.__or__(self, other)

调用这些方法来实现二进制算术运算 (+, -, *, @, /, [ X131X]、%divmod()pow()**<<、 、&^|)。 例如,要计算表达式 x + y,其中 x 是具有 __add__() 方法的类的实例,调用 x.__add__(y) . __divmod__() 方法应该等同于使用 __floordiv__()__mod__(); 它不应该与 __truediv__() 相关。 请注意,如果要支持内置 pow() 函数的三元版本,则应定义 __pow__() 以接受可选的第三个参数。

如果这些方法之一不支持使用所提供参数的操作,则应返回 NotImplemented

object.__radd__(self, other)
object.__rsub__(self, other)
object.__rmul__(self, other)
object.__rmatmul__(self, other)
object.__rtruediv__(self, other)
object.__rfloordiv__(self, other)
object.__rmod__(self, other)
object.__rdivmod__(self, other)
object.__rpow__(self, other[, modulo])
object.__rlshift__(self, other)
object.__rrshift__(self, other)
object.__rand__(self, other)
object.__rxor__(self, other)
object.__ror__(self, other)

调用这些方法来实现二进制算术运算 (+, -, *, @, /, [ X131X]、%divmod()pow()**<<、 , &, ^, |) 与反射(交换)操作数。 这些函数仅在左操作数不支持相应操作 3 且操作数类型不同时调用。 4 例如,要计算表达式 x - y,其中 y 是具有 __rsub__() 方法的类的实例,[如果 x.__sub__(y) 返回 NotImplemented,则调用 X154X]。

请注意,三元 pow() 不会尝试调用 __rpow__()(强制规则会变得太复杂)。

笔记

如果右操作数的类型是左操作数类型的子类,并且该子类为操作提供了反射方法的不同实现,则将在左操作数的非反射方法之前调用此方法。 此行为允许子类覆盖其祖先的操作。

object.__iadd__(self, other)
object.__isub__(self, other)
object.__imul__(self, other)
object.__imatmul__(self, other)
object.__itruediv__(self, other)
object.__ifloordiv__(self, other)
object.__imod__(self, other)
object.__ipow__(self, other[, modulo])
object.__ilshift__(self, other)
object.__irshift__(self, other)
object.__iand__(self, other)
object.__ixor__(self, other)
object.__ior__(self, other)

调用这些方法来实现增强算术赋值(+=-=*=@=/=、[ X135X]、%=**=<<=>>=&=^=[X] ])。 这些方法应该尝试就地执行操作(修改 self)并返回结果(可能但不一定是 self)。 如果未定义特定方法,则增广赋值回退到正常方法。 例如,如果 x 是具有 __iadd__() 方法的类的实例,则 x += y 等价于 x = x.__iadd__(y) 。 否则,考虑 x.__add__(y)y.__radd__(x),与 x + y 的评估一样。 在某些情况下,增强赋值可能会导致意外错误(请参阅 为什么 a_tuple[i] += ['item'] 在添加有效时引发异常?),但这种行为实际上是数据模型。

笔记

由于 **= 的调度机制中的错误,定义 __ipow__() 但返回 NotImplemented 的类将无法回退到 x.__pow__(y)y.__rpow__(x)。 此错误已在 Python 3.10 中修复。

object.__neg__(self)

object.__pos__(self)
object.__abs__(self)
object.__invert__(self)

调用以实现一元算术运算(-+abs()~)。
object.__complex__(self)

object.__int__(self)
object.__float__(self)

调用以实现内置函数 complex()int()float()。 应该返回适当类型的值。
object.__index__(self)

调用以实现 operator.index(),以及每当 Python 需要将数字对象无损转换为整数对象时(例如在切片中,或在内置 bin() 中) , hex()oct() 函数)。 此方法的存在表明数字对象是整数类型。 必须返回一个整数。

如果没有定义 __int__(), __float__()__complex__() 则对应的内置函数 int(), [ X141X]float() 和 complex() 回退到 __index__()

object.__round__(self[, ndigits])
object.__trunc__(self)
object.__floor__(self)
object.__ceil__(self)

调用以实现内置函数 round()math 函数 trunc()floor()ceil ()。 除非将 ndigits 传递给 __round__(),否则所有这些方法都应返回截断为 Integral(通常为 int)的对象的值。

如果 __int__()__index__() 都没有定义,则内置函数 int() 回退到 __trunc__()


3.3.9. 使用语句上下文管理器

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

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

有关上下文管理器的更多信息,请参阅 上下文管理器类型

object.__enter__(self)
输入与此对象相关的运行时上下文。 with 语句将此方法的返回值绑定到语句的 as 子句中指定的目标(如果有)。
object.__exit__(self, exc_type, exc_value, traceback)

退出与此对象相关的运行时上下文。 参数描述了导致上下文退出的异常。 如果上下文无异常退出,则所有三个参数都将是 None

如果提供了一个异常,并且该方法希望抑制该异常(即防止它被传播),它应该返回一个真值。 否则,退出该方法时异常将被正常处理。

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

也可以看看

PEP 343 - “with”语句
Python with 语句的规范、背景和示例。


3.3.10. 特殊方法查找

对于自定义类,特殊方法的隐式调用只有在对象类型上定义时才能保证正常工作,而不是在对象的实例字典中。 这种行为是以下代码引发异常的原因:

>>> class C:
...     pass
...
>>> c = C()
>>> c.__len__ = lambda: 5
>>> len(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'C' has no len()

这种行为背后的基本原理在于许多特殊方法,例如 __hash__()__repr__(),这些方法由所有对象(包括类型对象)实现。 如果这些方法的隐式查找使用传统的查找过程,则在类型对象本身上调用时它们将失败:

>>> 1 .__hash__() == hash(1)
True
>>> int.__hash__() == hash(int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' of 'int' object needs an argument

以这种方式错误地尝试调用类的未绑定方法有时被称为“元类混淆”,并且可以通过在查找特殊方法时绕过实例来避免:

>>> type(1).__hash__(1) == hash(1)
True
>>> type(int).__hash__(int) == hash(int)
True

除了为了正确性而绕过任何实例属性之外,隐式特殊方法查找通常还会绕过 __getattribute__() 方法,甚至是对象元类的方法:

>>> class Meta(type):
...     def __getattribute__(*args):
...         print("Metaclass getattribute invoked")
...         return type.__getattribute__(*args)
...
>>> class C(object, metaclass=Meta):
...     def __len__(self):
...         return 10
...     def __getattribute__(*args):
...         print("Class getattribute invoked")
...         return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__()                 # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c)          # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c)                      # Implicit lookup
10

以这种方式绕过 __getattribute__() 机制为解释器中的速度优化提供了很大的空间,代价是在处理特殊方法时具有一定的灵活性(特殊方法 必须在类上设置 对象本身,以便解释器始终如一地调用)。


3.4. 协程

3.4.1. 等待对象

awaitable 对象通常实现 __await__() 方法。 async def 函数返回的协程对象 是可等待的。

笔记

从用 types.coroutine()asyncio.coroutine() 修饰的生成器返回的 generator iterator 对象也是可等待的,但它们没有实现 __await__()


object.__await__(self)
必须返回一个 迭代器 。 应该用于实现 awaitable 对象。 例如, asyncio.Future 实现此方法以与 await 表达式兼容。

3.5 版中的新功能。


也可以看看

PEP 492 有关可等待对象的其他信息。


3.4.2. 协程对象

协程对象awaitable对象。 可以通过调用 __await__() 并迭代结果来控制协程的执行。 当协程执行完毕并返回时,迭代器引发 StopIteration,异常的 value 属性保存返回值。 如果协程引发异常,它将由迭代器传播。 协程不应直接引发未处理的 StopIteration 异常。

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

在 3.5.2 版更改: 在协同程序上等待多次是 RuntimeError


coroutine.send(value)
开始或恢复协程的执行。 如果 valueNone,这相当于推进了 __await__() 返回的迭代器。 如果 value 不是 None,则此方法委托给导致协程挂起的迭代器的 send() 方法。 结果(返回值、StopIteration 或其他异常)与上述迭代 __await__() 返回值时相同。
coroutine.throw(type[, value[, traceback]])
在协程中引发指定的异常。 此方法委托给导致协程挂起的迭代器的 throw() 方法,如果它有这样的方法的话。 否则,在挂起点引发异常。 结果(返回值、StopIteration 或其他异常)与上述迭代 __await__() 返回值时相同。 如果异常没有在协程中被捕获,它会传播回调用者。
coroutine.close()

使协程自行清理并退出。 如果协程被挂起,这个方法首先委托给导致协程挂起的迭代器的 close() 方法,如果它有这样的方法的话。 然后它在挂起点引发 GeneratorExit,导致协程立即清理自己。 最后,协程被标记为已完成执行,即使它从未启动。

协程对象在即将被销毁时使用上述过程自动关闭。


3.4.3. 异步迭代器

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

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

object.__aiter__(self)
必须返回一个 异步迭代器 对象。
object.__anext__(self)
必须返回一个 awaitable 导致迭代器的下一个值。 迭代结束时应该引发 StopAsyncIteration 错误。

异步可迭代对象的示例:

class Reader:
    async def readline(self):
        ...

    def __aiter__(self):
        return self

    async def __anext__(self):
        val = await self.readline()
        if val == b'':
            raise StopAsyncIteration
        return val

3.5 版中的新功能。


在 3.7 版中更改: 在 Python 3.7 之前,__aiter__ 可以返回一个 awaitable,它将解析为 异步迭代器

从 Python 3.7 开始,__aiter__ 必须返回一个异步迭代器对象。 返回任何其他内容将导致 TypeError 错误。


3.4.4. 异步上下文管理器

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

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

object.__aenter__(self)
在语义上类似于 __enter__(),唯一的区别是它必须返回一个 awaitable
object.__aexit__(self, exc_type, exc_value, traceback)
在语义上类似于 __exit__(),唯一的区别是它必须返回一个 awaitable

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

class AsyncContextManager:
    async def __aenter__(self):
        await log('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        await log('exiting context')

3.5 版中的新功能。


脚注

1
在某些受控条件下, 可能在某些情况下更改对象的类型。 不过,这通常不是一个好主意,因为如果处理不当,可能会导致一些非常奇怪的行为。
2
__hash__()__iter__()__reversed__()__contains__() 方法对此有特殊处理; 其他人仍然会引发 TypeError,但可能会依赖于 None 不可调用的行为。
3
这里的“不支持”是指该类没有该方法,或者该方法返回NotImplemented。 如果您想强制回退到正确操作数的反射方法,请不要将方法设置为 None - 这将产生与明确 阻止 此类回退的相反效果。
4
对于相同类型的操作数,假设如果非反射方法 - 例如 __add__() - 失败,则不支持整体操作,这就是不调用反射方法的原因。