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]:     # 列表迭代器 

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)
 in ()
----> 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 数据模型的特殊方法还有很多, 本书会涵盖其中的绝大部分, 探讨如何使用和实现它们。

你可能感兴趣的:(fluent python-第 1 章 1.2 如何使用特殊方法/模拟数值类型)