专栏名称: VPointer
软件开发
目录
相关文章推荐
北京商报  ·  315晚会曝光“问题虾仁”已下架!此前头部主 ... ·  18 小时前  
环球网  ·  3·15曝光“保水虾仁”,磷酸盐超标! ·  20 小时前  
环球网  ·  3·15曝光“保水虾仁”,磷酸盐超标! ·  20 小时前  
中国药闻  ·  中国经济航船必将乘风破浪、行稳致远 ·  2 天前  
51好读  ›  专栏  ›  VPointer

Python学习之路29-序列的修改、散列和切片

VPointer  · 掘金  ·  · 2018-06-24 12:09

正文

Python学习之路29-序列的修改、散列和切片

《流畅的Python》笔记。

本篇是“面向对象惯用方法”的第三篇。本篇将以上一篇中的Vector2d为基础,定义多维向量Vector。

1. 前言

自定义 Vector 类的行为将与Python标准中的不可变扁平序列一样,它将支持如下功能:

  • 基本的序列协议: __len__ __getitem__
  • 正确表述拥有很多元素的实例;
  • 适当的切片支持,用于生成新的 Vector 实例;
  • 综合各个元素的值计算散列值;
  • 自定义的格式语言扩展。

本篇还将通过 __getattr__ 方法实现属性的动态存取(虽然序列类型通常不会这么做),以及穿插讨论一个概念:把协议当做正式接口。我们将说明协议和 鸭子类型 之间的关系,以及对自定义类型的影响。

2. 初版Vector

Vector 的构造方法将和所有内置序列类型一样,以可迭代对象为参数。如果其中元素过多, repr() 函数返回的字符串将会使用 ... 省略一部分内容,它的初始版本如下:

# 代码1
from array import array
import reprlib
import math

class Vector:
    typecode = "d"

    def __init__(self, components):  # 以可迭代对象为参数
        self._components = array(self.typecode, components)

    def __iter__(self):
        return iter(self._components)

    def __repr__(self):
        components = reprlib.repr(self._components)
        components = components[components.find("["):-1]
        return "Vector({})".format(components)

    def __str__(self):   # 和Vector2d相同
        return str(tuple(self))

    def __bytes__(self):
        return (bytes([ord(self.typecode)]) + bytes(self._components))

    def __eq__(self, other):   # 和Vector2d相同
        return tuple(self) == tuple(other)

    def __abs__(self):
        return math.sqrt(sum(x * x for x in self))

    def __bool__(self):   # 和Vector2d相同
        return bool(abs(self))

    @classmethod
    def frombytes(cls, octets):
        typecode = chr(octets[0])
        memv = memoryview(octets[1:]).cast(typecode)
        return cls(memv)   # 去掉了Vector2d中的星号*

之所以没有直接继承制 Vector2d ,既是因为这两个类的构造方法不兼容,也是因为我们要为 Vector 实现序列协议。

3. 协议和鸭子类型

协议和鸭子类型在之前的文章中也有所提及。在面向对象编程中, 协议是非正式的接口 ,只在文档中定义,在代码中不定义。

在Python中,只要实现了协议需要的某些方法,其实就算实现了协议,而 不一定需要继承 。比如只要实现了 __len__ __getitem__ 这两个方法,那么这个类就是满足序列协议的,而不需要从什么“序列基类”继承。

鸭子类型:和现实中相反,Python中确定一个东西是不是“鸭子”,不是测它的“DNA”是不是”鸭子“的DNA,而是看这东西像不像只鸭子。只要像”鸭子“,那它就是“鸭子”。比如,只要一个类实现了 __len__ __getitem__ 方法,那它就是序列类,而不必管它是从哪来的;文件类对象也常是鸭子类型。

4. 第2版Vector:支持切片

Vector 变为序列类型,并能正确返回切片:

# 代码2,将以下代码添加到初版Vector中
class Vector:
    -- snip --
    def __len__(self):
        return len(self._components)
    
    def __getitem__(self, index):
        cls = type(self)
        if isinstance(index, slice):  # 如果index是个切片类型,则构造新实例
            return cls(self._components[index])
        elif isinstance(index, numbers.Integral):  # 如果index是个数,则直接返回
            return self._components[index]
        else:
            msg = "{cls.__name__} indices must be integers"
            raise TypeError(msg.format(cls=cls))

如果 __getitem__ 函数直接返回切片: return self._components[index] ,那么得到的数据将是 array 类型,而不是 Vector 类型。正是为了使切片的类型正确,这里才做了类型判断。

上述代码中用到了 slice 类型,它是Python的内置类型,这里顺便补充一下 切片原理 ,直接上代码:

# 代码3
>>> class MySeq:
...     def __getitem__(self, index):
...         return index  # 直接返回传给它的值
...    
>>> s = MySeq()
>>> s[1]   
1  # 单索引,没啥新奇的
>>> s[1:3]
slice(1, 3, None)  # 返回来一个slice类型
>>> s[1:10:2]
slice(1, 10, 2)    # 注意slice类型的结构
>>> s[1:10:2, 9]
(slice(1, 10, 2), 9)   # 如果[]中有逗号,__getitem__收到的是元组
>>> s[1:10:2, 7:9]
(slice(1, 10, 2), slice(7, 9, None))

>>> dir(slice)  # 注意最后四个元素
['__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'






请到「今天看啥」查看全文