流畅的Python(十)-序列的修改、散列和切片

一、核心要义

以第九章定义的二维向量为基础,定义表示多为向量的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)

相关推荐

  1. 流畅Python()-序列修改切片

    2024-02-18 09:00:03       46 阅读
  2. 流畅Python(四)- 文本字节序列

    2024-02-18 09:00:03       38 阅读
  3. 07-7.5.2 函数构造

    2024-02-18 09:00:03       30 阅读
  4. python列表创建、访问、切片方法

    2024-02-18 09:00:03       48 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-18 09:00:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-18 09:00:03       101 阅读
  3. 在Django里面运行非项目文件

    2024-02-18 09:00:03       82 阅读
  4. Python语言-面向对象

    2024-02-18 09:00:03       91 阅读

热门阅读

  1. 关于预训练模型的一点感悟

    2024-02-18 09:00:03       51 阅读
  2. 23种设计模式之:命令模式

    2024-02-18 09:00:03       44 阅读
  3. DAY52:动态规划(打家劫舍系列)

    2024-02-18 09:00:03       53 阅读
  4. 力扣热题100_滑动窗口_3_无重复字符的最长子串

    2024-02-18 09:00:03       59 阅读
  5. 掘根宝典之C++类模板大全

    2024-02-18 09:00:03       29 阅读
  6. 【设计模式】观察者模式Observer Pattern

    2024-02-18 09:00:03       52 阅读
  7. 在Ubuntu-12.04环境下使用新的Rust开发工具

    2024-02-18 09:00:03       48 阅读
  8. UI自动化-(web入门示例)

    2024-02-18 09:00:03       47 阅读
  9. ValueError check_hostname requires server_hostname 报错

    2024-02-18 09:00:03       41 阅读
  10. WordPress Nginx 报错 502 Bad Gateway

    2024-02-18 09:00:03       211 阅读