python基础教程之数据模型

python基础教程之数据模型


3. 数据模型


3.1. 对象,值和类型

对象 是Python 对数据的抽象。Python程序中所有数据都由对象或对象之间的关系表示。(合理且与冯.诺依曼的“存储程序计算机”模型一致,代码也由对象表示。)

每个对象由标识、类型以及值组成。对象一旦建立,它的标识永远不会改变;你可以认为它是该对象在内存中的地址。 is’操作符用来比较两个对象的标识是否一致; id() 函数用来返回对象标识的整数形式。

CPython实现细节: 在CPython中,id(x) 返回x 的内存地址。

对象的类型决定对象支持的操作(例如,它是否具有长度?)同时还定义该类型的对象可能具有的值。type() 函数返回对象的类型(类型本身也是一个对象)。与ID 一样,对象的类型 也是不可以修改的。[1]

某些对象的值可以改变。值可以改变的对象称为可变的一旦建立,值就不可以改变的对象称为不可变的(包含可变对象引用的不可变容器对象在可变对象改变时是可以改变的;但容器仍被看作是不可变的,因为它所包含的对象集合是不能变的。 所以不可变对象与值不可变不是完全一样的,它更加微妙。)一个对象的可变性由它的类型决定; 例如,数值、字符串和元组是不可变的,而字典和列表是可变的。

对象不可以显式地销毁;但是当它们不可用时可能被当作垃圾回收。具体的实现可以推迟垃圾回收或完全忽略它 — 这是垃圾回收如何实现的质量问题,只要依然能访问的对象不被回收。

CPython实现细节: CPython 当前使用引用计数机制与(可选的)循环连接垃圾延迟检测机制,一旦对象变得不可访问,它将收集其中大部分,但是不保证收集包含循环引用的垃圾。参考gc 模块的文档可以获得控制循环垃圾回收的信息。其它实现的行为与之不同并且CPython 的实现将来也可能会变化。不要依赖对象不可访问后会立即终结(所以你应该始终显式关闭文件)。

注意使用具体实现的跟踪和调试工具可能会保持正常情况下可以回收的对象一直存活。 还要注意使用‘tryexcept’语句捕获异常也可能保持对象一直存活。

有些对象包含“外部”资源的引用,例如打开的文件或窗口。 可以理解在对象被当作垃圾回收时这些资源也被释放,但因为垃圾回收不保证一定发生,这样的对象也提供显式的方法释放外部资源,通常是close()方法。强烈建议程序显式关闭这些对象。tryfinally’ 语句和 ‘with‘ 语句提供了便利的方式来实现。

有些对象包含其它对象的引用;它们叫做容器容器的例子有元组,列表和字典。引用是容器的值的一部分。 大多数情况下,当我们谈到一个容器的值时,我们是指值,而不是所包含的对象的ID;然而,当我们谈论容器对象的可变性的时候,就只是指被直接包含的对象的ID。因此,如果一个不可变对象(如元组)包含了一个可变对象的引用,那么当这个可变对象的值改变时它的值也发生改变。

对象的类型几乎影响对象的所有行为。在某种意义上甚至重要到影响对象的识别:对于不可变对象,计算新值的运算符可能实际上返回的是一个已存在的具有相同类型和值的对象的引用,而对于可变对象,这是不允许的。 例如,在a = 1; b = 1之后,ab可能是或者可能不是引用同一个值为1的对象,这依赖于实现,但c = []; d = []之后,cd可以保证是引用两个不同的、唯一的、新创建的空列表。 (注意c = d = []是把相同的对象赋给cd。)


3.2. 标准类型层级

以下是一份Python 内建类型的清单。扩展模块(无论是用C、Java还是用其它语言编写,依赖于具体实现)可以定义额外的类型。未来版本的Python 可能在这个类型层次结构中增加其它类型(例如:有理数、高效存储的整数数组,等等),尽管这些增加的类型通常在标准库中提供。

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

None

这种类型只有一个值。只有一个对象具有这个值。这个对象通过内建名字None访问。 它在许多情况下用来表示没有值,例如,没有显式返回任何内容的函数会返回它。 它的真值为假。

NotImplemented

这种类型只有一个值。只有一个对象具有这个值。这个对象通过内建名字NotImplemented访问。如果数值方法和复杂的比较方法没有为提供的操作数实现某种运算,它们可能返回这个值。 (解释器会尝试反射的操作,或者其它退化的操作,依赖于具体的运算符。)它的真值为真。

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

Ellipsis

这种类型只有一个值。只有一个对象具有这个值。这个对象通过字面量... 或内建名字Ellipsis 访问。它的真值为真。

numbers.Number

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

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

numbers.Integral

它们表示数学上的整数集中的元素(包括正数和负数)。

有两种类型的整数:

Integers (int)

整数表示的数值范围没有限制,只受限于可用的(虚拟内存)内存。对于以移位和掩码为目的的运算,将假设整数为补码表示,这时负数的补码将像是符号位一直延伸至最左端。
Booleans (bool)

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

整数表示法的规则意在让负数的移位和掩码运算具有最有意义的解释。

numbers.Real (float)

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

numbers.Complex (complex)

这种类型以一对机器级别的双精度浮点数表示复数。单精度浮点数同样可以用于复数类型。复数z的实部和虚部可以通过只读属性z.realz.imag获得。

序列

这种类型表示有限的顺序集合,用非负数索引。内建的函数len() 返回序列的元素个数。当序列的长度为n时,索引集合包含数字0, 1, …, n-1。序列a的元素i 的选择使用a[i]

序列也支持切片:a[i:j]选择索引k满足i <= k < j的所有元素。作为表达式使用的时候,切片是一个相同类型的序列。这隐含着索引会从零开始重新计数。

某些序列还支持带有第三个“步长”参数的“扩展切片”: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 的任何对象。两个或多个元素的元组由逗号分隔的一连串表达式形成。一个元素的元组(单元素集)可以在一个表达式的后面附加一个逗号形成(一个表达式自身不会形成一个元组,因为圆括号必须可以用来分组表达式)。 一个空的元组可以由一个空的圆括号对形成。

Bytes

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

可变序列

可变序列在生成之后可以修改。下标和切片表示法可以用于赋值和del (delete)语句的对象。

当前有两种内建的可变序列类型:

列表

列表的对象可以是Python 任何对象。列表由在方括号中放置一个逗号分隔的一连串表达式形成。(注意生成长度为0或1的列表没有特殊的情形。)

Byte Arrays

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

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

集合类型

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

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

当前有两种内建的集合类型:

集合

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

固定集合

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

映射

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

当前只有一个内建映射类型:

字典

这种类型表示几乎可以由任何值索引的有限对象集合。仅有的不可以作为键值的是包含列表或者字典或者其它可变类型的值(这些类型通过值而不是对象ID比较),原因是字典的高效实现要求键的哈希值保持常量。 注意,作为键的数值类型遵循正常的数值比较规则:如果两个数字相等(例如,11.0),那么它们可以互换地使用来索引同一个字典入口。

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

扩展模块dbm.ndbmdbm.gnu 提高映射类型的其它例子,类似collections 模块。

可调用类型

这是一种可以使用函数调用操作(参考Calls一节)的类型:

用户定义的函数

用户定义的函数对象由函数定义创建(参见函数定义一节)。它调用时参数列表的元素个数应该和函数的形式参数列表相同。

特殊属性:

属性 含义  
__doc__ 函数的文档字符串,如果没有就为None;不会被子类继承 可写
__name__ 函数的名字 可写
__qualname__

函数的qualified name

版本3.3 中新增。

可写
__module__ 函数定义所在的模块名,如果没有就为None 可写
__defaults__ 为具有默认值的参数保存默认参数值的元组,如果没有参数具有默认值则为None 可写
__code__ 表示编译后的函数体的代码对象。 可写
__globals__ 保存函数全局变量的字典的引用 — 函数定义所在模块的全局命名空间。 只读
__dict__ 支持任意函数属性的命名空间。 可写
__closure__ None 或者包含函数自由变量绑定的元组。 只读
__annotations__ 一个包含参数annotation 的字典。字典的键为参数的名字和'return',它表示返回值的annotation。 可写
__kwdefaults__ 一个包含只是关键字参数默认值的字典。 可写

大部分标有“可写”的属性会检查所赋值的类型。

函数对象同样支持获取和设置任意属性,这可以用来附加元数据到函数中。常规属性可以用点号表示法获取和设置。注意当前的实现只在用户定义的函数上支持函数属性。 未来可能支持内建函数上的函数属性。

函数定义的更多信息可以从它的代码对象中获取;参见下面内部类型的描述。

实例方法

实例方法将类、类的实例和任何可调用对象(通常是一个用户定义的函数)结合起来。

特殊的只读属性:__self__指类实例对象,__func__指函数对象; __doc__指方法的文档(与__func__.__doc__)相同); __name__指方法的名字(与__func__.__name__一样); __module__指方法定义所在模块的名字,如果没有则为None

方法也支持访问(但不能设置)底层函数对象的任何函数属性。

用户定义的方法对象在获取类的一个属性的时候创建(可能通过类的一个实例),如果那个属性是用户定义的函数对象或者一个类方法对象。

如果是通过从类的某个实例中获取用户定义的方法创建,它的__self__是那个实例,且方法对象称为绑定的。 新的方法的__func__ 属性为原始的函数对象。

如果用户定义的方法是通过从类或者实例中获取另外一个方法对象创建,新的实例的__func__ 属性不是原始的方法对象而是它的__func__ 属性,除此之外其行为和处理函数对象时一样。

如果用户定义的方法对象通过从类或者实例中获取类方法对象创建,它的__self__ 属性为类本身,且它的__func__ 属性为类方法底层的函数对象。

当调用绑定的用户定义方法时,调用的是底层的函数(__func__),同时在参数列表的前面插入类的实例(__self__)。 例如,当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 异常且迭代器将到达要返回的数据集合的末尾。

内建的函数

内建的函数对象是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 语句(参见import)或调用importlib.import_module() 和内建的__import__() 函数。模块对象有一个用字典对象实现的命名空间(这就是模块中定义的函数的__globals__ 属性引用的字典)。属性的引用被转换成查询这个字典,例如m.x 等同于m.__dict__["x"]模块对象不包含初始化模块的代码对象(因为一旦初始化完成,它不会再需要了)。

属性的赋值会更新模块的命名空间字典,例如m.x = 1 等同于m.__dict__["x"] = 1

特殊的只读属性:__dict__ 指模块字典对象形式的命名空间。

CPython实现细节: 由于CPython 清除模块字典的方式,当模块离开作用域之外时,模块的字典将被清除即使字典仍然被引用。为了避免这一行为,复制一份模块的字典或者在直接使用模块字典的时候把模块保留着。

预定义的(可写)属性:__name__ 为模块的名字; __doc__ 指模块的文档字符串,如果没有则为None__file__ 为模块加载的文件路径,如果它是从文件中加载的话。 某些类型的模块可能没有__file__ 属性,例如与解释器静态连接的C 模块; 对于从共享的库中动态加载的扩展模块,它是共享的库文件的路径。

自定义的类

自定义的类一般由类定义创建(参见类的定义 一节)。 类有一个用字典对象实现的命名空间。类属性的引用被转换为这个字典的查询,例如,C.x 被转换为C.__dict__["x"](尽管有若干特别的钩子允许其它方式定位属性)。 当属性名在字典中没有找到时,会继续在基类中搜索。基类的搜索使用C3 方法解析顺序,它的行为在即使出现‘diamond’继承时也是正确的,这时有多个继承路径可以回溯到同一个祖先。 新式类使用的C3 MRO 的更多细节可以在2.3 版的文档中找到 http://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 对象(或者叫文件对象)

文件对象t 表示一个打开的文件。文件对象可以由内建函数open()os.popen()os.fdopen() 和socket 对象的makefile()方法创建(或者其它扩展模块提供的函数或方法)。

sys.stdinsys.stdoutsys.stderr 文件对象分别初始化为解释器的标准输入、输出和错误流;它们都以文本模式打开,因此遵循io.TextIOBase 抽象基类定义的接口。

内部类型

解释器内部使用的一些类型会暴露给用户。它们的定义可能跟随解释器以后的版本变化,但是为了完整性,这里还是会提及。

代码对象

代码对象表示byte-compiled 的可执行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

帧对象

帧对象表示执行的帧。它们可以出现在回溯对象中(参见下面)。

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

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

帧对象支持一个方法:

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)

这个方法带有一个整数参数length,计算扩展切片的信息,如果切片对象应用到一个长度为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的引用达到零时调用。某些常见的情况可能阻止一个对象的引用变成零:对象间的循环引用(例如,一个双向链表或者一个具有指向父亲和儿子指针的树数据结构);a reference to the object on the stack frame of a function that caught an exception (the traceback stored in sys.exc_info()[2] keeps the stack frame alive); or a reference to the object on the stack frame that raised an unhandled exception in interactive mode (the traceback stored in sys.last_traceback keeps the stack frame alive). 第一种情形只有显式地破坏这个换才能修复;the second can be resolved by freeing the reference to the traceback object when it is no longer useful, and the third can be resolved by storing None in sys.last_traceback. Circular references which are garbage are detected and cleaned up when the cyclic garbage collector is enabled (it’s on by default). Refer to the documentation for the gc module for more information about this topic.

警告

由于调用__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() 和内建函数format() 以及print()语句调用以计算一个对象的“非正式的”字符串表示。 返回值必须是一个字符串对象。

object.__repr__()不同的是__str__()不需要是一个合法的Python 表达式:可以使用更合适或者简明的表示。

内建类型object 的默认实现是调用object.__repr__()

object.__bytes__(self)

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

object.__format__(self, format_spec)

format() 内建函数(以及类strstr.format() 方法)调用以返回对象的“格式化”字符串表示。format_spec 参数是一个包含格式选项的字符串。format_spec 参数的解释取决于实现__format__() 的类型,大部分类将格式托管给一个内建的类型或者使用一个类似的格式选项。

标准格式的语法参见Format Specification Mini-Language

返回值必须是一个字符串对象。

版本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()来决定结果是真还是假。

比较操作之间没有隐含的关系。x==y这样的事实并不暗含x!=y为假。于是,当定义__eq__()时,还应该定义__ne__()以便操作符与预期的行为一致。See the paragraph on __hash__() for some important notes on creating hashable objects which support custom comparison operations and are usable as dictionary keys.

这些方法没有参数交换后的版本(用于左侧参数不支持该操作单右侧参数支持时);当然,__lt__()__gt__()互为对方的反射,__le__()__ge__()互为对方的反射,__eq__()__ne__()是它们自己的反射。

多元比较方法的参数永远不会被强制转换。

若要从一个单独的根操作自动产生排序的操作,请参阅functools.total_ordering()

object.__hash__(self)

由内建函数hash()调用和用于哈希后的集合成员的操作,包括set, frozensetdict__hash__()应该返回一个整数。唯一要求的性质是比较起来相等的对象具有相同的哈希值;it is advised to somehow mix together (e.g. using exclusive or) the hash values for the components of the object that also play a part in comparison of objects.

注意

hash() truncates the value returned from an object’s custom __hash__() method to the size of a Py_ssize_t. This is typically 8 bytes on 64-bit builds and 4 bytes on 32-bit builds. If an object’s __hash__() must interoperate on builds of different bit sizes, be sure to check the width on all supported builds. An easy way to do this is with python -c "import sys; print(sys.hash_info.width)"

如果一个类没有定义__eq__()方法,它也不应该定义__hash__()操作;如果它定义了__eq__()方法但没有定义__hash__(),它的实例不可以用于哈希集合中。 如果一个类定义的是可变对象且实现了__eq__()方法,那么它不应该实现__hash__(), 因为可哈希的集合实现要求对象的哈希值是不可变的(如果对象的哈希值改变,那么它将在错误的哈希桶中)。

用户自定义的类默认具有__eq__()__hash__()方法; 有了它们,所有的对象比较起来都不相等(除非和它们自己比较)且x.__hash__()返回一个合适的值使得x == y 表示x is yhash(x) == hash(y)

A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None. When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError when a program attempts to retrieve their hash value, and will also be correctly identified as unhashable when checking isinstance(obj, collections.Hashable).

If a class that overrides __eq__() needs to retain the implementation of __hash__() from a parent class, the interpreter must be told this explicitly by setting __hash__ = <ParentClass>.__hash__.

If a class that does not override __eq__() wishes to suppress hash support, it should include __hash__ = None in the class definition. A class which defines its own __hash__() that explicitly raises a TypeError would be incorrectly identified as hashable by an isinstance(obj, collections.Hashable) call.

注意

By default, the __hash__() values of str, bytes and datetime objects are “salted” with an unpredictable random value. Although they remain constant within an individual Python process, they are not predictable between repeated invocations of Python.

This is intended to provide protection against a denial-of-service caused by carefully-chosen inputs that exploit the worst case performance of a dict insertion, O(n^2) complexity. See http://www.ocert.org/advisories/ocert-2011-003.html for details.

Changing hash values affects the iteration order of dicts, sets and other mappings. Python has never made guarantees about this ordering (and it typically varies between 32-bit and 64-bit builds).

See also PYTHONHASHSEED.

Changed in version 3.3: Hash randomization is enabled by default.

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是属性的名字,value是将要赋给它的值。

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

object.__delattr__(self, name)

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

object.__dir__(self)

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


3.3.2.1. 实现描述器

The following methods only apply when an instance of the class containing the method (a so-called descriptor class) appears in an owner class (the descriptor must be in either the owner’s class dictionary or in the class dictionary for one of its parents). In the examples below, “the attribute” refers to the attribute whose name is the key of the property in the owner class’ __dict__.

object.__get__(self, instance, owner)

Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). owner is always the owner class, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner. This method should return the (computed) attribute value or raise an AttributeError exception.

object.__set__(self, instance, value)

Called to set the attribute on an instance instance of the owner class to a new value, value.

object.__delete__(self, instance)

Called to delete the attribute on an instance instance of the owner class.

The attribute __objclass__ is interpreted by the inspect module as specifying the class where this object was defined (setting this appropriately can assist in runtime introspection of dynamic class attributes). For callables, it may indicate that an instance of the given type (or a subclass) is expected or required as the first positional argument (for example, CPython sets this attribute for unbound methods that are implemented in C).


3.3.2.2. Invoking Descriptors

一般情况下,描述器是一个具有“绑定行为”的对象属性,它的属性访问被描述器协议中的方法:__get__(), __set__()__delete__()覆盖。如果对象定义了这些方法中的任何一个,则称该对象是一个描述器。

属性访问的默认行为是从一个对象的字典中获取、设置或删除属性。例如,a.x的查找链从a.__dict__['x']开始,然后是type(a).__dict__['x'],接着继续搜寻出元类以外的type(a)的基类。

然而,如果要查找的值是一个定义了某个描述器方法的对象,那么Python可能覆盖默认的行为而调用描述器方法。它在优先链中发生的位置取决于定义了哪个描述器方法已经它们是如何调用的。

描述器调用的起点是一个绑定,a.x参数如何组装取决于a

Direct Call
The simplest and least common call is when user code directly invokes a descriptor method: x.__get__(a).
Instance Binding
If binding to an object instance, a.x is transformed into the call: type(a).__dict__['x'].__get__(a, type(a)).
Class Binding
If binding to a class, A.x is transformed into the call: A.__dict__['x'].__get__(None, A).
Super Binding
If a is an instance of super, then the binding super(B, obj).m() searches obj.__class__.__mro__ for the base class A immediately preceding B and then invokes the descriptor with the call: A.__dict__['m'].__get__(obj, obj.__class__).

对于实例绑定,描述器调用的优先级取决于定义的描述器方法。描述器可以定义__get__()__set__()__delete__()的任何组合。如果它没有定义__get__(),那么访问该属性将返回描述器对象自己除非该对象实例的字典中有一个值。如果描述器定义了__set__()和/或__delete__(),那么它是一个数据描述器;如果都没有定义,则是一个非数据描述器。通常,数据描述器__get__()__set__()两个都定义,而非数据描述器值定义__get__()方法。定义了__set__()__get__()的数据描述器始终覆盖实例字典中的重复定义。相反,非数据描述器可以被实例覆盖。

Python的方法(包括staticmethod()classmethod())实现为非数据描述器。因此,实例可以重新定义并覆盖这些方法。这允许相同类的每个实例个体获得与其它实例不同的行为。

property()函数实现为数据描述器。因此,实例不可以覆盖property的行为。


3.3.2.3. __slots__

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

该默认行为可以通过在类定义中定义__slots__覆盖。 __slots__声明接受一个实例变量的序列并且在每个实例中只保留为每个变量保存一个值的空间。空间被省下来了是因为不会为每个实例创建__dict__

object.__slots__

这个类变量可以赋值为一个字符串、可迭代对象或者由实例使用的变量名组成的字符串序列。__slots__将保留空间给声明的变量且防止为每个实例自动创建__dict____weakref__

3.3.2.3.1. Notes on using __slots__
  • 如果继承自一个没有__slots__的类,该类的__dict__属性将始终可以访问,所以在子类中定义__slots__毫无意义。
  • 没有__dict__变量,实例不可以给不在__slots__中定义的新变量赋值。尝试给没有列出的变量名赋值将引发AttributeError如果需要动态地给新的变量赋值,那么可以在__slots__的声明的字符串序列中增加'__dict__'
  • 因为每个实例都没有__weakref__变量,定义__slots__ 的类不支持对其实例的弱引用。如果需要支持弱引用,可以在__slots__声明的字符串序列中增加'__weakref__'
  • __slots__在类级别上实现,通过为每个变量名创建描述器(Implementing Descriptors)。结果,类属性不可以用于设置__slots__定义的实例变量的默认值;否则,该类属性将覆盖描述器的赋值。
  • __slots__定义的动作只限于它所定义的类。结果,字典将具有__dict__,除非它们也定义了__slots__(必须只能包含额外的slots名称)。
  • 如果类定义了一个在基类中定义了的slot,基类slot定义的实例变量将不可访问(除非直接从基类获取它的描述器)。这致使程序的含义无法定义。在未来,可能会增加一个检查来防止这个行为。
  • 非空的__slots__对于从“可变长度”的内建类型例如longstrtuple继承的类不能工作。
  • 非字符串形式的可迭代类型可以赋值给__slots__映射也可以使用;然而,在未来,可能对每个键对应的值赋予特殊的含义。
  • __class__赋值只有在两个类具有相同的__slots__是才工作。

3.3.3. 自定义类的创建

默认情况下,类的创建使用type()类的主体部分在新的命名空间中执行,然后将类的名称绑定到type(name, bases, namespace) 的结果作为局部变量。

类的创建过程可以通过在类定义的那一行中传递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 – Metaclasses in 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() 能够在词法上正确识别正在定义的类,而用于调用的类或实例是基于传递给方法的第一个参数识别的。

类对象创建之后,它被传递给类定义中包含的装饰器,最终的结果对象被绑定到局部命名空间作为定义好的类。

请参阅

PEP 3135 – New 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. Customizing instance and subclass checks

下面的方法用于覆盖内建函数isinstance()issubclass()的默认行为。

In particular, the metaclass abc.ABCMeta implements these methods in order to allow the addition of Abstract Base Classes (ABCs) as “virtual base classes” to any class or type (including built-in types), including other ABCs.

class.__instancecheck__(self, instance)

如果instance应该被认为是class的(直接或间接)实例,返回真。如果有定义,则在isinstance(instance, class)时调用。

class.__subclasscheck__(self, subclass)

如果subclass应该被认为是class的(直接或间接)子类。如果有定义,则在issubclass(subclass, class)时调用。

注意这些方法在类的类型(元类)上查找。它们不可以在真实类中定义为类方法。这与在实例上调用特殊方法的查找一致,只有在这种情况下实例本身也是一个类。

请参阅

PEP 3119 – Introducing Abstract Base Classes
包括通过__instancecheck__()__subclasscheck__()定制isinstance()issubclass()行为的说明,该功能推动了添加抽象基类(参见abc模块)到该语言中。

3.3.5. 模拟可调用对象

object.__call__(self[, args…])

当实例作为函数“调用”时调用;如果定义了该方法,则x(arg1, arg2, ...)x.__call__(arg1, arg2, ...)的简写。


3.3.6. Emulating container types

可以定义下面的方法来实现容器对象。容器类型通常是序列(例如列表或元组)或映射(比如字典),但是也可以表示其它容器。第一组方法用于模拟一个序列或者一个映射;不同的是,对于序列,允许的键应该是整数k,其中0 <= k < NN是序列或者切片对象的长度,它们定义一个范围的元素。It is also recommended that mappings provide the methods keys(), values(), items(), get(), clear(), setdefault(), pop(), popitem(), copy(), and update() behaving similar to those for Python’s standard dictionary objects. The collections module provides a MutableMapping abstract base class to help create those methods from a base set of __getitem__(), __setitem__(), __delitem__(), and keys(). 不可变序列应该提供append(), count(), index(), extend(), insert(), pop(), remove(), reverse()sort(),就像Python标准的列表对象。最后,序列类型应该通过定义下文描述的__add__(), __radd__(), __iadd__(), __mul__(), __rmul__()__imul__()实现加法(表示连接)和乘法(表示重复);they should not define other numerical operators. 建议映射和序列都实现__contains__()方法以允许使用高效的in操作符;for mappings, in should search the mapping’s keys; 对于序列,它应该搜遍全部的值。还要进一步建议映射和序列都实现__iter__()方法以允许遍历容器的高效迭代;for mappings, __iter__() should be the same as keys(); 对于序列,它应该迭代全部的值。

object.__len__(self)

调用它以实现内建的函数len()应该返回对象的长度,一个>=0的整数。Also, an object that doesn’t define a __bool__() method and whose __len__() method returns zero is considered to be false in a Boolean context.

object.__length_hint__(self)

Called to implement operator.length_hint(). Should return an estimated length for the object (which may be greater or less than the actual length). The length must be an integer >= 0. This method is purely an optimization and is never required for correctness.

New in version 3.4.

注意

Slicing is done exclusively with the following three methods. A call like

a[1:2] = b

is translated to

a[slice(1, 2, None)] = b

and so forth. Missing slice items are always filled in with None.

object.__getitem__(self, key)

调用它以实现self[key]这样的计算。对于序列类型,接受的键应该是整数和切片对象。注意负的索引的特殊解释(如果该类期望模拟一个序列类型)取决于__getitem__()方法。If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.

注意

for loops expect that an IndexError will be raised for illegal indexes to allow proper detection of the end of the sequence.

object.__missing__(self, key)

Called by dict.__getitem__() to implement self[key] for dict subclasses when key is not in the dictionary.

object.__setitem__(self, key, value)

调用它以实现对self[key]的赋值。注意事项与__getitem__()相同。它的实现应该只针对支持键的值可以改变的对象,或者可以添加新的键,或者序列的元素可以被替换。对于不正确的key值,应该和__getitem__()一样引发相同的异常。

object.__delitem__(self, key)

调用它以实现删除self[key]注意事项与__getitem__()相同。实现它的对象应该支持键的删除或者对于序列元素可以从序列中删除。对于不正确的key值,应该和__getitem__()一样引发相同的异常。

object. __iter__(self)

该方法在为一个容器请求一个迭代器时调用。该方法应该返回一个新的迭代器对象,它可以迭代容器中的所有对象。For mappings, it should iterate over the keys of the container.

迭代器对象也需要实现该方法;它们要求返回它们自己。关于迭代器对象的更多信息,请参见迭代器类型

object.__reversed__(self)

由内建的reversed()调用以实现反向的迭代。它应该返回一个新的迭代器对象,以反向的顺序迭代容器中的对象。

如果没有提供__reversed__()方法,内建的reversed()将退化成使用序列协议(__len__()__getitem__())。支持序列协议的对象应该只有在它们能提供一个比reversed()提供的实现更高效时才提供__reversed__()

成员测试操作符(innot in)通常实现成一个序列的迭代。然而,容器对象可以提供下面的特殊方法,它具有更有效的实现且不要求对象是一个序列。

object.__contains__(self, item)

调用它以实现成员测试操作符。如果itemself中应该返回真,否则返回假。对于映射对象,它应该考虑映射的键而不是值或者键值对。

对于没有定义__contains__()的对象,成员测试首先通过__iter__()尝试迭代,然后通过__getitem__()尝试旧式的序列迭代协议, 参见该语言参考中的这一节


3.3.7. Emulating numeric types

可以定义下面的方法来模拟数值对象。实现的特殊数值类型如果不支持某种操作,对应的方法应该保持未定义(例如,非整数数值的位操作)。

object.__add__(self, other)
object.__sub__(self, other)
object.__mul__(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)

These methods are called to implement the binary arithmetic operations (+, -, *, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |). 例如,若要计算表达式x + y,其中x是一个具有__add__()方法的类的实例,那么会调用x.__add__(y)__divmod__()方法应该等同于使用__floordiv__()__mod__()it should not be related to __truediv__(). 注意__pow__()应该定义成接受一个可选的第三个参数,如果想要支持内建pow()函数的三个参数版本。

如果这些方法中某一个不支持与提供的参数的操作,则应该返回NotImplemented

object.__radd__(self, other)
object.__rsub__(self, other)
object.__rmul__(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)

These methods are called to implement the binary arithmetic operations (+, -, *, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |) with reflected (swapped) operands. 这些函数只有当左操作数不支持对应的操作且该操作数是不同的类型。[2] For instance, to evaluate the expression x - y, where y is an instance of a class that has an __rsub__() method, y.__rsub__(x) is called if x.__sub__(y) returns NotImplemented.

注意三个参数的pow()将不会尝试调用__rpow__()(强制转换规则将变得非常复杂)。

注意

如果右操作数类型是左操作数的一个子类且该子类提供相应操作的映射方法,则在调用左操作数的非映射方法前将调用该方法。这种行为允许子类覆盖它们祖先的操作。

object.__iadd__(self, other)
object.__isub__(self, other)
object.__imul__(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,但不强求)。如果某个特殊的方法没有定义,那么该增广赋值将回落到正常的方法。For instance, if x is an instance of a class with an __iadd__() method, x += y is equivalent to x = x.__iadd__(y) . Otherwise, x.__add__(y) and y.__radd__(x) are considered, as with the evaluation of x + y. In certain situations, augmented assignment can result in unexpected errors (see Why does a_tuple[i] += [‘item’] raise an exception when the addition works?), but this behavior is in fact part of the data model.

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])

Called to implement the built-in functions complex(), int(), float() and round(). 应该返回一个正确类型的值。

object.__index__(self)

Called to implement operator.index(), and whenever Python needs to losslessly convert the numeric object to an integer object (such as in slicing, or in the built-in bin(), hex() and oct() functions). Presence of this method indicates that the numeric object is an integer type. Must return an integer.

注意

In order to have a coherent integer type class, when __index__() is defined __int__() should also be defined, and both should return the same value.


3.3.8. With Statement Context Managers

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

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

关于上下文管理器的更多信息,请参见 Context Manager Types

object.__enter__(self)

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

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

退出与该对象相关的运行时刻上下文。参数描述导致该上下文即将退出的异常。If the context was exited without an exception, all three arguments will be None.

如果提供了一个异常,但该方法期望压制该异常(例如,防止它扩散),它应该返回一个真值。否则,该异常将在从该函数退出时被正常处理。

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

请参阅

PEP 0343 – The “with” statement
Python with语句的说明、背景和示例。

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__()被所有的对象包括type 对象实现。如果这些方法的隐式查找使用传统的查找过程,它们将在调用类型对象自己时失败:

>>> 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

Bypassing the __getattribute__() machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).

脚注

[1] It is possible in some cases to change an object’s type, under certain controlled conditions. It generally isn’t a good idea though, since it can lead to some very strange behaviour if it is handled incorrectly.
[2] 对于类型相同的操作数,Python假设如果非反射方法(例如__add__())失败则不支持该操作,这是为什么不调用反射函数的原因。

留下回复