摘自流畅的python 第九章 符合Python风格的对象
什么是特殊方法它们在面向对象的Python的处处皆是。它们是一些可以让你对类添加“魔法”的特殊方法。它们经常是两个下划线包围来命名的(比如 __init__ , __lt__ )
接下来让我们在代码中了解特殊方法的妙用
vector2d_v3_slots.py
我们封装一个类 用于描述矢量 ,该类有两个私有属性 x,y 分表表示坐标
class Vector2d: def __init__(self, x, y): # 在__init__方法中把x和y转换成浮点数,尽早捕获错误,以防调用Vector2d函数时传入不当参数 self.__x = float(x) # 使用两个前导下划线(尾部没有下划线,或者有一个下划线),把属性标记为私有 self.__y = float(y) @property # 该装饰器把读值方法标记为特性 def x(self): # 读值方法与公开属性同名,都是x ''' 该装饰器装饰的变量 需要单独重构赋值方式,如果未重构将会出现一下报错 。 在此示例中 x 变量只能访问 a = Vector2d(1,1) print(a.x) a.x = 2 1.0 Traceback (most recent call last): File "/Users/coco/PycharmProjects/FluentPython/09-pythonic-obj/vector2d_v3.py", line 158, in__iter__a.x = 2 AttributeError: can't set attribute :return: ''' return self.__x # 直接返回self.__x @property def y(self): return self.__y
def __iter__(self): ''' 定义 :使该对象可迭代 示例 : a = Vector2d(1,1) x,y = a print(x,y) 1.0 1.0 print(*a) 1.0 1.0 :return: ''' # 定义__iter__方法,把Vector2d实例变成可迭代对象,这样才能拆包(例如,x,y = my_vector)。这个方法的实现方式很简单,直接调用生成器表达式一个接一个产出分量 return (i for i in (self.x, self.y))__repr__
def __repr__(self): ''' 定义 :重构交互模式下打印 (例如:Python Console下的打印) 示例 : 在Python Console 中定义如下变量 >>> a = Vector2d(1,1) >>> a Vector2d(1.0,1.0) 与 __str__ 回显并不相同 当 __str__ 方法未重构时 print() 会调用__repr__ :return: ''' class_name = type(self).__name__ # __repr__方法使用{!r}获取各个分量的表示形式,然后插值,构成一个字符串;因为Vector2d实例是可迭代的对象,所以*self会把x和y分量提供给format函数 return '{}({!r},{!r})'.format(class_name, *self)__str__
def __str__(self): ''' 定义 :重构用户打印 示例: 分别在实现与实现的情况下 运行这条语句 print(Vector2d(1,1)) 不实现该方法时 打印如下 <__main__.Vector2d object at 0x7ffc726e8fa0> 实现该方法时 打印如下 (1.0, 1.0) :return: ''' return str(tuple(self)) # 从可迭代的Vector2d实例中可以轻松地得到一个元祖,显示为一个有序对__bytes__
def __bytes__(self): ''' 定义 : 重构bytes() 方法 返回字节序列的逻辑 打印 : print(bytes(Vector2d(1,1))) b'dx00x00x00x00x00x00xf0?x00x00x00x00x00x00xf0?' :return: ''' # 为了生成字节序列,我们typecode转换成字节序列,然后 return bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)) # 迭代Vector2d实例,得到一个数组,再把数组转换成字节序列__eq__
def __eq__(self, other): ''' 定义 :重构 == 运算逻辑 示例 : print(Vector2d(1,1) == Vector2d(1,1)) True print(Vector2d(1,1) == Vector2d(1,2)) False :param other: :return: ''' # 为了快速比较所有分量,在 *** 作数中构建元祖。对Vector2d实例来说,可以这样做,不过仍有问题。参见下面的警告 # 该方法将自身转换成元祖与比较对象比较,在进行如下比较时 也会输出True,这显然是不符合预期的,可以在方法中添加对象类型的判断,这样更严谨 # print(Vector2d(1, 1) == (1, 1)) return tuple(self) == tuple(other)__abs__
def __abs__(self): ''' 定义 : 重构abs() 判断逻辑 示例 : print(abs(Vector2d(1,1))) 1.4142135623730951 :return: ''' return math.hypot(self.x, self.y) # 模是x和y分量构成的直角三角形的斜边长__bool__
def __bool__(self): ''' 定义 : 重构bool() 判断逻辑 示例 : print(bool(Vector2d(1,1))) True :return: ''' return bool(abs(self)) # __bool__方法使用abs(self)计算模,然后把结果转换成布尔值,因此,0.0是False,非零值是True__fotmat__
def __format__(self, format_spec): ''' 定义 : 重构format() 运行逻辑 示例 : print(format(Vector2d(1,1),'p')) <1.4142135623730951, 0.7853981633974483> print(format(Vector2d(1,1),'.3ep')) <1.414e+00, 7.854e-01> print(format(Vector2d(1,1),'.5fp')) <1.41421, 0.78540> :param format_spec: :return: ''' if format_spec.endswith('p'): # 如果格式代码以'p'结尾,使用极坐标 format_spec = format_spec[:-1] # 从fmt_spec中删除'p'后缀 coords = (abs(self), self.angle()) # 构建一个元祖,表示极坐标:(magnitude,angle) outer_fmt = '<{}, {}>' # 把外层格式设为一对尖括号 else: coords = self # 如果不以'p'结尾,使用self的x和y分量构建直角坐标 outer_fmt = '({}, {})' # 把外层格式设为一对圆括号 components = (format(c, format_spec) for c in coords) # 使用各个分量生成可迭代的对象,构成格式化字符串 return outer_fmt.format(*components) # 把格式化字符串代入外层格式__hash__
def __hash__(self): ''' 定义 : 试该对象可散列 示例 : v1 = Vector2d(3,4) v2 = Vector2d(3.1,4.2) print(hash(v1)) print(hash(v2)) print(set([v1,v2])) 7 384307168202284039 {Vector2d(3.1,4.2), Vector2d(3.0,4.0)} :return: ''' return hash(self.x) ^ hash(self.y)小结
以上使用的特殊方法和约定的机构,定义了行为良好且符合Python风格的类
用于获取字符串和字节序列表示形式的方法:__repr__,__str__,__format__和__bytes__把对象转换成数字的方法:__abs__,__bool__,__hash__用于测试字节序列转换和支持散列(连同__hash__方法)的__eq__运算符 完整代码
from array import array import math class Vector2d: typecode = 'd' # typecode 是类属性,在Vector2d实例和字节序列之间转换时使用 # 在类中定义__slots__属性的目的是告诉解释器:这个类多有的实例属性都在这儿了,Python 会在各个实例中使用类似元祖的结构存储实例变量, # 从而避免使用消耗内存的__dict__属性。如果有百万个实例同时活动,这样做能节省大量内存 __slots__ = ('__x', '__y') def __init__(self, x, y): # 在__init__方法中把x和y转换成浮点数,尽早捕获错误,以防调用Vector2d函数时传入不当参数 self.__x = float(x) # 使用两个前导下划线(尾部没有下划线,或者有一个下划线),把属性标记为私有 self.__y = float(y) @property # 该装饰器把读值方法标记为特性 def x(self): # 读值方法与公开属性同名,都是x ''' 该装饰器装饰的变量 需要单独重构赋值方式,如果未重构将会出现一下报错 。 在此示例中 x 变量只能访问 a = Vector2d(1,1) print(a.x) a.x = 2 1.0 Traceback (most recent call last): File "/Users/coco/PycharmProjects/FluentPython/09-pythonic-obj/vector2d_v3.py", line 158, ina.x = 2 AttributeError: can't set attribute :return: ''' return self.__x # 直接返回self.__x @property def y(self): return self.__y def __iter__(self): ''' 定义 :使该对象可迭代 示例 : a = Vector2d(1,1) x,y = a print(x,y) 1.0 1.0 print(*a) 1.0 1.0 :return: ''' # 定义__iter__方法,把Vector2d实例变成可迭代对象,这样才能拆包(例如,x,y = my_vector)。这个方法的实现方式很简单,直接调用生成器表达式一个接一个产出分量 return (i for i in (self.x, self.y)) def __repr__(self): ''' 定义 :重构交互模式下打印 (例如:Python Console下的打印) 示例 : 在Python Console 中定义如下变量 >>> a = Vector2d(1,1) >>> a Vector2d(1.0,1.0) 与 __str__ 回显并不相同 当 __str__ 方法未重构时 print() 会调用__repr__ :return: ''' class_name = type(self).__name__ # __repr__方法使用{!r}获取各个分量的表示形式,然后插值,构成一个字符串;因为Vector2d实例是可迭代的对象,所以*self会把x和y分量提供给format函数 return '{}({!r},{!r})'.format(class_name, *self) def __str__(self): ''' 定义 :重构用户打印 示例: 分别在实现与实现的情况下 运行这条语句 print(Vector2d(1,1)) 不实现该方法时 打印如下 <__main__.Vector2d object at 0x7ffc726e8fa0> 实现该方法时 打印如下 (1.0, 1.0) :return: ''' return str(tuple(self)) # 从可迭代的Vector2d实例中可以轻松地得到一个元祖,显示为一个有序对 def __bytes__(self): ''' 定义 : 重构bytes() 方法 返回字节序列的逻辑 打印 : print(bytes(Vector2d(1,1))) b'dx00x00x00x00x00x00xf0?x00x00x00x00x00x00xf0?' :return: ''' # 为了生成字节序列,我们typecode转换成字节序列,然后 return bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)) # 迭代Vector2d实例,得到一个数组,再把数组转换成字节序列 def __eq__(self, other): ''' 定义 :重构 == 运算逻辑 示例 : print(Vector2d(1,1) == Vector2d(1,1)) True print(Vector2d(1,1) == Vector2d(1,2)) False :param other: :return: ''' # 为了快速比较所有分量,在 *** 作数中构建元祖。对Vector2d实例来说,可以这样做,不过仍有问题。参见下面的警告 # 该方法将自身转换成元祖与比较对象比较,在进行如下比较时 也会输出True,这显然是不符合预期的,可以在方法中添加对象类型的判断,这样更严谨 # print(Vector2d(1, 1) == (1, 1)) return tuple(self) == tuple(other) def __abs__(self): ''' 定义 : 重构abs() 判断逻辑 示例 : print(abs(Vector2d(1,1))) 1.4142135623730951 :return: ''' return math.hypot(self.x, self.y) # 模是x和y分量构成的直角三角形的斜边长 def __bool__(self): ''' 定义 : 重构bool() 判断逻辑 示例 : print(bool(Vector2d(1,1))) True :return: ''' return bool(abs(self)) # __bool__方法使用abs(self)计算模,然后把结果转换成布尔值,因此,0.0是False,非零值是True def angle(self): # math.atan2 方法以弧度返回y / x的反正切 return math.atan2(self.y, self.x) def __format__(self, format_spec): ''' 定义 : 重构format() 运行逻辑 示例 : print(format(Vector2d(1,1),'p')) <1.4142135623730951, 0.7853981633974483> print(format(Vector2d(1,1),'.3ep')) <1.414e+00, 7.854e-01> print(format(Vector2d(1,1),'.5fp')) <1.41421, 0.78540> :param format_spec: :return: ''' if format_spec.endswith('p'): # 如果格式代码以'p'结尾,使用极坐标 format_spec = format_spec[:-1] # 从fmt_spec中删除'p'后缀 coords = (abs(self), self.angle()) # 构建一个元祖,表示极坐标:(magnitude,angle) outer_fmt = '<{}, {}>' # 把外层格式设为一对尖括号 else: coords = self # 如果不以'p'结尾,使用self的x和y分量构建直角坐标 outer_fmt = '({}, {})' # 把外层格式设为一对圆括号 components = (format(c, format_spec) for c in coords) # 使用各个分量生成可迭代的对象,构成格式化字符串 return outer_fmt.format(*components) # 把格式化字符串代入外层格式 def __hash__(self): ''' 定义 : 试该对象可散列 示例 : v1 = Vector2d(3,4) v2 = Vector2d(3.1,4.2) print(hash(v1)) print(hash(v2)) print(set([v1,v2])) 7 384307168202284039 {Vector2d(3.1,4.2), Vector2d(3.0,4.0)} :return: ''' return hash(self.x) ^ hash(self.y) @classmethod # 类方法使用classmethod装饰器修饰 def frombytes(cls, octets): # 不用传入self参数;相反,要通过cls传入类本身 typecode = chr(octets[0]) # 从第一个字节中读取typecode memv = memoryview(octets[1:]).cast(typecode) # 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换 return cls(*memv) # 拆包转换后的memoryview,得到构造方法所需的一对参数
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)