# 第十章 序列的修改、散列和切片
"""
本章定义表示多维向量的Vector类:
它将实现以下功能:
基本的序列协议,__len__,__getitem__
正确表述拥有很多元素的实例
适当的切片支持,用于生成Vector实例
总和各个元素的值,计算散列值
自定义的格式扩展语言
属性的动态读取,__getattr__
"""
# 10.1 Vector类:用户定义的序列类型
"""
向量的分量存储在浮点数数组中,还将实现不可变扁平序列所需的方法
确保和前一章的Vector2d类兼容,除非兼容没有意义
"""
# 10.2 Vector类第一版:于Vector2d兼容
# vector_v1.py
# 10.3 协议和鸭子类型
"""
协议:
非正式的接口,只在文档中定义,在代码中不定义
例如python的序列协议只需要实现了__len__和__getitem__两个方法
任何类(如Spam)只要使用标准的签名和语义实现了这两个方法
就能用在任何期待序列的地方,Spam是不是某个类的子类无关紧要,只要提供了所需的方法即可
协议是非正式的,没有强制力
"""
# 示例10-3
"""
import collections
Card = collections.namedtuple('card',['rank','suit'])
class FrenchDeck:
ranks = [str(x) for x in range(2,11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split() # 黑桃 方块 梅花 红心
def __init__(self):
self._cards = [Card(rank,suit) for suit in self.suits for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
"""
"""
FrenchDeck是一个序列类型,因为它实现了序列协议,
我们说它是序列,因为它的行为像序列,这就是鸭子类型
"""
# 10.4 Vetor第二版:可以切片的序列
# vector_v2.py
# 10.4.1切片原理
# 示例10-4 __getitem__和切片的行为
class MySeq:
def __getitem__(self, index):
'''定义__getitem__方法直接返回传入的参数,index'''
return index
s = MySeq()
print(s[1]) # 1
print(s[1:4]) # slice(1, 4, None)
print(s[1:4:2]) # slice(1, 4, 2)
print(s[1:4:2, 9]) # (slice(1, 4, 2), 9)
print(s[1:4:2, 7:9]) # (slice(1, 4, 2), slice(7, 9, None))
# 传入两个参数([,])会得到一个元组,元组中甚至可以有两个切片对象
print(slice) # 审查slice对象 <class 'slice'>
print(dir(slice)) # 审查slice对象,发现它有start,stop,step,indices属性
"""
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', 'indices', 'start', 'step', 'stop']
"""
help(slice.indices) # 查看indices的帮助
"""
indices(...)
S.indices(len) -> (start, stop, stride)
Assuming a sequence of length len, calculate the start and stop
indices, and the stride length of the extended slice described by
S. Out of bounds indices are clipped in a manner consistent with the
handling of normal slices.
"""
# S.indices(len) -> (start, stop, stride)能得到切片的起始位置和终止位置以及步长
# 并且能优雅地处理索引越界的问题,使切片的索引落在序列长度的边界内
# 假设有长度为5的序列s = 'abcde'
print(slice(None, 10, 2).indices(5)) # (0, 5, 2)
print(slice(-3, None, None).indices(5)) # (2, 5, 1)
# s[:10:2]等同于s[0,5,2]
# s[-3:]等同于s[0,5,2]
# 10.5 Vetor类第三版:动态存取属性 vetor_v3.py
"""
为了像Vector2d类使用实例.x,实例.y的方法读取属性,例如
使用v.x,v.y,v.z,v.t读取前四个分量
Vector2d使用的是@property的方式,Vector类分量太多,不能使用这种方式
而是使用__getattr__来是实现
"""
"""
简单来说对于 my_obj.x表达式,python会检查实例my_obj有没有x属性,
如果没有,到类(my_obj.__class__)中去查找,
还是找不到则调用my_obj所属类中定义的__getattr__方法,传入self和属性名称的字符串形式如('x')
实际查找属性的过程比这个复杂
"""
# Vector类第四版:散列和快速等值测试 vetor_v4.py
import functools
# functiontools.reduce计算5!
print(functools.reduce(lambda a, b: a * b, range(1, 6)))
# 示例10-11 计算整数0~5的累计异或的三种方式
# 1
print('=====================')
n = 0
for i in range(1,6):
n ^= i
print(n) # 1
# 2
print(functools.reduce(lambda a, b: a ^ b, range(1, 6)))
# 3
import operator
print(functools.reduce(operator.xor, range(1, 6)))
# 示例10-15 zip函数的使用
print('=============================')
print(list(zip(range(3), 'ABC')))
print(list(zip(range(3), 'ABC',[100,200,300,400,500])))
from itertools import zip_longest
# 不足的部分用默认值None补充,知道最长的序列迭代完 或者用fillvalue指定的值来补充
print(list(zip_longest(range(3), 'ABC', [100, 200, 300, 400, 500])))
print(list(zip_longest(range(3), 'ABC', [100, 200, 300, 400, 500],fillvalue='?')))
print('====================================')
# Vector类第五版:格式化 vetor_v5.py
# 格式化使用超球面坐标,其中的数学知识参见维基百科,词条n维球体
# vector_v1.py
from array import array
import math
import reprlib
class Vector:
typecode = 'd' # 类属性,在Vector实例和字节序列转化时使用
def __init__(self,components):
# _components是一个受保护的属性,把Vector的各分量保存在一个数组中
self._components = array(self.typecode,components)
def __iter__(self):
# 使用self._components构建一个迭代器
return iter(self._components)
def __repr__(self):
# 通过reprlib.repr获取self.__components的有限长度表示形式
components = reprlib.repr(self._components) # 超过5个使用...表示
# 去掉前缀部分
components = components[components.find('['):-1]
return 'Vetor({})'.format(components)
def __str__(self): # 显示为一个有序对
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+ # 把typecode转换成字节序列
bytes(self._components)) # 直接使用self.components创建bytes对象
def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
return tuple(self) == tuple(other)
def __abs__(self):
# 计算平方和然后开方
return math.sqrt(sum(x for x in self))
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
#从字节序列转换成Vector实例
@classmethod # 类方法装饰器
def frombytes(cls,octets):
typecode = chr(octets[0]) # 从第一个字节读取typecode
# 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv) # 不用拆包
"""
reprlib.repr方法用于生成大型结构或者递归结构的安全表示形式
它会限制输出字符串的长度,用...的形式表示"""
if __name__ == '__main__':
my_vector = Vector((1,2,3))
print(my_vector)
my_vector = Vector(range(10))
print(my_vector)
print(abs(my_vector))
# vector_v2.py
from array import array
import math
import reprlib
import numbers
class Vector:
typecode = 'd' # 类属性,在Vector实例和字节序列转化时使用
def __init__(self,components):
# _components是一个受保护的属性,把Vector的各分量保存在一个数组中
self._components = array(self.typecode,components)
def __iter__(self):
# 使用self._components构建一个迭代器
return iter(self._components)
def __repr__(self):
# 通过reprlib.repr获取self.__components的有限长度表示形式
components = reprlib.repr(self._components) # 超过5个使用...表示
# 去掉前缀部分
components = components[components.find('['):-1]
return 'Vetor({})'.format(components)
def __str__(self): # 显示为一个有序对
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+ # 把typecode转换成字节序列
bytes(self._components)) # 直接使用self.components创建bytes对象
def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
return tuple(self) == tuple(other)
def __abs__(self):
# 计算平方和然后开方
return math.sqrt(sum(x for x in self))
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
#从字节序列转换成Vector实例
@classmethod # 类方法装饰器
def frombytes(cls,octets):
typecode = chr(octets[0]) # 从第一个字节读取typecode
# 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv) # 不用拆包
'''
==========================第二版新增部分==============================
'''
# def __len__(self):
# return len(self._components)
# def __getitem__(self, index):
# return self._components[index]
"""==========================第二版改进部分=============================="""
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index,slice):
# 如果是切片,调用类的构造方法使用_comonents的切片构建一个新的Vector对象
return cls(self._components[index])
elif isinstance(index,numbers.Integral):
# 如果是数字,返回数组_components的对应元素
return self._components[index]
else:
# 否则,抛出异常
msg = '{cls.__name__} indeices must be integers'
raise TypeError(msg.format(cls=cls))
"""
reprlib.repr方法用于生成大型结构或者递归结构的安全表示形式
它会限制输出字符串的长度,用...的形式表示
"""
if __name__ == '__main__':
my_vector = Vector((1,2,3))
print(my_vector)
my_vector = Vector(range(10))
print(my_vector)
print(abs(my_vector))
print("=======================")
print(len(my_vector))
print(my_vector[0],my_vector[-1])
print(my_vector[2:8:2])
print(my_vector[2,4])
print(my_vector[:-3])
# vector_v3.py
# 增加动态存取属性
from array import array
import math
import reprlib
import numbers
class Vector:
typecode = 'd' # 类属性,在Vector实例和字节序列转化时使用
def __init__(self,components):
# _components是一个受保护的属性,把Vector的各分量保存在一个数组中
self._components = array(self.typecode,components)
def __iter__(self):
# 使用self._components构建一个迭代器
return iter(self._components)
def __repr__(self):
# 通过reprlib.repr获取self.__components的有限长度表示形式
components = reprlib.repr(self._components) # 超过5个使用...表示
# 去掉前缀部分
components = components[components.find('['):-1]
return 'Vetor({})'.format(components)
def __str__(self): # 显示为一个有序对
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+ # 把typecode转换成字节序列
bytes(self._components)) # 直接使用self.components创建bytes对象
def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
return tuple(self) == tuple(other)
def __abs__(self):
# 计算平方和然后开方
return math.sqrt(sum(x for x in self))
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
#从字节序列转换成Vector实例
@classmethod # 类方法装饰器
def frombytes(cls,octets):
typecode = chr(octets[0]) # 从第一个字节读取typecode
# 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
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):
# 如果是切片,调用类的构造方法使用_comonents的切片构建一个新的Vector对象
return cls(self._components[index])
elif isinstance(index,numbers.Integral):
# 如果是数字,返回数组_components的对应元素
return self._components[index]
else:
# 否则,抛出异常
msg = '{cls.__name__} indeices must be integers'
raise TypeError(msg.format(cls=cls))
"""
reprlib.repr方法用于生成大型结构或者递归结构的安全表示形式
它会限制输出字符串的长度,用...的形式表示
"""
"""==========================第三版新增部分=============================="""
shortcut_names = 'xyzt'
def __getattr__(self, name):
cls = type(self)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
# 如果位置落在范围内,返回数组中对应的元素
return self._components[pos]
# 测试失败,抛出错误
msg = '{.__name__!r} object has no attribute{!r}'
raise AttributeError(msg.format(cls,name))
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = "can't set attribute 'a' to 'z' in {cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__,attr_name=name)
raise AttributeError(msg)
super().__setattr__(name,value)
if __name__ == '__main__':
my_vector = Vector(range(10))
print(my_vector.x)
print(my_vector.y)
print(my_vector.z)
print(my_vector.t)
# print(my_vector.r)
# 古怪的交互行为
print('=============')
v = Vector(range(5))
print(repr(v))
print(v.x)
v.x = 10
print(v.x)
print(repr(v))
# 读取v.x以后对其赋新值,没有报错,此时还没有定义__setattr__
# 但是vector并没有被修改,导致结果前后矛盾
# 之所以这样是查找属性的方法造成的
# 解决方法是定义__setattr__方法
# vector_v=4.py
# 增加hash和等值测试
from array import array
import math
import reprlib
import numbers
import functools
import operator
class Vector:
typecode = 'd' # 类属性,在Vector实例和字节序列转化时使用
def __init__(self,components):
# _components是一个受保护的属性,把Vector的各分量保存在一个数组中
self._components = array(self.typecode,components)
def __iter__(self):
# 使用self._components构建一个迭代器
return iter(self._components)
def __repr__(self):
# 通过reprlib.repr获取self.__components的有限长度表示形式
components = reprlib.repr(self._components) # 超过5个使用...表示
# 去掉前缀部分
components = components[components.find('['):-1]
return 'Vetor({})'.format(components)
def __str__(self): # 显示为一个有序对
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+ # 把typecode转换成字节序列
bytes(self._components)) # 直接使用self.components创建bytes对象
# def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
# return tuple(self) == tuple(other)
def __abs__(self):
# 计算平方和然后开方
return math.sqrt(sum(x for x in self))
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
#从字节序列转换成Vector实例
@classmethod # 类方法装饰器
def frombytes(cls,octets):
typecode = chr(octets[0]) # 从第一个字节读取typecode
# 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
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):
# 如果是切片,调用类的构造方法使用_comonents的切片构建一个新的Vector对象
return cls(self._components[index])
elif isinstance(index,numbers.Integral):
# 如果是数字,返回数组_components的对应元素
return self._components[index]
else:
# 否则,抛出异常
msg = '{cls.__name__} indeices must be integers'
raise TypeError(msg.format(cls=cls))
"""
reprlib.repr方法用于生成大型结构或者递归结构的安全表示形式
它会限制输出字符串的长度,用...的形式表示
"""
shortcut_names = 'xyzt'
def __getattr__(self, name):
cls = type(self)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
# 如果位置落在范围内,返回数组中对应的元素
return self._components[pos]
# 测试失败,抛出错误
msg = '{.__name__!r} object has no attribute{!r}'
raise AttributeError(msg.format(cls,name))
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = "can't set attribute 'a' to 'z' in {cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__,attr_name=name)
raise AttributeError(msg)
super().__setattr__(name,value)
"""==========================第四版新增部分=============================="""
def __hash__(self):
hashes = (hash(x) for x in self._components)
# 上一行也可以写成这样
# hashes = map(hash,self._components)
# 参数0,表示初始值,能避免redunce()of empty sequence with no initial value异常
return functools.reduce(operator.xor,hashes,0)
# 改写__eq__方法,减少处理时间和内存用量
# def __eq__(self, other):
# if len(self) != len(other):
# return False
# # zip函数返回一个由元组构成的生成器
# for a,b in zip(self,other):
# if a != b :
# return False
# return True
# 使用all再次改良__eq__
def __eq__(self, other):
return len(self) == len(other) and all(a==b for a,b in zip(self,other))
if __name__ == '__main__':
my_vector = Vector(range(10))
# vector_v5.py
# 增加__format__方法
from array import array
import math
import reprlib
import numbers
import functools
import operator
import itertools
class Vector:
typecode = 'd' # 类属性,在Vector实例和字节序列转化时使用
def __init__(self,components):
# _components是一个受保护的属性,把Vector的各分量保存在一个数组中
self._components = array(self.typecode,components)
def __iter__(self):
# 使用self._components构建一个迭代器
return iter(self._components)
def __repr__(self):
# 通过reprlib.repr获取self.__components的有限长度表示形式
components = reprlib.repr(self._components) # 超过5个使用...表示
# 去掉前缀部分
components = components[components.find('['):-1]
return 'Vetor({})'.format(components)
def __str__(self): # 显示为一个有序对
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+ # 把typecode转换成字节序列
bytes(self._components)) # 直接使用self.components创建bytes对象
# def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
# return tuple(self) == tuple(other)
def __abs__(self):
# 计算平方和然后开方
return math.sqrt(sum(x for x in self))
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
#从字节序列转换成Vector实例
@classmethod # 类方法装饰器
def frombytes(cls,octets):
typecode = chr(octets[0]) # 从第一个字节读取typecode
# 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
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):
# 如果是切片,调用类的构造方法使用_comonents的切片构建一个新的Vector对象
return cls(self._components[index])
elif isinstance(index,numbers.Integral):
# 如果是数字,返回数组_components的对应元素
return self._components[index]
else:
# 否则,抛出异常
msg = '{cls.__name__} indeices must be integers'
raise TypeError(msg.format(cls=cls))
"""
reprlib.repr方法用于生成大型结构或者递归结构的安全表示形式
它会限制输出字符串的长度,用...的形式表示
"""
shortcut_names = 'xyzt'
def __getattr__(self, name):
cls = type(self)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
# 如果位置落在范围内,返回数组中对应的元素
return self._components[pos]
# 测试失败,抛出错误
msg = '{.__name__!r} object has no attribute{!r}'
raise AttributeError(msg.format(cls,name))
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = "can't set attribute 'a' to 'z' in {cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__,attr_name=name)
raise AttributeError(msg)
super().__setattr__(name,value)
def __hash__(self):
hashes = (hash(x) for x in self._components)
# 上一行也可以写成这样
# hashes = map(hash,self._components)
# 参数0,表示初始值,能避免redunce()of empty sequence with no initial value异常
return functools.reduce(operator.xor,hashes,0)
# 改写__eq__方法,减少处理时间和内存用量
# def __eq__(self, other):
# if len(self) != len(other):
# return False
# # zip函数返回一个由元组构成的生成器
# for a,b in zip(self,other):
# if a != b :
# return False
# return True
# 使用all再次改良__eq__
def __eq__(self, other):
return len(self) == len(other) and all(a==b for a,b in zip(self,other))
"""==========================第五版新增部分=============================="""
def angel(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-a
else:
return a
def angels(self): # 创建生成器表达式,按需计算所有角坐标
return (self.angel(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.angels())
outer_fmt = '<{}>'
else:
coords = self
outer_fmt = '({})'
componets = (format(c,fmt_spec) for c in coords)
return outer_fmt.format(','.join(componets))
if __name__ == '__main__':
# format测试
# 兼容Vector2d的方式
v1 = Vector([3,4])
print(format(v1))
print(format(v1, '.2f'))
print(format(v1, '.3e'))
# 多维向量测试
v3 = Vector([3,4,5])
print(format(v3))
print(format(Vector([1, 1]), 'h'))
print(format(Vector([1, 1,1]), 'h'))
print(format(Vector([2, 2,2]), '0.5fh'))
print('================================')
print(format(Vector([-1,-1,-1,-1]), 'h')) # ???
35岁学Python,也不知道为了啥?
|