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
1
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
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
3
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
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
1
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
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
7
1.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))
1
class 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))
3
class 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))
5
class 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))
7
class 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))
9
1.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
2
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
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
4
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
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
9
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)
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)
1
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)
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)
3
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)
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)
5
1.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
0
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
1
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
2
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
3
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
4
class 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
0
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
1
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
2
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
3
Vector([3.1, 4.2])
4
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
5
class Vector(VectorBase,DimensionMixin,AbsMixin, LiteralMixin,CodecMixin): pass
0
1.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])
4
Vector([3.1, 4.2])
5
Vector([3.1, 4.2])
6
1.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))
5
Vector((3, 4, 5))
6
Vector((3, 4, 5))
7
Vector((3, 4, 5))
8
Vector((3, 4, 5))
9
Vector([3.0, 4.0, 5.0])
0
Vector((3, 4, 5))
9
Vector([3.0, 4.0, 5.0])
2
Vector([3.0, 4.0, 5.0])
3
Vector([3.0, 4.0, 5.0])
4
Vector([3.0, 4.0, 5.0])
5
1.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
00
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
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
02
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
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
还没有评论,来说两句吧...