读书笔记:《流畅的Python》第10章 序列的修改、散列、切片

# 第十章 序列的修改、散列和切片

"""
本章定义表示多维向量的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,也不知道为了啥?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值