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
可能引用或不引用同一个值为一的对象,这取决于实现,但在c = []; d = []
之后,c
和d
保证引用两个不同的、唯一的、新创建的空列表。(注意c = d = []
将同一个对象分配给c
和d
。)
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__()
计算得出,具有以下属性
它们是有效的数值字面量,当传递给它们的类构造函数时,会生成一个具有原始数值值的实例。
表示形式为十进制,如果可能。
不显示前导零,可能除小数点前的一个零外。
不显示尾随零,可能除小数点后一个零外。
仅当数字为负数时才显示符号。
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()
将范围内的整数转换为相应的长度为1
的字符串对象0 - 10FFFF
。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()
构造函数创建。由于冻结集合是不可变的并且 可散列,因此它可以再次用作另一个集合的元素或字典键。
3.2.7. 映射¶
这些表示由任意索引集索引的有限对象集。下标符号 a[k]
从映射 a
中选择由 k
索引的项目;这可以在表达式中使用,也可以用作赋值或 del
语句的目标。内置函数 len()
返回映射中的项目数量。
目前只有一个内在的映射类型
3.2.7.1. 字典¶
这些表示由几乎任意值索引的有限对象集。唯一不能作为键的值类型是包含列表或字典或其他可变类型的值,这些类型通过值而不是通过对象标识进行比较,原因是字典的有效实现需要键的散列值保持不变。用作键的数字类型遵循数字比较的正常规则:如果两个数字比较相等(例如,1
和 1.0
),则它们可以互换使用来索引相同的字典条目。
字典保留插入顺序,这意味着键将按照它们在字典中依次添加的顺序生成。替换现有键不会改变顺序,但是删除一个键并重新插入它会将其添加到末尾,而不是保留其旧位置。
字典是可变的;它们可以通过 {...}
符号创建(参见部分 字典显示)。
扩展模块 dbm.ndbm
和 dbm.gnu
提供了映射类型的其他示例,collections
模块也是如此。
在 3.7 版本中变更: 在 3.6 版本之前的 Python 版本中,字典不保留插入顺序。在 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 所表示的对象。(该属性具有与 other instance methods
相同的语义。)
3.2.8.8. 类¶
类是可调用的。这些对象通常充当自身新实例的工厂,但对于覆盖 __new__()
的类类型来说,可能存在变体。调用的参数传递给 __new__()
,并且在典型情况下,传递给 __init__()
以初始化新实例。
3.2.8.9. 类实例¶
通过在它们的类中定义 __call__()
方法,可以使任意类的实例可调用。
3.2.9. 模块¶
模块是 Python 代码的基本组织单元,由 导入系统 创建,该系统由 import
语句调用,或者通过调用函数(如 importlib.import_module()
和内置的 __import__()
)调用。模块对象有一个由 dictionary
对象实现的命名空间(这是由模块中定义的函数的 __globals__
属性引用的字典)。属性引用被转换为在这个字典中的查找,例如,m.x
等价于 m.__dict__["x"]
。模块对象不包含用于初始化模块的代码对象(因为初始化完成后不再需要它)。
属性赋值更新模块的命名空间字典,例如,m.x = 1
等价于 m.__dict__["x"] = 1
。
预定义的(可写的)属性
特殊只读属性:__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.11. 类实例¶
类实例是通过调用类对象创建的(见上文)。类实例有一个用字典实现的命名空间,它是属性引用搜索的第一个位置。当在该命名空间中找不到属性时,如果实例的类具有该名称的属性,则搜索将继续进行类属性。如果找到一个类属性,该属性是用户定义的函数对象,则将其转换为实例方法对象,其 __self__
属性是实例。静态方法和类方法对象也会被转换;见上文“类”部分。有关通过实例检索类的属性与实际存储在类的 __dict__
中的对象之间的另一种差异,请参见 实现描述符 部分。如果找不到类属性,并且对象的类具有 __getattr__()
方法,则调用该方法以满足查找。
属性赋值和删除更新实例的字典,而不是类的字典。如果类具有 __setattr__()
或 __delattr__()
方法,则调用该方法,而不是直接更新实例字典。
如果类实例具有某些特殊名称的方法,则它们可以假装是数字、序列或映射。请参见 特殊方法名称 部分。
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.14 中删除。 |
|
代码对象所需的堆栈大小 |
|
一个 |
以下标志位定义了 co_flags
: 位 0x04
如果函数使用 *arguments
语法接受任意数量的位置参数,则设置该位;位 0x08
如果函数使用 **keywords
语法接受任意关键字参数,则设置该位;位 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 字节偏移量。此位置信息可能丢失。以下是一些可能发生这种情况的非详尽列表
当这种情况发生时,元组元素中的一些或全部可以是
None
。在 3.11 版本中添加。
注意
此功能需要在代码对象中存储列位置,这可能会导致编译的 Python 文件或解释器内存使用量的略微增加。为了避免存储额外的信息和/或停用打印额外的回溯信息,可以使用
-X
no_debug_ranges
命令行标志或PYTHONNODEBUGRANGES
环境变量。
- codeobject.co_lines()¶
返回一个迭代器,该迭代器生成有关连续范围的 字节码 的信息。生成的每个项目都是一个
(start, end, lineno)
tuple
生成的项目将具有以下属性
生成的第一个范围将具有
start
为 0。(start, end)
范围将是非递减且连续的。也就是说,对于任何一对tuple
,第二个的start
将等于第一个的end
。没有范围是向后的:
end >= start
对于所有三元组。
零宽度范围,其中
start == end
,是允许的。零宽度范围用于源代码中存在的但已被 字节码 编译器消除的行。在 3.10 版本中添加。
另请参阅
- PEP 626 - 用于调试和其他工具的精确行号。
介绍了
co_lines()
方法的 PEP。
- codeobject.replace(**kwargs)¶
返回具有指定字段新值的代码对象的副本。
在 3.8 版本中添加。
3.2.13.2. 帧对象¶
帧对象表示执行帧。它们可能出现在 跟踪对象 中,并且也会传递给已注册的跟踪函数。
3.2.13.2.1. 特殊只读属性¶
|
指向前一个堆栈帧(朝向调用者),如果这是最底层的堆栈帧,则为 |
|
此帧中正在执行的 代码对象。访问此属性会引发 审计事件 |
|
帧用来查找 局部变量 的字典 |
|
帧用来查找 全局变量 的字典 |
|
帧用来查找 内置(固有)名称 的字典 |
|
3.2.13.2.2. 特殊可写属性¶
|
如果非 |
|
将此属性设置为 |
|
将此属性设置为 |
|
帧的当前行号 - 从跟踪函数中写入此内容会跳转到给定行(仅适用于最底层的帧)。调试器可以通过写入此属性来实现 Jump 命令(也称为 Set Next Statement)。 |
3.2.13.2.3. 帧对象方法¶
帧对象支持一种方法
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 的实例)。典型的实现通过使用
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__()
方法会被调用。weakref.finalize
提供了一种直接的方法来注册一个清理函数,该函数在对象被垃圾回收时被调用。注意
del x
不会直接调用x.__del__()
— 前者将x
的引用计数减 1,而后者只有在x
的引用计数达到 0 时才会被调用。CPython 实现细节: 对象的引用计数可能由于引用循环而无法降至 0。在这种情况下,循环将稍后被 循环垃圾收集器 检测并删除。引用循环的常见原因是当异常被局部变量捕获时。然后,框架的局部变量引用异常,异常引用其自身的回溯,回溯引用回溯中捕获的所有框架的局部变量。
另请参阅
有关
gc
模块的文档。警告
由于
__del__()
方法被调用的情况下非常微妙,因此在执行期间发生的异常会被忽略,并且会向sys.stderr
打印一个警告。特别是
- object.__repr__(self)¶
由
repr()
内置函数调用,用于计算对象的“官方”字符串表示。如果可能,这应该看起来像一个有效的 Python 表达式,可以用来重新创建具有相同值的(在适当的环境下)对象。如果不可能,则应返回<...some useful description...>
形式的字符串。返回值必须是字符串对象。如果一个类定义了__repr__()
但没有定义__str__()
,那么当需要该类的实例的“非正式”字符串表示时,也会使用__repr__()
。这通常用于调试,因此表示信息丰富且明确非常重要。
- object.__str__(self)¶
由
str(object)
和内置函数format()
和print()
调用,以计算对象的“非正式”或易于打印的字符串表示形式。返回值必须是 字符串 对象。此方法不同于
object.__repr__()
,因为没有期望__str__()
返回有效的 Python 表达式:可以使用更方便或更简洁的表示形式。内置类型
object
定义的默认实现调用object.__repr__()
。
- 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(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()
。有关创建支持自定义比较操作并可用作字典键的 可哈希 对象的一些重要说明,请参见有关
__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__()
方法;使用它们,所有对象比较结果都不相等(除了与自身比较),并且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 的重复调用之间是不可预测的。这样做是为了防止由精心选择的输入导致的拒绝服务攻击,这些输入利用了 dict 插入的最坏情况性能,即 *O*(*n*2) 复杂度。有关详细信息,请参见 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__()
由于 name 不是实例属性或self
类树中的属性而引发AttributeError
;要么 name 属性的__get__()
引发AttributeError
)。此方法应返回(计算的)属性值或引发AttributeError
异常。请注意,如果通过正常机制找到属性,则不会调用
__getattr__()
。(这是__getattr__()
和__setattr__()
之间有意不对称。)这样做既是为了效率原因,也是因为否则__getattr__()
将无法访问实例的其他属性。请注意,至少对于实例变量,可以通过不在实例属性字典中插入任何值(而是将它们插入另一个对象)来模拟完全控制。有关实际控制属性访问的方法,请参见下面的__getattribute__()
方法。
- object.__getattribute__(self, name)¶
无条件调用以实现类实例的属性访问。如果类还定义了
__getattr__()
,除非__getattribute__()
显式调用它或引发AttributeError
,否则不会调用后者。此方法应返回(计算的)属性值或引发AttributeError
异常。为了避免此方法中的无限递归,其实现应始终调用具有相同名称的基类方法来访问它需要的任何属性,例如object.__getattribute__(self, name)
。对于某些敏感的属性访问,会引发 审计事件
object.__getattr__
,其参数为obj
和name
。
- object.__setattr__(self, name, value)¶
当尝试进行属性赋值时调用。这将取代正常的机制(即在实例字典中存储值)。name 是属性名称,value 是要分配给它的值。
如果
__setattr__()
想要分配给实例属性,它应该调用具有相同名称的基类方法,例如,object.__setattr__(self, name, value)
。对于某些敏感的属性分配,会引发一个 审计事件
object.__setattr__
,其参数为obj
、name
和value
。
- object.__delattr__(self, name)¶
类似于
__setattr__()
,但用于属性删除而不是赋值。只有当del obj.name
对对象有意义时才应实现此方法。对于某些敏感的属性删除,会引发一个 审计事件
object.__delattr__
,其参数为obj
和name
。
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.__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__ 的类不支持
weak references
到其实例。如果需要弱引用支持,则将'__weakref__'
添加到 __slots__ 声明中的字符串序列中。__slots__ 在类级别通过为每个变量名创建 descriptors 来实现。因此,类属性不能用于为 __slots__ 定义的实例变量设置默认值;否则,类属性将覆盖描述符分配。
__slots__ 声明的作用不仅限于定义它的类。在父类中声明的 __slots__ 在子类中可用。但是,子类将获得一个
__dict__
和 __weakref__,除非它们也定义 __slots__(它应该只包含任何附加插槽的名称)。如果一个类定义了一个在基类中也定义的插槽,那么基类插槽定义的实例变量将不可访问(除非直接从基类检索其描述符)。这使得程序的含义变得不确定。将来,可能会添加一个检查来防止这种情况。
TypeError
如果为从"variable-length" built-in type
(如int
、bytes
和tuple
)派生的类定义了非空的 __slots__,则会引发。任何非字符串的 可迭代对象 都可以分配给 __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
对类型模块和泛型类型的核心支持。
3.3.3.3. 确定合适的元类¶
类定义的合适元类按以下方式确定:
最派生的元类是从显式指定的元类(如果有)和所有指定基类的元类(即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__
或 super
,则 __class__
是编译器创建的隐式闭包引用。这允许 super()
的零参数形式根据词法作用域正确识别正在定义的类,而用于进行当前调用的类或实例则根据传递给方法的第一个参数识别。
CPython 实现细节:在 CPython 3.6 及更高版本中,__class__
单元格作为类命名空间中的 __classcell__
条目传递给元类。如果存在,则必须将其传播到 type.__new__
调用中,以便类能够正确初始化。如果未能这样做,将在 Python 3.8 中导致 RuntimeError
。
当使用默认元类 type
或最终调用 type.__new__
的任何元类时,在创建类对象后会调用以下额外的自定义步骤
The
type.__new__
方法收集类命名空间中所有定义了__set_name__()
方法的属性;这些
__set_name__
方法被调用,传入正在定义的类以及该特定属性的分配名称;The
__init_subclass__()
钩子在新的类的直接父类上调用,该父类位于其方法解析顺序中。
在创建类对象后,它将传递给类定义中包含的类装饰器(如果有),并且生成的类对象将绑定到本地命名空间中,作为定义的类。
当通过 type.__new__
创建一个新类时,作为命名空间参数提供的对象将被复制到一个新的有序映射中,并且原始对象将被丢弃。新的副本被包装在一个只读代理中,该代理成为类对象的 __dict__
属性。
另请参阅
- PEP 3135 - 新的 super
描述了隐式
__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. 模拟泛型类型¶
当使用类型注解时,通常使用 Python 的方括号表示法对泛型类型进行参数化非常有用。例如,注解 list[int]
可能用于表示所有元素都是类型 int
的list
。
另请参阅
- 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 - 对 typing 模块和泛型类型的核心支持
介绍
__class_getitem__()
,并概述何时 订阅 会导致调用__class_getitem__()
而不是__getitem__()
3.3.6. 模拟可调用对象¶
- object.__call__(self[, args...])¶
当实例作为函数“调用”时调用;如果定义了此方法,则
x(arg1, arg2, ...)
大致等效于type(x).__call__(x, arg1, ...)
。
3.3.7. 模拟容器类型¶
The following methods can be defined to implement container objects. Containers
usually are sequences (such as lists
or
tuples
) or mappings (like
dictionaries
),
but can represent other containers as well. The first set of methods is used
either to emulate a sequence or to emulate a mapping; the difference is that for
a sequence, the allowable keys should be the integers k for which 0 <= k <
N
where N is the length of the sequence, or slice
objects, which define a
range of items. It is also recommended that mappings provide the methods
keys()
, values()
, items()
, get()
, clear()
,
setdefault()
, pop()
, popitem()
, copy()
, and
update()
behaving similar to those for Python’s standard dictionary
objects. The collections.abc
module provides a
MutableMapping
abstract base class to help create those methods from a base set of
__getitem__()
, __setitem__()
,
__delitem__()
, and keys()
.
Mutable sequences should provide methods append()
, count()
,
index()
, extend()
, insert()
, pop()
, remove()
,
reverse()
and sort()
, like Python standard list
objects. Finally,
sequence types should implement addition (meaning concatenation) and
multiplication (meaning repetition) by defining the methods
__add__()
, __radd__()
, __iadd__()
,
__mul__()
, __rmul__()
and __imul__()
described below; they should not define other numerical
operators. It is recommended that both mappings and sequences implement the
__contains__()
method to allow efficient use of the in
operator; for
mappings, in
should search the mapping’s keys; for sequences, it should
search through the values. It is further recommended that both mappings and
sequences implement the __iter__()
method to allow efficient iteration
through the container; for mappings, __iter__()
should iterate
through the object’s keys; for sequences, it should iterate through the values.
- 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__()
调用,用于在字典子类中实现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.__add__(y)
和y.__radd__(x)
,就像评估x + y
一样。在某些情况下,增强的赋值可能会导致意外错误(参见为什么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()
如果未定义__int__()
或__index__()
,则会回退到__trunc__()
。版本 3.11 中的变更: 将
int()
的委托给__trunc__()
已被弃用。
3.3.9. With 语句上下文管理器¶
一个 上下文管理器 是一个对象,它定义了在执行 with
语句时要建立的运行时上下文。上下文管理器处理进入和退出代码块执行所需的运行时上下文。上下文管理器通常使用 with
语句(在第 With 语句 节中描述)调用,但也可以通过直接调用其方法来使用。
上下文管理器的典型用途包括保存和恢复各种全局状态、锁定和解锁资源、关闭打开的文件等。
有关上下文管理器的更多信息,请参阅 上下文管理器类型。
- 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
表达式兼容。
在版本 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.__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.__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 中添加。
脚注