3. 数据模型¶
3.1. 对象、值和类型¶
对象是 Python 对数据的抽象。Python 程序中的所有数据都由对象或对象之间的关系表示。(在某种意义上,并且符合冯·诺伊曼的“存储程序计算机”模型,代码也由对象表示。)
每个对象都有一个标识、一个类型和一个值。对象的标识一旦创建就永远不会改变;你可以把它想象成对象在内存中的地址。is
运算符比较两个对象的标识;id()
函数返回一个表示其标识的整数。
CPython 实现细节: 对于 CPython,id(x)
是 x
存储的内存地址。
对象的类型决定了该对象支持的操作(例如,“它是否有长度?”),并且还定义了该类型对象的可能值。type()
函数返回对象的类型(它本身也是一个对象)。与它的标识一样,对象的 类型 也是不可变的。[1]
某些对象的值可以更改。值可以更改的对象被称为可变的;一旦创建其值就不可更改的对象被称为不可变的。(当可变对象的值更改时,包含对可变对象的引用的不可变容器对象的值可能会更改;但是,该容器仍然被认为是不可变的,因为它包含的对象集合无法更改。因此,不可变性并不严格等同于拥有不可更改的值,它更加微妙。)对象的变异性由其类型决定;例如,数字、字符串和元组是不可变的,而字典和列表是可变的。
对象永远不会被显式销毁;但是,当它们变得不可访问时,它们可能会被垃圾回收。允许实现延迟垃圾回收或完全省略垃圾回收——垃圾回收的实现方式是实现质量的问题,只要没有收集到仍然可访问的对象即可。
CPython 实现细节: CPython 目前使用引用计数方案,并(可选地)延迟检测循环链接的垃圾。这种方案会在大多数对象变为不可达时立即回收它们,但不能保证回收包含循环引用的垃圾。有关控制循环垃圾回收的信息,请参阅 gc
模块的文档。其他实现的行为可能不同,CPython 也可能会发生变化。不要依赖于对象变为不可达时立即进行终结(因此,您应该始终显式关闭文件)。
请注意,使用实现的跟踪或调试功能可能会使通常可以回收的对象保持活动状态。另请注意,使用 try
…except
语句捕获异常可能会使对象保持活动状态。
某些对象包含对“外部”资源的引用,例如打开的文件或窗口。可以理解的是,当对象被垃圾回收时,这些资源会被释放,但由于不能保证会发生垃圾回收,因此这些对象还提供了一种显式释放外部资源的方法,通常是 close()
方法。强烈建议程序显式关闭此类对象。try
…finally
语句和 with
语句提供了执行此操作的便捷方法。
某些对象包含对其他对象的引用;这些对象被称为容器。容器的示例包括元组、列表和字典。这些引用是容器值的一部分。在大多数情况下,当我们谈论容器的值时,我们指的是包含对象的值,而不是它们的标识;但是,当我们谈论容器的可变性时,仅指立即包含的对象的标识。因此,如果不可变容器(如元组)包含对可变对象的引用,则如果该可变对象被更改,其值也会更改。
类型会影响对象行为的几乎所有方面。甚至对象标识的重要性在某种意义上也会受到影响:对于不可变类型,计算新值的操作实际上可能会返回对具有相同类型和值的任何现有对象的引用,而对于可变对象,则不允许这样做。例如,在 a = 1; b = 1
之后,a 和 b 可能指向也可能不指向同一个值为 1 的对象,具体取决于实现。这是因为 int
是一个不可变类型,因此可以重用对 1
的引用。此行为取决于所使用的实现,因此不应依赖它,但在使用对象标识测试时应注意这一点。但是,在 c = []; d = []
之后,保证 c 和 d 指向两个不同且唯一的新创建的空列表。(请注意,e = f = []
将相同的对象分配给 e 和 f。)
3.2. 标准类型层次结构¶
以下是 Python 中内置的类型列表。扩展模块(用 C、Java 或其他语言编写,具体取决于实现)可以定义其他类型。未来版本的 Python 可能会向类型层次结构添加类型(例如,有理数、有效存储的整数数组等),尽管此类添加通常会通过标准库提供。
以下某些类型描述包含一个段落,列出“特殊属性”。这些属性提供对实现的访问,不适合一般使用。它们的定义将来可能会更改。
3.2.1. None¶
此类型只有一个值。此值只有一个对象。可以通过内置名称 None
访问此对象。它用于表示许多情况下缺少值,例如,它从不显式返回任何内容的函数返回。其真值为假。
3.2.2. NotImplemented¶
此类型只有一个值。此值只有一个对象。可以通过内置名称 NotImplemented
访问此对象。如果数值方法和丰富的比较方法没有为提供的操作数实现该操作,则应返回此值。(解释器将尝试反射操作或其他回退,具体取决于运算符。)不应在布尔上下文中对其求值。
有关更多详细信息,请参阅 实现算术运算。
在 3.9 版本中更改:在布尔上下文中评估 NotImplemented
已被弃用。虽然它目前评估为真,但它会发出 DeprecationWarning
。在未来版本的 Python 中,它将引发 TypeError
。
3.2.3. Ellipsis¶
此类型只有一个值。此值只有一个对象。可以通过字面量 ...
或内置名称 Ellipsis
访问此对象。其真值为真。
3.2.4. numbers.Number
¶
这些是通过数字字面量创建的,并作为算术运算符和算术内置函数的结果返回。数值对象是不可变的;一旦创建,它们的值永远不会改变。Python 数字当然与数学数字密切相关,但受计算机中数值表示的限制。
数值类的字符串表示形式(由 __repr__()
和 __str__()
计算)具有以下属性
它们是有效的数值字面量,当传递给其类构造函数时,会生成一个具有原始数值值的对象。
可能时,表示形式为 10 进制。
不显示前导零,可能除了小数点前的单个零。
不显示尾随零,可能除了小数点后的单个零。
仅当数字为负数时才显示符号。
Python 区分整数、浮点数和复数
3.2.4.1. numbers.Integral
¶
这些表示来自数学整数集(正数和负数)的元素。
注意
整数表示的规则旨在给出涉及负整数的移位和掩码操作的最有意义的解释。
有两种类型的整数
3.2.4.2. numbers.Real
(float
)¶
这些表示机器级双精度浮点数。您必须受制于底层机器架构(以及 C 或 Java 实现)对于可接受的范围和溢出处理。Python 不支持单精度浮点数;通常使用这些数字的原因是为了节省处理器和内存使用,但它们与在 Python 中使用对象的开销相比相形见绌,因此没有理由用两种浮点数来复杂化语言。
3.2.4.3. numbers.Complex
(complex
)¶
这些表示复数,使用一对机器级的双精度浮点数。浮点数的所有注意事项同样适用于复数。复数 z
的实部和虚部可以通过只读属性 z.real
和 z.imag
获取。
3.2.5. 序列¶
这些表示由非负数索引的有限有序集合。内置函数 len()
返回序列中项目的数量。当序列的长度为 *n* 时,索引集包含数字 0, 1, …, *n*-1。序列 *a* 的第 *i* 项通过 a[i]
选取。一些序列,包括内置序列,通过加上序列长度来解释负下标。例如,a[-2]
等于 a[n-2]
,即长度为 n
的序列 a 的倒数第二项。
序列也支持切片:a[i:j]
选择所有索引 *k* 满足 i <=
k <
j 的项。当用作表达式时,切片是相同类型的序列。上面关于负索引的注释也适用于负切片位置。
一些序列还支持带有第三个“步长”参数的“扩展切片”:a[i:j:k]
选择 *a* 中索引为 *x* 的所有项,其中 x = i + n*k
, n >=
0
且 i <=
x <
j。
序列根据其可变性进行区分
3.2.5.1. 不可变序列¶
不可变序列类型的对象一旦创建就不能更改。(如果对象包含对其他对象的引用,则这些其他对象可能是可变的并且可能会被更改;但是,不可变对象直接引用的对象集合不能更改。)
以下类型是不可变序列
- 字符串
字符串是表示 Unicode 代码点的数值序列。范围
U+0000 - U+10FFFF
中的所有代码点都可以在字符串中表示。Python 没有 char 类型;相反,字符串中的每个代码点都表示为一个长度为1
的字符串对象。内置函数ord()
将代码点从其字符串形式转换为范围0 - 10FFFF
中的整数;chr()
将范围0 - 10FFFF
中的整数转换为相应的长度为1
的字符串对象。str.encode()
可以用来使用给定的文本编码将str
转换为bytes
,bytes.decode()
可以用来实现相反的操作。- 元组
元组的项目是任意 Python 对象。两个或多个项目的元组由以逗号分隔的表达式列表形成。可以通过在表达式后附加逗号来形成一个项目的元组(“单例”)(表达式本身不会创建元组,因为括号必须可用于对表达式进行分组)。可以通过空括号对形成一个空元组。
- 字节
字节对象是不可变数组。项目是 8 位字节,由范围 0 <= x < 256 中的整数表示。字节字面量(如
b'abc'
)和内置的bytes()
构造函数可用于创建字节对象。此外,字节对象可以通过decode()
方法解码为字符串。
3.2.5.2. 可变序列¶
可变序列可以在创建后进行更改。下标和切片表示法可以用作赋值和 del
(删除) 语句的目标。
注意
collections
和 array
模块提供了可变序列类型的其他示例。
目前有两种内在的可变序列类型
- 列表
列表的项目是任意 Python 对象。列表是通过将以逗号分隔的表达式列表放在方括号中形成的。(请注意,不需要特殊情况来形成长度为 0 或 1 的列表。)
- 字节数组
字节数组对象是可变数组。它们由内置的
bytearray()
构造函数创建。除了是可变的(因此不可哈希)之外,字节数组在其他方面提供了与不可变的bytes
对象相同的接口和功能。
3.2.6. 集合类型¶
这些表示无序的、有限的、唯一的不可变对象集合。因此,它们不能通过任何下标索引。但是,它们可以被迭代,并且内置函数 len()
返回集合中项目的数量。集合的常见用途是快速成员资格测试、从序列中删除重复项以及计算数学运算,例如交集、并集、差集和对称差集。
对于集合元素,与字典键相同的不可变性规则适用。请注意,数字类型遵循数字比较的正常规则:如果两个数字比较相等(例如,1
和 1.0
),则它们中只能有一个包含在集合中。
目前有两种内在的集合类型
- 集合
这些表示可变集合。它们由内置的
set()
构造函数创建,并且可以通过几种方法(例如add()
)进行修改。- 冻结集合
这些表示不可变集合。它们由内置的
frozenset()
构造函数创建。由于 frozenset 是不可变的并且 可哈希,因此可以再次用作另一个集合的元素或字典键。
3.2.7. 映射¶
这些表示由任意索引集索引的对象的有限集合。下标表示法 a[k]
从映射 a
中选择由 k
索引的项目;这可以用在表达式中,也可以用作赋值或 del
语句的目标。内置函数 len()
返回映射中项目的数量。
目前只有一种内在的映射类型
3.2.7.1. 字典¶
这些表示由几乎任意的值索引的对象的有限集合。唯一不能接受作为键的值是包含列表或字典或其他通过值而不是对象标识进行比较的可变类型的值,原因是字典的有效实现要求键的哈希值保持不变。用于键的数字类型遵循数字比较的正常规则:如果两个数字比较相等(例如,1
和 1.0
),则它们可以互换使用来索引相同的字典条目。
字典保留插入顺序,这意味着键将以它们在字典中按顺序添加的相同顺序生成。替换现有键不会更改顺序,但是删除一个键并重新插入它会将其添加到末尾,而不是保留其旧位置。
字典是可变的;它们可以通过 {}
表示法创建(请参阅字典显示部分)。
扩展模块 dbm.ndbm
和 dbm.gnu
提供了映射类型的其他示例,collections
模块也提供了。
3.7 版本已更改: 在 Python 3.6 之前的版本中,字典不保留插入顺序。在 CPython 3.6 中,保留了插入顺序,但当时被认为是实现细节,而不是语言保证。
3.2.8. 可调用类型¶
这些类型是可以应用函数调用操作的类型(参见 调用 部分)
3.2.8.1. 用户自定义函数¶
用户自定义函数对象是通过函数定义创建的(参见 函数定义 部分)。在调用时,它应该使用与函数的形参列表项数相同的实参列表。
3.2.8.1.1. 特殊的只读属性¶
属性 |
含义 |
---|---|
|
|
|
单元格对象具有属性 |
3.2.8.1.2. 特殊的 可写属性¶
大多数这些属性都会检查所赋值的类型
属性 |
含义 |
---|---|
|
该函数的文档字符串,如果不可用,则为 |
|
该函数的名称。另请参见: |
|
该函数的限定名称。另请参见: 3.3 版本新增。 |
|
定义该函数的模块的名称,如果不可用,则为 |
|
|
|
表示已编译函数体的代码对象。 |
|
支持任意函数属性的命名空间。另请参见: |
|
一个 |
|
|
|
3.12 版本新增。 |
函数对象还支持获取和设置任意属性,例如,这可以用于将元数据附加到函数。 使用常规属性点号表示法来获取和设置此类属性。
CPython 实现细节: CPython 的当前实现仅支持用户自定义函数上的函数属性。未来可能会支持内置函数上的函数属性。
3.2.8.2. 实例方法¶
实例方法对象将类、类实例和任何可调用对象(通常是用户自定义函数)组合在一起。
特殊的只读属性
|
指向该方法绑定到的类实例对象 |
|
指向原始函数对象 |
|
该方法的文档(与 |
|
该方法的名称(与 |
|
定义该方法的模块的名称,如果不可用,则为 |
方法还支持访问(但不设置)底层函数对象上的任意函数属性。
当获取类的属性(可能通过该类的实例)时,如果该属性是用户定义的函数对象或classmethod
对象,则可以创建用户定义的方法对象。
当通过类的实例检索类中的用户定义的函数对象时创建实例方法对象,其__self__
属性是该实例,并且该方法对象被称为是*绑定的*。新方法的__func__
属性是原始函数对象。
当从类或实例中检索 classmethod
对象时创建实例方法对象,其 __self__
属性是该类本身,其 __func__
属性是类方法下的函数对象。
当调用实例方法对象时,会调用底层函数(__func__
),并将类实例(__self__
)插入到参数列表的前面。例如,当 C
是一个包含函数 f()
定义的类,而 x
是 C
的一个实例时,调用 x.f(1)
等价于调用 C.f(x, 1)
。
当实例方法对象派生自 classmethod
对象时,存储在 __self__
中的“类实例”实际上将是类本身,因此调用 x.f(1)
或 C.f(1)
都等价于调用 f(C,1)
,其中 f
是底层函数。
需要注意的是,作为类实例属性的用户定义函数不会转换为绑定方法;只有当函数是类的属性时才会发生这种情况。
3.2.8.3. 生成器函数¶
使用 yield
语句(参见 yield 语句 部分)的函数或方法称为生成器函数。这种函数在调用时,总是返回一个 迭代器 对象,该对象可用于执行函数体:调用迭代器的 iterator.__next__()
方法将导致函数执行,直到它使用 yield
语句提供一个值。当函数执行 return
语句或执行到末尾时,会引发 StopIteration
异常,并且迭代器将到达要返回的值的集合的末尾。
3.2.8.4. 协程函数¶
使用 async def
定义的函数或方法称为协程函数。这种函数在调用时,会返回一个 协程 对象。它可以包含 await
表达式,以及 async with
和 async for
语句。另请参阅 协程对象 部分。
3.2.8.5. 异步生成器函数¶
使用 async def
定义并且使用 yield
语句的函数或方法称为异步生成器函数。这种函数在调用时,返回一个 异步迭代器 对象,该对象可以在 async for
语句中使用,以执行函数体。
调用异步迭代器的 aiterator.__anext__
方法将返回一个 可等待对象,当等待该对象时,它将执行直到使用 yield
表达式提供一个值。当函数执行空的 return
语句或执行到末尾时,会引发 StopAsyncIteration
异常,并且异步迭代器将到达要生成的值的集合的末尾。
3.2.8.6. 内置函数¶
内置函数对象是 C 函数的包装器。内置函数的示例有 len()
和 math.sin()
(math
是一个标准的内置模块)。参数的数量和类型由 C 函数确定。特殊的只读属性
__doc__
是函数的文档字符串,如果不可用,则为None
。请参阅function.__doc__
。__name__
是函数的名称。请参阅function.__name__
。__self__
设置为None
(但请参阅下一项)。__module__
是函数定义所在的模块的名称,如果不可用,则为None
。请参阅function.__module__
。
3.2.8.7. 内置方法¶
这实际上是内置函数的另一种伪装,这次包含一个传递给 C 函数的对象作为隐式额外参数。内置方法的示例是 alist.append()
,假设 *alist* 是一个列表对象。在这种情况下,特殊的只读属性 __self__
将设置为由 *alist* 表示的对象。(该属性的语义与 其他 实例 方法
相同。)
3.2.8.8. 类¶
类是可调用的。这些对象通常充当自身新实例的工厂,但对于覆盖 __new__()
的类类型,可能存在变体。调用的参数传递给 __new__()
,并且在典型情况下,传递给 __init__()
来初始化新实例。
3.2.8.9. 类实例¶
通过在其类中定义 __call__()
方法,可以使任意类的实例可调用。
3.2.9. 模块¶
模块是 Python 代码的基本组织单元,由 导入系统 创建,该系统由 import
语句调用,或通过调用诸如 importlib.import_module()
和内置 __import__()
等函数来调用。模块对象具有一个由 字典
对象实现的命名空间(这是模块中定义的函数的 __globals__
属性引用的字典)。属性引用将转换为在此字典中的查找,例如,m.x
等价于 m.__dict__["x"]
。模块对象不包含用于初始化模块的代码对象(因为一旦初始化完成,就不再需要它)。
属性赋值会更新模块的命名空间字典,例如,m.x = 1
等价于 m.__dict__["x"] = 1
。
3.2.9.2. 模块对象上的其他可写属性¶
除了上面列出的与导入相关的属性外,模块对象还具有以下可写属性
- module.__doc__¶
模块的文档字符串,如果不可用,则为
None
。另请参阅:__doc__ 属性
。
- module.__annotations__¶
一个字典,其中包含在模块主体执行期间收集的 变量注解。有关使用
__annotations__
的最佳实践,请参阅 注解最佳实践。
3.2.9.3. 模块字典¶
模块对象还具有以下特殊只读属性
- module.__dict__¶
模块的命名空间,作为字典对象。在本文列出的属性中,
__dict__
是唯一不能从模块内部作为全局变量访问的属性;它只能作为模块对象上的属性访问。CPython 实现细节: 由于 CPython 清理模块字典的方式,即使字典仍有活动引用,当模块超出作用域时,模块字典也会被清理。为了避免这种情况,请复制字典或在使用其字典时保留模块。
3.2.10. 自定义类¶
自定义类类型通常通过类定义创建(请参阅 类定义 部分)。类具有由字典对象实现的命名空间。类属性引用被转换为在此字典中的查找,例如,C.x
被转换为 C.__dict__["x"]
(尽管有许多钩子允许其他方式查找属性)。当在那里找不到属性名称时,属性搜索会继续在基类中进行。这种对基类的搜索使用 C3 方法解析顺序,即使在存在“菱形”继承结构(其中有多条继承路径通向共同祖先)的情况下,该顺序也能正确运行。有关 Python 使用的 C3 MRO 的更多详细信息,请参阅 Python 2.3 方法解析顺序。
当类属性引用(例如,对于类 C
)会产生类方法对象时,它会被转换为实例方法对象,该实例方法对象的 __self__
属性为 C
。当它会产生 staticmethod
对象时,它会被转换为静态方法对象包装的对象。有关从类检索的属性可能与实际包含在其 __dict__
中的属性不同的另一种方式,请参阅 实现描述符 部分。
类属性赋值会更新类的字典,而不是基类的字典。
可以调用类对象(如上所述)来产生类实例(如下所述)。
3.2.10.1. 特殊属性¶
属性 |
含义 |
---|---|
|
类的名称。另请参阅: |
|
类的 限定名称。另请参阅: |
|
定义该类的模块的名称。 |
|
一个 |
|
一个 |
|
类的文档字符串,如果未定义,则为 |
|
一个字典,其中包含在类主体执行期间收集的 变量注解。有关使用 注意 直接访问类对象的 |
|
3.12 版本新增。 |
|
一个 3.13 版本新增。 |
|
类定义的第一行行号,包括装饰器。设置 3.13 版本新增。 |
|
在方法解析期间查找基类时考虑的类的 |
3.2.10.2. 特殊方法¶
除了上面描述的特殊属性之外,所有 Python 类还具有以下两个可用的方法
- type.__subclasses__()¶
每个类都保留一个指向其直接子类的弱引用列表。 此方法返回所有仍然存活的引用列表。 该列表的顺序为定义顺序。 示例
>>> class A: pass >>> class B(A): pass >>> A.__subclasses__() [<class 'B'>]
3.2.11. 类实例¶
通过调用类对象(见上文)来创建类实例。 类实例具有一个以字典形式实现的命名空间,该字典是搜索属性引用的第一个位置。 如果在那里找不到属性,并且实例的类具有该名称的属性,则继续搜索类属性。 如果找到的类属性是用户定义的函数对象,则将其转换为实例方法对象,其 __self__
属性是该实例。 静态方法和类方法对象也会被转换; 请参见上面 “类” 下的内容。 有关通过实例检索的类属性可能与实际存储在类的 __dict__
中的对象不同的另一种方式,请参阅实现描述符 部分。 如果没有找到类属性,并且该对象的类具有 __getattr__()
方法,则会调用该方法来满足查找。
属性赋值和删除会更新实例的字典,而不是类的字典。 如果该类具有 __setattr__()
或 __delattr__()
方法,则会调用该方法,而不是直接更新实例字典。
如果类实例具有某些特殊名称的方法,则可以假装为数字、序列或映射。 请参阅特殊方法名称部分。
3.2.11.1. 特殊属性¶
- object.__class__¶
类实例所属的类。
3.2.12. I/O 对象(也称为文件对象)¶
文件对象表示一个打开的文件。 有多种快捷方式可用于创建文件对象:open()
内置函数,以及 os.popen()
、os.fdopen()
和套接字对象的 makefile()
方法(也可能是扩展模块提供的其他函数或方法)。
对象 sys.stdin
、sys.stdout
和 sys.stderr
被初始化为与解释器的标准输入、输出和错误流相对应的文件对象; 它们都以文本模式打开,因此遵循 io.TextIOBase
抽象类定义的接口。
3.2.13. 内部类型¶
解释器内部使用的一些类型会暴露给用户。 它们的定义可能会在解释器的未来版本中更改,但在此为了完整性而提及它们。
3.2.13.1. 代码对象¶
代码对象表示*字节编译的*可执行 Python 代码或字节码。 代码对象和函数对象之间的区别在于,函数对象包含对函数全局变量(定义它的模块)的显式引用,而代码对象不包含上下文;此外,默认参数值存储在函数对象中,而不是在代码对象中(因为它们表示在运行时计算的值)。 与函数对象不同,代码对象是不可变的,并且不包含(直接或间接)对可变对象的引用。
3.2.13.1.1. 特殊只读属性¶
|
函数名称 |
|
函数的完全限定名称 3.11 版本新增。 |
|
函数具有的位置参数总数(包括仅限位置的参数和带有默认值的参数) |
|
函数具有的仅限位置参数数量(包括带有默认值的参数) |
|
函数具有的仅关键字参数数量(包括带有默认值的参数) |
|
函数使用的局部变量的数量(包括参数) |
|
包含函数中局部变量名称的 |
|
|
|
一个 注意:不包括对全局名称和内置名称的引用。 |
|
一个字符串,表示函数中字节码指令的序列。 |
|
|
|
|
|
编译代码的文件名。 |
|
函数的第一行行号。 |
|
一个字符串,编码从字节码偏移到行号的映射。有关详细信息,请参阅解释器的源代码。 3.12 版本起已弃用: 代码对象的此属性已弃用,可能会在 Python 3.15 中删除。 |
|
代码对象所需的堆栈大小。 |
|
一个 |
为co_flags
定义了以下标志位:如果函数使用 *arguments
语法接受任意数量的位置参数,则设置位 0x04
;如果函数使用 **keywords
语法接受任意关键字参数,则设置位 0x08
;如果函数是生成器,则设置位 0x20
。有关可能存在的每个标志的语义的详细信息,请参阅代码对象位标志。
未来特性声明 ( from __future__ import division
) 也在co_flags
中使用位来指示是否在启用特定特性时编译代码对象:如果函数是在启用未来除法的情况下编译的,则设置位 0x2000
;位 0x10
和 0x1000
在早期版本的 Python 中使用。
co_flags
中的其他位保留供内部使用。
如果代码对象表示一个函数,则co_consts
中的第一项是函数的文档字符串,如果未定义,则为 None
。
3.2.13.1.2. 代码对象的方法¶
- codeobject.co_positions()¶
返回代码对象中每个字节码指令的源代码位置的可迭代对象。
迭代器返回包含
(start_line, end_line, start_column, end_column)
的元组
。第 *i* 个元组对应于编译为第 *i* 个代码单元的源代码的位置。列信息是给定源行上的 0 索引 utf-8 字节偏移量。此位置信息可能会丢失。以下是一些可能发生这种情况的非详尽列表:
使用
-X
no_debug_ranges
运行解释器。加载在使用
-X
no_debug_ranges
时编译的 pyc 文件。与人工指令对应的位置元组。
由于特定于实现的限制而无法表示的行号和列号。
发生这种情况时,某些或所有元组元素可能是
None
。3.11 版本新增。
注意
此功能需要在代码对象中存储列位置,这可能会导致编译的 Python 文件或解释器内存使用量的磁盘使用量略有增加。要避免存储额外信息和/或停用打印额外的回溯信息,可以使用
-X
no_debug_ranges
命令行标志或PYTHONNODEBUGRANGES
环境变量。
- codeobject.co_lines()¶
返回一个迭代器,该迭代器产生有关连续的字节码范围的信息。产生的每一项都是一个
(start, end, lineno)
元组
产生的项将具有以下属性:
产生的第一个范围的
start
为 0。(start, end)
范围将是非递减且连续的。也就是说,对于任意一对tuple
,第二个的start
将等于第一个的end
。不会有向后范围:对于所有三元组,
end >= start
。
允许零宽度范围,其中
start == end
。零宽度范围用于在源代码中存在,但已被 字节码 编译器消除的行。在 3.10 版本中添加。
另请参阅
- PEP 626 - 用于调试和其他工具的精确行号。
引入
co_lines()
方法的 PEP。
- codeobject.replace(**kwargs)¶
返回代码对象的副本,其中包含指定字段的新值。
通用函数
copy.replace()
也支持代码对象。在 3.8 版本中添加。
3.2.13.2. 帧对象¶
帧对象表示执行帧。它们可能出现在 回溯对象 中,并且还会传递给注册的跟踪函数。
3.2.13.2.1. 特殊的只读属性¶
|
指向前一个堆栈帧(朝向调用者),如果这是最底层的堆栈帧,则为 |
|
在此帧中执行的 代码对象。访问此属性会引发 审计事件 |
|
帧用于查找 局部变量 的映射。如果帧引用 优化作用域,则可能返回一个直写代理对象。 在 3.13 版本中更改: 为优化作用域返回代理。 |
|
帧用于查找 全局变量 的字典 |
|
帧用于查找 内置(内在)名称 的字典 |
|
3.2.13.2.2. 特殊的写入属性¶
|
如果不是 |
|
将此属性设置为 |
|
将此属性设置为 |
|
帧的当前行号 – 从跟踪函数内部写入此属性会跳转到给定的行(仅适用于最底层的帧)。调试器可以通过写入此属性来实现跳转命令(又名设置下一条语句)。 |
3.2.13.2.3. 帧对象方法¶
帧对象支持一种方法
- frame.clear()¶
此方法清除帧持有的所有对 局部变量 的引用。此外,如果该帧属于 生成器,则该生成器将被最终确定。这有助于打破涉及帧对象的引用循环(例如,当捕获 异常 并存储其 回溯 以供以后使用时)。
如果帧当前正在执行或暂停,则会引发
RuntimeError
。3.4 版本新增。
在 3.13 版本中更改: 尝试清除暂停的帧会引发
RuntimeError
(就像执行帧一直以来的情况一样)。
3.2.13.3. 回溯对象¶
回溯对象表示 异常 的堆栈跟踪。当发生异常时,会隐式创建回溯对象,也可以通过调用 types.TracebackType
来显式创建。
在 3.7 版本中更改: 现在可以从 Python 代码显式实例化回溯对象。
对于隐式创建的回溯,当搜索异常处理程序展开执行堆栈时,在每个展开级别,回溯对象都会插入到当前回溯的前面。当输入异常处理程序时,堆栈跟踪可供程序使用。(请参阅 try 语句 部分。)它可以作为 sys.exc_info()
返回的元组的第三项访问,也可以作为捕获的异常的 __traceback__
属性访问。
当程序不包含合适的处理程序时,堆栈跟踪将(格式精美地)写入标准错误流;如果解释器是交互式的,它也会作为 sys.last_traceback
提供给用户。
对于显式创建的回溯,如何链接 tb_next
属性以形成完整的堆栈跟踪取决于回溯的创建者。
特殊的只读属性
|
指向当前级别的执行 帧。 访问此属性会引发 审计事件 |
|
给出发生异常的行号 |
|
指示“精确指令”。 |
如果异常发生在没有匹配的 except 子句或带有 finally
子句的 try
语句中,则回溯中的行号和最后一条指令可能与其 帧对象 的行号不同。
- traceback.tb_next¶
特殊的、可写的属性
tb_next
是堆栈跟踪中的下一层(朝向发生异常的帧),如果没有下一层,则为None
。在 3.7 版本中更改: 此属性现在可写
3.2.13.4. 切片对象¶
切片对象用于表示 __getitem__()
方法的切片。它们也由内置的 slice()
函数创建。
特殊的只读属性:start
是下界;stop
是上界;step
是步长值;如果省略,则每个都为 None
。这些属性可以具有任何类型。
切片对象支持一个方法
- slice.indices(self, length)¶
此方法接受一个整数参数 length,并计算如果将切片对象应用于长度为 length 的项的序列时,该切片对象将描述的切片的信息。它返回一个包含三个整数的元组;分别为切片的 start 和 stop 索引以及 step 或跨度长度。缺失或超出范围的索引的处理方式与常规切片一致。
3.2.13.5. 静态方法对象¶
静态方法对象提供了一种克服上述函数对象到方法对象的转换的方法。静态方法对象是任何其他对象的包装器,通常是用户定义的方法对象。当从类或类实例检索静态方法对象时,实际返回的对象是被包装的对象,该对象不受任何进一步转换的影响。静态方法对象也是可调用的。静态方法对象由内置的 staticmethod()
构造函数创建。
3.2.13.6. 类方法对象¶
类方法对象,就像静态方法对象一样,是另一个对象的包装器,它会改变从类和类实例中检索该对象的方式。类方法对象在此类检索时的行为在上面的 “实例方法” 中进行了描述。类方法对象由内置的 classmethod()
构造函数创建。
3.3. 特殊方法名称¶
类可以通过定义具有特殊名称的方法来实现由特殊语法(例如算术运算或下标和切片)调用的某些操作。这是 Python 的 运算符重载 方法,允许类定义它们自己关于语言运算符的行为。例如,如果一个类定义了一个名为 __getitem__()
的方法,并且 x
是此类的实例,那么 x[i]
大致等价于 type(x).__getitem__(x, i)
。除非另有说明,否则当没有定义适当的方法时,尝试执行操作会引发异常(通常为 AttributeError
或 TypeError
)。
将特殊方法设置为 None
表示相应的操作不可用。例如,如果一个类将 __iter__()
设置为 None
,则该类不可迭代,因此在其实例上调用 iter()
将引发 TypeError
(而不会回退到 __getitem__()
)。[2]
在实现模拟任何内置类型的类时,重要的是模拟的实现程度应与所建模对象有意义的程度相符。例如,某些序列可能非常适合检索单个元素,但提取切片可能没有意义。(这方面的一个例子是 W3C 文档对象模型中的 NodeList
接口。)
3.3.1. 基本自定义¶
- object.__new__(cls[, ...])¶
调用此方法以创建类 cls 的新实例。
__new__()
是一个静态方法(特殊情况,因此您不需要将其声明为静态方法),它将请求实例的类作为其第一个参数。其余参数是传递给对象构造函数表达式(对类的调用)的参数。__new__()
的返回值应该是新的对象实例(通常是 cls 的实例)。典型的实现通过使用适当的参数调用超类的
__new__()
方法,并使用super().__new__(cls[, ...])
来创建类的新实例,然后根据需要修改新创建的实例,然后再返回它。如果在对象构造期间调用
__new__()
并且它返回 cls 的实例,则将像__init__(self[, ...])
一样调用新实例的__init__()
方法,其中 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__()
方法会被调用。weakref.finalize
提供了一种直接的方法来注册一个清理函数,以便在对象被垃圾回收时调用。注意
del x
不会直接调用x.__del__()
— 前者将x
的引用计数减 1,而后者只有在x
的引用计数达到零时才会被调用。CPython 实现细节: 引用循环可能会阻止对象的引用计数变为零。在这种情况下,循环稍后会被 循环垃圾回收器 检测并删除。引用循环的一个常见原因是当异常在局部变量中被捕获时。帧的局部变量随后引用该异常,该异常引用其自己的回溯,而回溯又引用回溯中捕获的所有帧的局部变量。
另请参阅
gc
模块的文档。警告
由于调用
__del__()
方法的情况很复杂,因此在执行过程中发生的异常会被忽略,并且会在sys.stderr
上打印警告。特别是
- object.__repr__(self)¶
由内置函数
repr()
调用,以计算对象的“官方”字符串表示形式。如果可能,这应该看起来像一个有效的 Python 表达式,可以用来重新创建具有相同值的对象(给定适当的环境)。如果不可能,则应返回<...一些有用的描述...>
形式的字符串。返回值必须是字符串对象。如果一个类定义了__repr__()
但没有定义__str__()
,那么当需要该类的实例的“非正式”字符串表示形式时,也会使用__repr__()
。这通常用于调试,因此表示形式必须信息丰富且明确。
object
类本身提供了默认实现。
- object.__str__(self)¶
由
str(object)
、默认的__format__()
实现和内置函数print()
调用,以计算对象的“非正式”或漂亮的可打印字符串表示形式。返回值必须是 str 对象。此方法与
object.__repr__()
不同之处在于,不期望__str__()
返回有效的 Python 表达式:可以使用更方便或简洁的表示形式。内置类型
object
定义的默认实现调用object.__repr__()
。
- object.__format__(self, format_spec)¶
由内置函数
format()
调用,并且通过扩展,由 格式化字符串字面量 和str.format()
方法的计算调用,以生成对象的“格式化”字符串表示形式。format_spec 参数是一个字符串,其中包含所需格式选项的描述。format_spec 参数的解释取决于实现__format__()
的类型,但是大多数类会将格式化委托给内置类型之一,或者使用类似的格式化选项语法。有关标准格式语法的描述,请参阅格式规范微语言。
返回值必须是字符串对象。
object
类的默认实现应该给定一个空的 format_spec 字符串。它会委托给__str__()
。在 3.4 版本中变更: 如果传递任何非空字符串,则
object
本身的 __format__ 方法会引发TypeError
。在 3.7 版本中变更:
object.__format__(x, '')
现在等价于str(x)
而不是format(str(x), '')
。
- 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
。按照惯例,对于成功的比较,返回False
和True
。但是,这些方法可以返回任何值,因此,如果比较运算符在布尔上下文中(例如,在if
语句的条件中)使用,Python 将对该值调用bool()
,以确定结果是真还是假。默认情况下,
object
通过使用is
来实现__eq__()
,在比较为假的情况下返回NotImplemented
:True if x is y else NotImplemented
。对于__ne__()
,默认情况下它会委托给__eq__()
并反转结果,除非它是NotImplemented
。比较运算符之间或默认实现之间没有其他隐含的关系;例如,(x<y or x==y)
的真值并不意味着x<=y
。要从单个根操作自动生成排序操作,请参阅functools.total_ordering()
。默认情况下,
object
类提供了与 值比较 一致的实现:相等性根据对象标识进行比较,而顺序比较会引发TypeError
。每个默认方法可以直接生成这些结果,但也可能返回NotImplemented
。有关创建支持自定义比较操作并可用作字典键的 可哈希 对象的一些重要说明,请参阅关于
__hash__()
的段落。这些方法没有交换参数的版本(在左参数不支持该操作但右参数支持时使用);相反,
__lt__()
和__gt__()
互为反射,__le__()
和__ge__()
互为反射,以及__eq__()
和__ne__()
是它们自己的反射。如果操作数的类型不同,并且右操作数的类型是左操作数类型的直接或间接子类,则右操作数的反射方法具有优先级,否则左操作数的方法具有优先级。不考虑虚拟子类化。当没有合适的方法返回除
NotImplemented
以外的任何值时,==
和!=
运算符将分别回退到is
和is not
。
- object.__hash__(self)¶
由内置函数
hash()
以及对哈希集合的成员的操作(包括set
、frozenset
和dict
)调用。__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__()
方法(继承自object
类);有了它们,所有对象都会被认为是不相等的(除了它们自身),并且x.__hash__()
会返回一个合适的值,使得x == y
意味着x is y
和hash(x) == hash(y)
两者都成立。一个重写了
__eq__()
但没有定义__hash__()
的类,它的__hash__()
将被隐式设置为None
。当一个类的__hash__()
方法为None
时,当程序尝试检索它们的哈希值时,该类的实例将会引发相应的TypeError
,并且在检查isinstance(obj, collections.abc.Hashable)
时也会被正确地识别为不可哈希。如果一个重写了
__eq__()
的类需要保留父类的__hash__()
实现,则必须通过设置__hash__ = <ParentClass>.__hash__
来显式告知解释器。如果一个没有重写
__eq__()
的类希望抑制哈希支持,它应该在类定义中包含__hash__ = None
。一个定义了自己的__hash__()
并显式引发TypeError
的类,会被isinstance(obj, collections.abc.Hashable)
调用错误地识别为可哈希的。注意
默认情况下,str 和 bytes 对象的
__hash__()
值会使用一个不可预测的随机值进行“加盐”。虽然它们在单个 Python 进程中保持不变,但它们在 Python 的重复调用之间是不可预测的。这旨在提供保护,防止由于精心选择的输入利用了字典插入的最坏情况性能,即 O(n2) 复杂度而导致的拒绝服务。有关详细信息,请参阅 http://ocert.org/advisories/ocert-2011-003.html。
更改哈希值会影响集合的迭代顺序。Python 从未对这种排序做出保证(并且它通常在 32 位和 64 位版本之间有所不同)。
另请参阅
PYTHONHASHSEED
。在 3.3 版本中更改: 默认启用哈希随机化。
3.3.2. 自定义属性访问¶
可以定义以下方法来自定义类实例的属性访问(使用、赋值或删除 x.name
)的含义。
- object.__getattr__(self, name)¶
当默认属性访问失败并引发
AttributeError
时调用(要么是__getattribute__()
引发了AttributeError
,因为 name 不是self
的实例属性或类树中的属性;要么是 name 属性的__get__()
引发了AttributeError
)。此方法应该返回(计算出的)属性值或引发AttributeError
异常。object
类本身不提供此方法。请注意,如果通过正常机制找到了属性,则不会调用
__getattr__()
。(这是__getattr__()
和__setattr__()
之间有意的不对称。)这样做既是出于效率方面的考虑,也是因为否则__getattr__()
将无法访问实例的其他属性。请注意,至少对于实例变量,您可以通过不在实例属性字典中插入任何值(而是将它们插入到另一个对象中)来实现完全控制。请参阅下面的__getattribute__()
方法,以了解如何真正地完全控制属性访问。
- object.__getattribute__(self, name)¶
无条件调用,以实现类的实例的属性访问。如果该类还定义了
__getattr__()
,则除非__getattribute__()
显式调用它或引发AttributeError
,否则不会调用后者。此方法应返回(计算后的)属性值,或引发AttributeError
异常。为了避免此方法中的无限递归,其实现应始终调用具有相同名称的基类方法来访问它需要的任何属性,例如,object.__getattribute__(self, name)
。对于某些敏感的属性访问,会引发带有参数
obj
和name
的 审计事件object.__getattr__
。
- object.__setattr__(self, name, value)¶
当尝试属性赋值时调用。这将代替正常机制(即,将值存储在实例字典中)被调用。name 是属性名称,value 是要分配给它的值。
如果
__setattr__()
想要分配给实例属性,它应该调用具有相同名称的基类方法,例如,object.__setattr__(self, name, value)
。对于某些敏感的属性赋值,会引发带有参数
obj
、name
、value
的 审计事件object.__setattr__
。
- object.__delattr__(self, name)¶
类似于
__setattr__()
,但用于属性删除而不是赋值。只有当del obj.name
对该对象有意义时才应实现此方法。对于某些敏感的属性删除,会引发带有参数
obj
和name
的 审计事件object.__delattr__
。
3.3.2.1. 自定义模块属性访问¶
特殊名称 __getattr__
和 __dir__
也可用于自定义对模块属性的访问。模块级别的 __getattr__
函数应接受一个参数,该参数是属性的名称,并返回计算后的值或引发 AttributeError
。如果通过正常查找(即 object.__getattribute__()
)在模块对象上找不到属性,则在引发 AttributeError
之前,会在模块的 __dict__
中搜索 __getattr__
。如果找到,则会使用属性名称调用它并返回结果。
__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. 实现描述符¶
以下方法仅当包含该方法的类的实例(所谓的描述符类)出现在所有者类中时才适用(描述符必须位于所有者的类字典中或其父级之一的类字典中)。在下面的示例中,“属性”指的是其名称是所有者类的 __dict__
中属性的键的属性。object
类本身不实现这些协议中的任何一个。
- object.__get__(self, instance, owner=None)¶
调用以获取所有者类的属性(类属性访问)或该类的实例的属性(实例属性访问)。可选的 owner 参数是所有者类,而 instance 是通过其访问属性的实例,或者当通过 owner 访问属性时为
None
。此方法应返回计算后的属性值或引发
AttributeError
异常。PEP 252 指定
__get__()
可使用一个或两个参数调用。Python 自己的内置描述符支持此规范;但是,某些第三方工具可能具有需要两个参数的描述符。Python 自己的__getattribute__()
实现始终传递两个参数,无论它们是否是必需的。
- object.__set__(self, instance, value)¶
调用以将所有者类的实例 instance 上的属性设置为新值 value。
请注意,添加
__set__()
或__delete__()
会将描述符的类型更改为“数据描述符”。有关更多详细信息,请参阅 调用描述符。
- object.__delete__(self, instance)¶
调用以删除所有者类的实例 instance 上的属性。
描述符的实例也可能存在 __objclass__
属性
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)
。- 超类绑定
诸如
super(A, a).x
的点查找会在a.__class__.__mro__
中搜索跟随A
的基类B
,然后返回B.__dict__['x'].__get__(a, A)
。如果不是描述器,则x
将保持不变地返回。
对于实例绑定,描述器调用的优先级取决于定义了哪些描述器方法。一个描述器可以定义 __get__()
、 __set__()
和 __delete__()
的任意组合。如果它没有定义 __get__()
,那么除非对象的实例字典中有一个值,否则访问该属性将返回描述器对象本身。如果描述器定义了 __set__()
和/或 __delete__()
,则它是一个数据描述器;如果它都没有定义,则它是一个非数据描述器。通常,数据描述器定义了 __get__()
和 __set__()
,而非数据描述器只有 __get__()
方法。定义了 __get__()
和 __set__()
(和/或 __delete__()
) 的数据描述器总是会覆盖实例字典中的重新定义。相反,非数据描述器可以被实例覆盖。
Python 方法(包括用 @staticmethod
和 @classmethod
修饰的方法)被实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许各个实例获得与同一类的其他实例不同的行为。
property()
函数被实现为数据描述器。因此,实例无法覆盖属性的行为。
3.3.2.4. __slots__¶
__slots__ 允许我们显式声明数据成员(如属性),并禁止创建 __dict__
和 __weakref__ (除非在 __slots__ 中显式声明或在父类中可用。)
与使用 __dict__
相比,节省的空间可能非常显著。属性查找速度也可以显著提高。
- object.__slots__¶
这个类变量可以被赋值为一个字符串、可迭代对象或带有实例使用的变量名的字符串序列。__slots__ 为声明的变量保留空间,并阻止为每个实例自动创建
__dict__
和 __weakref__。
关于使用 __slots__ 的注意事项
当从没有 __slots__ 的类继承时,实例的
__dict__
和 __weakref__ 属性始终可访问。如果没有
__dict__
变量,实例无法被分配未在 __slots__ 定义中列出的新变量。尝试分配给未列出的变量名会引发AttributeError
。如果需要动态分配新变量,请将'__dict__'
添加到 __slots__ 声明中的字符串序列中。如果没有每个实例的 __weakref__ 变量,则定义 __slots__ 的类不支持对其实例的
弱引用
。如果需要弱引用支持,请将'__weakref__'
添加到 __slots__ 声明中的字符串序列中。__slots__ 在类级别通过为每个变量名创建 描述符 来实现。因此,类属性不能用于为 __slots__ 定义的实例变量设置默认值;否则,类属性将覆盖描述符的赋值。
__slots__ 声明的作用不限于它定义的类。在父类中声明的 __slots__ 在子类中可用。但是,子类的实例将获得
__dict__
和 __weakref__,除非子类也定义了 __slots__ (其中应仅包含任何额外槽的名称)。如果一个类定义了一个基类中也定义的槽,则由基类槽定义的实例变量不可访问(除非直接从基类检索其描述符)。这使得程序的含义未定义。将来可能会添加一个检查以防止这种情况。
如果为从
“可变长度” 内置类型
(例如int
、bytes
和tuple
)派生的类定义了非空的 __slots__,则会引发TypeError
。任何非字符串 可迭代对象 都可以赋值给 __slots__。
如果使用
字典
来赋值 __slots__,则字典的键将用作槽名。字典的值可以用于提供每个属性的文档字符串,这些文档字符串将被inspect.getdoc()
识别,并显示在help()
的输出中。__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 版本中添加。
创建类时,type.__new__()
会扫描类变量,并回调到具有 __set_name__()
钩子的那些变量。
- object.__set_name__(self, owner, name)¶
在创建所有者类 owner 时自动调用。该对象已在该类中分配给 name
class A: x = C() # Automatically calls: x.__set_name__(A, 'x')
如果在创建类后分配类变量,则不会自动调用
__set_name__()
。 如果需要,可以直接调用__set_name__()
。class A: pass c = C() A.x = c # The hook is not called c.__set_name__(A, 'x') # Manually invoke the hook
有关更多详细信息,请参阅 创建类对象。
在 3.6 版本中添加。
3.3.3.1. 元类¶
默认情况下,类是使用 type()
构造的。 类主体在一个新的命名空间中执行,并且类名称在本地绑定到 type(name, bases, namespace)
的结果。
可以通过在类定义行中传递 metaclass
关键字参数,或通过继承包含此类参数的现有类来自定义类创建过程。在以下示例中,MyClass
和 MySubclass
都是 Meta
的实例
class Meta(type):
pass
class MyClass(metaclass=Meta):
pass
class MySubclass(MyClass):
pass
在类定义中指定的任何其他关键字参数都会传递给下面描述的所有元类操作。
当执行类定义时,会发生以下步骤
解析 MRO 条目;
确定适当的元类;
准备类命名空间;
执行类主体;
创建类对象。
3.3.3.2. 解析 MRO 条目¶
- object.__mro_entries__(self, bases)¶
如果出现在类定义中的基类不是
type
的实例,则会在基类上搜索__mro_entries__()
方法。 如果找到__mro_entries__()
方法,则在创建类时,将基类替换为对__mro_entries__()
的调用的结果。 该方法使用传递给 bases 参数的原始基类元组进行调用,并且必须返回一个类元组,该元组将代替基类使用。 返回的元组可以为空:在这些情况下,将忽略原始基类。
另请参阅
types.resolve_bases()
动态解析不是
type
实例的基类。types.get_original_bases()
检索在
__mro_entries__()
修改之前类的“原始基类”。- PEP 560
对 typing 模块和泛型类型的核心支持。
3.3.3.3. 确定适当的元类¶
确定类定义的适当元类,如下所示
如果没有给出基类和显式元类,则使用
type()
;如果给出了显式元类,并且它不是
type()
的实例,则直接将其用作元类;如果
type()
的实例作为显式元类给出,或者定义了基类,则使用最派生的元类。
最派生的元类选自显式指定的元类(如果有)和所有指定基类的元类(即 type(cls)
)。 最派生的元类是 所有 这些候选元类的子类型。 如果没有候选元类满足该标准,则类定义将失败并显示 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()
的零参数形式能够根据词法作用域正确识别正在定义的类,而用于进行当前调用的类或实例则根据传递给方法的第一个参数来识别。
CPython 实现细节: 在 CPython 3.6 及更高版本中,__class__
单元格作为类命名空间中的 __classcell__
条目传递给元类。如果存在,则必须将其传播到 type.__new__
调用,以便正确初始化类。否则,在 Python 3.8 中将导致 RuntimeError
。
当使用默认元类 type
或最终调用 type.__new__
的任何元类时,在创建类对象后,将调用以下额外的自定义步骤:
type.__new__
方法收集类命名空间中定义了__set_name__()
方法的所有属性;这些
__set_name__
方法会被调用,并传入正在定义的类和该特定属性的分配名称;新类的直接父类在其方法解析顺序中调用
__init_subclass__()
钩子。
创建类对象后,它会被传递给类定义中包含的类装饰器(如果有),并且结果对象会作为定义的类绑定到局部命名空间中。
当 type.__new__
创建一个新类时,作为命名空间参数提供的对象会被复制到一个新的有序映射中,并且原始对象会被丢弃。新的副本会包装在一个只读代理中,该代理会成为类对象的 __dict__
属性。
另请参阅
- PEP 3135 - 新的 super
描述隐式的
__class__
闭包引用
3.3.3.7. 元类的用途¶
元类的潜在用途是无限的。一些已被探索的想法包括枚举、日志记录、接口检查、自动委托、自动属性创建、代理、框架以及自动资源锁定/同步。
3.3.4. 自定义实例和子类检查¶
以下方法用于覆盖内置函数 isinstance()
和 issubclass()
的默认行为。
特别是,元类 abc.ABCMeta
实现了这些方法,以便允许将抽象基类(ABC)作为“虚拟基类”添加到任何类或类型(包括内置类型),包括其他 ABC。
- type.__instancecheck__(self, instance)¶
如果 instance 应被视为 class 的(直接或间接)实例,则返回 true。如果已定义,则调用它来实现
isinstance(instance, class)
。
- type.__subclasscheck__(self, subclass)¶
如果 subclass 应被视为 class 的(直接或间接)子类,则返回 true。如果已定义,则调用它来实现
issubclass(subclass, class)
。
请注意,这些方法在类的类型(元类)上查找。它们不能在实际类中定义为类方法。这与在实例上调用的特殊方法的查找一致,只是在这种情况下,实例本身就是一个类。
另请参阅
- PEP 3119 - 引入抽象基类
包括通过
__instancecheck__()
和__subclasscheck__()
自定义isinstance()
和issubclass()
行为的规范,以及在将抽象基类(请参阅abc
模块)添加到语言的背景下对此功能的需求。
3.3.5. 模拟泛型类型¶
当使用 类型注解 时,使用 Python 的方括号表示法参数化 泛型类型 通常很有用。例如,注解 list[int]
可用于表示一个 list
,其中所有元素都是 int
类型。
另请参阅
- PEP 484 - 类型提示
介绍 Python 的类型注解框架
- 泛型别名类型
表示参数化泛型类的对象的文档
- 泛型、用户定义的泛型 和
typing.Generic
关于如何实现可在运行时参数化并可被静态类型检查器理解的泛型类的文档。
一个类通常只有在定义了特殊的类方法 __class_getitem__()
时才能被参数化。
- classmethod object.__class_getitem__(cls, key)¶
返回一个对象,表示由 key 中找到的类型参数对泛型类的特化。
当在类上定义时,
__class_getitem__()
自动成为类方法。因此,在定义它时,无需使用@classmethod
进行装饰。
3.3.5.1. __class_getitem__ 的目的¶
__class_getitem__()
的目的是允许运行时参数化标准库泛型类,以便更轻松地将 类型提示 应用于这些类。
要实现可以在运行时参数化并可被静态类型检查器理解的自定义泛型类,用户应该从已经实现 __class_getitem__()
的标准库类继承,或者从 typing.Generic
继承,它有自己的 __class_getitem__()
实现。
在标准库之外定义的类上自定义实现 __class_getitem__()
可能不会被 mypy 等第三方类型检查器理解。不鼓励在任何类上使用 __class_getitem__()
用于类型提示以外的目的。
3.3.5.2. __class_getitem__ 与 __getitem__¶
通常,使用方括号对对象进行下标操作会调用对象类中定义的 __getitem__()
实例方法。但是,如果被下标操作的对象本身是一个类,则可能会调用类方法 __class_getitem__()
。如果定义正确,__class_getitem__()
应该返回一个 GenericAlias 对象。
对于表达式 obj[x]
,Python 解释器会遵循类似以下的过程来决定是调用 __getitem__()
还是 __class_getitem__()
from inspect import isclass
def subscribe(obj, x):
"""Return the result of the expression 'obj[x]'"""
class_of_obj = type(obj)
# If the class of obj defines __getitem__,
# call class_of_obj.__getitem__(obj, x)
if hasattr(class_of_obj, '__getitem__'):
return class_of_obj.__getitem__(obj, x)
# Else, if obj is a class and defines __class_getitem__,
# call obj.__class_getitem__(x)
elif isclass(obj) and hasattr(obj, '__class_getitem__'):
return obj.__class_getitem__(x)
# Else, raise an exception
else:
raise TypeError(
f"'{class_of_obj.__name__}' object is not subscriptable"
)
在 Python 中,所有类本身都是其他类的实例。一个类的类被称为该类的元类,大多数类的元类都是 type
类。type
没有定义 __getitem__()
,这意味着诸如 list[int]
、dict[str, float]
和 tuple[str, bytes]
等表达式都会导致调用 __class_getitem__()
>>> # list has class "type" as its metaclass, like most classes:
>>> type(list)
<class 'type'>
>>> type(dict) == type(list) == type(tuple) == type(str) == type(bytes)
True
>>> # "list[int]" calls "list.__class_getitem__(int)"
>>> list[int]
list[int]
>>> # list.__class_getitem__ returns a GenericAlias object:
>>> type(list[int])
<class 'types.GenericAlias'>
但是,如果一个类有一个定义了 __getitem__()
的自定义元类,则对该类进行下标操作可能会导致不同的行为。在 enum
模块中可以找到一个这样的例子
>>> from enum import Enum
>>> class Menu(Enum):
... """A breakfast menu"""
... SPAM = 'spam'
... BACON = 'bacon'
...
>>> # Enum classes have a custom metaclass:
>>> type(Menu)
<class 'enum.EnumMeta'>
>>> # EnumMeta defines __getitem__,
>>> # so __class_getitem__ is not called,
>>> # and the result is not a GenericAlias object:
>>> Menu['SPAM']
<Menu.SPAM: 'spam'>
>>> type(Menu['SPAM'])
<enum 'Menu'>
另请参阅
- PEP 560 - 对类型模块和泛型类型的核心支持
介绍了
__class_getitem__()
,并概述了当一个下标操作导致调用__class_getitem__()
而不是__getitem__()
的情况
3.3.6. 模拟可调用对象¶
3.3.7. 模拟容器类型¶
可以定义以下方法来实现容器对象。object
类本身不提供任何这些方法。容器通常是序列(例如 列表
或 元组
)或映射(如字典),但也可以表示其他容器。第一组方法用于模拟序列或映射;区别在于,对于序列,允许的键应该是整数 *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__()
、__rmul__()
和 __imul__()
方法来实现加法(表示连接)和乘法(表示重复),如下所述;它们不应该定义其他数值运算符。建议映射和序列都实现 __contains__()
方法,以允许有效使用 in
运算符;对于映射,in
应该搜索映射的键;对于序列,它应该搜索值。进一步建议映射和序列都实现 __iter__()
方法,以允许有效地迭代容器;对于映射,__iter__()
应该迭代对象的键;对于序列,它应该迭代值。
- object.__len__(self)¶
调用以实现内置函数
len()
。应返回对象的长度,一个大于等于 0 的整数。此外,一个没有定义__bool__()
方法且其__len__()
方法返回零的对象在布尔上下文中被认为是假的。CPython 实现细节: 在 CPython 中,长度必须最多为
sys.maxsize
。如果长度大于sys.maxsize
,某些功能(例如len()
)可能会引发OverflowError
。为了防止真值测试引发OverflowError
,对象必须定义一个__bool__()
方法。
- 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]
的求值。对于序列类型,接受的键应该是整数。可选地,它们也可以支持slice
对象。负索引支持也是可选的。如果 *key* 是不适当的类型,则可能会引发TypeError
;如果 *key* 是序列索引集之外的值(在对负值进行任何特殊解释之后),则应引发IndexError
。对于映射类型,如果 *key* 缺失(不在容器中),则应引发KeyError
。注意
for
循环期望对非法索引引发IndexError
,以便正确检测序列的结尾。注意
当下标访问一个*类*时,可能会调用特殊的类方法
__class_getitem__()
,而不是__getitem__()
。有关更多详细信息,请参见__class_getitem__ 与 __getitem__。
- object.__setitem__(self, key, value)¶
调用此方法以实现对
self[key]
的赋值。与__getitem__()
的注释相同。仅当对象支持更改键的值,或者可以添加新键时,才应为映射实现此方法;如果可以替换元素,则应为序列实现此方法。对于不正确的 *key* 值,应引发与__getitem__()
方法相同的异常。
- object.__delitem__(self, key)¶
调用此方法以实现删除
self[key]
。与__getitem__()
的注释相同。仅当对象支持删除键时,才应为映射实现此方法;如果可以从序列中删除元素,则应为序列实现此方法。对于不正确的 *key* 值,应引发与__getitem__()
方法相同的异常。
- object.__missing__(self, key)¶
当键不在字典中时,
dict
.__getitem__()
调用此方法来实现 dict 子类的self[key]
。
- object.__reversed__(self)¶
当
reversed()
内置函数(如果存在)调用此方法来实现反向迭代。它应返回一个新的迭代器对象,该对象以相反的顺序迭代容器中的所有对象。如果未提供
__reversed__()
方法,则reversed()
内置函数将回退到使用序列协议(__len__()
和__getitem__()
)。支持序列协议的对象仅当可以提供比reversed()
提供的实现更高效的实现时,才应提供__reversed__()
。
成员资格测试运算符(in
和 not in
)通常通过迭代容器来实现。但是,容器对象可以提供以下特殊方法来使用更高效的实现,而无需对象可迭代。
- object.__contains__(self, item)¶
调用以实现成员测试运算符。如果item在self中,则应返回 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)¶
这些方法被调用以实现二进制算术运算(
+
、-
、*
、@
、/
、//
、%
、divmod()
、pow()
、**
、<<
、>>
、&
、^
、|
)。例如,要计算表达式x + y
,其中 x 是具有__add__()
方法的类的实例,则会调用type(x).__add__(x, 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)¶
这些方法被调用以实现反射(交换)操作数的二进制算术运算 (
+
,-
,*
,@
,/
,//
,%
,divmod()
,pow()
,**
,<<
,>>
,&
,^
,|
)。 只有在左操作数不支持相应的操作[3] 且操作数的类型不同时才会调用这些函数。[4] 例如,为了计算表达式x - y
,其中 *y* 是一个具有__rsub__()
方法的类的实例,如果type(x).__sub__(x, y)
返回NotImplemented
,则调用type(y).__rsub__(y, x)
。请注意,三元
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)¶
这些方法被调用以实现增强的算术赋值(
+=
,-=
,*=
,@=
,/=
,//=
,%=
,**=
,<<=
,>>=
,&=
,^=
,|=
)。 这些方法应尝试就地执行操作(修改 *self*)并返回结果(可以是,但不必是 *self*)。 如果未定义特定方法,或者该方法返回NotImplemented
,则增强赋值将回退到正常方法。 例如,如果 *x* 是具有__iadd__()
方法的类的实例,则x += y
等效于x = x.__iadd__(y)
。 如果__iadd__()
不存在,或者x.__iadd__(y)
返回NotImplemented
,则会像评估x + y
一样考虑x.__add__(y)
和y.__radd__(x)
。 在某些情况下,增强赋值可能会导致意外错误(请参阅 为什么当加法有效时,a_tuple[i] += [‘item’] 会引发异常?),但此行为实际上是数据模型的一部分。
- object.__neg__(self)¶
- object.__pos__(self)¶
- object.__abs__(self)¶
- object.__invert__(self)¶
调用此方法以实现一元算术运算(
-
,+
,abs()
和~
)。
- object.__index__(self)¶
调用此方法以实现
operator.index()
,并且在 Python 需要将数字对象无损转换为整数对象时(例如在切片中,或在内置的bin()
、hex()
和oct()
函数中)。此方法的存在表示数字对象是整数类型。必须返回一个整数。如果未定义
__int__()
,__float__()
和__complex__()
,则相应的内置函数int()
,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.11 版本中更改: 将
int()
委托给__trunc__()
已被弃用。
3.3.9. with 语句上下文管理器¶
上下文管理器 是一个对象,它定义了执行 with
语句时要建立的运行时上下文。 上下文管理器处理代码块执行的所需运行时上下文的进入和退出。上下文管理器通常使用 with
语句(在 with 语句 部分中描述)调用,但也可以通过直接调用它们的方法来使用。
上下文管理器的典型用途包括保存和恢复各种全局状态、锁定和解锁资源、关闭打开的文件等。
有关上下文管理器的更多信息,请参见 上下文管理器类型。 object
类本身不提供上下文管理器方法。
- object.__exit__(self, exc_type, exc_value, traceback)¶
退出与此对象相关的运行时上下文。 这些参数描述了导致上下文退出的异常。 如果上下文在没有异常的情况下退出,则所有三个参数都将为
None
。如果提供了异常,并且该方法希望抑制该异常(即,阻止其传播),则应返回一个真值。 否则,该异常将在退出此方法时正常处理。
请注意,
__exit__()
方法不应重新引发传入的异常;这是调用者的责任。
3.3.10. 自定义类模式匹配中的位置参数¶
在模式中使用类名称时,默认情况下不允许模式中的位置参数,即 case MyClass(x, y)
通常在 MyClass
中没有特殊支持的情况下无效。为了能够使用这种模式,类需要定义一个 __match_args__ 属性。
- object.__match_args__¶
此类变量可以分配一个字符串元组。 当此类在带有位置参数的类模式中使用时,每个位置参数都将转换为关键字参数,并使用 __match_args__ 中的相应值作为关键字。 缺少此属性等效于将其设置为
()
。
例如,如果 MyClass.__match_args__
是 ("left", "center", "right")
,则意味着 case MyClass(x, y)
等价于 case MyClass(left=x, center=y)
。请注意,模式中的参数数量必须小于或等于 __match_args__ 中的元素数量;如果大于,则模式匹配尝试将引发 TypeError
。
在 3.10 版本中添加。
另请参阅
- PEP 634 - 结构化模式匹配
Python
match
语句的规范。
3.3.11. 模拟缓冲区类型¶
缓冲区协议 为 Python 对象提供了一种暴露对底层内存数组的高效访问的方法。此协议由诸如 bytes
和 memoryview
之类的内置类型实现,并且第三方库可以定义其他缓冲区类型。
虽然缓冲区类型通常用 C 实现,但也可以在 Python 中实现该协议。
- object.__buffer__(self, flags)¶
当从 self 请求缓冲区时调用(例如,由
memoryview
构造函数调用)。 flags 参数是一个整数,表示请求的缓冲区的类型,例如,影响返回的缓冲区是只读还是可写的。inspect.BufferFlags
提供了一种解释标志的便捷方法。该方法必须返回一个memoryview
对象。
- object.__release_buffer__(self, buffer)¶
当不再需要缓冲区时调用。 buffer 参数是一个
memoryview
对象,该对象先前由__buffer__()
返回。 该方法必须释放与缓冲区关联的任何资源。此方法应返回None
。不需要执行任何清理的缓冲区对象不需要实现此方法。
3.12 版本新增。
另请参阅
- PEP 688 - 使缓冲区协议在 Python 中可访问
引入了 Python
__buffer__
和__release_buffer__
方法。collections.abc.Buffer
缓冲区类型的 ABC。
3.3.12. 特殊方法查找¶
对于自定义类,只有在对象的类型上定义而不是在对象的实例字典中定义时,才能保证特殊方法的隐式调用正常工作。 这就是以下代码引发异常的原因
>>> 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. 可等待对象¶
一个 可等待 对象通常实现一个 __await__()
方法。 从 async def
函数返回的协程对象是可等待的。
注意
从使用 types.coroutine()
修饰的生成器返回的 生成器迭代器 对象也是可等待的,但它们不实现 __await__()
。
- object.__await__(self)¶
必须返回一个 迭代器。 应使用它来实现 可等待 对象。 例如,
asyncio.Future
实现此方法以与await
表达式兼容。object
类本身不是可等待的,并且不提供此方法。
在 3.5 版本中添加。
另请参阅
有关可等待对象的更多信息,请参阅 PEP 492。
3.4.2. 协程对象¶
协程对象是 可等待 对象。可以通过调用 __await__()
并迭代结果来控制协程的执行。 当协程执行完成并返回时,迭代器会引发 StopIteration
异常,并且该异常的 value
属性保存返回值。 如果协程引发异常,则该异常将由迭代器传播。 协程不应直接引发未处理的 StopIteration
异常。
协程也具有以下列出的方法,这些方法类似于生成器的方法(请参阅 生成器-迭代器方法)。但是,与生成器不同,协程不直接支持迭代。
在 3.5.2 版本中更改: 多次等待一个协程会引发 RuntimeError
错误。
- coroutine.send(value)¶
启动或恢复协程的执行。如果 value 为
None
,则等效于推进由__await__()
返回的迭代器。如果 value 不为None
,则此方法会委托给导致协程挂起的迭代器的send()
方法。结果(返回值、StopIteration
或其他异常)与迭代__await__()
返回值时相同,如上所述。
- coroutine.throw(value)¶
- coroutine.throw(type[, value[, traceback]])
在协程中引发指定的异常。如果迭代器具有此方法,则此方法会委托给导致协程挂起的迭代器的
throw()
方法。否则,异常会在暂停点引发。结果(返回值、StopIteration
或其他异常)与迭代__await__()
返回值时相同,如上所述。如果协程中未捕获该异常,它将传播回调用方。在 3.12 版本中更改: 第二个签名 (type[, value[, traceback]]) 已弃用,可能会在 Python 的未来版本中删除。
- coroutine.close()¶
导致协程清理自身并退出。如果协程被挂起,此方法首先会委托给导致协程挂起的迭代器的
close()
方法(如果它有此方法)。然后,它会在暂停点引发GeneratorExit
,导致协程立即清理自身。最后,协程会被标记为已完成执行,即使它从未启动过。协程对象在即将被销毁时会自动使用上述过程关闭。
3.4.3. 异步迭代器¶
异步迭代器可以在其 __anext__
方法中调用异步代码。
异步迭代器可以在 async for
语句中使用。
object
类本身不提供这些方法。
- object.__aiter__(self)¶
必须返回一个异步迭代器对象。
- object.__anext__(self)¶
必须返回一个可等待对象,该对象会生成迭代器的下一个值。当迭代结束时,应引发
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__()
可以返回一个可等待对象,该对象会解析为 异步迭代器。
从 Python 3.7 开始,__aiter__()
必须返回一个异步迭代器对象。返回任何其他内容都会导致 TypeError
错误。
3.4.4. 异步上下文管理器¶
异步上下文管理器是一个上下文管理器,它能够在 __aenter__
和 __aexit__
方法中暂停执行。
异步上下文管理器可以在 async with
语句中使用。
object
类本身不提供这些方法。
- object.__aenter__(self)¶
在语义上类似于
__enter__()
,唯一的区别是它必须返回一个可等待对象。
- object.__aexit__(self, exc_type, exc_value, traceback)¶
在语义上类似于
__exit__()
,唯一的区别是它必须返回一个可等待对象。
异步上下文管理器类的示例
class AsyncContextManager:
async def __aenter__(self):
await log('entering context')
async def __aexit__(self, exc_type, exc, tb):
await log('exiting context')
在 3.5 版本中添加。
脚注