深入Python3(十四) 特殊方法名称

0.摘要

  在本书其它几处,我们已经见识过一些特殊方法——即在使用某些语法时 P y t h o n Python Python 所调用的“神奇”方法。使用特殊方法,类用起来如同序列、字典、函数、迭代器,或甚至像个数字!本附录为我们已经见过特殊方法提供了参考,并对一些更加深奥的特殊方法进行了简要介绍。

1.基础知识

在这里插入图片描述

  1. _ _ i n i t _ _ ( ) \_\_init\_\_() __init__() 方法的调用发生在实例被创建之后。如果要控制实际创建进程,请使用 _ _ n e w _ _ ( ) \_\_new\_\_() __new__() 方法。
  2. 按照约定, _ _ r e p r _ _ ( ) \_\_repr\_\_() __repr__() 方法所返回的字符串为合法的 P y t h o n Python Python 表达式。
  3. 在调用 p r i n t ( x ) print(x) print(x) 的同时也调用了 _ _ s t r _ _ ( ) \_\_str\_\_() __str__()方法。
  4. 由于 b y t e s bytes bytes 类型的引入而从 P y t h o n 3 Python 3 Python3 开始出现。
  5. 按照约定, 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__() 方法从零开始创建迭代器。
在这里插入图片描述

  1. 无论何时创建迭代器都将调用 _ _ i t e r _ _ ( ) \_\_iter\_\_() __iter__() 方法。这是用初始值对迭代器进行初始化的绝佳之处。
  2. 无论何时从迭代器中获取下一个值都将调用 _ _ n e x t _ _ ( ) \_\_next\_\_() __next__() 方法。
  3. _ _ 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.计算属性

在这里插入图片描述

  1. 如果某个类定义了 _ _ g e t a t t r i b u t e _ _ ( ) \_\_getattribute\_\_() __getattribute__() 方法,在每次引用属性或方法名称时 P y t h o n Python Python都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)。
  2. 如果某个类定义了 _ _ 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 已定义好的值。
  3. 无论何时给属性赋值,都会调用 _ _ s e t a t t r _ _ ( ) \_\_setattr\_\_() __setattr__() 方法。
  4. 无论何时删除一个属性,都将调用 _ _ d e l a t t r _ _ ( ) \_\_delattr\_\_() __delattr__() 方法。
  5. 如果定义了 _ _ 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),有两种方法来实现它:

  1. 告诉 x x x 将自己除以 y y y,或者
  2. 告诉 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 将会:

  1. 试着调用 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 之外的值,那已经大功告成了。
  2. 试图调用 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 运算。
  3. 试图调用 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 垃圾收集和类析构器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值