3. 数据模型

3.1. 对象、值和类型

对象是Python对数据的抽象。Python程序中的所有数据都由对象或对象之间的关系来表示。(从某种意义上说,为了符合Von Neumann的“存储程序计算机”模型,代码也由对象表示。)

每个对象由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。因此,如果一个不可变容器(如元组)包含对可变对象的引用,那么如果该可变对象发生更改,则其值将更改。

类型几乎影响对象行为的所有方面。即使对象身份的重要性也在某种意义上受到影响:对于不可变类型,计算新值的操作实际上可能会返回对具有相同类型和值的任何现有对象的引用,而对于可变对象则不允许。E.g., after a = 1; b = 1, a and b may or may not refer to the same object with the value one, depending on the implementation, but after c = []; d = [], c and d are guaranteed to refer to two different, unique, newly created empty lists. (注意c = d = []是把相同的对象赋给cd。)

3.2. 标准类型的层次结构

以下是一份Python内建类型的清单。扩展模块(用C,Java或其他语言编写,具体取决于实现)可以定义其他类型。未来版本的Python可能在这个类型层次结构中增加其它类型(例如,有理数、高效存储的整数数组,等等),虽然这种添加通常由标准库提供。

下面有些类型的描述包含一个列出“特殊属性”的段落。这些属性提供对具体实现的访问,而不是作为一般的目的使用。他们的定义可能会在未来发生变化。

None

这种类型只有一个值。有这个值的单个对象。这个对象通过内建名字None访问。它被用来表示在许多情况下缺少值,例如,它是从不显式返回任何东西的函数返回的。它的真实价值是错误的。

NotImplemented

这种类型只有一个值。有这个值的单个对象。这个对象通过内建名字NotImplemented访问。数字方法和丰富的比较方法应该返回这个值,如果他们没有实现提供的操作数的操作。(解释者然后将根据操作员尝试反映的操作或其他后退操作。)其真值是True。

更多细节参见实现算术操作

Ellipsis

这个类型有一个单独的值。这个值是个单独的对象。这个对象通过字面值...或内建名字Ellipsis访问。其真值是True。

numbers.Number

它们由数值字面值生成或者由算术操作符和内建的算术函数作为结果返回。数值对象是不可变的;一旦创建,它们的值永远不会改变。Python数字当然与数学数字密切相关,但受到计算机中数值表示的限制。

Python区分整数,浮点数和复数:

numbers.Integral

这些表示整数数学集合中的元素(正数和负数)。

有两种类型的整数:

整数(int)

它们表示的数值范围没有限制,只受限于可用的(虚拟内存)内存。对于以移位和掩码为目的的运算,长整数采用二进制的形式,负数用二进制补码形式表示,给人的错觉是一个符号位向左无限扩展的字符串。
布尔型(bool)

它们表示真值False和True。表示FalseTrue的两个对象是仅有的布尔对象。布尔类型是普通整数的子类型,布尔值的行为在几乎所有环境下分别类似0和1,例外的情况是转换成字符串的时候分别返回字符串"False""True"

整数表示规则旨在给出涉及负整数的移位和掩码操作的最有意义的解释。

numbers.Real (float)

这些代表机器级双精度浮点数。接受的范围受底层的机器体系结构(和C或者Java的实现)控制,你需要做溢出处理。Python不支持单精度浮点数;使用它的原因通常是节省处理器和内存的使用,但是相比Python中对象使用的开销是微不足道的,因此没有必要支持两种浮点数使语言变的复杂。

numbers.Complex (complex)

这些将复数表示为一对机器级双精度浮点数。浮点数也适用相同的注意事项。复数z的实部和虚部可以通过只读属性z.realz.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 >= 0i <= x < j

序列按其可变性区分:

不可变的序列

不可变序列类型的对象一旦创建就不能更改。(如果对象包含对其他对象的引用,则这些其他对象可能是可变的并且可能会被更改;但是,不可变对象直接引用的对象集合不能更改。)

以下类型是不可变的序列:

字符串

字符串是由表示Unicode码点的值组成的一个序列。U+0000 - U+10FFFF范围内的所有码点都可以在字符串中表示。Python没有char类型;字符串中个每个码点通过长度为1的字符串对象表示。内建函数ord()将一个码点从字符串形式转换为范围在0 - 10FFFF之间的一个整数;chr()0 - 10FFFF范围之间的一个整数转换为对应的长度为1的字符串对象。str.encode()可以用来使用给定的文本编码将str转换为bytesbytes.decode()可以用来实现相反的操作。

元组

元组的元素是任意的Python对象。两个或更多项目的元组由逗号分隔的表达式列表形成。通过在表达式中附加逗号,可以形成一个元素(单个元素)的元组(由于括号必须可用于表达式分组,因此表达式本身不会创建元组)。一个空元组可以由一对空括号组成。

字节

字节对象是一个不可变的数组。元素是由范围在0 <= x < 256之间的整数表示的8比特字节。字节字面值(如b'abc')和内建的函数bytes()可以用来构造字节对象。另外,字节对象可以通过decode()方法解码成字符串。

可变序列

可变序列在创建后可以更改。下标和切片表示法可以用于赋值和del(delete)语句的目标。

目前有两种内在的可变序列类型:

清单

列表中的项目是任意的Python对象。列表是通过在方括号中放置逗号分隔的表达式列表形成的。(请注意,没有特殊情况需要形成长度为0或1的列表。)

字节数组

bytearray对象是一个可变数组。它们由内建的bytearray()构造函数创建。除了可变(因此不可变)外,字节数组提供了与不可变字节对象相同的接口和功能。

扩展模块array提供另外一个可变序列类型的例子,collections模块也是。

集合类型

这些代表了无序的,有限的唯一的,不可变的对象集合。因此,它们不能被任何下标索引。然而,它们可以迭代,内建函数len()返回集合中元素的个数。集合的常见用途是快速成员测试,删除序列中的重复项,并计算数学运算,如交集,联合,差异和对称差异。

对于集合元素,与字典键相同的不变性规则适用。注意,数值类型遵循正常的数值比较规则:如果两个数字相等(例如,11.0),其中只有一个可以包含在集合中。

目前有两种内在集合类型:

这些代表了一个可变的集合。它们由内建函数set() 构造函数创建并可以在此之后通过几种方法修改,例如add()

固定集合

这些代表了一个不变的集合。它们由内建函数frozenset()构造函数创建。因为固定集合不可变且可以哈希,它可以作为另外一个集合的元素,或者作为字典的键。

映射

这些表示由任意索引集合索引的有限集合的对象。下标表示法a[k]从映射a中选择由k索引的元素;它可以用在表达式中并作为赋值或del语句的目标。内建函数len()返回映射中元素的个数。

目前有一种内部映射类型:

字典

这些代表几乎任意值索引的有限对象集。仅作为键不可接受的值的类型是包含列表或词典或其他可变类型的值,这些值是通过值而不是通过对象标识进行比较的,原因是字典的有效实现需要密钥的哈希值保持不变。用于键的数值类型遵循正常的数值比较规则:如果两个数字相等(例如,11.0),那么它们可以互换地使用来索引同一个字典条目。

字典是可变的;它们可以通过{...}表示法创建(参见字典的显示一节)。

扩展模块dbm.ndbmdbm.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()的类,xC的一个实例,调用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 withasync 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方法解析顺序,即使存在具有通向共同祖先的多个继承路径的“菱形”继承结构,其行为也是正确的。有关Python使用的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__()方法,那么会调用这个方法而不是直接更新实例的字典。

如果类实例具有某些特殊名称的方法,它们可以伪装成数字,序列或映射。参见特殊方法的名字 一节。

特殊的属性:__dict__是属性字典;__class__是实例的类。

I/O对象(也称为文件对象)

文件对象表示打开的文件。有各种快捷方式可以创建文件对象:内建函数open()os.popen()os.fdopen()和socket对象的makefile()方法(或者其它扩展模块提供的函数或方法)。

sys.stdinsys.stdoutsys.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 版本中,还使用过0x100x1000标志位。

co_flags中的其它位保留作内部使用。

如果代码对象表示一个函数,co_consts中的第一个元素是函数的文档字符串,如果没有定义则为None

Frame 对象

框架对象表示执行框架。它们可能发生在追溯对象中(见下文)。

特殊的只读属性:f_back指向堆栈中的前一帧(朝调用的方向),如果这是堆栈最底部的帧则为Nonef_code是帧中正在执行的代码对象;f_locals是用于查询局部变量的字典;f_globals用于全局变量;f_builtins用与内建的(固有的)名字;f_lasti给出精确的指令(它是代码对象的字节码字符串的一个索引)。

特殊的可写属性:f_trace,如果不为None,则是源代码开始调用的函数(用于调试器);f_lineno帧当前的行号 — 从跟踪函数的内部写入这个值将跳转到指定的行(只用于最底部的帧)。调试器可以通过写入f_lineno来实现Jump命令(又名Set Next Statement)。

框架对象支持一种方法:

frame.clear()

该方法清除对帧保存的局部变量的所有引用。另外,如果框架属于发电机,则发电机已完成。这有助于中断涉及框架对象的引用周期(例如捕获异常并存储其追溯以备后用时)。

如果帧当前正在执行,则引发RuntimeError

版本3.4中新增。

追溯对象

跟踪对象表示异常的堆栈跟踪。发生异常时会创建一个追溯对象。当搜索异常处理程序展开执行堆栈时,在每个展开级别上,将在当前回溯前插入回溯对象。当输入异常处理程序时,堆栈跟踪可用于程序。(参见try 语句一节。)它可以通过sys.exc_info()返回的元组的第三个元素得到。当程序没有包含合适的处理函数时,栈回溯被写到(格式很漂亮)标准错误流;如果解释器是交互式的,用户还可以通过sys.last_traceback得到它。

特殊的只读属性:tb_next是栈回溯中的下一个层级(朝异常发生的那个帧的方向),如果没有下一级则为Nonetb_frame指向当前层级的执行帧;tb_lineno给出异常发生的行号;tb_lasti指示精确的指令。如果异常发生在没有匹配的except字句或者带有finally字句的try语句中,回溯中行号和最后的指令可能与帧对象的行号不同。

切片对象

切片对象用于表示__getitem__()方法产生的切片。它们还可以由内建的slice()函数创建。

特殊的只读属性:start指下边界;stop指上边界;step指步长;每个属性在省略的情况下都为None这些属性可以有任何类型。

切片对象支持一种方法:

slice.indices(self, length)

此方法采用单个整数参数长度,并计算关于切片对象在应用于长度项目序列时将描述的切片的信息。它返回一个包含三个整数的元组;它们分别是startstop索引和切片的步长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__()是一个静态方法(这是个特列,所以你不需要显式地声明),以实例的类为第一个参数。剩下的参数是传递给对象构造函数表达式(对类的调用)的参数。__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中的回溯使得调用栈的帧一直存活)。第一种情形只有显式地破坏这个环才能修复;第二种情形可以通过释放对回溯对象不在有用的引用来解决,第三种情形可以通过保存Nonesys.exc_traceback中解决。当循环垃圾回收器启用时,会检测并清除垃圾循环引用(默认是打开的)。参见gc模块的文档以获得关于这个主题的更多信息。

警告

由于调用__del__()方法的环境是不确定的,在它们执行时的异常会被忽略,而会打印警告到sys.stderr另外,当因为正在删除一个模块而调用__del__()时(例如,当程序执行完毕),__del__()方法引用的其它目标可能已经被删除或者正在销毁的过程中(例如,导入的机器正在关机)。由于这个原因,__del__()方法应该干需要的尽量少的事情以维持外部的不变量。从1.5版开始,Python保证名字以单个下划线开始的全局变量在其它全局变量删除前从它们的模块中删除;如果没有其它对这样的全局变量的引用操作,这可能帮助假定在调用__del__()方法时刻导入的模块仍然可以访问。

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.__bytes__(self)

bytes()调用来计算一个对象的字节字符串的表示形式。它应该返回一个bytes对象。

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,如果给出的一对参数没有实现该操作。按照惯例,成功的比较返回FalseTrue然而,这些方法可以返回任意值,所以如果比较操作符在布尔环境中使用(例如,在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__(), 因为可哈希的容器实现要求对象的哈希值是不可变的(如果对象的哈希值改变,那么它将在错误的哈希桶中)。

用户自定义的类默认具有__ed__()__hash__()方法;有了它们,所有的对象比较起来都不相等(除非和它们自己比较)且x.__hash__()返回一个从恰当的值,使得x == y暗含x is yhash(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位版本间的差别非常大)。

另见PYTHONHASHSEED

版本3.3中的变化:默认启用将哈希随机化。

object.__bool__(self)

用来实现真值测试和内建的bool()操作;应该返回FalseTrue当这个方法没有定义时,如果__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)

在试图进行属性分配时调用。它的调用将代替普通的机制(例如,将值存储在实例的字典中)。name是属性名称,是要分配给它的值。

如果__setattr__()想赋值给一个实例属性,它应该调用基类相同名称的方法,例如,object.__setattr__(self, name, value)

object.__delattr__(self, name)

类似__setattr__()但是用于属性的删除而不是赋值。它应该只有在del obj.name对该对象有意义时才实现。

object.__dir__(self)

当在对象上调用dir()时调用。必须返回一个序列。dir()将返回的序列转换成一个列表并对它排序。

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 __ ¶ T0>

默认情况下,类的实例有一个字典用于存储属性。这对于具有很少实例变量的对象会浪费空间。当创建大量实例时,空间消耗可能会变得尖锐。

可以通过在类定义中定义__ 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 __声明的操作仅限于定义它的类。因此,除非子类还定义__ slots __(它只能包含任何附加槽的名称),否则子类将具有__ dict __
  • 如果一个类定义了一个也在基类中定义的槽,则由基类槽定义的实例变量是不可访问的(除了直接从基类中检索它的描述符)。这使得程序的含义未定义。在将来,可能会添加一个检查来防止这种情况发生。
  • 非空的__slots__对于从“可变长度”的内建类型例如longstrtuple继承的类不能工作。
  • 任何非字符串可迭代都可以分配给__ slots __映射也可以使用;然而,在未来,可能对每个键对应的值赋予特殊的含义。
  • __ class __赋值仅在两个类具有相同的__ slots __时才起作用。

3.3.3. 自定义类的创建

默认情况下,新式类使用type()构建。类定义被读取到一个单独的命名空间,然后类名称的值被绑定到type(name, bases, dict)的结果。

类的创建过程可以通过在类定义的行传递metaclass关键字参数来自定义,或者通过从一个包含这个参数的类继承。在下面的例子中,MyClassMySubclass都是Meta的实例:

class Meta(type):
    pass

class MyClass(metaclass=Meta):
    pass

class MySubclass(MyClass):
    pass

在类定义中指定的任何其它关键字参数都将传递到下面描述的所有元类操作。

当执行类定义时,将执行以下步骤:

  • 确定适当的元类
  • 准备类名称空间
  • 执行类的主体
  • 创建类对象

3.3.3.1. 确定正确的元类

类定义的正确元类的确定如下所述:

  • 如果没有给出基类和显式的元类,那么使用type()
  • 如果给出显式的元类,而它type()的实例,那么它直接用做元类。
  • 如果给出type()的实例作为显式的元类,或者定义了基类,那么使用最渊源的元类。

最渊源的元类从显式指定的元类(如果有的话)和所有基类的元类(即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()调用的关键区别在于,当类定义位于一个函数中时,词法作用域允许类的主体(包括所有方法)引用当前的以及外部的作用域。

但是,即使在函数内部发生类定义时,类中定义的方法仍然无法看到在类作用域中定义的名称。类变量必须通过实例或类方法的第一个参数来访问,且完全不能从静态方法中访问。

3.3.3.4. 创建类对象

类的命名空间通过执行类的主体创建完之后,通过调用metaclass(name, bases, namespace, **kwds)创建类对象(这里传递过来的额外的关键字参数与传递给__prepare__的相同)。

这个类对象将由零个参数形式的super()引用。如果类体中的任何方法引用__class__super,编译器将创建一个隐式闭包引用__class__这允许零个参数形式的super()正确识别基于词法作用域定义的类,虽然用来发出当前调用的类或实例是基于传递给方法的第一个参数识别的。

创建类对象后,它将被传递给包含在类定义中的类装饰器(如果有的话),并将生成的对象作为定义的类绑定到本地名称空间中。

新类通过type.__new__创建之后,提供给 namespace 参数的对象被拷贝到一个标准的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 < NN是序列或者切片对象的长度,它们定义一个范围的元素。同时建议映射类型提供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]这样的计算。对于序列类型,接受的 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__()

成员测试操作符(innot in)通常实现成一个序列的迭代。但是,容器对象可以提供以下特殊的方法和更高效的实现,这也不需要将对象作为序列。

object.__contains__(self, item)

被称为实施会员测试运营商。如果item位于self中,则返回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])

调用以实现内建函数complex(), int(), long()float()应该返回适​​当类型的值。

object.__index__(self)

调用以实现operator.index()和每当Python需要无损转换一个数值对象到一个整数对象的时候(例如在切片中,或者在内建的bin(), hex()oct()函数中)。有这个方法表示数值对象是一个整数类型。它必须返回一个整数。

注意

为了表示一个完整的整数类型的类,当定义__index__()时,也应该定义__int__() ,而且两个应该返回相同的值。

3.3.8. With语句上下文管理器

上下文管理器是一个对象,它定义执行with语句时将要建立的运行时上下文。上下文管理器处理用于执行代码块的期望的运行时上下文的入口以及退出。上下文管理器通常使用with语句调用(在with语句一节描述),但也可以通过直接调用它们的方法使用。

上下文管理器的典型用途包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等。

关于上下文管理器的更多信息,请参见上下文管理器类型

object.__enter__(self)

输入与此对象相关的运行时上下文。如果存在的话,with语句将绑定该方法的返回值到该语句的as子句中指定的目标。

object.__exit__(self, exc_type, exc_value, traceback)

退出与此对象相关的运行时上下文。参数描述导致上下文退出的异常。如果该上下文退出时没有异常,三个参数都将为None

如果提供了一个异常,并且该方法希望抑制该异常(即防止它被传播),它应该返回一个真值。否则,在退出此方法后,异常将被正常处理。

注意__exit__()方法不应该重新抛出传递进去的异常;这是调用者的责任。

也可以看看

PEP 343 —— “with”语句
Pythonwith语句的说明、背景和示例。

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)

开始或继续协程的执行。如果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__()) 失败,则该操作不支持,这就是为什么不调用反射方法的原因。