1. 自定义序列Vector
本篇通过自定义Vector来看如何使用组合模式实现Vector类,而不使用继承.既然是使用组合,那么我们首先想到的就是Mixin.
向量的分量存储在浮点数数组中,而且还将实现不可变扁平序列所需的方法.
不过,在实现序列方法之前,我们要确保Vector类与之前定义的Vector2D类兼容,除非有些地方让二者兼容没有什么意义.
1.1. 第一版--与Vector2D兼容
from array import array from typing import Sequence,Optional,Iterator import reprlib from math import sqrt class VectorBase: typecode:str = 'd' _components:Optional[array]=None def __init__(self, components:Sequence): self._components = array(self.typecode, components) self._dimension = None def __iter__(self)->Iterator: return iter(self._components) def __bool__(self)->bool: return bool(abs(self)) class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass Vector([3.1, 4.2]) Vector([3.1, 4.2]) Vector((3, 4, 5)) Vector([3.0, 4.0, 5.0]) class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 0 class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 1class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 2 class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 3class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 4 class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 1class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 6 class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 71.2. 第二版--实现可切片的序列
实现可切片需要实现__len__ 和__getitem__,我们希望切片后得到的还是Vector.实际上切片是通过slice实现
class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 8 class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 9 class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 0 class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 1class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 2 class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 3class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 4 class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 5class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 6 class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 7class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 8 class AbsMixin: def __abs__(self)->float: return sqrt(sum(x * x for x in self)) 91.2.1. 切片原理
slice是内置的类型.它有start、stop 和step数据属性,以及indices方法.
indices这个方法有很大的作用,但是鲜为人知.help(slice.indices)给出的信息如下:
from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 0 给定长度为len的序列,计算S表示的扩展切片的起始(start)和结尾(stop)索引,以及步幅(stride).超出边界的索引会被截掉,这与常规切片的处理方式一样.
换句话说,indices方法开放了内置序列实现的棘手逻辑,用于优雅地处理缺失索引和负数索引,以及长度超过目标序列的切片.这个方法会"整顿"元组,把start、stop 和stride都变成非负数,而且都落在指定长度序列的边界内.
from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 1 from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 2from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 3 from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 4from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 5 from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 6 from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 7 from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 8 from typing import Optional from array import array class LiteralMixin: _components:Optional[array]=None def __str__(self)->str: return str(tuple(self)) def __repr__(self)->str: """ 如果 Vector 实例的分量超过 6 个,`repr()` 生成的字符串就会使用 ... 省略一 部分, 包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的 (因此不想让大型对象在控制台或日 志中输出几千行内容). 使用 reprlib 模块可以生成长度有限的表示形式. """ components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components) def __format__(self,fmt_spec='')->str: return NotImplemented 9from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 0 from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 1from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 2 from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 3from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 4 from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 51.3. 第三版 动态存取属性
Vector2D变成Vector之后,就没办法通过名称访问向量的分量了(如v.x 和v.y).现在我们处理的向量可能有大量分量.不过,若能通过单个字母访问前几个分量的话会比较方便.比如,用x、y和z代替v[0]、v[1] 和v[2].
我们想额外提供下述句法,用于读取向量的前四个分量:
from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 6 在Vector2D中,我们使用@property装饰器把x和y标记为只读特性.我们可以在Vector中编写四个特性,但这样太麻烦.特殊方法__getattr__提供了更好的方式.
属性查找失败后,解释器会调用__getattr__方法.简单来说,对my_obj.x表达式:
- Python会检查my_obj实例有没有名为x的属性
- 如果没有,到类(
my_obj.__class__)中查找 - 如果还没有,顺着继承树继续查找
- 如果依旧找不到,调用myobj所属类中定义的`_getattr`方法,传入self 和属性名称的字符串形式(如'x')
下例中列出的是我们为Vector类定义的__getattr__方法.这个方法的作用很简单,它检查所查找的属性是不是xyzt中的某个字母
from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 7 from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 8 from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 9 class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 0class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 1 class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 2class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 3 class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 4class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 5 class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 0可以看到,为v.x 赋值没有抛出错误但是前后矛盾.上面之所以前后矛盾是__getattr__的运作方式导致的:
仅当对象没有指定名称的属性时,Python才会调用那个方法,这是一种后备机制.
可是像v.x = 10这样赋值之后v对象有x属性了,因此使用v.x获取x属性的值时不会调用__getattr__方法了,解释器直接返回绑定到v.x上的值即10.另一方面,__getattr__方法的实现没有考虑到self._components之外的实例属性,而是从这个属性中获取shortcut_names中所列的"虚拟属性".
为了避免这种前后矛盾的现象,我们要改写mixin中设置属性的逻辑
多数时候,如果实现了__getattr__方法,那么也要定义__setattr__方法,以防对象的行为不一致
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 7 class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 8 from array import array class CodecMixin: typecode:str _components:Optional[array] def __bytes__(self)->bytes: return (bytes([ord(self.typecode)]) + bytes(self._components)) @classmethod def frombytes(cls, octets:bytes)->'VectorBase': typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) 9 class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 0class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 1 class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 2class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 3 Vector([3.1, 4.2]) 4class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 5 class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass 01.4. Vector类第4版:散列和快速等值测试
我们要再次实现__hash__方法.加上现有的__eq__方法,这会把Vector实例变成可散列的对象.
我们的散列方式就是计算各个分量的散列值,然后聚合求异或
Vector([3.1, 4.2]) 7 Vector([3.1, 4.2]) 8 1.5. 但是还没暖点每次这这有 Vector类第5版:格式化
Vector类的__format__方法与Vector2D类的相似,但是不使用极坐标,而使用超球面坐标,因为Vector类支持n个维度,而超过四维后,球体变成了"超球体".
因此,我们会把自定义的格式后缀由'p'变成'h'
Vector([3.1, 4.2]) 9 Vector([3.1, 4.2]) 0 Vector([3.1, 4.2]) 1 Vector([3.1, 4.2]) 2 Vector([3.1, 4.2]) 3 Vector([3.1, 4.2]) 4Vector([3.1, 4.2]) 5 Vector([3.1, 4.2]) 61.6. Vector类第6版:运算符重载
向量的求反运算就是每位求反
向量的求和运算就是对应位求和.
向量的标量乘法就是每位乘以一个常数
向量点乘则是各位相乘后再相加
Vector([3.1, 4.2]) 7 Vector([3.1, 4.2]) 8 Vector([3.1, 4.2]) 9 Vector((3, 4, 5)) 0 Vector((3, 4, 5)) 1 Vector((3, 4, 5)) 2 Vector((3, 4, 5)) 3 Vector((3, 4, 5)) 4 Vector((3, 4, 5)) 5Vector((3, 4, 5)) 6 Vector((3, 4, 5)) 7Vector((3, 4, 5)) 8 Vector((3, 4, 5)) 9Vector([3.0, 4.0, 5.0]) 0 Vector((3, 4, 5)) 9Vector([3.0, 4.0, 5.0]) 2 Vector([3.0, 4.0, 5.0]) 3Vector([3.0, 4.0, 5.0]) 4 Vector([3.0, 4.0, 5.0]) 51.7. Vector类第7版:比较符号
使用==或者!=判断两个向量是否一致
Vector([3.0, 4.0, 5.0]) 6 Vector([3.0, 4.0, 5.0]) 7 Vector([3.0, 4.0, 5.0]) 8 Vector([3.0, 4.0, 5.0]) 9 class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 00class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 01 class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 02class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 03 class DimensionMixin: _components:Optional[array]=None _dimension:Optional[int]=None def __len__(self)->int: return len(self._components) @property def dimension(self)->int: if not self._dimension: self._dimension = len(self) return self._dimension 02 



还没有评论,来说两句吧...