How would I implement a dict with Abstract Base Classes in Python?
我试图使用抽象基类mutablemapping在python中实现映射,但在实例化时出错。我该如何制作一个有效版本的字典,以尽可能多的方式来模拟内置的
1 2 3 4 5 6 7 | >>> class D(collections.MutableMapping): ... pass ... >>> d = D() Traceback (most recent call last): File"<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class D with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__ |
一个很好的答案将演示如何使其工作,特别是在不将
How would I implement a dict with Abstract Base Classes?
A good answer will demonstrate how to make this work, specifically
without subclassing dict.
这是错误信息:
事实证明,必须实现它们才能使用抽象基类(abc),
因此,我实现了一个映射,它在大多数方面像dict一样工作,使用对象的属性引用dict进行映射。(委托与继承不同,因此我们只委托给实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import collections class D(collections.MutableMapping): ''' Mapping that works like both a dict and a mutable object, i.e. d = D(foo='bar') and d.foo returns 'bar' ''' # ``__init__`` method required to create instance from class. def __init__(self, *args, **kwargs): '''Use the object dict''' self.__dict__.update(*args, **kwargs) # The next five methods are requirements of the ABC. def __setitem__(self, key, value): self.__dict__[key] = value def __getitem__(self, key): return self.__dict__[key] def __delitem__(self, key): del self.__dict__[key] def __iter__(self): return iter(self.__dict__) def __len__(self): return len(self.__dict__) # The final two methods aren't required, but nice for demo purposes: def __str__(self): '''returns simple dict representation of the mapping''' return str(self.__dict__) def __repr__(self): '''echoes class, id, & reproducible representation in the REPL''' return '{}, D({})'.format(super(D, self).__repr__(), self.__dict__) |
示范
并演示用法:
1 2 3 4 5 6 7 8 9 10 11 | >>> d = D((e, i) for i, e in enumerate('abc')) >>> d <__main__.D object at 0x7f75eb242e50>, D({'b': 1, 'c': 2, 'a': 0}) >>> d.a 0 >>> d.get('b') 1 >>> d.setdefault('d', []).append(3) >>> d.foo = 'bar' >>> print(d) {'b': 1, 'c': 2, 'a': 0, 'foo': 'bar', 'd': [3]} |
为了确保dict api,经验教训是您可以始终检查
1 2 3 4 | >>> isinstance(d, collections.MutableMapping) True >>> isinstance(dict(), collections.MutableMapping) True |
虽然由于集合导入上的注册,dict始终是可变映射的实例,但反过来并不总是正确的:
1 2 3 4 | >>> isinstance(d, dict) False >>> isinstance(d, (dict, collections.MutableMapping)) True |
在执行了这个练习之后,我很清楚使用抽象基类只能为类的用户提供标准API的保证。在这种情况下,假设一个可变映射对象的用户将被保证是Python的标准API。
Caveats:未实现
1 2 3 4 5 6 | >>> dict.fromkeys('abc') {'b': None, 'c': None, 'a': None} >>> D.fromkeys('abc') Traceback (most recent call last): File"<stdin>", line 1, in <module> AttributeError: type object 'D' has no attribute 'fromkeys' |
我们可以屏蔽诸如
1 2 3 4 5 | >>> d['get'] = 'baz' >>> d.get('get') Traceback (most recent call last): File"<stdin>", line 1, in <module> TypeError: 'str' object is not callable |
很容易再次取消屏蔽:
1 2 3 | >>> del d['get'] >>> d.get('get', 'Not there, but working') 'Not there, but working' |
但我不会在生产中使用此代码。
没有dict的演示,python 3:
1 2 3 4 5 6 7 8 | >>> class MM(MutableMapping): ... __delitem__, __getitem__, __iter__, __len__, __setitem__ = (None,) *5 ... __slots__ = () ... >>> MM().__dict__ Traceback (most recent call last): File"<stdin>", line 1, in <module> AttributeError: 'MM' object has no attribute '__dict__' |
在没有实际使用
哈希表只是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class FixedHashTable(object): hashsize = 8 def __init__(self, elementsize, size): self.elementsize = elementsize self.size = size self.entrysize = self.hashsize + self.elementsize * 2 self.format = 'q{}s{}s'.format(self.elementsize, self.elementsize) assert struct.calcsize(self.format) == self.entrysize self.zero = b'\0' * self.elementsize self.store = bytearray(struct.pack(self.format, 0, self.zero, self.zero) ) * self.size def hash(self, k): return hash(k) or 1 def stash(self, i, h, k, v): entry = struct.pack(self.format, h, k, v) self.store[i*self.entrysize:(i+1)*self.entrysize] = entry def fetch(self, i): entry = self.store[i*self.entrysize:(i+1)*self.entrysize] return struct.unpack(self.format, entry) def probe(self, keyhash): i = keyhash % self.size while True: h, k, v = self.fetch(i) yield i, h, k, v i = (i + 1) % self.size if i == keyhash % self.size: break |
正如错误消息所说,您需要提供抽象方法
首先,我们来处理一下
1 2 3 4 | class FixedDict(collections.abc.MutableMapping): def __init__(self, elementsize, size): self.hashtable = FixedHashTable(elementsize, size) self.len = 0 |
现在,
1 2 3 4 5 | def __getitem__(self, key): keyhash = self.hashtable.hash(key) for i, h, k, v in self.hashtable.probe(keyhash): if h and k == key: return v |
而
1 2 3 4 5 6 7 8 | def __delitem__(self, key): keyhash = self.hashtable.hash(key) for i, h, k, v in self.hashtable.probe(keyhash): if h and k == key: self.hashtable.stash(i, 0, self.hashtable.zero, self.hashtable.zero) self.len -= 1 return raise KeyError(key) |
1 2 3 4 5 6 7 8 9 | def __setitem__(self, key, value): keyhash = self.hashtable.hash(key) for i, h, k, v in self.hashtable.probe(keyhash): if not h or k == key: if not h: self.len += 1 self.hashtable.stash(i, keyhash, key, value) return raise ValueError('hash table full') |
这就剩下了
1 2 3 | def __iter__(self): return (k for (h, k, v) in self.hashtable.fetch(i) for i in range(self.hashtable.size) if h) |
当我们这样做的时候,我们也可以写一个
1 2 | def __repr__(self): return '{}({})'.format(type(self).__name__, dict(self.items())) |
但是,请注意,默认的
1 2 3 4 | def items(self): pairs = ((k, v) for (h, k, v) in self.hashtable.fetch(i) for i in range(self.hashtable.size) if h) return collections.abc.ItemsView._from_iterable(pairs) |
同样适用于
至少
需要在子类中实现从可变映射继承的所有抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class D(MutableMapping): def __delitem__(self): ''' Your Implementation for deleting the Item goes here ''' raise NotImplementedError("del needs to be implemented") def __getitem__(self): ''' Your Implementation for subscripting the Item goes here ''' raise NotImplementedError("obj[index] needs to be implemented") def __iter__(self): ''' Your Implementation for iterating the dictionary goes here ''' raise NotImplementedError("Iterating the collection needs to be implemented") def __len__(self): ''' Your Implementation for determing the size goes here ''' raise NotImplementedError("len(obj) determination needs to be implemented") def __setitem__(self): ''' Your Implementation for determing the size goes here ''' raise NotImplementedError("obj[index] = item, needs to be implemented") >>> D() <__main__.D object at 0x0258CD50> |
此外
您需要提供一个数据结构来存储映射(哈希、AVL、红黑),以及一种构造字典的方法。
抽象基类的全部思想是它有一些成员,(C++中的纯虚拟成员),你的代码必须提供——C++,这些是纯虚拟成员和其他可以重写的虚拟成员。
Python与C++的不同之处在于,所有类的所有成员都是虚拟的,并且可以被重写,(并且可以向所有类和实例添加成员),但是抽象基类具有一些需要的缺失类,这些类是C++纯虚拟的等价物。
如果不这样做,您只需提供缺少的成员就可以创建派生类的实例。
举一个例子,你试图在这里看到一个被接受的答案,而不是在课堂上使用听写,你必须提供它提供给你自己的方法。
使用
要创建自定义dict类,可以从dict派生它:
1 2 3 4 5 6 | >>> class D(dict): ... pass ... >>> d = D() >>> d {} |