文章目录
0.摘要
在本书其它几处,我们已经见识过一些特殊方法——即在使用某些语法时 P y t h o n Python Python 所调用的“神奇”方法。使用特殊方法,类用起来如同序列、字典、函数、迭代器,或甚至像个数字!本附录为我们已经见过特殊方法提供了参考,并对一些更加深奥的特殊方法进行了简要介绍。
1.基础知识
- 对 _ _ i n i t _ _ ( ) \_\_init\_\_() __init__() 方法的调用发生在实例被创建之后。如果要控制实际创建进程,请使用 _ _ n e w _ _ ( ) \_\_new\_\_() __new__() 方法。
- 按照约定, _ _ r e p r _ _ ( ) \_\_repr\_\_() __repr__() 方法所返回的字符串为合法的 P y t h o n Python Python 表达式。
- 在调用 p r i n t ( x ) print(x) print(x) 的同时也调用了 _ _ s t r _ _ ( ) \_\_str\_\_() __str__()方法。
- 由于 b y t e s bytes bytes 类型的引入而从 P y t h o n 3 Python 3 Python3 开始出现。
- 按照约定, f o r m a t _ s p e c format\_spec format_spec 应当遵循相关的规范。
在此多提一下
r
e
p
r
repr
repr的用法:
2.行为方式与迭代器类似的类
在《迭代器》一章中,我们已经学习了如何使用
_
_
i
t
e
r
_
_
(
)
\_\_iter\_\_()
__iter__() 和
_
_
n
e
x
t
_
_
(
)
\_\_next\_\_()
__next__() 方法从零开始创建迭代器。
- 无论何时创建迭代器都将调用 _ _ i t e r _ _ ( ) \_\_iter\_\_() __iter__() 方法。这是用初始值对迭代器进行初始化的绝佳之处。
- 无论何时从迭代器中获取下一个值都将调用 _ _ n e x t _ _ ( ) \_\_next\_\_() __next__() 方法。
-
_
_
r
e
v
e
r
s
e
d
_
_
(
)
\_\_reversed\_\_()
__reversed__() 方法并不常用。它以一个现有序列为参数,并将该序列中所有元素从尾到头以逆序排列生成一个新的迭代器。
P y t h o n 3 Python 3 Python3 将会调用 s e q . _ _ i t e r _ _ ( ) seq.\_\_iter\_\_() seq.__iter__() 以创建一个迭代器,然后对迭代器调用 _ _ n e x t _ _ ( ) \_\_next\_\_() __next__() 方法以获取 x x x 的每个值。当 _ _ n e x t _ _ ( ) \_\_next\_\_() __next__() 方法引发 S t o p I t e r a t i o n StopIteration StopIteration 例外时, f o r for for 循环正常结束。
3.计算属性
- 如果某个类定义了 _ _ g e t a t t r i b u t e _ _ ( ) \_\_getattribute\_\_() __getattribute__() 方法,在每次引用属性或方法名称时 P y t h o n Python Python都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)。
- 如果某个类定义了 _ _ g e t a t t r _ _ ( ) \_\_getattr\_\_() __getattr__() 方法, P y t h o n Python Python 在访问不存在的属性时会调用它。如果实例 x x x 定义了属性 c o l o r color color, x . c o l o r x.color x.color 将不会调用 x . _ _ g e t a t t r _ _ ( ′ c o l o r ′ ) x.\_\_getattr\_\_('color') x.__getattr__(′color′);而只会返回 x . c o l o r x.color x.color 已定义好的值。
- 无论何时给属性赋值,都会调用 _ _ s e t a t t r _ _ ( ) \_\_setattr\_\_() __setattr__() 方法。
- 无论何时删除一个属性,都将调用 _ _ d e l a t t r _ _ ( ) \_\_delattr\_\_() __delattr__() 方法。
- 如果定义了
_
_
g
e
t
a
t
t
r
_
_
(
)
\_\_getattr\_\_()
__getattr__() 或
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法,
_
_
d
i
r
_
_
(
)
\_\_dir\_\_()
__dir__() 方法将非常有用。通常,调用
d
i
r
(
x
)
dir(x)
dir(x) 将只显示正常的属性和方法。如果
_
_
g
e
t
a
t
t
r
_
_
(
)
\_\_getattr\_\_()
__getattr__() 方法动态处理
c
o
l
o
r
color
color 属性,
d
i
r
(
x
)
dir(x)
dir(x) 将不会将
c
o
l
o
r
color
color 列为可用属性。可通过覆盖
_
_
d
i
r
_
_
(
)
\_\_dir\_\_()
__dir__() 方法允许将
c
o
l
o
r
color
color列为可用属性,对于想使用你的类但却不想深入其内部的人来说,该方法非常有益。
① 属性名称以字符串的形式传入
_
_
g
e
t
a
t
t
r
_
_
(
)
\_\_getattr\_\_()
__getattr__()方法。如果名称为
′
c
o
l
o
r
′
'color'
′color′,该方法返回一个值。(在此情况下,它只是一个硬编码的字符串,但可以正常地进行某些计算并返回结果。)
② 如果属性名称未知,
_
_
g
e
t
a
t
t
r
_
_
(
)
\_\_getattr\_\_()
__getattr__() 方法必须引发一个
A
t
t
r
i
b
u
t
e
E
r
r
o
r
AttributeError
AttributeError 例外,否则在访问未定义属性时,代码将只会默默地失败。(从技术角度而言,如果方法不引发例外或显式地返回一个值,它将返回
N
o
n
e
None
None ——
P
y
t
h
o
n
Python
Python 的空值。这意味着所有未显式定义的属性将为
N
o
n
e
None
None,几乎可以肯定这不是你想看到的。)
③
d
y
n
dyn
dyn 实例没有名为
c
o
l
o
r
color
color 的属性,因此在提供计算值时将调用
_
_
g
e
t
a
t
t
r
_
_
(
)
\_\_getattr\_\_()
__getattr__() 。
④ 在显式地设置
d
y
n
.
c
o
l
o
r
dyn.color
dyn.color 之后,将不再为提供
d
y
n
.
c
o
l
o
r
dyn.color
dyn.color 的值而调用
_
_
g
e
t
a
t
t
r
_
_
(
)
\_\_getattr\_\_()
__getattr__() 方法,因为
d
y
n
.
c
o
l
o
r
dyn.color
dyn.color 已在该实例中定义。
① 在获取
d
y
n
.
c
o
l
o
r
dyn.color
dyn.color 的值时将调用
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法。
② 即便已经显式地设置
d
y
n
.
c
o
l
o
r
dyn.color
dyn.color,在获取
d
y
n
.
c
o
l
o
r
dyn.color
dyn.color 的值时, 仍将调用
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法。如果存在
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法,将在每次查找属性和方法时 无条件地调用它,哪怕在创建实例之后已经显式地设置了属性。
如果定义了类的
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法,你可能还想定义一个
_
_
s
e
t
a
t
t
r
_
_
(
)
\_\_setattr\_\_()
__setattr__() 方法,并在两者之间进行协同,以跟踪属性的值。否则,在创建实例之后所设置的值将会消失在黑洞中。
① 该类定义了一个总是引发
A
t
t
r
i
b
u
t
e
E
r
r
o
r
AttributeError
AttributeError 例外的
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法。没有属性或方法的查询会成功。
② 调用
h
e
r
o
.
s
w
i
m
(
)
hero.swim()
hero.swim() 时,
P
y
t
h
o
n
Python
Python 将在
R
a
s
t
a
n
Rastan
Rastan 类中查找
s
w
i
m
(
)
swim()
swim() 方法。该查找将执行整个
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法,因为所有的属性和方法查找都通过
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法。在此例中,
_
_
g
e
t
a
t
t
r
i
b
u
t
e
_
_
(
)
\_\_getattribute\_\_()
__getattribute__() 方法引发
A
t
t
r
i
b
u
t
e
E
r
r
o
r
AttributeError
AttributeError 例外,因此该方法查找过程将会失败,而方法调用也将失败。
4.行为方式与函数类似的类
可以让类的实例变得可调用——就像函数可以调用一样——通过定义
_
_
c
a
l
l
_
_
(
)
\_\_call\_\_()
__call__() 方法。
zipfile 模块通过该方式定义了一个可以使用给定密码解密经过加密的
z
i
p
zip
zip文件的类。该
z
i
p
zip
zip 解密算法需要在解密的过程中保存状态。通过将解密器定义为类,使我们得以在
d
e
c
r
y
p
t
o
r
decryptor
decryptor 类的单个实例中对该状态进行维护。状态在
_
_
i
n
i
t
_
_
(
)
\_\_init\_\_()
__init__() 方法中进行初始化,如果文件经过加密则需要进行更新。但由于该类像函数一样“可调用”,因此可以将实例作为
m
a
p
(
)
map()
map() 函数的第一个参数传入,代码如下:
①
_
Z
i
p
D
e
c
r
y
p
t
o
r
\_ZipDecryptor
_ZipDecryptor 类维护了以三个旋转密钥形式出现的状态,该状态稍后将在
_
U
p
d
a
t
e
K
e
y
s
(
)
\_UpdateKeys()
_UpdateKeys() 方法中更新(此处未展示)。
② 该类定义了一个
_
_
c
a
l
l
_
_
(
)
\_\_call\_\_()
__call__() 方法,使得该类可像函数一样调用。在此例中,
_
_
c
a
l
l
_
_
(
)
\_\_call\_\_()
__call__() 对
z
i
p
zip
zip 文件的单个字节进行解密,然后基于经解密的字节对旋转密码进行更新。
③
z
d
zd
zd 是
_
Z
i
p
D
e
c
r
y
p
t
o
r
\_ZipDecryptor
_ZipDecryptor 类的一个实例。变量
p
w
d
pwd
pwd 被传入
_
_
i
n
i
t
_
_
(
)
\_\_init\_\_()
__init__() 方法,并在其中被存储和用于首次旋转密码更新。
④ 给出
z
i
p
zip
zip 文件的头 12 个字节,将这些字节映射给
z
d
zd
zd 进行解密,实际上这将导致调用
_
_
c
a
l
l
_
_
(
)
\_\_call\_\_()
__call__() 方法 12 次,也就是更新内部状态并返回结果字节 12 次。
5.行为方式与序列类似的类
如果类作为一系列值的容器出现——也就是说如果对某个类来说,是否“包含”某值是件有意义的事情——那么它也许应该定义下面的特殊方法已,让它的行为方式与序列类似。
① 一旦创建了
c
g
i
.
F
i
e
l
d
S
t
o
r
a
g
e
cgi.FieldStorage
cgi.FieldStorage 类的实例,就可以使用
i
n
in
in 运算符来检查查询字符串中是否包含了某个特定参数。
② 而
_
_
c
o
n
t
a
i
n
s
_
_
(
)
\_\_contains\_\_()
__contains__() 方法是令该魔法生效的主角。
③ 如果代码为
i
f
′
q
′
i
n
f
s
if\ 'q'\ in\ fs
if ′q′ in fs,Python 将在
f
s
fs
fs 对象中查找
_
_
c
o
n
t
a
i
n
s
_
_
(
)
\_\_contains\_\_()
__contains__() 方法,而该方法在
c
g
i
.
p
y
cgi.py
cgi.py 中已经定义。
′
q
′
'q'
′q′ 的值被当作
k
e
y
key
key 参数传入
_
_
c
o
n
t
a
i
n
s
_
_
(
)
\_\_contains\_\_()
__contains__() 方法。
④ 同样的
F
i
e
l
d
S
t
o
r
a
g
e
FieldStorage
FieldStorage 类还支持返回其长度,因此可以编写代码
l
e
n
(
f
s
)
len(fs)
len(fs) 而其将调用
F
i
e
l
d
S
t
o
r
a
g
e
FieldStorage
FieldStorage 的
_
_
l
e
n
_
_
(
)
\_\_len\_\_()
__len__() 方法,并返回其识别的查询参数个数。
⑤
s
e
l
f
.
k
e
y
s
(
)
self.keys()
self.keys() 方法检查
s
e
l
f
.
l
i
s
t
i
s
N
o
n
e
self.list\ is\ None
self.list is None 是否为真值,因此
_
_
l
e
n
_
_
(
)
\_\_len\_\_()
__len__() 方法无需重复该错误检查。
6.行为方式与字典类似的类
在前一节的基础上稍作拓展,就不仅可以对
i
n
in
in 运算符和
l
e
n
(
)
len()
len() 函数进行响应,还可像全功能字典一样根据键来返回值。
② f s [ ′ q ′ ] fs['q'] fs[′q′] 将 k e y key key 参数设置为 ′ q ′ 'q' ′q′ 来调用 _ _ g e t i t e m _ _ ( ) \_\_getitem\_\_() __getitem__() 方法。然后它将在其内部维护的查询参数列表 ( s e l f . l i s t self.list self.list) 中查找一个 . n a m e .name .name 与给定键相符的字典项。
7.行为方式与数值类似的类
使用适当的特殊方法,可以将类的行为方式定义为与数字相仿。也就是说,可以进行相加、相减,并进行其它数学运算。这就是分数的实现方式——
F
r
a
c
t
i
o
n
Fraction
Fraction 类实现了这些特殊方法,然后就可以进行下列运算了:
以下是实现“类数字”类的完整特殊方法清单:
如果
x
x
x 是某个实现了所有这些方法的类的实例,那么万事大吉。但如果未实现其中之一呢?或者更糟,如果实现了,但却无法处理某几类参数会怎么样?例如:
这并不是传入一个分数并将其除以一个整数(如前例那样)的情况。前例中的情况非常直观: x / 3 x / 3 x/3 调用 x . _ _ t r u e d i v _ _ ( 3 ) x.\_\_truediv\_\_(3) x.__truediv__(3),而 F r a c t i o n Fraction Fraction 的 _ _ t r u e d i v _ _ ( ) \_\_truediv\_\_() __truediv__() 方法处理所有的数学运算。但整数并不“知道”如何对分数进行数学计算。因此本例该如何运作呢?
和 反映操作 相关的还有第二部分算数特殊方法。给定一个二元算术运算 (例如: x / y x / y x/y),有两种方法来实现它:
- 告诉 x x x 将自己除以 y y y,或者
- 告诉 y y y 去除 x x x
之前提到的特殊方法集合采用了第一种方式:对于给定
x
/
y
x / y
x/y,它们为
x
x
x 提供了一种途径来表述“我知道如何将自己除以
y
y
y。”下面的特殊方法集合采用了第二种方法:它们向
y
y
y 提供了一种途径来表述“我知道如何成为分母,并用自己去除
x
x
x。”
但是等一下!还有更多特殊方法!如果在进行“原地”操作,如:
x
/
=
3
x /= 3
x/=3,还可定义更多的特殊方法。
注意:多数情况下,并不需要原地操作方法。如果未对特定运算定义“就地”方法,
P
y
t
h
o
n
Python
Python 将会试着使用(普通)方法。例如,为执行表达式
x
/
=
y
x /= y
x/=y,
P
y
t
h
o
n
Python
Python 将会:
- 试着调用 x . _ _ i t r u e d i v _ _ ( y ) x.\_\_itruediv\_\_(y) x.__itruediv__(y)。如果该方法已经定义,并返回了 N o t I m p l e m e n t e d NotImplemented NotImplemented 之外的值,那已经大功告成了。
- 试图调用
x
.
_
_
t
r
u
e
d
i
v
_
_
(
y
)
x.\_\_truediv\_\_(y)
x.__truediv__(y)。如果该方法已定义并返回一个
N
o
t
I
m
p
l
e
m
e
n
t
e
d
NotImplemented
NotImplemented 之外的值,
x
x
x
的旧值将被丢弃,并将所返回的值替代它,就像是进行了 x = x / y x = x / y x=x/y 运算。 - 试图调用
y
.
_
_
r
t
r
u
e
d
i
v
_
_
(
x
)
y.\_\_rtruediv\_\_(x)
y.__rtruediv__(x)。如果该方法已定义并返回了一个
N
o
t
I
m
p
l
e
m
e
n
t
e
d
NotImplemented
NotImplemented 之外的值,
x
x
x
的旧值将被丢弃,并用所返回值进行替换。
因此如果想对原地运算进行优化,仅需像 _ _ i t r u e d i v _ _ ( ) \_\_itruediv\_\_() __itruediv__() 方法一样定义“原地”方法。否则,基本上 P y t h o n Python Python 将会重新生成原地运算公式,以使用常规的运算及变量赋值。
还有一些“一元”数学运算,可以对“类-数字”对象自己执行。
8.可比较的类
我将此内容从前一节中拿出来使其单独成节,是因为“比较”操作并不局限于数字。许多数据类型都可以进行比较——字符串、列表,甚至字典。如果要创建自己的类,且对象之间的比较有意义,可以使用下面的特殊方法来实现比较。
如果定义了
_
_
l
t
_
_
(
)
\_\_lt\_\_()
__lt__() 方法但没有定义
_
_
g
t
_
_
(
)
\_\_gt\_\_()
__gt__() 方法,
P
y
t
h
o
n
Python
Python 将通过经交换的算子调用
_
_
l
t
_
_
(
)
\_\_lt\_\_()
__lt__() 方法。然而,
P
y
t
h
o
n
Python
Python 并不会组合方法。例如,如果定义了
_
_
l
t
_
_
(
)
\_\_lt\_\_()
__lt__() 方法和
_
_
e
q
_
_
(
)
\_\_eq\_\_()
__eq__() 方法,并试图测试是否
x
<
=
y
x <= y
x<=y,
P
y
t
h
o
n
Python
Python 不会按顺序调用
_
_
l
t
_
_
(
)
\_\_lt\_\_()
__lt__() 和
_
_
e
q
_
_
(
)
\_\_eq\_\_()
__eq__() 。它将只调用
_
_
l
e
_
_
(
)
\_\_le\_\_()
__le__() 方法。
9.可序列化的类
P
y
t
h
o
n
Python
Python 支持 任意对象的序列化和反序列化。(多数
P
y
t
h
o
n
Python
Python 参考资料称该过程为
“
p
i
c
k
l
i
n
g
"
“pickling"
“pickling"和
“
u
n
p
i
c
k
l
i
n
g
”
“unpickling”
“unpickling”)。该技术对于将状态保存为文件并在稍后恢复它非常有意义。所有的 内置数据类型 均已支持
p
i
c
k
l
i
n
g
pickling
pickling 。如果创建了自定义类,且希望它能够
p
i
c
k
l
e
pickle
pickle,阅读
p
i
c
k
l
e
pickle
pickle 协议 了解下列特殊方法何时以及如何被调用。
* 要重建序列化对象,
P
y
t
h
o
n
Python
Python 需要创建一个和被序列化的对象看起来一样的新对象,然后设置新对象的所有属性。
_
_
g
e
t
n
e
w
a
r
g
s
_
_
(
)
\_\_getnewargs\_\_()
__getnewargs__() 方法控制新对象的创建过程,而
_
_
s
e
t
s
t
a
t
e
_
_
(
)
\_\_setstate\_\_()
__setstate__() 方法控制属性值的还原方式。
10.可在 with 语块中使用的类
w
i
t
h
with
with 语块定义了运行时刻上下文环境;在执行
w
i
t
h
with
with 语句时将“进入”该上下文环境,而执行该语块中的最后一条语句将“退出”该上下文环境。
① 该文件对象同时定义了一个
_
_
e
n
t
e
r
_
_
(
)
\_\_enter\_\_()
__enter__() 和一个
_
_
e
x
i
t
_
_
(
)
\_\_exit\_\_()
__exit__() 方法。该
_
_
e
n
t
e
r
_
_
(
)
\_\_enter\_\_()
__enter__() 方法检查文件是否处于打开状态;如果没有,
_
c
h
e
c
k
C
l
o
s
e
d
(
)
\_checkClosed()
_checkClosed() 方法引发一个例外。
②
_
_
e
n
t
e
r
_
_
(
)
\_\_enter\_\_()
__enter__() 方法将始终返回
s
e
l
f
self
self —— 这是
w
i
t
h
with
with 语块将用于调用属性和方法的对象。
③ 在
w
i
t
h
with
with 语块结束后,文件对象将自动关闭。怎么做到的?在
_
_
e
x
i
t
_
_
(
)
\_\_exit\_\_()
__exit__() 方法中调用了
s
e
l
f
.
c
l
o
s
e
(
)
self.close()
self.close() 。
该
_
_
e
x
i
t
_
_
(
)
\_\_exit\_\_()
__exit__() 方法将总是被调用,哪怕是在
w
i
t
h
with
with 语块中引发了例外。实际上,如果引发了例外,该例外信息将会被传递给
_
_
e
x
i
t
_
_
(
)
\_\_exit\_\_()
__exit__() 方法。
11.真正神奇的东西
如果知道自己在干什么,你几乎可以完全控制类是如何比较的、属性如何定义,以及类的子类是何种类型。
* 确切掌握 P y t h o n Python Python 何时调用 _ _ d e l _ _ ( ) \_\_del\_\_() __del__() 特别方法 是件难以置信的复杂事情。要想完全理解它,必须清楚 P y t h o n Python Python 如何在内存中跟踪对象。以下有一篇好文章介绍 P y t h o n Python Python 垃圾收集和类析构器。