整理自小甲鱼鱼C论坛
定制容器与协议(protocol)
成功地实现容器定制,需要先了解一下协议(protocol)。Python中的协议与其他编程语言中的接口很相似,它规定哪些方法必须定义。然后,在Python中的协议就不那么正式。事实上,Python的协议更像是一种指南。
这有点像Python极力推崇的鸭子类型。
鸭子类型(duck typing)复习
鸭子类型(duck typing)是动态类型的一种风格。这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。
即关注的不是对象的类型本身,而是它是如何使用的。在使用鸭子类型的语言中,一个函数可以接受一个任意类型的对象,并调用它的方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的方法的对象,都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于不测试方法和函数中的参数的类型,而是依赖文档、清晰的代码和测试来确保正确的使用。
从静态类型语言转向动态类型语言的用户通常试图添加一些静态的(在运行之前的)类型检查,从而影响了鸭子类型的益处和可伸缩性,并约束了语言的动态特性。
代码示例,
class Duck: |
in_the_forest() 函数对参数duck只有一个要求:就是可以实现quack()和feathers()方。然而Duck类和Person类都实现了quack()和feathers()方法,因此它们的实例对象donald和john都可以用作in_the_forest()的参数。这就是鸭子类型。
我们可以看出,鸭子类型给予Python这样的动态语言以多态。但是这种多态的实现完全由程序员来约束强制实现(文档、清晰的代码和测试),并没有语言上的约束(如C++ 继承和虚函数)。因此这种方法即灵活,又提高了对程序员的要求。
与定制容器类型相关的魔法方法及含义
在Python中,像序列类型(如列表、元组、字符串)或映射类型(如字典)都属于容器类型。
下面讲一下与定制容器有关的一些协议:
- 如果希望定制的容器不可变,则只需要定义__len__()和__getitem__()方法。
- 如果希望地址的容器是可变的,除了__len__()和__getitem__()方法,还需要定义__setitem__()和__delitem__()两个方法。
下表列举了与定制容器有关的魔法方法及含义。
| 魔法方法 | 含义 |
|---|---|
| __len__(self) | 定义当被len()函数调用时的行为,即返回容器中元素的个数 |
| __getitem__(self, key) | 定义获取容器中指定元素时的行为,相当于self[key] |
| __setitem__(self, key, value) | 定义设置容器中指定元素的行为,相当于self[key]=value |
| __delitem__(self, key) | 定义删除容器中指定元素的行为,相当于del self[key] |
| __iter__(self) | 定义当迭代容器中的元素的行为 |
| __reversed__(self) | 定义当被reversed()函数调用时的行为 |
| __contains__(self, item) | 定义当使用成员测试运算符(in或not in)时的行为 |
实例,编写一个不可变的自定义列表,要求记录列表中的每个元素被访问的次数。
class CountList: |
练习
根据上面的例子,定制一个列表,同样要求记录列表中每个元素被访问的次数。并且支持append()、pop()、extand()原生列表所拥有的方法。
要求:
- 实现获取、设置和删除一个元素的行为(删除一个元素的时候对应的计数器也会被删除)
- 增加counter(index)方法,返回index参数所指定的元素记录的访问次数
- 实现append()、pop()、remove()、insert()、clear()和reverse()方法(重写这些方法时要注意考虑计数器的对应改变)
自己的代码如下,
class CountList: |
答案该出的代码类继承并严重依赖其父类(list)的行为,并按要求重写了一些方法,代码如下,
class CountList(list): |