3. 数据模型 — Python 文档
3. 数据模型
3.1. 对象、值和类型
Objects 是 Python 对数据的抽象。 Python 程序中的所有数据都由对象或对象之间的关系表示。 (从某种意义上说,为了符合冯诺依曼的“存储程序计算机”模型,代码也由对象表示。)
每个对象都有一个身份、一个类型和一个值。 对象的 标识 一旦创建就永远不会改变; 您可以将其视为对象在内存中的地址。 'is' 运算符比较两个对象的身份; id() 函数返回一个表示其身份的整数(当前实现为它的地址)。 对象的 类型 也是不可更改的。 1 对象的类型决定了该对象支持的操作(例如,“它有长度吗?”)并且还定义了该类型对象的可能值。 type() 函数返回一个对象的类型(它是一个对象本身)。 某些对象的 值 可以更改。 值可以改变的对象被称为 mutable; 其值一旦创建就不可更改的对象称为 immutable。 (包含对可变对象的引用的不可变容器对象的值可以在后者的值改变时改变;但是容器仍然被认为是不可变的,因为它包含的对象集合不能改变。 因此,不变性与具有不可改变的值并不严格相同,它更微妙。)一个对象的可变性由它的类型决定; 例如,数字、字符串和元组是不可变的,而字典和列表是可变的。
对象永远不会被显式销毁; 但是,当它们无法访问时,它们可能会被垃圾收集。 允许一个实现推迟垃圾收集或完全忽略它——垃圾收集如何实现是一个实现质量的问题,只要没有收集到仍然可以访问的对象。
请注意,使用实现的跟踪或调试工具可能会使通常可收集的对象保持活动状态。 另请注意,使用 'try...except' 语句捕获异常可能会使对象保持活动状态。
一些对象包含对“外部”资源的引用,例如打开的文件或窗口。 据了解,这些资源在对象被垃圾回收时会被释放,但由于垃圾回收并不能保证一定会发生,因此此类对象也提供了显式的释放外部资源的方式,通常是close()
方法。 强烈建议程序明确关闭此类对象。 'try…finally'语句提供了一种方便的方法来做到这一点。
一些对象包含对其他对象的引用; 这些被称为 容器 。 容器的例子是元组、列表和字典。 引用是容器值的一部分。 在大多数情况下,当我们谈论容器的价值时,我们暗示的是价值,而不是所包含对象的身份; 然而,当我们谈论容器的可变性时,只隐含了直接包含的对象的身份。 因此,如果不可变容器(如元组)包含对可变对象的引用,则如果该可变对象发生更改,则其值也会更改。
类型几乎影响对象行为的所有方面。 甚至对象标识的重要性在某种意义上也会受到影响:对于不可变类型,计算新值的操作实际上可能返回对任何具有相同类型和值的现有对象的引用,而对于可变对象,这是不允许的。 例如,在 a = 1; b = 1
之后,a
和 b
可能会或可能不会引用值为 1 的同一个对象,具体取决于实现,但在 c = []; d = []
之后, c
和 d
保证引用两个不同的、唯一的、新创建的空列表。 (请注意,c = d = []
将相同的对象分配给 c
和 d
。)
3.2. 标准类型层次结构
下面是 Python 内置的类型列表。 扩展模块(用 C、Java 或其他语言编写,取决于实现)可以定义其他类型。 Python 的未来版本可能会向类型层次结构添加类型(例如,有理数、有效存储的整数数组等)。
下面的一些类型描述包含一个列出“特殊属性”的段落。 这些是提供对实现的访问的属性,不用于一般用途。 他们的定义将来可能会改变。
- 没有任何
这种类型只有一个值。 有一个具有此值的对象。 该对象通过内置名称
None
访问。 它在许多情况下用于表示没有值,例如,它是从不显式返回任何内容的函数中返回的。 它的真值是假的。- 未实现
这种类型只有一个值。 有一个具有此值的对象。 该对象通过内置名称
NotImplemented
访问。 如果数值方法和富比较方法没有实现对提供的操作数的操作,它们可能会返回此值。 (然后解释器将尝试反射操作,或其他一些回退,这取决于操作符。)它的真值是真的。- 省略
这种类型只有一个值。 有一个具有此值的对象。 该对象通过内置名称
Ellipsis
访问。 它用于指示切片中是否存在...
语法。 它的真值是真的。numbers.Number
它们由数字文字创建,并由算术运算符和算术内置函数作为结果返回。 数字对象是不可变的; 一旦创造了它们的价值就永远不会改变。 Python 数字当然与数学数字密切相关,但受到计算机中数字表示的限制。
Python 区分整数、浮点数和复数:
numbers.Integral
这些代表来自数学整数集(正数和负数)的元素。
共有三种类型的整数:
- 普通整数
它们表示 -2147483648 到 2147483647 范围内的数字。 (在自然字长较大的机器上,范围可能更大,但不会更小。)当操作的结果超出此范围时,结果通常作为长整数返回(在某些情况下,异常 [X232X ] 改为升高)。 出于移位和掩码操作的目的,假设整数具有使用 32 位或更多位的二进制、2 的补码表示法,并且不向用户隐藏任何位(即,所有 4294967296 种不同的位模式对应于不同的值)。
- 长整数
这些代表无限范围内的数字,仅受可用(虚拟)内存的影响。 出于移位和掩码操作的目的,假设使用二进制表示,并且负数以 2 的补码的变体表示,这会产生向左延伸的无限符号位串的错觉。
- 布尔值
这些代表真值 False 和 True。 表示值
False
和True
的两个对象是唯一的布尔对象。 布尔类型是普通整数的子类型,布尔值的行为分别类似于值 0 和 1,在几乎所有上下文中,例外是当转换为字符串时,字符串"False"
或"True"
分别返回。
整数表示规则旨在对涉及负整数的移位和掩码操作给出最有意义的解释,并在纯整数域和长整数域之间切换时给出最少的意外。 任何操作,如果它在普通整数域中产生结果,将在长整数域中或使用混合操作数时产生相同的结果。 域之间的切换对程序员是透明的。
- numbers.Real (float)
这些代表机器级双精度浮点数。 对于可接受的范围和溢出处理,您受底层机器架构(以及 C 或 Java 实现)的支配。 Python 不支持单精度浮点数; 处理器和内存使用量的节省通常是使用这些的原因,但与在 Python 中使用对象的开销相比,这是相形见绌的,因此没有理由用两种浮点数使语言复杂化。
numbers.Complex
这些将复数表示为一对机器级双精度浮点数。 同样的警告也适用于浮点数。 复数
z
的实部和虚部可以通过只读属性z.real
和z.imag
获取。
- 序列
这些表示由非负数索引的有限有序集。 内置函数 len() 返回序列的项数。 当序列长度为n时,索引集包含数字0、1、……、n-1。 序列a的项目i由
a[i]
选择。序列也支持切片:
a[i:j]
选择所有索引为 k 的项目,使得 i<=
k<
] j。 当用作表达式时,切片是相同类型的序列。 这意味着索引集被重新编号,使其从 0 开始。某些序列还支持带有第三个“步长”参数的“扩展切片”:
a[i:j:k]
选择 a 的所有项目,索引为 x,其中x = i + n*k
, n>=
0
和 i<=
x [X2218X][X2218X][X223X]X 。序列根据其可变性来区分:
- 不可变序列
不可变序列类型的对象一旦创建就不能更改。 (如果对象包含对其他对象的引用,则这些其他对象可能是可变的并且可能会被更改;但是,不可变对象直接引用的对象集合不能更改。)
以下类型是不可变序列:
- 字符串
字符串的项目是字符。 没有单独的字符类型; 一个字符由一个项目的字符串表示。 字符表示(至少)8 位字节。 内置函数 chr() 和 ord() 在字符和表示字节值的非负整数之间进行转换。 值为 0-127 的字节通常表示相应的 ASCII 值,但值的解释取决于程序。 字符串数据类型还用于表示字节数组,例如,保存从文件中读取的数据。
(在本机字符集不是 ASCII 的系统上,字符串可以在其内部表示中使用 EBCDIC,前提是函数 chr() 和 ord() 实现了 ASCII 和 EBCDIC 之间的映射,和字符串比较保留 ASCII 顺序。 或者也许有人可以提出更好的规则?)
- 统一码
Unicode 对象的项是 Unicode 代码单元。 一个 Unicode 代码单元由一个项目的 Unicode 对象表示,可以包含一个 16 位或 32 位的值来表示一个 Unicode 序数(序数的最大值在
sys.maxunicode
中给出,并且取决于Python 在编译时是如何配置的)。 Unicode 对象中可能存在代理对,并将报告为两个单独的项目。 内置函数 unichr() 和 ord() 在代码单元和表示 Unicode 标准 3.0 中定义的 Unicode 序数的非负整数之间进行转换。 可以通过 Unicode 方法encode()
和内置函数 unicode() 与其他编码进行转换。- 元组
元组的项是任意的 Python 对象。 两个或多个项目的元组由逗号分隔的表达式列表构成。 可以通过将逗号附加到表达式(表达式本身不会创建元组,因为括号必须可用于表达式分组)来形成一个项目的元组(“单例”)。 空元组可以由一对空括号组成。
- 可变序列
可变序列在创建后可以更改。 订阅和切片符号可以用作赋值和 del(删除)语句的目标。
目前有两种固有的可变序列类型:
- 列表
列表的项目是任意的 Python 对象。 列表是通过将逗号分隔的表达式列表放在方括号中来形成的。 (请注意,形成长度为 0 或 1 的列表不需要特殊情况。)
- 字节数组
bytearray 对象是一个可变数组。 它们由内置的 bytearray() 构造函数创建。 除了可变(因此不可散列)之外,字节数组还提供与不可变字节对象相同的接口和功能。
扩展模块 array 提供了一个额外的可变序列类型示例。
- 设置类型
这些代表无序、有限的唯一、不可变对象集。 因此,它们不能被任何下标索引。 但是,它们可以迭代,内置函数 len() 返回集合中的项目数。 集合的常见用途是快速成员资格测试、从序列中删除重复项以及计算交集、并集、差和对称差等数学运算。
对于集合元素,同样的不变性规则适用于字典键。 请注意,数字类型遵循数字比较的正常规则:如果两个数字比较相等(例如,
1
和1.0
),则只能将其中一个包含在一个集合中。目前有两种内在集合类型:
- 套
这些代表一个可变集。 它们由内置的 set() 构造函数创建,之后可以通过多种方法进行修改,例如
add()
。- 冷冻套装
这些代表一个不可变的集合。 它们由内置的 frozenset() 构造函数创建。 由于frozenset 是不可变的并且是hashable,因此它可以再次用作另一个集合的元素,或用作字典键。
- 映射
这些表示由任意索引集索引的有限对象集。 下标符号
a[k]
从映射a
中选择由k
索引的项; 这可以在表达式中使用,也可以作为赋值或 del 语句的目标。 内置函数 len() 返回映射中的项目数。目前有一个内在映射类型:
- 可调用类型
这些是可以应用函数调用操作的类型(参见 Calls):
- 用户定义函数
用户定义的函数对象由函数定义创建(请参阅 函数定义 部分)。 应该使用包含与函数的形式参数列表相同数量的项目的参数列表调用它。
特殊属性:
属性
意义
__doc__
func_doc
函数的文档字符串,或
None
(如果不可用)。可写
__name__
func_name
函数名
可写
__module__
定义函数的模块的名称,如果不可用,则为
None
。可写
__defaults__
func_defaults
包含具有默认值的参数的默认参数值的元组,如果没有参数具有默认值,则包含
None
。可写
__code__
func_code
表示已编译函数体的代码对象。
可写
__globals__
func_globals
对保存函数全局变量的字典的引用——定义函数的模块的全局命名空间。
只读
__dict__
func_dict
支持任意函数属性的命名空间。
可写
__closure__
func_closure
None
或包含函数自由变量绑定的元组。只读
大多数标记为“Writable”的属性检查分配值的类型。
2.4 版更改:
func_name
现在可写。2.6 版更改: 双下划线属性
__closure__
、__code__
、__defaults__
和__globals__
作为别名引入相应的func_*
属性以向前兼容 Python 3。函数对象还支持获取和设置任意属性,例如,可用于将元数据附加到函数。 常规属性点符号用于获取和设置此类属性。 请注意,当前实现仅支持用户定义函数的函数属性。 将来可能会支持内置函数的函数属性。
可以从其代码对象中检索有关函数定义的附加信息; 请参阅下面的内部类型说明。
- 用户定义的方法
用户定义的方法对象结合了类、类实例(或
None
)和任何可调用对象(通常是用户定义的函数)。特殊只读属性:
im_self
为类实例对象,im_func
为函数对象;im_class
为绑定方法的im_self
类或未绑定方法请求方法的类;__doc__
是方法的文档(与im_func.__doc__
相同); __name__为方法名(同im_func.__name__
);__module__
是定义方法的模块的名称,如果不可用,则为None
。2.2 版更改:
im_self
用于引用定义方法的类。2.6 版更改: 对于 Python 3 向前兼容性,
im_func
也可用作__func__
,而im_self
可用作__self__
。方法还支持访问(但不设置)底层函数对象上的任意函数属性。
如果该属性是用户定义的函数对象、未绑定的用户定义方法对象或类方法对象,则可以在获取类的属性时(可能通过该类的实例)创建用户定义的方法对象。 当属性是用户定义的方法对象时,仅当从中检索新方法对象的类与存储在原始方法对象中的类相同或派生类时,才会创建新方法对象; 否则,将按原样使用原始方法对象。
当通过从类中检索用户定义的函数对象来创建用户定义的方法对象时,其
im_self
属性为None
并且该方法对象被称为未绑定。 当通过其实例之一从类中检索用户定义的函数对象来创建一个时,它的im_self
属性是实例,并且方法对象被称为已绑定。 无论哪种情况,新方法的im_class
属性都是从中进行检索的类,而其im_func
属性是原始函数对象。当通过从类或实例中检索另一个方法对象来创建用户定义的方法对象时,行为与函数对象相同,只是新实例的
im_func
属性不是原始方法对象,但它的im_func
属性。当通过从类或实例中检索类方法对象来创建用户定义的方法对象时,其
im_self
属性是类本身,其im_func
属性是该类底层的函数对象方法。当调用未绑定的用户定义方法对象时,将调用底层函数 (
im_func
),但限制是第一个参数必须是正确类 (im_class
) 或其派生类。当调用绑定的用户定义方法对象时,会调用底层函数(
im_func
),在参数列表前面插入类实例(im_self
)。 例如,当C
是一个包含函数f()
定义的类,而x
是C
的实例时,调用 [ X149X]相当于调用C.f(x, 1)
。当用户定义的方法对象派生自类方法对象时,存储在
im_self
中的“类实例”实际上就是类本身,因此调用x.f(1)
或 [ X189X] 相当于调用f(C,1)
,其中f
是底层函数。请注意,每次从类或实例中检索属性时,都会发生从函数对象到(未绑定或绑定)方法对象的转换。 在某些情况下,一种富有成效的优化是将属性分配给局部变量并调用该局部变量。 另请注意,此转换仅适用于用户定义的函数; 其他可调用对象(以及所有不可调用对象)无需转换即可检索。 同样重要的是要注意,作为类实例属性的用户定义函数不会转换为绑定方法; 这个 only 当函数是类的属性时发生。
- 发电机功能
使用 yield 语句(参见 yield 语句 部分)的函数或方法称为 生成器函数 。 这样的函数在调用时总是返回一个迭代器对象,该对象可用于执行函数体:调用迭代器的 next() 方法将导致函数执行,直到它使用yield 语句。 当函数执行 return 语句或掉到末尾时,会引发
StopIteration
异常并且迭代器将到达要返回的值集的末尾。- 内置功能
内置函数对象是 C 函数的包装器。 内置函数的例子有 len() 和 math.sin()(math 是一个标准的内置模块)。 参数的数量和类型由 C 函数决定。 特殊的只读属性:
__doc__
是函数的文档字符串,或者None
如果不可用; __name__ 是函数名;__self__
设置为None
(但见下一项);__module__
是定义函数的模块的名称,如果不可用,则为None
。- 内置方法
这确实是内置函数的另一种伪装,这次包含作为隐式额外参数传递给 C 函数的对象。 内置方法的一个例子是
alist.append()
,假设 alist 是一个列表对象。 在这种情况下,特殊的只读属性__self__
被设置为由 alist 表示的对象。- 班级类型
类类型或“新式类”是可调用的。 这些对象通常充当它们自己的新实例的工厂,但对于覆盖
__new__()
的类类型,可能会发生变化。 调用的参数传递给__new__()
,在典型情况下,传递给__init__()
以初始化新实例。- 经典课程
类对象描述如下。 当一个类对象被调用时,一个新的类实例(也在下面描述)被创建并返回。 这意味着调用类的
__init__()
方法(如果有的话)。 任何参数都传递给__init__()
方法。 如果没有__init__()
方法,则必须不带参数调用该类。- 类实例
类实例描述如下。 只有当类具有
__call__()
方法时,类实例才可调用;x(arguments)
是x.__call__(arguments)
的简写。
- 模块
模块由 import 语句导入(请参阅 导入语句 部分)。 模块对象具有由字典对象实现的命名空间(这是由模块中定义的函数的 func_globals 属性引用的字典)。 属性引用被翻译成在这个字典中查找,例如,
m.x
等价于m.__dict__["x"]
。 模块对象不包含用于初始化模块的代码对象(因为一旦初始化完成就不需要它了)。属性赋值更新模块的命名空间字典,例如,
m.x = 1
等价于m.__dict__["x"] = 1
。特殊只读属性:__dict__ 是模块的命名空间作为字典对象。
预定义(可写)属性:
__name__
是模块的名称;__doc__
是模块的文档字符串,或者None
如果不可用;__file__
是加载模块的文件的路径名,如果它是从文件加载的。__file__
属性对于静态链接到解释器的 C 模块不存在; 对于从共享库动态加载的扩展模块,它是共享库文件的路径名。- 班级
类类型(新式类)和类对象(旧式/经典类)通常由类定义创建(参见 类定义 部分)。 一个类有一个由字典对象实现的命名空间。 类属性引用被翻译成本字典中的查找,例如,
C.x
被翻译成C.__dict__["x"]
(尽管对于新式类,特别是有许多钩子允许其他方式定位属性)。 如果在那里找不到属性名称,则在基类中继续进行属性搜索。 对于旧式类,搜索是深度优先的,按照在基类列表中出现的顺序从左到右进行。 新式类使用更复杂的 C3 方法解析顺序,即使存在“菱形”继承结构,其中存在多个返回共同祖先的继承路径,该顺序也能正确运行。 有关新型类使用的 C3 MRO 的其他详细信息,请参见 https://www.python.org/download/releases/2.3/mro/ 2.3 版本随附的文档。当类属性引用(例如,对于类
C
)将产生用户定义的函数对象或未绑定的用户定义的方法对象时,其关联类是C
或其基类之一,它被转化为一个未绑定的用户定义方法对象,其im_class
属性为C
。 当它产生一个类方法对象时,它被转换为一个绑定的用户定义方法对象,其im_self
属性为C
。 当它产生一个静态方法对象时,它被转换成由静态方法对象包装的对象。 请参阅 实现描述符 部分,了解从类中检索的属性可能与其 __dict__ 中实际包含的属性不同的另一种方式(请注意,只有新型类支持描述符)。类属性赋值更新类的字典,而不是基类的字典。
可以调用类对象(见上文)以产生类实例(见下文)。
特殊属性:__name__为类名;
__module__
是定义类的模块名称; __dict__ 是包含类的命名空间的字典; __bases__ 是一个包含基类的元组(可能为空或单例),按照它们在基类列表中出现的顺序;__doc__
是类的文档字符串,或者None
如果未定义。- 类实例
类实例是通过调用类对象创建的(见上文)。 类实例具有作为字典实现的命名空间,这是搜索属性引用的第一个位置。 如果在那里找不到属性,并且实例的类具有该名称的属性,则搜索将继续使用类属性。 如果发现类属性是用户定义的函数对象或未绑定的用户定义的方法对象,其关联的类是为其启动属性引用的实例的类(称为
C
)或一个在它的基础中,它被转换成一个绑定的用户定义方法对象,其im_class
属性为C
,其im_self
属性为实例。 静态方法和类方法对象也进行了转换,就好像它们是从类C
中检索到的一样; 见上文“类”部分。 请参阅 实现描述符 部分,了解通过其实例检索的类的属性可能与实际存储在该类的 __dict__ 中的对象不同的另一种方式。 如果未找到类属性,并且对象的类具有__getattr__()
方法,则调用该方法以满足查找。属性分配和删除更新实例的字典,而不是类的字典。 如果类具有
__setattr__()
或__delattr__()
方法,则调用该方法而不是直接更新实例字典。如果类实例具有具有某些特殊名称的方法,则它们可以伪装成数字、序列或映射。 请参阅部分 特殊方法名称 。
- 文件
一个文件对象代表一个打开的文件。 文件对象由 open() 内置函数创建,也由 os.popen()、os.fdopen() 和 [ X130X] 套接字对象的方法(也可能是由扩展模块提供的其他函数或方法)。 对象
sys.stdin
、sys.stdout
和sys.stderr
被初始化为对应于解释器的标准输入、输出和错误流的文件对象。 有关文件对象的完整文档,请参阅 文件对象 。- 内部类型
解释器内部使用的一些类型暴露给用户。 它们的定义可能会随着解释器的未来版本而改变,但为了完整起见,这里提到它们。
- 代码对象
代码对象表示 字节编译的 可执行 Python 代码,或 字节码 。 代码对象和函数对象的区别在于,函数对象包含对函数全局变量(定义它的模块)的显式引用,而代码对象不包含上下文; 默认参数值也存储在函数对象中,而不是代码对象中(因为它们代表在运行时计算的值)。 与函数对象不同,代码对象是不可变的,并且不包含对可变对象的引用(直接或间接)。
特殊只读属性:
co_name
给出函数名;co_argcount
是位置参数的个数(包括带默认值的参数);co_nlocals
为函数使用的局部变量个数(包括参数);co_varnames
是一个包含局部变量名称的元组(从参数名称开始);co_cellvars
是一个包含嵌套函数引用的局部变量名称的元组;co_freevars
是一个包含自由变量名称的元组;co_code
是表示字节码指令序列的字符串;co_consts
是包含字节码使用的文字的元组;co_names
是包含字节码使用的名称的元组;co_filename
是编译代码的文件名;co_firstlineno
是函数的第一行号;co_lnotab
是对字节码偏移量到行号的映射进行编码的字符串(详见解释器源码);co_stacksize
是需要的栈大小(包括局部变量);co_flags
是一个整数,为解释器编码了许多标志。为
co_flags
定义了以下标志位: 如果函数使用*arguments
语法接受任意数量的位置参数,则设置位0x04
; 如果函数使用**keywords
语法接受任意关键字参数,则设置位0x08
; 如果函数是发生器,则位0x20
被设置。未来的功能声明 (
from __future__ import division
) 还使用co_flags
中的位来指示代码对象是否是在启用特定功能的情况下编译的:如果函数是使用编译的,则位0x2000
被设置未来的分裂启用; 位0x10
和0x1000
用于早期版本的 Python。co_flags
中的其他位保留供内部使用。如果代码对象表示函数,则
co_consts
中的第一项是函数的文档字符串,如果未定义,则为None
。
- 框架对象
帧对象代表执行帧。 它们可能出现在回溯对象中(见下文)。
特殊的只读属性:
f_back
指向前一个堆栈帧(朝向调用者),或者None
如果这是底部堆栈帧;f_code
是本帧正在执行的代码对象;f_locals
是用于查找局部变量的字典;f_globals
用于全局变量;f_builtins
用于内置(固有)名称;f_restricted
为函数是否在受限执行模式下执行的标志;f_lasti
给出了精确的指令(这是代码对象字节码字符串的索引)。特殊的可写属性:
f_trace
,如果不是None
,则是在每个源代码行的开头调用的函数(这是调试器使用的);f_exc_type
、f_exc_value
、f_exc_traceback
表示父框架中最后引发的异常,前提是当前框架中曾经引发过另一个异常(在所有其他情况下它们是 [ X334X]);f_lineno
是帧的当前行号——从跟踪函数中写入它会跳转到给定的行(仅适用于最底部的帧)。 调试器可以通过写入 f_lineno 来实现跳转命令(又名 Set Next Statement)。- 回溯对象
Traceback 对象表示异常的堆栈跟踪。 发生异常时会创建一个回溯对象。 当对异常处理程序的搜索展开执行堆栈时,在每个展开级别,都会在当前回溯之前插入一个回溯对象。 当进入异常处理程序时,堆栈跟踪可供程序使用。 (参见 try 语句 部分。)它可以作为
sys.exc_traceback
访问,也可以作为sys.exc_info()
返回的元组的第三项访问。 后者是首选接口,因为当程序使用多线程时它可以正常工作。 当程序不包含合适的处理程序时,堆栈跟踪被写入(格式良好)到标准错误流; 如果解释器是交互式的,它也可以作为sys.last_traceback
提供给用户。特殊的只读属性:
tb_next
是堆栈跟踪中的下一级(朝向发生异常的帧),如果没有下一级,则为None
;tb_frame
指向当前关卡的执行帧;tb_lineno
给出异常发生的行号;tb_lasti
表示精确指令。 如果异常发生在没有匹配的 except 子句或 finally 子句的 try 语句中,则回溯中的行号和最后一条指令可能与其框架对象的行号不同。- 切片对象
当使用 扩展切片语法 时,切片对象用于表示切片。 这是一个使用两个冒号的切片,或者用逗号分隔的多个切片或省略号,例如
a[i:j:step]
、a[i:j, k:l]
或a[..., i:j]
。 它们也是由内置的 slice() 函数创建的。特殊只读属性:
start
为下界;stop
为上限;step
为步长值; 如果省略,每个都是None
。 这些属性可以是任何类型。切片对象支持一种方法:
- slice.indices(self, length)
此方法采用单个整数参数 length 并计算有关切片对象在应用于 length 项序列时将描述的扩展切片的信息。 它返回一个由三个整数组成的元组; 这些分别是 start 和 stop 索引和 step 或切片的步幅长度。 缺失或越界索引的处理方式与常规切片一致。
2.3 版中的新功能。
- 静态方法对象
静态方法对象提供了一种阻止上述函数对象到方法对象的转换的方法。 静态方法对象是任何其他对象的包装器,通常是用户定义的方法对象。 当从类或类实例中检索静态方法对象时,实际返回的对象是包装后的对象,不进行任何进一步的转换。 静态方法对象本身不可调用,尽管它们通常包装的对象是可调用的。 静态方法对象由内置的 staticmethod() 构造函数创建。
- 类方法对象
类方法对象与静态方法对象一样,是另一个对象的包装器,它改变了从类和类实例中检索该对象的方式。 类方法对象在此类检索时的行为在上面的“用户定义的方法”中进行了描述。 类方法对象由内置的 classmethod() 构造函数创建。
3.3. 新式经典班
类和实例有两种风格:旧式(或经典)和新式。
在 Python 2.1 之前,class
的概念与 type
的概念无关,旧式类是唯一可用的风格。 对于旧式类,语句 x.__class__
提供 x 的类,但 type(x)
始终是 <type 'instance'>
。 这反映了一个事实,即所有旧式实例,独立于它们的类,都是用一个称为 instance
的内置类型实现的。
Python 2.2 中引入了新式类以统一 class
和 type
的概念。 新式类只是用户定义的类型,不多也不少。 如果 x 是新式类的实例,则 type(x)
通常与 x.__class__
相同(尽管这不能保证——新式类实例是允许覆盖为 x.__class__
返回的值)。
引入新式类的主要动机是提供具有完整元模型的统一对象模型。 它还具有许多实际好处,例如能够对大多数内置类型进行子类化,或引入“描述符”,从而启用计算属性。
出于兼容性原因,默认情况下类仍然是旧式的。 新式类是通过指定另一个新式类(即 一个类型)作为父类,或者如果不需要其他父类,则为“顶级类型”object。 除了 type() 返回的内容之外,新样式类的行为在许多重要细节上与旧样式类的行为不同。 其中一些更改是新对象模型的基础,例如调用特殊方法的方式。 其他是出于兼容性问题之前无法实现的“修复”,例如多重继承情况下的方法解析顺序。
虽然本手册旨在提供对 Python 类机制的全面覆盖,但在涉及新式类的覆盖方面可能仍存在某些方面的不足。 请参阅 https://www.python.org/doc/newstyle/ 以获取更多信息来源。
Python 3 中删除了旧式类,只留下新式类。
3.4. 特殊方法名称
类可以通过定义具有特殊名称的方法来实现由特殊语法(例如算术运算或下标和切片)调用的某些操作。 这是 Python 实现 运算符重载 的方法,允许类根据语言运算符定义自己的行为。 例如,如果一个类定义了一个名为 __getitem__()
的方法,而 x
是这个类的一个实例,那么 x[i]
大致相当于旧的 x.__getitem__(i)
-style 类和 type(x).__getitem__(x, i)
用于新样式类。 除非另有说明,否则在未定义适当方法(通常为 AttributeError
或 TypeError
)时,尝试执行操作会引发异常。
当实现一个模拟任何内置类型的类时,重要的是模拟只实现到它对被建模的对象有意义的程度。 例如,某些序列可能适用于检索单个元素,但提取切片可能没有意义。 (W3C 的文档对象模型中的 NodeList
接口就是一个例子。)
3.4.1. 基本定制
- object.__new__(cls[, ...])
调用以创建类 cls 的新实例。 __new__() 是一个静态方法(特殊情况,所以你不需要这样声明它),它将请求实例的类作为它的第一个参数。 其余参数是传递给对象构造函数表达式(对类的调用)的参数。 __new__() 的返回值应该是新的对象实例(通常是 cls 的实例)。
典型的实现通过使用具有适当参数的
super(currentclass, cls).__new__(cls[, ...])
调用超类的 __new__() 方法来创建类的新实例,然后在返回之前根据需要修改新创建的实例。如果 __new__() 返回 cls 的实例,则新实例的 __init__() 方法将像
__init__(self[, ...])
一样被调用,其中 ]self 是新实例,其余参数与传递给 __new__() 的参数相同。如果 __new__() 没有返回 cls 的实例,则不会调用新实例的 __init__() 方法。
__new__() 主要用于允许不可变类型(如 int、str 或 tuple)的子类自定义实例创建。 它也通常在自定义元类中被覆盖,以自定义类创建。
- object.__init__(self[, ...])
在创建实例之后(通过 __new__())调用,但在它返回给调用者之前调用。 参数是传递给类构造函数表达式的参数。 如果基类具有 __init__() 方法,则派生类的 __init__() 方法(如果有)必须显式调用它以确保正确初始化实例的基类部分; 例如:
BaseClass.__init__(self, [args...])
。因为 __new__() 和 __init__() 在构造对象时协同工作(__new__() 来创建它,而 __init__() 到自定义),__init__() 不能返回非
None
值; 这样做会导致TypeError
在运行时被引发。
- object.__del__(self)
在实例即将被销毁时调用。 这也称为析构函数。 如果基类具有 __del__() 方法,则派生类的 __del__() 方法(如果有)必须显式调用它以确保正确删除实例的基类部分. 请注意,__del__() 方法可以(尽管不推荐!)通过创建对实例的新引用来推迟实例的销毁。 然后可以在稍后删除此新引用时调用它。 不保证 __del__() 方法在解释器退出时仍然存在的对象被调用。
笔记
del x
不直接调用x.__del__()
— 前者将x
的引用计数减一,后者仅在x
的引用时调用计数达到零。 一些可能阻止对象的引用计数为零的常见情况包括: 对象之间的循环引用(例如,双向链表或具有父子指针的树数据结构); 对捕获异常的函数的堆栈帧上的对象的引用(存储在sys.exc_traceback
中的回溯使堆栈帧保持活动状态); 或对堆栈帧上在交互模式下引发未处理异常的对象的引用(存储在sys.last_traceback
中的回溯使堆栈帧保持活动状态)。 第一种情况只能通过明确打破循环来补救; 后两种情况可以通过将None
存储在sys.exc_traceback
或sys.last_traceback
中来解决。 在启用选项周期检测器时检测到垃圾的圆形引用(默认情况下),但只有没有Python级[x171x] __del __()[x184x]方法,只能清除。 有关循环检测器如何处理 __del__() 方法的更多信息,请参阅 gc 模块的文档,尤其是garbage
值的描述。警告
由于调用 __del__() 方法的不稳定情况,在执行过程中发生的异常将被忽略,而是向
sys.stderr
打印警告。 此外,当 __del__() 被调用以响应模块被删除时(例如,当程序执行完成时),__del__() 方法引用的其他全局变量可能已经已被删除或正在被拆除(例如 进口机器关闭)。 出于这个原因, __del__() 方法应该做维持外部不变量所需的绝对最小值。 从版本 1.5 开始,Python 保证名称以下划线开头的全局变量在删除其他全局变量之前从其模块中删除; 如果不存在对此类全局变量的其他引用,这可能有助于确保在调用 __del__() 方法时导入的模块仍然可用。另请参阅 -R 命令行选项。
- object.__repr__(self)
由 repr() 内置函数和字符串转换(反引号)调用以计算对象的“官方”字符串表示。 如果可能的话,这应该看起来像一个有效的 Python 表达式,可用于重新创建具有相同值的对象(给定适当的环境)。 如果这是不可能的,则应返回
<...some useful description...>
形式的字符串。 返回值必须是字符串对象。 如果一个类定义了 __repr__() 而不是 __str__(),那么 __repr__() 也用于该类实例的“非正式”字符串表示是必须的。这通常用于调试,因此表示信息丰富且明确是很重要的。
- object.__str__(self)
- 由 str() 内置函数和 print 语句调用以计算对象的“非正式”字符串表示。 这与 __repr__() 的不同之处在于它不必是有效的 Python 表达式:可以使用更方便或更简洁的表示代替。 返回值必须是字符串对象。
- object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other) 2.1 版中的新功能。
这些是所谓的“丰富的比较”方法,并且优先于下面的 __cmp__() 调用比较运算符。 操作符和方法名的对应关系如下:
x<y
调用x.__lt__(y)
,x<=y
调用x.__le__(y)
,x==y
调用x.__eq__(y)
、x!=y
和x<>y
调用x.__ne__(y)
、x>y
调用x.__gt__(y)
和x>=y
调用。如果富比较方法没有实现给定参数对的操作,则它可能返回单例
NotImplemented
。 按照惯例,返回False
和True
以进行成功比较。 但是,这些方法可以返回任何值,因此如果在布尔上下文中使用比较运算符(例如,在if
语句的条件下),Python 将调用 bool() on用于确定结果是真还是假的值。比较运算符之间没有隐含的关系。
x==y
的真相并不意味着x!=y
是假的。 因此,在定义 __eq__() 时,还应该定义 __ne__() 以便运算符按预期运行。 有关创建支持自定义比较操作并可用作字典键的 hashable 对象的一些重要说明,请参阅 __hash__() 上的段落。这些方法没有交换参数版本(当左参数不支持操作但右参数支持时使用); 相反,__lt__() 和 __gt__() 是彼此的反映,__le__() 和 __ge__() 是彼此的反映,而 __eq__() 和 __ne__() 是他们自己的反映。
丰富的比较方法的论据永远不会被强制。
要从单个根操作自动生成排序操作,请参阅 functools.total_ordering()。
- object.__cmp__(self, other)
- 如果未定义丰富的比较(见上文),则由比较操作调用。 如果
self < other
应该返回一个负整数,如果self == other
返回零,如果self > other
返回一个正整数。 如果没有定义 __cmp__()、__eq__() 或 __ne__() 操作,类实例通过对象标识(“地址”)进行比较。 有关创建支持自定义比较操作并可用作字典键的 hashable 对象的一些重要说明,另请参阅 __hash__() 的描述。 (注意:__cmp__() 不传播异常的限制自 Python 1.5 起已被删除。)
- object.__rcmp__(self, other)
2.1 版变更:不再支持。
- object.__hash__(self)
由内置函数 hash() 调用,用于对散列集合成员的操作,包括 set、frozenset 和 dict。 __hash__() 应该返回一个整数。 唯一需要的属性是比较相等的对象具有相同的哈希值; 建议将对象组件的散列值混合在一起,这些组件也在对象比较中起作用,方法是将它们打包成一个元组并对元组进行散列。 例子:
def __hash__(self): return hash((self.name, self.nick, self.color))
如果一个类没有定义 __cmp__() 或 __eq__() 方法,它也不应该定义 __hash__() 操作; 如果它定义了 __cmp__() 或 __eq__() 而不是 __hash__(),它的实例将不能用于散列集合。 如果一个类定义了可变对象并实现了 __cmp__() 或 __eq__() 方法,它不应该实现 __hash__(),因为可散列集合的实现要求对象的散列值是不可变的(如果对象的散列值发生变化,它将位于错误的散列桶中)。
用户定义的类默认有 __cmp__() 和 __hash__() 方法; 与它们相比,所有对象都比较不相等(除了它们自己)并且
x.__hash__()
返回从id(x)
派生的结果。从父类继承 __hash__() 方法但更改 __cmp__() 或 __eq__() 的含义的类,使得返回的哈希值为 no更合适的(例如 通过切换到基于值的相等概念而不是基于默认身份的相等概念)可以通过在类定义中设置
__hash__ = None
来显式地将自己标记为不可散列。 这样做意味着当程序尝试检索它们的哈希值时,类的实例不仅会引发适当的TypeError
,而且在检查isinstance(obj, collections.Hashable)
时,它们也会被正确识别为不可哈希的(与类不同)它们定义了自己的 __hash__() 以显式提高TypeError
)。2.5 版更改: __hash__() 现在也可以返回一个长整型对象; 然后从该对象的散列中导出 32 位整数。
- object.__nonzero__(self)
- 调用实现真值测试和内置操作
bool()
; 应返回False
或True
,或它们的等效整数0
或1
。 如果未定义此方法,则调用 __len__()(如果已定义),并且如果其结果非零,则该对象被视为真。 如果一个类既没有定义 __len__() 也没有定义 __nonzero__(),那么它的所有实例都被认为是真的。
- object.__unicode__(self)
- 调用以实现内置的 unicode(); 应该返回一个 Unicode 对象。 未定义此方法时,会尝试进行字符串转换,并使用系统默认编码将字符串转换的结果转换为 Unicode。
3.4.2. 自定义属性访问
可以定义以下方法来自定义类实例的属性访问(x.name
的使用、分配或删除)的含义。
- object.__getattr__(self, name)
当属性查找在通常的地方没有找到属性时调用(即 它不是实例属性,也不是在
self
的类树中找到)。name
是属性名称。 此方法应返回(计算的)属性值或引发AttributeError
异常。请注意,如果通过正常机制找到该属性,则不会调用 __getattr__()。 (这是 __getattr__() 和 __setattr__() 之间有意的不对称。)这样做既是出于效率原因,也是因为否则 __getattr__() 将没有访问实例其他属性的方法。 请注意,至少对于实例变量,您可以通过不在实例属性字典中插入任何值(而是将它们插入到另一个对象中)来伪造完全控制。 请参阅下面的 __getattribute__() 方法,了解在新式类中实际获得完全控制的方法。
- object.__setattr__(self, name, value)
尝试进行属性分配时调用。 这被称为而不是正常机制(即 将值存储在实例字典中)。 name 是属性名称,value 是分配给它的值。
如果 __setattr__() 想要分配给一个实例属性,它不应该简单地执行
self.name = value
- 这会导致对自身的递归调用。 相反,它应该在实例属性字典中插入值,例如,self.__dict__[name] = value
。 对于新式类,不访问实例字典,而是调用同名的基类方法,例如object.__setattr__(self, name, value)
。
- object.__delattr__(self, name)
- 像 __setattr__() 但用于属性删除而不是赋值。 仅当
del obj.name
对对象有意义时才应实现。
3.4.2.1. 新式类的更多属性访问
以下方法仅适用于新式类。
- object.__getattribute__(self, name)
无条件调用以实现对类实例的属性访问。 如果该类还定义了 __getattr__(),则不会调用后者,除非 __getattribute__() 显式调用它或引发
AttributeError
。 此方法应返回(计算的)属性值或引发AttributeError
异常。 为了避免这个方法的无限递归,它的实现应该总是调用同名的基类方法来访问它需要的任何属性,例如,object.__getattribute__(self, name)
。笔记
作为通过语言语法或内置函数进行隐式调用的结果,在查找特殊方法时仍可能绕过此方法。 请参阅 新式类的特殊方法查找 。
3.4.2.2. 实现描述符
以下方法仅适用于包含该方法的类的实例(所谓的 descriptor 类)出现在 owner 类中(描述符必须在所有者的类字典中)或在其父母之一的类字典中)。 在下面的示例中,“属性”是指名称为所有者类'__dict__ 中的属性键的属性。
- object.__get__(self, instance, owner)
- 调用以获取所有者类的属性(类属性访问)或该类的实例的属性(实例属性访问)。 owner 始终是所有者类,而 instance 是属性被访问的实例,或者
None
当属性通过 owner[ X188X]。 此方法应返回(计算的)属性值或引发AttributeError
异常。
- object.__set__(self, instance, value)
- 调用以将所有者类的实例 实例 上的属性设置为新值 值 。
- object.__delete__(self, instance)
- 调用以删除所有者类的实例 实例 上的属性。
3.4.2.3. 调用描述符
通常,描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖:__get__()
、__set__()
和 __delete__()
. 如果为对象定义了这些方法中的任何一个,则称其为描述符。
属性访问的默认行为是从对象的字典中获取、设置或删除属性。 例如,a.x
有一个从 a.__dict__['x']
开始的查找链,然后是 type(a).__dict__['x']
,并继续通过 type(a)
的基类,不包括元类。
但是,如果查找值是定义描述符方法之一的对象,则 Python 可能会覆盖默认行为并改为调用描述符方法。 这在优先链中发生的位置取决于定义了哪些描述符方法以及如何调用它们。 请注意,描述符仅为新样式对象或类(子类 object() 或 type())调用。
描述符调用的起点是一个绑定,a.x
。 参数的组合方式取决于 a
:
- 直接呼叫
- 最简单和最不常见的调用是当用户代码直接调用描述符方法时:
x.__get__(a)
。 - 实例绑定
- 如果绑定到新样式的对象实例,则
a.x
转换为调用:type(a).__dict__['x'].__get__(a, type(a))
。 - 类绑定
- 如果绑定到新式类,则
A.x
转换为调用:A.__dict__['x'].__get__(None, A)
。 - 超级绑定
- 如果
a
是 super 的实例,则绑定super(B, obj).m()
在obj.__class__.__mro__
中搜索紧接在B
然后通过以下调用调用描述符:A.__dict__['m'].__get__(obj, obj.__class__)
。
对于实例绑定,描述符调用的优先级取决于定义了哪些描述符方法。 描述符可以定义 __get__()
、__set__()
和 __delete__()
的任意组合。 如果它没有定义 __get__()
,那么访问该属性将返回描述符对象本身,除非该对象的实例字典中有一个值。 如果描述符定义了__set__()
和/或__delete__()
,则为数据描述符; 如果两者都没有定义,则它是一个非数据描述符。 通常,数据描述符定义了 __get__()
和 __set__()
,而非数据描述符只有 __get__()
方法。 定义了 __set__()
和 __get__()
的数据描述符总是覆盖实例字典中的重新定义。 相反,非数据描述符可以被实例覆盖。
Python 方法(包括 staticmethod() 和 classmethod())被实现为非数据描述符。 因此,实例可以重新定义和覆盖方法。 这允许单个实例获得与同一类的其他实例不同的行为。
property() 函数作为数据描述符实现。 因此,实例不能覆盖属性的行为。
3.4.2.4. __插槽__
默认情况下,旧式和新式类的实例都有一个用于属性存储的字典。 这浪费了实例变量很少的对象的空间。 创建大量实例时,空间消耗会变得很严重。
可以通过在新式类定义中定义 __slots__ 来覆盖默认值。 __slots__ 声明采用一系列实例变量,并在每个实例中保留足够的空间来保存每个变量的值。 因为没有为每个实例创建 __dict__,所以节省了空间。
- __slots__
可以为此类变量分配一个字符串、可迭代对象或具有实例使用的变量名称的字符串序列。 如果在新式类中定义,__slots__ 为声明的变量保留空间并防止为每个实例自动创建 __dict__ 和 __weakref__。
2.2 版中的新功能。
__slots__使用注意事项
从没有 __slots__ 的类继承时,该类的 __dict__ 属性将始终可访问,因此子类中的 __slots__ 定义是没有意义的。
如果没有 __dict__ 变量,则无法为实例分配未在 __slots__ 定义中列出的新变量。 尝试分配给未列出的变量名称会引发
AttributeError
。 如果需要动态分配新变量,则将'__dict__'
添加到 __slots__ 声明中的字符串序列。2.3 版更改: 以前,将
'__dict__'
添加到 __slots__ 声明将无法分配未在实例变量名称序列中明确列出的新属性。如果每个实例没有 __weakref__ 变量,定义 __slots__ 的类不支持对其实例的弱引用。 如果需要弱引用支持,则将
'__weakref__'
添加到 __slots__ 声明中的字符串序列。2.3 版更改: 以前,将
'__weakref__'
添加到 __slots__ 声明将不会启用对弱引用的支持。__slots__ 通过为每个变量名称创建描述符(Implementing Descriptors)在类级别实现。 因此,类属性不能用于为 __slots__ 定义的实例变量设置默认值; 否则,类属性将覆盖描述符分配。
__slots__ 声明的作用仅限于定义它的类。 因此,子类将有一个 __dict__,除非它们还定义了 __slots__(它必须只包含任何 附加 槽的名称)。
如果一个类定义了一个也在基类中定义的槽,则基类槽定义的实例变量是不可访问的(除非直接从基类检索其描述符)。 这使得程序的含义未定义。 将来,可能会添加检查以防止这种情况发生。
任何非字符串可迭代对象都可以分配给 __slots__。 也可以使用映射; 但是,将来可能会为每个键对应的值赋予特殊含义。
__class__ 赋值仅在两个类具有相同的 __slots__ 时才有效。
2.6 版更改: 以前,如果新类或旧类具有 __slots__,则 __class__ 赋值会引发错误。
3.4.3. 自定义类创建
默认情况下,使用 type() 构造新式类。 类定义被读入一个单独的命名空间,类名的值绑定到 type(name, bases, dict)
的结果。
读取类定义时,如果定义了 __metaclass__,则将调用分配给它的可调用对象而不是 type()。 这允许编写监视或更改类创建过程的类或函数:
- 在创建类之前修改类字典。
- 返回另一个类的实例——本质上是执行工厂函数的角色。
这些步骤必须在元类的 __new__()
方法中执行 - 然后可以从此方法调用 type.__new__()
以创建具有不同属性的类。 此示例在创建类之前向类字典添加一个新元素:
class metacls(type):
def __new__(mcs, name, bases, dict):
dict['foo'] = 'metacls was here'
return type.__new__(mcs, name, bases, dict)
您当然也可以覆盖其他类方法(或添加新方法); 例如,在元类中定义自定义 __call__()
方法允许在调用类时自定义行为,例如 并不总是创建一个新实例。
- __metaclass__
此变量可以是
name
、bases
和dict
的任何可调用的接受参数。 创建类时,将使用可调用对象而不是内置的 type()。2.2 版中的新功能。
适当的元类由以下优先规则确定:
- 如果存在
dict['__metaclass__']
,则使用它。 - 否则,如果至少有一个基类,则使用其元类(这首先查找 __class__ 属性,如果未找到,则使用其类型)。
- 否则,如果存在名为 __metaclass__ 的全局变量,则使用它。
- 否则,将使用旧式经典元类 (types.ClassType)。
元类的潜在用途是无限的。 已经探索的一些想法包括日志记录、接口检查、自动委托、自动属性创建、代理、框架和自动资源锁定/同步。
3.4.4. 自定义实例和子类检查
2.6 版中的新功能。
以下方法用于覆盖 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.4.5. 模拟可调用对象
- object.__call__(self[, args...])
- 当实例作为函数被“调用”时调用; 如果定义了此方法,则
x(arg1, arg2, ...)
是x.__call__(arg1, arg2, ...)
的简写。
3.4.6. 模拟容器类型
可以定义以下方法来实现容器对象。 容器通常是序列(如列表或元组)或映射(如字典),但也可以表示其他容器。 第一组方法用于模拟序列或模拟映射; 不同之处在于,对于序列,允许的键应该是整数 k 其中 0 <= k < N
其中 N 是序列或切片对象的长度,其中定义一个项目范围。 (为了向后兼容,方法 __getslice__()
(见下文)也可以定义为处理简单但不扩展的切片。)还建议映射提供方法 keys()
、values()
、items()
、has_key()
、get()
、clear()
、setdefault()
、iterkeys()
X281X]、iteritems()
、pop()
、popitem()
、copy()
和 update()
的行为类似于 Python 标准字典对象的行为。 UserDict 模块提供了一个 DictMixin
类来帮助从 __getitem__()
、__setitem__()
、__delitem__()
和keys()
。 可变序列应提供方法 append()
、count()
、index()
、extend()
、insert()
、pop()
、remove()
、reverse()
和 sort()
,类似于 Python 标准列表对象。 最后,序列类型应该通过定义方法 __add__()
、__radd__()
、__iadd__()
、__mul__()
、[ X190X] 和 __imul__()
如下所述; 它们不应定义 __coerce__()
或其他数值运算符。 建议映射和序列都实现 __contains__()
方法以允许有效使用 in
运算符; 对于映射,in
应该等同于 has_key()
; 对于序列,它应该搜索值。 进一步建议映射和序列都实现 __iter__()
方法,以允许通过容器进行高效迭代; 对于映射,__iter__()
应与 iterkeys()
相同; 对于序列,它应该遍历值。
- object.__len__(self)
- 调用以实现内置函数 len()。 应该返回对象的长度,一个整数
>=
0。 此外,未定义 __nonzero__() 方法且其 __len__() 方法返回零的对象在布尔上下文中被认为是假的。
- object.__getitem__(self, key)
调用以实现对
self[key]
的评估。 对于序列类型,接受的键应该是整数和切片对象。 请注意,负索引的特殊解释(如果类希望模拟序列类型)取决于 __getitem__() 方法。 如果key的类型不合适,可能会引发TypeError
; 如果序列的索引集之外的值(在对负值进行任何特殊解释之后),则应提高IndexError
。 对于映射类型,如果 key 缺失(不在容器中),则应引发KeyError
。笔记
for 循环期望为非法索引引发
IndexError
以允许正确检测序列的结尾。
- object.__setitem__(self, key, value)
- 调用以实现对
self[key]
的赋值。 与 __getitem__() 相同的注释。 如果对象支持更改键的值,或者如果可以添加新键,或者如果可以替换元素,则应该仅针对映射实现此功能。 对于不正确的 key 值,应该引发与 __getitem__() 方法相同的异常。
- object.__delitem__(self, key)
- 调用实现删除
self[key]
。 与 __getitem__() 相同的注释。 如果对象支持删除键,则只应为映射实现,或者如果可以从序列中删除元素,则应为序列实现。 对于不正确的 key 值,应该引发与 __getitem__() 方法相同的异常。
- object.__missing__(self, key)
- 当 key 不在字典中时,由 dict.__getitem__() 调用以实现 dict 子类的
self[key]
。
- object.__iter__(self)
当容器需要迭代器时调用此方法。 此方法应返回一个新的迭代器对象,该对象可以迭代容器中的所有对象。 对于映射,它应该遍历容器的键,并且还应该作为方法
iterkeys()
可用。迭代器对象也需要实现这个方法; 他们必须自己返回。 有关迭代器对象的更多信息,请参阅 迭代器类型 。
- object.__reversed__(self)
由内置的 reversed() 调用(如果存在)以实现反向迭代。 它应该返回一个新的迭代器对象,该对象以相反的顺序迭代容器中的所有对象。
如果未提供 __reversed__() 方法,内置的 reversed() 将回退到使用序列协议 (__len__() 和 ]__getitem__())。 支持序列协议的对象应该只提供 __reversed__(),前提是它们可以提供比 reversed() 提供的实现更有效的实现。
2.6 版中的新功能。
成员资格测试操作符(in 和 not in)通常实现为通过序列的迭代。 但是,容器对象可以提供以下具有更高效实现的特殊方法,这也不需要对象是序列。
- object.__contains__(self, item)
调用以实现成员资格测试运算符。 如果 item 在 self 中,应该返回 true,否则返回 false。 对于映射对象,这应该考虑映射的键,而不是值或键项对。
对于没有定义 __contains__() 的对象,成员资格测试首先通过 __iter__() 尝试迭代,然后通过 __getitem__() 尝试旧的序列迭代协议,请参阅语言参考 中的 此部分。
3.4.7. 用于模拟序列类型的其他方法
可以定义以下可选方法来进一步模拟序列对象。 不可变序列方法最多只能定义 __getslice__()
; 可变序列可能定义了所有三种方法。
- object.__getslice__(self, i, j)
自 2.0 版起已弃用: 支持切片对象作为 __getitem__() 方法的参数。 (但是,CPython 中的内置类型目前仍然实现 __getslice__()。 因此,在实现切片时,您必须在派生类中覆盖它。)
调用以实现对
self[i:j]
的评估。 返回的对象应该与 self 的类型相同。 请注意,切片表达式中缺少的 i 或 j 分别被零或 sys.maxsize 替换。 如果在切片中使用负索引,则将序列的长度添加到该索引中。 如果实例未实现 __len__() 方法,则会引发AttributeError
。 不保证以这种方式调整的索引不会仍然为负。 大于序列长度的索引不会被修改。 如果没有找到 __getslice__(),则会创建一个切片对象,并将其传递给 __getitem__()。
- object.__setslice__(self, i, j, sequence)
调用以实现对
self[i:j]
的赋值。 i 和 j 的注释与 __getslice__() 的注释相同。此方法已弃用。 如果没有找到 __setslice__(),或者对于
self[i:j:k]
形式的扩展切片,则会创建一个切片对象,并传递给 __setitem__(),而不是 [ X163X]__setslice__() 被调用。
- object.__delslice__(self, i, j)
- 调用实现删除
self[i:j]
。 i 和 j 的注释与 __getslice__() 的注释相同。 此方法已弃用。 如果没有找到 __delslice__(),或者对于self[i:j:k]
形式的扩展切片,则会创建一个切片对象,并将其传递给 __delitem__(),而不是 [ X163X]__delslice__() 被调用。
请注意,这些方法仅在使用带有单个冒号的单个切片时调用,并且 slice 方法可用。 对于涉及扩展切片符号的切片操作,或在没有切片方法的情况下,__getitem__()
、__setitem__()
或 __delitem__()
以切片对象作为参数调用。
以下示例演示了如何使您的程序或模块与早期版本的 Python 兼容(假设方法 __getitem__()
、__setitem__()
和 __delitem__()
支持切片对象作为参数):
class MyClass:
...
def __getitem__(self, index):
...
def __setitem__(self, index, value):
...
def __delitem__(self, index):
...
if sys.version_info < (2, 0):
# They won't be defined if version is at least 2.0 final
def __getslice__(self, i, j):
return self[max(0, i):max(0, j):]
def __setslice__(self, i, j, seq):
self[max(0, i):max(0, j):] = seq
def __delslice__(self, i, j):
del self[max(0, i):max(0, j):]
...
注意对 max() 的调用; 这些是必要的,因为在调用 __*slice__()
方法之前处理负索引。 当使用负索引时,__*item__()
方法按照提供的方式接收它们,但 __*slice__()
方法获取索引值的“熟”形式。 对于每个负索引值,在调用该方法之前将序列的长度添加到索引中(这仍然可能导致负索引); 这是内置序列类型对负索引的习惯处理,__*item__()
方法也有望做到这一点。 但是,由于他们应该已经这样做了,因此不能传入负索引; 在传递给 __*item__()
方法之前,它们必须被限制在序列的边界内。 调用 max(0, i)
可以方便地返回正确的值。
3.4.8. 模拟数字类型
可以定义以下方法来模拟数字对象。 与所实现的特定类型的数字不支持的操作相对应的方法(例如,非整数的按位操作)应该保持未定义。
- object.__add__(self, other)
object.__sub__(self, other)
object.__mul__(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__() 方法的类的实例,调用x.__add__(y)
. __divmod__() 方法应该等同于使用 __floordiv__() 和 __mod__(); 它不应该与 __truediv__()(如下所述)相关。 请注意,如果要支持内置 pow() 函数的三元版本,则应定义 __pow__() 以接受可选的第三个参数。如果这些方法之一不支持使用所提供参数的操作,则应返回
NotImplemented
。
- object.__div__(self, other)
object.__truediv__(self, other)
- 除法运算符 (
/
) 由这些方法实现。 __truediv__()方法在__future__.division
生效时使用,否则使用__div__()。 如果只定义了这两种方法中的一种,则对象将不支持备用上下文中的除法;TypeError
将改为升高。
- object.__radd__(self, other)
object.__rsub__(self, other)
object.__rmul__(self, other)
object.__rdiv__(self, other)
object.__rtruediv__(self, other)
object.__rfloordiv__(self, other)
object.__rmod__(self, other)
object.__rdivmod__(self, other)
object.__rpow__(self, other)
object.__rlshift__(self, other)
object.__rrshift__(self, other)
object.__rand__(self, other)
object.__rxor__(self, other)
object.__ror__(self, other) 调用这些方法来实现二进制算术运算 (
+
,-
,*
,/
,%
, divmod ()、pow()、**
、<<
、>>
、&
、^
,|
) 与反射(交换)操作数。 仅当左操作数不支持相应操作且操作数为不同类型时,才会调用这些函数。 2 例如,要计算表达式x - y
,其中 y 是具有 __rsub__() 方法的类的实例,[如果x.__sub__(y)
返回 NotImplemented,则调用 X154X]。请注意,三元 pow() 不会尝试调用 __rpow__()(强制规则会变得太复杂)。
笔记
如果右操作数的类型是左操作数类型的子类,并且该子类为操作提供反射方法,则该方法将在左操作数的非反射方法之前调用。 此行为允许子类覆盖其祖先的操作。
- object.__iadd__(self, other)
object.__isub__(self, other)
object.__imul__(self, other)
object.__idiv__(self, other)
object.__itruediv__(self, other)
object.__ifloordiv__(self, other)
object.__imod__(self, other)
object.__ipow__(self, other[, modulo])
object.__ilshift__(self, other)
object.__irshift__(self, other)
object.__iand__(self, other)
object.__ixor__(self, other)
object.__ior__(self, other)
- 调用这些方法来实现增强算术赋值(
+=
、-=
、*=
、/=
、//=
、[ X135X]、**=
、<<=
、>>=
、&=
、^=
、|=
)。 这些方法应该尝试就地执行操作(修改 self)并返回结果(可能但不一定是 self)。 如果未定义特定方法,则增广赋值回退到正常方法。 例如,要执行语句x += y
,其中 x 是具有 __iadd__() 方法的类的实例,调用x.__iadd__(y)
. 如果 x 是未定义 __iadd__() 方法的类的实例,则考虑x.__add__(y)
和y.__radd__(x)
,与评估一样x + y
。
- object.__neg__(self)
object.__pos__(self)
object.__abs__(self)
object.__invert__(self)
- 调用以实现一元算术运算(
-
、+
、abs() 和~
)。
- object.__complex__(self)
object.__int__(self)
object.__long__(self)
object.__float__(self)
- object.__oct__(self)
object.__hex__(self)
- object.__index__(self)
调用以实现 operator.index()。 在 Python 需要整数对象时(例如在切片中)也调用。 必须返回一个整数(int 或 long)。
2.5 版中的新功能。
- object.__coerce__(self, other)
- 调用以实现“混合模式”数字算术。 应该返回一个包含 self 和 other 的 2 元组转换为常见的数字类型,或者
None
如果转换不可能。 当公共类型是other
的类型时,返回None
就足够了,因为解释器也会要求另一个对象尝试强制转换(但有时,如果其他类型无法更改,此处转换为其他类型很有用)。NotImplemented
的返回值等价于返回None
。
3.4.9. 强制规则
本节用于记录强制规则。 随着语言的发展,强制规则变得难以准确记录; 记录一个特定实现的一个版本所做的事情是不可取的。 相反,这里有一些关于强制的非正式指南。 在 Python 3 中,将不支持强制转换。
If the left operand of a % operator is a string or Unicode object, no coercion takes place and the string formatting operation is invoked instead.
不再推荐定义强制操作。 未定义强制转换的类型的混合模式操作将原始参数传递给操作。
新式类(从 object 派生的那些)从不调用
__coerce__()
方法来响应二元运算符; 唯一一次调用__coerce__()
是在调用内置函数 coerce() 时。对于大多数意图和目的,返回
NotImplemented
的运算符被视为完全未实现的运算符。下面用
__op__()
和__rop__()
表示操作符对应的泛型方法名;__iop__()
用于对应的就地运算符。 例如,对于运算符 '+
',__add__()
和__radd__()
用于二元运算符的左右变体,__iadd__()
用于就地变体。对于对象 x 和 y,首先尝试
x.__op__(y)
。 如果未实现或返回NotImplemented
,则尝试y.__rop__(x)
。 如果这也未实现或返回NotImplemented
,则会引发TypeError
异常。 但看到以下异常:上一项的例外:如果左操作数是内置类型或新式类的实例,而右操作数是该类型或类的适当子类的实例并覆盖基类的 [ X217X] 方法,右操作数的
__rop__()
方法在 左操作数的__op__()
方法之前尝试 。这样做是为了让子类可以完全覆盖二元运算符。 否则,左操作数的
__op__()
方法将始终接受右操作数:当需要给定类的实例时,该类的子类的实例总是可接受的。当任一操作数类型定义强制转换时,该强制转换会在调用该类型的
__op__()
或__rop__()
方法之前调用,但不会更早。 如果强制转换为调用其强制转换的操作数返回不同类型的对象,则使用新对象重做该过程的一部分。当使用就地运算符(如 '
+=
')时,如果左操作数实现了__iop__()
,则无需任何强制即可调用它。 当操作回退到__op__()
和/或__rop__()
时,正常强制规则适用。在
x + y
中,如果x是一个实现序列拼接的序列,则调用序列拼接。丰富的比较(由方法
__eq__()
等实现)从不使用强制转换。 三路比较(由__cmp__()
实现)在与其他二元运算使用它相同的条件下确实使用强制转换。在当前实现中,内置数字类型 int、long、float 和 complex 不使用强制转换。 所有这些类型都实现了一个
__coerce__()
方法,供内置的 coerce() 函数使用。2.7 版更改: 复杂类型不再隐式调用
__coerce__()
方法进行混合类型的二进制算术运算。
3.4.10. 使用语句上下文管理器
2.5 版中的新功能。
上下文管理器 是一个对象,它定义了在执行 with 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需的运行时上下文以执行代码块。 上下文管理器通常使用 with 语句调用(在 with 语句 节中描述),但也可以通过直接调用它们的方法来使用。
上下文管理器的典型用途包括保存和恢复各种全局状态、锁定和解锁资源、关闭打开的文件等。
有关上下文管理器的更多信息,请参阅 上下文管理器类型 。
- object.__exit__(self, exc_type, exc_value, traceback)
退出与此对象相关的运行时上下文。 参数描述了导致上下文退出的异常。 如果上下文无异常退出,则所有三个参数都将是 None。
如果提供了一个异常,并且该方法希望抑制该异常(即防止它被传播),它应该返回一个真值。 否则,退出该方法时异常将被正常处理。
请注意, __exit__() 方法不应重新引发传入的异常; 这是调用者的责任。
3.4.11. 旧式类的特殊方法查找
对于旧式类,特殊方法总是以与任何其他方法或属性完全相同的方式进行查找。 无论是在 x.__getitem__(i)
中显式查找方法还是在 x[i]
中隐式查找方法,情况都是如此。
这种行为意味着如果适当的特殊属性设置不同,特殊方法可能会针对单个旧式类的不同实例表现出不同的行为:
>>> class C:
... pass
...
>>> c1 = C()
>>> c2 = C()
>>> c1.__len__ = lambda: 5
>>> c2.__len__ = lambda: 9
>>> len(c1)
5
>>> len(c2)
9
3.4.12. 新式类的特殊方法查找
对于新式类,特殊方法的隐式调用只有在对象的类型上定义时才能保证正确工作,而不是在对象的实例字典中。 这种行为是以下代码引发异常的原因(与旧式类的等效示例不同):
>>> class C(object):
... 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__()
机制为解释器中的速度优化提供了很大的空间,代价是在处理特殊方法时具有一定的灵活性(特殊方法 必须在类上设置 对象本身,以便解释器始终如一地调用)。
脚注
- 1
- 在某些受控条件下, 是 可能在某些情况下更改对象的类型。 不过,这通常不是一个好主意,因为如果处理不当,可能会导致一些非常奇怪的行为。
- 2
- 对于相同类型的操作数,假设如果非反射方法(例如
__add__()
)失败则不支持该操作,这就是为什么不调用反射方法的原因。