摘自流畅的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 <module>
a.x = 2
AttributeError: can't set attribute
:return:
'''
return self.__x # 直接返回self.__x
@property
def y(self):
return self.__y
__iter__
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'd\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?'
: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, in <module>
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))
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'd\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?'
: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,得到构造方法所需的一对参数
|