一、核心要义
以第九章定义的二维向量为基础,定义表示多为向量的Vector类。该类将支持如下功能:
1. 基本的序列协议
2. 适当的切片支持,且返回的是新Vector实例
3.综合各个元素的值计算散列值
4.格式化展示
二、代码示例
1、前情提要
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/17 13:56
# @Author : Maple
# @File : 00-相关背景知识.py
# @Software: PyCharm
class MySeq:
def __getitem__(self, index):
return index
if __name__ == '__main__':
# mylist = [1,2,3,4,5,6]
# print(mylist[1:5:2]) # [2, 4]
# 1. 自定义序列的getitem方法中的index测试
s = MySeq()
## 1-1 单个索引,index返回的是整数
print(s[1])
## 1-2 1:4 返回的是slice(1, 4, None),其中1是序列的起始位置,4是结束位置,而且是左闭右开,即[1,4)
print(s[1:4]) # slice(1, 4, None)
## 1-3 1:4:2 返回的是slice(1, 4, 2),其中1是序列的起始位置,4是结束位置,2代表步长
print(s[1:4:2]) # slice(1, 4, 2)
## 1-4 复合切片1
print(s[1:4:2,9]) # (slice(1, 4, 2), 9)
#1-5 复合切片2
print(s[1:4:2,7:9]) #(slice(1, 4, 2), slice(7, 9, None))
# 2.slice测试
"""slice是Python内置的类型
"""
##2-1 slice有一个indices方法, 以及start,step和stop三个数据属性
print(dir(slice)) # [..'indices', 'start', 'step', 'stop']]
##2-2 而indices方法主要是用于优雅地处理缺失索引和负索引,以及长度超过目标序列的切片
### 2-2-1 对一个长度为5的序列,进行切片,其中起始位置缺失,结束位置为10(很显然已经超过了序列长度),步长为2
### indices方法会帮忙将切片调整为 :(0, 5, 2)
print(slice(None,10,2).indices(5))
### 2-2-2 对一个长度为5的序列,进行切片,其中起始位置为-3,结束位置未知,步长未知
### indices方法会帮忙将切片调整为 :(2, 5, 1)
print(slice(-3,None,None).indices(5))
# 3. 格式化测试
name = 'maple'
age = 18
print('{name!r} and {age!r}'.format(name= name,age= age)) # 'maple' and 18
print('{!r} and {!r}'.format(name,age)) # 'maple' and 18,不能使用关键字参数传递
print('{name!r} and {!r}'.format(age,name=name)) # 'maple' and 18,positional argument要放在前面
2、Vector类的改造
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/17 14:19
# @Author : Maple
# @File : 01-Vector类的改造.py
# @Software: PyCharm
"""相对于第9章的Vector类-构造方法是传入形如(1,2)这样的二元元组
本章的Vector类
-(1)首先是可以接收任意多个参数
-(2)构造方法传入的是可迭代的对象
"""
import math
import reprlib
from array import array
class Vector:
typecode = 'd'
def __init__(self,components):
self._components = array(self.typecode,components)
def __repr__(self):
# 返回的components是str类型
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __iter__(self):
return iter(self._components)
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __bool__(self):
return bool(abs(self))
def __abs__(self):
return math.sqrt(sum(x * x for x in self._components))
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
if __name__ == '__main__':
v = Vector([1,2,3,4])
print('*******1. repr测试*****************')
# 1. repr测试 :如果没有定义str方法,print(v)会调用__repr__方法
print(v) # Vector([1.0, 2.0, 3.0, 4.0])
# 2.str测试
print('*******2. str测试*****************')
print(v) # (1.0, 2.0, 3.0, 4.0)
# 3.相等性测试
print('*******3.相等性测试*****************')
v2 = Vector([1,2,3,4])
print(v is v2) # False,不同的对象
print(v == v2) # False,值相等
# 4.迭代测试
print('*******4.迭代测试****************')
for i in v:
"""1.0
2.0
3.0
4.0
"""
print(i)
# 5.模长(abs)测试
print('*******5.模长(abs)测试****************')
print(abs(v)) # 5.477225575051661
# 6. bytes测试
print('*******6. bytes测试****************')
print(bytes(v)) # b'd\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@
#7. frombytes测试
print('*******7. frombytes测试****************')
v_byte = bytes(v)
v_clone = Vector.frombytes(v_byte)
print(v ==v_clone)
# 8. 切片测试: 未实现getitem方法,无法使用切片
print(v[1:3]) # TypeError: 'Vector' object is not subscriptable
3、Vector类切片功能实现
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/17 14:57
# @Author : Maple
# @File : 02-Vector类切片功能实现.py
# @Software: PyCharm
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/17 14:19
# @Author : Maple
# @File : 01-Vector类的改造.py
# @Software: PyCharm
"""相对于第9章的Vector类-构造方法是传入形如(1,2)这样的二元元组
本章的Vector类
-(1)首先是可以接收任意多个参数
-(2)构造方法传入的是可迭代的对象
"""
import math
import numbers
import reprlib
from array import array
class Vector:
"""切片返回数组"""
typecode = 'd'
def __init__(self,components):
self._components = array(self.typecode,components)
def __repr__(self):
# 返回的components是str类型
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __iter__(self):
return iter(self._components)
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __bool__(self):
return bool(abs(self))
def __abs__(self):
return math.sqrt(sum(x * x for x in self._components))
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
def __len__(self):
return len(self._components)
def __getitem__(self, index):
# index是一个slice类型数据,返回数组
return self._components[index]
class Vector2:
"""切片仍然返回Vector类型数据"""
typecode = 'd'
def __init__(self,components):
self._components = array(self.typecode,components)
def __repr__(self):
# 返回的components是str类型
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __iter__(self):
return iter(self._components)
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __bool__(self):
return bool(abs(self))
def __abs__(self):
return math.sqrt(sum(x * x for x in self._components))
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index,slice):
return cls(self._components[index])
elif isinstance(index,numbers.Integral):
return self._components[index]
else:
msg = '{cls.__name__} indices must be integers'
return TypeError(msg.format(cls = cls))
if __name__ == '__main__':
v = Vector([1,2,3,4,5])
# 1. 切片测试:返回的是数组
print(v[1]) # 2.0
print(v[1:3]) # array('d', [2.0, 3.0])
print(v[1:5:2]) # array('d', [2.0, 4.0])
# slice(1,5,2)
print(v[-4:10:2]) # array('d', [2.0, 4.0])
# 2. 切片测试:返回的是Vector类型数据
v2 = Vector2([1, 2, 3, 4, 5])
print(v2[1]) # 2.0
print(v2[1:3]) # (2.0, 3.0)
print(v2[1:5:2]) # (2.0, 4.0)
print(v2[1.0]) # Vector2 indices must be integers
4、Vector类动态存取属性
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/17 15:10
# @Author : Maple
# @File : 03-Vector类动态存取属性.py
# @Software: PyCharm
import numbers
import reprlib
from array import array
class Vector:
"""实现能够通过单个变量访问前几个分量,比如 v.x,v.y等
实现方式: getattr方法
"""
typecode = 'd'
shortcut_names = 'xyzt'
def __init__(self,components):
self._components = array(self.typecode,components)
def __repr__(self):
# 返回的components是str类型
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __iter__(self):
return iter(self._components)
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __bool__(self):
return bool(abs(self))
def __abs__(self):
return math.sqrt(sum(x * x for x in self._components))
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index,slice):
return cls(self._components[index])
elif isinstance(index,numbers.Integral):
return self._components[index]
else:
msg = '{cls.__name__} indices must be integers'
return TypeError(msg.format(cls = cls))
def __getattr__(self, item):
# 只有当v.x实例不存在x属性时,才会调用getattr
cls = type(self)
if len(item) == 1:
position = cls.shortcut_names.find(item)
if 0 <= position < len(self._components):
return self._components[position]
msg = '{.__name__!r} object has not attribute {!r}'
raise AttributeError(msg.format(cls,item))
class Vector2:
"""实现能够通过单个变量访问前几个分量,比如 v.x,v.y等
实现方式: getattr方法
"""
typecode = 'd'
shortcut_names = 'xyzt'
def __init__(self, components):
self._components = array(self.typecode, components)
def __repr__(self):
# 返回的components是str类型
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __iter__(self):
return iter(self._components)
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __bool__(self):
return bool(abs(self))
def __abs__(self):
return math.sqrt(sum(x * x for x in self._components))
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
msg = '{cls.__name__} indices must be integers'
return TypeError(msg.format(cls=cls))
def __getattr__(self, item):
# 只有当v.x实例不存在x属性时,才会调用getattr
cls = type(self)
if len(item) == 1:
position = cls.shortcut_names.find(item)
if 0 <= position < len(self._components):
return self._components[position]
msg = '{.__name__!r} object has not attribute {!r}'
raise AttributeError(msg.format(cls, item))
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
# 限制修改'xyzt'单字母属性值
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
# 限制修改单字母(a-z)的属性值
error = "can't set attributes 'a' to 'z' in {cls_name}!r"
else:
error = ''
if error:
msg = error.format(cls_name=cls, attr_name=name)
raise AttributeError(msg)
# 允许修改名字为其它值的属性
super().__setattr__(name, value)
if __name__ == '__main__':
# 1.未设置setattr方法时的测试
v = Vector([1,2,3,4,5])
print(v.x) # 1.0
# print(v.q) # 'Vector' object has not attribute 'q'
v.x = 10
# 当设置了v.x = 10之后,相当于给v实例新增了属性x(self.x = x),之后再访问v.x,不会走getattr方法,而是直接访问实例v的属性x的值
print(v.x) # 10
# 但是v的第一个分量值仍然为1.0,并没有发生变化(也正常,毕竟getattr方法也没有修改_components的值)
# 但是上述两条结果时矛盾的:v.x(本意是对应实例v的第一个分量)的值发生了变化,但是通过直接访问v-的第一个分量值却又没有发生变化
# 为了避免上述矛盾,可以通过setattr方法,限制修改`单字母`属性的值
print(v) # (1.0, 2.0, 3.0, 4.0, 5.0)
# 2. 设置setattr方法时的测试
print('*********2. 设置setattr方法时的测试************')
v2 = Vector2([1, 2, 3, 4, 5])
print(v2.x)
## 2-1 尝试修改v2.x的值
#v2.x = 10 # AttributeError: readonly attribute 'x'
## 2-2 尝试修改[a-z]之间某个单值属性的值
#v2.a = 100 # AttributeError: can't set attributes 'a' to 'z' in <class '__main__.Vector2'>!r
## 2-3 尝试修改名字为其它值的属性
v2.name = 'Maple'
# 修改成功
print(v2.name) # Maple
# 3.散列测试
v3 = Vector2([1, 2, 3, 4, 5])
v4 = Vector2([1, 2, 3, 4, 5])
# 未实现hash方法,不能散列化
set([v3,v4]) #TypeError: unhashable type: 'Vector2'
5、Vector类实现散列化
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/17 17:26
# @Author : Maple
# @File : 04-Vector类实现散列化.py
# @Software: PyCharm
import functools
import numbers
import operator
import reprlib
from array import array
"""
实现__eq__(已实现,为提升效率,改写以下)和__hash__
"""
class Vector:
typecode = 'd'
shortcut_names = 'xyzt'
def __init__(self, components):
self._components = array(self.typecode, components)
def __repr__(self):
# 返回的components是str类型
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __iter__(self):
return iter(self._components)
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __bool__(self):
return bool(abs(self))
def __abs__(self):
return math.sqrt(sum(x * x for x in self._components))
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
msg = '{cls.__name__} indices must be integers'
return TypeError(msg.format(cls=cls))
def __getattr__(self, item):
# 只有当v.x实例不存在x属性时,才会调用getattr
cls = type(self)
if len(item) == 1:
position = cls.shortcut_names.find(item)
if 0 <= position < len(self._components):
return self._components[position]
msg = '{.__name__!r} object has not attribute {!r}'
raise AttributeError(msg.format(cls, item))
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
# 限制修改'xyzt'单字母属性值
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
# 限制修改单字母(a-z)的属性值
error = "can't set attributes 'a' to 'z' in {cls_name}!r"
else:
error = ''
if error:
msg = error.format(cls_name=cls, attr_name=name)
raise AttributeError(msg)
# 允许修改名字为其它值的属性
super().__setattr__(name, value)
def __eq__(self, other):
# 如果分量太多,下面这种方式效率太低
# return tuple(self) == tuple(other)
if len(self) != len(other):
return False
for x, y in zip(self, other):
if x != y:
return False
return True
def __hash__(self):
# 生成一个迭代器
hashes = (hash(x) for x in self._components)
return functools.reduce(operator.xor, hashes, 0)
# 等价于下面的写法
# return functools.reduce(lambda x,y : x *y ,hashes,0)
if __name__ == '__main__':
v1 = Vector([1, 2, 3, 4, 5])
v2 = Vector([1, 2, 3, 4, 5])
print(set([v1, v2])) # {Vector([1.0, 2.0, 3.0, 4.0, 5.0])}
6、Vector类格式化
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/17 17:38
# @Author : Maple
# @File : 05-Vector类格式化.py
# @Software: PyCharm
import functools
import itertools
import math
import numbers
import operator
import reprlib
from array import array
class Vector:
typecode = 'd'
shortcut_names = 'xyzt'
def __init__(self, components):
self._components = array(self.typecode, components)
def __repr__(self):
# 返回的components是str类型
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __iter__(self):
return iter(self._components)
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __bool__(self):
return bool(abs(self))
def __abs__(self):
return math.sqrt(sum(x * x for x in self._components))
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
msg = '{cls.__name__} indices must be integers'
return TypeError(msg.format(cls=cls))
def __getattr__(self, item):
# 只有当v.x实例不存在x属性时,才会调用getattr
cls = type(self)
if len(item) == 1:
position = cls.shortcut_names.find(item)
if 0 <= position < len(self._components):
return self._components[position]
msg = '{.__name__!r} object has not attribute {!r}'
raise AttributeError(msg.format(cls, item))
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
# 限制修改'xyzt'单字母属性值
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
# 限制修改单字母(a-z)的属性值
error = "can't set attributes 'a' to 'z' in {cls_name}!r"
else:
error = ''
if error:
msg = error.format(cls_name=cls, attr_name=name)
raise AttributeError(msg)
# 允许修改名字为其它值的属性
super().__setattr__(name, value)
def __eq__(self, other):
# 如果分量太多,下面这种方式效率太低
# return tuple(self) == tuple(other)
if len(self) != len(other):
return False
for x, y in zip(self, other):
if x != y:
return False
return True
def __hash__(self):
# 生成一个迭代器
hashes = (hash(x) for x in self._components)
return functools.reduce(operator.xor, hashes, 0)
# 等价于下面的写法
# return functools.reduce(lambda x,y : x *y ,hashes,0)
# 计算n维球体的某个角坐标
def angle(self,n):
r = math.sqrt(sum(x * x for x in self[n:]))
a = math.atan2(r,self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - 1
else:
return a
def angles(self):
return (self.angle(n) for n in range(1,len(self)))
def __format__(self, fmt_spec):
if fmt_spec.endswith('h'): # 超球面体
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)],self.angles())
outer_format = '<{}>'
else:
coords = self
outer_format = '({})'
components = (format(c,fmt_spec) for c in coords)
return outer_format.format(', '.join(components))
if __name__ == '__main__':
# 格式化输出测试
v1 = Vector([1, 2, 3, 4, 5])
# 球面极坐标输出
print(format(v1,'.2fh')) # <7.42, 1.44, 1.30, 1.13, 0.90>
# 其它正常格式输出
print(format(v1,'.2f')) # (1.00, 2.00, 3.00, 4.00, 5.00)
print(format(v1,'.2e')) #(1.00e+00, 2.00e+00, 3.00e+00, 4.00e+00, 5.00e+00)