《流畅的Python》学习笔记(10)—— 类的序列协议与切片

1. Vector类要求

  • Vector类的第1版和之前的Vector2d类兼容
  • 提供切片支持
  • 序列协议—— __len__和__getitem__两个方法
  • 自定义的格式语言扩展

2. Vector类的实现

这里我们重点实现的功能为:

从类中,任意抽取其中若干元素,可以实现类似列表的功能。

2.1 Vector类的第一版

如果要实现序列协议,时需要实现__len__和__getitem__协议即可

class Vecotr:
    
    def __len__(self):
        return len(self._components)
    
    def __getitem__(self, index):
        return self._components[index]

这样一来,就可以实现了列表的全部功能,连切片都可以支持,但是存在一个缺陷:

Vector实例的切片并不是Vector类,而是list类

这种缺陷使得切片后的元素不能够实现Vector类内置的功能,也是的定义上不统一,因为向量的部分也应该是向量。那么该如何修改?所以继续观察切片的原理。

Python的协议
在面向对象编程中,协议是非正式的接口,只在文档中定义,在代码中不定义。例如,Python 的序列协议只需要 __len __ 和 __getitem __ 两个方法。

任何类(如 Spam),只要使用 标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。 Spam 是不是哪个类的子类无关紧要,只要提供了所需的方法即可。

2.2 第二版与切片的原理

用一个例子来看,切片到底是怎么完成?首先需要观察,当我们切片时,给__getitem__函数传入了什么参数。

class MySeq:
    def __getitem__(self, index):
        return index
>>> s = MySeq()
>>> s[1] # ➋
1
>>> s[1:4] # ➌
slice(1, 4, None)
>>> s[1:4:2] # ➍
slice(1, 4, 2)
>>> s[1:4:2, 9] # ➎
(slice(1, 4, 2), 9)
>>> s[1:4:2, 7:9] # ➏
(slice(1, 4, 2), slice(7, 9, None))

从案例中可以看到,切片如数输入的类型时个slice类,slice系Python的内置类。内省slice类中,可以看到一个非常有用的方法:S.indices(len) -> (start, stop, stride),这个方法可把indices转成标准形式。基于此,我们可以改造我们的__getitem__方法。

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'
	raise TypeError(msg.format(cls=cls))

➊ 获取实例所属的类(即 Vector),供后面使用。
➋ 如果 index 参数的值是 slice 对象……
➌ ……调用类的构造方法,使用 _components 数组的切片构建一个新 Vector 实例。
➍ 如果 index 是 int 或其他整数类型……3
➎ ……那就返回 _components 中相应的元素。
➏ 否则,抛出异常。

这样就可以实现切片的功能,其实在此基础上,还可以实现一些numpy中的一些特性。

2.3 第三版与动态存储特性

Vector2d 变成 Vector 之后,就没办法通过名称访问向量的分量了(如 v.x 和 v.y)。现在我们处理的向量可能有大量分量。不过,若能通过单个字母访问前几个分量的话会比较方便。比如,用 x、 y 和 z 代替 v[0]、 v[1] 和 v[2]。

在 Vector2d 中,我们使用 @property 装饰器把 x 和 y 标记为只读特性。我们可以在 Vector 中编写四个特性,但这样太麻烦。特殊方法 __getattr__提供了更好的方式。

因为Python在查找属性失败后,解释器会调用__getattr__方法。简单来说,对 my_obj.x 表达式, Python会检查 my_obj 实例有没有名为 x 的属性;如果没有到在类( my_obj.class)中查找;如果还没有,顺着继承树继续查找。

对于此,我们将之前类的代码增加方法

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))

但是如此写程序,只是实现了查看,但是并没有实现修改,所以需要再次实现__setattr__()方法

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 attributes '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)

➊ 特别处理名称是单个字符的属性。
➋ 如果 name 是 xyzt 中的一个,设置特殊的错误消息。
➌ 如果 name 是小写字母,为所有小写字母设置一个错误消息。
➍ 否则,把错误消息设为空字符串。
➎ 如果有错误消息,抛出 AttributeError。
➏ 默认情况:在超类上调用__setattr__ 方法,提供标准行为。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值