fluent python-第 1 章 1.2 如何使用特殊方法/模拟数值类型

特殊方法__xxx__

首先明确一点, 特殊方法的存在是为了被 Python 解释器调用的, 你自己并不需要调用它们。 也就是说比如没有 my_object.__len__()这种写法,而应该使用 len(my_object)。 在执行 len(my_object)的时候, 如果my_object 是一个自定义类的对象, 那么 Python 会自己去调用其中由你实现的__len__ 方法。
然而如果是 Python 内置的类型, 比如列表( list) 、 字符串( str) 、字节序列( bytearray) 等, 那么 CPython 会抄个近路,__len__实际上会直接返回 PyVarObject 里的 ob_size属性。PyVarObject 是表示内存中长度可变的内置对象的 C 语言结构体。 直接读取这个值比调用一个方法要快很多。
很多时候, 特殊方法的调用是隐式的, 比如 for i in x:这个语句,背后其实用的是iter(x), 而这个函数的背后则是x.__iter__()方法。 当然前提是这个方法在 x 中被实现了。
注意:调用__xxx__特殊方法的前提是你自带 xxx 方法。某些对象自带有__yyy__方法,这种调用时必须还得是 __yyy__ 方法

In [98]: x = [1,2,3]

In [99]: iter(x)
Out[99]: <list_iterator at 0x10f830208>    # 列表迭代器 

In [100]: x
Out[100]: [1, 2, 3]

如下两段代码对比,可以看出特殊方法的作用:

In [89]: class A(object):
    ...:     def __init__(self):
    ...:         self.aaa = 'i love you'
    ...:     def getName(self):
    ...:         return 'A '+self.aaa
    ...:     def __len__(self):
    ...:         return len(self.aaa)
    ...:
    ...:

In [90]: test = A()

In [91]: len(test)
Out[91]: 10

对比:

In [92]: class A(object):
    ...:     def __init__(self):
    ...:         self.aaa = 'i love you'
    ...:     def getName(self):
    ...:         return 'A '+self.aaa
    ...:
    ...:
    ...:

In [93]: test = A()

In [94]: len(test)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-94-0d22009e0a68> in <module>()
----> 1 len(test)

TypeError: object of type 'A' has no len()

通常你的代码无需直接使用特殊方法。 除非有大量的元编程存在, 直接调用特殊方法的频率应该远远低于你去实现它们的次数。 唯一的例外可能是 __init__方法, 你的代码里可能经常会用到它, 目的是在你自己的子类的__init__方法中调用超类的构造器。

通过内置的函数( 例如 len、 iter、 str, 等等) 来使用特殊方法是最好的选择。 这些内置函数不仅会调用特殊方法, 通常还提供额外的好处, 而且对于内置的类来说, 它们的速度更快。 14.12 节中有详细的例子。
不要自己想当然地随意添加特殊方法, 比如 __foo__之类的, 因为虽然现在这个名字没有被 Python 内部使用, 以后就不一定了。


模拟数值模型

为了给这个类设计 API, 我们先写个模拟的控制台会话来做 doctest(文档测试)。 下
面这一段代码就是向量加法:

>>> v1 = Vector(2, 4)
>>> v2 = Vector(2, 1)
>>> v1 + v2
Vector(4, 5)

abs 是一个内置函数, 如果输入是整数或者浮点数, 它返回的是输入值的绝对值; 如果输入是复数( complex number) , 那么返回这个复数的模。 为了保持一致性, 我们的 API 在碰到 abs 函数的时候, 也应该返回该向量的模:

>>> v = Vector(3, 4)
>>> abs(v)
5.0

我们还可以利用 * 运算符来实现向量的标量乘法( 即向量与数的乘法,得到的结果向量的方向与原向量一致 , 模变大) :

>>> v * 3
Vector(9, 12)
>>> abs(v * 3)
15.0

好了,现在我们开始写代码

# 一个简单的二维向量 类
from math import hypot
class Vector:
    def __init__(self,x=0,y=0):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Vecotr(%s,%s)' %(self.x, self.y)

    def __abs__(self):
        return hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

    def __add__(self, other):
        x = self.x + other.x
        y = self.x + other.y
        return Vector(x, y)

    def __mul__(self, other):
        return Vector(self.x * scalar, self.y * scalar)

为什么len不是普通方法

如果 x 是一个内置类型的实例, 那么 len(x) 的速度会非常快。 背后的原因是 CPython 会直接从一个 C 结构体里读取对象的长度, 完全不会调用任何方法。 获取一个集合中元素的数量是一个很常见的操作, 在 str、 list、 memoryview 等类型上, 这个操作必须高效。
换句话说,len之所以不是一个普通方法, 是为了让 Python 自带的数据结构可以走后门,abs也是同理。 但是多亏了它是特殊方法, 我们也可以把 len 用于自定义数据类型。 这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点, 也印证了“Python 之禅”中的另外一句话: “不能让特例特殊到开始破坏既定规则。 ”


总结

通过实现特殊方法, 自定义数据类型可以表现得跟内置类型一样, 从而让我们写出更具表达力的代码——或者说, 更具 Python 风格的代码
Python 对象的一个基本要求就是它得有合理的字符串表示形式, 我们可以通过__repr____str__ 来满足这个要求。 前者方便我们调试和记录日志, 后者则是给终端用户看的。 这就是数据模型中存在特殊方法__repr____str__ 的原因。

对序列数据类型的模拟是特殊方法用得最多的地方, 这一点在FrenchDeck 类的示例中有所展现。 在第 2 章中, 我们会着重介绍序列数据类型, 然后在第 10 章中, 我们会把 Vector 类扩展成一个多维的数据类型, 通过这个练习你将有机会实现自定义的序列。

Python 通过运算符重载这一模式提供了丰富的数值类型, 除了内置的那些之外, 还有 decimal.Decimalfractions.Fraction。 这些数据类型都支持中缀算术运算符。 在第 13 章中, 我们还会通过对 Vector类的扩展来学习如何实现这些运算符, 当然还会提到如何让运算符满足交换律和增强赋值。

Python 数据模型的特殊方法还有很多, 本书会涵盖其中的绝大部分, 探讨如何使用和实现它们。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容