我尝试使用抽象基类MutableMapping在Python中实现映射,但是我在实例化时遇到了错误。我将如何使用抽象基类来制作一个能够以尽可能多的方式模拟内置dict
类的字典的工作版本?
>>> 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__
一个好的答案将演示如何使这项工作,特别是没有子类化dict
(a concept that I am quite familiar with)。
答案 0 :(得分:18)
如何使用抽象基类实现dict?
一个好的答案将演示如何使这项工作,特别是 没有子类化dict。
以下是错误消息:TypeError: Can't instantiate abstract class D with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__
事实证明,必须实现它们才能使用抽象基类(ABC),MutableMapping
。
因此,我实现了一个映射,它在大多数方面都像dict一样,使用对象的属性引用dict进行映射。 (委托与继承不同,所以我们只委托给实例__dict__
,我们可以使用任何其他ad-hoc映射,但你似乎并不关心实现的那一部分。在Python 2中这样做是有道理的,因为MutableMapping在Python 2中没有__slots__
,所以你要么以任何方式创建__dict__
。在Python 3中,你可以完全避免使用dicts设置__slots__
。)
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__)
并演示用法:
>>> 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,我们吸取的教训是,您始终可以检查collections.MutableMapping
:
>>> isinstance(d, collections.MutableMapping)
True
>>> isinstance(dict(), collections.MutableMapping)
True
虽然由于在集合导入时注册了dict总是一个MutableMapping的实例,但反过来并不总是如此:
>>> isinstance(d, dict)
False
>>> isinstance(d, (dict, collections.MutableMapping))
True
执行此练习后,我很清楚使用Abstract Base Classes只为该类用户提供标准API的保证。在这种情况下,假定MutableMapping对象的用户将被保证为Python的标准API。
未实现fromkeys
类构造函数方法。
>>> 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'
可以屏蔽内置字典方法,例如get
或setdefault
>>> d['get'] = 'baz'
>>> d.get('get')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
再次取消屏蔽非常简单:
>>> del d['get']
>>> d.get('get', 'Not there, but working')
'Not there, but working'
但我不会在制作中使用这段代码。
没有词典的演示,Python 3:
>>> 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 :(得分:1)
至少
您需要在子类中实现从MutableMapping继承的所有抽象方法
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,红黑)以及构建词典的方法
答案 2 :(得分:1)
在没有实际使用dict
的情况下演示此问题的最佳方法可能是实现一些简单的,与dict
非常不同的东西,而不是完全没用。就像固定大小bytes
到相同固定大小bytes
的固定大小的映射一样。 (你可以将它用于例如路由表 - 它比解包值的dict
映射解包密钥要紧凑得多,尽管显然是以速度和灵活性为代价。)
哈希表只是(hash, key, value)
元组的数组。由于整个过程就是打包数据,我们将其填入struct
,这意味着我们可以使用大bytearray
来存储。要将广告位标记为空,我们将其哈希值设置为0
- 这意味着我们需要通过将其转换为0
来“逃避”任何真实的1
,这是愚蠢的,但更简单编码。为简单起见,我们还将使用最愚蠢的probe
算法。
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
如错误消息所示,您需要提供抽象方法__delitem__
,__getitem__
,__iter__
,__len__
和__setitem__
的实现。但是,一个更好看的地方是the docs,它会告诉你如果你实现这五个方法(加上基类所需的任何其他方法,但正如你从表中看到的那样没有),你将免费获得所有其他方法。您可能无法获得所有这些实现中最有效的实现,但我们将回过头来看。
首先,让我们处理__len__
。通常人们期望这是O(1),这意味着我们需要独立跟踪它,根据需要更新它。所以:
class FixedDict(collections.abc.MutableMapping):
def __init__(self, elementsize, size):
self.hashtable = FixedHashTable(elementsize, size)
self.len = 0
现在,__getitem__
只是探测,直到找到所需的键或到达结尾:
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
并且__delitem__
执行相同的操作,但如果找到则会清空插槽,并更新len
。
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)
__setitem__
有点棘手 - 如果找到,我们必须替换插槽中的值;如果没有,我们必须填补空位。在这里,我们必须处理哈希表可能已满的事实。当然,我们必须照顾len
:
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')
离开__iter__
。就像dict
一样,我们没有任何特定的顺序,所以我们可以迭代哈希表槽并产生所有非空的:
def __iter__(self):
return (k for (h, k, v) in self.hashtable.fetch(i)
for i in range(self.hashtable.size) if h)
虽然我们正在努力,但我们不妨写一个__repr__
。请注意,我们可以使用免费获得items
的事实:
def __repr__(self):
return '{}({})'.format(type(self).__name__, dict(self.items()))
但请注意,默认items
只会创建ItemsView(self)
,如果您通过源跟踪,则会看到它会迭代self
并查找每个值。如果表现很重要,你显然可以做得更好:
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)
同样适用于values
,也可能适用于其他方法。
答案 3 :(得分:-1)
抽象基类的整个想法是它有一些成员(C ++术语中的纯虚拟成员),你的代码必须提供 - 在C ++中,这些是纯虚拟成员和你的其他虚拟成员可能覆盖。
Python与C ++的不同之处在于,所有类的所有成员都是虚拟的,可以被覆盖,(并且可以向所有类和实例添加成员),但是抽象基类有一些必需的缺失类,这些类与C ++纯有效。虚拟完成这项工作之后,您只需提供缺少的成员即可创建派生类的实例。
有关您尝试做的事情的示例,请参阅接受的答案here,但不要在课程中使用dict,而是必须提供自己提供的方法。
答案 4 :(得分:-2)
以MutableMapping
作为基类,您应该自己在班级中创建此方法:__delitem__, __getitem__, __iter__, __len__, __setitem__
。
要创建自定义dict类,您可以从dict派生它:
>>> class D(dict):
... pass
...
>>> d = D()
>>> d
{}