3. 数据模型¶
3.1. 对象、值和类型¶
对象是 Python 对数据的抽象。Python 程序中的所有数据都由对象或对象之间的关系表示。(从某种意义上说,并符合冯·诺依曼的“存储程序计算机”模型,代码也由对象表示。)
每个对象都有一个标识、一个类型和一个值。对象的 标识 一旦创建就不会改变;你可以将其视为对象在内存中的地址。is
运算符比较两个对象的标识;id()
函数返回一个表示其标识的整数。
CPython 实现细节: 对于 CPython,id(x)
是存储 x
的内存地址。
对象的类型决定了该对象支持的操作(例如,“它有长度吗?”),并定义了该类型对象可能的值。type()
函数返回对象的类型(它本身也是一个对象)。与其标识一样,对象的 类型 也是不可变的。[1]
某些对象的 值 可以改变。值可以改变的对象称为 可变 的;一旦创建其值就不可改变的对象称为 不可变 的。(包含对可变对象引用的不可变容器对象,当后者值改变时,其值也可能改变;但是容器仍被认为是不可变的,因为其包含的对象集合不能改变。因此,不可变性不严格等同于值不可变,它更为微妙。)对象的 mutability 由其类型决定;例如,数字、字符串和元组是不可变的,而字典和列表是可变的。
对象从不被显式销毁;但是,当它们变得不可访问时,它们可能会被垃圾回收。实现允许推迟垃圾回收或完全省略它——垃圾回收的实现方式是实现质量问题,只要没有收集到仍然可访问的对象即可。
CPython 实现细节: CPython 目前使用引用计数方案,并(可选地)延迟检测循环链接的垃圾,这会在大多数对象变得不可访问时立即收集它们,但不能保证收集包含循环引用的垃圾。有关控制循环垃圾收集的信息,请参阅 gc
模块的文档。其他实现方式不同,CPython 也可能改变。不要依赖于对象变得不可访问时立即终结(因此应始终显式关闭文件)。
请注意,使用实现的跟踪或调试工具可能会使通常可回收的对象保持活动状态。另请注意,使用 try
…except
语句捕获异常可能会使对象保持活动状态。
一些对象包含对“外部”资源的引用,例如打开的文件或窗口。这些资源在对象被垃圾回收时会释放,但由于不保证会发生垃圾回收,因此这些对象还提供了一种显式释放外部资源的方式,通常是 close()
方法。强烈建议程序显式关闭此类对象。try
…finally
语句和 with
语句提供了方便的方式来完成此操作。
一些对象包含对其他对象的引用;这些对象被称为 容器。容器的例子有元组、列表和字典。引用是容器值的一部分。在大多数情况下,当我们谈论容器的值时,我们指的是包含对象的值,而不是它们的标识;然而,当我们谈论容器的可变性时,仅涉及直接包含对象的标识。因此,如果一个不可变容器(如元组)包含对可变对象的引用,则当该可变对象改变时,其值也会改变。
类型影响对象行为的几乎所有方面。甚至对象标识的重要性在某种程度上也受到影响:对于不可变类型,计算新值的操作实际上可能会返回对具有相同类型和值的任何现有对象的引用,而对于可变对象则不允许这样做。例如,在 a = 1; b = 1
之后,a 和 b 可能引用也可能不引用值为一的同一对象,这取决于实现。这是因为 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
已被弃用。
3.14 版中已更改: 在布尔上下文中求值 NotImplemented
现在会引发 TypeError
。之前它求值为 True
并在 Python 3.9 后发出 DeprecationWarning
。
3.2.3. Ellipsis¶
这种类型只有一个值。只有一个对象具有此值。此对象通过字面量 ...
或内置名称 Ellipsis
访问。其真值为 True。
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()
将范围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
),则集合中只能包含其中一个。
目前有两种固有的集合类型
- 集合
- 冻结集合
这些表示一个不可变集合。它们由内置的
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.14 版本加入。 |
|
|
|
3.12 新版功能. |
函数对象还支持获取和设置任意属性,例如,可用于将元数据附加到函数。常规属性点符号用于获取和设置此类属性。
CPython 实现细节: CPython 当前的实现只支持用户定义函数上的函数属性。将来可能会支持 内置函数 上的函数属性。
3.2.8.2. 实例方法¶
一个实例方法对象结合了一个类、一个类实例和任何可调用对象(通常是用户定义函数)。
特殊只读属性
|
引用方法 绑定 到的类实例对象 |
|
引用原始的 函数对象 |
|
方法的文档(与 |
|
方法的名称(与 |
|
定义方法的模块的名称,如果不可用则为 |
方法还支持访问(但不设置)底层 函数对象 上的任意函数属性。
如果类的一个属性(可能通过该类的一个实例)是用户定义的 函数对象 或 classmethod
对象,则在获取该类的属性时可能会创建用户定义的方法对象。
当通过其某个实例从类中检索用户定义的 函数对象 来创建实例方法对象时,其 __self__
属性是该实例,并且该方法对象被称为 绑定 的。新方法的 __func__
属性是原始函数对象。
当通过从类或实例中检索 classmethod
对象来创建实例方法对象时,存储在 __self__
中的“类实例”实际上将是类本身,因此调用 x.f(1)
或 C.f(1)
都等价于调用 f(C,1)
,其中 f
是类方法底层的函数。
当实例方法对象被调用时,底层函数 (__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__
的最佳实践,请参阅annotationlib
。
- module.__annotate__¶
此模块的 注解函数,如果模块没有注解则为
None
。另请参阅:__annotate__
属性。在 3.14 版本加入。
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.14 版本加入。 |
|
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
中的位来指示代码对象是否以特定特性启用编译。请参阅 compiler_flag
。
co_flags
中的其他位保留供内部使用。
如果代码对象表示一个函数并具有文档字符串,则 CO_HAS_DOCSTRING
位在 co_flags
中设置,并且 co_consts
中的第一个项是函数的文档字符串。
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)
元组
生成的项将具有以下属性
生成的第一个范围的
start
为 0。(start, end)
范围将是非递减和连续的。也就是说,对于任何一对元组
,第二个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.14 版本加入。 |
3.2.13.2.2. 特殊的、可写的属性¶
|
如果不是 |
|
将此属性设置为 |
|
将此属性设置为 |
|
帧的当前行号——从跟踪函数中写入此行号会跳转到给定行(仅适用于最底层的帧)。调试器可以通过写入此属性来实现 Jump 命令(又称 Set Next Statement)。 |
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__()
方法将像__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
的引用计数减一,后者仅在x
的引用计数达到零时才调用。CPython 实现细节: 引用循环可能会阻止对象的引用计数降至零。在这种情况下,循环将稍后由循环垃圾收集器检测并删除。引用循环的常见原因是异常被捕获到局部变量中。然后帧的局部变量引用异常,异常引用其自身的追溯,追溯引用追溯中捕获的所有帧的局部变量。
参见
有关
gc
模块的文档。警告
由于调用
__del__()
方法的危险情况,其执行期间发生的异常将被忽略,而是向sys.stderr
打印警告。特别是
- object.__repr__(self)¶
由内置函数
repr()
调用,用于计算对象的“正式”字符串表示形式。如果可能,这应该看起来像一个有效的 Python 表达式,可用于(在适当的环境中)重新创建具有相同值的对象。如果不可能,则应返回<...some useful description...>
形式的字符串。返回值必须是字符串对象。如果一个类定义了__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
。一个定义了自己明确引发TypeError
的__hash__()
的类,在isinstance(obj, collections.abc.Hashable)
调用中会被错误地识别为可哈希。备注
默认情况下,str 和 bytes 对象的
__hash__()
值会用一个不可预测的随机值进行“加盐”。尽管它们在单个 Python 进程中保持不变,但在重复调用 Python 之间是不可预测的。这旨在提供针对因精心选择的输入而导致的拒绝服务攻击的保护,这些输入利用了字典插入的最坏情况性能,即 *O*(*n*2) 复杂度。有关详细信息,请参阅http://ocert.org/advisories/ocert-2011-003.html。
哈希值变化会影响集合的迭代顺序。Python 从未对此顺序做出保证(并且它通常在32位和64位构建之间有所不同)。
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)
。对于某些敏感属性访问,会引发审计事件
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()
搜索。
- module.__class__¶
为了更细粒度地自定义模块行为(设置属性、特性等),可以将模块对象的__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 - Module __getattr__ and __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 绑定
像
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__。
如果使用
dictionary
来分配 __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 - 对 typing 模块和泛型类型的核心支持
引入
__class_getitem__()
,并概述了何时 下标 会导致调用__class_getitem__()
而不是__getitem__()
。
3.3.6. 模拟可调用对象¶
3.3.7. 模拟容器类型¶
可以定义以下方法来实现容器对象。它们都不是由 object
类本身提供的。容器通常是 序列(例如 lists
或 tuples
)或 映射(例如 dictionaries),但也可能表示其他容器。第一组方法用于模拟序列或模拟映射;区别在于,对于序列,允许的键应该是整数 k,其中 0 <= k < N
,其中 N 是序列的长度,或者是 slice
对象,它定义了一个项的范围。还建议映射提供 keys()
、values()
、items()
、get()
、clear()
、setdefault()
、pop()
、popitem()
、copy()
和 update()
方法,其行为类似于 Python 标准 dictionary
对象的方法。collections.abc
模块提供了一个 MutableMapping
抽象基类,以帮助从 __getitem__()
、__setitem__()
、__delitem__()
和 keys()
的基本集合创建这些方法。
可变序列应提供方法 append()
、clear()
、count()
、extend()
、index()
、insert()
、pop()
、remove()
和 reverse()
,类似于 Python 标准 list
对象。最后,序列类型应通过定义下述方法 __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 进行下标时,可能会调用特殊的类方法
__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)
是type(x)
的子类,则会调用type(y).__rsub__(y, x)
。[5]请注意,如果内置
pow()
函数的三参数版本要得到支持,则__rpow__()
应该定义为接受可选的第三个参数。3.14 版本中的改动: 三参数的
pow()
现在会在必要时尝试调用__rpow__()
。之前它仅在双参数pow()
和二进制幂运算符中调用。备注
如果右操作数的类型是左操作数类型的子类,并且该子类为该操作提供了不同的反射方法实现,则此方法将在左操作数的非反射方法之前调用。此行为允许子类覆盖其祖先的操作。
- 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__()
。
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 参数是一个之前由
__buffer__()
返回的memoryview
对象。此方法必须释放与缓冲区相关的任何资源。此方法应返回None
。不需要执行任何清理的缓冲区对象无需实现此方法。
3.12 新版功能.
参见
- PEP 688 - 使缓冲区协议在 Python 中可访问
引入了 Python
__buffer__
和__release_buffer__
方法。collections.abc.Buffer
缓冲区类型的抽象基类。
3.3.12. 注解¶
函数、类和模块可能包含 注解,这是一种将信息(通常是 类型提示)与符号关联起来的方法。
- object.__annotations__¶
此属性包含对象的注解。它是 惰性求值 的,因此访问此属性可能会执行任意代码并引发异常。如果求值成功,则此属性将设置为一个字典,其中包含从变量名到注解的映射。
3.14 版本中的改动: 注解现在是惰性求值的。
- object.__annotate__(format)¶
一个 注解函数。返回一个新字典对象,将属性/参数名映射到其注解值。
接受一个 format 参数,指定注解值的提供格式。它必须是
annotationlib.Format
枚举的成员,或者是一个与枚举成员对应的值的整数。如果注解函数不支持请求的格式,它必须引发
NotImplementedError
。注解函数必须始终支持VALUE
格式;当使用此格式调用时,它们不得引发NotImplementedError()
。当使用
VALUE
格式调用时,注解函数可能会引发NameError
;当请求任何其他格式时,它不得引发NameError
。如果对象没有任何注解,
__annotate__
应该优先设置为None
(它不能被删除),而不是设置为返回空字典的函数。在 3.14 版本加入。
参见
- PEP 649 — 使用描述符延迟求值注解
引入了注解的惰性求值和
__annotate__
函数。
3.3.13. 特殊方法查找¶
对于自定义类,特殊方法的隐式调用只有在对象类型而非对象实例字典中定义时才能保证正确工作。这种行为就是以下代码引发异常的原因:
>>> 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 版本加入。
脚注