3. 数据模型¶
3.1. 对象、值和类型¶
对象是Python对数据的抽象。Python程序中的所有数据都由对象或对象之间的关系表示。(在某种意义上,并且符合冯·诺依曼的“存储程序计算机”的模型,代码也由对象表示)。
每个对象由ID、类型以及值组成。对象一旦建立,它的ID永远不会改变;你可以认为它是该对象在内存中的地址。‘is
’操作符比较两个对象的ID;id()
函数返回一个表示对象ID的整数。
CPython实现细节:对于CPython,id(x)
为x
存储在内存中的地址。
对象的类型决定对象支持的操作(例如,它是否具有长度?),还定义该类型的对象可能具有的值。type()
函数返回对象的类型(它本身也是一个对象)。与ID 一样,对象的类型也是不可以修改的。[1]
某些对象的值可能会更改。值可以改变的对象称为可变的;一旦建立,值就不可以改变的对象称为不可变的。(包含对可变对象的引用的不可变容器对象的值可以在后者的值更改时更改;但是仍然认为该容器是不可变的,因为它包含的对象的容器不能更改。所以,不变性不是完全一样的有一个不可改变的价值,它是更微妙的。)一个对象的可变性由它的类型决定;例如,数值、字符串和元组是不可变的,而字典和列表是可变的。
对象永远不会显式地销毁;但是当它们不可用时可以被当作垃圾回收。一个实现允许推迟垃圾容器或者完全忽略它 - 这是一个实现质量如何实现垃圾容器的问题,只要没有收集仍然可达的对象。
CPython实现细节: CPython目前使用带有(可选)循环链接垃圾的延迟检测的引用计数方案,它会在大多数对象变得无法访问时收集大多数对象,但不能保证收集包含循环的垃圾参考。参考gc
模块的文档可以获得控制循环垃圾回收的信息。其他实现的行为不同,CPython可能会改变。当它们变得不可达时,不要依赖对象的立即结束(因此你应该总是明确地关闭文件)。
请注意,使用实现的跟踪或调试工具可能会使对象活动,通常是可收集的。另请注意,使用“try
... except
”语句)捕获异常可能会使对象活动。
某些对象包含对“外部”资源的引用,例如打开的文件或窗口。可以理解在对象被当作垃圾回收时这些资源也被释放,但因为垃圾回收不保证一定发生,这样的对象也提供显式的方法释放外部资源,通常是close()
方法。强烈建议程序明确关闭此类对象。'try
... finally
'语句和'with
'语句提供了方便的方法。
有些对象包含其它对象的引用;它们叫做容器。容器的例子是元组,列表和字典。引用是容器值的一部分。大多数情况下,当我们谈到一个容器的值时,我们是指值,而不是所包含的对象的ID;然而,当我们谈论容器对象的可变性的时候,就只是指被直接包含的对象的ID。因此,如果一个不可变容器(像一个元组)包含对一个可变对象的引用,如果该可变对象被改变,它的值会改变。
类型几乎影响对象行为的所有方面。即使对象标识的重要性在某种意义上受到影响:对于不可变类型,计算新值的操作实际上可能返回对具有相同类型和值的任何现有对象的引用,而对于可变对象,这是不允许的。例如,在a = 1; b = 1
,a
和b
可能或可能不会引用值为1的相同对象,具体取决于实现方式,但在 c = []; d = t17>
,c
和d
保证指代两个不同的,唯一的新创建的空列表。(注意c = d = []
是把相同的对象赋给c
和d
。)
3.2. 标准类型的层次结构¶
以下是一份Python内建类型的清单。扩展模块(用C,Java或其他语言编写,取决于实现)可以定义其他类型。未来版本的Python可能在这个类型层次结构中增加其它类型(例如,有理数、高效存储的整数数组,等等),虽然这种添加通常由标准库提供。
下面有些类型的描述包含一个列出“特殊属性”的段落。这些属性提供对具体实现的访问,而不是作为一般的目的使用。他们的定义可能会在将来改变。
- None
此类型具有单个值。有一个具有此值的单个对象。这个对象通过内建名字
None
访问。它用于表示在许多情况下不存在值,例如,它从没有显式返回任何东西的函数返回。它的真值是假的。- NotImplemented
此类型具有单个值。有一个具有此值的单个对象。这个对象通过内建名字
NotImplemented
访问。如果数值方法和丰富的比较方法不对所提供的操作数实施操作,则应返回此值。(解释器将会尝试反射操作,或一些其他回退,取决于操作符号。)其真值是True。更多细节参见实现算术操作。
- Ellipsis
此类型具有单个值。有一个具有此值的单个对象。这个对象通过字面值
...
或内建名字Ellipsis
访问。其真值是True。numbers.Number
它们由数值字面值生成或者由算术操作符和内建的算术函数作为结果返回。数值对象是不可变的;一旦创建,它们的值永远不会改变。Python数字当然与数学数字密切相关,但受计算机中数字表示的限制。
Python区分整数,浮点数和复数:
numbers.Integral
这些表示来自整数的数学集合(正和负)的元素。
有两种类型的整数:
整数(
int
)它们表示的数值范围没有限制,只受限于可用的(虚拟内存)内存。对于以移位和掩码为目的的运算,长整数采用二进制的形式,负数用二进制补码形式表示,给人的错觉是一个符号位向左无限扩展的字符串。- 布尔型(
bool
) 它们表示真值False和True。表示
False
和True
的两个对象是仅有的布尔对象。布尔类型是普通整数的子类型,布尔值的行为在几乎所有环境下分别类似0和1,例外的情况是转换成字符串的时候分别返回字符串"False"
或"True"
。
整数表示的规则旨在给出涉及负整数的移位和掩码运算的最有意义的解释。
- 布尔型(
numbers.Real
(float
)这些表示机器级双精度浮点数。接受的范围受底层的机器体系结构(和C或者Java的实现)控制,你需要做溢出处理。Python不支持单精度浮点数;使用它的原因通常是节省处理器和内存的使用,但是相比Python中对象使用的开销是微不足道的,因此没有必要支持两种浮点数使语言变的复杂。
numbers.Complex
(complex
)这些表示作为一对机器级双精度浮点数的复数。同样的注意事项适用于浮点数。复数
z
的实部和虚部可以通过只读属性z.real
和z.imag
获得。
- Sequences
这些表示由非负数索引的有限有序集。内建的函数
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<
j。序列根据它们的可变性区分:
- 不可变序列
不可变序列类型的对象在创建后不能更改。(如果对象包含对其他对象的引用,这些其他对象可以是可变的并且可以改变;但是,由不可变对象直接引用的对象的容器不能改变。
以下类型是不可变序列:
- 字符串
字符串是由表示Unicode码点的值组成的一个序列。在
U+0000 - U+10FFFF
范围内的所有码点都可以在字符串中表示。Python没有char
类型;字符串中个每个码点通过长度为1
的字符串对象表示。内建函数ord()
将一个码点从字符串形式转换为范围在0 - 10FFFF
之间的一个整数;chr()
将0 - 10FFFF
范围之间的一个整数转换为对应的长度为1
的字符串对象。str.encode()
可以用来使用给定的文本编码将str
转换为bytes
,bytes.decode()
可以用来实现相反的操作。- 元组
元组的项目是任意的Python对象。两个或多个项的元组由逗号分隔的表达式列表形成。可以通过将逗号附加到表达式(表达式本身不创建元组,因为括号必须可用于表达式的分组)来形成一个项目('singleton')的元组。一个空的元组可以由空的一对括号形成。
- 字节
字节对象是一个不可变的数组。元素是由范围在0 <= x < 256之间的整数表示的8比特字节。字节字面值(如
b'abc'
)和内建的函数bytes()
可以用来构造字节对象。另外,字节对象可以通过decode()
方法解码成字符串。
- 可变序列
可变序列在创建后可以更改。下标和切片表示法可以用于赋值和
del
(delete)语句的目标。目前有两种内在的可变序列类型:
- 列表
列表的项目是任意的Python对象。列表是通过在方括号中放置逗号分隔的表达式列表而形成的。(注意,没有特殊情况需要形成长度为0或1的列表)
- 字节数组
bytearray对象是一个可变数组。它们由内建的
bytearray()
构造函数创建。除了可变(因此不可缓冲)之外,字节数组提供与不可变字节对象相同的接口和功能。
扩展模块
array
提供另外一个可变序列类型的例子,collections
模块也是。
- 集合类型
这些表示无序的,有限的独特的,不可变的对象集合。因此,它们不能被任何下标索引。然而,它们可以迭代,内建函数
len()
返回集合中元素的个数。集合的常用用法是快速成员测试,从序列中去除重复,以及计算数学运算,例如交叉,共用体,差和对称差。对于集合元素,相同的不变性规则适用于字典键。注意,数值类型遵循正常的数值比较规则:如果两个数字相等(例如,
1
和1.0
),其中只有一个可以包含在集合中。目前有两种内在集合类型:
- 集合
- 固定集合
这些代表一个不可变的集合。它们由内建函数
frozenset()
构造函数创建。因为固定集合不可变且可以哈希,它可以作为另外一个集合的元素,或者作为字典的键。
- 映射
这些表示由任意索引集索引的有限对象集合。下标表示法
a[k]
从映射a
中选择由k
索引的元素;它可以用在表达式中并作为赋值或del
语句的目标。内建函数len()
返回映射中元素的个数。目前有一种单一的固有映射类型:
- 词典
这些表示由几乎任意值索引的对象的有限集合。不能作为键接受的值的唯一类型是包含列表或字典或其他可变类型的值,它们通过值而不是对象标识进行比较,原因是字典的有效实现需要密钥的哈希值保持不变。用于键的数值类型遵循正常的数值比较规则:如果两个数字相等(例如,
1
和1.0
),那么它们可以互换地使用来索引同一个字典入口。字典是可变的;它们可以通过
{...}
表示法创建(参见字典的显示一节)。扩展模块
dbm.ndbm
和dbm.gnu
提供另外几种类型的映射,collections
模块也是。
- 可调用类型
这些是可以使用函数调用操作(参考调用一节)的类型:
- 用户定义的函数
用户定义的函数对象由函数定义创建(参见函数定义一节)。它应该使用包含与函数的形式参数列表相同数量的项的参数列表来调用。
特殊属性:
属性 含义 __doc__
函数的文档字符串,如果没有就为 None
;不会被子类继承。可写 __name__
函数的名称 可写 __qualname__
函数的限定名称
3.3 版本中新加入。
可写 __module__
函数定义所在的模块的名称,如果没有则为 None
。可写 __defaults__
一个元组,包含具有默认值的参数的默认参数值,如果没有参数具有默认值则为 None
可写 __code__
代码对象表示编译的函数体。 可写 __globals__
对保存函数的全局变量的字典的引用 - 定义函数的模块的全局命名空间。 只读 __dict__
支持任意函数属性的命名空间。 可写 __closure__
None
或包含函数的自由变量的绑定的单元格元组。只读 __annotations__
一个包含参数注解的字典。字典的键为参数的名称,如果返回值有注解,则用 'return'
表示返回值的注解。可写 __kwdefaults__
一个包含“非关键字不可”的参数的默认值的字典。 可写 大多数标记为“可写”的属性检查所分配值的类型。
函数对象还支持获取和设置任意属性,这些属性可用于例如将元数据附加到函数。常规属性点表示法用于获取和设置此类属性。注意当前的实现只在用户定义的函数上支持函数属性。未来可能支持内建函数上的函数属性。
函数定义的额外信息可以从它的代码对象中获取;参见下面内部类型的描述。
- 实例方法
实例方法对象将类、类的实例和任何可调用对象(通常是一个用户定义的函数)结合起来。
特殊的只读属性:
__self__
为类实例对象,__func__
为函数对象;__doc__
为函数的文档(与__func__.__doc__
相同);__name__
为方法的名称(与__func__.__name__
相同);__module__
为方法定义所在的模块的名称,如果没有则为None
。方法还支持访问(但不能设置)底层函数对象的任何函数属性。
用户定义的方法对象可以在获取类的一个属性的时候创建(可能通过类的一个实例),如果这个属性是用户定义的函数对象或者一个类方法对象。
如果一个实例方法对象是通过从类的一个实例获取用户定义的函数对象创建的,那么它的
__self__
属性就是这个实例,此时称这个方法对象是绑定的。新方法的__func__
属性就是原始的函数对象。当一个用户定义的方法对象是通过从一个类或实例获取另外一个方法对象创建的,那么其行为和函数对象相同,区别是这个新创建的方法的
__func__
属性不是原始的方法对象,而是原始方法对象的__func__
属性。当一个实例方法对象是通过从一个类或实例获取一个类方法对象创建的,那么
__self__
属性是这个类本身,__func__
属性是这个类方法底层的函数对象。当调用一个实例方法对象时,将类的实例(
__self__
)插入到参数列表的最前面,并调用底层的函数(__func__
)。例如,如果C
是一个包含函数定义f()
的类,x
是C
的一个实例,调用x.f(1)
等同于调用C.f(x, 1)
。当一个实例方法对象是从一个类方法对象中得到的,存储在
__self__
中的“类实例”将实际上是类自己,所以调用x.f(1)
或C.f(1)
等同于调用f(C,1)
,其中f
是底层的函数。注意,每次从实例检索属性时,都会发生从函数对象到实例方法对象的转换。在某些情况下,富有成效的优化是将属性分配给局部变量并调用该局部变量。还要注意这种转换只有用户定义的函数会发生;其它可调用的对象(和所有不可调用的对象)在获取时不会转换。同样要注意如果用户定义的函数是类实例的属性不会被转换为绑定的方法;这种转换只发生在函数是类的属性的时候。
- 生成器函数
用到
yield
语句 ( 参见yield 语句)的函数或方法叫做生成器函数。这种函数,在调用的时候,总是返回一个可以用来执行函数体的迭代器对象:调用迭代器的iterator.__next__()
方法将导致函数执行直到它使用yield
语句提供一个值。当函数执行到return
语句或结束的时候, 会引发一个StopIteration
异常,并且迭代器已经到达要返回的数据集的末尾。- 协程函数
使用
async def
定义的函数或方法叫做协程函数。这种函数,在调用时,返回一个协程对象。它可以包含await
表达式,以及async with
和async for
语句。另见协程对象一节。- 内建函数
内建函数对象是C函数的包装器。内建函数的例子有
len()
和math.sin()
(math
是标准的内建模块)。参数的数量和类型由C函数确定。特殊的只读属性:__doc__
指函数的文档字符串,如果没有则为None
;__name__
指函数的名字;__self__
被设置为None
(看看下一个属性);__module__
指函数定义所在模块的名字,如果没有则为None
。- 内建方法
这实际上是一个内建函数的不同伪装,这次包含作为隐式额外参数传递给C函数的对象。一个内建方法的例子是
alist.append()
,假设alist是一个列表对象。在这种情况下,特殊的只读属性__self__
设置为alist对象。- 类
- 类是可调用的。这些对象通常作为工厂创建它们自己的实例,但是覆盖
__new__()
方法的类类型可以有所变化。调用的参数传递给__new__()
,且在正常情况下再传递给__init__()
来初始化新的实例。 - 类实例
- 任何类的实例只有当它们的类定义了
__call__()
方法时才是可以调用的。
- 模块
模块是Python代码的一个基本组织单元, 通过
import
语句(参见import
)或者importlib.import_module()
和内建的__import__()
函数触发import系统创建。模块对象有一个用字典对象实现的命名空间(这就是模块中定义的函数的__globals__
属性引用的字典)。属性的引用被转换成查询这个字典,例如m.x
等同于m.__dict__["x"]
。模块对象不包含用于初始化模块的代码对象(因为初始化完成后不需要它)。属性的赋值会更新模块的命名空间字典,例如
m.x = 1
等同于m.__dict__["x"] = 1
。特殊的只读属性:
__dict__
指模块字典形式的命名空间。CPython实现细节:由于CPython清除模块字典的方式,当模块离开作用域之外时,模块的字典将被清除即使字典仍然被引用。为了避免这种情况,请复制字典或保留模块,直接使用其字典。
预定义的(可写)属性:
__name__
为模块的名字;__doc__
指模块的文档字符串,如果没有则为None
;__file__
为模块加载的文件路径,如果它是从文件中加载的话。与解释器静态连接的C模块没有__file__
属性;从共享库中动态连接的扩展模块,它是共享库文件的路径名称。- 自定义类
自定义的类类型通常由类定义创建(参见类定义一节)。类有一个由字典对象实现的命名空间。类属性的引用被转换为这个字典的查询,例如,
C.x
被转换为C.__dict__["x"]
(尽管有若干特别的钩子允许其它方式定位属性)。当在那里没有找到属性名称时,属性搜索在基类中继续。这种基类的搜索使用C3方法分辨率顺序,即使存在“菱形”继承结构,其中存在导致回到共同祖先的多个继承路径的行为也正确。有关C3使用的C3 MRO的更多详细信息,请参阅随2.3版本的文档,网址为https://www.python.org/download/releases/2.3/mro/。当类属性引用(例如类
C
)产生一个类方法对象时,它被转换成一个实例方法对象,其__self__
属性是C
。当类属性引用产生一个静态方法对象时,它被转换成用静态方法对象封装的对象。在实现描述器一节可以看到另外一种方式,从类中获取的属性可能与真正包含在__dict__
中的不同。类属性赋值更新类的字典,而不是基类的字典。
类对象可以被调用(见上文)以产生类实例(见下文)。
特殊的属性:
__name__
是类的名字;__module__
是类定义所在的模块名;__dict__
是包含类命名空间的字典;__bases__
是包含类的基类的元组,顺序为它们在基类列表中出现的顺序;__doc__
是类的文档字符串,如果没有定义则为None。- 类实例
类实例通过调用类对象(见上文)创建。类实例具有实现为字典的命名空间,这是搜索属性引用的第一个位置。当在那里没有找到属性,并且实例的类具有该名称的属性时,搜索继续类属性。如果找到的类属性是一个用户定义函数对象,那么它被转换成一个实例方法对象,其
__self__
属性为这个实例。静态方法和类方法对象也会转换;参见上文“类”。在实现描述器 一节可以看到另外一种方式,通过实例获取类的属性可能与真正包含在类的__dict__
中的不同。如果没有找到相应的类属性,并且对象的类有一个__getattr__()
方法,那么这个方法会被调用以满足搜索。属性赋值和删除更新实例的字典,从不是类的字典。如果类有一个
__setattr__()
或者__delattr__()
方法,那么会调用这个方法而不是直接更新实例的字典。如果类实例具有某些特殊名称的方法,则它们可以伪装成数字,序列或映射。参见特殊方法的名字 一节。
- I/O对象(也称为文件对象)
文件对象表示打开的文件。有各种快捷方式可以创建文件对象:内建函数
open()
,os.popen()
,os.fdopen()
和socket对象的makefile()
方法(或者其它扩展模块提供的函数或方法)。sys.stdin
、sys.stdout
和sys.stderr
文件对象分别初始化为解释器的标准输入、输出和错误流;它们都以文本模式打开,因此遵循io.TextIOBase
抽象类定义的接口。- 内部类型
解释器内部使用的几种类型暴露给用户。它们的定义可能随着解释器的未来版本而改变,但在这里提及它们是为了完整性。
- 代码对象
代码对象表示编译成字节的的可执行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
;在早期的Python 版本中,还使用过0x10
和0x1000
标志位。co_flags
中的其它位保留作内部使用。如果代码对象表示一个函数,
co_consts
中的第一个元素是函数的文档字符串,如果没有定义则为None
。
- 框架对象
框架对象表示执行框架。它们可能出现在追溯对象中(见下文)。
特殊的只读属性:
f_back
指向堆栈中的前一帧(朝调用的方向),如果这是堆栈最底部的帧则为None
;f_code
是帧中正在执行的代码对象;f_locals
是用于查询局部变量的字典;f_globals
用于全局变量;f_builtins
用与内建的(固有的)名字;f_lasti
给出精确的指令(它是代码对象的字节码字符串的一个索引)。特殊的可写属性:
f_trace
,如果不为None
,则是源代码开始调用的函数(用于调试器);f_lineno
帧当前的行号 — 从跟踪函数的内部写入这个值将跳转到指定的行(只用于最底部的帧)。调试器可以通过写入f_lineno来实现Jump命令(也称为设置下一个语句)。框架对象支持一种方法:
-
frame.
clear
()¶ 此方法清除对框架所持有的局部变量的所有引用。此外,如果框架属于生成器,则生成器被最终确定。这有助于打破涉及框架对象的引用循环(例如捕获异常并存储其回溯以供以后使用时)。
如果帧当前正在执行,则引发
RuntimeError
。版本3.4中新增。
-
- 跟踪对象
跟踪对象表示异常的堆栈跟踪。发生异常时将创建一个跟踪对象。当搜索异常处理程序展开执行堆栈时,在每个展开层次,跟踪对象被插入到当前跟踪前面。当输入异常处理程序时,堆栈跟踪对程序可用。(参见try 语句一节。)它可以通过
sys.exc_info()
返回的元组的第三个元素得到。当程序没有包含合适的处理函数时,栈回溯被写到(格式很漂亮)标准错误流;如果解释器是交互式的,用户还可以通过sys.last_traceback
得到它。特殊的只读属性:
tb_next
是栈回溯中的下一个层级(朝异常发生的那个帧的方向),如果没有下一级则为None
;tb_frame
指向当前层级的执行帧;tb_lineno
给出异常发生的行号;tb_lasti
指示精确的指令。如果异常发生在没有匹配的except字句或者带有finally字句的try
语句中,回溯中行号和最后的指令可能与帧对象的行号不同。- 切片对象
切片对象用于表示
__getitem__()
方法产生的切片。它们还可以由内建的slice()
函数创建。特殊的只读属性:
start
指下边界;stop
指上边界;step
指步长;每个属性在省略的情况下都为None
。这些属性可以具有任何类型。切片对象支持一种方法:
-
slice.
indices
(self, length)¶ 此方法采用单个整数参数length,并计算如果应用于长度序列的条目对象将描述的切片信息。它返回一个包含三个整数的元组;它们分别是start,stop索引和切片的步长step。以与规则切片一致的方式处理缺失或超出界限索引。
-
- 静态方法对象
- 静态方法对象提供了一种消除上述方法对象到函数对象的转换的方法。静态方法对象是围绕任何其他对象(通常是用户定义的方法对象)的包装器。当从类或类实例检索静态方法对象时,实际返回的对象是包装对象,不需要进行任何进一步的变换。静态方法对象本身不是可调用的,虽然它们包装的对象通常是。静态方法对象由内建的
staticmethod()
构造函数创建。 - 类方法对象
- 类方法对象,就像一个静态方法对象,是另一个对象的包装,改变从类和类实例检索对象的方式。以上在“用户定义的方法”下描述了类方法对象在这种检索上的行为。类方法对象由内建的
classmethod()
构造函数创建。
3.3. 特殊方法的名称¶
类可以通过使用特殊名称定义方法来实现通过特殊语法(如算术运算或下标和切片)调用的某些操作。这是Python的操作符重载方法,允许类根据语言操作符定义自己的行为。例如,如果某个类定义了一个名字为__getitem__()
的方法,并且x
是这个类的实例,那么x[i]
粗略等价于type(x).__getitem__(x, i)
。除非特别说明,当没有定义适当方法时,执行某个操作会抛出异常(一般是AttributeError
或者TypeError
)。
当实现一个模拟任何内建类型的类时,重要的是,模拟只能被实现到对被建模对象有意义的程度。例如,某些序列擅长获取单个元素,但抽取切片却是没有意义的。(一个例子是W3C的文档对象模型中的NodeList
接口。)
3.3.1. 基本的定制¶
-
object.
__new__
(cls[, ...])¶ 调用以创建类cls的新实例。
__new__()
是一个静态方法(这是个特列,所以你不需要显式地声明),以实例的类为第一个参数。其余的参数是传递给对象构造函数expression(对类的调用)。__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_info()[2]
中的回溯使得调用栈的帧一直存活);或者在交互模型中引发未处理的异常的调用栈的帧上对一个对象的引用(保存在sys.last_traceback
中的回溯使得调用栈的帧一直存活)。第一种情形只有显式地破坏这个环才能修复;第二种情形可以通过释放对回溯对象不在有用的引用来解决,第三种情形可以通过保存None
到sys.exc_traceback
中解决。当循环垃圾回收器启用时,会检测并清除垃圾循环引用(默认是打开的)。参见gc
模块的文档以获得关于这个主题的更多信息。
-
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
类的str.format()
方法)调用来产生一个对象的“格式化”的字符串表示形式。format_spec
参数是一个字符串,包含要求的格式化选项的描述。format_spec
参数的解释取决于实现__format__()
的类型,然而大部分类都将格式化托管给一个内建的类型,或者使用一个相似的格式化选项语法。标准格式化语法的描述,参见格式化微语言细则。
返回值必须是字符串对象。
版本3.4中的变化:如果传递任何非空字符串给
object
本身的__format__方法,它将引发一个TypeError
。
-
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()
来决定结果是真还是假。默认情况下,
__ne__()
托管给__eq__()
,只要结果不是NotImplemented
就对它取反。除此之外,比较操作之间没有其它的隐式关系,例如(x<y 或 x==y)
这样的事实并不暗含x<=y
为假。若要仅从一个操作自动生成排序操作,请参见functools.total_ordering()
。参见
__hash__()
的段落,关于创建hashable对象的重要注意事项,这种对象支持自定义比较操作并可用于字典的键。这些方法没有参数交换后的版本(用于左侧参数不支持该操作单右侧参数支持时);当然,
__lt__()
和__gt__()
互为对方的反射,__le__()
和__ge__()
互为对方的反射,__eq__()
和__ne__()
是它们自己的反射。如果操作数是不同的类型,而且右操作数是左操作数的直接或间接子类,那么右操作数的反射方法具有优先权,否则左操作数具有优先权。不考虑虚的子类。
-
object.
__hash__
(self)¶ 由内建函数
hash()
调用和用于哈希后的容器成员的操作,包括集合
、固定集合
和字典
。__hash__()
应该返回一个整数。要求唯一的性质是比较起来相等的对象具有相同的哈希值;it is advised to somehow mix together (e.g.使用排它或)散列值用于对象的组件,其也在对象的比较中起作用。注意
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__()
, 因为可哈希的容器实现要求对象的哈希值是不可变的(如果对象的哈希值改变,那么它将在错误的哈希桶中)。用户自定义的类默认具有
__cmp__()
和__hash__()
方法;有了它们,所有的对象比较起来都不相等(除非和它们自己比较)且x.__hash__()
返回一个从恰当的值,使得x == y
暗含x is y
且hash(x) == hash(y)
。覆盖
__eq__()
且没有定义__hash__()
的类将使得它的__hash__()
隐式设置为None
。当一个类的__hash__()
方法为None
时,如果程序视图获取这个类的实例的哈希值,它们将引发一个恰当的TypeError
,如果检查isinstance(obj, collections.Hashable)
,它们还会被正确地识别为不可哈希的。如果一个覆盖
__eq__()
的类需要保留父类的__hash__()
实现,则必须通过设置__hash__ = <ParentClass>.__hash__
显式地告诉解释器。如果没有覆盖
__eq__()
的类希望不支持哈希,它应该在类的定义中包含__hash__ = None
。如果一个类自己定义的__hash__()
显式引发一个TypeError
,它将不能被isinstance(obj, collections.Hashable)
调用正确识别为可哈希的。注意
默认情况下, 字符串、字节和日期对象的
__hash__()
值是通过一个不可预测的随机数“腌制过的”。虽然它们在一个单独的Python进程中保持一致性,但是它们在重复地启用Python之间是不可预测的。这么做的目的是提供保护以防拒绝服务攻击,通过精心选择的输入可以将字典插入的性能降低到最差的性能,O(n^2)复杂度。详细信息参见http://www.ocert.org/advisories/ocert-2011-003.html。
哈希值的改变影响字典、集合和其它映射的迭代顺序。Python从不保证这个顺序(32位和64位版本间的差别非常大)。
版本3.3中的变化:默认启用将哈希随机化。
-
object.
__bool__
(self)¶ 用来实现真值测试和内建的
bool()
操作;应该返回False
或True
。当这个方法没有定义时,如果__len__()
有定义则调用它,并且如果它的结果非零,则对象被认为是真。如果类既没有定义__len__()
也没有定义__bool__()
,那么它的所有实例都认为是真。
3.3.2. 自定义属性访问¶
可以定义下面的方法来自定义类实例的属性访问的含义(访问、赋值或者删除x.name
)。
-
object.
__getattr__
(self, name)¶ 当属性查找在通常的地方没有找到该属性时调用(例如,它既不是实例属性也没有在
self
的类树中找到)。name
为属性的名字。该方法应该返回(计算后的)属性值或者抛出一个AttributeError
异常。注意,如果属性可以通过正常的机制找到,则不会调用
__getattr__()
。(__getattr__()
和__setattr__()
之间的这种不对称是故意设计的。)这既是出于效率的原因也是因为否则的话__getattr__()
将无法访问实例的其他属性。请注意,至少对于实例变量,您可以通过不在实例属性字典中插入任何值(而是将它们插入另一个对象)来伪造总体控制。参见下文的__getattribute__()
方法以获得一种真正完全控制属性访问的方式。
-
object.
__getattribute__
(self, name)¶ 无条件地调用对类的实例实现属性访问。如果类同时定义了
__getattr__()
,那么后者将不会被调用除非__getattribute__()
显式调用它或者抛出一个AttributeError
。该方法应该返回(计算后的)值或者抛出一个AttributeError
异常。为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如object.__getattribute__(self, name)
。注意
当通过语言的语法或者内建函数的隐式调用的结果导致的查询特殊方法,这个方法可能仍然被绕开。参见特殊方法的查找。
-
object.
__setattr__
(self, name, value)¶ 尝试进行属性分配时调用。它的调用将代替普通的机制(例如,将值存储在实例的字典中)。名称是属性名称,值是要分配给它的值。
如果
__setattr__()
想赋值给一个实例属性,它应该调用基类相同名称的方法,例如,object.__setattr__(self, name, value)
。
-
object.
__delattr__
(self, name)¶ 类似
__setattr__()
但是用于属性的删除而不是赋值。它应该只有在del obj.name
对该对象有意义时才实现。
3.3.2.1. 实现描述器¶
包含以下方法的类叫做描述器类,当这种类的实例出现在属主类中(这些描述器必须在属主类的字典中或者属主类的父类的字典中)时,这些方法才会调用。在下面的例子当中, “属性”指的是名称为属主类的__dict__
中的键的属性。
-
object.
__get__
(self, instance, owner)¶ 获取属主类的属性(类属性访问)或者该类的一个实例的属性(实例属性访问)。owner始终是属主,instance是属性访问的实例,当属性通过owner访问时则为
None
。这个方法应该返回(计算后)的属性值,或者引发一个AttributeError
异常。
-
object.
__set__
(self, instance, value)¶ 设置属主类的实例instance的属性为一个新值value。
-
object.
__delete__
(self, instance)¶ 删除属主类的实例instance的属性。
__objclass__
属性用于inspect
模块,解释为指定对象定义的类(设置这个属性可以帮助在运行时刻查看动态类的属性)。对于可调用对象,它可以表示给定类型的实例(或子类)被期望或要求作为第一个位置参数(例如,CPython设置这个属性用于C实现的未绑定的方法)。
3.3.2.2. 调用描述器¶
一般情况下,描述器是一个具有“绑定行为”的对象属性,该对象的属性访问通过描述器协议覆盖:__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)
。 - 父类的绑定
- 如果
a
是一个超类
的实例,那么绑定super(B, obj).m()
搜索obj.__class__.__mro__
中B
的直接前导A
,然后调用描述器:A.__dict__['m'].__get__(obj, obj.__class__)
。
对于实例绑定,描述器调用的优先级取决于定义哪些描述器方法。描述器可以定义__get__()
、__set__()
和__delete__()
直接的任何组合。如果它没有定义__get__()
,那么属性访问将返回描述器对象本身,除非在对象的实例字典中有个值。如果描述器定义__set__()
或者/和__delete__()
,那么它是一个数据描述器;如果两个都没有定义,那么它是一个非数据描述器。通常情况下,数据描述器同时定义__get__()
和__set__()
,而非数据描述器只定义__get__()
方法。定义__set__()
和__get__()
的数据描述器始终覆盖实例字典中的定义。相反,非数据描述器可以被实例覆盖。
Python的方法(包括staticmethod()
和classmethod()
)都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。
property()
函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。
3.3.2.3. __slots__¶
默认情况下,类的实例具有用于属性存储的字典。这浪费了具有非常少的实例变量的对象的空间。创建大量实例时,空间消耗可能变得非常严重。
可以通过在类定义中定义__ slots __来覆盖默认值。__ slots __声明采用一系列实例变量,并在每个实例中保留足够的空间以保存每个变量的值。由于不为每个实例创建__ dict __,因此保存了空间。
-
object.
__slots__
¶ 可以为该类变量分配一个字符串,可迭代的,或由实例使用的变量名称的字符串序列。__ slots __为已声明的变量保留空间,并阻止对每个实例自动创建__ dict __和__ weakref __。
3.3.2.3.1. 使用__slots__的注意事项¶
- 当从不带__ slots __的类继承时,该类的__ dict __属性将始终可访问,因此子类中的__ slots __定义是无意义的。
- 如果没有__ dict __变量,则不能为实例分配__ slots __定义中未列出的新变量。尝试给没有列出的变量名赋值将引发
AttributeError
。如果需要动态地给新的变量赋值,那么可以在__slots__的声明的字符串序列中增加'__dict__'
。 - 如果没有每个实例的__ weakref __变量,定义__ slots __的类不支持对其实例的弱引用。如果需要支持弱引用,可以在__slots__声明的字符串序列中增加
'__weakref__'
。 - __slots__在类级别上实现,通过为每个变量名创建描述器(实现描述器)。结果,类属性不可以用于设置__slots__定义的实例变量的默认值;否则,该类属性将覆盖描述器的赋值。
- __ slots __声明的操作仅限于定义它的类。因此,子类将有__ dict __,除非它们还定义__ slots __(它必须只包含任何附加插槽的名称)。
- 如果一个类定义了一个在基类中定义的槽,那么由基类槽定义的实例变量是不可访问的(除了直接从基类检索它的描述器)。这使得程序的含义未定义。将来,可能会添加一个检查来防止这种情况。
- 非空的__slots__对于从“可变长度”的内建类型例如
long
、str
和tuple
继承的类不能工作。 - 任何非字符串可迭代可以分配给__ slots __。映射也可以使用;然而,在未来,可能对每个键对应的值赋予特殊的含义。
- __ class __只有当两个类都具有相同的__ slot __时,分配才起作用。
3.3.3. 自定义类的创建¶
默认情况下,新式类使用type()
构建。类定义被读取到一个单独的命名空间,然后类名称的值被绑定到type(name, bases, dict)
的结果。
类的创建过程可以通过在类定义的行传递metaclass
关键字参数来自定义,或者通过从一个包含这个参数的类继承。在下面的例子中,MyClass
和MySubclass
都是Meta
的实例:
class Meta(type):
pass
class MyClass(metaclass=Meta):
pass
class MySubclass(MyClass):
pass
在类定义中指定的任何其他关键字参数将传递到下面描述的所有元类操作。
当执行类定义时,将执行以下步骤:
- 确定适当的元类
- 类名称空间已准备好
- 类主体被执行
- 将创建类对象
3.3.3.1. 确定正确的元类¶
类定义的适当元类确定如下:
最渊源的元类从显式指定的元类(如果有的话)和所有基类的元类(即type(cls)
)中选出。最渊源的元类是所有这些候选元类的子类。如果没有候选的元类满足条件,那么类定义将以TypeError
而失败。
3.3.3.2. 准备类的命名空间¶
一旦已经识别了适当的元类,则准备类命名空间。如果元类具有__prepare__
属性,那么它以namespace = metaclass.__prepare__(name, bases, **kwds)
形式调用(其中如果有额外的关键字参数,那么它们来自类的定义)。
如果元类没有__prepare__
属性,那么类的命名空间初始化一个空的dict()
实例。
也可以看看
- PEP 3115 —— Python 3000中的元类
- 介绍
__prepare__
命名空间的钩子
3.3.3.3. 执行类的主体¶
类的主体(大体上)以exec(body, globals(), namespace)
的方式执行。与普通的exec()
调用的关键区别在于,当类定义位于一个函数中时,词法作用域允许类的主体(包括所有方法)引用当前的以及外部的作用域。
然而,即使类定义出现在函数内部,类中定义的方法仍然看不到在类scope中定义的名称。类变量必须通过实例或类方法的第一个参数来访问,且完全不能从静态方法中访问。
3.3.3.4. 创建类对象¶
类的命名空间通过执行类的主体创建完之后,通过调用metaclass(name, bases, namespace, **kwds)
创建类对象(这里传递过来的额外的关键字参数与传递给__prepare__
的相同)。
这个类对象将由零个参数形式的super()
引用。如果类体中的任何方法引用__class__
或super
,编译器将创建一个隐式闭包引用__class__
。这允许零个参数形式的super()
正确识别基于词法作用域定义的类,虽然用来发出当前调用的类或实例是基于传递给方法的第一个参数识别的。
在创建类对象之后,它将被传递给类定义中包含的类装饰器(如果有的话),并且生成的对象在本地命名空间中作为定义类绑定。
新类通过type.__new__
创建之后,作为命名空间参数的对象被拷贝到一个标准的Python字典,而原始的对象被丢弃。新拷贝的字典成为这个类对象的__dict__
属性。
也可以看看
- PEP 3135 —— 新的super
- 描述隐式的
__class__
闭包引用
3.3.3.5. 元类示例¶
元类潜在的使用场景无以计数。已经探索的一些想法包括日志记录,接口检查,自动委派,自动属性创建,代理,框架和自动资源锁定/同步。
下面是一个元类的示例,它使用collections.OrderedDict
记住类变量定义的顺序:
class OrderedClass(type):
@classmethod
def __prepare__(metacls, name, bases, **kwds):
return collections.OrderedDict()
def __new__(cls, name, bases, namespace, **kwds):
result = type.__new__(cls, name, bases, dict(namespace))
result.members = tuple(namespace)
return result
class A(metaclass=OrderedClass):
def one(self): pass
def two(self): pass
def three(self): pass
def four(self): pass
>>> A.members
('__module__', 'one', 'two', 'three', 'four')
当执行A的类定义时,该过程从调用元类的__prepare__()
方法开始,该方法返回一个空的collections.OrderedDict
。该映射记录了A的方法和属性,因为它们在类语句的主体中定义。一旦这些定义执行,顺序字典将完全填充,元类的__new__()
方法将被调用。这个方法构建新的类型,并且它将顺序字典的键保存在叫members
的属性中。
3.3.4. 自定义实例和子类的检查¶
下面的方法用于覆盖isinstance()
和issubclass()
内建函数的默认行为。
特别地,元类abc.ABCMeta
实现这些方法,目的是让抽象基类(ABCs)作为任何类或类型(包括内建类型)的”虚拟基类“,包括其它ABCs。
-
class.
__instancecheck__
(self, instance)¶ 如果实例应视为类的(直接或间接)实例,则返回true。如果有定义,则用于实现
isinstance(instance, class)
。
-
class.
__subclasscheck__
(self, subclass)¶ 如果子类应被视为类的(直接或间接)子类,则返回true。如果有定义,则用于实现
issubclass(subclass, class)
。
注意,这些方法是在类的类型(元类)上查找的。它们不能被定义为实际类中的类方法。这与对实例调用的特殊方法的查找一致,只有在这种情况下,实例本身是一个类。
也可以看看
- PEP 3119 —— 介绍抽象基类
- 包含通过
__instancecheck__()
和__subclasscheck__()
自定义isinstance()
和issubclass()
的行为的细则,该功能推动了添加抽象基类(参见abc
模块)到该语言中。
3.3.5. 模拟可调用对象¶
-
object.
__call__
(self[, args...])¶ 当实例作为函数“调用”时调用;如果定义了该方法,则
x(arg1, arg2, ...)
为x.__call__(arg1, arg2, ...)
的简写。
3.3.6. 模拟容器类型¶
可以定义以下方法来实现容器对象。容器通常是序列(例如列表或元组)或映射(如字典),但也可以表示其他容器。第一组方法用于模拟一个序列或者一个映射;不同的是,对于序列,允许的键应该是整数k,其中0 <= k < N
,N是序列或者切片对象的长度,它们定义一个范围的元素。同时建议映射类型提供keys()
, values()
, items()
, get()
, clear()
, setdefault()
, pop()
, popitem()
, copy()
和update()
方法,且行为与Python标准字典对象相似。collections
模块提供一个MutableMapping
抽象基类类帮助从一个基本集合__getitem__()
, __setitem__()
, __delitem__()
和keys()
中创建这些方法。可变序列应该提供append()
, count()
, index()
, extend()
, insert()
, pop()
, remove()
, reverse()
和sort()
,就像Python标准的列表对象。最后,序列类型应该通过定义下文描述的__add__()
, __radd__()
, __iadd__()
, __mul__()
, __rmul__()
和__imul__()
实现加法(表示连接)和乘法(表示重复);它们不应该定义其它数值操作符。建议映射和序列都实现__contains__()
方法以允许使用高效的in
操作符;对于映射,in
应该搜索映射的键;对于序列,它应该搜遍全部的值。还要进一步建议映射和序列都实现__iter__()
方法以允许遍历容器的高效迭代;对于映射,__iter__()
应该与keys()
相同;对于序列,它应该迭代全部的值。
-
object.
__len__
(self)¶ 调用它以实现内建的函数
len()
。应该返回对象的长度,一个>=
0的整数。另外,一个没有定义__nonzero__()
方法且__len__()
方法回执零的对象在布尔上下文中被认为是假。
-
object.
__length_hint__
(self)¶ 用于实现
operator.length_hint()
。应该返回对象预估的长度(可能大于或小于实际的长度)。长度必须为整数>=
0。这个方法纯粹是一种优化,永远不要求正确性。版本3.4中新增。
注意
切片只使用以下三种方法完成。像这样的调用
a[1:2] = b
转换为
a[slice(1, 2, None)] = b
等等。丢失的切片元素始终以None
填充。
-
object.
__getitem__
(self, key)¶ 实现
self[key]
这样的计算。对于序列类型,接受的键应该是整数和片段对象。注意负的索引的特殊解释(如果该类期望模拟一个序列类型)取决于__getitem__()
方法。如果key是一个不合适的类型,可能引发TypeError
;如果是一个位于序列的索引集之外的值(在负值有特殊解释之后),应该引发IndexError
。对于映射类型,如果key不存在(不在容器中),应该引发KeyError
。注意
for
循环期望非法的索引将引发一个IndexError
以允许正确地检测到序列的结束。
-
object.
__missing__
(self, key)¶ 用于字典的子类由
dict
.__getitem__()
调用以实现当key不存在时的self[key]
。
-
object.
__setitem__
(self, key, value)¶ 调用它以实现对
self[key]
的赋值。注意事项与__getitem__()
相同。如果对象支持对键的值进行更改,或者如果可以添加新键,或者对于序列(如果可以替换元素),则应该仅对映射实施此操作。对于不正确的key值,应该和__getitem__()
一样引发相同的异常。
-
object.
__delitem__
(self, key)¶ 调用以实现删除
self[key]
。注意事项与__getitem__()
相同。如果对象支持删除键,或者对于序列,如果可以从序列中删除元素,则应该仅对映射执行此操作。对于不正确的key值,应该和__getitem__()
一样引发相同的异常。
-
object.
__iter__
(self)¶ 当容器需要迭代器时,将调用此方法。这个方法应该返回一个新的迭代器对象,它可以遍历容器中的所有对象。对于映射,它应该在容器的键上迭代。
迭代器对象也需要实现该方法;它们要求返回它们自己。关于迭代器对象的更多信息,请参见迭代器类型。
-
object.
__reversed__
(self)¶ 由内建的
reversed()
调用以实现反向的迭代。它应该返回一个新的迭代器对象,以相反的顺序在容器中的所有对象上进行迭代。如果没有提供
__reversed__()
方法,内建的reversed()
将退化成使用序列协议(__len__()
和__getitem__()
)。支持序列协议的对象应该只有在它们能提供一个比reversed()
提供的实现更高效时才提供__reversed__()
。
成员测试操作符(in
和not in
)通常实现成一个序列的迭代。然而,容器对象可以提供具有更高效实现的以下特殊方法,这也不要求对象是序列。
-
object.
__contains__
(self, item)¶ 调用实现成员资格测试运算符。如果项在自身中,则应返回true,否则返回false。对于映射对象,这应该考虑映射的键而不是值或键 - 项对。
对于没有定义
__contains__()
的对象,成员测试首先通过__iter__()
尝试迭代,然后通过__getitem__()
尝试旧式的序列迭代协议, 参见该语言参考中的这一节。
3.3.7. 模拟数值类型¶
可以定义以下方法来模拟数值对象。与所实现的特定类型的数字不支持的操作(例如,非整数数字的按位操作)相对应的方法应保持未定义。
-
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__()
方法的类的实例,那么会调用x.__add__(y)
。__divmod__()
方法应该等同于使用__floordiv__()
和__mod__()
;不应该与__truediv__()
相关。注意__pow__()
应该定义成接受一个可选的第三个参数,如果想要支持内建pow()
函数的三个参数版本。如果这些方法中某一个不支持与提供的参数的操作,则应该返回
NotImplemented
。
-
object.
__radd__
(self, other)¶ -
object.
__rsub__
(self, other)¶ -
object.
__rmul__
(self, other)¶ -
object.
__rmatmul__
(self, other)¶ -
object.
__rtruediv__
(self, other)¶ -
object.
__rfloordiv__
(self, other)¶ -
object.
__rmod__
(self, other)¶ -
object.
__rdivmod__
(self, other)¶ -
object.
__rpow__
(self, other)¶ -
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则调用y.__rsub__(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)。如果未定义特定方法,则增强的分配将返回到正常方法。例如,如果 x是一个带有__iadd__()
方法的类的实例,x += y
等同于x = x.__iadd__(y)
。否则,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.
__complex__
(self)¶ -
object.
__int__
(self)¶ -
object.
__float__
(self)¶ -
object.
__round__
(self[, n])¶
-
object.
__index__
(self)¶ 调用以实现
operator.index()
和每当Python需要无损转换一个数值对象到一个整数对象的时候(例如在切片中,或者在内建的bin()
,hex()
和oct()
函数中)。有这个方法表示数值对象是一个整数类型。它必须返回一个整数。注意
为了表示一个完整的整数类型的类,当定义
__index__()
时,也应该定义__int__()
,而且两个应该返回相同的值。
3.3.8. With语句上下文管理器¶
上下文管理器是一个对象,它定义执行with
语句时将要建立的运行时上下文。上下文管理器处理用于代码块的执行的期望的运行时上下文的入口和退出。上下文管理器通常使用with
语句调用(在with语句一节描述),但也可以通过直接调用它们的方法使用。
上下文管理器的典型用途包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等。
关于上下文管理器的更多信息,请参见上下文管理器类型。
-
object.
__exit__
(self, exc_type, exc_value, traceback)¶ 退出与此对象相关的运行时上下文。参数描述导致上下文退出的异常。如果该上下文退出时没有异常,三个参数都将为
None
。如果提供了异常,并且该方法希望抑制异常(即,防止异常传播),则它应该返回一个真值。否则,在退出此方法时,将正常处理异常。
注意
__exit__()
方法不应该重新抛出传递进去的异常;这是调用者的责任。
3.3.9. 特殊方法的查找¶
对于自定义类,只有在对象的类型而不是对象的实例字典中定义时,才能保证特殊方法的隐式调用正确工作。这种行为是为什么下面的代码引发异常的原因:
>>> class C:
... pass
...
>>> c = C()
>>> c.__len__ = lambda: 5
>>> len(c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'C' has no len()
这种行为背后的原理是有大量特殊方法例如__hash__()
和__repr__()
被所有的对象包括类型对象实现。如果这些方法的隐式查找使用常规的查找过程,那么当对类型对象本身进行调用时,它们将失败:
>>> 1 .__hash__() == hash(1)
True
>>> int.__hash__() == hash(int)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' of 'int' object needs an argument
不正确地尝试以这种方式调用类的未绑定方法有时被称为“元类混淆”,并且通过在查找特殊方法时绕过实例来避免:
>>> type(1).__hash__(1) == hash(1)
True
>>> type(int).__hash__(int) == hash(int)
True
除了通过绕开实例以保证正确性之外,隐式的方法查询通常绕开__getattribute__()
方法,包括对象的元类的这个方法:
>>> class Meta(type):
... def __getattribute__(*args):
... print("Metaclass getattribute invoked")
... return type.__getattribute__(*args)
...
>>> class C(object, metaclass=Meta):
... def __len__(self):
... return 10
... def __getattribute__(*args):
... print("Class getattribute invoked")
... return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__() # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c) # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c) # Implicit lookup
10
以这种方式绕过__getattribute__()
机制给解释器内部提供重要的速度优化空间,代价是特殊方法的处理缺乏一些灵活性(特殊方法必须在类对象自身上设置,这样解释器调用能够具有一致性)。
3.4. 协程¶
3.4.1. Awaitable对象¶
awaitable对象通常实现一个__await__()
方法。async def
函数返回的Coroutine对象是awaitable的。
注意
通过types.coroutine()
或者asyncio.coroutine()
装饰的生成器返回的生成器迭代器对象也是awaitable的,但是它们没有实现__await__()
。
-
object.
__await__
(self)¶ 必须返回一个迭代器。应该用于实现awaitable的对象。例如,
asyncio.Future
实现此方法以与await
表达式兼容。
版本3.5中新增。
也可以看看
PEP 492中关于awaitable对象更多的信息。
3.4.2. Coroutine对象¶
Coroutine对象是awaitable对象。协程的执行可以通过调用__await__()
并在其结果上迭代控制。当协程执行完成并返回,迭代器引发一个StopIteration
,该异常的value
属性保存返回的值。如果协程引发异常,它是由迭代器传播。协程不应直接引发未处理的StopIteration
异常。
协程还具有以下列出的方法,类似于生成器的方法(见 生成器迭代器方法)。然而,与生成器不同,协程不直接支持迭代。
版本3.5.2中的更改︰不止一次在协程上await是一个RuntimeError
。
-
coroutine.
send
(value)¶ 开始或继续协程的执行。如果value为
None
,则相当于推进由__await__()
返回的迭代器。如果value不为None
,这方法将委托给引起协程挂起的迭代器的send()
方法。结果(返回值、StopIteration
或其它异常)与迭代__await__()
的返回值相同,上文所述。
-
coroutine.
throw
(type[, value[, traceback]])¶ 引发协程中指定的异常。这个方法委托给造成协程挂起的迭代器的
throw()
方法,如果它有这样一个方法。否则,在挂起点引发异常。结果(返回值、StopIteration
或其它异常)与迭代__await__()
的返回值相同,如上文所述。如果异常不在协程中捕获,它将传播回调用方。
-
coroutine.
close
()¶ 导致协程清理本身并退出。如果协程被挂起,这个方法首先委托给造成协程挂起的迭代器的
close
()方法,如果它有这样一种方法。然后,它在挂起点引发GeneratorExit
,造成协程立即清理本身。最后,协程被标记为完成执行,即使它从未启动过。当协程对象即将被销毁时,它们会使用上述过程自动关闭。
3.4.3. 异步迭代器 ¶
异步可迭代对象能够在其__aiter__
实现中调用调用异步代码,异步迭代器可以在其__anext__
方法中调用异步代码 。
在 async for
语句中,可以使用异步迭代器。
-
object.
__aiter__
(self)¶ 必须返回一个异步迭代器对象。
-
object.
__anext__
(self)¶ 必须返回一个awaitable,来生成迭代器的下一个值。当迭代结束的时候,应该引发一个
StopAsyncIteration
错误。
异步可迭代对象的一个示例︰
class Reader:
async def readline(self):
...
def __aiter__(self):
return self
async def __anext__(self):
val = await self.readline()
if val == b'':
raise StopAsyncIteration
return val
版本3.5中新增。
注意
版本3.5.2中的更改︰从CPython 3.5.2开始,__aiter__
可以直接返回异步迭代器。返回一个awaitable对象会导致PendingDeprecationWarning
。
在CPython 3.5.x中编写向后兼容代码的推荐方式是继续从__aiter__
返回awaitable对象。如果你想要避免PendingDeprecationWarning并使代码保持向后兼容,可以使用下面的修饰器︰
import functools
import sys
if sys.version_info < (3, 5, 2):
def aiter_compat(func):
@functools.wraps(func)
async def wrapper(self):
return func(self)
return wrapper
else:
def aiter_compat(func):
return func
例:
class AsyncIterator:
@aiter_compat
def __aiter__(self):
return self
async def __anext__(self):
...
从CPython 3.6开始,PendingDeprecationWarning
将替换为DeprecationWarning
。在CPython 3.7中,从 __aiter__
返回awaitable对象会导致RuntimeError
。
3.4.4. 异步上下文管理器¶
异步上下文管理器是一种 上下文管理器,它能够暂停执行其 __aenter__
和__aexit__
方法。
异步上下文管理者可以在async with
语句中使用。
-
object.
__aenter__
(self)¶ 这种方法在语义上类似于
__enter__()
,仅有的差异是它必须返回一个awaitable对象。
-
object.
__aexit__
(self, exc_type, exc_value, traceback)¶ 这个方法在语义上类似于
__exit__()
,仅有的差异是它必须返回一个awaitable对象。
异步上下文管理器类的示例︰
class AsyncContextManager:
async def __aenter__(self):
await log('entering context')
async def __aexit__(self, exc_type, exc, tb):
await log('exiting context')
版本 3.5中新增。
脚注
[1] | 在某些情况下,在某些可控的条件下,是可以更改对象的类型的。它一般不是好主意,因为如果它处理不正确,可能导致一些非常奇怪的行为。 |
[2] | 对于同一类型的操作数,假定如果非反射的方法(如 __add__() ) 失败,则该操作不支持,这就是为什么不调用反射方法的原因。 |