特殊方法

Python 中的对象通常根据它们的行为和实现的功能进行分类。例如,所有序列类型都分在一组,如字符串、列表和元组,就是因为它们都支持一组相同的序列操作,如 s[n]len(s) 等。所有基本的解释器操作都是通过特殊的对象方法实现的。这些特殊方法的名称前后始终带有双下划线 __ 。当程序执行时,这些方法都由解释器自动触发。例如,操作 x + y 被映射为内部方法 x.__add__(y) ,而索引操作 x[k] 被映射为 x.__getitem__(k) 。每种数据类型的行为完全取决于它实现的一组特殊方法。

用户定义的类可以定义行为类似于内置类型的新对象,只要提供本节所述特殊方法的恰当子集即可。另外,像列表和字典这样的内置类型可以通过重新定义一些特殊方法来进行自定义(通过继承)。

对象的创建与销毁

下表中的方法分别用于创建、初始化和销毁对象的特殊方法。调用 __new__() 类方法可以创建实例。 __init__() 方法用于初始化对象的属性,在创建新对象后将立即调用。需要销毁对象时调用 __del__() 方法,只有该对象不再被使用时才会调用该方法。需要注意的是,语句 del x 只会减少一个对象的引用计数,而不一定会导致该函数被调用。

方法

描述

__new__( cls [,* args [,** kwargs ]])

创建新实例时调用的类方法

__init__( self [,* args [,** kwargs ]])

初始化新实例时调用

__del__( self )

销毁实例时调用

__new__()__init__() 方法用于创建和初始化新实例。调用 A(args) 创建对象时,会将其转换为以下步骤:

x = A.__new__(A,args)
is isinstance(x,A): x.__init__(args)

在用户定义的对象中,很少定义 __new__()__del__() 方法。 __new__() 方法通常只定义在元类或继承自不可变类型之一(整数、字符串、元组等)的用户定义对象中。 __del__() 方法只有存在某种关键资源管理问题的情况下才会定义,如释放锁定或关闭连接时。

对象字符串表示

下表中的方法用于创建一个对象的各种字符串表示的特殊方法。

方法

描述

__format__( self , format_spec )

创建格式化后的表示

__repr__( self )

创建对象的字符串表示

__str__( self )

创建简单的字符串表示

__repr__()__str__() 方法用于创建对象的简单字符串表示。 __repr__() 方法通常返回一个表达式字符串,可对该字符串求值以重新创建对象。该方法还负责创建在交互式解释器中检查变量时看到的输出值,其调用者是内置的 repr() 函数。下面给出了一个使用 repr()eval() 的例子:

a = [2,3,4,5]   # 创建一个列表
s = repr(a)    # s = '[2, 3, 4, 5]'
b = eval(s)    # 将 s 变为一个列表

如果无法创建字符串表达式,习惯做法是让 __repr__() 方法返回一个 <…message…> 形式的字符串,例如:

f = open("foo")
a = repr(f)     # a = "<open file 'foo', mode 'r' at dc030>"

__str__() 方法的调用者是内置 str() 函数和与打印相关的函数。它与 __repr__() 方法的区别在于,它返回的字符串更加简明易懂。如果该方法未定义,就会调用 __repr__() 方法。

__format__() 方法的调用者是 format() 函数或字符串的 format() 方法。format_spec 参数是包含了格式规范的字符串。该字符串与 format() 方法的 format_spce 参数相同,例如:

format(x,"spec")      # 调用 x.__format__("spec")
"x is {0:spec}".format(x) # 调用 x.__format__("spec")

格式规范的语法是任意的,可以根据对象进行自定义。不过也存在标准语法,第4章将做介绍。

对象比较与排序

下表中的方法可执行对象的简单测试。 __bool__() 方法用于真值测试,返回值应该为 TrueFalse 。如果该方法未定义,Python 将调用 __len__() 方法来确定对象的真值。 __hash__() 方法定义在希望用作字典中键的对象上。如果两个对象比较后相等,作为返回值的整数应该完全相同。此外,可变对象不应定义该方法,因为对象的任何改动都将改变散列值,导致在后续的字典查找中无法定位对象。

方法

描述

__bool__( self )

为真值测试返回 False 或 True

__hash__( self )

计算整数的散列索引

对象可以实现一个或多个关系运算符(<><=>=!= )。这些方法均接受两个参数,而且支持返回任意类型的对象,包括布尔值、列表或任意其他 Python 类型。例如,有数学包可能使用该方法对两个矩阵的元素进行比较,并返回矩阵结果。如果无法进行比较,这些函数也会引发异常。下表中列出了用于比较运算符的特殊方法。

方法

结果

__lt__( self , other )

self < other

__le__( self , other )

self <= other

__gt__( self , other )

self > other

__ge__( self , other )

self >= other

__eq__( self , other )

self == other

__ne__( self , other )

self != other

对象不必实现表中的所有操作。然而,如果要使用 == 比较对象或者使用对象作为字典键,应该定义 __eq__() 方法。如果要为对象排序或者使用诸如 min()max() 之类的函数,必须要至少定义 __lt__() 方法。

类型检查

下表中的方法可用于重新定义类型检查函数 isinstance()issubclass() 的行为。这些方法最常见的应用是定义抽象的基类和接口。

方法

结果

__instancecheck__( cls , object )

isinstance( object , cls )

__subclasscheck__( cls , sub )

issubclass( sub , cls )

属性访问的特殊方法

下表中的方法分别使用点 . 运算符和 del 运算符读、写和删除对象的属性。

方法

描述

__getattribute__( self , name )

返回属性 self.name

__getattr__( self , name )

如果通过常规属性查找未找到属性,返回属性 self.name,无法计算属性则引发 AttributeError 异常

__setattr__( self , name , value )

设置属性 self.name = value,覆盖默认值

__delattr__( self , name )

删除属性 self.name

访问属性时始终会调用 __getattribute__() 方法。如果找到属性则返回之,否则调用 __getattr__() 方法。 __getattr__() 方法的默认行为是引发 AttributeError 异常。设置属性时始终会调用 __setattr__() 方法,而删除属性时始终会调用 __delattr__() 方法。

属性包装与描述符

属性操作有时候使用一个额外逻辑层来包装对象的属性。实现此类包装的方法是创建一个描述符对象来实现下表中的一个或多个方法。记住,描述符是可选的,极少情况下才需要定义。

方法

描述

__get__( self , instance , cls )

返回一个属性值,否则引发 AttributeError 异常

__set__( self , instance , value )

将属性设为 value

__delete__( self , instance )

删除属性

描述符的 __get__()__set__()__delete__() 方法用于与类和类型的 __getattribute__()__setattr__()__delattr__() 方法进行交互。如果在用户自定义类的主体中放入一个描述符对象的实例,这种交互就会发生。在这种情况下,对于描述符属性的所有访问都将显式地调用描述符对象本身的相应方法。描述符一般用于实现对象系统的底层功能,包括绑定和非绑定方法、类方法、静态方法和特性。第7章中给出了一些更加深入的例子。

序列与映射方法

如果对象要模拟序列和映射对象的行为,就要用到下表中的方法。

方法

描述

__len__( self )

返回 self 的长度

__getitem__( self , key )

返回 self [ key ]

__setitem__( self , key , value )

设置 self [ key ] = value

__delitem__( self , key )

删除 self [ key ]

__contains__( self , obj )

如果 obj 在 self 中,则返回 True,否则返回 False

例如:

a = [1,2,3,4,5,6]
len(a)        # a.__len__()
x = a[2]       # x = a.__getitem__(2)
a[1] = 7       # a.__setitem__(1,7)
del a[2]       # a.__delitem__(2)
5 in a        # a.__contains__(5)

内置的 len() 函数调用 __len__() 方法,返回一个非负的长度值。该函数还用于确定真值,除非已经定义了 __bool__() 方法。

为了操作单个项, __getitem__() 方法可根据键返回项。这里的键可以是任意 Python 对象,但对于序列而言通常为整数。 __setitem__() 方法用于给元素赋值。 __delitem__() 方法在对单个元素进行 del 操作时调用。 __contains__() 方法用于实现in运算符。

切片运算(如 x = s[i:j] )也使用 __getitem__()__setitem__()__delitem__() 方法来实现。但给切片传递的键是一个特殊的 slice 对象。该对象拥有可描述所请求切片范围的属性,例如:

a = [1,2,3,4,5,6]
x = a[1:5]       # x = a.__getitem__(slice(1,5,None))
a[1:3] = [10,11,12] # a.__setitem__(slice(1,3,None), [10,11,12])
del a[1:4]       # a.__delitem__(slice(1,4,None))

Python 的切片功能实际上比很多程序员认为的更强大。例如,它支持以下扩展切片的变体,在处理矩阵和数组这样的多维数据结构时可能非常有用:

a = m[0:100:10]       # 带步进的切片(步进值=10)
b = m[1:10, 3:20]      # 多维切片
c = m[0:100:10, 50:75:5] # 带步长的多维切片
m[0:5, 5:10] = n      # 扩展切片分配
del m[:10, 15:]       # 扩展切片删除

扩展切片每个维度的一般格式是 i:j[:stride] ,stride 是可选的。和普通切片一样,可以省略切片每个部分的开始或结束值。另外,省略号(写为…)可用于表示扩展切片中结束或开始的任意维数:

a = m[..., 10:20] # 使用Ellipsis对象访问扩展切片
m[10:20, ...] = n

使用扩展切片时, __getitem__()__setitem__()__delitem__() 方法分别用于实现访问、修改和删除操作。然而,传递给这些方法的值是一个包含 slice 或 Ellipsis 对象组合的元组,而非整数,例如:

a = m[0:10, 0:100:5, ...]

调用 __getitem__() 方法的方式如下:

a = m.__getitem__((slice(0,10,None), slice(0,100,5), Ellipsis))

Python 字符串、元组和列表目前在一定程度上支持扩展切片,这一点将在第4章中介绍。特殊用途的 Python 扩展,特别是与科学相关的扩展,可能会提供新的类型和对象,从而为扩展切片操作提供高级支持。

迭代

如果对象 obj 支持迭代,它必须提供方法 obj.__iter__() ,该方法返回一个迭代器对象。而迭代器对象 iter 必须实现一个方法 iter.__next__() ,该方法返回下一个对象,或者在迭代结束时引发 StopIteration 异常。这两个方法均用于 for 语句的实现,以及其他一些隐式执行迭代的操作。例如,语句 for x in s 执行的步骤等同于以下代码:

_iter = s.__iter__()
while 1:
  try:
     x =_iter.next()(# Python 3中为_iter.__next__())
  except StopIteration:
     break
  # 在for循环体内执行语句
  ...

数学操作

下表中列出了对象在模拟数字时必须实现的特殊方法。执行表达式 x + y 时,解释器会试着调用方法 x.__add__(y) 。以字母 r 开头的特殊方法支持以反向的操作数进行运算,它们只在左操作数没有实现指定操作时被调用。例如,如果表达式 x + y 中的 x 不支持 __add()__ 方法,解释器就会试着调用方法 y.__radd__(x)

方法

结果

__add__( self , other )

self + other

__sub__( self , other )

self - other

__mul__( self , other )

self * other

__truediv__( self , other )

self / other (Python 3)

__floordiv__( self , other )

self // other

__mod__( self , other )

self % other

__divmod__( self , other )

divmod( self , other )

__pow__( self , other [, modulo ])

self ** other , pow( self , other , modulo )

__lshift__(s elf , other )

self << other

__rshift__( self , other )

self >> other

__and__( self , other )

self & other

__or__( self , other )

self│other

__xor__(self, other )

self ^ other

__radd__( self , other )

other + self

__rsub__( self , other )

other - self

__rmul__( self , other )

other * self

__rtruediv__( self , other )

other / self (Python 3)

__rfloordiv__( self , other )

other // self

__rmod__( self , other )

other % self

__rdivmod__( self , other )

divmod( other , self )

__rpow__( self , other )

other ** self

__rlshift__( self , other )

other << self

__rrshift__( self , other )

other >> self

__rand__( self , other )

other & self

__ror__( self , other )

other│self

__rxor__( self , other )

other ^ self

__iadd__( self , other )

self += other

__isub__( self , other )

self -= other

__imul__( self , other )

self *= other

__itruediv__( self , other )

self /= other (Python 3)

__ifloordiv__( self , other )

self //= other

__imod__( self , other )

self %= other

__ipow__( self , other )

self **= other

__iand__( self , other )

self &= other

__ior__( self , other )

self │= other

__ixor__( self , other )

self ^= other

__ilshift__( self , other )

self <<= other

__irshift__( self , other )

self >>= other

__neg__( self )

– self

__pos__( self )

+ self

__abs__( self )

abs( self )

__invert__( self )

~ self

__int__( self )

int( self )

__float__( self )

float( self )

__complex__( self )

complex( self )

方法 __iadd()____isub()__ 等用于实现原地算术操作,如 a+=ba-=b (也称为增量赋值)。这些运算符与标准算术方法之间的区别在于,原地运算符的实现能够提供某种自定义,如性能优化。例如,如果 self 参数不是共享的,就可以原地修改对象的值,而不必为结果分配一个新创建的对象。

除法运算符共有 3 种, __div__()__truediv()____floordiv__() ,它们用于实现常规除法 / 和截断除法 // 操作。存在3种除法操作的原因是,在 Python 2.2 中整数除法的语义开始有了变化,而这种变化在 Python 3 中则变成了默认行为。在 Python 2 中,Python 的默认行为是将 / 运算符映射到 __div__() 方法,如果操作数都为整数,这种操作会把结果截断为一个整数。在 Python 3 中,除法被映射到 __truediv__() 方法,对于整数操作数将返回一个浮点数。在 Python 2 中,后面这种行为是一项可选特性,在程序中包含语句 from__future__ import division 即可启用该特性。

转换方法 __int__()__long__()__float__()__complex__() 用于将对象转换为 4 种内置的数值类型之一。出现显式的类型转换时(如 int()float() ),就会调用这些方法。但这些方法不能用于在数学操作中隐式地强制类型转换。例如,表达式 3 + x 会引发一个 TypeError 错误,即使 x 是定义了整数转换方法 __int__() 的用户定义对象也是如此。

可调用接口

对象通过提供 __call__(self[,*args[, **kwargs]]) 方法可以模拟函数的行为。如果一个对象 x 提供了该方法,就可以像函数一样调用它。也就是说, x(arg1, arg2,...) 等同于调用 x.__call__(self,arg1,arg2,...) 。模拟函数的对象可以用于创建仿函数(functor)或代理(proxy)。下面给出了一个简单的例子:

class DistanceFrom(object):
  def__init__(self,origin):
    self.origin = origin
  def__call__(self, x):
    return abs(x - self.origin)

nums = [1, 37, 42, 101, 13, 9, -20]
nums.sort(key=DistanceFrom(10))    # 按照与 10 的距离进行排序

在这个例子中,DistanceFrom 类创建的实例模拟了一个单参数函数。这些实例可用于代替普通的函数,如本例中对于 sort() 的调用。

上下文管理协议

with 语句支持在另一个称为上下文管理器的对象的控制下执行一系列语句。它的语法如下所示:

with context [ as var]:
   statements

其中 context 对象需要实现下表中所示的方法。执行 with 语句时,就会调用 __enter__() 方法。该方法的返回值将被放入由可选的 as var 说明符指定的变量中。只要控制流离开与 with 语句相关的语句块,就会立即调用 __exit__() 方法。__exit__() 方法接收当前异常的类型、值和跟踪作为参数。如果没有要处理的错误,所有 3 个值都将被置为 None

方法

描述

__enter__( self )

进入新的上下文时调用该方法,其返回值将被放入由 with 语句的 as 说明符指定的变量中

__exit__( self , type , value , tb )

离开上下文时调用该方法。如果有异常出现,type、value 和 tb 的值分别为异常的类型、值和跟踪信息。 上下文管理接口的首要用途是简化涉及系统状态(如打开文件、网络连接和锁定的对象)的对象的资源控制。实现该接口后,当执行离开使用对象的上下文时,该对象可以安全地释放资源。

对象检查与 dir()

dir() 函数通常用于检查对象。实现 __dir__(self) 方法后,对象就可以使用 dir() 返回名称列表。定义该方法可以更加方便地隐藏不想让用户直接访问的对象内部细节。但要记住,用户仍然可以检查实例和类的底层 __dict__ 属性,从而了解已定义的所有内容。