8.14 实现自定义容器

问题

你想实现一个自定义的类来模拟内置的容器类功能,比如列表和字典。但是你不确定到底要实现哪些方法。

解决方案

collections 定义了很多抽象基类,当你想自定义容器类的时候它们会非常有用。比如你想让你的类支持迭代,那就让你的类继承 collections.Iterable 即可:

  1. import collections
  2. class A(collections.Iterable):
  3. pass

不过你需要实现 collections.Iterable 所有的抽象方法,否则会报错:

  1. >>> a = A()
  2. Traceback (most recent call last):
  3. File "<stdin>", line 1, in <module>
  4. TypeError: Can't instantiate abstract class A with abstract methods __iter__
  5. >>>

你只要实现 iter() 方法就不会报错了(参考4.2和4.7小节)。

你可以先试着去实例化一个对象,在错误提示中可以找到需要实现哪些方法:

  1. >>> import collections
  2. >>> collections.Sequence()
  3. Traceback (most recent call last):
  4. File "<stdin>", line 1, in <module>
  5. TypeError: Can't instantiate abstract class Sequence with abstract methods \
  6. __getitem__, __len__
  7. >>>

下面是一个简单的示例,继承自上面Sequence抽象类,并且实现元素按照顺序存储:

  1. class SortedItems(collections.Sequence):
  2. def __init__(self, initial=None):
  3. self._items = sorted(initial) if initial is not None else []
  4.  
  5. # Required sequence methods
  6. def __getitem__(self, index):
  7. return self._items[index]
  8.  
  9. def __len__(self):
  10. return len(self._items)
  11.  
  12. # Method for adding an item in the right location
  13. def add(self, item):
  14. bisect.insort(self._items, item)
  15.  
  16.  
  17. items = SortedItems([5, 1, 3])
  18. print(list(items))
  19. print(items[0], items[-1])
  20. items.add(2)
  21. print(list(items))

可以看到,SortedItems跟普通的序列没什么两样,支持所有常用操作,包括索引、迭代、包含判断,甚至是切片操作。

这里面使用到了 bisect 模块,它是一个在排序列表中插入元素的高效方式。可以保证元素插入后还保持顺序。

讨论

使用 collections 中的抽象基类可以确保你自定义的容器实现了所有必要的方法。并且还能简化类型检查。你的自定义容器会满足大部分类型检查需要,如下所示:

  1. >>> items = SortedItems()
  2. >>> import collections
  3. >>> isinstance(items, collections.Iterable)
  4. True
  5. >>> isinstance(items, collections.Sequence)
  6. True
  7. >>> isinstance(items, collections.Container)
  8. True
  9. >>> isinstance(items, collections.Sized)
  10. True
  11. >>> isinstance(items, collections.Mapping)
  12. False
  13. >>>

collections 中很多抽象类会为一些常见容器操作提供默认的实现,这样一来你只需要实现那些你最感兴趣的方法即可。假设你的类继承自 collections.MutableSequence ,如下:

  1. class Items(collections.MutableSequence):
  2. def __init__(self, initial=None):
  3. self._items = list(initial) if initial is not None else []
  4.  
  5. # Required sequence methods
  6. def __getitem__(self, index):
  7. print('Getting:', index)
  8. return self._items[index]
  9.  
  10. def __setitem__(self, index, value):
  11. print('Setting:', index, value)
  12. self._items[index] = value
  13.  
  14. def __delitem__(self, index):
  15. print('Deleting:', index)
  16. del self._items[index]
  17.  
  18. def insert(self, index, value):
  19. print('Inserting:', index, value)
  20. self._items.insert(index, value)
  21.  
  22. def __len__(self):
  23. print('Len')
  24. return len(self._items)

如果你创建 Items 的实例,你会发现它支持几乎所有的核心列表方法(如append()、remove()、count()等)。下面是使用演示:

  1. >>> a = Items([1, 2, 3])
  2. >>> len(a)
  3. Len
  4. 3
  5. >>> a.append(4)
  6. Len
  7. Inserting: 3 4
  8. >>> a.append(2)
  9. Len
  10. Inserting: 4 2
  11. >>> a.count(2)
  12. Getting: 0
  13. Getting: 1
  14. Getting: 2
  15. Getting: 3
  16. Getting: 4
  17. Getting: 5
  18. 2
  19. >>> a.remove(3)
  20. Getting: 0
  21. Getting: 1
  22. Getting: 2
  23. Deleting: 2
  24. >>>

本小节只是对Python抽象类功能的抛砖引玉。numbers 模块提供了一个类似的跟整数类型相关的抽象类型集合。可以参考8.12小节来构造更多自定义抽象基类。

原文:

http://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p14_implementing_custom_containers.html